120 lines
4.1 KiB
Python
Executable File
120 lines
4.1 KiB
Python
Executable File
#!/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 <MCP_SERVER_TOKEN>")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|