Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
30 changes: 29 additions & 1 deletion opencode.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
182 changes: 166 additions & 16 deletions scripts/mcp_command.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -30,30 +40,134 @@ def status_line(entry: dict) -> str:

def usage() -> int:
print(
"usage: /mcp status | /mcp enable <context7|gh_grep|all> | /mcp disable <context7|gh_grep|all>"
"usage: /mcp status | /mcp help | /mcp doctor [--json] | /mcp profile <minimal|research|context7|ghgrep> | /mcp enable <context7|gh_grep|all> | /mcp disable <context7|gh_grep|all>"
)
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()
Expand All @@ -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:
Expand All @@ -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:]))
Expand Down