diff --git a/README.md b/README.md index 646f6513..264d3ce1 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,13 @@ Use these directly in OpenCode: ```text /mcp status +/mcp help +/mcp doctor +/mcp doctor --json +/mcp profile minimal +/mcp profile research +/mcp profile context7 +/mcp profile ghgrep /mcp enable context7 /mcp disable context7 /mcp enable gh_grep @@ -82,6 +89,18 @@ Use these directly in OpenCode: /mcp disable all ``` +MCP autocomplete-friendly shortcuts: + +```text +/mcp-help +/mcp-doctor +/mcp-doctor-json +/mcp-profile-minimal +/mcp-profile-research +/mcp-profile-context7 +/mcp-profile-ghgrep +``` + ## Plugin control inside OpenCode 🎛️ Use these directly in OpenCode: diff --git a/install.sh b/install.sh index 52b2fae4..9b603022 100755 --- a/install.sh +++ b/install.sh @@ -81,6 +81,8 @@ printf "\nDone! ✅\n" printf "Config linked: %s -> %s\n" "$CONFIG_PATH" "$INSTALL_DIR/opencode.json" printf "\nOpen OpenCode and use:\n" printf " /mcp status\n" +printf " /mcp help\n" +printf " /mcp doctor\n" printf " /mcp enable context7\n" printf " /mcp disable context7\n" printf " /plugin status\n" diff --git a/opencode.json b/opencode.json index 332c9c12..8f042550 100644 --- a/opencode.json +++ b/opencode.json @@ -27,9 +27,37 @@ }, "command": { "mcp": { - "description": "Manage MCP usage (status|enable|disable)", + "description": "Manage MCP usage (status|help|doctor|profile|enable|disable)", "template": "!`python3 \"$HOME/.config/opencode/my_opencode/scripts/mcp_command.py\" $ARGUMENTS`\nShow only the command output." }, + "mcp-help": { + "description": "Show MCP command usage and examples", + "template": "!`python3 \"$HOME/.config/opencode/my_opencode/scripts/mcp_command.py\" help`\nShow only the command output." + }, + "mcp-doctor": { + "description": "Run MCP diagnostics", + "template": "!`python3 \"$HOME/.config/opencode/my_opencode/scripts/mcp_command.py\" doctor`\nShow only the command output." + }, + "mcp-doctor-json": { + "description": "Run MCP diagnostics in JSON", + "template": "!`python3 \"$HOME/.config/opencode/my_opencode/scripts/mcp_command.py\" doctor --json`\nShow only the command output." + }, + "mcp-profile-minimal": { + "description": "Disable all MCP servers", + "template": "!`python3 \"$HOME/.config/opencode/my_opencode/scripts/mcp_command.py\" profile minimal`\nShow only the command output." + }, + "mcp-profile-research": { + "description": "Enable both MCP servers", + "template": "!`python3 \"$HOME/.config/opencode/my_opencode/scripts/mcp_command.py\" profile research`\nShow only the command output." + }, + "mcp-profile-context7": { + "description": "Enable only context7 MCP", + "template": "!`python3 \"$HOME/.config/opencode/my_opencode/scripts/mcp_command.py\" profile context7`\nShow only the command output." + }, + "mcp-profile-ghgrep": { + "description": "Enable only gh_grep MCP", + "template": "!`python3 \"$HOME/.config/opencode/my_opencode/scripts/mcp_command.py\" profile ghgrep`\nShow only the command output." + }, "plugin": { "description": "Manage plugin usage (status|doctor|setup-keys|profile|enable|disable)", "template": "!`python3 \"$HOME/.config/opencode/my_opencode/scripts/plugin_command.py\" $ARGUMENTS`\nShow only the command output." diff --git a/scripts/mcp_command.py b/scripts/mcp_command.py index 3278cf3d..bb10e3f7 100755 --- a/scripts/mcp_command.py +++ b/scripts/mcp_command.py @@ -1,12 +1,22 @@ #!/usr/bin/env python3 import json +import os +import re import sys from pathlib import Path -CONFIG_PATH = Path("~/.config/opencode/opencode.json").expanduser() +CONFIG_PATH = Path( + os.environ.get("OPENCODE_CONFIG_PATH", "~/.config/opencode/opencode.json") +).expanduser() SUPPORTED = ("context7", "gh_grep") +PROFILE_MAP = { + "minimal": [], + "research": ["context7", "gh_grep"], + "context7": ["context7"], + "ghgrep": ["gh_grep"], +} def load_config() -> dict: @@ -30,30 +40,134 @@ def status_line(entry: dict) -> str: def usage() -> int: print( - "usage: /mcp status | /mcp enable | /mcp disable " + "usage: /mcp status | /mcp help | /mcp doctor [--json] | /mcp profile | /mcp enable | /mcp disable " ) return 2 -def main(argv: list[str]) -> int: - data = load_config() - mcp = data.setdefault("mcp", {}) +def print_next_steps() -> None: + print("\nnext:") + print("- /mcp enable context7") + print("- /mcp enable gh_grep") + print("- /mcp disable all") + print("- /mcp profile minimal|research|context7|ghgrep") + print("- /mcp doctor") - if not argv or argv[0] == "status": - for name in SUPPORTED: - entry = mcp.get(name, {}) if isinstance(mcp.get(name), dict) else {} - print(f"{name}: {status_line(entry)}") - print(f"config: {CONFIG_PATH}") - return 0 - if len(argv) < 2: - return usage() +def print_status(mcp: dict) -> None: + for name in SUPPORTED: + entry = mcp.get(name, {}) if isinstance(mcp.get(name), dict) else {} + state = status_line(entry) + url = entry.get("url", "") if isinstance(entry.get("url", ""), str) else "" + print(f"{name}: {state}" + (f" ({url})" if url else "")) + print(f"config: {CONFIG_PATH}") - action = argv[0] - target = argv[1] - if action not in ("enable", "disable"): + +def collect_doctor(mcp: dict) -> dict: + problems: list[str] = [] + warnings: list[str] = [] + servers: dict[str, dict[str, str]] = {} + + for name in SUPPORTED: + entry = mcp.get(name, {}) if isinstance(mcp.get(name), dict) else {} + url = entry.get("url", "") if isinstance(entry.get("url"), str) else "" + state = status_line(entry) + servers[name] = { + "status": state, + "url": url, + "configured": "true" if isinstance(mcp.get(name), dict) else "false", + } + + if not isinstance(mcp.get(name), dict): + problems.append(f"{name} server config missing in mcp block") + continue + + if not url: + problems.append(f"{name} url is missing") + elif not re.match(r"^https?://", url): + problems.append(f"{name} url is invalid: {url}") + + enabled_count = sum(1 for name in SUPPORTED if servers[name]["status"] == "enabled") + if enabled_count == 0: + warnings.append("all MCP servers are disabled") + + return { + "result": "PASS" if not problems else "FAIL", + "config": str(CONFIG_PATH), + "servers": servers, + "warnings": warnings, + "problems": problems, + "quick_fixes": [ + "run /mcp enable context7 or /mcp enable gh_grep", + "set missing URLs in ~/.config/opencode/opencode.json under mcp", + "use /mcp profile research for both context MCP servers", + ] + if problems or warnings + else [], + } + + +def print_doctor(mcp: dict, json_output: bool = False) -> int: + report = collect_doctor(mcp) + + if json_output: + print(json.dumps(report, indent=2)) + return 0 if report["result"] == "PASS" else 1 + + print("mcp doctor") + print("----------") + print(f"config: {report['config']}") + for name in SUPPORTED: + item = report["servers"][name] + print( + f"- {name}: {item['status']}" + (f" ({item['url']})" if item["url"] else "") + ) + + if report["warnings"]: + print("\nwarnings:") + for item in report["warnings"]: + print(f"- {item}") + + if report["problems"]: + print("\nproblems:") + for item in report["problems"]: + print(f"- {item}") + print("\nquick fixes:") + for item in report["quick_fixes"]: + print(f"- {item}") + print("\nresult: FAIL") + return 1 + + print("\nresult: PASS") + return 0 + + +def apply_profile(data: dict, mcp: dict, profile: str) -> int: + if profile not in PROFILE_MAP: return usage() + enable_set = set(PROFILE_MAP[profile]) + for name in SUPPORTED: + if not isinstance(mcp.get(name), dict): + mcp[name] = {} + mcp[name]["enabled"] = name in enable_set + + data["mcp"] = mcp + save_config(data) + + print(f"profile: {profile}") + print("enabled servers:") + if enable_set: + for name in SUPPORTED: + if name in enable_set: + print(f"- {name}") + else: + print("- none") + print(f"config: {CONFIG_PATH}") + return 0 + + +def set_enabled(data: dict, mcp: dict, action: str, target: str) -> int: targets = SUPPORTED if target == "all" else (target,) if any(name not in SUPPORTED for name in targets): return usage() @@ -64,6 +178,7 @@ def main(argv: list[str]) -> int: mcp[name] = {} mcp[name]["enabled"] = value + data["mcp"] = mcp save_config(data) state = "enabled" if value else "disabled" for name in targets: @@ -72,6 +187,41 @@ def main(argv: list[str]) -> int: return 0 +def main(argv: list[str]) -> int: + data = load_config() + mcp = data.setdefault("mcp", {}) + + if not argv or argv[0] == "status": + print_status(mcp) + print_next_steps() + return 0 + + if argv[0] == "help": + usage() + print_next_steps() + return 0 + + if argv[0] == "doctor": + json_output = len(argv) > 1 and argv[1] == "--json" + if len(argv) > 1 and not json_output: + return usage() + return print_doctor(mcp, json_output=json_output) + + if argv[0] == "profile": + if len(argv) < 2: + return usage() + return apply_profile(data, mcp, argv[1]) + + if len(argv) < 2: + return usage() + + action, target = argv[0], argv[1] + if action not in ("enable", "disable"): + return usage() + + return set_enabled(data, mcp, action, target) + + if __name__ == "__main__": try: raise SystemExit(main(sys.argv[1:]))