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
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1679,6 +1679,68 @@ moshcode skill list --json
Each row reports `installed` and `supported` separately, so an installed engine
without that integration primitive remains visible rather than looking absent.

### Managing a registered server

Registering a server is a fan-out; so is everything you do to it afterwards.

```sh
moshcode mcp add # interactive wizard
moshcode mcp add sentry --url https://mcp.sentry.dev/mcp --token env:SENTRY_TOKEN
moshcode mcp add tools --engines claude,codex -- npx -y my-mcp-server
moshcode mcp remove sentry
moshcode mcp reauth sentry # each engine's own OAuth login
moshcode mcp unauth sentry
moshcode mcp reconnect --all
moshcode mcp list --servers # what moshcode registered
```

**Scope here is two axes, not one.** `--engine-scope user|project` picks the
config file each engine writes. `--engines claude,codex` picks which of the six
engines get the server at all. Both default to the widest useful answer: user
scope, every installed MCP-capable engine. `--scope` is deliberately neither of
them, because on `/mcp answer` it already means the OAuth permissions of a
shared session.

**`--token env:VAR` is the form worth using.** A literal token is in your shell
history before moshcode sees it and in every engine's config afterwards. The
`env:` form is read from the environment at registration time, and it is what
`~/.moshcode/mcp.json` records. That file keeps header *names* and the variable
a value came from, never a value.

**`disable` and `enable` are a round trip, not a live toggle.** No engine
moshcode drives has an enable or disable command, and moshcode does not edit
their config files. So `disable` takes the server out of every engine and keeps
its spec; `enable` registers exactly that spec again.

What a given engine cannot do is reported rather than faked. OpenCode has no
`mcp remove`. Codex has no project scope. Only the Gemini family has
`mcp reconnect`. Gemini and Qwen authorize from inside their own session. Kimi
and omp run MCP servers perfectly well and have no scriptable `mcp` subcommand
for moshcode to drive. Each of those prints the reason and what to type instead.

### Testing a server before you trust it

