MCP
Exposes a devframe's agent-facing API as a Model Context Protocol server: coding agents call flagged RPCs and read resources.
import { createMcpServer } from 'devframe/adapters/mcp'
import myDevframe from './my-tool'
await createMcpServer(myDevframe, { transport: 'stdio' })createMcpServer serves stdio through the MCP SDK's serveStdio, pinning one server instance per connection.
Route-based server
The dev server exposes the same MCP API over HTTP, live. The default setting is 'auto': the route mounts once the devframe exposes an agent surface (an agent-flagged RPC, a registered tool or resource) - flag your first function and the agent view is on. A devframe with nothing flagged mounts no route and loads no MCP code.
Pin the behavior where you host the tool - it's a hosting decision, so pass mcp to createCac when you assemble the CLI (or to createDevServer / initDevframe / initHub when you host it programmatically): true always mounts, false never mounts, an object customises the route:
import { createCac } from 'devframe/adapters/cac'
import myDevframe from './my-tool'
createCac(myDevframe, { mcp: true }).parse() // force on; `false` forces off; omit for 'auto'The endpoint speaks Streamable-HTTP at /__mcp (/__<id>/__mcp under a host framework), sharing its origin/port. --mcp / --no-mcp override per run; __connection.json advertises the mounted route.
The endpoint is stateless: it serves the 2026-07-28 revision per request through the SDK's createMcpHandler, building a fresh MCP server for each request, so every HTTP request stands alone, with no Mcp-Session-Id to correlate. 2025-era clients are still served through the SDK's stateless legacy path.
Origin gate, and opt-in identity
The origin gate guards every request: Origin must be loopback (or allow-listed), and Origin-less requests are rejected (a disallowed origin gets 403). This is DNS-rebinding hardening that keeps browsers and remote hosts out, and it trusts same-machine callers - the 'auto' default and mcp: true both mount origin-only, all a local dev tool needs.
Origin proves nothing about who is calling, though: a native process on the same box can send any Origin. When a same-machine process isn't your trust boundary (a LAN/tunnel origin, a shared/CI host, a destructive tool surface), layer on an identity check with authorization:
createCac(myDevframe, {
mcp: { authorization: process.env.MY_TOKEN },
}).parse()authorization takes a bearer token (backed by an env var, never a literal), a (request) => boolean callback that governs identity only and cannot relax the origin gate, or false for the explicit origin-only default.
A request presents the bearer as Authorization: Bearer <token>, matched in constant time; a missing or wrong bearer gets 401 with a WWW-Authenticate: Bearer challenge. The origin gate always runs first, so a disallowed origin is 403 regardless of the credential. Widen the origin allow-list for a tunnel/LAN reach with mcp: { authorization: process.env.MY_TOKEN, allowedOrigins: ['https://tunnel.example.com'] }.
Never place the token in a URL, in __connection.json, in the instance registry, in logs, or on the command line; it belongs only in configuration and the Authorization header.
Hosted bridges
Both bridges forward the setting to their side-car dev server, advertising the mounted endpoint in __connection.json:
// Vite (@devframes/vite)
devframeViteBridge(myDevframe, { mcp: true })
// Next.js (@devframes/next)
createDevframeNextHandler(myDevframe, { mcp: true })Both honor the same contract: omitted is 'auto', true forces the origin-only route on; add mcp: { authorization } to harden.
Custom host frameworks
createMcpFetchHandler(ctx, options) returns the endpoint as a Request → Response handler plus a dispose(); mount it on any fetch server.
import { createMcpFetchHandler } from 'devframe/adapters/mcp'
const mcp = createMcpFetchHandler(ctx, {
serverName: 'my-tool (devframe)',
serverVersion: '1.0.0',
exposeSharedState: true,
// Optional identity check on top of the origin gate; omit for origin-only.
// authorization: process.env.MY_TOKEN,
})
// route every method on /__mcp to mcp.fetch(request)Discovery: devframe connect
The devframe bin ships an MCP connector (next-devtools-mcp-style) that finds every running devframe. Configure once:
{
"mcpServers": {
"devframe": { "command": "npx", "args": ["devframe", "connect"] }
}
}Two gateway tools (devframe:connect:* ids; see tool ids and wire names):
devframe_connect_list-instances: list running dev servers and their MCP tools.devframe_connect_call-tool: invoke one tool on a running devframe ({ port, tool, args }) over Streamable-HTTP.
Discovery reads the instance registry: every createDevServer writes ~/.devframe/instances/<pid>-<port>.json, dialed with a loopback origin. In-process host frameworks register via registerDevframeInstance (devframe/node). --port <n> probes a port; DEVFRAME_INSTANCES_DIR relocates the registry, DEVFRAME_DISABLE_INSTANCE_REGISTRY=1 opts out.
Most instances trust same-machine callers, so the connector reaches them with no credential. For an instance you hardened with a bearer, the connector reads DEVFRAME_MCP_AUTH_TOKEN and presents it (never a CLI flag, since command-line arguments are visible to other processes). Connect to a fleet with distinct credentials by driving startConnectServer with a per-instance authToken resolver.
See Agent-Native for the API and safety model.
Embedded
Register a devframe into an already-running context at runtime: dynamic, post-startup registration (unlike vite's plugin-scan). Inherits the hosted /__<id>/ default.
Frameworks
The framework kits (@devframes/vite, @devframes/nuxt, @devframes/next) integrate devframe with a meta-framework's dev server. Two subpaths: