MCP protocol revision 2026-07-28 shipped on 2026-07-28, and the TypeScript SDK moved from the monolithic @modelcontextprotocol/sdk (still 1.30.0 on npm) to split v2 packages: @modelcontextprotocol/server, client, core, node, hono, fastify, server-legacy, all at 2.0.0. fastmcp currently pins @modelcontextprotocol/sdk ^1.24.3.
I ran the official codemod against this repo to measure the real cost rather than guess, and I would be glad to do the work. But two of the remaining items are decisions about fastmcp's public API that are yours to make, so I am opening this instead of a PR. Related: #99, #212.
Nothing here is urgent in the sense of a deadline. The SDK's own guide states that v2 speaks the 2025-era protocol unless you explicitly opt in, and the spec now carries a minimum twelve-month deprecation window, so there is room to decide deliberately.
What the codemod does on its own
npx @modelcontextprotocol/codemod@latest v1-to-v2 .
Changes: 86 across 16 file(s)
package.json updated: Removed @modelcontextprotocol/sdk; Added @modelcontextprotocol/client, @modelcontextprotocol/core, @modelcontextprotocol/server
3 location(s) marked with @mcp-codemod-error comments
pnpm install is clean afterwards. vitest run gives 442 passed / 6 failed of 448. src/auth/ is untouched entirely, so the OAuth subsystem is decoupled from the SDK's protocol types. Almost all of it is import consolidation plus setRequestHandler(XSchema, ...) becoming setRequestHandler('x/method', ...).
Also worth knowing: mcp-proxy is not a blocker. Its startHTTPServer takes type ServerLike = { close, connect }, structural typing with no instanceof check, and fastmcp already passes its own FastMCPSession rather than a raw SDK Server. Every mcp-proxy-exercising test passes post-codemod.
The two decisions I cannot make for you
1. tools/call handler signature (src/FastMCP.ts:2176). The one production type error. v2 types this handler as (request, ctx: ServerContext) => InputRequiredResult | ..., so the current plain ContentResult return no longer typechecks. This fails pnpm lint (tsc --noEmit) and the DTS step of pnpm build, so CI is red until it is resolved. Notably this is protocol-revision shape reaching into what is otherwise a mechanical SDK-surface upgrade.
2. Context shape (src/FastMCP.ts:2278). The codemod flagged fastmcp's own Context object literal, because v2 handlers read ctx.mcpReq.* whereas fastmcp's public contract keeps requestId/sessionId at the top level. This is not a mock, it is the public API at src/FastMCP.ts:217-266, so reshaping it is a user-visible change. context.sessionId is documented as coming from the Mcp-Session-Id header, which the new revision removes; FastMCPSession.sessionId/.clientCapabilities/.roots and the connect/disconnect events are in the same position.
The remaining 6 test failures are smaller: two whitebox tests pull handlers out of server._requestHandlers and invoke them with one argument (v2 always passes a second ctx), three assert a doubled "MCP error -32602: MCP error -32602:" prefix that v2 emits once, and one is the custom non-spec notifications/tool/streamContent needing v2's 3-argument setNotificationHandler.
A path that avoids breaking users
Reading the SDK's docs/migration/support-2026-07-28.md, the sanctioned pattern for a project in exactly fastmcp's position (an existing sessionful Streamable HTTP setup) is additive dual-stack rather than an in-place rewrite: leave the current sessionful path untouched, add a separate createMcpHandler entry, and route with isLegacyRequest(request). One caveat I verified in the installed @modelcontextprotocol/server types: createMcpHandler's default legacy: 'stateless' leg builds a fresh instance per request and answers GET/DELETE with 405, so it does not preserve today's session semantics and the routing has to point legacy traffic at a hand-wired handler.
Two things that make this cheaper than it looks: MRTR handlers written once in inputRequired(...) style serve both eras through the SDK's legacy shim, so requestSampling/requestElicitation/roots get rewritten once rather than twice; and ttlMs/cacheScope are emitted automatically by the SDK rather than hand-added per handler.
If you tell me how you want (1) and (2) resolved, I will open the mechanical piece as its own PR with CI green, and keep the architectural piece separate.
MCP protocol revision
2026-07-28shipped on 2026-07-28, and the TypeScript SDK moved from the monolithic@modelcontextprotocol/sdk(still 1.30.0 on npm) to split v2 packages:@modelcontextprotocol/server,client,core,node,hono,fastify,server-legacy, all at 2.0.0. fastmcp currently pins@modelcontextprotocol/sdk ^1.24.3.I ran the official codemod against this repo to measure the real cost rather than guess, and I would be glad to do the work. But two of the remaining items are decisions about fastmcp's public API that are yours to make, so I am opening this instead of a PR. Related: #99, #212.
Nothing here is urgent in the sense of a deadline. The SDK's own guide states that v2 speaks the 2025-era protocol unless you explicitly opt in, and the spec now carries a minimum twelve-month deprecation window, so there is room to decide deliberately.
What the codemod does on its own
npx @modelcontextprotocol/codemod@latest v1-to-v2 .pnpm installis clean afterwards.vitest rungives 442 passed / 6 failed of 448.src/auth/is untouched entirely, so the OAuth subsystem is decoupled from the SDK's protocol types. Almost all of it is import consolidation plussetRequestHandler(XSchema, ...)becomingsetRequestHandler('x/method', ...).Also worth knowing:
mcp-proxyis not a blocker. ItsstartHTTPServertakestype ServerLike = { close, connect }, structural typing with noinstanceofcheck, and fastmcp already passes its ownFastMCPSessionrather than a raw SDKServer. Every mcp-proxy-exercising test passes post-codemod.The two decisions I cannot make for you
1.
tools/callhandler signature (src/FastMCP.ts:2176). The one production type error. v2 types this handler as(request, ctx: ServerContext) => InputRequiredResult | ..., so the current plainContentResultreturn no longer typechecks. This failspnpm lint(tsc --noEmit) and the DTS step ofpnpm build, so CI is red until it is resolved. Notably this is protocol-revision shape reaching into what is otherwise a mechanical SDK-surface upgrade.2.
Contextshape (src/FastMCP.ts:2278). The codemod flagged fastmcp's ownContextobject literal, because v2 handlers readctx.mcpReq.*whereas fastmcp's public contract keepsrequestId/sessionIdat the top level. This is not a mock, it is the public API atsrc/FastMCP.ts:217-266, so reshaping it is a user-visible change.context.sessionIdis documented as coming from theMcp-Session-Idheader, which the new revision removes;FastMCPSession.sessionId/.clientCapabilities/.rootsand theconnect/disconnectevents are in the same position.The remaining 6 test failures are smaller: two whitebox tests pull handlers out of
server._requestHandlersand invoke them with one argument (v2 always passes a secondctx), three assert a doubled"MCP error -32602: MCP error -32602:"prefix that v2 emits once, and one is the custom non-specnotifications/tool/streamContentneeding v2's 3-argumentsetNotificationHandler.A path that avoids breaking users
Reading the SDK's
docs/migration/support-2026-07-28.md, the sanctioned pattern for a project in exactly fastmcp's position (an existing sessionful Streamable HTTP setup) is additive dual-stack rather than an in-place rewrite: leave the current sessionful path untouched, add a separatecreateMcpHandlerentry, and route withisLegacyRequest(request). One caveat I verified in the installed@modelcontextprotocol/servertypes:createMcpHandler's defaultlegacy: 'stateless'leg builds a fresh instance per request and answers GET/DELETE with 405, so it does not preserve today's session semantics and the routing has to point legacy traffic at a hand-wired handler.Two things that make this cheaper than it looks: MRTR handlers written once in
inputRequired(...)style serve both eras through the SDK's legacy shim, sorequestSampling/requestElicitation/roots get rewritten once rather than twice; andttlMs/cacheScopeare emitted automatically by the SDK rather than hand-added per handler.If you tell me how you want (1) and (2) resolved, I will open the mechanical piece as its own PR with CI green, and keep the architectural piece separate.