`test`, `resources`, `prompts` and `notifications` talk *to* a server rather
than about it, and they run through [mcpjam](#mcp-server-testing), which
moshcode already installs. They take a registered name, a catalog name, or a
bare URL. The last one matters, because "does this thing work" is a question
you ask before deciding to register it.

```sh
moshcode install mcpjam
moshcode mcp test https://mcp.sentry.dev/mcp
moshcode mcp resources sentry --json
moshcode mcp prompts sentry
moshcode mcp notifications sentry # what it declares
moshcode mcp notifications sentry --listen --for 30000
moshcode mcp catalog search postgres # the public MCP directories
```

`catalog search` is the generalized form of a registry search: it sweeps the
scraped MCP directories through mcpjam rather than adding a vendor-specific
verb, and the registry API key stays mcpjam's to hold rather than being copied
into a second place.

### Connect ChatGPT, Claude, or Chovy to this session

Start the interactive pit, then mint a short-lived remote MCP URL for its live
Expand Down
155 changes: 146 additions & 9 deletions src/cli-schema.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -401,9 +401,15 @@
synopsis: [["moshcode mcp <verb> [args…]", ""]],
verbs: "MCP_VERBS",
examples: [
["moshcode mcp list --json", "support and install status"],
["moshcode mcp install https://mcp.example.com", "a remote server"],
],
["moshcode mcp list --json", "registered servers + engine support"],
["moshcode mcp install https://mcp.example.com", "a remote server, everywhere"],
["moshcode mcp add tools --engines claude,codex -- npx -y srv", "two engines only"],
["moshcode mcp test sentry", "does it actually answer?"],
["moshcode mcp catalog search postgres", "the public MCP directories"],
],
note: "moshcode drives each engine's own `mcp` commands and never writes their config files, so a "
+ "verb an engine lacks is skipped with the reason rather than faked. The verbs that talk TO a "
+ "server run through mcpjam.",
seeAlso: ["skill", "engines"],
},
{
Expand Down Expand Up @@ -1122,30 +1128,161 @@
],
flags: [
["--name <n>", "override the derived server name", ""],
["--url <url>", "the remote server, spelled explicitly", ""],
["-t, --transport <t>", "http | sse | stdio", "inferred from the target"],
["--token <t|env:VAR>", "Authorization: Bearer …; env:VAR keeps it out of history", ""],
["--engine-scope <s>", "user | project: the config file each engine writes", "user"],
["--engines <a,b>", "only these engines", "every MCP-capable engine"],
["-e, --env K=V", "repeatable", ""],
["-H, --header 'K: V'", "repeatable", ""],
["--", "everything after this is the server's argv", ""],
],
note: "scope here is two axes, not one. --engine-scope picks the config file inside each engine; "
+ "--engines picks which of the six engines get the server at all. `--scope` is deliberately NOT "
+ "either of them: on `mcp answer` it already means the OAuth permissions of a shared session.",
},
{
name: "add",
description: "register a named MCP server",
description: "register a named MCP server, or run the wizard",
acceptsServerSpec: true,
synopsis: [["moshcode mcp add --name <n> <target>", ""]],
synopsis: [
["moshcode mcp add", "interactive wizard"],
["moshcode mcp add <name> --url <url> [-t http|sse]", "remote server"],
["moshcode mcp add <name> -- <cmd…>", "local stdio server"],
],
flags: [
["--url <url>", "the remote server, spelled explicitly", ""],
["-t, --transport <t>", "http | sse | stdio", "inferred from the target"],
["--token <t|env:VAR>", "Authorization: Bearer …; env:VAR keeps it out of history", ""],
["--engine-scope <s>", "user | project", "user"],
["--engines <a,b>", "only these engines", "every MCP-capable engine"],
],
},
{
name: "bridge",
description: "serve moshcode's verbs over MCP, on stdio",
synopsis: [["moshcode mcp bridge", "speaks MCP on stdin/stdout; register it with any engine"]],
},
{ name: "catalog", description: "show known MCP servers", synopsis: [["moshcode mcp catalog", ""]] },
{
name: "list",
description: "show MCP support and install status",
synopsis: [["moshcode mcp list [--json]", ""]],
name: "remove",
takesServerName: true,
description: "deregister a server from every engine",
synopsis: [["moshcode mcp remove <name> [--engine-scope user|project] [--engines a,b]", ""]],
note: "OpenCode and privacycode have no `mcp remove`; they are skipped with the file to edit. "
+ "Codex has no project scope.",
},
{
name: "enable",
takesServerName: true,
description: "re-register a server moshcode disabled",
synopsis: [["moshcode mcp enable <name>", ""]],
note: "no engine moshcode drives has an enable/disable verb, and moshcode will not edit their "
+ "config files to fake one. So disable deregisters the server everywhere and keeps its spec in "
+ "~/.moshcode/mcp.json, and enable registers exactly that spec again. A credential is never "
+ "kept, so a token-authenticated server needs --token again on the way back.",
},
{
name: "disable",
takesServerName: true,
description: "deregister a server, keeping its spec for enable",
synopsis: [["moshcode mcp disable <name>", ""]],
seeAlso: ["mcp"],
},
{
name: "test",
takesServerName: true,
description: "connect to a server and report what it serves",
synopsis: [["moshcode mcp test <name|url> [--json]", ""]],
flags: [["--json", "machine-readable", ""]],
note: "runs through mcpjam, which moshcode already installs (`/install mcpjam`). "
+ "Takes a registered name, a catalog name, or a bare URL you have not registered yet.",
},
{
name: "reauth",
takesServerName: true,
description: "run each engine's own OAuth login for a server",
synopsis: [["moshcode mcp reauth <name>", ""]],
note: "drives `claude mcp login`, `codex mcp login` and `opencode mcp auth` in turn. Each runs the "
+ "MCP spec's OAuth 2.1 flow (authorization code + PKCE) and keeps its own rotating refresh "
+ "token; moshcode mints nothing and stores nothing. Gemini and Qwen authorize from inside "
+ "the session, so they are skipped with the words to type there.",
},
{
name: "unauth",
takesServerName: true,
description: "clear a server's stored OAuth credentials",
synopsis: [["moshcode mcp unauth <name>", ""]],
},
{
name: "reconnect",
takesServerName: true,
description: "redial a server in the engines that can",
synopsis: [
["moshcode mcp reconnect <name>", ""],
["moshcode mcp reconnect --all", "every configured server"],
],
flags: [["-a, --all", "reconnect every server", ""]],
note: "only the Gemini family has `mcp reconnect`. Claude Code, Codex and OpenCode dial their "
+ "servers when a session starts, so they are skipped with what to do instead.",
},
{
name: "resources",
takesServerName: true,
description: "list the resources a server exposes",
synopsis: [["moshcode mcp resources <name|url> [--json]", ""]],
flags: [["--json", "machine-readable", ""]],
},
{
name: "prompts",
takesServerName: true,
description: "list the prompts a server exposes",
synopsis: [["moshcode mcp prompts <name|url> [--json]", ""]],
flags: [["--json", "machine-readable", ""]],
},
{
name: "notifications",
takesServerName: true,
description: "what a server can notify about, and watch it",
synopsis: [
["moshcode mcp notifications <name|url>", "the capabilities it declares"],
["moshcode mcp notifications <name|url> --listen --for 30000", "stream them"],
],
flags: [
["--listen", "stream notifications instead of reading capabilities", ""],
["--for <ms>", "stop listening after this long", "until Ctrl-C"],
["--json", "machine-readable", ""],
],
},
{
name: "catalog",
description: "show known MCP servers, or search the public directories",
synopsis: [
["moshcode mcp catalog", "moshcode's own curated list"],
["moshcode mcp catalog search <keyword> [--limit 1-100]", "the public MCP directories"],
],
flags: [
["--limit <1-100>", "results per page", ""],
["--source <id>", "one directory instead of all of them", "all"],
["--json", "machine-readable", ""],
],
note: "search runs through mcpjam's registry, which sweeps the scraped MCP directories "
+ "(Smithery among them). There is no Smithery-specific verb and no second API key here on "
+ "purpose: the key is mcpjam's, mcpjam already stores it, and a copy in moshcode would be a "
+ "second place to leak it from.",
},
{
name: "list",
description: "show registered servers, plus MCP support per engine",
synopsis: [
["moshcode mcp list [--json]", "engine support (--json is the engine array)"],
["moshcode mcp list --servers [--json]", "just the servers moshcode registered"],
],
flags: [
["--servers", "only the servers, not the engine matrix", ""],
["--json", "machine-readable", ""],
],
},
{ name: "help", description: "show this help", synopsis: [["moshcode mcp help", ""]] },
];

