All notable changes to mcp/sdk will be documented in this file.
- [BC Break] Remove the
providerClassargument of#[CompletionProvider]. Useprovider:, which takes the same class-string and is now the first positional argument. - Add
HttpTransport::getSessionId()to read the server-mintedMcp-Session-Id: a request-scoped caller can persist it and pass it back through the constructor's$headerson a later transport. Alwaysnullon2026-07-28, which removed protocol-level sessions. - Fix OIDC discovery rejecting issuers with a trailing slash (e.g. Authentik, Auth0).
- Support for MCP spec version 2026-07-28 incl. stateless HTTP transport
- Negotiate the protocol revision during
initialize: the server counter-offers a revision it supports and the client fails the handshake instead of continuing on an unagreed one. AddsClient::getProtocolVersion(). - Serve both protocol eras from one endpoint:
StreamableHttpTransportclassifies 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. - Speak the lifecycle from the client:
Clientopens withserver/discover, stamps_metawith version, capabilities and client info, and sends theMcp-Method/Mcp-Name/Mcp-Param-*headers (viaClient\Stateless\ToolCatalog).Schema\Wire\McpHeaderholds the shared header names. - Add multi round-trip requests (SEP-2322): a handler returning
InputRequiredResultyieldsresultType: "input_required"with an opaque, signedrequestState(RequestStateCodec, key viaBuilder::setRequestState()); the client retries withinputResponses, read throughRequestContext::getInputContext()and its typedelicitResult()/samplingResult()/rootsResult(). On the client,InputRequestResolveranswers such results automatically from the host's elicitation, sampling and roots handlers. - Serve
ClientGateway::elicit()/elicitUrl()on every revision: where the client cannot be asked mid-request,Server\Stateless\ElicitationReplayturns the ask intoinput_requiredand resumes once re-sent — the handler is entered once per ask.InputRequiredShimdoes the reverse for handshake-era clients.sample()/listRoots()raise aLogicExceptionon2026-07-28, which removed them. - Run stateless handlers in a fiber so
$gateway->progress()/log()stream over SSE when the handler emits something and the client acceptstext/event-stream; honourio.modelcontextprotocol/logLevel(SEP-2575). AddsLoggingLevel::severity()/isAtLeast(). - Deliver notifications on
subscriptions/listen(SEP-2575) viaNotificationBusInterface—InMemoryNotificationBusfor persistent runtimes,Psr16NotificationBusfor PHP-FPM — set withBuilder::setNotificationBus();Builder::setSubscriptionLifetime()replaces the hard-coded 30s ceiling. - Validate the standard request headers (SEP-2243) with
StandardHeaderValidator(Builder::setHeaderValidator()), answering-32020when they contradict the body. - Add
Wire\CachePolicy(Builder::setCachePolicy()) for SEP-2549 caching hints; defaults tottlMs: 0, cacheScope: private. AReadResourceResultmay override with its own values. - Carry W3C trace context (SEP-414):
traceparent/tracestate/baggagefrom_metaare exposed viaRequestContext::getTraceContext()and echoed onto the request's notifications. - Close the schema gaps for
2025-06-18/2025-11-25and add the2026-07-28surface (SEP-2106): url-mode elicitation (ClientGateway::elicitUrl()/supportsElicitationUrl()),Implementation::title, andoutputSchema/structuredContentaccepting any JSON value. - Deprecate Roots, Sampling and Logging (SEP-2577, earliest removal
2027-07-28); they keep working but trigger a deprecation notice. - [BC Break] Answer a not-found subject with
-32602instead of-32002(SEP-2164):resources/readpicks the code by revision (-32602from2026-07-28on),prompts/get,completion/completeandtools/callswitch on every revision. AddsProtocolVersion::usesInvalidParamsForResourceNotFound(). - [BC Break] A list-shaped tool result is only sent as
structuredContenton2026-07-28+; older revisions keep the JSON-encoded value incontent.
- Negotiate the protocol revision during
- [BC Break] Add the extensions framework (SEP-2133) MCP Apps sits on:
ExtensionInterface::getId()returns anExtensionIdentifiervalue object, and the interface gainsgetMessages()/getRequestHandlers()(extendAbstractExtensionto skip both).MessageFactory::make()takes an$additionalmessage list;RequestHandlerInterface's result template is covariant.ServerExtensionInterfaceis replaced by the side-agnosticSchema\Extension\ExtensionInterface. - Add client-side extension negotiation:
ClientGateway::supportsExtension(),Client\Builder::enableExtension(),ClientCapabilities::withExtensions(). - Add sampling-with-tools: sampling requests can carry tools and tool-choice preferences, messages support tool-use/tool-result blocks, and clients advertise
sampling.context/sampling.tools(ClientGateway::supportsSamplingTools()/supportsSamplingContext()). Requests violating the tool-flow rules are rejected with a JSON-RPC error. - [BC Break]
SamplingMessage::$contentandCreateSamplingMessageResult::$contentmay be a list of content blocks — usegetContentBlocks().CreateSamplingMessageResultrejects roles other thanassistantand empty content. - Add client-side Roots support (
RootsCallbackInterface,Client::sendRootsListChanged()) and server-sideClientGateway::listRoots()/supportsRoots()/supportsSampling(). - Add
Schema\Content\ResourceLinkto reference a resource by URI in tool results and prompt messages. - [BC Break]
Schema\JsonRpc\Erroracceptsnullas$id; an unreadable id now omits the member instead of sending"id": "".MessageFactorydecodes a missing or null id as an id-less error. - Preserve the request
idon an invalid-but-parseable message (-32600) viaInvalidInputMessageException::getRequestId(). - [BC Break] Drop the SDK-only name pattern on
ResourceDefinition/ResourceTemplate$name; the spec allows any string. - Log expected tool failures (
ToolCallException) at debug level instead of error. - Add
annotationstoImageContent. - Fix empty tool/resource schemas serializing as
[]instead of{}. - Fix
PromptResultFormatterdroppingannotations,_metaandmimeTypefor plain-array content.
- Add client-side elicitation support:
ElicitationCallbackInterface,ElicitationRequestHandler, andElicitationExceptionlet clients respond to server elicitation requests. - Defer element loading to the first registry read: loaders now run at request time (first
has*/get*call) instead of eagerly atBuilder::build(), fixing empty registries under persistent runtimes (e.g. FrankenPHP worker mode) where a loader's data source is not ready at build time. AddsBuilder::setLazyLoading()(default on), a publicRegistry::load(), and an optionalLoaderInterfaceconstructor argument onRegistry. - [BC Break] Element loading is lazy by default: loader failures now surface on the first request rather than at
Builder::build(), andinitializeadvertises capabilities from the configured sources rather than the loaded registry. CallBuilder::setLazyLoading(false)to restore eager build-time loading. - Allow
[$instance, 'methodName']as an element handler inBuilder::addTool(),addResource(),addResourceTemplate(), andaddPrompt(). Unblocks handlers with constructor dependencies that the container-lessnew $className()fallback cannot build. - Always emit an
itemsschema for array tool parameters: untyped arrays getitems: {}and nullable typed arrays (e.g.string[]|null) keep their element type. Fixes strict clients rejecting tools with "array type must have items" (#151). - Harden JSON-RPC input parsing: single-message vs batch is now decided from the decoded JSON type (object → single, list array → batch) instead of the raw first byte. Scalars, empty payloads, and non-object batch elements are surfaced as
InvalidInputMessageExceptionentries instead of triggering warnings or aTypeError. - Add
maxBatchSize(default100) toMessageFactory— oversized JSON-RPC batches are rejected before any message is constructed, guarding against amplification. - Add
maxBodyBytes(default 4 MiB) toStreamableHttpTransport— POST bodies exceeding the cap are rejected with413. Unknown-size/chunked bodies are read incrementally and stopped at the cap so they cannot exhaust memory. - Reject malformed
Mcp-Session-Idheaders with a400response: a repeated header or a value that is not a valid UUID is now rejected up front instead of surfacing as an uncaughtUuid::fromString()error. - Extract RFC 9728 metadata serving into
ProtectedResourceMetadataHandler, a transport-neutral PSR-15RequestHandlerInterfacethat can be mounted directly as a Symfony/Laravel controller;ProtectedResourceMetadataMiddlewarenow delegates to it (no BC break).
- Add
Builder::add(Tool|ResourceDefinition|ResourceTemplate|Prompt $definition, ElementHandlerInterface $handler)for explicit registration of elements whose schema is only known at runtime. - Add handler interfaces
ToolHandlerInterface,ResourceHandlerInterface,ResourceTemplateHandlerInterface,PromptHandlerInterface, and theElementHandlerInterfacemarker. - [BC Break] Renamed
Mcp\Schema\ResourcetoMcp\Schema\ResourceDefinition. No alias. - [BC Break] Renamed
Mcp\Capability\Registry\Loader\ArrayLoadertoMcp\Capability\Registry\Loader\ReflectedElementLoader. - [BC Break] Bump default protocol version to
2025-11-25 - Add support for MCP Apps extension in schema and server
- Add
extensionstoServerCapabilitiesandClientCapabilitiesandBuilder::enableExtension() - Allow overriding the default name pattern for Discovery
- Add configurable session garbage collection (
gcProbability/gcDivisor) - Add optional
titlefield toResourceDefinitionandResourceTemplatefor MCP spec compliance - Add
ChainLoaderto compose multipleLoaderInterfaceimplementations via explicit ordering. - Add
RegistryInterface::unregisterTool(),unregisterResource(),unregisterResourceTemplate(),unregisterPrompt()— idempotent removals. - Add
RegistryInterface::hasTool(),hasResource(),hasResourceTemplate(),hasPrompt()— by-name existence checks. DiscoveryLoadernow refreshes only its own previously written entries; manual registrations (viaBuilder::addTool()etc. or runtime$registry->registerTool()calls) survive rediscovery, and a same-name manual registration takes precedence over discovery on collision.- [BC Break] Removed
ElementReference::$isManualpublic property and thebool $isManualparameter from all*Referenceconstructors. Origin tracking is no longer carried on the element; manual-over-discovered precedence is encoded by loader execution order. - [BC Break]
RegistryInterface::registerTool(),registerResource(),registerResourceTemplate(),registerPrompt()lost their trailingbool $isManual = falseparameter. Callers using positional arguments must drop the flag. - [BC Break] Removed
RegistryInterface::clear(),getDiscoveryState(),setDiscoveryState(). Rediscovery now goes throughDiscoveryLoader::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 byDiscoveryLoadervia reference-identity tracking — and still emits a debug log when a discovery is skipped due to a conflicting registration.- Add optional
titleparameter toBuilder::addResource()andBuilder::addResourceTemplate()for MCP spec compliance - [BC Break]
Builder::addResource()signature changed —$titleparameter added between$nameand$description. Callers using positional arguments must switch to named arguments. - [BC Break]
Builder::addResourceTemplate()signature changed —$titleparameter added between$nameand$description. Callers using positional arguments must switch to named arguments. - Add
CorsMiddleware,DnsRebindingProtectionMiddleware, andProtocolVersionMiddlewareforStreamableHttpTransport, composed automatically as the default stack viaStreamableHttpTransport::defaultMiddleware() - [BC BREAK]
StreamableHttpTransportconstructor:$corsHeadersparameter removed; CORS is now configured viaCorsMiddleware. The$middlewareparameter is nullable —null(or omitted) installs the default stack;[]disables all defaults. DefaultAccess-Control-Allow-Originis no longer set (was*). - [BC Break]
ResourceDefinition::__construct()signature changed —$titleparameter added between$nameand$description. Callers using positional arguments must switch to named arguments. - [BC Break]
ResourceTemplate::__construct()signature changed —$titleparameter added between$nameand$description. Callers using positional arguments must switch to named arguments. - [BC Break]
McpResourceandMcpResourceTemplateattribute signatures changed —$titleparameter added between$nameand$description. Callers using positional arguments must switch to named arguments.
- Add built-in authentication middleware for HTTP transport using OAuth
- Add client component for building MCP clients
- Add
Builder::setReferenceHandler()to allow customReferenceHandlerInterfaceimplementations (e.g. authorization decorators) - Add elicitation enum schema types per SEP-1330:
TitledEnumSchemaDefinition,MultiSelectEnumSchemaDefinition,TitledMultiSelectEnumSchemaDefinition - [BC break] Make Symfony Finder component optional. Users would need to install
symfony/findernow themselves - Add
LenientOidcDiscoveryMetadataPolicyfor identity providers that omitcode_challenge_methods_supported(e.g. FusionAuth, Microsoft Entra ID) - Add OAuth 2.0 Dynamic Client Registration middleware (RFC 7591)
- Add optional
titlefield toPromptandMcpPromptfor MCP spec compliance - [BC Break]
Builder::addPrompt()signature changed —$titleparameter added between$nameand$description. Callers using positional arguments for$descriptionmust switch to named arguments. - Add optional
titlefield toToolandMcpToolfor MCP spec compliance - [BC Break]
Tool::__construct()signature changed —$titleparameter added between$nameand$inputSchema. Callers using positional arguments must switch to named arguments or passnullfor$title. - [BC Break]
McpToolattribute signature changed —$titleparameter added between$nameand$description. Callers using positional arguments for$descriptionmust switch to named arguments. - [BC Break]
Builder::addTool()signature changed —$titleparameter added between$nameand$description. Callers using positional arguments for$descriptionmust switch to named arguments.
- Rename
Mcp\Server\Session\Psr16StoreSessiontoMcp\Server\Session\Psr16SessionStore - Add missing handlers for resource subscribe/unsubscribe and persist subscriptions via session
- Introduce
SessionManagerto encapsulate session handling (replacesSessionFactory) and move garbage collection logic fromProtocol.
- Add output schema support to MCP tools
- Add validation of the input parameters given to a Tool.
- Rename
Mcp\Capability\Registry\ResourceReference::$schematoMcp\Capability\Registry\ResourceReference::$resource. - Introduce
SchemaGeneratorInterfaceandDiscovererInterfaceto allow custom schema generation and discovery implementations. - Remove
DocBlockParser::getSummary()method, useDocBlockParser::getDescription()instead.
- Throw exception when trying to inject parameter with the unsupported names
$_sessionor$_request. Throwableobjects are passed to log context instead of the exception message.
- Add
RunnerControlforStdioTransportto allow break out from continuously listening for new input. - Open range of supported Symfony versions to include v5.4
- Make
Protocolstateless by decouple if fromTransportInterface. RemovedProtocol::getTransport(). - Change signature of
Builder::addLoaders(...$loaders)toBuilder::addLoaders(iterable $loaders). - Removed
ClientAwareInterfacein favor of injecting aRequestContextwith argument injection. - The
ClientGatewaycannot be injected with argument injection anymore. UseRequestContextinstead. - Removed
ClientAwareTrait - Removed
Protocol::getTransport() - Added parameter for
TransportInterfacetoProtocol::processInput()
- First tagged release of package
- Support for implementing MCP server