NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Packagist · #647 most downloaded on Packagist
Model Context Protocol SDK for Client and Server applications in PHP
Last release 1 months ago
29 Aug 2026
Ships fairly regularly
a new release about every 5 weeks
Nearly every release is documented
notes for 12 of 12 stable releases
Nothing withdrawn
no release was ever pulled
10 months old
12 releases · first in 2025
One column per month.
chore(deps): bump astral-sh/setup-uv from 9.0.0 to 10.0.1 by @dependabot [bot] in #483
Full Changelog: v0.8.0...v0.8.1
[Schema][Client][Server] Deprecate Roots, Sampling and Logging (SEP-2577) by @chr-hertel in #427
With v0.8.0 we are shipping support for the 2026-07-28 Model Context Protocol specification, which includes a major rework on HTTP transport going stateless, while keeping older versions of the specifications alive and mostly compatible in userland code. Find a detailed CHANGELOG in the repository, and per PR below.
structuredContent on the protocol revision by @chr-hertel in #417Full Changelog: v0.7.1...v0.8.0
initialize: the server counter-offers a revision it supports and the client fails the handshake instead of continuing on an unagreed one. Adds Client::getProtocolVersion().StreamableHttpTransport classifies each request (InboundClassifier) and routes it to the matching dispatcher, so one URL answers modern and handshake-era clients alike. Builder::withoutModernEra() opts out, Builder::setModernVersions() narrows the modern leg.Client opens with server/discover, stamps _meta with version, capabilities and client info, and sends the Mcp-Method/Mcp-Name/Mcp-Param-* headers (via Client\Stateless\ToolCatalog). Schema\Wire\McpHeader holds the shared header names.InputRequiredResult yields resultType: "input_required" with an opaque, signed requestState (RequestStateCodec, key via Builder::setRequestState()); the client retries with inputResponses, read through RequestContext::getInputContext() and its typed elicitResult()/samplingResult()/rootsResult(). On the client, InputRequestResolver answers such results automatically from the host's elicitation, sampling and roots handlers.ClientGateway::elicit()/elicitUrl() on every revision: where the client cannot be asked mid-request, Server\Stateless\ElicitationReplay turns the ask into input_required and resumes once re-sent — the handler is entered once per ask. InputRequiredShim does the reverse for handshake-era clients. sample()/listRoots() raise a LogicException on 2026-07-28, which removed them.$gateway->progress()/log() stream over SSE when the handler emits something and the client accepts text/event-stream; honour io.modelcontextprotocol/logLevel (SEP-2575). Adds LoggingLevel::severity()/isAtLeast().subscriptions/listen (SEP-2575) via NotificationBusInterface — InMemoryNotificationBus for persistent runtimes, Psr16NotificationBus for PHP-FPM — set with Builder::setNotificationBus(); Builder::setSubscriptionLifetime() replaces the hard-coded 30s ceiling.StandardHeaderValidator (Builder::setHeaderValidator()), answering -32020 when they contradict the body.Wire\CachePolicy (Builder::setCachePolicy()) for SEP-2549 caching hints; defaults to ttlMs: 0, cacheScope: private. A ReadResourceResult may override with its own values.traceparent/tracestate/baggage from _meta are exposed via RequestContext::getTraceContext() and echoed onto the request's notifications.2025-06-18/2025-11-25 and add the 2026-07-28 surface (SEP-2106): url-mode elicitation (ClientGateway::elicitUrl()/supportsElicitationUrl()), Implementation::title, and outputSchema/structuredContent accepting any JSON value.2027-07-28); they keep working but trigger a deprecation notice.-32602 instead of -32002 (SEP-2164): resources/read picks the code by revision (-32602 from 2026-07-28 on), prompts/get, completion/complete and tools/call switch on every revision. Adds ProtocolVersion::usesInvalidParamsForResourceNotFound().structuredContent on 2026-07-28+; older revisions keep the JSON-encoded value in content.ExtensionInterface::getId() returns an ExtensionIdentifier value object, and the interface gains getMessages()/getRequestHandlers() (extend AbstractExtension to skip both). MessageFactory::make() takes an $additional message list; RequestHandlerInterface's result template is covariant. ServerExtensionInterface is replaced by the side-agnostic Schema\Extension\ExtensionInterface.ClientGateway::supportsExtension(), Client\Builder::enableExtension(), ClientCapabilities::withExtensions().sampling.context/sampling.tools (ClientGateway::supportsSamplingTools()/supportsSamplingContext()). Requests violating the tool-flow rules are rejected with a JSON-RPC error.SamplingMessage::$content and CreateSamplingMessageResult::$content may be a list of content blocks — use getContentBlocks(). CreateSamplingMessageResult rejects roles other than assistant and empty content.RootsCallbackInterface, Client::sendRootsListChanged()) and server-side ClientGateway::listRoots()/supportsRoots()/supportsSampling().Schema\Content\ResourceLink to reference a resource by URI in tool results and prompt messages.Schema\JsonRpc\Error accepts null as $id; an unreadable id now omits the member instead of sending "id": "". MessageFactory decodes a missing or null id as an id-less error.id on an invalid-but-parseable message (-32600) via InvalidInputMessageException::getRequestId().ResourceDefinition/ResourceTemplate $name; the spec allows any string.ToolCallException) at debug level instead of error.annotations to ImageContent.[] instead of {}.PromptResultFormatter dropping annotations, _meta and mimeType for plain-array content.Fix codestyle pipeline on main by @chr-hertel in #402
Full Changelog: v0.7.0...v0.7.1
[Server] Fix completion/complete crash for plain ref/resource by @NarimanGardi in #388
items for array tool parameter schemas by @valeriudev in #378Full Changelog: v0.6.0...v0.7.0
ElicitationCallbackInterface, ElicitationRequestHandler, and ElicitationException let clients respond to server elicitation requests.has*/get* call) instead of eagerly at Builder::build(), fixing empty registries under persistent runtimes (e.g. FrankenPHP worker mode) where a loader's data source is not ready at build time. Adds Builder::setLazyLoading() (default on), a public Registry::load(), and an optional LoaderInterface constructor argument on Registry.Builder::build(), and initialize advertises capabilities from the configured sources rather than the loaded registry. Call Builder::setLazyLoading(false) to restore eager build-time loading.[$instance, 'methodName'] as an element handler in Builder::addTool(), addResource(), addResourceTemplate(), and addPrompt(). Unblocks handlers with constructor dependencies that the container-less new $className() fallback cannot build.items schema for array tool parameters: untyped arrays get items: {} and nullable typed arrays (e.g. string[]|null) keep their element type. Fixes strict clients rejecting tools with "array type must have items" (#151).InvalidInputMessageException entries instead of triggering warnings or a TypeError.maxBatchSize (default 100) to MessageFactory — oversized JSON-RPC batches are rejected before any message is constructed, guarding against amplification.maxBodyBytes (default 4 MiB) to StreamableHttpTransport — POST bodies exceeding the cap are rejected with 413. Unknown-size/chunked bodies are read incrementally and stopped at the cap so they cannot exhaust memory.Mcp-Session-Id headers with a 400 response: a repeated header or a value that is not a valid UUID is now rejected up front instead of surfacing as an uncaught Uuid::fromString() error.ProtectedResourceMetadataHandler, a transport-neutral PSR-15 RequestHandlerInterface that can be mounted directly as a Symfony/Laravel controller; ProtectedResourceMetadataMiddleware now delegates to it (no BC break).[Server] Allow overriding the default file name pattern for Discovery by @johnhunt-lc in #292
exception context when logging exceptions by @svenmuennich in #300Mcp\Schema\Resource to Mcp\Schema\ResourceDefinition. No alias.Mcp\Capability\Registry\Loader\ArrayLoader to Mcp\Capability\Registry\Loader\ReflectedElementLoader.2025-11-25ElementReference::$isManual public property and the bool $isManual parameter from all *Reference constructors. Origin tracking is no longer carried on the element; manual-over-discovered precedence is encoded by loader execution order.RegistryInterface::registerTool(), registerResource(), registerResourceTemplate(), registerPrompt() lost their trailing bool $isManual = false parameter. Callers using positional arguments must drop the flag.RegistryInterface::clear(), getDiscoveryState(), setDiscoveryState(). Rediscovery now goes through DiscoveryLoader::load() directly.Builder::addResource() signature changed — $title parameter added between $name and $description. Callers using positional arguments must switch to named arguments.Builder::addResourceTemplate() signature changed — $title parameter added between $name and $description. Callers using positional arguments must switch to named arguments.StreamableHttpTransport constructor: $corsHeaders parameter removed; CORS is now configured via CorsMiddleware. The $middleware parameter is nullable — null (or omitted) installs the default stack; [] disables all defaults. Default Access-Control-Allow-Origin is no longer set (was *).ResourceDefinition::__construct() signature changed — $title parameter added between $name and $description. Callers using positional arguments must switch to named arguments.ResourceTemplate::__construct() signature changed — $title parameter added between $name and $description. Callers using positional arguments must switch to named arguments.McpResource and McpResourceTemplate attribute signatures changed — $title parameter added between $name and $description. Callers using positional arguments must switch to named arguments.Full Changelog: v0.5.0...v0.6.0
Builder::add(Tool|ResourceDefinition|ResourceTemplate|Prompt $definition, ElementHandlerInterface $handler) for explicit registration of elements whose schema is only known at runtime.ToolHandlerInterface, ResourceHandlerInterface, ResourceTemplateHandlerInterface, PromptHandlerInterface, and the ElementHandlerInterface marker.Mcp\Schema\Resource to Mcp\Schema\ResourceDefinition. No alias.Mcp\Capability\Registry\Loader\ArrayLoader to Mcp\Capability\Registry\Loader\ReflectedElementLoader.2025-11-25extensions to ServerCapabilities and ClientCapabilities and Builder::enableExtension()gcProbability/gcDivisor)title field to ResourceDefinition and ResourceTemplate for MCP spec complianceChainLoader to compose multiple LoaderInterface implementations via explicit ordering.RegistryInterface::unregisterTool(), unregisterResource(), unregisterResourceTemplate(), unregisterPrompt() — idempotent removals.RegistryInterface::hasTool(), hasResource(), hasResourceTemplate(), hasPrompt() — by-name existence checks.DiscoveryLoader now refreshes only its own previously written entries; manual registrations (via Builder::addTool() etc. or runtime $registry->registerTool() calls) survive rediscovery, and a same-name manual registration takes precedence over discovery on collision.ElementReference::$isManual public property and the bool $isManual parameter from all *Reference constructors. Origin tracking is no longer carried on the element; manual-over-discovered precedence is encoded by loader execution order.RegistryInterface::registerTool(), registerResource(), registerResourceTemplate(), registerPrompt() lost their trailing bool $isManual = false parameter. Callers using positional arguments must drop the flag.RegistryInterface::clear(), getDiscoveryState(), setDiscoveryState(). Rediscovery now goes through DiscoveryLoader::load() directly.Registry::register*() semantics changed to plain last-write-wins (overwrites silently) and the methods now return the stored *Reference. The previous "discovered registration is ignored when a manual one already exists" precedence rule still applies, but is now enforced by DiscoveryLoader via reference-identity tracking — and still emits a debug log when a discovery is skipped due to a conflicting registration.title parameter to Builder::addResource() and Builder::addResourceTemplate() for MCP spec complianceBuilder::addResource() signature changed — $title parameter added between $name and $description. Callers using positional arguments must switch to named arguments.Builder::addResourceTemplate() signature changed — $title parameter added between $name and $description. Callers using positional arguments must switch to named arguments.CorsMiddleware, DnsRebindingProtectionMiddleware, and ProtocolVersionMiddleware for StreamableHttpTransport, composed automatically as the default stack via StreamableHttpTransport::defaultMiddleware()StreamableHttpTransport constructor: $corsHeaders parameter removed; CORS is now configured via CorsMiddleware. The $middleware parameter is nullable — null (or omitted) installs the default stack; [] disables all defaults. Default Access-Control-Allow-Origin is no longer set (was *).ResourceDefinition::__construct() signature changed — $title parameter added between $name and $description. Callers using positional arguments must switch to named arguments.ResourceTemplate::__construct() signature changed — $title parameter added between $name and $description. Callers using positional arguments must switch to named arguments.McpResource and McpResourceTemplate attribute signatures changed — $title parameter added between $name and $description. Callers using positional arguments must switch to named arguments.[Server] chore: remove final keyword on attributes classes by @fox-john in #240
title field to Prompt for MCP spec compliance by @chr-hertel in #278GetPromptRequest::$arguments by @vjik in #285Full Changelog: v0.4.0...v0.5.0
Builder::setReferenceHandler() to allow custom ReferenceHandlerInterface implementations (e.g. authorization decorators)TitledEnumSchemaDefinition, MultiSelectEnumSchemaDefinition, TitledMultiSelectEnumSchemaDefinitionsymfony/finder now themselvesLenientOidcDiscoveryMetadataPolicy for identity providers that omit code_challenge_methods_supported (e.g. FusionAuth, Microsoft Entra ID)title field to Prompt and McpPrompt for MCP spec complianceBuilder::addPrompt() signature changed — $title parameter added between $name and $description. Callers using positional arguments for $description must switch to named arguments.title field to Tool and McpTool for MCP spec complianceTool::__construct() signature changed — $title parameter added between $name and $inputSchema. Callers using positional arguments must switch to named arguments or pass null for $title.McpTool attribute signature changed — $title parameter added between $name and $description. Callers using positional arguments for $description must switch to named arguments.Builder::addTool() signature changed — $title parameter added between $name and $description. Callers using positional arguments for $description must switch to named arguments.chore: update licensing to Apache 2.0 for new contributions by @domdomegg in #222
Psr16StoreSession to Psr16SessionStore for consistency by @chr-hertel in #236Full Changelog: v0.3.0...v0.4.0
Mcp\Server\Session\Psr16StoreSession to Mcp\Server\Session\Psr16SessionStoreSessionManager to encapsulate session handling (replaces SessionFactory) and move garbage collection logic from Protocol.[Server] Add support for binary resource in conformance server example by @luoyue712 in #216
schema to resource in Capability by @luoyue712 in #217Full Changelog: v0.2.2...v0.3.0
Mcp\Capability\Registry\ResourceReference::$schema to Mcp\Capability\Registry\ResourceReference::$resource.SchemaGeneratorInterface and DiscovererInterface to allow custom schema generation and discovery implementations.DocBlockParser::getSummary() method, use DocBlockParser::getDescription() instead.Normalize composer.json with ergebnis/composer-normalize by @Nyholm in #208
Full Changelog: v0.2.1...v0.2.2
$_session or $_request.Throwable objects are passed to log context instead of the exception message.House keeping in cs fixer config and makefile by @chr-hertel in #196
Full Changelog: v0.2.0...v0.2.1
RunnerControl for StdioTransport to allow break out from continuously listening for new input.fix minor typo in readme by @Nyholm in #162
Full Changelog: v0.1.0...v0.2.0
Protocol stateless by decouple if from TransportInterface. Removed Protocol::getTransport().Builder::addLoaders(...$loaders) to Builder::addLoaders(iterable $loaders).ClientAwareInterface in favor of injecting a RequestContext with argument injection.ClientGateway cannot be injected with argument injection anymore. Use RequestContext instead.ClientAwareTraitProtocol::getTransport()TransportInterface to Protocol::processInput()[Server] Kicking off the SDK based on Symfony's MCP SDK by @chr-hertel in #1
actions/checkout from v4 to v5 by @OskarStark in #24nyholm/nsa dev dependency by @OskarStark in #28composer.json file before install by @OskarStark in #29ConfigurationException by @OskarStark in #31suggest section in composer.json by @OskarStark in #34.yml to .yaml by @OskarStark in #35self to static by @markinigor in #53Server::make() to Server::builder() for better clarity by @CodeWithKyrian in #84completion/complete handler, centralize provider logic, and move completion capability by @CodeWithKyrian in #97Full Changelog: https://github.com/modelcontextprotocol/php-sdk/commits/0.1.0
Your coding agent can read these notes before it upgrades. Set up the MCP server →