Skip to content

Commit 00a573f

Browse files
ralyodioclaude
andauthored
feat(mcp): remove, enable, disable, test, reauth, unauth, reconnect, resources, prompts, notifications (#543)
Brings `moshcode mcp` and the pit's `/mcp` to parity with omp's MCP surface, adapted to what a wrapper can honestly deliver. Both surfaces call the same `mcpCommand`, so every verb lands in the CLI and the pit at once. Ten new verbs, plus a wizard and a search. Fan-out verbs, each driving the engine's own native command and never editing an engine config file: remove claude/gemini/qwen/codex `mcp remove` enable re-register the kept spec disable deregister everywhere, keep the spec reauth claude/codex `mcp login`, opencode/privacycode `mcp auth` unauth the matching `mcp logout` reconnect gemini/qwen `mcp reconnect` (nobody else has one) Probe verbs, delegating to mcpjam, which TOOLS already installs and already describes as the companion to this command: test mcpjam server info resources mcpjam resources list prompts mcpjam prompts list notifications mcpjam server capabilities, or subscriptions listen `add` grows `--url`, `--token`, `--engine-scope`, `--engines`, and a wizard when it is called with no arguments. `catalog` grows `search`. `list` grows `--servers`. `help` renders the command's own help. Scope is two axes here, not one. `--engine-scope user|project` is the config file each engine writes; `--engines claude,codex` is which of the six engines get the server at all. Neither is spelled `--scope`, because that already means the OAuth permissions of a `/mcp answer` share, and a bare `--scope` on these verbs now fails pointing at the two that exist. New: `~/.moshcode/mcp.json`, moshcode's own record of what it registered, so a name has something to resolve against. It keeps header NAMES and the variable a token came from, never a value. `--token env:VAR` is the form that keeps a credential out of the shell history and out of this file. Every new verb satisfies the standard test/mcp-add-fanout.test.mjs sets: the plan covers every engine, and an engine that cannot do the thing says why. omp moves off the blanket "no MCP support" onto a stated reason: it has the richest MCP surface here and no scriptable `mcp` subcommand at all. Left out on purpose: `reload`, and the three Smithery-branded verbs. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent bbaf5ff commit 00a573f

12 files changed

Lines changed: 2033 additions & 74 deletions

‎README.md‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1679,6 +1679,68 @@ moshcode skill list --json
16791679
Each row reports `installed` and `supported` separately, so an installed engine
16801680
without that integration primitive remains visible rather than looking absent.
16811681

1682+
### Managing a registered server
1683+
1684+
Registering a server is a fan-out; so is everything you do to it afterwards.
1685+
1686+
```sh
1687+
moshcode mcp add # interactive wizard
1688+
moshcode mcp add sentry --url https://mcp.sentry.dev/mcp --token env:SENTRY_TOKEN
1689+
moshcode mcp add tools --engines claude,codex -- npx -y my-mcp-server
1690+
moshcode mcp remove sentry
1691+
moshcode mcp reauth sentry # each engine's own OAuth login
1692+
moshcode mcp unauth sentry
1693+
moshcode mcp reconnect --all
1694+
moshcode mcp list --servers # what moshcode registered
1695+
```
1696+
1697+
**Scope here is two axes, not one.** `--engine-scope user|project` picks the
1698+
config file each engine writes. `--engines claude,codex` picks which of the six
1699+
engines get the server at all. Both default to the widest useful answer: user
1700+
scope, every installed MCP-capable engine. `--scope` is deliberately neither of
1701+
them, because on `/mcp answer` it already means the OAuth permissions of a
1702+
shared session.
1703+
1704+
**`--token env:VAR` is the form worth using.** A literal token is in your shell
1705+
history before moshcode sees it and in every engine's config afterwards. The
1706+
`env:` form is read from the environment at registration time, and it is what
1707+
`~/.moshcode/mcp.json` records. That file keeps header *names* and the variable
1708+
a value came from, never a value.
1709+
1710+
**`disable` and `enable` are a round trip, not a live toggle.** No engine
1711+
moshcode drives has an enable or disable command, and moshcode does not edit
1712+
their config files. So `disable` takes the server out of every engine and keeps
1713+
its spec; `enable` registers exactly that spec again.
1714+
1715+
What a given engine cannot do is reported rather than faked. OpenCode has no
1716+
`mcp remove`. Codex has no project scope. Only the Gemini family has
1717+
`mcp reconnect`. Gemini and Qwen authorize from inside their own session. Kimi
1718+
and omp run MCP servers perfectly well and have no scriptable `mcp` subcommand
1719+
for moshcode to drive. Each of those prints the reason and what to type instead.
1720+
1721+
### Testing a server before you trust it
1722+
1723+
`test`, `resources`, `prompts` and `notifications` talk *to* a server rather
1724+
than about it, and they run through [mcpjam](#mcp-server-testing), which
1725+
moshcode already installs. They take a registered name, a catalog name, or a
1726+
bare URL. The last one matters, because "does this thing work" is a question
1727+
you ask before deciding to register it.
1728+
1729+
```sh
1730+
moshcode install mcpjam
1731+
moshcode mcp test https://mcp.sentry.dev/mcp
1732+
moshcode mcp resources sentry --json
1733+
moshcode mcp prompts sentry
1734+
moshcode mcp notifications sentry # what it declares
1735+
moshcode mcp notifications sentry --listen --for 30000
1736+
moshcode mcp catalog search postgres # the public MCP directories
1737+
```
1738+
1739+
`catalog search` is the generalized form of a registry search: it sweeps the
1740+
scraped MCP directories through mcpjam rather than adding a vendor-specific
1741+
verb, and the registry API key stays mcpjam's to hold rather than being copied
1742+
into a second place.
1743+
16821744
### Connect ChatGPT, Claude, or Chovy to this session
16831745

16841746
Start the interactive pit, then mint a short-lived remote MCP URL for its live

‎src/cli-schema.mjs‎

Lines changed: 146 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -401,9 +401,15 @@ export const CORE_CLI_COMMANDS = [
401401
synopsis: [["moshcode mcp <verb> [args…]", ""]],
402402
verbs: "MCP_VERBS",
403403
examples: [
404-
["moshcode mcp list --json", "support and install status"],
405-
["moshcode mcp install https://mcp.example.com", "a remote server"],
406-
],
404+
["moshcode mcp list --json", "registered servers + engine support"],
405+
["moshcode mcp install https://mcp.example.com", "a remote server, everywhere"],
406+
["moshcode mcp add tools --engines claude,codex -- npx -y srv", "two engines only"],
407+
["moshcode mcp test sentry", "does it actually answer?"],
408+
["moshcode mcp catalog search postgres", "the public MCP directories"],
409+
],
410+
note: "moshcode drives each engine's own `mcp` commands and never writes their config files, so a "
411+
+ "verb an engine lacks is skipped with the reason rather than faked. The verbs that talk TO a "
412+
+ "server run through mcpjam.",
407413
seeAlso: ["skill", "engines"],
408414
},
409415
{
@@ -1122,30 +1128,161 @@ export const MCP_VERBS = [
11221128
],
11231129
flags: [
11241130
["--name <n>", "override the derived server name", ""],
1131+
["--url <url>", "the remote server, spelled explicitly", ""],
11251132
["-t, --transport <t>", "http | sse | stdio", "inferred from the target"],
1133+
["--token <t|env:VAR>", "Authorization: Bearer …; env:VAR keeps it out of history", ""],
1134+
["--engine-scope <s>", "user | project: the config file each engine writes", "user"],
1135+
["--engines <a,b>", "only these engines", "every MCP-capable engine"],
11261136
["-e, --env K=V", "repeatable", ""],
11271137
["-H, --header 'K: V'", "repeatable", ""],
11281138
["--", "everything after this is the server's argv", ""],
11291139
],
1140+
note: "scope here is two axes, not one. --engine-scope picks the config file inside each engine; "
1141+
+ "--engines picks which of the six engines get the server at all. `--scope` is deliberately NOT "
1142+
+ "either of them: on `mcp answer` it already means the OAuth permissions of a shared session.",
11301143
},
11311144
{
11321145
name: "add",
1133-
description: "register a named MCP server",
1146+
description: "register a named MCP server, or run the wizard",
11341147
acceptsServerSpec: true,
1135-
synopsis: [["moshcode mcp add --name <n> <target>", ""]],
1148+
synopsis: [
1149+
["moshcode mcp add", "interactive wizard"],
1150+
["moshcode mcp add <name> --url <url> [-t http|sse]", "remote server"],
1151+
["moshcode mcp add <name> -- <cmd…>", "local stdio server"],
1152+
],
1153+
flags: [
1154+
["--url <url>", "the remote server, spelled explicitly", ""],
1155+
["-t, --transport <t>", "http | sse | stdio", "inferred from the target"],
1156+
["--token <t|env:VAR>", "Authorization: Bearer …; env:VAR keeps it out of history", ""],
1157+
["--engine-scope <s>", "user | project", "user"],
1158+
["--engines <a,b>", "only these engines", "every MCP-capable engine"],
1159+
],
11361160
},
11371161
{
11381162
name: "bridge",
11391163
description: "serve moshcode's verbs over MCP, on stdio",
11401164
synopsis: [["moshcode mcp bridge", "speaks MCP on stdin/stdout; register it with any engine"]],
11411165
},
1142-
{ name: "catalog", description: "show known MCP servers", synopsis: [["moshcode mcp catalog", ""]] },
11431166
{
1144-
name: "list",
1145-
description: "show MCP support and install status",
1146-
synopsis: [["moshcode mcp list [--json]", ""]],
1167+
name: "remove",
1168+
takesServerName: true,
1169+
description: "deregister a server from every engine",
1170+
synopsis: [["moshcode mcp remove <name> [--engine-scope user|project] [--engines a,b]", ""]],
1171+
note: "OpenCode and privacycode have no `mcp remove`; they are skipped with the file to edit. "
1172+
+ "Codex has no project scope.",
1173+
},
1174+
{
1175+
name: "enable",
1176+
takesServerName: true,
1177+
description: "re-register a server moshcode disabled",
1178+
synopsis: [["moshcode mcp enable <name>", ""]],
1179+
note: "no engine moshcode drives has an enable/disable verb, and moshcode will not edit their "
1180+
+ "config files to fake one. So disable deregisters the server everywhere and keeps its spec in "
1181+
+ "~/.moshcode/mcp.json, and enable registers exactly that spec again. A credential is never "
1182+
+ "kept, so a token-authenticated server needs --token again on the way back.",
1183+
},
1184+
{
1185+
name: "disable",
1186+
takesServerName: true,
1187+
description: "deregister a server, keeping its spec for enable",
1188+
synopsis: [["moshcode mcp disable <name>", ""]],
1189+
seeAlso: ["mcp"],
1190+
},
1191+
{
1192+
name: "test",
1193+
takesServerName: true,
1194+
description: "connect to a server and report what it serves",
1195+
synopsis: [["moshcode mcp test <name|url> [--json]", ""]],
1196+
flags: [["--json", "machine-readable", ""]],
1197+
note: "runs through mcpjam, which moshcode already installs (`/install mcpjam`). "
1198+
+ "Takes a registered name, a catalog name, or a bare URL you have not registered yet.",
1199+
},
1200+
{
1201+
name: "reauth",
1202+
takesServerName: true,
1203+
description: "run each engine's own OAuth login for a server",
1204+
synopsis: [["moshcode mcp reauth <name>", ""]],
1205+
note: "drives `claude mcp login`, `codex mcp login` and `opencode mcp auth` in turn. Each runs the "
1206+
+ "MCP spec's OAuth 2.1 flow (authorization code + PKCE) and keeps its own rotating refresh "
1207+
+ "token; moshcode mints nothing and stores nothing. Gemini and Qwen authorize from inside "
1208+
+ "the session, so they are skipped with the words to type there.",
1209+
},
1210+
{
1211+
name: "unauth",
1212+
takesServerName: true,
1213+
description: "clear a server's stored OAuth credentials",
1214+
synopsis: [["moshcode mcp unauth <name>", ""]],
1215+
},
1216+
{
1217+
name: "reconnect",
1218+
takesServerName: true,
1219+
description: "redial a server in the engines that can",
1220+
synopsis: [
1221+
["moshcode mcp reconnect <name>", ""],
1222+
["moshcode mcp reconnect --all", "every configured server"],
1223+
],
1224+
flags: [["-a, --all", "reconnect every server", ""]],
1225+
note: "only the Gemini family has `mcp reconnect`. Claude Code, Codex and OpenCode dial their "
1226+
+ "servers when a session starts, so they are skipped with what to do instead.",
1227+
},
1228+
{
1229+
name: "resources",
1230+
takesServerName: true,
1231+
description: "list the resources a server exposes",
1232+
synopsis: [["moshcode mcp resources <name|url> [--json]", ""]],
11471233
flags: [["--json", "machine-readable", ""]],
11481234
},
1235+
{
1236+
name: "prompts",
1237+
takesServerName: true,
1238+
description: "list the prompts a server exposes",
1239+
synopsis: [["moshcode mcp prompts <name|url> [--json]", ""]],
1240+
flags: [["--json", "machine-readable", ""]],
1241+
},
1242+
{
1243+
name: "notifications",
1244+
takesServerName: true,
1245+
description: "what a server can notify about, and watch it",
1246+
synopsis: [
1247+
["moshcode mcp notifications <name|url>", "the capabilities it declares"],
1248+
["moshcode mcp notifications <name|url> --listen --for 30000", "stream them"],
1249+
],
1250+
flags: [
1251+
["--listen", "stream notifications instead of reading capabilities", ""],
1252+
["--for <ms>", "stop listening after this long", "until Ctrl-C"],
1253+
["--json", "machine-readable", ""],
1254+
],
1255+
},
1256+
{
1257+
name: "catalog",
1258+
description: "show known MCP servers, or search the public directories",
1259+
synopsis: [
1260+
["moshcode mcp catalog", "moshcode's own curated list"],
1261+
["moshcode mcp catalog search <keyword> [--limit 1-100]", "the public MCP directories"],
1262+
],
1263+
flags: [
1264+
["--limit <1-100>", "results per page", ""],
1265+
["--source <id>", "one directory instead of all of them", "all"],
1266+
["--json", "machine-readable", ""],
1267+
],
1268+
note: "search runs through mcpjam's registry, which sweeps the scraped MCP directories "
1269+
+ "(Smithery among them). There is no Smithery-specific verb and no second API key here on "
1270+
+ "purpose: the key is mcpjam's, mcpjam already stores it, and a copy in moshcode would be a "
1271+
+ "second place to leak it from.",
1272+
},
1273+
{
1274+
name: "list",
1275+
description: "show registered servers, plus MCP support per engine",
1276+
synopsis: [
1277+
["moshcode mcp list [--json]", "engine support (--json is the engine array)"],
1278+
["moshcode mcp list --servers [--json]", "just the servers moshcode registered"],
1279+
],
1280+
flags: [
1281+
["--servers", "only the servers, not the engine matrix", ""],
1282+
["--json", "machine-readable", ""],
1283+
],
1284+
},
1285+
{ name: "help", description: "show this help", synopsis: [["moshcode mcp help", ""]] },
11491286
];
11501287

