DocsWorking with Rahti
MCP server
Rahti gives opted-in projects local stdio and bearer-protected Streamable HTTP MCP: nine read-only tools, public service context, project metadata and conventions, without acting as a signed-in application user.
A coding agent opening a Rahti project spends its first minutes doing what a grep can only half do: working out which files became routes, which functions became RPCs, which of those require a session, and whether the generated manifest still matches what is on disk. The build step already knows all of it — it is what wrote the manifest — so the answers exist before anybody goes looking for them.
rahti-mcp hands them over through two transports. Local clients launch the standalone stdio executable. An opted-in application mounts the same read-only server over Streamable HTTP at /mcp/. Both can inspect configuration, the generated manifest, conventions, public service information and a bounded tail of the development log.
Asking for it
MCP is a scaffold feature like a database or WebSockets: name the flag and the project carries it, leave it out and the project has never heard of it. An interactive cargo rahti new asks about whatever you did not name, defaulting to no; a run with nowhere to ask takes the flags at their word.
# A new project with local and remote MCP support.
cargo rahti new my-app --tailwind --mcp
# A project that started without it.
cargo rahti upgrade --mcpThat records "mcp": true, enables rahti's optional mcp Cargo feature, writes .mcp.json for local clients, creates an editable public/llms.txt, and provisions the HTTP bearer-token settings in the ignored .env.
{
"mcpServers": {
"rahti": {
"command": "rahti-mcp",
"args": ["--project", "."]
}
}
}Installing the server
Once per machine, the way cargo-rahti is installed once per machine, and only for local stdio clients. It rides the framework's release line, so its version moves with rahti — upgrade it alongside the scaffolder.
cargo install rahti-mcp # once per machine, like cargo-rahti
# What the client runs for you. Useful once, by hand, to see it start.
rahti-mcp --project /path/to/projectThen point an MCP client at the project. Clients that read a project-local .mcp.json find the configuration the scaffold already wrote and need nothing else; a client configured elsewhere wants the same command and the same argument. The path must name a directory containing rahti.config.json, which is how the server decides it is looking at a Rahti project at all — without one it refuses to start rather than serving an empty answer.
Remote agents and discovery
The application publishes a discovery document at /.well-known/mcp.json. Its endpoint is deliberately relative, so a reverse proxy cannot downgrade an HTTPS request by supplying the wrong scheme. /mcp redirects with 307 to the canonical /mcp/, preserving the request method and body.
{
"name": "rahti",
"transport": "streamable-http",
"endpoint": "/mcp/",
"authentication": {
"type": "bearer",
"environment": "RAHTI_MCP_AUTH_TOKEN"
},
"siteInfo": "/llms.txt"
}Set a long random RAHTI_MCP_AUTH_TOKEN in the deployment environment and configure the remote client with /mcp/ plus that bearer token. Development permits an unset token for first-run convenience; a release build without one fails closed with 503. A missing or incorrect credential receives 401 and a Bearer challenge.
# Required for the deployed Streamable HTTP endpoint.
RAHTI_MCP_AUTH_TOKEN="generate-a-long-random-secret"
# Comma-separated deployment hostnames and browser origins when needed.
RAHTI_MCP_ALLOWED_HOSTS="example.com"
RAHTI_MCP_ALLOWED_ORIGINS="https://example.com"Public service information
public/llms.txt is intentionally public and editable. It is the short answer to ‘what does this service offer?’ before an agent authenticates. The site_info tool and rahti://site/llms resource return the same text after MCP authentication. Keep offers, audience, constraints, pricing approach and contact paths there; keep credentials and private implementation details out.
# My application
> A concise public description of the service for people and agents.
- Home: /
- MCP discovery: /.well-known/mcp.json
- MCP endpoint: /mcp/
## Services
Describe the services, ideal customers, deliverables, constraints,
pricing approach, and contact path. Never put secrets here.The tools
Nine, each one a question with a JSON answer. Project-structure tools read the generated .rahti/manifest.json and rahti.config.json, which means they describe the project as the last build saw it — build again and ask again to see an edit.
| Tool | Answers |
|---|---|
| project_summary | What this project is: scaffold version, app directory, whether WebSockets and a database are configured, counts of routes, layouts, components, sockets and RPCs, and where the authentication boundaries lie. |
| list_routes | Every generated page and API route, with the file each came from. |
| list_components | Application components under src/components/, and the component-owned RPCs each declares. |
| list_rpcs | Page and component RPCs together, each carrying whether it requires a session. |
| list_sockets | WebSocket endpoints, each carrying whether the handshake requires a session. |
| list_database_artifacts | The configured SeaORM backend, the model files, and the migration files. |
| site_info | The public agent-facing service description served from /llms.txt. |
| validate_project | Whether every file the manifest references still exists, and whether the manifest was written by rahti-build. Reports; changes nothing. |
| recent_diagnostics | The newest entries from .rahti/dev.log — 50 by default, 200 at most. See Diagnostics. |
{
"projectRoot": "/home/you/my-app",
"createdWith": "0.0.24",
"appDir": "src/app",
"webSocketsEnabled": true,
"database": { "backend": "sqlite" },
"counts": {
"routes": 14,
"layouts": 3,
"components": 9,
"sockets": 1,
"pageRpcs": 6,
"componentRpcs": 2
},
"authentication": {
"routePolicy": "runtime",
"routePrivacyKnown": false,
"rpcAndSocketRequirementsKnown": true,
"mcpTransport": "stdio-local",
"browserSessionUsed": false
}
}The authentication block in that answer is the honest part. Whether an RPC or a socket demands a session is generated metadata, so the server knows it exactly. Whether a page is private is not: that policy is ordinary Rust the application installs at runtime, which no build-time tool can read. So the summary reports routePrivacyKnown: false rather than guessing, and validate_project repeats it as a warning.
{
"valid": true,
"checkedFiles": 27,
"errors": [],
"warnings": [
"Private route policy is installed at runtime and is not inferred from source."
]
}The resources
Tools answer questions; resources are documents a client can read whole. There are three of the project's own, and one per convention document:
rahti://site/llms— the same public service description served at/llms.txt;rahti://project/config— this project'srahti.config.json, with anything that looks like a secret removed on the way out;rahti://project/manifest— the generated.rahti/manifest.json, which is the build step's own account of what it found;rahti://conventions/<name>— one per file indocs/conventions/, the same documents this site is written from and the same copiescargo rahti upgradekeeps current.
That last one is the reason a project scaffolded with MCP is quicker to work in than one without. The rules an agent has to follow are not summarised for it — they are handed to it verbatim, from the project it is editing, at the version that project is actually on.
What it will not do
A tool that reads a project is a tool worth being precise about. The boundary is not a setting; it is what the server was built without.
| It never | Because |
|---|---|
Reads .env | Signing secrets and connection strings are credentials. The server has no code that opens that file, so there is no configuration that turns this on. |
| Writes source or generated output | Every tool is a read. validate_project reports a mismatch rather than repairing it — repairing is what a build is for. |
| Carries a browser session | Neither transport inherits a signed-in visitor's cookie, so MCP cannot call an #[rpc(auth)] or open a #[socket(auth)] as that user. |
| Makes outbound requests | The HTTP transport accepts MCP requests through the application router; its project tools still read local generated information and make no outbound network calls. |
| Serves an unbounded log | recent_diagnostics is capped at 200 entries and reads the tail, so a long development session cannot flood a client's context. |
Using it well
The server earns its keep at the two moments an agent is otherwise guessing. First, at the start of a task: project_summary and list_routes describe the shape of the application in one round trip, and the convention resources say how it is meant to be written. Second, after a change: validate_project catches a manifest that no longer matches the tree, and recent_diagnostics surfaces the browser warnings and handled backend failures that a build error would never mention.
None of it replaces the documents in the project. AGENTS.md and docs/conventions/ are written into every project, MCP or not — this is the same material with an index and a live view of the build attached. A project without MCP loses the index, not the rules.
