MCP TypeScript SDK: v1 → v2 Migration

June 12, 2026 · View on GitHub

Apply these changes in order: dependencies → imports → API calls → type aliases.

1. Environment

  • Node.js 20+ required (v18 dropped)
  • ESM only (CJS dropped). If the project uses require(), convert to import/export or use dynamic import().

2. Dependencies

Remove the old package and install only what you need:

npm uninstall @modelcontextprotocol/sdk
You needInstall
Client onlynpm install @modelcontextprotocol/client
Server onlynpm install @modelcontextprotocol/server
Server + Node.js HTTPnpm install @modelcontextprotocol/server @modelcontextprotocol/node
Server + Expressnpm install @modelcontextprotocol/server @modelcontextprotocol/express
Server + Hononpm install @modelcontextprotocol/server @modelcontextprotocol/hono

@modelcontextprotocol/core is installed automatically as a dependency.

3. Import Mapping

Replace all @modelcontextprotocol/sdk/... imports using this table.

Client imports

v1 import pathv2 package
@modelcontextprotocol/sdk/client/index.js@modelcontextprotocol/client
@modelcontextprotocol/sdk/client/auth.js@modelcontextprotocol/client
@modelcontextprotocol/sdk/client/streamableHttp.js@modelcontextprotocol/client
@modelcontextprotocol/sdk/client/sse.js@modelcontextprotocol/client
@modelcontextprotocol/sdk/client/stdio.js@modelcontextprotocol/client/stdio
@modelcontextprotocol/sdk/client/websocket.jsREMOVED (use Streamable HTTP or stdio; implement Transport for custom needs)

Server imports

