#!/usr/bin/env python3 """Install or stage redMCP for another agent's MCP client.""" import argparse import json import os import shutil import stat import subprocess import sys from pathlib import Path DEFAULT_INSTALL_DIR = Path.home() / ".local" / "share" / "redmcp" def main(): parser = argparse.ArgumentParser(description="Install redMCP and print MCP client configuration.") parser.add_argument("--source-redmcp", type=Path, default=repo_root() / "redMCP") parser.add_argument("--install-dir", type=Path, default=DEFAULT_INSTALL_DIR) parser.add_argument("--redmine-url", required=True) parser.add_argument("--redmine-api-key", required=True) parser.add_argument("--transport", choices=["stdio", "http"], default="stdio") parser.add_argument("--mcp-server-token", default="") parser.add_argument("--host", default="127.0.0.1") parser.add_argument("--port", type=int, default=8765) parser.add_argument("--apply", action="store_true", help="Copy files and write .env. Default is dry-run.") args = parser.parse_args() if args.transport == "http" and not args.mcp_server_token: print("error: --mcp-server-token is required for --transport http", file=sys.stderr) return 2 if not args.source_redmcp.is_dir(): print("error: source redMCP directory not found: {0}".format(args.source_redmcp), file=sys.stderr) return 1 print("mode={0}".format("apply" if args.apply else "dry-run")) print("source_redmcp={0}".format(args.source_redmcp)) print("install_dir={0}".format(args.install_dir)) if args.apply: copy_redmcp(args.source_redmcp, args.install_dir) write_env(args.install_dir / ".env", args) ensure_executable(args.install_dir / "bin" / "redmcp-server.php") ensure_executable(args.install_dir / "bin" / "redmcp-http-server.php") else: print("would copy redMCP files") print("would write {0} with REDMINE_URL and REDMINE_API_KEY".format(args.install_dir / ".env")) print_runtime_notes(args) print_mcp_config(args) return 0 def repo_root(): return Path(__file__).resolve().parents[3] def copy_redmcp(source, target): if target.exists(): shutil.rmtree(str(target)) ignore = shutil.ignore_patterns(".env", ".cache", "__pycache__", "*.pyc", "*.log") shutil.copytree(str(source), str(target), ignore=ignore) def write_env(path, args): lines = [ "REDMINE_URL={0}".format(args.redmine_url.rstrip("/")), "REDMINE_API_KEY={0}".format(args.redmine_api_key), ] if args.transport == "http": lines.append("MCP_SERVER_TOKEN={0}".format(args.mcp_server_token)) path.write_text("\n".join(lines) + "\n") os.chmod(str(path), 0o600) def ensure_executable(path): if path.exists(): mode = path.stat().st_mode path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) def print_runtime_notes(args): composer = shutil.which("composer") vendor = args.install_dir / "vendor" / "autoload.php" if args.apply and not vendor.exists(): if composer: print("vendor/autoload.php missing; run: cd {0} && composer install".format(args.install_dir)) else: print("vendor/autoload.php missing and composer was not found on PATH") elif not args.apply: print("after apply, ensure vendor/autoload.php exists or run composer install in the install dir") def print_mcp_config(args): if args.transport == "stdio": config = { "mcpServers": { "redmcp": { "command": str(args.install_dir / "bin" / "redmcp-server.php") } } } print(json.dumps(config, indent=2)) return command = "{0} --host {1} --port {2}".format( args.install_dir / "bin" / "redmcp-http-server.php", args.host, args.port, ) print("start_http_server={0}".format(command)) print("mcp_url=http://{0}:{1}/mcp".format(args.host, args.port)) print("authorization=Bearer ") if __name__ == "__main__": raise SystemExit(main())