# NestJS (/docs/adapters/nestjs)

## Overview

The NestJS adapter allows you to integrate xmcp into your existing NestJS application. It provides:

* **Automatic tool discovery** from your `src/tools/` directory
* **Scaffolded module** with customizable controller, filter, and route configuration
* **NestJS integration** with `xmcpService` and `xmcpController`

## Installation

`xmcp` can work on top of your existing NestJS project. To get started, run the following command in your project directory:

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

On initialization, you'll see the following prompts:

<TerminalPrompt>
  {"? Tools directory path: (tools)"}
</TerminalPrompt>

The package manager and framework will be detected automatically.

After initialization, xmcp generates a `src/xmcp/` folder with customizable module files:

```
src/xmcp/
├── xmcp.filter.ts      # Exception filter for JSON-RPC errors
├── xmcp.controller.ts  # Controller with configurable route
└── xmcp.module.ts      # NestJS module configuration
```

After setting up the project, update your `package.json` scripts:

```json title="package.json"
{
  "scripts": {
    "dev": "xmcp dev & nest start --watch",
    "build": "xmcp build && nest build",
    "start": "node dist/main.js"
  }
}
```

<Callout variant="info">
  Before using the `@xmcp/adapter` import, you need to:

  1. Run `npx xmcp build` to generate the `.xmcp` folder
  2. Update your `tsconfig.json` to include the path mapping and the `.xmcp` folder:

  ```json title="tsconfig.json"
  {
    "compilerOptions": {
      "paths": {
        "@xmcp/*": ["./.xmcp/*"]
      }
    },
    "include": ["src/**/*", "xmcp-env.d.ts", ".xmcp/**/*"]
  }
  ```

  After these steps, TypeScript errors will be resolved.
</Callout>

## Project Structure

After initialization, your project structure will look like this:

```
my-nestjs-app/
├── src/
│   ├── tools/              # Tool files are auto-discovered here
│   │   └── greet.ts
│   ├── xmcp/               # Generated xmcp module (customizable)
│   │   ├── xmcp.filter.ts
│   │   ├── xmcp.controller.ts
│   │   └── xmcp.module.ts
│   ├── app.module.ts       # Import XmcpModule here
│   └── main.ts
├── .xmcp/                  # Generated by xmcp build (gitignored)
├── xmcp.config.ts          # xmcp configuration
├── xmcp-env.d.ts           # Type declarations
├── package.json
└── tsconfig.json
```

### Generated Files

**`src/xmcp/`** - Contains the scaffolded module with controller, filter, and module configuration. These files are yours to customize - change the route path, add middleware, modify error handling, or extend functionality as needed.

**`.xmcp/`** - Contains the compiled adapter and auto-generated TypeScript definitions. This directory is created by `xmcp build` and should be added to `.gitignore`. It includes `xmcpService`, `xmcpController`, and all type definitions needed to integrate with NestJS.

**`xmcp-env.d.ts`** - Provides TypeScript type declarations for xmcp imports like `@xmcp/adapter`. This file is auto-generated and should not be edited manually. It ensures TypeScript can resolve the path alias configured in `tsconfig.json`.

## Basic Usage

Import and add the `XmcpModule` to 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 {}
```

This registers a `/mcp` endpoint that handles MCP requests via POST.

## Generated Module Files

### Exception Filter

The exception filter provides JSON-RPC error handling for MCP endpoints:

```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,
      });
    }
  }
}
```

This filter is scaffolded into your project, so you can customize it to handle specific error types or modify the error response format.

### Controller

The controller extends `xmcpController` and uses NestJS 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 {}
```

To change the route, simply modify the `@Controller` argument:

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

### Module

The module configures the controller and providers:

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

@Module({
  controllers: [McpController],
  providers: [xmcpService, McpExceptionFilter],
  exports: [xmcpService],
})
export class XmcpModule {}
```

## Configuration

Configure the NestJS adapter in your `xmcp.config.ts`:

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

const config: XmcpConfig = {
  http: true,
  experimental: {
    adapter: "nestjs",
  },
};

export default config;
```

The NestJS adapter uses HTTP transport and integrates with NestJS's module system, allowing you to use the `xmcpService` in your custom controllers.

## NestJS Integration Features

The adapter provides full NestJS integration with proper lifecycle management and error handling:

### Lifecycle Hooks

`xmcpService` implements `OnModuleInit` and `OnModuleDestroy` for proper initialization and shutdown logging:

* **Startup**: Logs `[xmcpService] XMCP service initialized` when the module initializes
* **Shutdown**: Logs `[xmcpService] XMCP service shutting down` when the application stops

### Structured Logging

All xmcp internal logs use the NestJS `Logger` class, automatically inheriting your application's logging configuration.

## Adding Tools

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

```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 automatically discovers this file and registers it as an MCP tool. No additional configuration needed.

<Callout variant="info">
  For more details on creating tools, schemas, and metadata, see the [Tools
  documentation](/docs/core-concepts/tools).
</Callout>

## Authentication

The NestJS adapter provides a `createMcpAuthGuard` factory function for JWT authentication. You provide the verification logic, and the adapter handles token extraction, error responses, and attaching auth info to requests.

### Setup

First, install the JWT library:

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

Create an auth guard configuration file:

```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: false, // Set to true to require authentication
});
```

### Enable Authentication

To enable authentication, add the guard to your controller:

```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 {}
```

And add it to your module providers:

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

@Module({
  controllers: [McpController],
  providers: [xmcpService, McpExceptionFilter, McpAuthGuard],
  exports: [xmcpService],
})
export class XmcpModule {}
```

### Configuration Options

| Option        | Type                                               | Default  | Description                                   |
| ------------- | -------------------------------------------------- | -------- | --------------------------------------------- |
| `verifyToken` | `(token: string) => Promise<AuthInfo> \| AuthInfo` | Required | Verify the token and return auth info         |
| `required`    | `boolean`                                          | `false`  | If true, requests without tokens are rejected |

The `verifyToken` function receives the Bearer token (without the "Bearer " prefix) and should return:

```typescript
interface AuthInfo {
  clientId: string; // User/client identifier
  scopes: string[]; // Permissions/scopes
  expiresAt?: number; // Token expiration (Unix timestamp)
  extra?: Record<string, unknown>; // Additional custom data
}
```

If verification fails, throw an error with a descriptive message.

### Accessing Auth Info in Tools

Auth info is available in tools via 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;
  const clientInfo = extra.clientInfo;

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

  return JSON.stringify(
    {
      clientId: authInfo.clientId,
      scopes: authInfo.scopes,
      clientName: clientInfo?.name,
      clientVersion: clientInfo?.version,
    },
    null,
    2
  );
}
```

### Testing with curl

```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}'
```

## Troubleshooting

### Cannot find module '@xmcp/adapter'

This error occurs when the `.xmcp` directory hasn't been generated yet.

**Solution:** Run `npx xmcp build` before starting your NestJS application.

### TypeScript path resolution errors

If TypeScript can't resolve `@xmcp/*` imports:

1. Ensure `tsconfig.json` has the path mapping:

   ```json
   {
     "compilerOptions": {
       "paths": {
         "@xmcp/*": ["./.xmcp/*"]
       }
     }
   }
   ```

2. Ensure `.xmcp` is included in the `include` array:
   ```json
   {
     "include": ["src/**/*", "xmcp-env.d.ts", ".xmcp/**/*"]
   }
   ```

## Next Steps

<ConceptBoxes>
  <ConceptBox title="Tools" description="Learn how to create tools with schemas and metadata." href="/docs/core-concepts/tools" />

  <ConceptBox title="Prompts" description="Define reusable prompts for AI interactions." href="/docs/core-concepts/prompts" />

  <ConceptBox title="Resources" description="Expose data through queryable resources." href="/docs/core-concepts/resources" />
</ConceptBoxes>
