Skip to content

feat(mcp): remove, enable, disable, test, reauth, unauth, reconnect, resources, prompts, notifications - #543

Merged
ralyodio merged 1 commit into
mainfrom
mcp-surface
Sep 25, 2026
Merged

ralyodio merged 1 commit into
mainfrom
mcp-surface

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Brings moshcode mcp and the pit's /mcp to parity with the pasted target
surface, adapted to what a wrapper can honestly deliver.

The target list turns out to be omp's /mcp surface. That matters, because
omp owns an MCP client and writes its own .omp/mcp.json. moshcode owns
neither: it drives six other engines' native mcp commands and never touches
their config files (prd/0003). So the list is a starting point, not a spec, and
this PR ships the part that is real and refuses the part that would be theatre.

Both surfaces get everything at once: bin/moshcode.mjs and src/tui.mjs both
call the same mcpCommand, so there is no second code path to keep in sync.

What shipped

Fan-out verbs. Each drives the engine's own native command, verified by running
--help against the real binaries on this box rather than guessed:

verb claude gemini/qwen codex opencode/privacycode
remove mcp remove [-s] mcp remove [-s] mcp remove skipped: no such command
reauth mcp login skipped: in-session mcp login mcp auth
unauth mcp logout skipped: in-session mcp logout mcp logout
reconnect skipped: in-session mcp reconnect [-a] skipped: none skipped: none
enable/disable see below see below see below see below

Probe verbs. These talk to a server, and delegate to mcpjam:

verb mcpjam
test server info
resources resources list
prompts prompts list
notifications server capabilities, or subscriptions listen --list-changed with --listen

Plus: add with no arguments opens a wizard; add/install gain --url,
--token, --engine-scope, --engines; catalog search <keyword>;
list --servers; mcp help.

The judgement calls

1. Scope is two axes, and neither is called --scope

moshcode registers a server in six engines at once, so "scope" answers two
questions, not one:

  • --engine-scope user|project picks the config file each engine writes.
  • --engines claude,codex picks which engines get the server at all.

Both default to the widest useful answer: user scope, every installed
MCP-capable engine. Reducing this to a single project|user would have thrown
away the thing moshcode exists to do.

Neither is spelled --scope on purpose. /mcp answer --scope sessions:read,sessions:write already means the OAuth permissions of a
shared session, and one word meaning two things across sibling verbs of the
same command is how somebody grants a session write access while trying to pick
a config file. A bare --scope on the new verbs now fails with a line naming
the two flags that do exist, so anyone typing from a parity list elsewhere gets
corrected instead of confused.

Claude Code's third scope, local, is deliberately not offered: no other engine
has it, so it would be a flag that silently means "claude only", which moshcode
already spells --engines claude.

Codex, OpenCode and privacycode have exactly one config location each and are
skipped with their own reason when --engine-scope project is asked for.

2. test delegates to mcpjam

TOOLS.mcpjam already describes itself in this repo as "the companion to
moshcode mcp: that registers a server across engines, this one tells you
whether the server is actually worth registering". This PR makes that true.

The reuse is better than a shortcut: mcpjam's flag surface is uniform across all
its subcommands (--transport/--url/--header/--command/--args/-e), so one
builder covers four verbs. moshcode speaks no JSON-RPC, opens no socket, and
holds no protocol version that will be wrong in six weeks.

When mcpjam is absent the verbs print what is missing and the command that fixes
it (moshcode install mcpjam), and spawn nothing. Verified end to end against a
live server:

$ moshcode mcp test https://bufferoverride.com/mcp
· test bufferoverride via mcpjam (url)
{"protocolVersion":"2025-06-18","transport":"streamable-http",...}

The probes also take a bare URL, not only a registered name. "Does this server
work" is a question people ask before deciding to register it, and a probe
that only accepted registered names would be useless at the exact moment it is
wanted.

3. The Smithery verbs became catalog search

Generalized, not copied. moshcode already has a catalog, and a second parallel
registry concept beside it is how you end up explaining why mcp catalog and
mcp smithery-search disagree about what exists. So searching is a verb of the
catalog that already exists, and the registry behind it is an implementation
detail: mcpjam's registry search, which sweeps the scraped MCP directories
(Smithery among them) rather than one vendor's.

