Route & Transport
The MCP server endpoint and its supported transport.
Endpoint
Handoff serves MCP from a single route: /api/mcp. GET, POST, and DELETE all funnel
into the same handler, the transport, not the HTTP method, distinguishes request types (see
API → /api/mcp).
Transport
The route uses the MCP SDK's WebStandardStreamableHTTPServerTransport, configured
stateless (sessionIdGenerator: undefined). There is no SSE transport and no session
affinity, every request builds a fresh server instance and is handled independently. This
also means there's nothing to "resume": a client reconnecting after a network blip just makes
a new request.
Correction vs. earlier design notes
The original MCP RFC describes a Streamable HTTP / SSE transport. The shipped implementation is stateless Streamable HTTP only: no SSE. If you're integrating a client, target Streamable HTTP.
// src/app/app/api/mcp/route.ts
export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'
async function handleMcp(request: Request) {
const auth = verifyHandoffApiAuth(request) // see Authentication
const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined })
const server = createHandoffMcpServer(auth, request) // fresh McpServer per request
await server.connect(transport)
return transport.handleRequest(request)
}Auth happens before the server is built
verifyHandoffApiAuth(request) runs first; the resolved auth context (user id, role, scope
string) is passed into createHandoffMcpServer(auth, request) and closed over by every tool
handler, so scope checks happen per-tool-call, not per-connection. See
Authentication for the two modes (workspace vs. registry) and the OAuth
flows.
Project context hydration
Handoff doesn't hydrate a session on connect, because there is no persistent session. Instead,
the first tool an agent typically calls is handoff_get_project_context, which reads:
- The active stack profile (
bootstrap-handlebarsby default, or whateverHANDOFF_DEFAULT_STACK_PROFILE/ the caller'sstackProfileargument resolves to), see Stack guides. HANDOFF_PROJECT_NAMEandHANDOFF_FIGMA_PROJECT_IDfor project identity.- The workspace's saved Design.MD / brand voice / component reference settings.
These are plain deployment-level environment variables (Vercel env / .env), set independently
of handoff.config.js, the MCP hydration layer (project-profile.ts) reads them directly and
does not parse the config file itself. The Config type does declare a projectProfile /
project_profile block, but nothing reads it, the env vars are the only wired path. See
Workspace → handoff.config.* resolution for how the
config file itself is located and read for everything else.