Skip to content

feat(cli): wheels map setup writes .mcp.json - #3618

Merged
bpamiri merged 1 commit into
developfrom
feat/map-setup
Sep 15, 2026
Merged

bpamiri merged 1 commit into
developfrom
feat/map-setup

Conversation

@bpamiri

@bpamiri bpamiri commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

wheels mcp could only print the JSON for you to paste by hand. This is the command that does it:

wheels map setup    -> writes .mcp.json in the project root

Behaviour

case result
no .mcp.json created with the documented wheels server
already correct reports it, writes nothing
other servers present preserved, reports how many
wrong wheels entry corrected
malformed JSON refused, file left untouched, typed throw
not a Wheels project writes nothing, explains why
--force rewrites the file

Merges rather than overwrites. A project's .mcp.json usually 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, 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
mcp: module not found: 'setup'

So any subcommand under mcp is 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's renamed to map(), so its guidance is now reachable, and --help 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. tools/list stays 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 that caused it. It now bounds the slice at the function's own closing brace.

Docs

Five places said there is no mcp setup command and to write .mcp.json by hand — including both consumer-tier copies. They now point at wheels map setup, keeping the manual JSON as the fallback. ship-consumer-docs.sh check passes.

CLI suite: 1382 pass, 0 error. The 4 remaining DbCommandSpec failures are environmental — they expect Wheels.ServerNotRunning and a live app was on 8080.

`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>
@github-actions github-actions Bot added the docs label Sep 15, 2026
@bpamiri
bpamiri merged commit c221a9a into develop Sep 15, 2026
21 checks passed
@bpamiri
bpamiri deleted the feat/map-setup branch September 15, 2026 03:49
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant