@modelcontextprotocol/server
2.3.1 · 5 Oct 2026Apache-2.08.2M / weekRepositoryHomepagenpmAtom feed
Latest releases
2.3.1
5 Oct 2026requireBearerAuthin@modelcontextprotocol/server-legacytakes the optionalexpectedResourcethat@modelcontextprotocol/server2.3.0 added: it accepts only tokens issued for this server (the token's audience). Off unless you set it.The npm pages of
@modelcontextprotocol/serverand@modelcontextprotocol/clientnow open with one note that links the documentation, the migration guide and the issue form.
2.3.0
2 Oct 2026One server per request.
Server.connect()now rejects while the instance is already connected, and a stateless Streamable HTTP transport handles one request. Create theMcpServerand the transport inside the request handler (or in thecreateMcpHandlerfactory) instead of sharing one instance across requests. Creating a server is cheap since #2889.Redirects stay on the same origin. The HTTP client transports now follow a redirect only when it stays on the same origin (same scheme, host and port; http to https on the same host is allowed). A deployment whose endpoint redirects to another host or port either configures the final URL or sets
redirectPolicy: 'follow'on the transport. In browsers, a redirected request fails unless that option is set.New options, both off unless you set them.
maxToolInputElementsonMcpServerlimits the number of array elements and object members in a tool call's arguments.expectedResourceonrequireBearerAuth/verifyBearerTokenaccepts only tokens issued for this server (the token's audience); with Express, upgrade@modelcontextprotocol/expresstogether with@modelcontextprotocol/server. (#2926, #2929)
Package Version @modelcontextprotocol/client2.3.0 @modelcontextprotocol/server2.3.0 @modelcontextprotocol/core2.3.0 @modelcontextprotocol/server-legacy2.3.0 @modelcontextprotocol/codemod2.3.0 @modelcontextprotocol/node2.1.1 @modelcontextprotocol/express,hono2.0.2 @modelcontextprotocol/fastify2.0.1 Upgrade notes
One server per request.
Server.connect()now rejects while the instance is already connected, and a stateless Streamable HTTP transport handles one request. Create theMcpServerand the transport inside the request handler (or in thecreateMcpHandlerfactory) instead of sharing one instance across requests. Creating a server is cheap since #2889.Redirects stay on the same origin. The HTTP client transports now follow a redirect only when it stays on the same origin (same scheme, host and port; http to https on the same host is allowed). A deployment whose endpoint redirects to another host or port either configures the final URL or sets
redirectPolicy: 'follow'on the transport. In browsers, a redirected request fails unless that option is set.New options, both off unless you set them.
maxToolInputElementsonMcpServerlimits the number of array elements and object members in a tool call's arguments.expectedResourceonrequireBearerAuth/verifyBearerTokenaccepts only tokens issued for this server (the token's audience); with Express, upgrade@modelcontextprotocol/expresstogether with@modelcontextprotocol/server. (#2926, #2929)prompts/getwithoutargumentsis validated as{}, astools/callalready is. A top-level.optional()or.default(...)on a prompt'sargsSchemano longer seesundefined.The client requires
eventsource-parser3.0.8 or later. It cuts the time and memory needed to receive a large message sent as a single SSE event: in our test a 100 MB tool result went from about a minute and 1.7 GB of peak memory to under a second and about 0.5 GB.
New
validateOriginHeaderand theallowedOriginsoption of the Express, Fastify and Hono app helpers accept<scheme>://*entries such asmoz-extension://*, so a server can admit MCP clients that run as a browser extension.tasks/getandtasks/cancelof the Tasks extension can be served and called on a 2026-07-28 connection.
Fixes
A large message received as a single SSE event over Streamable HTTP is fast again: a 50 MB tool result that took about 13 seconds arrives in under a second.
prompts/getwithoutargumentsno longer fails when every argument of the prompt is optional.SSEClientTransportrefreshes once after a 401 on connect instead of retrying without limit. (#2905, #2934)registerToolno longer converts tool schemas up front, so a server built per request stops converting every tool on every request.honois a regular dependency of@modelcontextprotocol/node, so installs with strict peer-dependency checking no longer fail.A
server/discoverprobe answered with an unusable 2xx reply now says so instead of reading like a network failure.McpServer.registerPrompt()types the callback correctly when noargsSchemais given.The
licensefield of the package manifests isApache-2.0.
2.2.0
28 Sep 2026- deprecated
Pass
expectedIssuerto the machine-to-machine OAuth providers. ConstructingClientCredentialsProvider,PrivateKeyJwtProvider,StaticPrivateKeyJwtProviderorCrossAppAccessProviderwithout it is deprecated and logs a warning. Set it to theissuerof the authorization server the credentials were registered with. fetchToken()checks which authorization server the client information belongs to. It throwsAuthorizationServerMismatchErrorbefore sending anything when the provider's client information is bound to a different authorization server.List calls return the whole list.
listTools(),listPrompts(),listResources()andlistResourceTemplates()called without a cursor now follownextCursoruntil the server stops sending one.listMaxPagesstill caps the walk.
Package Version @modelcontextprotocol/client2.2.0 @modelcontextprotocol/server2.2.0 @modelcontextprotocol/core2.2.0 @modelcontextprotocol/server-legacy2.2.0 @modelcontextprotocol/codemod2.2.0 @modelcontextprotocol/node,express,hono,fastifyunchanged Upgrade notes
- deprecated
Pass
expectedIssuerto the machine-to-machine OAuth providers. ConstructingClientCredentialsProvider,PrivateKeyJwtProvider,StaticPrivateKeyJwtProviderorCrossAppAccessProviderwithout it is deprecated and logs a warning. Set it to theissuerof the authorization server the credentials were registered with. fetchToken()checks which authorization server the client information belongs to. It throwsAuthorizationServerMismatchErrorbefore sending anything when the provider's client information is bound to a different authorization server.List calls return the whole list.
listTools(),listPrompts(),listResources()andlistResourceTemplates()called without a cursor now follownextCursoruntil the server stops sending one.listMaxPagesstill caps the walk.
Fixes
CommonJS TypeScript projects type-check again: the
josetypes used by the DPoP API are inlined into the declaration files (regression in 2.1.0).Client.listen()no longer lets a rejection escape as a process-level unhandled rejection, and no longer hangs when a send never settles._metais preserved oninput_requiredresults.Hostnames ending in
.localhostcount as loopback for OAuth token endpoints, so host-based multi-tenant local setups work.OAuthTokensSchemaandOAuthClientInformationSchemaaccept the optionalissuerfield, so a provider that reads its storage back through them keeps it.createMcpHandlerno longer overflows the stack when the factory returns the same server instance for more than one request. Returning a fresh instance per request is still required.createMcpHandlerends asubscriptions/listenstream right after the acknowledgement when it honored none of the requested notification types.Sending a notification on a closed connection no longer produces a briefly unhandled promise rejection.
The
v1-to-v2codemod keeps a file's leading comment block and directives above the rewritten imports.Corrected citations in the JSDoc for OAuth token endpoints and for the
registerClientdeprecation notice. (#2768, #2729)
Per-package changelogs
client · server · core · server-legacy · codemod
2.1.0
23 Sep 2026- deprecated
SdkErrorandSdkHttpErroraccept standardErrorOptionsas an optional fourth constructor argument and forward it toError, so a wrapped error is reachable through the standardError.causechain. Version-negotiation probe failures (SdkErrorCode.EraNegotiationFailed) now use it: the underlyingTypeError: fetch failedand the DNS or socket error beneath it surface viaerror.cause, so pino, Sentry, andutil.inspectrenderENOTFOUND/ECONNREFUSED/ETIMEDOUTinstead of stopping at theSdkError(#2657). The previouserror.data.causeslot is still populated for compatibility but is deprecated and slated for removal; readerror.causeinstead. Add request-time OAuth scope challenges for tools, resources, resource templates,
and prompts. Each primitive'sscopeChallengecallback receives the parsed
request and verified authentication info, then either continues or returns the
exact scope set for aninsufficient_scoperesponse.requireScopesprovides a
small helper for static all-of checks.createMcpHandlerand Streamable HTTP transports return HTTP 403 with an
insufficient_scopechallenge before handler execution or SSE setup. The
preflight is active whenever a registered primitive carries ascopeChallenge
callback — there is no handler- or transport-level configuration. The
challenge'sWWW-Authenticateheader is built by the same formatter as the
bearer-auth 401/403 answers, and itsresource_metadataparameter is derived
from the verifiedAuthInfo:requireBearerAuth/verifyBearerTokennow
stamp their configuredresourceMetadataUrlonto theAuthInfothey return
(new optionalAuthInfo.resourceMetadataUrlfield), with a fallback to the
well-known location for an HTTP(S) RFC 8707resourceidentifier; the
parameter is omitted when neither is available.Treat request id
0as a real id. Two guards tested aRequestIdfor truthiness, so the legal JSON-RPC ids0and''were read as absent. Id0is not a corner case: the outbound request counter is zero-based, so it is the first id every peer assigns, which on the server→client leg is the firstsampling/createMessage,elicitation/create, orroots/lista server sends.Absent is now the only value that means "no id".
Minor Changes
Add request-time OAuth scope challenges for tools, resources, resource templates,
and prompts. Each primitive'sscopeChallengecallback receives the parsed
request and verified authentication info, then either continues or returns the
exact scope set for aninsufficient_scoperesponse.requireScopesprovides a
small helper for static all-of checks.createMcpHandlerand Streamable HTTP transports return HTTP 403 with an
insufficient_scopechallenge before handler execution or SSE setup. The
preflight is active whenever a registered primitive carries ascopeChallenge
callback — there is no handler- or transport-level configuration. The
challenge'sWWW-Authenticateheader is built by the same formatter as the
bearer-auth 401/403 answers, and itsresource_metadataparameter is derived
from the verifiedAuthInfo:requireBearerAuth/verifyBearerTokennow
stamp their configuredresourceMetadataUrlonto theAuthInfothey return
(new optionalAuthInfo.resourceMetadataUrlfield), with a fallback to the
well-known location for an HTTP(S) RFC 8707resourceidentifier; the
parameter is omitted when neither is available.
Patch Changes
- deprecated
SdkErrorandSdkHttpErroraccept standardErrorOptionsas an optional fourth constructor argument and forward it toError, so a wrapped error is reachable through the standardError.causechain. Version-negotiation probe failures (SdkErrorCode.EraNegotiationFailed) now use it: the underlyingTypeError: fetch failedand the DNS or socket error beneath it surface viaerror.cause, so pino, Sentry, andutil.inspectrenderENOTFOUND/ECONNREFUSED/ETIMEDOUTinstead of stopping at theSdkError(#2657). The previouserror.data.causeslot is still populated for compatibility but is deprecated and slated for removal; readerror.causeinstead. Treat request id
0as a real id. Two guards tested aRequestIdfor truthiness, so the legal JSON-RPC ids0and''were read as absent. Id0is not a corner case: the outbound request counter is zero-based, so it is the first id every peer assigns, which on the server→client leg is the firstsampling/createMessage,elicitation/create, orroots/lista server sends.notifications/cancelledcarrying id0was ignored, and the in-flight handler ran to completion with itsAbortSignalnever fired.A notification sent with
relatedRequestId: 0wrongly passed the debounce gate (for methods opted intodebouncedNotificationMethods). Because the pending set is keyed by method alone, a second such notification in the same tick was silently dropped rather than sent.
Absent is now the only value that means "no id".
Stop sending
notifications/cancelledfor theinitializehandshake. The spec is explicit that a client MUST NOT attempt to cancel itsinitializerequest, but the outbound cancel path fired for any in-flight request: aborting theAbortSignalpassed toconnect(), or letting the handshake hit its timeout, put a forbidden cancellation on the wire naming the initialize request id.The local behaviour is unchanged — the caller's promise still rejects with the same abort/timeout error, and
connect()still tears the connection down. Only the wire notification is suppressed. Every other method keeps the existing cancellation path.Read Streamable HTTP request bodies with a size limit. Every SDK-owned body read
WebStandardStreamableHTTPServerTransport(and the Node transport built on it),createMcpHandler,toNodeHandler, andcreateMcpHonoApp's JSON pre-parse — now stops at
4 MiB by default (the limit the legacy SSE transport already uses; the Express adapter and stdio
bound their reads too) and answers413 Payload Too Largebefore anything is parsed.toWebRequest(when it reads the Node stream itself) now rejects once the body exceeds the
limit with an error whosenameis'RequestBodyTooLargeError'andstatusis413, andtoNodeHandleranswers that with413; hand-wired callers oftoWebRequestshould handle the
rejection or pass a pre-parsed body, andisLegacyRequestreports such a request as non-legacy
so the modern handler answers it. JSON-RPC batch arrays are limited to 100 messages; a longer
batch is answered400/-32600and none of it is dispatched.The limit is configurable with a new
maxRequestBodySizeoption (bytes, default
DEFAULT_MAX_REQUEST_BODY_SIZE= 4 MiB, exported from@modelcontextprotocol/server) on
WebStandardStreamableHTTPServerTransportOptions,CreateMcpHandlerOptions(forwarded to its
stateless legacy leg;isLegacyRequestandlegacyStatelessFallbacktake the same option),
CreateMcpHonoAppOptions, andToNodeHandlerOptions/ToWebRequestOptions(the adapter's
bound applies before the handler's, so raise both). The bounded reader is exported as
readRequestBodyfor adapter authors. Hosts that pre-parse the body and pass it as
parsedBodyskip the SDK's read and its size limit entirely; the batch bound applies either way.createMcpHonoAppandcreateMcpExpressAppnow run their Host/Origin validation before the
JSON body parser, so a request from a disallowed Host or Origin with an invalid JSON body is
answered403rather than400, and its body is not read.Reject a modern (2026-07-28) POST that omits the required
MCP-Protocol-Versionheader.createMcpHandleraccepted a request whose body carried a valid per-request_meta
envelope but whoseMCP-Protocol-Versionheader was absent: the request was classified
modern, dispatched, and answered200— tool handlers ran. Only the mismatch case
(header present, disagreeing with the body) was rejected, so of the standard headers
SEP-2243 requires on a modern POST, presence was enforced forMcp-Method(and for
Mcp-Nameon the methods that mirrorparams.name/params.uri) but not for
MCP-Protocol-Version.Such a request is now refused with
400 Bad Requestand JSON-RPC-32020
(HeaderMismatch), matching the shape the sibling missing-header cells already emit and
echoing the request id — per the Streamable HTTP spec, which requires the header on every
POST and lists a missing required standard header as aHeaderMismatchfailure. The
spec's allowance to treat a header-less request as2025-03-26is available only to a
server that also serves pre-2025-06-18 clients, and permits routing it to legacy
handling — never serving it as 2026-07-28; underlegacy: 'reject'the requirement is
unconditional.Era classification is deliberately unchanged and stays body-primary: a proxy that strips
the header still must not change the era, so such a request is still classified modern
and is refused one rung later, atstandard-header-validation— the same rung that
already answers a missingMcp-Method. Legacy-era traffic is untouched, notifications
are unaffected, body-lessGET/DELETEsession operations are method-routed before
any header validation, and stdio serving (which has no HTTP headers) is not involved.Clients built with this SDK always send the header, so no first-party client is affected;
hand-rolled clients that omitted it must add it.StdioServerTransportnow closes itself and firesonclosewhen its stdin ends or closes. The stdio binding says servers "SHOULD exit promptly when their standard input is closed" — stdin EOF is the primary graceful-shutdown signal, and on some platforms (notably Windows, where no signal is delivered when the parent goes away) the only reliable one. Previously the transport listened only fordataanderror, so when an MCP client hung up its end of the pipe (window closed, session restarted, host crashed) the server never noticed:onclosenever fired, nothing tore down, and server processes accumulated as zombies until killed by hand. The transport now attachesend/closelisteners on stdin that close the transport (idempotently —onclosestill fires exactly once ifclose()is also called), soServer/McpServerandserveStdiotear down through the existingonclosechain and a well-behaved server process exits naturally. Requests still in flight when stdin ends are aborted (their handlers observesignal.aborted) and their responses are not written: EOF means the client has hung up and is no longer waiting. A client that wants answers keeps stdin open until it has read them.Emit and validate the
Mcp-Nameheader for tasks requests per SEP-2663's Streamable HTTP binding: the client transport now mirrorsparams.taskIdintoMcp-Nameontasks/get/tasks/update/tasks/cancel(previously omitted, causing conforming servers to reject every task poll with-32020 HeaderMismatch), and the server-side standard-header validation cross-checks it via the same sharedMCP_NAME_HEADER_SOURCEtable.On the server,
createMcpHandlernow answers a modern (2026-07-28)tasks/get/tasks/update/tasks/cancelPOST that omitsMcp-Name, or whose header disagrees withparams.taskId, with400/-32020(HeaderMismatch) at thestandard-header-validationrung, the same treatmenttools/call/prompts/get/resources/readalready get. Legacy-era (2025-11-25) tasks traffic is unaffected. Clients built with this SDK release send the header; hand-rolled clients that omitted it must add it.
2.0.0
27 Jul 2026Export the
Protocolbase 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.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.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:
Minor Changes
Export the
Protocolbase 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.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.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').
Allow
inputRequired.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.Add configurable SSE keep-alive comment frames to Streamable HTTP transports and apply
createMcpHandler's existingkeepAliveMsoption to every HTTP SSE stream it serves.Add runtime-neutral Bearer authentication to
@modelcontextprotocol/server:requireBearerAuthgates web-standardfetch(request)hosts (Cloudflare
Workers, Deno, Bun, Hono), built on the exportedverifyBearerTokenandbearerAuthChallengeResponsepieces, 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-exportsOAuthTokenVerifieras before.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-standardfetch(request)hosts, built on the exportedbuildOAuthProtectedResourceMetadata, withgetOAuthProtectedResourceMetadataUrlnow 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 withVary.Add
preloadSchemas(), 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
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).Restore the v1 parse tolerance for
CallToolResult.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.Fix the CommonJS
validators/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.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.POSTs whose
Content-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 inspectContent-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.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.Construct the default Ajv validation engine lazily on first validation. Creating a
ClientorServerno longer pays the ajv + ajv-formats instantiation cost at startup when no JSON Schema validation ever runs.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.
Return JSON-RPC Invalid Params with the original URI and an
invalid_urireason whenresources/readreceives a syntactically malformed URI.Return HTTP 400 for a
MissingRequiredClientCapabilityError(-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.Stop advertising validator provider classes from the root client/server type declarations. The provider classes remain available from the explicit validator subpaths.
Strip RFC 9110 optional whitespace around inbound
MCP-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().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).
Dependency tracking · free for 3 projects
Know when your own dependencies move
@modelcontextprotocol/server had 3 security advisories in the last 12 months. Inlinr emails you the day one touches a project of yours, with the version that fixes it.
Advisories reach you
An email when a critical or high advisory affects a project, before the next install.
Upgrades checked first
Peer ranges, Node, ESM-only and types changes read before you bump.
Only what matters
Majors and breaking notes stay; patches and sibling bumps fold into one line.
Registry facts last checked 22 Oct 2026. Releases and advisories last read 22 Oct 2026.