Minor Changes
-
#2369
24be404Thanks @mattzcarey! - AllowinputRequired.elicit()to accept a Standard Schema such as a Zod object forrequestedSchema. The builder converts it to MCP's restricted form-elicitation JSON Schema, while the same schema can validate and type the response throughacceptedContent()on handler re-entry. Zod formats mapping toemail,uri,date, anddate-timeare supported. Shapes the restricted schema cannot express reject before anything is sent — nested objects,.regex()and customized zod format patterns, exclusive number bounds (.positive()/.gt()), literal unions (usez.enumorz.literal(['a', 'b'])), and non-spec root keywords likez.strictObject()'sadditionalProperties. -
#2420
7635115Thanks @felixweinberger! - Add runtime-neutral Bearer authentication to@modelcontextprotocol/server:
requireBearerAuthgates web-standardfetch(request)hosts (Cloudflare
Workers, Deno, Bun, Hono), built on the exportedverifyBearerTokenand
bearerAuthChallengeResponsepieces, withOAuthTokenVerifiernow defined
here. The Express middleware adapts the same core and is unchanged in
behavior, except thatWWW-Authenticatechallenge values are now RFC 7235
quoted-string sanitized (quotes and backslashes escaped, control and
non-ASCII characters replaced);@modelcontextprotocol/expressre-exports
OAuthTokenVerifieras before. -
#2422
61866d7Thanks @felixweinberger! - Add runtime-neutral OAuth discovery serving to@modelcontextprotocol/server:
oauthMetadataResponseserves the RFC 9728 Protected Resource Metadata and
RFC 8414 Authorization Server metadata documents from web-standard
fetch(request)hosts, built on the exported
buildOAuthProtectedResourceMetadata, with
getOAuthProtectedResourceMetadataUrlnow defined here. The Express metadata
router adapts the same core and is unchanged in behavior; the insecure-issuer
escape hatch is an explicitdangerouslyAllowInsecureIssuerUrloption in the
neutral core instead of a module-scope environment read. The web-standard
matcher validates lazily so unmatched traffic always falls through, tolerates
a trailing slash, supports HEAD, and marks reflected CORS preflights with
Vary.
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. -
#2451
7e69735Thanks @mattzcarey! - Return JSON-RPC Invalid Params with the original URI and aninvalid_urireason whenresources/readreceives a syntactically malformed URI. -
#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. -
#2453
0ab5d14Thanks @mattzcarey! - Strip RFC 9110 optional whitespace around inboundMCP-Protocol-Version,Mcp-Method, andMcp-Namevalues before classifying and validating modern HTTP requests. This keeps valid requests portable across Fetch runtimes that expose raw leading or trailing SP/HTAB throughHeaders.get().