NestJS MCP: add the official module to your existing backend

Back
Kevin Riedl

14 min read · 4 Nov 2026
Last reviewed

Next
Made on your device, with no Instagram connection. We copy the post link for Instagram’s Link sticker.

Your NestJS backend already knows who can read an invoice and which operations can change it. An AI assistant needs access to that behavior without creating a second version of your application rules.

Add the official NestJS MCP module to an existing app by checking compatibility first, registering McpModule, and exposing selected provider methods. Keep object permissions and business operations in your application services. Validate the schemas, guards and results through an MCP client, then test the HTTP endpoint before connecting a remote assistant.

Checked and tested on 4 November 2026. We compiled an invoice-status example with @nestjs/mcp 0.0.2 and ran ten behavioral checks, including two over real HTTP. The downloadable fixture uses synthetic data and test tokens.

What does the official NestJS MCP module add?

The official NestJS MCP module exposes selected provider methods as tools, resources and prompts. It handles the protocol and connects those methods to Nest dependency injection and execution hooks. You decide what to expose; other service methods stay outside the MCP interface.

The release README documents Streamable HTTP on Express, Fastify and the Node adapter, plus stdio for local process connections. @nestjs/mcp is the official integration; @rekog/mcp-nest is a separate community package. NestJS Observe also has an MCP server for its observability data.

A support assistant can call the invoice service your dashboard already uses, keeping transaction rules and tenant checks in one place. We would start with one read operation. Review which records the caller can access, which fields leave the application, and what the assistant receives when the request fails.

Can your existing NestJS app install this release?

@nestjs/mcp 0.0.2 declares NestJS 12 and MCP SDK ^2.4.0 peer ranges. NestJS 10 and 11 fall outside that contract. Check your dependency tree first; a framework upgrade deserves a separate change and regression checks.

The 0.0.2 package manifest gives the dependency ranges below. Our fixture verifies one pinned combination within them.

Dependency or runtimeDeclared requirement or documented schema supportPractical check
@nestjs/common and @nestjs/core^12.0.0Keep the framework, HTTP adapter and testing packages aligned.
@modelcontextprotocol/server^2.4.0This is the v2 server package, not the older combined SDK import.
Node.js>=20.19.0 for this moduleCheck every dependency’s engine requirement as well.
Zod4.2 or laterReview an existing Zod 3 dependency before changing its major version.
@modelcontextprotocol/clientOptional peer ^2.4.0Install it for the in-memory client tests.
Package formatESM exportsVerify the build and runtime interop used by your application.
npm ls @nestjs/core @nestjs/common @modelcontextprotocol/server zod

npm install --save-exact @nestjs/[email protected] \
  @modelcontextprotocol/[email protected] [email protected]

npm install --save-dev --save-exact @modelcontextprotocol/[email protected]

Our fixture pairs these versions with NestJS 12.3.3 and Node.js 24.22.1. An ESM dependency need not force a rewrite of your CommonJS app; verify the import and build path. A peer-dependency override does not establish compatibility.

The schema preparation code also matters during an upgrade. Zod 3 is rejected. ArkType is supported, Valibot needs toStandardJsonSchema() from @valibot/to-json-schema, and existing JSON Schema can use fromJsonSchema() from the MCP server package. Tool arguments need a convertible object schema.

How do you add NestJS MCP to an existing app?

Add NestJS MCP to your existing app by registering McpModule and a tool provider that injects the application service it needs. Restrict discovery to that feature module. For a protected HTTP endpoint, configure a real token verifier before exposing sensitive tools.

This configuration is an integration sketch: supply your own InvoicesModule, IdentityModule and IdentityTokenVerifier.

McpModule.forRoot({
  name: 'billing-assistant',
  version: '1.0.0',
  include: [InvoicesModule],
  imports: [IdentityModule],
  auth: {
    verifier: IdentityTokenVerifier,
    resource: 'https://api.example.com/mcp',
    authorizationServers: ['https://login.example.com'],
  },
})

Also import InvoicesModule into the application and list InvoiceTools in its providers. The module’s imports option makes exported dependencies available to the verifier. The provider registry discovers decorated methods at startup; include controls which modules it scans.

