feat(cli): wheels map setup writes .mcp.json - #3618
Merged
Merged
Conversation
`wheels mcp` could only print the JSON for the user to paste by hand. This
is the command that does it.
wheels map setup -> writes .mcp.json in the project root
Merges rather than overwrites. A project's .mcp.json usually lists other
servers (a browser MCP, a database MCP), and clobbering those to add one
entry would be a hostile default; the `wheels` entry is added or corrected
and everything else is left as written. Fails closed on malformed JSON
rather than rewriting it — a file that doesn't parse is either mid-edit or
has a syntax error the user needs to see, and replacing it would discard
their work and hide the mistake. Reports how many other servers it kept.
## Why `map` and not `mcp setup`
LuCLI owns the `mcp` verb at the RUNTIME level: `wheels mcp <name>` means
"run module <name>", so it never reaches Module.cfc. Verified live —
`wheels mcp setup` prints:
mcp: module not found: 'setup'
Any subcommand under `mcp` is therefore impossible. `map` is a free
top-level verb that does reach the module. This also means the existing
`mcp()` function in Module.cfc was dead code; it is renamed to `map()`, so
its guidance is now reachable, and the `--help` listing swaps the
misleading `mcp` entry for `map`.
`map` is added to mcpHiddenTools(): an assistant provisioning its own MCP
config is a side-effecting setup step, not a query, and it keeps
tools/list at 19.
## Also fixes a fragile spec
MainCommandSpec asserted on a fixed 1500-character window after
`mcpHiddenTools(` and expected two entries to fall inside it. Adding an
entry mid-list silently overran the window, and the failure ("needle not
found") points at the assertion rather than the window. It now bounds the
slice at the function's own closing brace.
CLI suite: 1382 pass, 0 error. The 4 remaining DbCommandSpec failures are
environmental — they expect Wheels.ServerNotRunning and a live app was on
8080.
Signed-off-by: Peter Amiri <peter@alurium.com>
bpamiri
added a commit
to wheels-dev/homebrew-wheels
that referenced
this pull request
Sep 15, 2026
`mcp` is a LuCLI RUNTIME verb: `wheels mcp <name>` means "run module <name>", so it never reaches Module.cfc and cannot configure anything. Listing it as "Configure Wheels MCP server for AI assistants" sent users to a command that answers with: mcp: module not found: 'setup' `wheels map setup` is the command that writes .mcp.json (wheels-dev/wheels#3618). Help text only — the wrapper has no other change, and the auto-update workflows sed only the version/sha lines, so this survives future bumps. Signed-off-by: Peter Amiri <peter@alurium.com>
bpamiri
added a commit
that referenced
this pull request
Sep 15, 2026
…g too (#3619) Two corrections to the command added in #3618. ## The name has to avoid the literal token `mcp` #3618 shipped `wheels map setup`, chosen because LuCLI owns the `mcp` verb at the runtime level. That was right but incomplete: the runtime intercepts the bare token `mcp` in ANY argument position, not just argv[1]. Verified live: wheels setup mcp -> mcp: missing module name. Usage: wheels mcp <module> wheels info mcp -> same wheels setup ai -> Usage: ai [-hV] [COMMAND] (`ai` is reserved too) wheels setup bogus -> my handler ("Unknown setup target: bogus") So no argument may be spelled `mcp`, which rules out `setup mcp` entirely. The target is now `agents`: reads as "set this app up for AI agents", and avoids the token. `wheels map setup` keeps working as a thin forwarder — that spelling is in snapshot 2499 — and both entry points stay in mcpHiddenTools() (an assistant provisioning its own config is a side-effecting setup step, not a query, so tools/list stays at 19). ## It now writes both client configs The wrapper's own `wheels mcp` help has always promised that setup generates `.mcp.json` (Claude Code) AND `.opencode.json` (OpenCode), and #3618 only did the first. Both are written now, in each client's own shape — Claude Code takes `mcpServers` with separate `command`/`args`; OpenCode takes an `mcp` key, `type: "local"`, and command+args as ONE array. Every file is validated BEFORE any is written, so a malformed `.opencode.json` cannot leave a half-applied setup with `.mcp.json` already rewritten. `$mcpEntryMatches()` compares only the fields setup owns, so a hand-added `environment` (or any other client key) on the wheels entry does not make setup consider it wrong and rewrite on every run. CLI suite: 1385 pass, 0 error, 0 real failures. Complexity gate: PASS. Consumer-docs check: OK. Signed-off-by: Peter Amiri <peter@alurium.com>
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.
wheels mcpcould only print the JSON for you to paste by hand. This is the command that does it:Behaviour
.mcp.jsonwheelsserverwheelsentry--forceMerges rather than overwrites. A project's
.mcp.jsonusually lists other servers (a browser MCP, a database MCP); clobbering those to add one entry would be a hostile default.Fails closed on malformed JSON. A file that doesn't parse is either mid-edit or has a syntax error the user needs to see — replacing it would discard their work and hide the mistake.
Why
map, notmcp setupLuCLI owns the
mcpverb at the runtime level:wheels mcp <name>means "run module<name>", so it never reachesModule.cfc. Verified live:So any subcommand under
mcpis impossible.mapis a free top-level verb that does reach the module. This also means the existingmcp()function inModule.cfcwas dead code — it's renamed tomap(), so its guidance is now reachable, and--helpswaps the misleadingmcpentry formap.mapis added tomcpHiddenTools(): an assistant provisioning its own MCP config is a side-effecting setup step, not a query.tools/liststays at 19.Also fixes a fragile spec
MainCommandSpecasserted on a fixed 1500-character window aftermcpHiddenTools(and expected two entries to fall inside it. Adding an entry mid-list silently overran the window, and the failure ("needle not found") points at the assertion rather than the window that caused it. It now bounds the slice at the function's own closing brace.Docs
Five places said there is no
mcp setupcommand and to write.mcp.jsonby hand — including both consumer-tier copies. They now point atwheels map setup, keeping the manual JSON as the fallback.ship-consumer-docs.sh checkpasses.CLI suite: 1382 pass, 0 error. The 4 remaining
DbCommandSpecfailures are environmental — they expectWheels.ServerNotRunningand a live app was on 8080.