11511288
export const SKILL_VERBS = [

‎src/completion.mjs‎

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ export function completionModel() {
5353
]),
5454
mcp: uniqueEntries(MCP_VERBS),
5555
mcpServerSpecs: uniqueEntries(MCP_VERBS.filter(({ acceptsServerSpec }) => acceptsServerSpec)),
56+
// The verbs that take an already-registered server NAME rather than a whole
57+
// spec. Their flag list below is the union of what the group accepts.
58+
// Offering `--listen` after `mcp remove` is a wasted keystroke; offering
59+
// nothing after `mcp test` is a wasted feature.
60+
mcpServerVerbs: uniqueEntries(MCP_VERBS.filter(({ takesServerName }) => takesServerName)),
5661
skills: uniqueEntries(SKILL_VERBS),
5762
trade: uniqueEntries(TRADE_VERBS),
5863
tradeOrderOptions: uniqueEntries([
@@ -129,6 +134,7 @@ ${powershellEntries("MoshcodeCompletionUninstall", model.uninstall)}
129134
${powershellEntries("MoshcodeCompletionUpgrade", model.upgrade)}
130135
${powershellEntries("MoshcodeCompletionMcp", model.mcp)}
131136
${powershellEntries("MoshcodeCompletionMcpServerSpecs", model.mcpServerSpecs)}
137+
${powershellEntries("MoshcodeCompletionMcpServerVerbs", model.mcpServerVerbs)}
132138
${powershellEntries("MoshcodeCompletionSkills", model.skills)}
133139
${powershellEntries("MoshcodeCompletionTrade", model.trade)}
134140
${powershellEntries("MoshcodeCompletionTradeOrderOptions", model.tradeOrderOptions)}
@@ -143,7 +149,8 @@ ${powershellEntries("MoshcodeCompletionConsole", optionEntries("serve --url", "c
143149
${powershellEntries("MoshcodeCompletionConsoleServe", optionEntries("--port --ttyd --bind", "console serve option"))}
144150
${powershellEntries("MoshcodeCompletionTemplate", optionEntries("list install", "template command"))}
145151
${powershellEntries("MoshcodeCompletionTemplateInstall", optionEntries("--into --force --dry-run", "template install option"))}
146-
${powershellEntries("MoshcodeCompletionMcpOptions", optionEntries("--name --transport -t --env -e --header -H", "MCP option"))}
152+
${powershellEntries("MoshcodeCompletionMcpOptions", optionEntries("--name --url --transport -t --token --engine-scope --engines --env -e --header -H", "MCP option"))}
153+
${powershellEntries("MoshcodeCompletionMcpNameOptions", optionEntries("--engine-scope --engines --json --listen --for --all", "MCP option"))}
147154
${powershellEntries("MoshcodeCompletionSkillOptions", optionEntries("--name", "skill option"))}
148155
149156
Register-ArgumentCompleter -Native -CommandName moshcode -ScriptBlock {
@@ -196,6 +203,8 @@ Register-ArgumentCompleter -Native -CommandName moshcode -ScriptBlock {
196203
$choices = $script:MoshcodeCompletionJson
197204
} elseif ($script:MoshcodeCompletionMcpServerSpecs.Name -contains $nested -and $wordToComplete.StartsWith('-')) {
198205
$choices = $script:MoshcodeCompletionMcpOptions
206+
} elseif ($script:MoshcodeCompletionMcpServerVerbs.Name -contains $nested -and $wordToComplete.StartsWith('-')) {
207+
$choices = $script:MoshcodeCompletionMcpNameOptions
199208
}
200209
}
201210
{ $_ -in @('skill', 'skills') } {
@@ -299,7 +308,9 @@ _moshcode_completion() {
299308
elif [[ "$nested" == "list" && "$cur" == -* ]]; then
300309
choices="--json"
301310
elif ${shellMatches("nested", model.mcpServerSpecs)} && [[ "$cur" == -* ]]; then
302-
choices="--name --transport -t --env -e --header -H --"
311+
choices="--name --url --transport -t --token --engine-scope --engines --env -e --header -H --"
312+
elif ${shellMatches("nested", model.mcpServerVerbs)} && [[ "$cur" == -* ]]; then
313+
choices="--engine-scope --engines --json --listen --for --all"
303314
fi
304315
;;
305316
skill|skills)
@@ -434,10 +445,12 @@ _moshcode() {
434445
_values "mcp list option" --json
435446
elif ${shellMatches("{words[3]}", model.mcpServerSpecs)}; then
436447
if [[ "$PREFIX" == -* ]]; then
437-
_values "mcp option" --name --transport -t --env -e --header -H --
448+
_values "mcp option" --name --url --transport -t --token --engine-scope --engines --env -e --header -H --
438449
else
439450
_files
440451
fi
452+
elif ${shellMatches("{words[3]}", model.mcpServerVerbs)} && [[ "$PREFIX" == -* ]]; then
453+
_values "mcp option" --engine-scope --engines --json --listen --for --all
441454
fi
442455
;;
443456
skill|skills)
@@ -579,6 +592,16 @@ complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l nam
579592
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l transport -s t -r -d 'MCP transport'
580593
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l env -s e -r -d 'environment KEY=VALUE'
581594
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l header -s H -r -d 'HTTP Name: Value header'
595+
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l url -r -d 'remote server URL'
596+
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l token -r -d 'bearer token, or env:VAR'
597+
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l engine-scope -r -d 'user or project'
598+
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l engines -r -d 'only these engines'
599+
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerVerbs)}' -l engine-scope -r -d 'user or project'
600+
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerVerbs)}' -l engines -r -d 'only these engines'
601+
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerVerbs)}' -l json -d 'print JSON'
602+
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerVerbs)}' -l listen -d 'stream notifications'
603+
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerVerbs)}' -l for -r -d 'stop listening after N ms'
604+
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerVerbs)}' -l all -s a -d 'every configured server'
582605
complete -c moshcode -n '${nestedCondition("skill", model.skillSources)}; or ${nestedCondition("skills", model.skillSources)}' -l name -r -d 'installed skill name'
583606
`;
584607
}

0 commit comments

Comments
 (0)