You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implement the stateless Streamable HTTP endpoint handler at POST /api/v1/mcp. This fills in the empty route shell from #7426 with the per-request McpServer lifecycle, authentication enforcement, and the Fastify-to-MCP transport bridge. Tool definitions are not part of this task; the endpoint ships with an empty tool list and a registration hook for subsequent tasks to plug into.
Add PAT (Personal Acces Tokens) scopes #7411 (PAT scopes) is assumed to be implemented. PAT scope enforcement on individual tool calls is handled in the tool definitions task, not here.
Requirements
Endpoint handler
POST /api/v1/mcp: each request creates a fresh McpServer and StreamableHTTPServerTransport (stateless, sessionIdGenerator: undefined)
Call reply.hijack() then delegate to transport.handleRequest(request.raw, reply.raw, request.body). The third parsedBody argument is a supported SDK parameter for frameworks that pre-parse the request body.
After handling, call mcpServer.close() and transport.close() to clean up per-request resources
GET /api/v1/mcp and DELETE /api/v1/mcp return 405 (not applicable for stateless mode)
Authentication
verifySession (inherited from the parent EE routes hook) handles PAT resolution from the Authorization header, no additional auth wiring needed
Add a preHandler that rejects requests without a resolved request.session.User (e.g. project/device tokens, missing auth) with 401
Reject requests where the PAT has adminOptIn enabled with 403. Admin users can use MCP with tokens that have admin opt-in disabled (the token behaves as a regular user scoped to their team role). See Add PAT (Personal Acces Tokens) scopes #7411 for the admin opt-in model.
Errors return structured JSON with code and error fields, descriptive enough for an LLM agent to relay to the user
Tool registration hook
Expose a mechanism for tool definition modules to register themselves on the per-request McpServer
Tool modules export entries of { name, description, inputSchema, annotations, handler }, endpoint iterates and calls mcpServer.registerTool() for each
Ships with zero tools: tools/list returns empty array, tools/call rejects unknown tools
Tests
POST /api/v1/mcp with valid PAT returns a valid MCP JSON-RPC response (tools/list returns empty array)
POST /api/v1/mcp without auth returns 401
POST /api/v1/mcp with a PAT that has adminOptIn = true returns 403
POST /api/v1/mcp with a PAT owned by an admin but with adminOptIn = false succeeds (token behaves as regular user)
GET /api/v1/mcp returns 405
DELETE /api/v1/mcp returns 405
Stateless behavior: no session state leaks between sequential requests
References
MCP SDK: McpServer from @modelcontextprotocol/sdk/server/mcp.js, StreamableHTTPServerTransport from @modelcontextprotocol/sdk/server/streamableHttp.js
Summary
Implement the stateless Streamable HTTP endpoint handler at
POST /api/v1/mcp. This fills in the empty route shell from #7426 with the per-request McpServer lifecycle, authentication enforcement, and the Fastify-to-MCP transport bridge. Tool definitions are not part of this task; the endpoint ships with an empty tool list and a registration hook for subsequent tasks to plug into.Prerequisites
Requirements
Endpoint handler
POST /api/v1/mcp: each request creates a freshMcpServerandStreamableHTTPServerTransport(stateless,sessionIdGenerator: undefined)reply.hijack()then delegate totransport.handleRequest(request.raw, reply.raw, request.body). The thirdparsedBodyargument is a supported SDK parameter for frameworks that pre-parse the request body.mcpServer.close()andtransport.close()to clean up per-request resourcesGET /api/v1/mcpandDELETE /api/v1/mcpreturn 405 (not applicable for stateless mode)Authentication
verifySession(inherited from the parent EE routes hook) handles PAT resolution from the Authorization header, no additional auth wiring neededrequest.session.User(e.g. project/device tokens, missing auth) with 401adminOptInenabled with 403. Admin users can use MCP with tokens that have admin opt-in disabled (the token behaves as a regular user scoped to their team role). See Add PAT (Personal Acces Tokens) scopes #7411 for the admin opt-in model.codeanderrorfields, descriptive enough for an LLM agent to relay to the userTool registration hook
{ name, description, inputSchema, annotations, handler }, endpoint iterates and callsmcpServer.registerTool()for eachtools/listreturns empty array,tools/callrejects unknown toolsTests
POST /api/v1/mcpwith valid PAT returns a valid MCP JSON-RPC response (tools/listreturns empty array)POST /api/v1/mcpwithout auth returns 401POST /api/v1/mcpwith a PAT that hasadminOptIn = truereturns 403POST /api/v1/mcpwith a PAT owned by an admin but withadminOptIn = falsesucceeds (token behaves as regular user)GET /api/v1/mcpreturns 405DELETE /api/v1/mcpreturns 405References
McpServerfrom@modelcontextprotocol/sdk/server/mcp.js,StreamableHTTPServerTransportfrom@modelcontextprotocol/sdk/server/streamableHttp.jsverifySession:forge/routes/auth/index.js:65-165forge/ee/routes/mcpServer/index.js(reference only, not to be reused as-is)