Skip to content

Latest commit

 

History

History
435 lines (337 loc) · 24.9 KB

File metadata and controls

435 lines (337 loc) · 24.9 KB
name description
migrate-v1-to-v2
Migrate MCP TypeScript SDK code from v1 (@modelcontextprotocol/sdk) to v2 (@modelcontextprotocol/core, /client, /server). Use when a user asks to migrate, upgrade, or port their MCP TypeScript code from v1 to v2.

MCP TypeScript SDK: v1 → v2 Migration

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 need Install
Client only npm install @modelcontextprotocol/client
Server only npm install @modelcontextprotocol/server
Server + Node.js HTTP npm install @modelcontextprotocol/server @modelcontextprotocol/node
Server + Express npm install @modelcontextprotocol/server @modelcontextprotocol/express
Server + Hono npm 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 path v2 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
@modelcontextprotocol/sdk/client/websocket.js @modelcontextprotocol/client

Server imports

v1 import path v2 package
@modelcontextprotocol/sdk/server/mcp.js @modelcontextprotocol/server
@modelcontextprotocol/sdk/server/index.js @modelcontextprotocol/server
@modelcontextprotocol/sdk/server/stdio.js @modelcontextprotocol/server
@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.js REMOVED (migrate to Streamable HTTP)
@modelcontextprotocol/sdk/server/auth/* REMOVED (use external auth library)
@modelcontextprotocol/sdk/server/middleware.js @modelcontextprotocol/express (signature changed, see section 8)

Types / shared imports

v1 import path v2 package
@modelcontextprotocol/sdk/types.js @modelcontextprotocol/core
@modelcontextprotocol/sdk/shared/protocol.js @modelcontextprotocol/core
@modelcontextprotocol/sdk/shared/transport.js @modelcontextprotocol/core
@modelcontextprotocol/sdk/shared/stdio.js @modelcontextprotocol/core
@modelcontextprotocol/sdk/shared/uriTemplate.js @modelcontextprotocol/core
@modelcontextprotocol/sdk/shared/auth.js @modelcontextprotocol/core

Notes:

  • @modelcontextprotocol/client and @modelcontextprotocol/server both re-export everything from @modelcontextprotocol/core, so you can import types from whichever package you already depend on.
  • When multiple v1 imports map to the same v2 package, consolidate them into a single import statement.
  • If code imports from sdk/client/..., install @modelcontextprotocol/client. If from sdk/server/..., install @modelcontextprotocol/server. If from sdk/types.js or sdk/shared/... only, install @modelcontextprotocol/core.

4. Renamed Symbols

v1 symbol v2 symbol v2 package
StreamableHTTPServerTransport NodeStreamableHTTPServerTransport @modelcontextprotocol/node

5. Removed / Renamed Type Aliases and Symbols

v1 (removed) v2 (replacement)
JSONRPCError JSONRPCErrorResponse
JSONRPCErrorSchema JSONRPCErrorResponseSchema
isJSONRPCError isJSONRPCErrorResponse
isJSONRPCResponse isJSONRPCResultResponse
ResourceReference ResourceTemplateReference
ResourceReferenceSchema ResourceTemplateReferenceSchema
IsomorphicHeaders REMOVED (use Web Standard Headers)
AuthInfo (from server/auth/types.js) AuthInfo (now in @modelcontextprotocol/core)
McpError ProtocolError
ErrorCode ProtocolErrorCode
ErrorCode.RequestTimeout SdkErrorCode.RequestTimeout
ErrorCode.ConnectionClosed SdkErrorCode.ConnectionClosed
StreamableHTTPError REMOVED (use SdkError with SdkErrorCode.ClientHttp*)

All other symbols from @modelcontextprotocol/sdk/types.js retain their original names (e.g., CallToolResultSchema, ListToolsResultSchema, etc.).

Error class changes

Two 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
Error scenario v1 type v2 type
Request timeout McpError with ErrorCode.RequestTimeout SdkError with SdkErrorCode.RequestTimeout
Connection closed McpError with ErrorCode.ConnectionClosed SdkError with SdkErrorCode.ConnectionClosed
Capability not supported new Error(...) SdkError with SdkErrorCode.CapabilityNotSupported
Not connected new Error('Not connected') SdkError with SdkErrorCode.NotConnected
Invalid params (server response) McpError with ErrorCode.InvalidParams ProtocolError with ProtocolErrorCode.InvalidParams
HTTP transport error StreamableHTTPError SdkError with SdkErrorCode.ClientHttp*
Failed to open SSE stream StreamableHTTPError SdkError with SdkErrorCode.ClientHttpFailedToOpenStream
401 after auth flow StreamableHTTPError SdkError with SdkErrorCode.ClientHttpAuthentication
403 after upscoping StreamableHTTPError SdkError with SdkErrorCode.ClientHttpForbidden
Unexpected content type StreamableHTTPError SdkError with SdkErrorCode.ClientHttpUnexpectedContent
Session termination failed StreamableHTTPError SdkError with SdkErrorCode.ClientHttpFailedToTerminateSession

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.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/core';
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 { SdkError, SdkErrorCode } from '@modelcontextprotocol/core';
if (error instanceof SdkError && error.code === SdkErrorCode.ClientHttpFailedToOpenStream) {
    const status = (error.data as { status?: number })?.status;
}

OAuth error consolidation

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

v1 Class v2 Equivalent
InvalidRequestError OAuthError with OAuthErrorCode.InvalidRequest
InvalidClientError OAuthError with OAuthErrorCode.InvalidClient
InvalidGrantError OAuthError with OAuthErrorCode.InvalidGrant
UnauthorizedClientError OAuthError with OAuthErrorCode.UnauthorizedClient
UnsupportedGrantTypeError OAuthError with OAuthErrorCode.UnsupportedGrantType
InvalidScopeError OAuthError with OAuthErrorCode.InvalidScope
AccessDeniedError OAuthError with OAuthErrorCode.AccessDenied
ServerError OAuthError with OAuthErrorCode.ServerError
TemporarilyUnavailableError OAuthError with OAuthErrorCode.TemporarilyUnavailable
UnsupportedResponseTypeError OAuthError with OAuthErrorCode.UnsupportedResponseType
UnsupportedTokenTypeError OAuthError with OAuthErrorCode.UnsupportedTokenType
InvalidTokenError OAuthError with OAuthErrorCode.InvalidToken
MethodNotAllowedError OAuthError with OAuthErrorCode.MethodNotAllowed
TooManyRequestsError OAuthError with OAuthErrorCode.TooManyRequests
InvalidClientMetadataError OAuthError with OAuthErrorCode.InvalidClientMetadata
InsufficientScopeError OAuthError with OAuthErrorCode.InsufficientScope
InvalidTargetError OAuthError with OAuthErrorCode.InvalidTarget
CustomOAuthError new OAuthError(customCode, message)

Removed: OAUTH_ERRORS constant.

Update OAuth error handling:

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

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

Unchanged APIs (only import paths changed): Client constructor and methods, McpServer constructor, server.connect(), server.close(), all client transports (StreamableHTTPClientTransport, SSEClientTransport, StdioClientTransport), StdioServerTransport, all Zod schemas, all callback return types.

6. McpServer API Changes

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

IMPORTANT: v2 requires full Zod schemas — raw shapes like { name: z.string() } are no longer supported. You must wrap with z.object(). This 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 (Zod schema)
{ 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

7. Headers API

Transport constructors and RequestInfo.headers now use the Web Standard Headers object instead of plain objects.

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

// v2: Headers object, .get() access
headers: new Headers({ 'Authorization': 'Bearer token' })
ctx.http?.req?.headers.get('mcp-session-id')

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.

Server-side auth

All server OAuth exports removed: mcpAuthRouter, OAuthServerProvider, OAuthTokenVerifier, requireBearerAuth, authenticateClient, ProxyOAuthServerProvider, allowedMethods, and associated types. Use an external auth library (e.g., better-auth). 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) => { ... });

Schema to method string mapping:

v1 Schema v2 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.

v1 v2
RequestHandlerExtra ServerContext (server) / ClientContext (client) / BaseContext (base)
extra (param name) ctx
extra.signal ctx.mcpReq.signal
extra.requestId ctx.mcpReq.id
extra._meta ctx.mcpReq._meta
extra.sendRequest(...) ctx.mcpReq.send(...)
extra.sendNotification(...) ctx.mcpReq.notify(...)
extra.authInfo ctx.http?.authInfo
extra.sessionId ctx.sessionId
extra.requestInfo ctx.http?.req (only ServerContext)
extra.closeSSEStream ctx.http?.closeSSE (only ServerContext)
extra.closeStandaloneSSEStream ctx.http?.closeStandaloneSSE (only ServerContext)
extra.taskStore ctx.task?.store
extra.taskId ctx.task?.id
extra.taskRequestedTtl ctx.task?.requestedTtl

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

Method Description Replaces
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 client server.createMessage(...) from within handler

11. Client Behavioral Changes

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.

12. Runtime-Specific JSON Schema Validators (Enhancement)

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

  • Node.js → AjvJsonSchemaValidator (no change from v1)
  • Cloudflare Workers (workerd) → CfWorkerJsonSchemaValidator (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' }, {});

Access validators via _shims export: import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';

13. 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, migrate to an external auth library
  10. If relying on listTools()/listPrompts()/etc. throwing on missing capabilities, set enforceStrictCapabilities: true
  11. Verify: build with tsc / run tests