Patch Changes
-
#2456
44797d7Thanks @felixweinberger! - Restore the v1 parse tolerance forCallToolResult.content: an inbound legacy-eratools/callresult withoutcontentdefaults to[]instead of failing validation. Deployed servers — accepted by SDK v1 for years — returnstructuredContent-only (or otherwise content-less) results, and the strict parse turned every such call into anINVALID_RESULTerror before application code could run.The silent-empty-success hazard the strictness guarded is preserved where it matters: the 2025 era's wire-seam schema refuses to default
contentfor a body carrying another result family's vocabulary (task,inputRequests,requestState— the era is frozen, so the list is complete), and the 2026-era wire schemas stay strict — modern-revision servers have no legacy excuse. Task interop through an explicit result schema is untouched (including bodies that also stamp a foreignresultType), and the server-side authoring normalization refuses the same foreign-family vocabulary.Server-side authoring is era-independent: a handler result without
content(dynamic/JS callers — the TypeScript surface requires it) is normalized tocontent: []before era validation on every leg, reaching the wire spec-valid.Conscious call: the nested sampling
ToolResultContentSchemastays spec-strict — v1 had defaulted itscontenttoo, but it is params-side (tool results a caller authors into a sampling message), deliberately not restored. -
#2431
1b90c96Thanks @morluto! - Fix the CommonJSvalidators/ajvsubpath so reading the exportedAjvclass no longer throwsReferenceError: import_ajv is not defined. The subpath now re-exports the bundled provider's concreteAjvvalue in CJS output, matching the existing ESM behavior. -
#2441
561c6d8Thanks @felixweinberger! - POSTs whoseContent-Typemedia type is notapplication/jsonare now
rejected with415 Unsupported Media Type; the header is parsed instead of
substring-matched. Previously any value merely containing the substring
passed the check (for exampletext/plain; a=application/json), case
variants were wrongly rejected, and the 2026-07-28 entry did not inspect
Content-Typeat all — requests with a missing or non-JSON header that used
to be served on that path now also answer 415. Values with parameters
(application/json; charset=utf-8, including malformed parameter sections
likeapplication/json;) continue to work. SDK clients always send the
correct header and are unaffected.The new
isJsonContentType(header)helper is exported for transport and
framework-adapter authors — custom entries composing the exported building
blocks (classifyInboundRequest,PerRequestHTTPServerTransport) must apply
it themselves. The hono adapter's JSON body pre-parse and the client's
response dispatch now use the same parsed-media-type comparison. -
#2384
ce2f65dThanks @felixweinberger! -instanceofon the SDK error classes (ProtocolErrorand its typed subclasses,SdkError/SdkHttpError,OAuthError, and the client'sSseError,UnauthorizedError, and OAuth-client-flow error family —OAuthClientFlowErrorand its subclasses) now works across separately bundled copies of the SDK. The classes match by a stable brand (viaSymbol.hasInstanceand a registry symbol) instead of prototype identity, so a process that uses both@modelcontextprotocol/clientand@modelcontextprotocol/server- a gateway, host, or in-process test - can check errors constructed by either package against the class re-exported by the other. Ordinary prototype-basedinstanceofis preserved as a fallback; user-defined subclasses keep plain prototype semantics. Notes: cross-bundle matching requires both copies to be at or after this release; brands assert identity, not field shape, across versions - keep reading fields defensively. As a side effect, a foreign-bundleSdkErrorused as an abort reason is now rethrown as-is instead of being wrapped as aRequestTimeout. Branded hierarchies additionally expose an explicit static guard,X.isInstance(value), that reads the same brand and narrows in TypeScript — an alternative for codebases that prefer predicate-style checks overinstanceof. Also:UnauthorizedErrornow setserror.nameto'UnauthorizedError'(previously'Error'), and per-package conformance tests enforce that every exported error class participates in branding. Version-negotiation probing now recognizesUnauthorizedError(previously a dead name-string check) and propagates it unchanged, soconnect()on an auth-gated server rejects with the originalUnauthorizedError(previously wrapped as thecauseof anSdkError(EraNegotiationFailed)) — runfinishAuth()and reconnect, and the retry probes with the token. -
#2469
9b41b56Thanks @felixweinberger! - The Streamable HTTP client transport no longer attaches a session ID to a POST containing aninitializerequest — a new session starts "without a session ID attached" (2025-11-25 transports §Session Management) — and it only captures themcp-session-idresponse header from a successful initialize response, since the spec assigns the session ID "at initialization time … on the HTTP response containing the InitializeResult". Previously the transport stored the header from any response, so a legacy server answering a protocol-version probe with an error that happened to carry a session ID would poison the fallback initialize, which then went out with a session ID it should not have had. A stale session ID from a previous connection is likewise no longer leaked onto the initialize handshake, and a successful initialize response that carries no session ID now clears any stale ID the transport was holding — clients include only an ID "returned by the server during initialization", so an ID the server never returned this session is outside the session model. Ignoringmcp-session-idheaders mid-session is the complement of the spec's one actual rotation mechanism: a server that wants a new session terminates the old one (it "MAY terminate the session at any time") and answers 404, after which the client "MUST start a new session by sending a new InitializeRequest without a session ID attached". Rotation exists as session replacement via 404 + re-initialize, never as a header swap on a live session, so a server that rotates per the spec's own flow is handled correctly by this transport. -
#2455
cc70c5eThanks @felixweinberger! - Version negotiation no longer discards transport handlers the caller set beforeconnect(). The probe window now saves any pre-setonmessage/onerror/onclose, forwards error and close events to them while the probe is in flight, and restores them when the window closes — soProtocol.connect()chains them exactly as it does on a plain connect. Previously, connecting withversionNegotiationsilently cleared pre-set handlers (e.g. anonerrorused to detect session-expiry auth failures), leaving them permanently detached for the life of the connection. -
#2425
e8de519Thanks @Sehlani042! - Stop advertising validator provider classes from the root client/server type declarations. The provider classes remain available from the explicit validator subpaths.