# Integrating NestJS with xmcp (/blog/nestjs-integration)

Published: 2026-05-22

Add an MCP server to your NestJS application with tool discovery and a customizable module.

Learn how to turn your existing [NestJS](https://nestjs.com/) app into an MCP server with xmcp. Instead of standing up a separate service, xmcp drops into your project as a regular Nest module: your tools live in `src/tools/`, they're discovered automatically, and they're served through Nest's own controllers, dependency injection, and lifecycle hooks.

## What You'll Build

By the end of this guide, you'll have:

* An `/mcp` endpoint served by a NestJS module
* Tools auto-discovered from your `src/tools/` directory
* Tools that read from your existing Nest providers and services
* Optional JWT authentication using a reusable guard

***

## Install xmcp

xmcp works on top of your existing NestJS project. From your project directory, run:

```bash
npx init-xmcp@latest
```

The framework (`@nestjs/core`) and package manager are detected automatically. The only thing you're prompted for is which of tools, prompts, and resources to scaffold.

```
? Which components do you want to initialize? (tools, prompts, resources)
```

That single command does the whole setup for you. After it finishes, init has:

* **Generated `xmcp.config.ts`** with the NestJS adapter and your selected paths.
* **Generated a `src/xmcp/` folder** with the module, controller, exception filter, and an auth guard stub.
* **Scaffolded a sample tool** in `src/tools/` (plus prompts/resources if you picked them).
* **Wired `package.json`** so `build` runs `xmcp build` before your Nest build.
* **Updated `tsconfig.json`** with the `@xmcp/*` path alias and `xmcp-env.d.ts`.
* **Updated `.gitignore`** to ignore the generated `.xmcp` folder.
* **Installed `xmcp` and `zod`** with your package manager.

The generated `src/xmcp/` folder holds the module files you'll customize:

```
src/xmcp/
├── xmcp.filter.ts      # Exception filter for JSON-RPC errors
├── xmcp.controller.ts  # Controller with configurable route
├── xmcp.module.ts      # NestJS module configuration
└── xmcp.auth.ts        # Auth guard stub (createMcpAuthGuard)
```

## Adjust the Dev Script

Init already wired your `build` script to run `xmcp build && nest build`, added the `@xmcp/*` path alias and `xmcp-env.d.ts` to your `tsconfig.json`, and ignored the generated `.xmcp` folder in `.gitignore`. There's one script worth adjusting by hand.

NestJS apps watch with `start:dev`, so init can't know to combine it with xmcp. It writes a standalone `dev: "xmcp dev"`. Update it to run both watchers together:

```json title="package.json"
{
  "scripts": {
    "dev": "xmcp dev & nest start --watch"
  }
}
```

> The scaffolded files import from `@xmcp/adapter`, which resolves to the local
>   `.xmcp` folder via the `@xmcp/*` alias init added to your `tsconfig.json`. That
>   folder is generated the first time you run `xmcp dev` or `xmcp build` (both are
>   wired into your scripts), so the imports resolve once you start the dev server.
>   `.xmcp` is already gitignored.

## The Generated Config

Because init detected `@nestjs/core`, it already generated an `xmcp.config.ts` set up for the NestJS adapter. You don't write this by hand:

```typescript title="xmcp.config.ts"
import { type XmcpConfig } from "xmcp";

const config: XmcpConfig = {
  http: true,
  experimental: {
    adapter: "nestjs",
  },
  paths: {
    tools: "src/tools",
    prompts: false,
    resources: false,
  },
};

export default config;
```

The `paths` reflect the components you selected during init (`false` for the ones you skipped). The NestJS adapter uses HTTP transport and integrates with Nest's module system, so you can inject `XmcpService` into your own controllers if you ever need to. Edit this file only if you want to change the adapter or move your tool directories.

## Register the Module

This is the one integration step init leaves to you, it prints these exact instructions when it finishes. This is deliberate: init won't edit your `app.module.ts` to auto-register `XmcpModule`, because mounting the service into your application graph without your say-so could introduce a breaking change to a project that's already running. You decide when the MCP endpoint goes live. Import `XmcpModule` into your application module:

```typescript title="src/app.module.ts"
import { Module } from "@nestjs/common";
import { XmcpModule } from "./xmcp/xmcp.module";

@Module({
  imports: [XmcpModule],
})
export class AppModule {}
```

That's it for wiring. This registers a `/mcp` endpoint that handles MCP requests via POST.

## A Tour of the Generated Files

The files in `src/xmcp/` are yours to customize. Here's what each one does.

### Controller

The controller extends `XmcpController` and uses standard Nest decorators:

```typescript title="src/xmcp/xmcp.controller.ts"
import { Controller, UseFilters } from "@nestjs/common";
import { XmcpController } from "@xmcp/adapter";
import { McpExceptionFilter } from "./xmcp.filter";

@Controller("mcp")
@UseFilters(McpExceptionFilter)
export class McpController extends XmcpController {}
```

Want a different route? Just change the `@Controller` argument:

```typescript
@Controller("api/v1/mcp") // Now accessible at /api/v1/mcp
export class McpController extends XmcpController {}
```

### Module

The module registers the controller and providers, and wires up OAuth resource metadata and the auth guard out of the box:

```typescript title="src/xmcp/xmcp.module.ts"
import { Module } from "@nestjs/common";
import { XmcpService, OAuthModule } from "@xmcp/adapter";
import { McpController } from "./xmcp.controller";
import { McpExceptionFilter } from "./xmcp.filter";
import { McpAuthGuard } from "./xmcp.auth";

const config = {
  authorizationServers: [process.env.OAUTH_ISSUER!],
};

@Module({
  imports: [OAuthModule.forRoot(config)],
  controllers: [McpController],
  providers: [XmcpService, McpExceptionFilter, McpAuthGuard],
  exports: [XmcpService],
})
export class XmcpModule {}
```

`OAUTH_ISSUER` is only read when a client requests the OAuth resource metadata endpoint, so the module boots and serves tools even if you haven't set it yet. Wire it up when you're ready to advertise an authorization server.

### Exception Filter

The filter handles MCP errors in JSON-RPC format. Customize it to handle specific error types or change the response shape:

```typescript title="src/xmcp/xmcp.filter.ts"
import { ExceptionFilter, Catch, ArgumentsHost, Logger } from "@nestjs/common";
import { Response } from "express";

@Catch()
export class McpExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger(McpExceptionFilter.name);

  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();

    this.logger.error(
      "MCP request failed",
      exception instanceof Error ? exception.stack : String(exception)
    );

    if (!response.headersSent) {
      response.status(500).json({
        jsonrpc: "2.0",
        error: {
          code: -32603,
          message: "Internal server error",
        },
        id: null,
      });
    }
  }
}
```

### Auth Guard

Init also generates an `xmcp.auth.ts` stub built around the `createMcpAuthGuard` factory. It's already imported by the module, so all you do is fill in `verifyToken` when you want authentication:

```typescript title="src/xmcp/xmcp.auth.ts"
import { createMcpAuthGuard } from "@xmcp/adapter";

/**
 * MCP Auth Guard configuration.
 *
 * To enable authentication:
 * 1. Add McpAuthGuard to providers in xmcp.module.ts
 * 2. Add @UseGuards(McpAuthGuard) to xmcp.controller.ts
 */
export const McpAuthGuard = createMcpAuthGuard({
  verifyToken: async (token) => {
    // TODO: Implement your token verification logic
    throw new Error("Token verification not implemented");
  },
  required: false, // Set to true to require authentication
});
```

With `required: false`, the guard lets unauthenticated requests through and never calls `verifyToken`, so the default scaffold runs without any auth wiring. The [Authentication](#authentication) section below shows how to fill it in.

## Add a Tool

Tools are discovered automatically from your `src/tools/` directory. Create a file, export a `schema`, `metadata`, and a default handler:

```typescript title="src/tools/greet.ts"
import { z } from "zod";
import { type InferSchema } from "xmcp";

export const schema = {
  name: z.string().describe("The name of the user to greet"),
};

export const metadata = {
  name: "greet",
  description: "Greet the user by name",
};

export default async function greet({ name }: InferSchema<typeof schema>) {
  return `Hello, ${name}!`;
}
```

When you run `xmcp dev` or `xmcp build`, xmcp registers this as an MCP tool. No extra configuration, no manual registration.

The more interesting part is that tools can read from the rest of your application. A tool is just a function, so it can call into your existing stores and services:

```typescript title="src/tools/list-users.ts"
import { type ToolMetadata } from "xmcp";
import { getUsersStore } from "../users/users.store";

export const schema = {};

export const metadata: ToolMetadata = {
  name: "list-users",
  description: "List all users in the system",
};

export default async function listUsers() {
  const usersStore = getUsersStore();
  const users = usersStore.findAll();

  if (users.length === 0) {
    return "No users found in the system.";
  }

  const userList = users
    .map(
      (user, index) =>
        `${index + 1}. ${user.name} (${user.email}) - ID: ${user.id}`
    )
    .join("\n");

  return `Found ${users.length} user(s):\n\n${userList}`;
}
```

This is the payoff of running inside your Nest app: the same data that backs your REST API now backs your MCP tools.

## Authentication

The guard from `createMcpAuthGuard` is already generated in `xmcp.auth.ts` and already registered in your module's providers. Two things are left to you: implement `verifyToken`, and apply the guard to the controller.

The generated stub throws from `verifyToken`. Fill it in with your verification logic — here's a JWT example. Start by installing the JWT library:

```bash
npm install jsonwebtoken
npm install -D @types/jsonwebtoken
```

Then replace the stub's `verifyToken`:

```typescript title="src/xmcp/xmcp.auth.ts"
import { createMcpAuthGuard } from "@xmcp/adapter";
import * as jwt from "jsonwebtoken";

export const McpAuthGuard = createMcpAuthGuard({
  verifyToken: async (token) => {
    const decoded = jwt.verify(
      token,
      process.env.JWT_SECRET!
    ) as jwt.JwtPayload;

    return {
      clientId: decoded.sub || "unknown",
      scopes: decoded.scope?.split(" ") || [],
      expiresAt: decoded.exp,
    };
  },
  required: true, // Reject requests without a valid token
});
```

The adapter handles token extraction, error responses, and attaching auth info to the request. The guard is registered in the module already, so the only wiring left is applying it to the controller with `@UseGuards`:

```typescript title="src/xmcp/xmcp.controller.ts"
import { Controller, UseFilters, UseGuards } from "@nestjs/common";
import { XmcpController } from "@xmcp/adapter";
import { McpExceptionFilter } from "./xmcp.filter";
import { McpAuthGuard } from "./xmcp.auth";

@Controller("mcp")
@UseFilters(McpExceptionFilter)
@UseGuards(McpAuthGuard)
export class McpController extends XmcpController {}
```

Inside a tool, the auth info is available through the `extra` argument:

```typescript title="src/tools/whoami.ts"
import { type ToolMetadata, type ToolExtraArguments } from "xmcp";

export const schema = {};

export const metadata: ToolMetadata = {
  name: "whoami",
  description: "Returns information about the authenticated user",
};

export default async function whoami(
  _args: unknown,
  extra: ToolExtraArguments
) {
  const authInfo = extra.authInfo;

  if (!authInfo) {
    return "Not authenticated";
  }

  return JSON.stringify(
    {
      clientId: authInfo.clientId,
      scopes: authInfo.scopes,
    },
    null,
    2
  );
}
```

For the full list of guard options, see the [NestJS adapter docs](/docs/adapters/nestjs).

## Lifecycle and Logging

Because the adapter runs as a real Nest service, it plays by Nest's rules. `XmcpService` implements `OnModuleInit` and `OnModuleDestroy`, so it initializes and shuts down with the rest of your application, and every internal log goes through Nest's `Logger`. That means MCP traffic shows up in the same logs, with the same formatting, as the rest of your app, no separate logging setup required.

## Test It

With the server running, send a request to the `/mcp` endpoint:

```bash
# Without authentication (if required: false)
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'

# With authentication
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-jwt-token>" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
```

## Conclusion

Now your NestJS app has an MCP server. Tools are auto-discovered, served through a module you fully control, and able to reuse the services and data you already have.

For the full reference, check the [NestJS adapter docs](/docs/adapters/nestjs).
