Minor Changes
-
#2501
1480241Thanks @felixweinberger! - Export theProtocolbase class andmergeCapabilitiesfrom the@modelcontextprotocol/clientand@modelcontextprotocol/serverpackage roots, restoring the v1 import for consumers that subclassProtocol(e.g. the MCP Apps SDK). The client and server packages each bundle their own compiled copy of the class, so import it from one package consistently within a process.The codemod now rewrites
ProtocolandmergeCapabilitiesimports fromshared/protocol.jsto the client or server package root, like the module's other symbols, instead of dropping them with an action-required marker. -
#2477
8e1d2e9Thanks @felixweinberger! - Move the schema source modules (spec schemas, OAuth schemas, protocol constants) into@modelcontextprotocol/coreand resolve them from there as a regular runtime dependency instead of bundling a private copy into each package. An application importing more than one of the packages now evaluates a single shared schema graph with shared object identity.@modelcontextprotocol/coregains a./internalsubpath (SDK-internal contract; may change in any release) and the four packages now version together. -
#2513
f413763Thanks @felixweinberger! - Align the 2026-07-28 wire with the final revision (spec PR #3002):serverInfomoves from theDiscoverResultbody to the result_meta, and the per-request envelope'sclientInfodemotes from required to SHOULD.Before this change the SDK shipped the pre-#3002 shape in both directions: the client hard-rejected a conforming server's
DiscoverResult(missing bodyserverInfofailed parse, so the probe misclassified the server as legacy and attempted aninitializehandshake against it — a hard connect failure against a modern-only server such as go-sdk v1.7.0-pre.3), and the server rejected conforming clients that omitclientInfo.Now:
- The 2026 wire schemas are the final revision exactly: no body
serverInfoonDiscoverResult, envelopeclientInfooptional (a present-but-malformed value still fails validation). - Servers stamp
_meta['io.modelcontextprotocol/serverInfo']on every 2026-era response (spec SHOULD; a handler-authored value wins, the 2025-era wire is untouched). This includes the entry-builtsubscriptions/listengraceful-close results — the spec'sSubscriptionsListenResultMetaextendsResultMetaObject. - Clients keep sending
clientInfoand read server identity from the discover result's_metaonly. A server that stamps no identity is anonymous:getServerVersion()isundefinedand the response cache partitions under a per-connection surrogate. A malformed_metaserverInfo value is treated as absent on receive (the spec marks the field self-reported, unverified, and display-only). - Breaking type changes:
DiscoverResultno longer declaresserverInfo;RequestMetaEnvelope'sclientInfois optional. New public constantSERVER_INFO_META_KEY('io.modelcontextprotocol/serverInfo').
- The 2026 wire schemas are the final revision exactly: no body
-
#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. -
#2541
470678dThanks @mattzcarey! - Add configurable SSE keep-alive comment frames to Streamable HTTP transports and applycreateMcpHandler's existingkeepAliveMsoption to every HTTP SSE stream it serves. -
#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. -
#2483
3f07a32Thanks @felixweinberger! - AddpreloadSchemas(), an explicit opt-in to eager wire-schema construction, and call it automatically in the Cloudflare Workers builds. The wire schemas are built lazily by default, which is the right trade on process-per-invocation runtimes — but on isolate platforms that bill request CPU while module evaluation runs during isolate warm-up, laziness moves construction into the first request each fresh isolate serves. CallingpreloadSchemas()at module scope (it is synchronous and idempotent) moves that one-time cost back to module evaluation; the packages' workerd export condition now does this automatically, while the Node and browser builds stay lazy. The server package gains a dedicated browser shim for this (itsbrowsercondition previously reused the workerd shim), so browser bundles keep lazy construction.
Patch Changes
-
#2402
a400259Thanks @felixweinberger! - First beta release of SDK v2 with support for the MCP 2026-07-28 specification
revision. See the migration guides for upgrading from v1
(docs/migration/upgrade-to-v2.md) and adopting the 2026-07-28 revision
(docs/migration/support-2026-07-28.md). -
#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. -
#2405
f172626Thanks @mattzcarey! - Ship CommonJS builds alongside ESM. Each package now emits both.mjs/.d.mts
and.cjs/.d.cts(via tsdownformat: ['esm', 'cjs']), and itsexportsmap
adds arequirecondition sorequire('@modelcontextprotocol/…')works from
CommonJS consumers. Output extensions are normalized across all packages
(@modelcontextprotocol/coremoves from.js/.d.tsto.mjs/.d.mts); the
public import paths are unchanged. -
#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. -
#2458
7c49b47Thanks @felixweinberger! - Construct the default Ajv validation engine lazily on first validation. Creating aClientorServerno longer pays the ajv + ajv-formats instantiation cost at startup when no JSON Schema validation ever runs. -
#2476
e0a0ab7Thanks @felixweinberger! - Build protocol-revision wire schemas lazily on first validation instead of at import. Each revision's schema set is now constructed by a module-level memoized factory, so importing the client or server package no longer pays the construction cost of both frozen wire-schema graphs up front. Method membership in the revision registries stays static, the schemas themselves are unchanged, and registry lookups keep returning reference-identical schema objects. -
#2451
7e69735Thanks @mattzcarey! - Return JSON-RPC Invalid Params with the original URI and aninvalid_urireason whenresources/readreceives a syntactically malformed URI. -
#2399
3c7ddafThanks @felixweinberger! - Return HTTP 400 for aMissingRequiredClientCapabilityError(-32021) produced after dispatch. The spec mandates400 Bad Requestfor this error with no condition on where it arose, but only the pre-dispatch capability gate honored that; the post-handler emission — theinput_requiredgate rejecting an embedded request whose required capability the caller did not declare — surfaced in-band on HTTP 200. The JSON-RPC error body is unchanged, every other error code (including a handler relaying a downstream peer's-32020/-32022) keeps the origin-keyed in-band behavior, and the mapping only applies while the response is uncommitted: an exchange that already streamed — or one hosted withresponseMode: 'sse', which opens its stream at dispatch end — keeps its committed 200 and carries the error in-stream. -
#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(). -
#2534
f130e1aThanks @felixweinberger! - The default validator now honors declared 2019-09 and draft-07/06 dialects instead of rejecting them: a schema stamped"$schema": "http://json-schema.org/draft-07/schema#"(zod-to-json-schema's default output) validates with draft-07 semantics, and a 2019-09 stamp (zod-to-json-schema's2019-09/openAitargets) with 2019-09 semantics, on both the Ajv and Cloudflare Workers providers (with known engine differences documented in the migration guide). Schemas with no$schemastill validate as 2020-12, and unknown dialects still produce the typed error (now listing the supported dialects: 2020-12, 2019-09, draft-07, draft-06). -
Updated dependencies [
a400259,44797d7,f172626,8e1d2e9,f413763]:- @modelcontextprotocol/core@2.0.0