mcp catalog search porkbun checks the local curated catalog first and for
free, then widens.

That also disposes of smithery-login / smithery-logout. A registry API key
is mcpjam's credential, mcpjam already stores it, and a second copy in moshcode
would be a second place to leak it from and a second to rotate. When a search
needs a key, mcpjam says so in its own words.

4. The live-session verbs, one at a time

  • reconnect shipped. It is real: gemini mcp reconnect [name] [-a] drops
    the engine's client for a server and dials again. Nobody else has it, so the
    rest are skipped with what to do instead.
  • resources, prompts, notifications shipped, but pointed at the
    server, not at a session. moshcode holds no session's tool list, but "what
    does this server expose" is a real question with a real answer, and mcpjam
    answers it.
  • enable / disable shipped, redefined and documented. No engine moshcode
    drives has an enable or disable command, and moshcode will not edit their
    config files to fake one. So disable means what a wrapper can actually
    deliver: take the server out of every engine, and keep its spec so enable
    puts exactly the same one back. A real round trip, stated plainly in the help
    text so nobody expects a live toggle.
  • reload NOT shipped. "Force reload and rediscover MCP runtime tools" is a
    statement about a live MCP client's tool cache. moshcode has no client and no
    cache; the nearest true thing is mcp reconnect --all, which is already a
    verb. Shipping reload would have been a command that prints something and
    changes nothing.

5. OAuth, and where a token lives

reauth drives each engine's own login, one at a time, with the terminal
inherited so the browser prompt is answerable. Each engine runs the MCP spec's
OAuth 2.1 flow (authorization code + PKCE, engine-rotated refresh token) and
keeps the result in its own store. moshcode mints nothing and stores nothing, so
there is no moshcode-side token to leak. Nothing on this path is OAuth 1.0a.
unauth clears what those flows left behind.

On the vault: there is no vault in moshcode today. Its own operator token is
a 0600 dotfile at ~/.moshcode/credentials.json, there is no keychain, and
there is no token store for registered MCP servers at all. So rather than invent
one, --token does the honest thing:

  • --token env:VAR reads the value from the environment at registration time.
    Nothing literal on the command line, nothing in shell history, and env:VAR
    is what gets recorded so mcp test can rebuild the same request later.
  • A literal --token still works and prints exactly what just happened: the
    token is now in each engine's config and in this shell's history, and
    --token env:VAR is the form that avoids both.

A real vault integration is follow-up work and is noted here as a gap.

6. ~/.moshcode/mcp.json (new)

The moment a verb takes a name, there has to be something to resolve it
against, and reading it back out of six config formats is exactly what prd/0003
forbids. So moshcode records what moshcode registered. Two things it is not,
both stated in the file header:

  • Not the source of truth. Each engine's config is. A server added with claude mcp add directly is real and is not in here, which is why every
    name-addressed verb falls through to the catalog and then to a bare URL.
  • Not a credential store. It keeps header names and the variable a value
    came from, never a value. A test asserts a bearer token and an API key never
    reach the file.

It also carries the engines and scope a server was registered with, which caught
a real bug during the smoke test: mcp add x --engines claude followed by
mcp remove x was fanning the removal out to all six engines. It now defaults
to the recorded set, and an explicit flag still wins.

7. omp

I could not make the "free win" of adding omp to MCP_ENGINES, and the
evidence says the win was not there. omp.sh/docs/cli
lists thirty-odd subcommands and mcp is not among them; omp's whole MCP
surface is TUI slash commands that write .omp/mcp.json itself. Registering
into it would mean writing another engine's config file, which is the one thing
this command does not do.

What I did instead is fix the real defect: omp was falling through to the
blanket "no MCP support", which is a claim about the engine that happens to be
false. It now carries its own stated reason on all five verbs, the way kimi
already did.

Left out, and why

