A staging MCP server that worked perfectly on laravel/mcp 0.9 returns HTTP 400 on every single tools/call after a routine composer update. That is the shape of Laravel MCP 1.0 in one example: GitHub tagged v1.0.0 on September 14, 2026, the official Laravel account announced it on September 15, and the first stable release of the package for building Model Context Protocol servers in Laravel swaps the protocol out from underneath you — MCP revision 2026-07-28, stateless core, no more handshake, mandatory header validation on every request.
Here is the verdict before the why: upgrade promptly, but read the 1.0 upgrade guide before you run the update — then audit which tools your app exposes, because framework-native MCP just made every Laravel app you deploy a potential agent-called tool server. Three things break loudly (request headers, server-side sessions, OAuth without PKCE) while two new features (searchable tool catalogs, cache hints) change the economics of exposing tools at all. This post covers the concrete change-set first and the deploy-surface consequences second: what to fix this week, and what per-app MCP endpoints mean for anyone operating Laravel apps — or a platform that hosts them.
What 1.0 actually ships: MCP 2026-07-28, minus the handshake
The headline is the protocol upgrade. MCP 2026-07-28, finalized July 28, 2026, is the largest revision since Anthropic open-sourced the protocol: the core is now stateless, the initialize/initialized handshake and the Mcp-Session-Id header are gone (SEP-2575, SEP-2567), and every request carries the protocol version plus client info and capabilities in a params._meta envelope. Capability discovery moves to an on-demand server/discover RPC, server-initiated elicitation and sampling give way to Multi Round-Trip Requests, and Sampling, Roots, and Logging enter a minimum 12-month deprecation window.
| Behavior | 0.x (2025-06-18 / 2025-11-25) | 1.0 (2026-07-28) |
|---|---|---|
| Capability discovery | initialize/initialized handshake | server/discover RPC, no handshake |
| Session state | Mcp-Session-Id header, server-side session | None — each request stands alone |
| Client identity | Established once at handshake | params._meta on every request |
| Gateway routing | No standard | Mcp-Method / Mcp-Name mirrored headers (SEP-2243) |
| List caching | Ad hoc | ttlMs cache hints from the server |
| OAuth posture | PKCE expected, Dynamic Client Registration normal | PKCE mandatory, Client ID Metadata Documents preferred, DCR deprecated |
On the Laravel side, statelessness deletes API surface outright: the MCP-Session-Id header, Request::sessionId(), Request::setSessionId(), and the SessionInitialized event are removed, along with the Server::CAPABILITY_UI constant. MCP Apps support moves under the extensions capability. If you tracked related calls through the session, you now pass your own identifier in the request arguments or _meta.
Clients that still connect with initialize keep working — the server answers with protocol version 2025-11-25 or 2025-06-18 depending on what the client asks for — so the break is migration-shaped, not flag-day-shaped.
The break most people will meet first is header validation. The new ValidateMcpHeaders middleware runs on every route registered through Mcp::web(): POST requests under the new protocol need MCP-Protocol-Version and Mcp-Method headers that match the body, and calls to tools/call, prompts/get, and resources/read additionally need Mcp-Name matching the tool or prompt name (or the resource URI). A mismatch returns HTTP 400 with JSON-RPC error code -32020.
Tests that exercise these routes with postJson() need the headers and the params._meta fields too — that is exactly the "green suite turns red after composer update" failure from the opening paragraph. Older initialize-style clients that send no protocol metadata in _meta are exempt from validation, which makes the failure look intermittent until you know what to look for.
Searchable catalogs and cache hints: context-window economics
Every tool definition you hand a model occupies room in its context window, so the size of your default tool list is now a per-session cost line. ToolSearch is the release's answer: keep the common tools in the main list and put the rest behind search.
class WeatherServer extends Server
{
protected array $tools = [
// Always available to the agent...
CurrentWeatherTool::class,
// Searched only when needed...
ToolSearch::class => [
HistoricalWeatherTool::class,
WeatherAlertsTool::class,
],
];
}The package registers two tools to serve this pattern. search_tools takes a query plus a result limit and returns matching tools with their names, descriptions, and expected inputs; execute_tools runs one or more tools by name. The agent finds and uses a tool without ever loading the whole catalog — which matters the moment a server grows past a handful of tools. Community servers already run large: one QuickBooks integration exposes 50 MCP tools across 11 entities, and dumping all of that into every agent session's tools/list response is exactly the cost this feature removes.
Cache hints attack the same bill from the other side. A server can now declare which responses clients may reuse, for how long, and whether they can be shared across users: set a default with the #[Cacheable(ttlMs: 60_000, scope: CacheScope::Public)] attribute, override per method with cacheHints(), and Laravel's MCP client honors the hints when caching is enabled with withCache(). Two rules to internalize: responses with a missing or zero ttlMs are never cached, and tool calls are never cacheable — only discovery-shaped reads like tools/list are. That second rule is a correctness guard disguised as a limitation: caching the result of execute_invoice_refund would be a Category 5 footgun, so the protocol takes it off the table entirely.
OAuth grows teeth: mandatory PKCE and client metadata documents
Authorization is the third loud break. OAuthClient::redirect() now throws an OAuthException when the authorization server's metadata omits code_challenge_methods_supported entirely; previously it only rejected servers that advertised the field without S256 support. If your identity provider's discovery document is stale or minimal, the upgrade surfaces that as an exception rather than a silent downgrade — check the metadata before you deploy, not after.
Client identification gets a new preferred path alongside the stick. With Client ID Metadata Documents, your client_id is an HTTPS URL pointing at a JSON document that describes the client, served by Mcp::oAuthRoutesFor() at GET /mcp/oauth/{client}/client-metadata.json. Laravel uses the document when the authorization server supports it and falls back to Dynamic Client Registration — which MCP 2026-07-28 deprecates — otherwise. Two migration details come with it: $token->clientSecret is null under metadata documents, so any database column storing it must accept null, and the same change fixes a bug that re-registered a new client on the authorization server every time redirect() ran. The ecosystem is already moving: Statamic's MCP integration cut a v3.0.0 release pinning laravel/mcp to ^1.0 within days of the stable tag.
The platform reading: every tenant app is now an MCP server
Those four sections are the "what changed." Here is the "why it matters" for anyone who deploys Laravel apps — or operates a platform that hosts other people's.
Until this release, an MCP server was a separate thing you built and deployed deliberately. A framework-native, versioned, first-class MCP package inverts that default: any Laravel app can now expose agent-callable tools with a server class and a route registration. On a git-push PaaS, the platform's own deploy-and-operate tool surface is no longer the only agent interface in the stack — it has to compose with, and compete for agent attention against, per-app tool catalogs defined by every tenant.
Statelessness sharpens both edges. With no handshake and no session affinity, a remote MCP server is just a standard HTTPS endpoint that scales behind a plain round-robin load balancer (AWS's AgentCore Gateway makes exactly this pitch for the new spec), so there is no longer an infrastructure reason not to expose one. Expect per-app MCP endpoints to become as ordinary as /api routes within a year.
That makes the following checklist the real deliverable of this post. Work through it for every Laravel app you ship — and, if you run the platform underneath, decide which rows are your tenants' responsibility and which ones you enforce:
| # | Check | What to verify | What breaks if you skip it |
|---|---|---|---|
| 1 | Header contract | Mcp::web() clients send MCP-Protocol-Version, Mcp-Method, and Mcp-Name matching the body | HTTP 400 / -32020 on every new-protocol call |
| 2 | Session removal | Nothing depends on server-side sessions; correlation uses your own IDs in args/_meta | Silent logic breaks, not just loud errors |
| 3 | Catalog scoping | Large servers hide rarely used tools behind ToolSearch; audit the default list | Context-window cost on every session, plus needless attack surface |
| 4 | OAuth posture | Provider metadata advertises PKCE S256; client_secret columns nullable | redirect() throws; token storage fails after upgrade |
| 5 | Per-tool authorization | Enforce who-may-call-what inside your tool handlers | The MCP spec defines no per-tool authz standard — every connected agent can call every tool by default |
| 6 | Abuse shape | Tenant-defined tools that reach platform APIs are sandboxed, scoped, and rate-limited | A tenant tool becomes a confused deputy: the Azure DevOps MCP server already shipped this exact flaw, returning pull-request text verbatim until prompt-injected content could hijack the agent session |
| 7 | Tool-call observability | Log tools/call like admin-API calls, with caller identity and arguments | Your first prompt-injection incident arrives with no audit trail |
Rows 5 through 7 deserve emphasis because no upgrade guide covers them. Treat an MCP server like an internal admin API being driven by an untrusted user — because that is what it is. The agent follows instructions it reads inside documents, tickets, and tool responses, so every tool that sends, publishes, pays, or deletes needs its own authorization check and its own log line. "It is behind auth" is the hand-waving that works right up until the first prompt-injection incident becomes a tool-permission incident.
What to do this week
The work splits into the upgrade and the audit, in that order. First, read the 1.0 upgrade guide end to end, fix session-dependent code and header-less tests, and verify your authorization server's PKCE metadata — the three loud breaks are all cheap to find before deploy and all embarrassing after. Then audit the tools you expose: shrink the default catalog behind ToolSearch, confirm cache hints only cover discovery-shaped reads, and add per-tool authorization plus call logging to every handler that touches something irreversible.
The longer arc is what the TODO item for this post called the deploy surface agents will call. Laravel is now the latest mainstream web framework to ship agent-operability as a versioned package feature instead of a community plugin, and the pattern will not stop here: first-class MCP servers in every framework, per-app tool catalogs as ordinary as route files, platform surfaces judged on how well they compose with tenant-defined tools rather than on being the only interface. Operators who build the checklist habit now — scope the catalog, enforce per-tool authz, log the calls — get to treat that future as scaling work. Everyone else gets to treat it as incident response.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Agents are first-class operators there: machine-readable infrastructure state and an MCP surface for deploy-and-operate workflows. Star the repo on GitHub or deploy your first app today.



