laravel/mcp 1.0 upgrade guide: what breaks, what to fix
Contributed by pushpak1300
Part of the mcp v1.0.1 release
Pull request #319 adds the official UPGRADE.md for the 1.0 release of laravel/mcp, and it is worth reading in full even if you plan a quick bump. The guide codifies the breaking changes between 0.9 and 1.0: a single supported protocol revision, a new server/discover handshake, mandatory HTTP headers, the removal of session IDs and the SessionInitialized event, tightened OAuth behavior, and a handful of smaller API cleanups. If you are upgrading from v0.9.6, here is what the new guide means for your application, roughly in order of how likely each change is to affect you.
One protocol revision, and no more initialize handshake
This is the highest-impact change. Version 1.0 speaks only protocol revision 2026-07-28; the 2025-11-25, 2025-06-18, 2025-03-26, and 2024-11-05 revisions are no longer accepted. The classic initialize handshake is gone entirely, replaced by a server/discover method, and every subsequent request must now carry the protocol version and client capabilities in its own params._meta.
If your application only defines Tool, Resource, Prompt, and Server classes, nothing in your code changes — but your MCP client must support the new revision. The bundled Laravel\Mcp\Client and the MCP Inspector both do. Older clients that only speak the initialize flow will now fail with a -32601 method-not-found error, and re-registering initialize via addMethod() will not rescue them, because every request still has to pass the protocol metadata check. In short: upgrade your clients first, then your server.
MCP HTTP headers are now mandatory
Every route registered with Mcp::web(...) gains a new ValidateMcpHeaders middleware. Each POST must include MCP-Protocol-Version and Mcp-Method headers matching the request body, and tools/call, prompts/get, and resources/read requests additionally need an Mcp-Name header matching params.name (or params.uri for resources). A mismatch returns HTTP 400 with JSON-RPC error -32020.
This applies to your test suite too, which is the change most teams will hit first:
$message = [
'jsonrpc' => '2.0', 'id' => 1, 'method' => 'tools/call',
'params' => [
'name' => 'say-hi',
'arguments' => [],
'_meta' => [
'io.modelcontextprotocol/protocolVersion' => '2026-07-28',
'io.modelcontextprotocol/clientCapabilities' => [],
],
],
];
$this->postJson('mcp-endpoint', $message, [
'MCP-Protocol-Version' => '2026-07-28',
'Mcp-Method' => 'tools/call',
'Mcp-Name' => 'say-hi',
]);
Sessions, events, and error codes
Request-scoped state is out. Request::sessionId() and setSessionId(), along with the MCP-Session-Id header, have been removed — every HTTP request and stdio message is now processed independently. There is no replacement, so if you need to correlate calls, thread your own identifier through the request arguments or _meta. The SessionInitialized event has also been removed with the handshake, so delete any listeners referencing it.
On errors, resources/read for an unresolvable URI now returns -32602 instead of -32002, and a new Laravel\Mcp\Enums\ErrorCode enum defines -32020 for header mismatches and -32022 for unsupported protocol versions. If your code compares numeric error codes, switch to matching against the enum.
OAuth: PKCE is enforced, and client IDs become URLs
Two related changes affect OAuth integrations. First, PKCE is now strictly required: OAuthClient::redirect() throws an OAuthException if the authorization server's metadata omits code_challenge_methods_supported, and there is no opt-out. Publish "code_challenge_methods_supported": ["S256"] on servers you control, or use clientCredentials() for third-party servers where appropriate.
Second, MCP 2026-07-28 deprecates Dynamic Client Registration in favor of Client ID Metadata Documents. When a metadata document is used, your application is a public client: client_id becomes an HTTPS URL and clientSecret becomes null. Any database column storing the secret must be nullable, and any code passing it to refreshCredentials() must handle null. As a bonus, this fixes a bug where every redirect() call performed a fresh dynamic registration.
Smaller changes worth a quick grep
A few low-likelihood items round out the guide. The bare ping method is gone in favor of server/discover — update health checks accordingly. Server::CAPABILITY_UI has been replaced by a nested extensions capability configured via a $extensions = [Extension::Ui] property. Custom transports should move setProtocolVersion() to the new UsesProtocol interface (the bundled transports need no changes). Finally, new TransportException and TimeoutException classes extend ClientException, so broad catch blocks keep working, though JsonRpcException remains a sibling rather than a child.
Takeaways
- Bump
laravel/mcpto^1.0and confirm your MCP clients support protocol revision2026-07-28before deploying. - Update tests and HTTP clients to send
MCP-Protocol-Version,Mcp-Method, andMcp-Nameheaders plusparams._metaon every request. - Remove
sessionId()usage andSessionInitializedlisteners; pass your own correlation IDs instead. - Make
client_secretstorage nullable and publish PKCE metadata on authorization servers you control. - Grep for
ping,CAPABILITY_UI, and customTransportimplementations — the fixes are small but easy to miss.