---
title: "NestJS MCP: Existing App Setup, Guards and Tests"
canonical: https://wavect.io/blog/nestjs-mcp-existing-app/
language: en
description: "Add NestJS MCP to an existing backend: check NestJS 12 compatibility, wire tools and guards, migrate mcp-nest, and reproduce our ten integration checks."
image: "https://wavect.io/img/blog/headers/header_nestjs-mcp-existing-app.png"
author: ["Kevin Riedl"]
published: "2026-11-04"
updated: "2026-11-04"
reviewed: "2026-11-04"
---

# NestJS MCP: add the official module to your existing backend

[**Back**](/blog/overview/)

[![Kevin Riedl](/img/team/kevin.webp)](/team/kevin-riedl/)

[Kevin Riedl](/team/kevin-riedl/) https://linkedin.com/in/wsdt

14 min read · 4 Nov 2026 Last reviewed November 4, 2026

[**Next**](/blog/enterprise-mcp-authorization-architecture/)

TL;DR

The official @nestjs/mcp module exposes provider methods as tools, resources and prompts inside your existing application. Version 0.0.2 declares NestJS 12 and MCP SDK 2.4 peers. Keep permissions in application services, adapt guards to the MCP context, and test the client contract separately from the HTTP route. Our downloadable synthetic invoice fixture passed eight in-memory and two real HTTP checks. Start with a useful read operation, then define authorization and replay behavior before enabling writes.

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](https://github.com/nestjs/mcp/blob/0.0.2/README.md) 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](https://docs.nestjs.com/observability/mcp-server).

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](https://github.com/nestjs/mcp/blob/0.0.2/package.json) gives the dependency ranges below. Our fixture verifies one pinned combination within them.

| Dependency or runtime | Declared requirement or documented schema support | Practical check |
| --- | --- | --- |
| `@nestjs/common` and `@nestjs/core` | `^12.0.0` | Keep the framework, HTTP adapter and testing packages aligned. |
| `@modelcontextprotocol/server` | `^2.4.0` | This is the v2 server package, not the older combined SDK import. |
| Node.js | `>=20.19.0` for this module | Check every dependency's engine requirement as well. |
| Zod | 4.2 or later | Review an existing Zod 3 dependency before changing its major version. |
| `@modelcontextprotocol/client` | Optional peer `^2.4.0` | Install it for the in-memory client tests. |
| Package format | ESM exports | Verify the build and runtime interop used by your application. |

```
npm ls @nestjs/core @nestjs/common @modelcontextprotocol/server zod

npm install --save-exact @nestjs/mcp@0.0.2 \
  @modelcontextprotocol/server@2.4.0 zod@4.6.7

npm install --save-dev --save-exact @modelcontextprotocol/client@2.4.0
```

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](https://github.com/nestjs/mcp/blob/0.0.2/lib/utils/schema.util.ts) 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](https://github.com/nestjs/mcp/blob/0.0.2/lib/services/mcp-registry.service.ts) 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](https://docs.nestjs.com/mcp/primitives) specifies the decorators and results. For an invoice-support application, we would divide the capabilities as follows.

| Client job | Primitive | Application design |
| --- | --- | --- |
| Ask for one permitted invoice's status | `@Tool()` with `inputSchema` | A narrow lookup through the existing access policy. |
| Read a published billing policy | `@Resource()` with `uri` | Versioned reference data with a clear audience. |
| Read an authorized invoice by URI | `@ResourceTemplate()` with `uriTemplate` | Apply the same record permission used by the lookup tool. |
| Prepare a support-reply template | `@Prompt()` with `argsSchema` | Return 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](https://github.com/nestjs/mcp/blob/0.0.2/lib/context/mcp-execution-context.ts) 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](https://github.com/nestjs/mcp/blob/0.0.2/lib/interfaces/mcp-context.interface.ts). 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](https://github.com/nestjs/mcp/blob/0.0.2/lib/auth/mcp-auth-gate.ts) 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](/blog/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](https://github.com/nestjs/mcp/blob/0.0.2/tests/authentication.e2e.spec.ts) 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](https://docs.nestjs.com/mcp/transports) is the reference for these choices:

| Connection | Useful capability | Boundary to verify |
| --- | --- | --- |
| HTTP, 2026-07-28 clients | Independent 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 clients | Ordinary calls through the same stateless endpoint. | Interactive elicitation requiring a server-to-client session is unavailable. |
| Stdio | A 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](/blog/mcp-stateless-server-migration-2026/) 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](https://docs.nestjs.com/mcp/migration).

| Existing integration | Official module change | Review consequence |
| --- | --- | --- |
| `@modelcontextprotocol/sdk` imports | Use the v2 `@modelcontextprotocol/server` API. | Check types and other consumers before removing the old dependency. |
| `parameters` | `inputSchema` for tools; `argsSchema` for prompts. | Verify advertised schemas and rejected arguments. |
| `mcpEndpoint` and module `guards` | `path` and endpoint `decorators`. | Distinguish route-level guards from provider-level authorization. |
| 2.x `@McpController()` and strategy bootstrap | Ordinary injectable providers and application bootstrap. | Move classes into `providers`; review any other microservices before removing their bootstrap. |
| RPC context extraction | `McpExecutionContext`. | Update shared guards, interceptors and filters. |
| Built-in authorization server | External identity provider and a verifier. | Token issuance and client sign-in need an explicit migration. |
| Request-scoped providers or runtime registration | Unsupported 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](https://github.com/nestjs/mcp/blob/0.0.2/lib/testing/index.ts) 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 surface | What to assert | What still needs another test |
| --- | --- | --- |
| Direct service or provider test | Domain decisions, returned fields, transaction behavior. | The protocol pipeline and HTTP route. |
| In-memory MCP client | Discovery, 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 client | Discovery 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](https://docs.nestjs.com/mcp/testing) 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](/downloads/nestjs-mcp-existing-app-fixture.zip).

| Check we ran | Observed result |
| --- | --- |
| Tool discovery | The intended tool and its input/output schemas were listed. |
| Authorized lookup | Structured content contained `inv_demo` and `open`. |
| Malformed input | A tool error; the invoice service call count stayed unchanged. |
| Another tenant with a valid token | The requested invoice was unavailable. |
| Missing scope | `InsufficientScopeError`; no service invocation. |
| Wrong department | The provider guard denied the call; no service invocation. |
| Missing token | Client connection was rejected. |
| Legacy client era | The authorized read returned successfully. |
| Unauthenticated real HTTP request | 401 with an OAuth resource-metadata challenge. |
| Unallowed browser origin over HTTP | 403. |

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](https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/). `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](/blog/mcp-security-boundary-data-level-access-control/) 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](/services/nestjs-development/) covers the underlying application changes. Our [MCP server development service](/services/mcp-server-development/) can help define the tool surface, connect authorization and establish acceptance checks. The [PromptID case study](/case-studies/promptid/) covers adjacent AI application work.

Use the [software QA checklist before launch](/software-development-guide/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.

Architecture and platforms

## Read next

Framework, platform and system-design choices that affect delivery over the long term.

[Start here**Smart City Architecture Best Practices: MQTT, LoRaWAN, Kubernetes and Terraform**](/blog/smart-city-architecture-best-practices-2026/)

- [Figma Weave and Figma Design: Choose the Right Connection](/blog/figma-weave-design-workflow/)
- [Craft Anything: How to Check a Website Rebuild](/blog/craft-anything-website-rebuild-verification/)
- [OpenMontage: Turn Your Coding Agent into a Video Workflow](/blog/openmontage-ai-video-production-review/)
- [Mireye Land Search API: MCP, Pricing and Parcel Screening](/blog/mireye-land-search-enrich-api-mcp/)
- [git-ai Review: Concurrent Agent Merges and Verification](/blog/git-ai-concurrent-agents-merge-verification/)

[Next article**Figma Weave and Figma Design: Choose the Right Connection**](/blog/figma-weave-design-workflow/)

[**Back**](/blog/overview/)

[![Kevin Riedl](/img/team/kevin.webp)](/team/kevin-riedl/)

[Kevin Riedl](/team/kevin-riedl/) https://linkedin.com/in/wsdt

14 min read · 4 Nov 2026 Last reviewed November 4, 2026

[**Next**](/blog/enterprise-mcp-authorization-architecture/)