v1 import pathv2 package
@modelcontextprotocol/sdk/server/mcp.js@modelcontextprotocol/server
@modelcontextprotocol/sdk/server/index.js@modelcontextprotocol/server
@modelcontextprotocol/sdk/server/stdio.js@modelcontextprotocol/server/stdio
@modelcontextprotocol/sdk/server/streamableHttp.js@modelcontextprotocol/node (class renamed to NodeStreamableHTTPServerTransport) OR @modelcontextprotocol/server (web-standard WebStandardStreamableHTTPServerTransport for Cloudflare Workers, Deno, etc.)
@modelcontextprotocol/sdk/server/sse.jsREMOVED (migrate to Streamable HTTP); legacy bridge: @modelcontextprotocol/server-legacy/sse
@modelcontextprotocol/sdk/server/auth/*RS helpers (requireBearerAuth, mcpAuthMetadataRouter, OAuthTokenVerifier) → @modelcontextprotocol/express; AS helpers (mcpAuthRouter, OAuthServerProvider, etc.) → @modelcontextprotocol/server-legacy/auth (deprecated); migrate AS to an external IdP/OAuth library
@modelcontextprotocol/sdk/server/middleware.js@modelcontextprotocol/express (signature changed, see section 8)

Types / shared imports

v1 import pathv2 package
@modelcontextprotocol/sdk/types.js@modelcontextprotocol/client or @modelcontextprotocol/server
@modelcontextprotocol/sdk/shared/protocol.js@modelcontextprotocol/client or @modelcontextprotocol/server
@modelcontextprotocol/sdk/shared/transport.js@modelcontextprotocol/client or @modelcontextprotocol/server
@modelcontextprotocol/sdk/shared/uriTemplate.js@modelcontextprotocol/client or @modelcontextprotocol/server
@modelcontextprotocol/sdk/shared/auth.js@modelcontextprotocol/client or @modelcontextprotocol/server
@modelcontextprotocol/sdk/shared/stdio.js@modelcontextprotocol/client or @modelcontextprotocol/server (ReadBuffer, serializeMessage, deserializeMessage are in the root barrel; the ./stdio subpath only has the transport class)

Notes:

  • @modelcontextprotocol/client and @modelcontextprotocol/server both re-export shared types from @modelcontextprotocol/core, so import from whichever package you already depend on. Do not import from @modelcontextprotocol/core directly — it is an internal package.
  • When multiple v1 imports map to the same v2 package, consolidate them into a single import statement.

4. Renamed Symbols

v1 symbolv2 symbolv2 package
StreamableHTTPServerTransportNodeStreamableHTTPServerTransport@modelcontextprotocol/node

5. Removed / Renamed Type Aliases and Symbols

v1 (removed)v2 (replacement)
JSONRPCErrorJSONRPCErrorResponse
JSONRPCErrorSchemaJSONRPCErrorResponseSchema
isJSONRPCErrorisJSONRPCErrorResponse
isJSONRPCResponse (deprecated in v1)isJSONRPCResultResponse (not v2's new isJSONRPCResponse, which correctly matches both result and error)
ResourceReferenceResourceTemplateReference
ResourceReferenceSchemaResourceTemplateReferenceSchema
IsomorphicHeadersREMOVED (use Web Standard Headers)
AuthInfo (from server/auth/types.js)AuthInfo (now re-exported by @modelcontextprotocol/client and @modelcontextprotocol/server)
McpErrorProtocolError
ErrorCodeProtocolErrorCode
ErrorCode.RequestTimeoutSdkErrorCode.RequestTimeout
ErrorCode.ConnectionClosedSdkErrorCode.ConnectionClosed
StreamableHTTPErrorREMOVED (use SdkHttpError with SdkErrorCode.ClientHttp*)
WebSocketClientTransportREMOVED (use StreamableHTTPClientTransport or StdioClientTransport)

All other type symbols from @modelcontextprotocol/sdk/types.js retain their original names. Zod schemas (e.g., CallToolResultSchema, ListToolsResultSchema) are no longer part of the public API — they are internal to the SDK. For runtime validation, use isSpecType.TypeName(value) (e.g., isSpecType.CallToolResult(v)) or specTypeSchemas.TypeName for the StandardSchemaV1Sync validator object. The keys are typed as SpecTypeName, a literal union of all spec type names.

Error class changes

Three error classes now exist:

  • ProtocolError (renamed from McpError): Protocol errors that cross the wire as JSON-RPC responses
  • SdkError (new): Local SDK errors that never cross the wire
  • SdkHttpError (extends SdkError): HTTP transport errors with typed .status and .statusText accessors
Error scenariov1 typev2 type
Request timeoutMcpError with ErrorCode.RequestTimeoutSdkError with SdkErrorCode.RequestTimeout
Connection closedMcpError with ErrorCode.ConnectionClosedSdkError with SdkErrorCode.ConnectionClosed
Capability not supportednew Error(...)SdkError with SdkErrorCode.CapabilityNotSupported
Not connectednew Error('Not connected')SdkError with SdkErrorCode.NotConnected
Invalid params (server response)McpError with ErrorCode.InvalidParamsProtocolError with ProtocolErrorCode.InvalidParams
HTTP transport errorStreamableHTTPErrorSdkHttpError with SdkErrorCode.ClientHttp*
Failed to open SSE streamStreamableHTTPErrorSdkHttpError with SdkErrorCode.ClientHttpFailedToOpenStream
401 after re-auth (circuit break)StreamableHTTPErrorSdkHttpError with SdkErrorCode.ClientHttpAuthentication
403 after upscopingStreamableHTTPErrorSdkHttpError with SdkErrorCode.ClientHttpForbidden
Unexpected content typeStreamableHTTPErrorSdkError with SdkErrorCode.ClientHttpUnexpectedContent
Session termination failedStreamableHTTPErrorSdkHttpError with SdkErrorCode.ClientHttpFailedToTerminateSession
Response result fails schemaZodError (raw)SdkError with SdkErrorCode.InvalidResult

New SdkErrorCode enum values:

  • SdkErrorCode.NotConnected = 'NOT_CONNECTED'
  • SdkErrorCode.AlreadyConnected = 'ALREADY_CONNECTED'
  • SdkErrorCode.NotInitialized = 'NOT_INITIALIZED'
  • SdkErrorCode.CapabilityNotSupported = 'CAPABILITY_NOT_SUPPORTED'
  • SdkErrorCode.RequestTimeout = 'REQUEST_TIMEOUT'
  • SdkErrorCode.ConnectionClosed = 'CONNECTION_CLOSED'
  • SdkErrorCode.SendFailed = 'SEND_FAILED'
  • SdkErrorCode.InvalidResult = 'INVALID_RESULT'
  • SdkErrorCode.ClientHttpNotImplemented = 'CLIENT_HTTP_NOT_IMPLEMENTED'
  • SdkErrorCode.ClientHttpAuthentication = 'CLIENT_HTTP_AUTHENTICATION'
  • SdkErrorCode.ClientHttpForbidden = 'CLIENT_HTTP_FORBIDDEN'
  • SdkErrorCode.ClientHttpUnexpectedContent = 'CLIENT_HTTP_UNEXPECTED_CONTENT'
  • SdkErrorCode.ClientHttpFailedToOpenStream = 'CLIENT_HTTP_FAILED_TO_OPEN_STREAM'
  • SdkErrorCode.ClientHttpFailedToTerminateSession = 'CLIENT_HTTP_FAILED_TO_TERMINATE_SESSION'

Update error handling:

// v1
if (error instanceof McpError && error.code === ErrorCode.RequestTimeout) { ... }

// v2
import { SdkError, SdkErrorCode } from '@modelcontextprotocol/client';
if (error instanceof SdkError && error.code === SdkErrorCode.RequestTimeout) { ... }

Update HTTP transport error handling:

// v1
import { StreamableHTTPError } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
if (error instanceof StreamableHTTPError) {
    console.log('HTTP status:', error.code);
}

// v2
import { SdkHttpError, SdkErrorCode } from '@modelcontextprotocol/client';
if (error instanceof SdkHttpError) {
    console.log('HTTP status:', error.status); // number — typed accessor
    console.log('Status text:', error.statusText); // string | undefined
    switch (error.code) {
        case SdkErrorCode.ClientHttpAuthentication: // 401 after re-auth
        case SdkErrorCode.ClientHttpForbidden: // 403 after upscoping
        case SdkErrorCode.ClientHttpFailedToOpenStream:
        case SdkErrorCode.ClientHttpNotImplemented:
            break;
    }
}

OAuth error consolidation

Individual OAuth error classes replaced with single OAuthError class and OAuthErrorCode enum:

v1 Classv2 Equivalent
InvalidRequestErrorOAuthError with OAuthErrorCode.InvalidRequest
InvalidClientErrorOAuthError with OAuthErrorCode.InvalidClient
InvalidGrantErrorOAuthError with OAuthErrorCode.InvalidGrant
UnauthorizedClientErrorOAuthError with OAuthErrorCode.UnauthorizedClient
UnsupportedGrantTypeErrorOAuthError with OAuthErrorCode.UnsupportedGrantType
InvalidScopeErrorOAuthError with OAuthErrorCode.InvalidScope
AccessDeniedErrorOAuthError with OAuthErrorCode.AccessDenied
ServerErrorOAuthError with OAuthErrorCode.ServerError
TemporarilyUnavailableErrorOAuthError with OAuthErrorCode.TemporarilyUnavailable
UnsupportedResponseTypeErrorOAuthError with OAuthErrorCode.UnsupportedResponseType
UnsupportedTokenTypeErrorOAuthError with OAuthErrorCode.UnsupportedTokenType
InvalidTokenErrorOAuthError with OAuthErrorCode.InvalidToken
MethodNotAllowedErrorOAuthError with OAuthErrorCode.MethodNotAllowed
TooManyRequestsErrorOAuthError with OAuthErrorCode.TooManyRequests
InvalidClientMetadataErrorOAuthError with OAuthErrorCode.InvalidClientMetadata
InsufficientScopeErrorOAuthError with OAuthErrorCode.InsufficientScope
InvalidTargetErrorOAuthError with OAuthErrorCode.InvalidTarget
CustomOAuthErrornew OAuthError(customCode, message)

Removed: OAUTH_ERRORS constant.

Update OAuth error handling:

// v1
import { InvalidClientError, InvalidGrantError } from '@modelcontextprotocol/client';
if (error instanceof InvalidClientError) { ... }

// v2
import { OAuthError, OAuthErrorCode } from '@modelcontextprotocol/client';
if (error instanceof OAuthError && error.code === OAuthErrorCode.InvalidClient) { ... }

Unchanged APIs (only import paths changed): Client constructor and most methods, McpServer constructor, server.connect(), server.close(), all client transports (StreamableHTTPClientTransport, SSEClientTransport, StdioClientTransport), StdioServerTransport, all Zod schemas, all callback return types. Note: callTool() and request() signatures changed (schema parameter removed, see section 11).

6. McpServer API Changes

The variadic .tool(), .prompt(), .resource() methods are removed. Use the register* methods with a config object.

IMPORTANT: v2 requires schema objects implementing Standard Schema — raw shapes like { name: z.string() } are no longer supported. Wrap with z.object() (Zod v4), or use ArkType's type({...}), or Valibot. For raw JSON Schema, wrap with fromJsonSchema(schema) from @modelcontextprotocol/server (validator defaults automatically; pass an explicit validator for custom configurations). Applies to inputSchema, outputSchema, and argsSchema.

Tools

// v1: server.tool(name, schema, callback) - raw shape worked
server.tool('greet', { name: z.string() }, async ({ name }) => {
    return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
});

// v1: server.tool(name, description, schema, callback)
server.tool('greet', 'Greet a user', { name: z.string() }, async ({ name }) => {
    return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
});

// v2: server.registerTool(name, config, callback)
server.registerTool(
    'greet',
    {
        description: 'Greet a user',
        inputSchema: z.object({ name: z.string() })
    },
    async ({ name }) => {
        return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
    }
);

Config object fields: title?, description?, inputSchema?, outputSchema?, annotations?, _meta?

Prompts

// v1: server.prompt(name, schema, callback) - raw shape worked
server.prompt('summarize', { text: z.string() }, async ({ text }) => {
    return { messages: [{ role: 'user', content: { type: 'text', text } }] };
});

// v2: server.registerPrompt(name, config, callback)
server.registerPrompt(
    'summarize',
    {
        argsSchema: z.object({ text: z.string() })
    },
    async ({ text }) => {
        return { messages: [{ role: 'user', content: { type: 'text', text } }] };
    }
);

Config object fields: title?, description?, argsSchema?

Resources

// v1: server.resource(name, uri, callback)
server.resource('config', 'config://app', async uri => {
    return { contents: [{ uri: uri.href, text: '{}' }] };
});

// v2: server.registerResource(name, uri, metadata, callback)
server.registerResource('config', 'config://app', {}, async uri => {
    return { contents: [{ uri: uri.href, text: '{}' }] };
});

Note: the third argument (metadata) is required — pass {} if no metadata.

Schema Migration Quick Reference

v1 (raw shape)v2 (Standard Schema object)
{ name: z.string() }z.object({ name: z.string() })
{ count: z.number().optional() }z.object({ count: z.number().optional() })
{} (empty)z.object({})
undefined (no schema)undefined or omit the field

Removed core exports

Removed from @modelcontextprotocol/coreReplacement
schemaToJson(schema)standardSchemaToJsonSchema(schema)
parseSchemaAsync(schema, data)validateStandardSchema(schema, data)
SchemaInput<T>StandardSchemaWithJSON.InferInput<T>
getSchemaShape, getSchemaDescription, isOptionalSchema, unwrapOptionalSchemanone (internal Zod introspection helpers)

7. Headers API

Transport constructors now use the Web Standard Headers object instead of plain objects. The custom RequestInfo type has been replaced with the standard Web Request object, giving access to headers, URL, query parameters, and method.

// v1: plain object, bracket access, custom RequestInfo
headers: { 'Authorization': 'Bearer token' }
extra.requestInfo?.headers['mcp-session-id']

// v2: Headers object, .get() access, standard Web Request
headers: new Headers({ 'Authorization': 'Bearer token' })
ctx.http?.req?.headers.get('mcp-session-id')
new URL(ctx.http?.req?.url).searchParams.get('debug')

8. Removed Server Features

SSE server transport

SSEServerTransport removed entirely. Migrate to NodeStreamableHTTPServerTransport (from @modelcontextprotocol/node). Client-side SSEClientTransport still available for connecting to legacy servers. Legacy bridge: import { SSEServerTransport } from '@modelcontextprotocol/server-legacy/sse' (deprecated, frozen v1 copy).

Server-side auth

Resource Server helpers (requireBearerAuth, mcpAuthMetadataRouter, getOAuthProtectedResourceMetadataUrl, OAuthTokenVerifier) are first-class in @modelcontextprotocol/express. Authorization Server helpers (mcpAuthRouter, OAuthServerProvider, ProxyOAuthServerProvider, authenticateClient, allowedMethods, etc.) are available from @modelcontextprotocol/server-legacy/auth (deprecated, frozen v1 copy). Migrate AS to an external IdP/OAuth library for production use. See examples/server/src/ for demos.

Host header validation (Express)

hostHeaderValidation() and localhostHostValidation() moved from server package to @modelcontextprotocol/express. Signature changed: takes string[] instead of options object.

// v1
import { hostHeaderValidation } from '@modelcontextprotocol/sdk/server/middleware.js';
app.use(hostHeaderValidation({ allowedHosts: ['example.com'] }));

// v2
import { hostHeaderValidation } from '@modelcontextprotocol/express';
app.use(hostHeaderValidation(['example.com']));

The server package now exports framework-agnostic alternatives: validateHostHeader(), localhostAllowedHostnames(), hostHeaderValidationResponse().

9. setRequestHandler / setNotificationHandler API

The low-level handler registration methods now take a method string instead of a Zod schema.

// v1: schema-based
server.setRequestHandler(InitializeRequestSchema, async (request) => { ... });
server.setNotificationHandler(LoggingMessageNotificationSchema, (notification) => { ... });

// v2: method string
server.setRequestHandler('initialize', async (request) => { ... });
server.setNotificationHandler('notifications/message', (notification) => { ... });

For custom (non-spec) methods, use the 3-arg form (method, schemas, handler):

// v1: Zod schema with method literal
server.setRequestHandler(z.object({ method: z.literal('acme/search'), params: P }), async req => { ... });

// v2: method string + schemas object; handler receives parsed params
server.setRequestHandler('acme/search', { params: P, result: R }, async (params, ctx) => { ... });
client.setNotificationHandler('acme/progress', { params: P }, (params, notification) => { ... });

The 3-arg notification handler receives the raw notification as its second argument, so _meta is recoverable via notification.params?._meta.

To send a custom-method request, pass a result schema as the second argument to request() (and ctx.mcpReq.send()):

// v1
await client.request({ method: 'acme/search', params }, ResultSchema);
// v2 (unchanged; now any Standard Schema, not Zod-only)
await client.request({ method: 'acme/search', params }, ResultSchema);

Schema to method string mapping:

v1 Schemav2 Method String
InitializeRequestSchema'initialize'
CallToolRequestSchema'tools/call'
ListToolsRequestSchema'tools/list'
ListPromptsRequestSchema'prompts/list'
GetPromptRequestSchema'prompts/get'
ListResourcesRequestSchema'resources/list'
ReadResourceRequestSchema'resources/read'
CreateMessageRequestSchema'sampling/createMessage'
ElicitRequestSchema'elicitation/create'
SetLevelRequestSchema'logging/setLevel'
PingRequestSchema'ping'
LoggingMessageNotificationSchema'notifications/message'
ToolListChangedNotificationSchema'notifications/tools/list_changed'
ResourceListChangedNotificationSchema'notifications/resources/list_changed'
PromptListChangedNotificationSchema'notifications/prompts/list_changed'
ProgressNotificationSchema'notifications/progress'
CancelledNotificationSchema'notifications/cancelled'
InitializedNotificationSchema'notifications/initialized'

Request/notification params remain fully typed. Remove unused schema imports after migration.

10. Request Handler Context Types

RequestHandlerExtra → structured context types with nested groups. Rename extractx in all handler callbacks.

v1v2
RequestHandlerExtraServerContext (server) / ClientContext (client) / BaseContext (base)
extra (param name)ctx
extra.signalctx.mcpReq.signal
extra.requestIdctx.mcpReq.id
extra._metactx.mcpReq._meta
extra.sendRequest(...)ctx.mcpReq.send(...)
extra.sendNotification(...)ctx.mcpReq.notify(...)
extra.authInfoctx.http?.authInfo
extra.sessionIdctx.sessionId
extra.requestInfoctx.http?.req (standard Web Request, only ServerContext)
extra.closeSSEStreamctx.http?.closeSSE (only ServerContext)
extra.closeStandaloneSSEStreamctx.http?.closeStandaloneSSE (only ServerContext)
extra.taskStore / taskId / taskRequestedTtlremoved; see §12

ServerContext convenience methods (new in v2, no v1 equivalent):

MethodDescriptionReplaces
ctx.mcpReq.log(level, data, logger?)Send log notification (respects client's level filter)server.sendLoggingMessage(...) from within handler
ctx.mcpReq.elicitInput(params, options?)Elicit user input (form or URL)server.elicitInput(...) from within handler
ctx.mcpReq.requestSampling(params, options?)Request LLM sampling from clientserver.createMessage(...) from within handler

11. Schema parameter removed from request(), send(), and callTool() (spec methods)

For spec methods, Protocol.request(), BaseContext.mcpReq.send(), and Client.callTool() no longer require a Zod result schema argument. The SDK resolves the schema internally from the method name.

// v1: schema required
import { CallToolResultSchema, ElicitResultSchema } from '@modelcontextprotocol/sdk/types.js';
const result = await client.request({ method: 'tools/call', params: { ... } }, CallToolResultSchema);
const elicit = await ctx.mcpReq.send({ method: 'elicitation/create', params: { ... } }, ElicitResultSchema);
const tool = await client.callTool({ name: 'my-tool', arguments: {} }, CompatibilityCallToolResultSchema);

// v2: no schema argument
const result = await client.request({ method: 'tools/call', params: { ... } });
const elicit = await ctx.mcpReq.send({ method: 'elicitation/create', params: { ... } });
const tool = await client.callTool({ name: 'my-tool', arguments: {} });
v1 callv2 call
client.request(req, ResultSchema)client.request(req)
client.request(req, ResultSchema, options)client.request(req, options)
ctx.mcpReq.send(req, ResultSchema)ctx.mcpReq.send(req)
ctx.mcpReq.send(req, ResultSchema, options)ctx.mcpReq.send(req, options)
client.callTool(params, CompatibilityCallToolResultSchema)client.callTool(params)
client.callTool(params, schema, options)client.callTool(params, options)

For custom (non-spec) methods, keep the result-schema argument — see §9. Only apply the rewrites above when req.method is a spec method.

Remove unused schema imports: CallToolResultSchema, CompatibilityCallToolResultSchema, ElicitResultSchema, CreateMessageResultSchema, etc., when they were only used in request()/send()/callTool() calls.

If a *Schema constant was used for runtime validation (not just as a request() argument), replace with isSpecType / specTypeSchemas:

v1 patternv2 replacement
CallToolResultSchema.safeParse(value).successisSpecType.CallToolResult(value)
<TypeName>Schema.safeParse(value).successisSpecType.<TypeName>(value)
<TypeName>Schema.parse(value)specTypeSchemas.<TypeName>['~standard'].validate(value) (returns a Result synchronously, not the value)
Passing <TypeName>Schema as a validator argumentspecTypeSchemas.<TypeName> (a StandardSchemaV1Sync<In, Out>)

isCallToolResult(value) still works, but isSpecType covers every spec type by name.

12. Experimental tasks interception removed

The 2025-11 task side-channel through Protocol is removed (was always @experimental). No mechanical migration; remove usages.

RemovedNotes
ProtocolOptions.tasksdrop the option
protocol.taskManagergone
RequestOptions.task / .relatedTask, NotificationOptions.relatedTaskdrop the option
BaseContext.task (ctx.task?.*)gone
assertTaskCapability / assertTaskHandlerCapability overridesdelete the override
*.experimental.tasks.* accessors, Experimental{Client,Server,McpServer}Tasksremoved
requestStream / callToolStream / createMessageStream / elicitInputStreamremoved; no streaming variant
registerToolTask, ToolTaskHandler, TaskRequestHandler, CreateTaskRequestHandlerremoved
TaskMessageQueue, InMemoryTaskMessageQueue, BaseQueuedMessage, Queued*, CreateTaskServerContext, TaskServerContext, TaskToolExecutionremoved
ResponseMessage, BaseResponseMessage, ErrorMessage, AsyncGeneratorValue, TaskStatusMessage, TaskCreatedMessage, ResultMessage, takeResult, toArrayAsyncremoved

TaskStore / InMemoryTaskStore / CreateTaskOptions / isTerminal (storage layer) are also removed; they will return with the SEP-2663 server-directed plugin.

NOT removed (wire surface, kept for 2025-11-25 interop): task Zod schemas + inferred types (Task, TaskStatus, TaskMetadata, RelatedTaskMetadata, CreateTaskResult, GetTask*, ListTasks*, CancelTask*, TaskStatusNotification*, TaskAugmentedRequestParams), task members of the request/result/notification unions, the tasks capability key, isTaskAugmentedRequestParams, RELATED_TASK_META_KEY. Inbound tasks/* requests → -32601.

13. Behavioral Changes

Client

Client.listPrompts(), listResources(), listResourceTemplates(), listTools() now return empty results when the server lacks the corresponding capability (instead of sending the request). Set enforceStrictCapabilities: true in ClientOptions to throw an error instead.

Server (Streamable HTTP transport)

No code changes required; these are wire-behavior notes:

  • Resumability behavior (SSE priming events, closeSSEStream / closeStandaloneSSEStream callbacks) is only enabled for protocol versions in the transport's supported-versions list that are >= 2025-11-25. Unknown future version strings in an initialize request body no longer enable it. Behavior for all currently supported protocol versions is unchanged.
  • Session-ID mismatch still responds 404 Not Found with JSON-RPC error code -32001 (Session not found), unchanged from v1. This -32001 usage is an SDK convention, not a spec-assigned code, and may be re-derived as 2026 protocol revision error handling is adopted — migrated client code should key off the HTTP 404 status, not the -32001 code.

14. Runtime-Specific JSON Schema Validators (Enhancement)

The SDK now auto-selects the appropriate JSON Schema validator based on runtime:

  • Node.js → AJV (no change from v1)
  • Cloudflare Workers (workerd) → @cfworker/json-schema (previously required manual config)

No action required for most users. Cloudflare Workers users can remove explicit jsonSchemaValidator configuration:

// v1 (Cloudflare Workers): Required explicit validator
new McpServer(
    { name: 'server', version: '1.0.0' },
    {
        jsonSchemaValidator: new CfWorkerJsonSchemaValidator()
    }
);

// v2 (Cloudflare Workers): Auto-selected, explicit config optional
new McpServer({ name: 'server', version: '1.0.0' }, {});

Validator behavior:

  • Do not add validator imports for normal migrations.
  • Do not install ajv, ajv-formats, or @cfworker/json-schema for the default path; client/server bundle the runtime-selected defaults and the root entry point does not pull either dep in.
  • To customize the built-in backend (e.g. register custom AJV formats, change @cfworker/json-schema draft), import the named class from the package subpath: @modelcontextprotocol/{client,server}/validators/ajv for AjvJsonSchemaValidator, @modelcontextprotocol/{client,server}/validators/cf-worker for CfWorkerJsonSchemaValidator. Importing from a subpath means the corresponding peer dep must be in your package.json.
  • To replace validation entirely, pass jsonSchemaValidator: myCustomValidator with your own implementation of the jsonSchemaValidator interface.

15. Migration Steps (apply in this order)

  1. Update package.json: npm uninstall @modelcontextprotocol/sdk, install the appropriate v2 packages
  2. Replace all imports from @modelcontextprotocol/sdk/... using the import mapping tables (sections 3-4), including StreamableHTTPServerTransportNodeStreamableHTTPServerTransport
  3. Replace removed type aliases (JSONRPCErrorJSONRPCErrorResponse, etc.) per section 5
  4. Replace .tool() / .prompt() / .resource() calls with registerTool / registerPrompt / registerResource per section 6
  5. Wrap all raw Zod shapes with z.object(): Change inputSchema: { name: z.string() }inputSchema: z.object({ name: z.string() }). Same for outputSchema in tools and argsSchema in prompts.
  6. Replace plain header objects with new Headers({...}) and bracket access (headers['x']) with .get() calls per section 7
  7. If using hostHeaderValidation from server, update import and signature per section 8
  8. If using server SSE transport, migrate to Streamable HTTP
  9. If using server auth from the SDK: RS helpers (requireBearerAuth, mcpAuthMetadataRouter, OAuthTokenVerifier) → @modelcontextprotocol/express; AS helpers → @modelcontextprotocol/server-legacy/auth (deprecated); migrate AS to external IdP/OAuth library
  10. If relying on listTools()/listPrompts()/etc. throwing on missing capabilities, set enforceStrictCapabilities: true
  11. Verify: build with tsc / run tests