Wheels + Claude: Building a Feature via the stdio MCP #3217
bpamiri
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Read: https://blog.wheels.dev/posts/wheels-claude-stdio-mcp
There are two ways to make an AI assistant useful inside a framework. The first is a sidecar -- a chat window glued to the IDE, fed a vector index of your codebase, generating snippets you copy-paste. That's where most frameworks landed first, and it's fine; the model is a smarter Stack Overflow.
The second is to teach the assistant the same vocabulary the framework already uses with humans. You don't ship "AI code generation" -- you ship a CLI with
wheels generate model Post title:string, and then you let the assistant call that CLI directly, with the same arguments, templates, and validation. The model writes nothing. The framework writes everything, the same way it always has. The model just decides which command to run. Wheels 4.0 ships the second one.The architecture
The AI editor spawns
wheels mcp wheelsas a subprocess and speaks newline-delimited JSON-RPC 2.0 over stdin/stdout. There's no port, no socket, no running dev server -- the subprocess lives for the session.wheels mcp wheelsis two pieces glued together:wheels mcpis LuCLI's generic MCP dispatcher (the binary's runtime is LuCLI, shipped under thewheelsbrand), and the trailingwheelsis the module name to expose. LuCLI loadscli/lucli/Module.cfc, scans its public functions, and turns each into a tool named after the function --generate,migrate,test. The server advertises these bare names; MCP clients namespace them under the server name on their end (Claude Code surfaces them asmcp__wheels__generate). The protocol framing is handled by the LuCLI runtime -- the Wheels codebase contributes the functions, not the protocol.The design choice worth noticing: the MCP server is not a separate codebase. It is the CLI. Anything you can do typing
wheels migrate latest, Claude can do by calling themigratetool with the samelatesttoken. New CLI features become MCP features automatically -- no schema to write, no router to update.Setup
There is no
wheels mcp setupcommand.wheels mcp(bare) is a help command -- it prints the config snippet and writes nothing. You paste this into a.mcp.jsonat your project root yourself:{"mcpServers":{"wheels":{"command":"wheels","args":["mcp","wheels"]}}}Claude Code reads it on startup, spawns the subprocess, and starts speaking JSON-RPC. No install step, no API key, no auth handshake. Other clients (Cursor, OpenCode) point at the same server definition from their own config files. You can smoke-test before restarting your editor with
wheels mcp wheels --once tools/list, which runs a single method and prints the tool list.The tool surface (18 tools)
Tool discovery is a one-line CFML reflection step over
Module.cfc. Two filters trim the surface. First, LuCLI's MCP runtime auto-excludes itsBaseModuleplumbing and convention functions -- includingversionandshowHelp-- before the module's own denylist is consulted. (That's whyversionandshowHelp, both real CLI commands, never appear as tools.) Then the module'smcpHiddenTools()denylist hidesmain,mcp, thed/galiases,new,console,start,stop,browser, plus internal$-prefixed helpers swept in structurally. The 18 that survive, all bare-named:generate,destroy,migrate,seed,db,packages,test,reload,routes,info,analyze,validate,doctor,stats,notes,upgrade,create,deploy.Most are read-only or strictly additive.
destroyis the one to think twice about -- but it's also the one a developer is most likely to want to drive through Claude.A worked example
The post builds commenting on an existing
Postmodel end-to-end. You type one prompt: "Add commenting to the blog. Each comment belongs to a Post, has anauthor(string) andbody(text). Generate the migration and model, wire the association on Post, run the migration, and add a smoke test."Claude (which called
tools/listat session start) recognizes the generate-then-migrate-then-test loop and dispatches: ageneratecall (wheels generate model Comment postId:integer author:string body:text), a one-line direct edit toPost.cfcto addhasMany(name="comments", dependent="delete"), amigratecall withlatest, agenerate testcall, then atestcall scoped totests.specs.models. Every tool call returns text content over stdio; the loop is conversational because the protocol is. One prompt; the framework does what it would have done if a developer had typed five commands. (One wrinkle:generateandmigratestill parse bare positional tokens, so positional keys are the safe currency there; ArgSpec-migrated commands liketestaccept named keys such asfilterdirectly.)The deprecated HTTP endpoint
If you used the MCP integration in a 3.x build, you'll remember a dev-server route at
/wheels/mcpthat spoke Streamable HTTP JSON-RPC and required a running app. It still exists, now with a deprecation notice and a one-time warning on first request, and is scheduled for removal. Its tool schemas were hand-written, separate from CLI behaviour, and drifted -- which is exactly the parallel-surface problem the stdio shift deletes. If you have a.mcp.jsonor.opencode.jsonpointing athttp://localhost:<port>/wheels/mcp, replace it with the stdio snippet above.Found while writing
Drafting the post turned up template drift: the OpenCode config templates (
cli/src/templates/OpenCodeConfig.jsonandapp/snippets/OpenCodeConfig.json) still pointed at the deprecated HTTP endpoint with a literal{PORT}placeholder that never substitutes -- so a copying user got{PORT}literally in their URL. Both are now corrected to the stdiotype: "local"form. A CHANGELOG entry had claimed the templates were updated everywhere; they hadn't been. Writing the article forces you to walk every path a reader will walk, and the spots where docs disagree with code are exactly where the next person gets stuck.Guide reference: https://guides.wheels.dev/v4-0-0/command-line-tools/mcp-integration
How are you wiring AI assistants into your Wheels workflow? Are you driving generators and migrations through the MCP surface, or still copy-pasting from a chat sidecar? Any tools you wish the surface exposed? Let's discuss.
Beta Was this translation helpful? Give feedback.
All reactions