In this piece
NestJS MCP: add the official module to your existing backend
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 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/[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 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 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:
| 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 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 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 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 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 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 testNode 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.