export const SKILL_VERBS = [
Expand Down
29 changes: 26 additions & 3 deletions src/completion.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ export function completionModel() {
]),
mcp: uniqueEntries(MCP_VERBS),
mcpServerSpecs: uniqueEntries(MCP_VERBS.filter(({ acceptsServerSpec }) => acceptsServerSpec)),
// The verbs that take an already-registered server NAME rather than a whole
// spec. Their flag list below is the union of what the group accepts.
// Offering `--listen` after `mcp remove` is a wasted keystroke; offering
// nothing after `mcp test` is a wasted feature.
mcpServerVerbs: uniqueEntries(MCP_VERBS.filter(({ takesServerName }) => takesServerName)),
skills: uniqueEntries(SKILL_VERBS),
trade: uniqueEntries(TRADE_VERBS),
tradeOrderOptions: uniqueEntries([
Expand Down Expand Up @@ -129,6 +134,7 @@ ${powershellEntries("MoshcodeCompletionUninstall", model.uninstall)}
${powershellEntries("MoshcodeCompletionUpgrade", model.upgrade)}
${powershellEntries("MoshcodeCompletionMcp", model.mcp)}
${powershellEntries("MoshcodeCompletionMcpServerSpecs", model.mcpServerSpecs)}
${powershellEntries("MoshcodeCompletionMcpServerVerbs", model.mcpServerVerbs)}
${powershellEntries("MoshcodeCompletionSkills", model.skills)}
${powershellEntries("MoshcodeCompletionTrade", model.trade)}
${powershellEntries("MoshcodeCompletionTradeOrderOptions", model.tradeOrderOptions)}
Expand All @@ -143,7 +149,8 @@ ${powershellEntries("MoshcodeCompletionConsole", optionEntries("serve --url", "c
${powershellEntries("MoshcodeCompletionConsoleServe", optionEntries("--port --ttyd --bind", "console serve option"))}
${powershellEntries("MoshcodeCompletionTemplate", optionEntries("list install", "template command"))}
${powershellEntries("MoshcodeCompletionTemplateInstall", optionEntries("--into --force --dry-run", "template install option"))}
${powershellEntries("MoshcodeCompletionMcpOptions", optionEntries("--name --transport -t --env -e --header -H", "MCP option"))}
${powershellEntries("MoshcodeCompletionMcpOptions", optionEntries("--name --url --transport -t --token --engine-scope --engines --env -e --header -H", "MCP option"))}
${powershellEntries("MoshcodeCompletionMcpNameOptions", optionEntries("--engine-scope --engines --json --listen --for --all", "MCP option"))}
${powershellEntries("MoshcodeCompletionSkillOptions", optionEntries("--name", "skill option"))}

Register-ArgumentCompleter -Native -CommandName moshcode -ScriptBlock {
Expand Down Expand Up @@ -196,6 +203,8 @@ Register-ArgumentCompleter -Native -CommandName moshcode -ScriptBlock {
$choices = $script:MoshcodeCompletionJson
} elseif ($script:MoshcodeCompletionMcpServerSpecs.Name -contains $nested -and $wordToComplete.StartsWith('-')) {
$choices = $script:MoshcodeCompletionMcpOptions
} elseif ($script:MoshcodeCompletionMcpServerVerbs.Name -contains $nested -and $wordToComplete.StartsWith('-')) {
$choices = $script:MoshcodeCompletionMcpNameOptions
}
}
{ $_ -in @('skill', 'skills') } {
Expand Down Expand Up @@ -299,7 +308,9 @@ _moshcode_completion() {
elif [[ "$nested" == "list" && "$cur" == -* ]]; then
choices="--json"
elif ${shellMatches("nested", model.mcpServerSpecs)} && [[ "$cur" == -* ]]; then
choices="--name --transport -t --env -e --header -H --"
choices="--name --url --transport -t --token --engine-scope --engines --env -e --header -H --"
elif ${shellMatches("nested", model.mcpServerVerbs)} && [[ "$cur" == -* ]]; then
choices="--engine-scope --engines --json --listen --for --all"
fi
;;
skill|skills)
Expand Down Expand Up @@ -434,10 +445,12 @@ _moshcode() {
_values "mcp list option" --json
elif ${shellMatches("{words[3]}", model.mcpServerSpecs)}; then
if [[ "$PREFIX" == -* ]]; then
_values "mcp option" --name --transport -t --env -e --header -H --
_values "mcp option" --name --url --transport -t --token --engine-scope --engines --env -e --header -H --
else
_files
fi
elif ${shellMatches("{words[3]}", model.mcpServerVerbs)} && [[ "$PREFIX" == -* ]]; then
_values "mcp option" --engine-scope --engines --json --listen --for --all
fi
;;
skill|skills)
Expand Down Expand Up @@ -579,6 +592,16 @@ complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l nam
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l transport -s t -r -d 'MCP transport'
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l env -s e -r -d 'environment KEY=VALUE'
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l header -s H -r -d 'HTTP Name: Value header'
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l url -r -d 'remote server URL'
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l token -r -d 'bearer token, or env:VAR'
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l engine-scope -r -d 'user or project'
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerSpecs)}' -l engines -r -d 'only these engines'
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerVerbs)}' -l engine-scope -r -d 'user or project'
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerVerbs)}' -l engines -r -d 'only these engines'
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerVerbs)}' -l json -d 'print JSON'
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerVerbs)}' -l listen -d 'stream notifications'
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerVerbs)}' -l for -r -d 'stop listening after N ms'
complete -c moshcode -n '${nestedCondition("mcp", model.mcpServerVerbs)}' -l all -s a -d 'every configured server'
complete -c moshcode -n '${nestedCondition("skill", model.skillSources)}; or ${nestedCondition("skills", model.skillSources)}' -l name -r -d 'installed skill name'
`;
}
Expand Down
Loading
Loading