This provider excerpt comes from our executed fixture. The download includes its service, access helper, guard and test module.

export const InvoiceInput = z.object({
  invoiceId: z.string().regex(/^inv_[a-z0-9]+$/).max(64),
}).strict();

export const InvoiceStatus = z.object({
  invoiceId: z.string(),
  status: z.enum(['open', 'paid']),
});

@Injectable()
export class InvoiceTools {
  constructor(
    private readonly invoices: InvoicesService,
    private readonly access: InvoiceAccess,
  ) {}

  @Tool({
    name: 'get-invoice-status',
    description: 'Read the status of one invoice available to the signed-in user. Does not modify an invoice.',
    inputSchema: InvoiceInput,
    outputSchema: InvoiceStatus,
    scopes: ['invoices:read'],
    annotations: { readOnlyHint: true },
  })
  @UseGuards(SupportTeamGuard)
  async status(input: z.infer<typeof InvoiceInput>, ctx: McpContext) {
    const actor = this.access.requirePrincipal(ctx);
    return this.invoices.statusForActor(actor, input.invoiceId, ctx.signal);
  }
}

InvoiceAccess reads the subject and tenant from verified context; InvoicesService checks access to the invoice. The model supplies an invoice identifier. Adding a tenant field cannot grant authority, and the strict schema rejects undeclared fields.

The fixture returns only an identifier and status. An ORM invoice entity might also expose addresses, tax information or internal notes, so define the assistant’s output contract explicitly.

If the endpoint is missing, check the global prefix and versioning. If a tool is absent from discovery, check provider registration, include and startup errors. A decorator cannot make an unregistered class discoverable.

Which methods should become tools, resources or prompts?

Use NestJS MCP tools for parameterized operations, resources for URI-addressed data, and prompts for reusable message templates. Give each primitive one clear purpose so a reviewer can assess its permissions and failure behavior together.

The official primitives guide specifies the decorators and results. For an invoice-support application, we would divide the capabilities as follows.

Client jobPrimitiveApplication design
Ask for one permitted invoice’s status@Tool() with inputSchemaA narrow lookup through the existing access policy.
Read a published billing policy@Resource() with uriVersioned reference data with a clear audience.
Read an authorized invoice by URI@ResourceTemplate() with uriTemplateApply the same record permission used by the lookup tool.
Prepare a support-reply template@Prompt() with argsSchemaReturn messages for the client to use; any sending operation needs its own authorization.

A generic run-sql or call-any-service tool is harder to review. We prefer a named business capability with bounded inputs: its permitted records and effects are easier to identify.

Will your existing guards work on MCP calls?

Your existing guards can run on NestJS MCP calls if they handle the mcp execution context. Review interceptors, pipes and exception filters for the same assumption. An HTTP-only context accessor can read the wrong arguments even when the guard is registered correctly.

The MCP execution-context helper exposes the input, call context and auth information. Our tool-specific guard reads a department claim that the fixture’s verifier supplies:

@Injectable()
export class SupportTeamGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const ctx = McpExecutionContext.create(context).getContext();
    return ctx.authInfo?.extra?.department === 'support';
  }
}

Apply this guard to the MCP provider or method. A global guard shared with HTTP routes must branch on context type and enforce the appropriate policy in each branch. Returning true for an unfamiliar context would bypass that policy.

Keep user and tenant state in arguments or call context. Another request can overwrite this.currentTenant on a shared provider. The registry rejects request-scoped dependency trees, so a provider that injects a request-scoped tenant service needs redesign.

In 0.0.2, McpContext is a union of client and internal-agent contexts. Check ctx.transport before using client-only fields such as server or era. The internal agent’s optional idempotencyKey does not deduplicate remote MCP writes automatically.

Interceptors should record the operation, outcome, correlation identifier and elapsed time without logging bearer tokens or complete invoice payloads. Review exception messages too: a deliberate HttpException can expose its message to the caller. Use messages that help the client recover without revealing internal records or credentials.