asked for shipped? why
reload no needs a live MCP client's tool cache. moshcode has neither. reconnect --all is the true version and is already here.
smithery-search as catalog search a second registry concept beside moshcode's catalog is the wrong shape.
smithery-login / smithery-logout no the key is mcpjam's, mcpjam stores it, a second copy is a second leak.
--scope project|user as --engine-scope + --engines --scope is taken, and one axis was never the whole story here.

Compatibility

mcp list --json still returns exactly the engine capability array it always
did, byte for byte, and skill list --json still matches it. The new server
listing is behind mcp list --servers, so no machine reader breaks. The human
mcp list shows both blocks.

Rebases

Rebased twice, as expected, once onto #541 (handoff / mcp bridge) and once
onto #542. mcp bridge is untouched and not duplicated: it makes moshcode an
MCP server, where every verb here acts on somebody else's. Conflicts were in
src/cli-schema.mjs, src/integrations.mjs, test/help.test.mjs and
test/mcp-catalog.test.mjs, all resolved to keep both sides.

Two of those test assertions had now failed on consecutive PRs for the same
reason, so both were rewritten to assert the property instead of a magic value:

  • test/mcp-catalog.test.mjs pinned the tail of the verb list. It now checks
    that an unknown verb names install, add, bridge, catalog and list,
    one at a time, so the next verb does not break it.
  • test/help.test.mjs pinned a line count that went 24 → 30 → 45 across three
    PRs. The ceiling stays as a loose guard, and the real assertion is now that
    the output does not contain the wall's closing line.

Tests

node --test over the whole repo, on the committed tree (git diff HEAD empty
at the time of the run), with apps/pwa deps installed the way CI installs
them:

tests 3496  ·  pass 3492  ·  fail 0  ·  skipped 4

The mcp-relevant subset alone (test/mcp*.test.mjs,
test/integrations-exit-code, test/support-matrix, test/completion,
test/help, test/tools, test/skill-command): 303 tests, 302 pass, 0 fail,
1 skipped.

test/mcp-surface.test.mjs is new: 67 tests. Every new fan-out verb is held to
the standard test/mcp-add-fanout.test.mjs set for add: the plan covers
every engine, and an engine that cannot do the thing carries a stated reason.

Also verified by hand: moshcode help mcp renders all 21 verbs,
moshcode --help still exits 0, the wizard refuses a non-TTY with the flag form
instead of hanging, and an add/disable/enable/remove round trip against a
stub engine produces the right argv at each step.

🤖 Generated with Claude Code

…resources, prompts, notifications

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>
Comment thread src/cli-schema.mjs
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 "
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

8 finding(s) in the 12 file(s) this pull request changes.

MEDIUM: 8

Severity Rule Location
MEDIUM sql-string-concatenation src/cli-schema.mjs:180
MEDIUM sql-string-concatenation src/cli-schema.mjs:267
MEDIUM sql-string-concatenation src/cli-schema.mjs:677
MEDIUM sql-string-concatenation src/cli-schema.mjs:710
MEDIUM sql-string-concatenation src/cli-schema.mjs:817
MEDIUM sql-string-concatenation src/cli-schema.mjs:1180
MEDIUM sql-string-concatenation src/cli-schema.mjs:1706
MEDIUM sql-string-concatenation src/cli-schema.mjs:1724
96 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 8 | **MEDIUM**: 77 | **LOW**: 11

Not introduced by this pull request. The full set is in the Security tab.

Severity Rule Location
HIGH js-ssrf-outbound-request apps/pwa/public/sw.js:45
HIGH tls-verification-disabled apps/pwa/src/lib/moshpit-gateway.mjs:299
HIGH sh-remote-script-execution install.sh:86
HIGH sh-remote-script-execution install.sh:90
HIGH sh-remote-script-execution install.sh:258
HIGH sh-remote-script-execution install.sh:269
HIGH sh-remote-script-execution install.sh:275
HIGH tls-verification-disabled src/dns.mjs:766
MEDIUM sql-template-interpolation apps/pwa/src/lib/moshpit-certs.mjs:44
MEDIUM sql-template-interpolation apps/pwa/src/lib/moshpit-certs.mjs:82
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:139
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:153
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:179
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:373
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:377
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:422
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:671
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:867
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:869
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:928

…and 76 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 00a573f into main Sep 25, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants