Repository navigation
feat: MCP server — API versions as tools for agents - #20
Merged
Merged
Conversation
With laravel-api.mcp.enabled, POST /{prefix}/mcp/{version} speaks MCP
(Streamable HTTP, JSON answers, stateless): initialize, ping, tools/list,
tools/call. One tool per action (controller.action), input schema from
the descriptor with templates under $defs, GET marked read-only. Calls go
through Runtime\Dispatcher as the caller of the MCP request. 'mcp' =>
false opts out; Origin is checked. api:cache caches the tool lists
(cache format 2); a call finds its action via the route map.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 26, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stage 2, step 2: the first non-HTTP transport, on top of
Runtime\Dispatcher(#19).What
With
laravel-api.mcp.enabled, every served version is an MCP server atPOST /{prefix}/mcp/{version}— Streamable HTTP with JSON answers, stateless (noMcp-Session-Id; GET/DELETE → 405).McpServer(JSON-RPC 2.0, no HTTP):initialize(protocol negotiation over2025-11-25,2025-06-18,2025-03-26;serverInfo=mcp.nameor the app name + the API version;instructions= the Api class docblock),ping,tools/list,tools/call. Notifications/responses → 202, no body. Batches →-32600; unknown method-32601; unknown tool / bad arguments-32602; bad JSON → 400-32700.McpToolset: one tool per action,controller.action(not the operationId:a_b+canda+b_ccollide).title= first line of the summary,description= summary + description (Deprecated.prefix when deprecated),inputSchema=InputDeclaration::schema()— templates moved under$defswith refs rewritten (#/components/schemas/…only resolves inside OpenAPI), a@Modelbody wrapped as{type: object, allOf: [$ref]}, empty input{type: object, properties: {}}. GET →annotations.readOnlyHint.'mcp' => falseon an action or controller leaves it out.tools/call→Dispatcher(router, middleware cascade, validation; the MCP request's headers — e.g.Authorization— passed on) →content: [{type: text, text: <payload JSON>}],structuredContentwhen the payload is an object,isError=!success. A failed action is a tool result, not a protocol error, so the model can correct the call.McpController: served versions (mcp.versions, default = all not hidden), Origin check (spec: against DNS rebinding) — no Origin passes, own origin passes,mcp.allowed_origins(*for any);mcp.middlewareon the endpoint.laravel-api.mcp(enabled/LARAVEL_API_MCP,versions,path,middleware,allowed_origins,name). Routeapi-mcpregistered only when enabled (ApiServiceProvider::registerMcpRoutes()).Performance (500 actions, OPcache)
tools/list, coldtools/list, fromapi:cachetools/calllookupapi:cachenow stores the tool lists of every served version, keyed by version and Api class. Cache format 1 → 2: a file written by 6.1 is ignored (as if there were none) until the nextapi:cache.ApiCache's exportability check acceptsstdClass(var_export writes it as(object) array(...), needed for{}).Deliberately not offered
resources, prompts, sampling, SSE streaming, sessions, file uploads;
outputSchema—@outputisn't trusted to be exact enough for clients that validatestructuredContentagainst it.Docs
README (en/ru/zh/de) "MCP server" with a
claude mcp add --transport http …example; config comments; CHANGELOG; CLAUDE.md.Tests
623 passing; golden-master unchanged. New
tests/Feature/Transports/McpTest.php(15): off by default; initialize + version negotiation + instructions; notification 202; ping{}; tools/list (names, readOnlyHint, middleware input in schema, title = first summary line); tools/call result shape; middleware cascade and header forwarding; JSON-RPC errors (unknown tool/method, batch, parse); 405s; served versions; Origin;'mcp' => false;$defs(incl. a template referring to another); api:cache stores tools (and{}survives var_export, other class not served from it); tools/list served from the cache while calls still resolve.🤖 Generated with Claude Code