How do OAuth scopes and user policies fit together?

For NestJS MCP, an OAuth scope permits a capability while application policy decides which objects the caller may access. The HTTP module verifies tokens from your identity provider. A valid token with an invoice-read scope still needs permission to read the particular invoice.

The authorization gate connects your verifier to OAuth challenges. Validate the issuer, signature or introspection result, audience and expiry. Merely decoding a JWT and returning the configured resource URL does not prove that token was issued for it.

Our service checks the actor’s invoice access after the invoices:read scope permits the capability. authInfo.clientId identifies the OAuth client; obtain the user’s identity and tenant separately. Our enterprise MCP authorization architecture covers that broader design.

The @CurrentUser() and @Can() integration requires configured @nestjs/authentication 0.1.3+ and @nestjs/authorization 0.1.1+. The upstream integration tests show both modules handling user injection and policies on tool calls.

The documented decorators: [Public()] setting exempts the endpoint route from the authentication guard so MCP can return its own OAuth discovery challenge. It must be paired with MCP auth and the intended provider authorization. Applying @Public() broadly to tool providers is not an equivalent configuration.

For an HTTP endpoint, declaring tool scopes without an enforcing auth configuration is rejected at startup. Stdio has a separate security model; an OAuth scope on a decorator does not protect a local stdio invocation by itself.

What changes between HTTP, stdio and older clients?

NestJS MCP serves remote clients over Streamable HTTP and local processes over stdio. HTTP requests are independent; stdio keeps a process connection. Check your client’s interaction requirements against the transport and protocol era, especially when a tool asks the user for input.

The transport documentation is the reference for these choices:

ConnectionUseful capabilityBoundary to verify
HTTP, 2026-07-28 clientsIndependent requests, JSON or SSE responses, supported input-required flows.OAuth, proxy routing, allowed origins, body limits and the client’s actual feature support.
HTTP, legacy 2025 clientsOrdinary calls through the same stateless endpoint.Interactive elicitation requiring a server-to-client session is unavailable.
StdioA client launches the server process; both protocol eras are supported.No HTTP request or HTTP OAuth scope enforcement; establish local identity and permissions explicitly.

Stateless HTTP removes the module’s need for sticky transport sessions. Your database, job queue, rate limits and business idempotency still need the consistency their operations require. Our MCP stateless server migration guide covers the protocol-wide transition.

For browser clients, distinguish the module’s hostname-based allowedOrigins setting from Nest CORS origins. Requests without an Origin header can pass that origin check, so it cannot replace authentication. An upstream HTTP body parser may also reject a request before the module’s body-size limit runs.

For stdio, send application logs through McpStdioLogger; stdout carries protocol messages. A successful local connection does not verify the OAuth discovery, TLS or proxy behavior of your remote deployment.

What changes when migrating from mcp-nest?

Migrating from @rekog/mcp-nest requires mapping your community package version to the official module’s provider, schema and context APIs. Inventory the features you use before replacing dependencies. Similar decorator names can conceal different authorization or transport behavior.

Review these changes against the official migration guide for the community package’s 1.x and 2.x APIs.

Existing integrationOfficial module changeReview consequence
@modelcontextprotocol/sdk importsUse the v2 @modelcontextprotocol/server API.Check types and other consumers before removing the old dependency.
parametersinputSchema for tools; argsSchema for prompts.Verify advertised schemas and rejected arguments.
mcpEndpoint and module guardspath and endpoint decorators.Distinguish route-level guards from provider-level authorization.
2.x @McpController() and strategy bootstrapOrdinary injectable providers and application bootstrap.Move classes into providers; review any other microservices before removing their bootstrap.
RPC context extractionMcpExecutionContext.Update shared guards, interceptors and filters.
Built-in authorization serverExternal identity provider and a verifier.Token issuance and client sign-in need an explicit migration.
Request-scoped providers or runtime registrationUnsupported in this release.Change the design or retain the current integration until it fits.

The old 2024 HTTP+SSE transport and several MCP servers within one application are also outside this version’s supported design. With HTTP auth enabled, per-tool anonymous exceptions from the community module do not carry over.

