One handler asks the user something on any revision now. Where the client can be asked mid-request it still is; where it cannot, the ask becomes the input_required result that revision carries and the same call returns the answer once the client re-sends it. Costs one handler entry per ask, so side effects belong after the last question. Answers from earlier rounds travel in the requestState, which is why asking more than once needs Builder::setRequestState(). sample() and listRoots() still raise a LogicException there: that revision removed them outright.
22 KiB
22 KiB
Changelog
All notable changes to mcp/sdk will be documented in this file.
0.8.0
- Serve
ClientGateway::elicit()/elicitUrl()under the 2026-07-28 lifecycle, so one handler asks the user something on any revision. Where the client can be asked mid-request it still is; where it cannot,Mcp\Server\Stateless\ElicitationReplayturns the ask into theinput_requiredresult that revision carries and returns from the same call once the client re-sends it with the answer — at the cost of entering the handler once per ask. Both methods take an optional$keynaming an ask across those rounds, defaulting to its position in the handler. Answers from earlier rounds travel in therequestState, so a handler asking more than once needsBuilder::setRequestState().sample()andlistRoots()still raise aLogicExceptionthere: that revision removed them outright. - Speak the 2026-07-28 lifecycle from the client:
Clientopens withserver/discoverinstead ofinitializeon that revision, stamps each request's_metawith the protocol version, its own capabilities and client info, and sends the standardMcp-Method/Mcp-Name/Mcp-Param-*headers an intermediary routes on — the last from the newMcp\Client\Stateless\ToolCatalog, which knows from the tool list which arguments a call must mirror. Aninput_requiredresult is answered automatically byInputRequestResolver, which asks the host's elicitation, sampling and roots handlers and retries the same request withinputResponsesand therequestStatethe server sent.Mcp\Schema\Wire\McpHeaderholds the header names and the=?base64?…?=sentinel both sides share. - Serve both protocol eras from one endpoint:
StreamableHttpTransportclassifies each request — a2026-07-28envelope, aninitializehandshake, or a session-bound follow-up — through the newMcp\Server\Wire\InboundClassifierand routes it to the dispatcher that owns it, so a single URL answers a modern client and a handshake-era one alike.Server::builder()->build()now carries both dispatchers;Builder::withoutModernEra()opts out andBuilder::setModernVersions()narrows what the modern leg answers for.Mcp\Server\InputRequiredShimlets a handler written for multi round-trip requests also serve a handshake-era client, by turning each ask into the request/response exchange that era has. - Carry W3C trace context through a request (SEP-414):
traceparent,tracestateandbaggagein a request's_metaare exposed to handlers asRequestContext::getTraceContext()and echoed onto the notifications that request causes, so a span stays joined across the response stream. Values pass through exactly as they arrived, and no OpenTelemetry dependency is added. - Deliver notifications on a
subscriptions/listenstream (SEP-2575), which previously acknowledged and then carried nothing for the rest of its life. NewMcp\Server\Subscription\NotificationBusInterfacewith two implementations —InMemoryNotificationBusfor stdio and persistent runtimes,Psr16NotificationBusfor PHP-FPM, where the worker holding the stream open and the worker publishing are different processes — set withBuilder::setNotificationBus(). Registry changes are published automatically through aPublishingEventDispatcherthat wraps whatever PSR-14 dispatcher was configured.Builder::setSubscriptionLifetime()replaces the hard-coded 30-second ceiling, where0means "until the client or the runtime ends it". - Add
Mcp\Server\Wire\CachePolicy, set withBuilder::setCachePolicy(), to configure the SEP-2549 caching hints the 2026-07-28 lifecycle stamps on a cacheable result. The conservativettlMs: 0, cacheScope: privatestays the default, sincepubliclets a shared proxy serve one caller's answer to another and only the operator can make that call. AReadResourceResultmay also carry its ownttlMs/cacheScope, which win over the policy. - Add typed readers to
Mcp\Server\Stateless\InputContext:elicitResult(),samplingResult()androotsResult()deserialize a multi round-trip answer instead of handing back the raw arrayresponse()returns. A malformed answer reads as absent rather than throwing, so a handler asks again — which is what the spec says a server SHOULD do when the information it needs is still missing. - Answer a request over a response stream under the 2026-07-28 lifecycle:
StatelessProtocolruns handlers in a fiber, so$gateway->progress()and$gateway->log()work there as they do in the handshake era. The stream opens only if the handler actually emits something and the client'sAcceptadmitstext/event-stream, and the choice is made after the handler's first suspension, so a request that turns out to need-32021or-32602is still answered with the status the spec fixes for it. - Honour
io.modelcontextprotocol/logLevel(SEP-2575), which replaced thelogging/setLevelRPC: a request naming no level receives nonotifications/messageat all, one naming a level receives the messages at or above it. AddsLoggingLevel::severity()andLoggingLevel::isAtLeast(). - [BC Break] Answer a not-found subject with
-32602(Invalid params) instead of-32002, which the 2026-07-28 revision reserves and forbids emitting (SEP-2164).resources/readpicks the code from the revision serving the request —-32602with the uri inerror.datafrom2026-07-28on,-32002below.prompts/getfor an unknown prompt,completion/completefor an unknown reference andtools/callfor an unknown tool switch to-32602in every revision:-32002was never the code for those. AddsProtocolVersion::usesInvalidParamsForResourceNotFound(). - Add the multi round-trip requests pattern for the 2026-07-28 lifecycle (SEP-2322): a
tools/callorprompts/gethandler returningMcp\Schema\Result\InputRequiredResultcomes back asresultType: "input_required"carrying theinputRequestsit needs answered and an opaquerequestState; the client retries the same request withinputResponses, which the handler reads throughRequestContext::getInputContext().Mcp\Server\Stateless\RequestStateCodecsigns and time-bounds the state — set the key withBuilder::setRequestState(). - Validate the standard MCP request headers under the 2026-07-28 lifecycle (SEP-2243):
Mcp\Server\Stateless\StandardHeaderValidator, set withBuilder::setHeaderValidator(), checks thatMcp-MethodandMcp-Nameagree with the body they travel with and that aMcp-Param-*mirrors the argument its tool markedx-mcp-header, answering-32020when they disagree. Intermediaries route on these headers, so a value contradicting the body has to be refused rather than ignored. - [BC Break]
Mcp\Schema\JsonRpc\Erroracceptsnullas its$id, andgetId()may return it. An error response whose id could not be read now omits the member instead of sending"id": ""— which claimed the peer had issued a request with an empty-string id. All thefor*()factories default tonull,fromArray()accepts a missing or explicitly-null id, andMessageFactorydecodes both as an id-less error rather than rejecting them. - Preserve the original request
idon an invalid-but-parseable message (-32600) instead of answering it id-less:InvalidInputMessageExceptionnow carries the recoverable id viagetRequestId()/setRequestId(), threaded fromMessageFactorythrough to the error response. - [BC Break] Add the extensions framework SEP-2133 defines, which MCP Apps sits on.
ExtensionInterface::getId()now returns the newMcp\Schema\Extension\ExtensionIdentifiervalue object instead of a string, which validates the identifier against the_metakey naming rules at construction time.ExtensionInterfacealso gainsgetMessages()/getRequestHandlers(), so an extension can contribute the message classes its methods decode into — without which its methods cannot be decoded at all — and the handlers serving them; extensions that only announce a capability can extend the newMcp\Schema\Extension\AbstractExtensionand skip both.MessageFactory::make()takes an$additionallist of message classes, andRequestHandlerInterface's result template is now covariant. - [BC Break] Drop the SDK-only name pattern on
ResourceDefinition/ResourceTemplate$name— the spec allows any string (its own examples usemain.rsandProject Files). URI/URI-template validation is unchanged. - Add
ClientGateway::supportsExtension(),Client\Builder::enableExtension(), andClientCapabilities::withExtensions()so clients can negotiate and check protocol extensions (e.g. MCP Apps) the same way servers already do. [BC Break]ServerExtensionInterfaceis replaced by the side-agnosticMcp\Schema\Extension\ExtensionInterface. - Deprecate Roots, Sampling and Logging per SEP-2577 (protocol revision
2026-07-28, earliest removal2027-07-28). They keep working but using them now triggers a deprecation notice — migrate to tool arguments/resource URIs, a direct LLM provider API, and stderr/OpenTelemetry respectively. - [BC Break] Gate
structuredContenton the negotiated protocol revision: a tool result that's a PHP list (or an object serializing to a JSON array) is now only sent asstructuredContenton protocol revisions2026-07-28+; older revisions omit it and keep the JSON-encoded value incontent. - Add protocol revision negotiation during
initialize: the server counter-offers a revision it supports, and the client now fails the handshake instead of continuing on an unagreed revision. AddsClient::getProtocolVersion()and the2026-07-28revision. - Add sampling-with-tools support: sampling requests can include tools and tool-choice preferences, messages support tool-use/tool-result content blocks, and clients advertise
sampling.context/sampling.toolsviaClientGateway::supportsSamplingTools()/supportsSamplingContext(). A request that violates the spec's tool-flow rules is now rejected with a proper JSON-RPC error instead of being left unanswered. - [BC Break]
SamplingMessage::$contentandCreateSamplingMessageResult::$contentmay now be a list of content blocks instead of just one — use the newgetContentBlocks()to always get a list. - [BC Break]
CreateSamplingMessageResultnow rejects any role other thanassistant, and rejects empty content, per spec. - Close the remaining 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 rather than only objects. - Add
Mcp\Schema\Content\ResourceLink— reference a resource by URI/name in tool results and prompt messages without embedding its contents. - Add client-side Roots support:
RootsCallbackInterface,Client::sendRootsListChanged(), and server-sideClientGateway::listRoots()/supportsRoots(). - Add
ClientGateway::supportsSampling()to check the client's advertised capabilities before sending a sampling request, matchingsupportsRoots()/supportsElicitation(). - Fix empty tool/resource schemas serializing as
[]instead of{}ininputSchema/outputSchema. - Fix
PromptResultFormatterdroppingannotations,_meta, andmimeTypewhen a prompt generator returns content as a plain array. - Add
annotationssupport toImageContent, matchingTextContent/AudioContent.
0.7.0
- 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).
0.6.0
- 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.
0.5.0
- 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.
0.4.0
- 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.
0.3.0
- 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.
0.2.2
- Throw exception when trying to inject parameter with the unsupported names
$_sessionor$_request. Throwableobjects are passed to log context instead of the exception message.
0.2.1
- Add
RunnerControlforStdioTransportto allow break out from continuously listening for new input. - Open range of supported Symfony versions to include v5.4
0.2.0
- 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()
0.1.0
- First tagged release of package
- Support for implementing MCP server