NestJS Logo

Migration

An MCP server built with the community package @rekog/mcp-nest moves to @nestjs/mcp with mostly mechanical changes: the decorators keep their names, and tools, resources and prompts stay methods of your classes. This page maps both of its APIs, the McpModule.forRoot() one of its 1.x releases and the McpStrategy one of 2.x, to @nestjs/mcp.

Packages#

@nestjs/mcp is built on version 2 of the MCP TypeScript SDK, @modelcontextprotocol/server. Replace the packages:


$ npm uninstall @rekog/mcp-nest @modelcontextprotocol/sdk
$ npm i --save @nestjs/mcp @modelcontextprotocol/server zod

From 2.x, also uninstall @modelcontextprotocol/core and @modelcontextprotocol/node, @rekog/mcp-nest-auth if you used its authorization server, and @nestjs/microservices if nothing else in the application uses it. Types you imported from @modelcontextprotocol/sdk, such as CallToolResult, come from @modelcontextprotocol/server.

The module#

From 1.x, McpModule.forRoot() stays, with fewer options:

app.module.ts
JS TS

// Before, with @rekog/mcp-nest 1.x
McpModule.forRoot({
  name: 'cat-store',
  version: '1.0.0',
  transport: [McpTransportType.STREAMABLE_HTTP],
  mcpEndpoint: 'mcp-operations',
  guards: [ApiKeyGuard],
}),

// After
McpModule.forRoot({
  name: 'cat-store',
  version: '1.0.0',
  path: 'mcp-operations',
  decorators: [UseGuards(ApiKeyGuard)],
}),
@rekog/mcp-nest 1.x@nestjs/mcp
name, versionThe same
transportStreamable HTTP is served unless path: false; stdio is served by McpStdioServer (see Transports)
mcpEndpoint, apiPrefixpath, under the application's global prefix
guardsdecorators: [UseGuards(...)]
streamableHttpNothing to set: every request is served on its own, and http.responseMode shapes the answers to 2026-07-28 requests
forRootAsync()forRootAsync(), with path, decorators and imports at its top level
forFeature()One MCP server per application

From 2.x, the strategy and its bootstrap code go away. McpModule.forRoot() takes the strategy's name and version, and the endpoint is a route of the application, served by app.listen():

main.ts
JS TS

// Before, with @rekog/mcp-nest 2.x
const app = await NestFactory.create(AppModule);
mcp.setHttpAdapter(app.getHttpAdapter());
app.connectMicroservice({ strategy: mcp });
await app.startAllMicroservices();
await app.listen(3000);

// After
const app = await NestFactory.create(AppModule);
await app.listen(3000);

@McpController() classes become ordinary @Injectable() providers, listed in a module's providers instead of its controllers, and the MCP_STRATEGY provider goes. A stdio server that NestFactory.createMicroservice() started is served from an application context with McpStdioServer instead, as Transports shows.

Tools, resources and prompts#

The decorators keep their names and most of their options. The method's arguments change:

greeting.tool.ts
JS TS

// Before, with @rekog/mcp-nest 1.x
@Tool({
  name: 'greeting-tool',
  description: 'Returns a greeting with progress updates',
  parameters: z.object({ name: z.string().default('World') }),
})
async sayHello({ name }, context: Context, request: Request) {
  await context.reportProgress({ progress: 50, total: 100 });
  return `Hello, ${name}, from ${request.headers['user-agent']}!`;
}

// After
@Tool({
  name: 'greeting-tool',
  description: 'Returns a greeting with progress updates',
  inputSchema: z.object({ name: z.string().default('World') }),
})
async sayHello({ name }: { name: string }, ctx: McpContext<Request>) {
  await ctx.reportProgress(50, 100);
  return `Hello, ${name}, from ${ctx.request?.headers['user-agent']}!`;
}
@rekog/mcp-nest@nestjs/mcp
parametersinputSchema on a tool, argsSchema on a prompt
outputSchema, annotationsThe same
(args, context, request) in 1.x, @Payload() and @Ctx() in 2.x(input, ctx): the request is ctx.request, the verified token ctx.authInfo
context.reportProgress({ progress, total })ctx.reportProgress(progress, total)
context.mcpServer.server.elicitInput()Return inputRequired(): see asking the user
A resource's ({ uri })ctx.uri. A resource template's method receives its variables as the input
{ content }, { contents } and { messages } resultsReturned as they are, or return the value itself and let the module build the result
@Injectable({ scope: Scope.REQUEST })A singleton provider: read what belongs to the call from ctx
McpRegistryService.registerTool(), strategy.registerTool()Not supported: declare tools with decorators

A resource template, for example:

greeting.resource.ts
JS TS

// Before, with @rekog/mcp-nest 1.x
@ResourceTemplate({
  name: 'user-language',
  description: "Get a specific user's preferred language",
  mimeType: 'application/json',
  uriTemplate: 'mcp://users/{name}',
})
getUserLanguage({ uri, name }) {
  return {
    contents: [
      {
        uri,
        mimeType: 'application/json',
        text: JSON.stringify({ name, language: this.languages.of(name) }),
      },
    ],
  };
}

// After
@ResourceTemplate({
  name: 'user-language',
  description: "Get a specific user's preferred language",
  mimeType: 'application/json',
  uriTemplate: 'mcp://users/{name}',
})
getUserLanguage({ name }: { name: string }) {
  return { name, language: this.languages.of(name) };
}

A request-scoped provider fails at startup with @nestjs/mcp: every request is served by the same singleton instances, and what differs between calls, the request, the token and the client, is in ctx.

Guards and errors#

Guards, interceptors, pipes and filters keep working, with McpExecutionContext.create(context) in place of context.switchToRpc().getContext<McpContext>(). The context's type is 'mcp', not 'rpc'.

A tool's errors still reach the model as tool results. An HttpException carries its message to the client, where 2.x needed an RpcException; any other error is answered with Internal server error.


// Before, with @rekog/mcp-nest 2.x
throw new RpcException('The address has no postcode');

// After
throw new BadRequestException('The address has no postcode');

Authorization#

@nestjs/mcp is a resource server, as the MCP spec asks of a server: the tokens come from an identity provider, and the module verifies them (see Authorization).

@rekog/mcp-nest@nestjs/mcp
McpAuthModule, the built-in authorization serverAn identity provider (Auth0, Keycloak, Entra ID, WorkOS, your own) that issues the tokens, and auth pointing at it
McpAuthJwtGuard in guardsauth.verifier
@ToolScopes(['orders:write'])scopes: ['orders:write'] on the decorator. Scoped tools stay listed to every client
@ToolRoles(['admin'])A guard, or a @Can() policy with @nestjs/authorization
@PublicTool(), allowUnauthenticatedAccessNot supported: with auth, every request needs a token
ctx.getUser() in 2.xctx.authInfo, or @CurrentUser() with @nestjs/authentication

What's not there#

  • The HTTP+SSE transport of the 2024-11-05 protocol revision isn't served. Clients connect over Streamable HTTP, the transport current clients use.
  • Sessions aren't kept: every HTTP request is served on its own, which is what lets the server run on many instances. A 2025 client can't be asked for input mid-call over HTTP; the call ends with a tool error.
  • Registering tools at runtime, several MCP servers in one application and request-scoped providers aren't supported in this version.
Edit on GitHub

Support us

Nest is an MIT-licensed open source project. It can grow thanks to the support of these awesome people. If you'd like to join them, please read more here.

Principal Sponsors

SerpApi LogoTrilon LogoMojam Logo

Sponsors / Partners

Become a sponsor