Migrate a read tool first, preserving its name and contract where possible. Compare discovery, results and denials with the previous integration before tackling writes and their replay behavior.

How do you test the MCP behavior your assistant sees?

Use connectMcpClient() to test NestJS MCP behavior, then exercise the HTTP route for middleware and deployment checks. Direct provider tests alone skip the protocol pipeline: argument validation, scope checks, guards and result serialization.

The in-memory helper calls the HTTP service without a socket, including its Host and Origin checks. It bypasses endpoint middleware and route guards supplied through decorators, and uses a simplified request object at http://localhost/mcp.

Test surfaceWhat to assertWhat still needs another test
Direct service or provider testDomain decisions, returned fields, transaction behavior.The protocol pipeline and HTTP route.
In-memory MCP clientDiscovery, input/output contracts, token checks, scopes, provider guards and result shape.HTTP middleware, endpoint guards, real CORS preflight, proxies and body parsers.
Real HTTP and your intended clientDiscovery challenge, routing, browser behavior, connection handling and client compatibility.Every business side effect still needs an explicit state assertion.

This excerpt verifies that malformed input never reaches the invoice service. The fixture supplies the testing module and authenticated client, then closes both afterwards.

const service = moduleRef.get(InvoicesService);
const before = service.calls;

const result = await client.callTool({
  name: 'get-invoice-status',
  arguments: { invoiceId: 123 },
});

assert.equal(result.isError, true);
assert.equal(service.calls, before);

The testing guide distinguishes tool errors from authentication failures: malformed arguments can return isError: true, missing scopes raise InsufficientScopeError, and missing required tokens prevent connection. Expecting every failure to reject a promise will miss errors returned as tool results.

If production sets allowedHosts, accommodate the helper’s localhost URL through a test-only configuration. Keep the production host policy intact.

What did our small integration test verify?

Our NestJS MCP fixture passed eight in-memory checks and two real Express HTTP checks. It uses one synthetic invoice, a tenant-aware service double and fixed test tokens. The results establish how this example handles successful and denied calls under those conditions.

The tested versions are @nestjs/mcp 0.0.2, NestJS 12.3.3, MCP client/server 2.4.0, Zod 4.6.7, TypeScript 7.0.2 and Node.js 24.22.1. The source, dependency lockfile and recorded output are included in the downloadable NestJS MCP integration fixture.

Check we ranObserved result
Tool discoveryThe intended tool and its input/output schemas were listed.
Authorized lookupStructured content contained inv_demo and open.
Malformed inputA tool error; the invoice service call count stayed unchanged.
Another tenant with a valid tokenThe requested invoice was unavailable.
Missing scopeInsufficientScopeError; no service invocation.
Wrong departmentThe provider guard denied the call; no service invocation.
Missing tokenClient connection was rejected.
Legacy client eraThe authorized read returned successfully.
Unauthenticated real HTTP request401 with an OAuth resource-metadata challenge.
Unallowed browser origin over HTTP403.

To reproduce the fixture, extract it and run these commands from its directory:

npm ci
npm test

Node reports 12 passed tests because it counts two parent tests alongside the ten checks. The fake verifier belongs only in test code. This fixture did not exercise Fastify, the Node adapter, production identity providers, concurrent writes or load-balancer behavior.

What should change before exposing write tools?

Before exposing a NestJS MCP write tool, define its authorization, durable operation identifier, concurrency rule and recovery behavior. The application service must decide what happens when a call repeats after a timeout. A decorator cannot decide whether to repeat a payment.

For an invoice correction, record the approved action and payload, recheck access at execution, and preserve the operation identifier across retries. Reject changed arguments under the same identifier. If the first attempt committed but its response was lost, return or reconcile the recorded outcome.

That is a proposed design, separate from our executed read-only fixture. It needs transaction and concurrency tests against your actual data store. A model that invents a new idempotency key for every attempt will defeat a key-based duplicate check.

The MCP maintainers describe tool annotations as hints. readOnlyHint and idempotentHint tell clients about intended behavior; they do not enforce it. Likewise, wording a prompt to request confirmation does not establish permission to execute.

