Readiness: Implemented
The toolchain
Agents
uf mcp serves the Model Context Protocol over stdio, so a coding agent can run
the checks a person runs — lint, type check, tests, the route table — and read
the same JSON reports CI reads, rather than scraping a terminal. Twelve tools, and
two of them write.
What you will be able to do: register uf with an MCP client, tell which tools read and which write, read what a call returns, and know where the server's limits are before an agent leans on them.
What you need first: a uf project — Start makes one — and a client that starts stdio MCP servers. Claude Code is the example below; any client that takes a command and its arguments works the same way.
Registering it
uf mcp is started by the client and talks JSON-RPC over its stdin and stdout,
one message per line. It is not a daemon and has no port. Typed into a terminal,
where there is no client on the other end, it says so and exits:
uf mcp: Model Context Protocol stdio server ready
The project is decided once, when the server starts: the directory it was
started in, or the one --cwd names, walked up to the nearest uf.config.js,
package.json or .git. Nothing a client sends afterwards changes it. So for a
client you run from anywhere, name the project:
claude mcp add uf -- uf --cwd /path/to/project mcp
Everything after -- is handed to the command untouched
(Claude Code's MCP documentation). A
project-scoped .mcp.json is committed and shared, so it cannot carry your
absolute path; written without one, it runs uf mcp in whatever directory the
client starts servers in, and uf walks up from there:
{
"mcpServers": {
"uf": { "command": "uf", "args": ["mcp"] }
}
}
If the answers look like they are about the wrong directory, call uf_info: it
prints the working directory the server was given.
The tools
tools/list names twelve, and each description ends with one sentence saying
what the tool does to your checkout. Command tools run in the MCP process; the dev tools read the running development server.
| Tool | Runs | Arguments | Its description ends |
|---|---|---|---|
uf_dev_errors | Current build, runtime and hydration diagnostics | — | Changes none of your files, … |
uf_dev_logs | Recent logs and request ids | — | Changes none of your files, … |
uf_dev_routes | Running server's resolved routes | — | Changes none of your files, … |
uf_dev_action | Module and export for a server-action id | id, required | Changes none of your files, … |
uf_check | uf check --json | paths | Changes none of your files, … |
uf_lint | uf lint --json | paths | Changes none of your files, … |
uf_info | uf info | — | Changes none of your files, … |
uf_routes | uf routes list | — | Changes none of your files, … |
uf_explain | uf explain <command> --json | command, required | Changes none of your files, … |
uf_test | uf test --json | filter, paths | RUNS the project's own code. |
uf_fmt_write | uf fmt | paths | WRITES to files in the checkout. |
uf_lint_fix | uf lint --fix --json | paths | WRITES to files in the checkout. |
The read tools end "Changes none of your files, though uf may write its own
cache under .uf/cache/", and uf_check's description names the one it keeps:
what it inferred, in .uf/cache/check/.
paths narrows a command to files whose path contains one of the strings, the
way the command line's own PATH arguments do. uf_lint_fix applies the
safe fixes only; the unsafe tier is not a tool. There
is no tool for uf build, uf dev, uf install or uf fmt --check, and the
server offers no resources and no prompts.
Reading a running server
Start uf dev separately in the same project. The dev tools read its local
.uf/dev-state.json channel: no network endpoint, application execution or
second dev server is needed. The channel is refreshed every five seconds and
refuses snapshots older than thirty seconds. It keeps at most 100 current errors
and 150 recent log entries, bounded to 3 MiB; source edits clear the current
errors and advance the generation. Request logs omit query strings and carry
requestId, also returned as the response's x-uf-request-id header.
Runtime stacks are mapped back to source. Hydration mismatches retain the DOM
and component detail reported by the browser; a DOM mismatch has no invented
source location. Route and action tables are bounded to 1,000 entries and mark
truncation explicitly. Unknown or stale action ids return an error.
uf new creates a version-matched AGENTS.md. uf dev refreshes only its
<!-- uf:agents:start --> / <!-- uf:agents:end --> block, preserving your
surrounding instructions. The block links the installed release's documentation
and lists the tools. Diagnostic messages remain application data, never agent
instructions.
What a call returns
A tool result is a list of text blocks and an isError flag. The first block is
what the command printed — JSON for uf_check, uf_lint, uf_lint_fix,
uf_explain, uf_test and the four dev tools, the command's own text for the other three. A command
that fails keeps its report whole in the first block and adds the reason as a
second, so the JSON still parses. From a session with one deliberate type error
in app/price.js:
tools/call uf_lint {"paths":["app/"]}
isError: false; blocks: 1
{
"command": "uf lint",
"filesChecked": 6,
"errors": 0,
"warnings": 0,
"diagnostics": [],
…
}
tools/call uf_check {"paths":["app/price.js"]}
isError: true; blocks: 2
errors: 1; typeCheck.status: checked
first type diagnostic: incompatible-type at app/price.js:2:30
second block: "uf check failed with 1 error"
A failing check is isError: true inside an ordinary result, not a protocol
error: the call worked, and its answer is that the project does not check. A tool
that does not exist is answered the same way, and so is a call whose arguments do
not fit the tool's inputSchema — a key it does not have, a value of the wrong
type, a missing command — which names the argument and runs nothing. Only a
method the server has no answer for at all is a JSON-RPC error:
tools/call uf_build
← {"jsonrpc":"2.0","id":5,"result":{"content":[{"type":"text","text":"no tool named \"uf_build\""}],"isError":true}}
tools/call uf_lint {"pathz":["app/"]}
← {"jsonrpc":"2.0","id":6,"result":{"content":[{"type":"text","text":"uf_lint did not run: `pathz` is not an argument; the arguments are `paths`"}],"isError":true}}
resources/list
← {"jsonrpc":"2.0","id":7,"error":{"code":-32601,"message":"this server has no method \"resources/list\""}}
Type checking has the whole shape of the report uf_check
returns, including the one trap in it: errors is the sum of the lint findings in
diagnostics and the type errors in typeCheck.diagnostics.
What an agent should not assume
These are the limits of the server as it is, and the ones that are defects have issues:
pathsstays inside the project — over MCP. An entry that is an absolute path, climbs out with.., or leads out through a symbolic link is refused, naming the entry, before anything is read. On the command lineuf fmt ../elsewherestill does what it says, because the person running it typed it. Inside the project there is no confirmation step, allow-list or read-only mode, so a client that asks before a tool writes is the only guard.- Changing none of your files is not writing nothing.
uf_checkwrites its inference cache under.uf/cache/check/, anduf_testwrites.uf/test-timings.json, beside whatever the tests themselves write. uf_infodoes not describe the project. It prints uf's version, the machine and the working directory, and reads nouf.config.js.uf_explainis the tool that says what a command will do and why.uf_testruns your tests with the access your project gives them. It needs the project installed — it stops at@uniflowed/hostis not installed otherwise — and on a host with nopermissionsdeclared, nothing restricts what a test reaches.- One call at a time. The server answers requests in order, so a long
uf_testholds up every call behind it, andnotifications/cancelledcancels nothing. - Warnings are not in the result. What a command or a test worker writes to stderr goes to the server's stderr, which the client may or may not show you.
- One protocol version. The server answers
2024-11-05whatever the client asked for.
Where to go next
That is the end of the toolchain. Build an app is next:
one guide per thing an application does — routes, data, actions, forms and UI.
Reference has every flag, config key and export, for when a guide
has told you which one you need, and IDE setup is the same lint
and formatting, pushed into an editor by uf lsp.
Edit this pagedocs/app/guide/agents/$page.mdx