Repository navigation
fix(deps): update dependency mcp/sdk to v0.10.0 - #607
Open
renovate[bot] wants to merge 1 commit into
Open
renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
Contributor
Author
|
renovate
Bot
force-pushed
the
renovate/mcp-sdk-0.x
branch
2 times, most recently
from
November 12, 2026 12:57
a3cc3a3 to
7d0c8f0
Compare
renovate
Bot
force-pushed
the
renovate/mcp-sdk-0.x
branch
from
November 12, 2026 21:52
7d0c8f0 to
6642d6b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
v0.9.0→v0.10.0Warning
Some dependencies could not be looked up. Check the Dependency Dashboard for more information.
Release Notes
modelcontextprotocol/php-sdk (mcp/sdk)
v0.10.0Compare Source
TL;DR
Mcp\Schema,Mcp\ClientandMcp\Servernamespaces, ahead of separate packages: most imports changeprivate_key_jwtand experimental cross-app accessBuilder::setSession()is called, and sessions belong to the principal that opened themBuilder::setFiberScheduler(), and stream their responses-32602Namespaces
Mcp\Schema,Mcp\ClientandMcp\Serverto prepare slicing them into separate packages; update your imports.Mcp\Schemadepends on nothing,Mcp\ClientandMcp\Serverdepend onMcp\Schemaonly — enforced by Deptrac (make deptrac).Mcp\Server=>Mcp\Server\ServerMcp\Client=>Mcp\Client\ClientMcp\Capability\*=>Mcp\Server\Capability\*(Attribute,Completion,Discovery,Formatter,Logger,Registry,Tool, plusRegistryandRegistryInterface)Mcp\Event\*=>Mcp\Server\Event\*Mcp\JsonRpc\MessageFactory=>Mcp\Schema\MessageFactoryMcp\Server\Stateless\RequestMeta=>Mcp\Schema\RequestMetaMcp\Exception\*across the three namespaces.Mcp\Exception\{ExceptionInterface,InvalidArgumentException,LogicException,RuntimeException}=> the same names underMcp\Schema\Exception\,Mcp\Client\Exception\andMcp\Server\Exception\— pick the one matching the package you catch aroundMcp\Exception\Exception=>Mcp\Client\Exception\ExceptionandMcp\Server\Exception\ExceptionMcp\Exception\{InvalidInputMessageException,MissingRequestMetaException}=>Mcp\Schema\Exception\*Mcp\Exception\{ConnectionException,ElicitationException,RequestCancelledException,RequestException,RootsException,SamplingException,TimeoutException}=>Mcp\Client\Exception\*Mcp\Exception\{BadMethodCallException,ClientException,ConfigurationException,ContainerException,InvalidCursorException,MissingRequiredClientCapabilityException,NotFoundExceptionInterface,PromptGetException,PromptNotFoundException,RegistryException,RequestStateException,ResourceNotFoundException,ResourceReadException,ServiceNotFoundException,SessionLockException,SessionLockTimeoutException,SessionStoreException,ToolCallException,ToolNotFoundException}=>Mcp\Server\Exception\*Mcp\Exception\HandlerNotFoundExceptionis removed; it was unused.Mcp\Server\Exception\ExceptionInterfaceandMcp\Client\Exception\ExceptionInterfaceextendMcp\Schema\Exception\ExceptionInterface, so catching the latter still catches any SDK exception.RuntimeException/InvalidArgumentExceptioninstead of the global ones, socatch (Mcp\Server\Exception\RuntimeException)reachesToolCallException,PromptGetExceptionand friends.ExtensionInterface::getRequestHandlers()to the newMcp\Server\Extension\ServerExtensionInterface, soMcp\Schemadoes not referenceMcp\Server.AbstractExtensionno longer defines it. An extension that serves its methods must implement the new interface, orBuilder::enableExtension()does not register its handlers.General
2025-06-18. The server refuses a JSON array with-32600, and HTTP status400, on every revision, including2025-03-26, which requires batches and which the server still negotiates. The client drops one with a warning.0.0.0.0,::, an IPv4 address within IPv6 or an IPv4 address in a non-standard form (e.g.127.1), or that hold percent-encoding or non-ASCII characters (give an internationalized name in punycode). In the server, this applies to the issuer, thejwks_uriand the resource.phpdocumentor/reflection-docblock^6.0, withphpdocumentor/type-resolver^2.0andphpstan/phpdoc-parser^2.0: older versions cannot parse types likenon-empty-array<T>, and the SDK silently ignored such a@param. Requirepsr/container^1.1, asContaineris not compatible with1.0.ext-fileinfois optional: without it, creating content from a file without a MIME type throws aLogicException.Server
Builder::setSession()is called, instead of falling back to anInMemorySessionStorethat PHP-FPM andphp -Slose between requests.initializeis answered withoutMcp-Session-Id, later requests run on anEphemeralSessionInterfacethat is never stored, andGETandDELETEget405, so a scaled-out server needs no shared session store or lock. The protocol version comes from theMCP-Protocol-Versionheader. Stdio and the2026-07-28era are not affected. Without a session, requests to the client (elicitation, sampling, roots),Protocol::sendRequest(),Protocol::sendNotification()and saving the session throw aSessionRequiredException, answered with-32603if a handler does not catch it. Input-required results (unlesswithoutInputRequiredShim()is used),resources/subscribe,resources/unsubscribeandlogging/setLevelget-32603,initializeoffers neither resource subscriptions nor logging, log messages are dropped and the client capability checks ofClientGatewayreturnfalse. Keep sessions with e.g.setSession(new InMemorySessionStore())in a long-running process.AccessToken::getPrincipal(), or theStreamableHttpTransport::PRINCIPAL_ATTRIBUTErequest attribute when set. A request of another principal, or to a session opened before the upgrade, gets404.Protocol::processInput(), the transports'handleMessage(),handleSessionEnd(),handlePostRequest()andhandleDeleteRequest(), and theonMessage()andonSessionEnd()listeners take a?string $principalas last argument.SessionInterface::isUnreadable(), which a custom session implements. A session the store holds without attributes, because a handler cleared it or its data is corrupt, gets404so the client opens a new one; only a session the store fails to return gets503.FileSessionStore::exists()no longer reports a zero-byte file.DELETEof an unknown or ended session with404instead of200, as the spec requires:Protocol::destroySession(),BaseTransport::handleSessionEnd()and the listener ofTransportInterface::onSessionEnd()returnfalsefor it. A listener that returns nothing keeps the200.initializesent with a session id with HTTP400, and a failure before the session is checked with500, instead of200. Such an answer no longer carries, or removes, the messages queued for the session inMcp-Session-Id.Psr6SessionStoreandPsr6NotificationBus, the PSR-6 counterparts ofPsr16SessionStoreandPsr16NotificationBus.Builder::setDiscovery()also accepts a PSR-6 pool as$cache.Session::save()wiping the other keys of a session the store failed to read, e.g. on aPsr16SessionStorecache error: it returnsfalseinstead.FileSessionStoreserving a partial session on Windows when a write could not replace the session file: it no longer falls back tocopy(), retries the move for up to a second while another process holds the file, andwrite()returnsfalseif the move fails.InMemorySessionStore::exists()reporting a session past its TTL as existing.Psr16NotificationBussubscribers missing notifications published after the bus stayed quiet for longer than its TTL.503withRetry-Afterand without a Bearer challenge whenJwtTokenValidatorcannot fetch the signing keys or reaches the limit of the newRateLimitedJwksClient, which replaces therateLimitofCachedKeySet.CorsMiddlewareexposesRetry-Afterby default.AuthorizationMiddlewareanswers a body over itsmaxBodyByteswith413and a JSON-RPC error, like the transports, instead of400with aWWW-Authenticatechallenge, and logs a warning to its new$logger.ScopePolicy::requiredFor()takes one decoded message, renames its parameter from$payloadto$messageand returns only the default scopes for a list.JwtTokenValidatorthrowing aTypeError, or answering503, on a token naming a JWKS key whose type or curvefirebase/php-jwtdoes not support (e.g.AKP,P-521since its v7.1, X25519): the token is rejected with401.SecureUrlrejecting plain http on*.localhosthosts (RFC 6761) and IPv4 loopback addresses other than127.0.0.1, which broke local setups likehttp://shop.localhost:8080. Plain http is accepted forlocalhost,*.localhost,127.0.0.0/8and::1; every other host still requires https.SecureUrlaccepting a URL with a backslash, whitespace or control character, sohttp://evil.example\@localhostpassed as loopback although browsers readevil.exampleas the host.tools/callarguments, so validation sees what the client sent:{}and{"0":"x"}are objects,[]and["x"]are arrays. Nested objects inCallToolRequest::$argumentsand in the arguments ofToolHandlerInterface::execute()are\stdClass, as they are for parameters without a type or typedmixed,objector\stdClass; parameters typedarraystill receive arrays at every level.array<string, T>parameters generate{"type": "object"}, andarray<int, T>parameters accept a JSON array or object.tools/callwhoseargumentsis not a JSON object, including[], with-32602, and one whose message has a member name starting with a NUL byte with-32600.tools/callwithoutname) with-32602instead of-32600, as JSON-RPC requires.Request::fromArray()throws the newInvalidParamsException, a subclass ofInvalidArgumentException, andInvalidInputMessageException::isInvalidParams()tells the protocols which code to send.-32602instead of-32603, for tools, prompts and resources. The message names the parameter. Astringparameter refuses a list or an object instead of receiving"Array", and an int-backed enum refuses a value with a fraction instead of truncating it. A missing required argument of a prompt, or of a tool that gets past schema validation, gets-32602too.outputSchemasend and validate a null result as structured content, with JSON textnullalongside it. A tool whose schema does not allow null, including avoidtool, returns a tool error instead of the text(null).ToolReference::extractStructuredContent()returnsStructuredContent::Absentwhen there is nothing to send.ReferenceHandlerresolves a string handler that is not an invokable class from the container by service id. The container goes first, so a function handler no longer runs if the container has an entry of that name.intparameter receiving a wrong value: a number beyond the integer range wrapped around, also past the schema'sminimumormaximum, a numeric string beyond it becamePHP_INT_MAXorPHP_INT_MIN, and"--5"became0. They are refused now.?string $category = 'books') receiving the default when the client sendsnull.-32602: PHPStan/Psalm pseudo-types and literal types (non-empty-string,positive-int,'asc'|'desc',array<array-key, T>) get the JSON Schema type of their base type, nullable and union keys of an array shape (array{nickname: string|null}) and union elements of a generic (array<string|null>) get their own types instead ofobject, and anullinside them, or in a class name, no longer makes the parameter nullable.A-Z,a-z,0-9,_,-and.): names of 65 to 128 characters no longer log a warning, names with a/or a trailing newline do.content,messagesandcontentsbeing sent as a JSON object when the handler returned a non-list array, e.g. fromarray_filter(), and named keys in nested mixed arrays overwriting each other.structuredContenton2026-07-28going out as raw text incontent:'sunny'is sent as the JSON"sunny", as SEP-2106 requires.resources/read:ResourceResultFormattersends it as a blob with the same MIME type.TextContentas compact JSON instead of pretty-printed JSON.EnumCompletionProviderandListCompletionProvideroffering all values for the prefix"0".Builder::build()andBuilder::buildStateless()throw aRuntimeExceptionwhensetDiscovery()is used withoutsymfony/finderand without a custom discoverer, instead of logging a warning and discovering nothing.DocBlockParser::parseDocBlock()no longer acceptsnull; passReflectionMethod::getDocComment()as is.CachedDiscovererreturning the elements of a discovery that only differs in its name patterns, andclearCache()wiping the whole cache instead of its own entries.-32602and the internal message: the client gets-32603and theConfigurationExceptionis logged.2026-07-28server/discoverwithDiscoverHandler, whichBuilder::addRequestHandler()can replace, and dispatchRequestEvent,ResponseEventandErrorEventfor it. AStatelessProtocolbuilt without theBuilderneedsDiscoverHandlerand aMessageFactorywithDiscoverRequest.binderargument toBuilder::setRequestState()that bindsrequestStateto the request it was minted for, as the spec recommends: a state echoed on a request that binds to another value is refused like a forged one.OriginatingRequestBinderbinds the principal, the method, the tool, prompt or resource and a digest of the arguments; A token without a subject fails closed, andRequestStateBinderInterfacetakes custom bindings. Without a binder, nothing changes.requestStatesigning key without failing calls in flight:Builder::setRequestState()andRequestStateCodectake a list of keys, the first signing and all of them verifying. The wire format is unchanged.subscriptions/listenstream that can carry nothing, or that asks for resource subscriptions without a notification bus, being held open for its whole lifetime and pinning a PHP-FPM worker: it is acknowledged with an empty filter and closed right away.server/discoverno longer announcesresources.subscribewithout a bus, unlesssetCapabilities()sets it. AddsNotificationFilter::isEmpty().resources/readleaving out the requiredttlMsandcacheScope, which the TypeScript client rejects: it always carriesttlMs: 0andcacheScope: private. AReadResourceResultno longer sends its own hints to handshake-era clients.StandardHeaderValidatoraccepting anMcp-Param-*header that does not match the body: a fraction that rounds to an integer argument, object and array arguments, and integers outside ±(2^53-1) are refused with-32020."id": nullbeing taken for a notification and left unanswered: it is refused with-32600.JsonExceptionor ending a stream without an answer: the client gets-32603, with HTTP status500or as the last frame of a stream, and the failure is logged and dispatched as anErrorEvent.jsonrpcother than"2.0"being served: a2026-07-28request that skipsMessageFactory, likesubscriptions/listen, is refused with-32600, andNotification::fromArray()throws, asRequest::fromArray()already did.Rev2025Codec, which nothing used.Builder::setFiberScheduler()andFiberSchedulerInterface: under an event loop (Revolt, ReactPHP's async), a handler awaiting I/O suspends its fiber for the loop, and the server waits through the scheduler until the handler suspends for the server or returns. The scheduler must wake the server on a later turn of the loop; the guide shows one for Revolt and one for ReactPHP.CallbackStream::chunks(), which hands the SSE text of a streamed response, in both protocol eras, to a server that writes the body itself (e.g.amphp/http-server,react/http). On an empty chunk, the server waits on its loop before it asks for the next one, and dropping the generator ends the stream. While a handshake-era handler waits for the client, the server keeps serving its loop. The Revolt and ReactPHP examples stream these responses instead of answering501.Builder::setFiberContext()andFiberContextInterfaceto carry execution context, e.g. the active OpenTelemetry context, into handler fibers.subscriptions/listenstream read fromStatelessProtocol::handle()no longer waits between polls or checksconnection_aborted(): its consumer paces it, asStatelessResponderdoes.StreamableHttpTransportno longer has the protectedflushOutgoingMessages()andhandleFiberTermination().\Fiber::suspend()itself with anything but aNotificationSuspensionorRequestSuspensionfails the request without a scheduler, instead of being resumed withnull.LogicExceptionnamingBuilder::setFiberScheduler().ProtocolandStatelessProtocoltake the scheduler as optional last constructor argument.503, or a resumed one losing its session writes, while another request of the session holds the lock across an await: both wait for the lock on the loop.ResponseEventorErrorEvent: what it throws is logged, and calling theClientGatewaythrows.StdioTransportreads only oncestream_select()reports input. Requires PHP 8.5 on Windows.initialize: the refusal is only logged.maxLineBytesofStdioTransportdiscarding lines two bytes shorter than the limit, andStdioTransportthrowing aValueErroron an input that cannot be selected, likephp://memory.Client
OAuth::forApplication('My App')->build()asauthand the client answers a401with discovery, dynamic or pre-registered client identity, the authorization code flow with PKCE and a retry. It refreshes tokens, handles step-up challenges and treats the server's metadata as untrusted input. See Authorization.OAuth::forServiceAccount()for the client credentials grant,setPrivateKeyJwt()forprivate_key_jwtclient authentication, the experimentalsetCrossAppAccess()to reach a server through an enterprise identity, andsetClientMetadataUrl()for a client ID metadata document.setExpectedIssuer()is required withsetClientCredentials(), so a hostile MCP server can't receive the secret or an authorization code.LoopbackAuthorizationHandler,HeadlessAuthorizationHandler,ConsoleAuthorizationHandlerfor a shell without a browser, andRedirectAuthorizationHandlerwithOAuthAuthenticator::finishAuthorization()to finish in a later request, such as a web callback. Credentials go toInMemoryCredentialStorageorFileCredentialStorage, which also implementsPendingAuthorizationStorageInterface.authparameter ofHttpTransportandAuthenticatorInterface, to authenticate every request and retry the ones the server refuses.BearerTokensends a token you already have, andAuthenticatingHttpClientoffers the same as a PSR-18 decorator.401or403answer to a request, a notification or a stream resumption fails with anAuthorizationExceptionholding theWWW-Authenticateheader, instead of aRequestException.Client::connect()wraps it in aConnectionException, without a retry.symfony/http-clientclient, discovered or passed, inHttpTransport,AuthenticatingHttpClientandOAuth.HttpTransportfails on every3xxanswer with an error that names the status and target, whileAuthenticatingHttpClientreturns it unchanged. A redirect to another origin used to receive the configured headers, like an API key, and on a307or308the body: rotate those credentials if an endpoint of yours may have redirected to a host you don't control. Configure the final URL of an endpoint reached through a redirect. A client you pass must not follow redirects to another origin.HttpTransportreads of a response that is not an SSE stream to the newmaxResponseBodyBytes(default: 8 MiB): a larger successful response fails the request, and an error body is cut at the limit.HttpTransporttaking anMcp-Session-Idfrom any response instead of only the answer toinitialize, which replaced the session mid-way and, on2026-07-28, madeclose()send aDELETE.HttpTransportnot reading the listening stream during a request answered with plain JSON.cmd.exe, which corrupted arguments containing",%or!. A batch file likenpxneedscommand: 'cmd'andargs: ['/c', 'npx', ...].Client::connect()leaving the previous transport open when called again.Schema
MessageFactory::create()returns oneMessageInterfaceand throwsInvalidInputMessageExceptionfor invalid input, instead of returning a list of messages and exceptions.MessageFactory::DEFAULT_MAX_BATCH_SIZEand the$maxBatchSizeparameter ofMessageFactory::__construct()andMessageFactory::make(), whose first parameter is now$additional.UnknownMethodException, a subclass ofInvalidInputMessageExceptionfor a method the factory has not registered.x-mcp-headerannotation that is not on a property reached throughpropertiesalone, e.g. underitems,anyOfor$defs, or that is null, makes the tool definition invalid:Toolthrows anInvalidArgumentException, and the client drops such a tool fromtools/listand refuses a sampling request carrying one.CallToolResultkeeps an explicitstructuredContent: nullwhen constructed and decoded. Code that passed null to omit it must leave the argument out (usemeta: $metafor metadata) or passStructuredContent::Absent. An explicit null that violates the output schema becomes a tool error, and strict handshake-era clients can reject it.hasStructuredContent()tells absent content from null;success()anderror()stay without structured content.Configuration
📅 Schedule: (UTC)
* * * * 0,6)🚦 Automerge: Enabled.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.