feat(mcp): remove, enable, disable, test, reauth, unauth, reconnect, resources, prompts, notifications - #543
Merged
Merged
Conversation
…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>
| 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 " |
ThreatCrush Security Scan8 finding(s) in the 12 file(s) this pull request changes. MEDIUM: 8
96 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 8 | **MEDIUM**: 77 | **LOW**: 11Not introduced by this pull request. The full set is in the Security tab.
…and 76 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings
moshcode mcpand the pit's/mcpto parity with the pasted targetsurface, adapted to what a wrapper can honestly deliver.
The target list turns out to be omp's
/mcpsurface. That matters, becauseomp owns an MCP client and writes its own
.omp/mcp.json. moshcode ownsneither: it drives six other engines' native
mcpcommands and never touchestheir 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.mjsandsrc/tui.mjsbothcall 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
--helpagainst the real binaries on this box rather than guessed:removemcp remove [-s]mcp remove [-s]mcp removereauthmcp loginmcp loginmcp authunauthmcp logoutmcp logoutmcp logoutreconnectmcp reconnect [-a]enable/disableProbe verbs. These talk to a server, and delegate to
mcpjam:testserver inforesourcesresources listpromptsprompts listnotificationsserver capabilities, orsubscriptions listen --list-changedwith--listenPlus:
addwith no arguments opens a wizard;add/installgain--url,--token,--engine-scope,--engines;catalog search <keyword>;list --servers;mcp help.The judgement calls
1. Scope is two axes, and neither is called
--scopemoshcode registers a server in six engines at once, so "scope" answers two
questions, not one:
--engine-scope user|projectpicks the config file each engine writes.--engines claude,codexpicks 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|userwould have thrownaway the thing moshcode exists to do.
Neither is spelled
--scopeon purpose./mcp answer --scope sessions:read,sessions:writealready means the OAuth permissions of ashared 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
--scopeon the new verbs now fails with a line namingthe 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 enginehas 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 projectis asked for.2.
testdelegates to mcpjamTOOLS.mcpjamalready describes itself in this repo as "the companion tomoshcode mcp: that registers a server across engines, this one tells youwhether 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 onebuilder 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 alive server:
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 searchGeneralized, not copied. moshcode already has a catalog, and a second parallel
registry concept beside it is how you end up explaining why
mcp catalogandmcp smithery-searchdisagree about what exists. So searching is a verb of thecatalog 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 porkbunchecks the local curated catalog first and forfree, then widens.
That also disposes of
smithery-login/smithery-logout. A registry API keyis 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
reconnectshipped. It is real:gemini mcp reconnect [name] [-a]dropsthe 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,notificationsshipped, but pointed at theserver, 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/disableshipped, redefined and documented. No engine moshcodedrives has an enable or disable command, and moshcode will not edit their
config files to fake one. So
disablemeans what a wrapper can actuallydeliver: take the server out of every engine, and keep its spec so
enableputs exactly the same one back. A real round trip, stated plainly in the help
text so nobody expects a live toggle.
reloadNOT shipped. "Force reload and rediscover MCP runtime tools" is astatement 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 averb. Shipping
reloadwould have been a command that prints something andchanges nothing.
5. OAuth, and where a token lives
reauthdrives each engine's own login, one at a time, with the terminalinherited 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.
unauthclears 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, andthere is no token store for registered MCP servers at all. So rather than invent
one,
--tokendoes the honest thing:--token env:VARreads the value from the environment at registration time.Nothing literal on the command line, nothing in shell history, and
env:VARis what gets recorded so
mcp testcan rebuild the same request later.--tokenstill works and prints exactly what just happened: thetoken is now in each engine's config and in this shell's history, and
--token env:VARis 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:
claude mcp adddirectly is real and is not in here, which is why everyname-addressed verb falls through to the catalog and then to a bare URL.
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 claudefollowed bymcp remove xwas fanning the removal out to all six engines. It now defaultsto the recorded set, and an explicit flag still wins.
7. omp
I could not make the "free win" of adding
omptoMCP_ENGINES, and theevidence says the win was not there. omp.sh/docs/cli
lists thirty-odd subcommands and
mcpis not among them; omp's whole MCPsurface is TUI slash commands that write
.omp/mcp.jsonitself. Registeringinto 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 befalse. It now carries its own stated reason on all five verbs, the way kimi
already did.
Left out, and why
reloadreconnect --allis the true version and is already here.smithery-searchcatalog searchsmithery-login/smithery-logout--scope project|user--engine-scope+--engines--scopeis taken, and one axis was never the whole story here.Compatibility
mcp list --jsonstill returns exactly the engine capability array it alwaysdid, byte for byte, and
skill list --jsonstill matches it. The new serverlisting is behind
mcp list --servers, so no machine reader breaks. The humanmcp listshows both blocks.Rebases
Rebased twice, as expected, once onto #541 (
handoff/mcp bridge) and onceonto #542.
mcp bridgeis untouched and not duplicated: it makes moshcode anMCP server, where every verb here acts on somebody else's. Conflicts were in
src/cli-schema.mjs,src/integrations.mjs,test/help.test.mjsandtest/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.mjspinned the tail of the verb list. It now checksthat an unknown verb names
install,add,bridge,catalogandlist,one at a time, so the next verb does not break it.
test/help.test.mjspinned a line count that went 24 → 30 → 45 across threePRs. 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 --testover the whole repo, on the committed tree (git diff HEADemptyat the time of the run), with
apps/pwadeps installed the way CI installsthem:
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.mjsis new: 67 tests. Every new fan-out verb is held tothe standard
test/mcp-add-fanout.test.mjsset foradd: the plan coversevery engine, and an engine that cannot do the thing carries a stated reason.
Also verified by hand:
moshcode help mcprenders all 21 verbs,moshcode --helpstill exits 0, the wizard refuses a non-TTY with the flag forminstead of hanging, and an
add/disable/enable/removeround trip against astub engine produces the right argv at each step.
🤖 Generated with Claude Code