Coding agents
Nifra ships an MCP server. It is not a documentation lookup bolted onto a framework: an agent can read the project, learn the real API, run actual requests against the backend it just edited, and finish with a report of what the change proved.
One MCP, two ways to connect
There is one Nifra MCP. It reaches your agent over the two standard MCP transports, and which one you use is decided by a single question: is the agent working inside a Nifra repo?
Local - nifra mcp | Hosted - mcp.nifra.dev | |
|---|---|---|
| Transport | stdio - the agent spawns it as a process | HTTP - add one URL, nothing to install |
| Runs | on your machine, in your project | on our infrastructure |
| Sees | your routes, schemas, and files - locally only, nothing leaves the machine | only Nifra's published docs corpus - never your code |
| Tools | everything: project tools (nifra_context, nifra_run, nifra_assure, …) plus the docs tools | docs tools only (nifra_docs, nifra_example, nifra_types, nifra_learn) |
| Use when | building or editing a Nifra app | learning Nifra, or the client can't spawn processes (Claude.ai, ChatGPT) |
The project tools must run where your code is - hosting them would mean uploading your source, which is exactly what this design refuses to do. And the local server does not proxy the hosted one for docs: the docs corpus ships inside the npm package, so the answers match the Nifra version installed in your project, work offline, and send nothing anywhere. This hosted-plus-local pairing is the same shape Supabase, Stripe, Sentry, and GitHub ship their MCP servers in, for the same reason: public knowledge can be hosted; tools that touch your own code and data run where that code lives.
So: inside a Nifra repo, register the local server (it includes the docs tools - you never need both). Anywhere else, add the URL.
Setup
# Registers the MCP server and writes the agent files. Never clobbers what is already there.
nifra init-agents
# .mcp.json the server, for Claude Code and anything else reading that format
# .cursor/mcp.json the same, for Cursor
# CLAUDE.md project conventions an agent reads on entry
# AGENTS.md the same, in the vendor-neutral format
# Then restart the agent so it picks the server up.The generated files are additive and the command will not overwrite an existing one, so it is safe to re-run after a Nifra upgrade.
The loop
# 1. What is here?
nifra_context # routes, page routes, conventions - one call, unfiltered, as an index
nifra_routes # API routes as JSON: { method, path, call, body?, query?, response? }
# 2. What is the real API?
nifra_docs {query} # searches the docs; returns only matching sections
nifra_example{task} # a snippet that is typechecked against the live API
nifra_types {name} # the exact declaration, parsed from the built .d.ts
# 3. Write the code, then close the loop.
nifra_check # typecheck + lints, each with a structured fix
nifra_fix # applies the mechanical ones
nifra_run {request} # a real request through the backend: status, headers, parsed body
nifra_render {path} # SSR a page route, returns the HTML
nifra_test # bun test, bounded structured results
# 4. What does it now prove?
nifra_assure # every route's required evidence, and what is missing
nifra_levels # { achieved, levels[] } - the ladderWhy the answers can be trusted
The three corpora an agent learns from are generated from the built packages, not written by hand. nifra_types is parsed out of each package's .d.ts, so a signature it returns is the signature that shipped. nifra_example serves only snippets that the docs gate compiles against the live API, so it cannot hand back a call that no longer exists. Both are regenerated and verified in CI, which is what makes them worth more to an agent than its own memory of the framework.
nifra_types also follows each package's exports map rather than scanning the build output, so it will not offer a type that is real but unimportable.
From any assistant, hosted
The teaching tools - nifra_docs, nifra_example, nifra_types, nifra_learn - are also served, project-independent, at mcp.nifra.dev. Add that one URL to any assistant and it learns Nifra from the same verified corpora, with no local checkout. It is read-only and needs no key. The project tools above still come from nifra mcp in your own repo, where they can see your routes.
# Claude Code
claude mcp add --transport http nifra-docs https://mcp.nifra.dev
# Claude.ai (web or desktop): Settings -> Connectors -> Add custom connector -> https://mcp.nifra.dev
# ChatGPT (developer mode): Settings -> Connectors -> add the same URL
# Cursor - .cursor/mcp.json
# { "mcpServers": { "nifra-docs": { "url": "https://mcp.nifra.dev" } } }
# VS Code - .vscode/mcp.json
# { "servers": { "nifra-docs": { "type": "http", "url": "https://mcp.nifra.dev" } } }Cursor reads it in one click: add Nifra docs to Cursor.
The gate to write against
nifra_check is the one an agent should loop on. It returns structured diagnostics, each carrying its own fix, so a failure is a work item rather than a wall.
{
"ok": true,
"typecheck": "pass",
"diagnostics": []
}It catches the drift that types alone miss: a hand-rolled fetch() to your own API instead of the typed client, a client(...) missing its type argument, a server-only import reaching a route module, a route manifest that no longer matches routes/.
Knowing when to stop
Passing tests say the code does what its tests say. nifra_levels answers the different question of what the project holds, as a cumulative ladder from a typed contract (L0) to contract-derived invariant tests (L4). A scaffolded app starts at L1, and each rung it has not reached reports the specific thing missing - so the ladder doubles as the list of what to do next. See the verification ladder.
# The two an agent should gate on. Both exit non-zero on failure, so they work in CI unchanged.
nifra check
nifra levels --min 1Building agent features, not just serving them
The tools above let an agent work on your app. Three packages are for the opposite case, where the app you are building is itself an AI feature.
@nifrajs/prompt binds an instruction to input and output schemas, so a model's reply is parsed before it becomes a value. Provider-agnostic - you supply the completion call, it owns the contract.
// doc-check: skip - the completion callback is yours; any provider SDK fits the shape.
import { prompt } from "@nifrajs/prompt"
import { t } from "@nifrajs/schema"
const extract = prompt("Extract the contact from the text.")
.input(t.object({ text: t.string() }))
.output(t.object({ name: t.string(), email: t.string({ format: "email" }) }))
// The result is PARSED against the output schema, so a malformed completion throws here
// rather than becoming a wrong value three layers away.
const contact = await extract.run({ text }, { complete })@nifrajs/agent-telemetry adds child spans for tool calls on /_nifra/tool/* and the MCP endpoints, so an agent-facing route is as observable as any other.
import { server } from "@nifrajs/core/server"
import { agentTelemetry, consoleAgentExporter } from "@nifrajs/agent-telemetry"
export const app = server().use(agentTelemetry({ exporter: consoleAgentExporter() }))@nifrajs/mcp-db serves a SQLite database as its own MCP server, fail-closed: allowlisted schema tools by default, and read-only queries only when you opt in, with plan verification. Handing a model a database connection is not the same as handing it a query tool, and this is the second one.
Projects without a web config
A backend-only project - the shape create-nifra's default template produces - has no nifra.config.ts and no routes/ directory. The server starts anyway and serves every tool that does not need a loaded app: the docs and type corpora, nifra_check, nifra_doctor, nifra_levels, nifra_test. The page-oriented tools report that they need a web app when called, rather than the session failing to open.
Monorepos
Point the server at the repository root and it discovers each workspace app, namespacing that app's own .tool() declarations so two apps exposing the same tool name stay distinct.