Releases: modelcontextprotocol/typescript-sdk
Release list
@modelcontextprotocol/server@2.0.0-beta.3
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().
@modelcontextprotocol/node@2.0.0-beta.3
Patch Changes
-
#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. -
#2445
78fabeaThanks @felixweinberger! - Document composing the host and origin validation guards in front oftoNodeHandlerfor hand-wirednode:httpservers, matching the protected wiring the examples and serving guide now demonstrate. -
Updated dependencies [
44797d7,1b90c96,561c6d8,ce2f65d,7e69735,e8de519,0ab5d14,24be404,7635115,61866d7]:- @modelcontextprotocol/server@2.0.0-beta.3
@modelcontextprotocol/hono@2.0.0-beta.3
Patch Changes
-
#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. -
Updated dependencies [
44797d7,1b90c96,561c6d8,ce2f65d,7e69735,e8de519,0ab5d14,24be404,7635115,61866d7]:- @modelcontextprotocol/server@2.0.0-beta.3
@modelcontextprotocol/fastify@2.0.0-beta.3
@modelcontextprotocol/express@2.0.0-beta.3
Patch Changes
-
#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. -
Updated dependencies [
44797d7,1b90c96,561c6d8,ce2f65d,7e69735,e8de519,0ab5d14,24be404,7635115,61866d7]:- @modelcontextprotocol/server@2.0.0-beta.3
@modelcontextprotocol/core@2.0.0-beta.3
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.
@modelcontextprotocol/codemod@2.0.0-beta.3
Patch Changes
-
#2419
79dc162Thanks @felixweinberger! - Read the v2 package versions the codemod writes into migratedpackage.jsonfiles directly from the workspace manifests at build time, replacing the committed generatedversions.ts(which went stale after every release and made source builds write outdated versions). -
#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.
@modelcontextprotocol/client@2.0.0-beta.3
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.
@modelcontextprotocol/server@2.0.0-beta.2
Patch Changes
-
#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. -
#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.
@modelcontextprotocol/server-legacy@2.0.0-beta.2
Patch Changes
- #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.