The REST API and MCP server stay as Next.js route handlers in apps/api instead of Hono, oRPC, and a separate apps/mcp.
The repo standard asks for a Hono shell with an oRPC contract in packages/contract, and a separate apps/mcp built on orpc-mcp. CentraKit already ships more than 200 versioned route handlers under apps/api/app/api/v1/, an MCP server at /api/mcp, and an OpenAPI 3.2 document generated by next-openapi-gen from Zod schemas and route JSDoc.
Keep Next.js route handlers, next-openapi-gen, and the MCP server inside apps/api. Scalar stays at /api-docs. Errors follow RFC 9457 Problem Details, so the wire format already matches the standard.
ADR 0011 later moved the API onto each brand's app host and retired the Scalar page, because its /_next assets would collide with the SaaS app on a shared host. /api-docs now redirects to the REST API page in the docs, and the MCP server is served at /mcp. The rest of this decision stands.
The MCP server no longer uses mcp-handler. apps/api/lib/mcp/server.ts builds one McpServer per request with createMcpHandler from @modelcontextprotocol/server, and gates it with requireBearerAuth from the same package, so the 401 and insufficient_scope challenges come from the SDK. mcp-handler had become a thin wrapper over those two calls.
Tool names are now the OpenAPI operation IDs in snake case (list_tasks, not listTasks), as the repo standard asks. This is a breaking change for MCP clients that pinned tool names. Each tool declares an outputSchema built from the generated REST response schema; a response that is not an object is wrapped as { value }, which matches the structuredContent the executors already return.
Tool input and output are validated by the SDK through Standard Schema, using the same Zod schemas. The SDK's JSON Schema validator (ajv on Node, CfWorker on workerd) only runs for elicitation results and fromJsonSchema(), and the server uses neither, so no jsonSchemaValidator is configured. Pick one when the server starts sending form elicitations.
withOAuthProtectedResource from the standard is only partly adopted: the protected resource metadata route is still hand-written in apps/api/app/.well-known/oauth-protected-resource/route.ts, because it has to advertise the resource of the request's brand host. Host header validation from the SDK is not applied, because the API is reached through the brand host rewrites and is protected by OAuth on every call.
apps/api/public/openapi.json stays the contract, and CI fails when it drifts.apps/api/lib/mcp and do not mirror REST one to one.Last updated on