For the first write rollout, we would require these observable outcomes:

  • A disallowed actor or object is rejected before any persistent change.
  • Two concurrent attempts at the same approved action produce the intended single business effect.
  • A changed payload under the same operation identifier is rejected.
  • A timeout after commit can be reconciled without blindly repeating the effect.
  • An audit record identifies the actor, operation, authorization outcome and resulting state without retaining tokens.

Apply the same data authorization when the operation is reached through REST, a queue job or MCP. Our guide to enforcing agent data access explains that boundary in more detail.

When would we use the official NestJS MCP module?

We would use the official NestJS MCP module when the app satisfies its dependency contract and already enforces business permissions in services. Start with a provider adapter whose permissions, output and failure paths you can inspect, and verify them through the client’s protocol.

For a NestJS 10 or 11 product, decide the framework upgrade before promising an MCP installation. If the system relies on request-scoped providers, runtime tool registration or several isolated MCP servers in one process, resolve that mismatch before replacing a working integration.

Wavect’s NestJS development work covers the underlying application changes. Our MCP server development service can help define the tool surface, connect authorization and establish acceptance checks. The PromptID case study covers adjacent AI application work.

Use the software QA checklist before launch to place the tool tests within the wider release decision. Start with a read capability whose permissions and output you can explain, prove its denial paths, then expand the surface one business operation at a time.

NestJS MCP implementation FAQ

Does @nestjs/mcp need an LLM API key?
The MCP module serves application capabilities to a client; it does not require an LLM API key merely to expose tools, resources or prompts. The assistant or agent using those capabilities may have its own model credentials and costs. A tool that calls a model introduces that separate dependency through your application code.
Can the MCP endpoint run beside my REST API?
Yes. The official module adds an HTTP endpoint to the existing Nest application, and the default path is /mcp. Your global prefix and endpoint versioning can change the final route. Keep REST controllers and MCP providers thin enough to call the same application services, and test both entry points when their shared behavior changes.
Are tools hidden from callers who lack their scopes?
No. A scope restricts execution, not discovery of the tool definition. Do not put secrets, customer names or sensitive operational details in tool names and descriptions on the assumption that an insufficiently scoped caller cannot list them. Record-level data access still requires authorization when the operation runs.
Can an MCP provider inject a request-scoped tenant service?
The inspected release rejects request-scoped dependency trees during discovery. Passing the verified tenant and actor through call arguments is a suitable alternative when your application services support it. Review the whole dependency chain: a provider that looks singleton-scoped can still depend on request-scoped state further down.
Does cancelling a call roll back a completed write?
Cancellation cannot by itself undo an effect your application has already committed. Pass the call’s AbortSignal to operations that support cancellation, then distinguish cancellation before commit from a lost response after commit. A durable operation record lets the caller recover the outcome. Rollback or compensation requires its own application design.
Is this the NestJS Observe MCP server?
No. This package lets you build an MCP interface for your own application. NestJS Observe’s MCP server exposes capabilities of the observability product. A search result about connecting an assistant to Observe does not establish how to register your own provider, secure an invoice operation or migrate a community NestJS MCP integration.
Can a prompt grant permission to call a write tool?
A prompt supplies instructions or messages to the client. Permission still comes from authenticated identity, configured scopes and application authorization at execution time. Even when a client asks a human for confirmation, bind that approval to the intended operation and inputs, then recheck access before the service changes persistent state.

Connect your application to an assistant

Define the first useful tool, connect its permissions and test what the client receives. Wavect helps you implement and verify that MCP interface in your existing backend.

Implementation help:

Inbox, without the noise

Follow the work that matters to you

Get a short email when we publish something new. Follow the whole blog or only the problems you care about.

What would you like to receive?
Choose your topics

Free, double opt-in, no tracking pixels.

Back
Kevin Riedl

14 min read · 4 Nov 2026
Last reviewed

Next

Get the next Delivery and QA field note

One concise email when we publish. No tracking pixels, and no inbox filler.

Free, double opt-in, no tracking pixels.