UNPKG

@nestjs-mcp/server

Version:

Modular library for building scalable MCP servers with NestJS, providing decorators and integration patterns as a wrapper for the official MCP TypeScript SDK.

1,066 lines (818 loc) 38.8 kB
# MCP Server NestJS Module Library <!-- omit in toc --> [![NPM Version](https://img.shields.io/npm/v/@nestjs-mcp/server)](https://www.npmjs.com/package/@nestjs-mcp/server) [![Semantic Release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![Downloads](https://img.shields.io/npm/dm/@nestjs-mcp/server)](https://www.npmjs.com/package/@nestjs-mcp/server) [![CI Pipeline](https://github.com/adrian-d-hidalgo/nestjs-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/adrian-d-hidalgo/nestjs-mcp-server/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/adrian-d-hidalgo/nestjs-mcp-server/graph/badge.svg?token=5E228VKY5K)](https://codecov.io/gh/adrian-d-hidalgo/nestjs-mcp-server) [![Known Vulnerabilities](https://snyk.io/test/github/adrian-d-hidalgo/nestjs-mcp-server/badge.svg)](https://snyk.io/test/github/adrian-d-hidalgo/nestjs-mcp-server) [![MIT License](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](./CONTRIBUTING.md) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) --- ## Overview <!-- omit in toc --> **NestJS MCP Server** is a modular library for building [Model Context Protocol (MCP)](https://github.com/modelcontextprotocol/typescript-sdk/tree/server) servers using [NestJS](https://nestjs.com/). It provides decorators, modules, and integration patterns to expose MCP resources, tools, and prompts in a scalable, maintainable way. This project is a wrapper for the official [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk/tree/server) and is always kept compatible with its types and specification. --- ## Table of Contents <!-- omit in toc --> - [Installation](#installation) - [Quickstart](#quickstart) - [What is MCP?](#what-is-mcp) - [Core Concepts](#core-concepts) - [Server](#server) - [Resource](#resource) - [Tool](#tool) - [Prompt](#prompt) - [Module API](#module-api) - [`McpModule.forRoot`](#mcpmoduleforroot) - [`McpModule.forRootAsync`](#mcpmoduleforrootasync) - [`McpModule.forFeature`](#mcpmoduleforfeature) - [Module Usage](#module-usage) - [1. Global Registration with `McpModule.forRoot`](#1-global-registration-with-mcpmoduleforroot) - [2. Feature Module Registration with `McpModule.forFeature`](#2-feature-module-registration-with-mcpmoduleforfeature) - [Capabilities](#capabilities) - [Resolver Decorator](#resolver-decorator) - [Prompt Decorator](#prompt-decorator) - [Resource Decorator](#resource-decorator) - [Tool Decorator](#tool-decorator) - [Tool Annotations](#tool-annotations) - [ToolOptions Variants](#tooloptions-variants) - [RequestHandlerExtra Argument](#requesthandlerextra-argument) - [Guards](#guards) - [Global-level guards](#global-level-guards) - [Resolver-level guards](#resolver-level-guards) - [Method-level guards](#method-level-guards) - [Guard Example](#guard-example) - [MCP Execution Context](#mcp-execution-context) - [Guards with Dependency Injection](#guards-with-dependency-injection) - [Session Management](#session-management) - [Session Management Options](#session-management-options) - [Transport Options](#transport-options) - [Inspector Playground](#inspector-playground) - [Examples](#examples) - [Changelog](#changelog) - [License](#license) - [Contributions](#contributions) --- ## Installation ```sh npm install @nestjs-mcp/server @modelcontextprotocol/sdk zod # or yarn add @nestjs-mcp/server @modelcontextprotocol/sdk zod # or pnpm add @nestjs-mcp/server @modelcontextprotocol/sdk zod ``` --- ## Quickstart Register the MCP module in your NestJS app and expose a simple tool: ```ts import { Module } from '@nestjs/common'; import { CallToolResult } from '@modelcontextprotocol/sdk/types'; import { Resolver, Tool, McpModule } from '@nestjs-mcp/server'; @Resolver() export class HealthResolver { /** * Simple health check tool */ @Tool({ name: 'server_health_check' }) healthCheck(): CallToolResult { return { content: [ { type: 'text', text: 'Server is operational. All systems running normally.', }, ], }; } } @Module({ imports: [ McpModule.forRoot({ name: 'My MCP Server', version: '1.0.0', }), ], providers: [HealthResolver], }) export class AppModule {} ``` --- ## What is MCP? The **Model Context Protocol (MCP)** is an open protocol for connecting LLMs to external data, tools, and prompts. MCP servers expose resources (data), tools (actions), and prompts (conversational flows) in a standardized way, enabling seamless integration with LLM-powered clients. - See the [Anthropic announcement](https://www.anthropic.com/news/model-context-protocol) for more background. --- ## Core Concepts ### Server The MCP Server is the main entry point for exposing capabilities to LLMs. It manages the registration and discovery of resources, tools, and prompts. ### Resource A Resource represents structured data or documents that can be queried or retrieved by LLMs. Resources are typically read-only and are identified by a unique URI. - Learn more: [MCP Resources documentation](https://modelcontextprotocol.io/docs/concepts/resources) ### Tool A Tool is an action or function that can be invoked by LLMs. Tools may have side effects and can accept parameters to perform computations or trigger operations. - Learn more: [MCP Tools documentation](https://modelcontextprotocol.io/docs/concepts/tools) ### Prompt A Prompt defines a conversational flow, template, or interaction pattern for LLMs. Prompts help guide the model's behavior in specific scenarios. - Learn more: [MCP Prompts documentation](https://modelcontextprotocol.io/docs/concepts/prompts) > **See the [Capabilities](#capabilities) section for implementation details and code examples.** --- ## Module API ### `McpModule.forRoot` Registers the MCP Server globally in your NestJS application. **Parameters:** - `options: McpModuleOptions` — Main server configuration object: - `name: string`: The name of your MCP server. - `version: string`: The version of your MCP server. - `instructions?: string`: Optional description of the MCP server for the client. - `capabilities?: Record<string, unknown>`: Optional additional capabilities metadata. - `providers?: Provider[]`: Optional array of NestJS providers to include in the module. - `imports?: any[]`: Optional array of NestJS modules to import. - `logging?: McpLoggingOptions`: Optional logging configuration: - `enabled?: boolean` (default: `true`): Enable/disable logging. - `level?: 'error' | 'warn' | 'log' | 'debug' | 'verbose'` (default: `'verbose'`): Set the logging level. - `transports?: McpModuleTransportOptions`: Optional transport configuration (see [Transport Options](#transport-options)). - `protocolOptions?: Record<string, unknown>`: Optional parameters passed directly to the underlying `@modelcontextprotocol/sdk` server instance. **Returns:** - A dynamic NestJS module with all MCP providers registered. **Example:** ```ts import { Module } from '@nestjs/common'; import { McpModule } from '@nestjs-mcp/server'; @Module({ imports: [ McpModule.forRoot({ name: 'My Server', version: '1.0.0', instructions: 'A server providing utility tools and data.', logging: { level: 'log' }, transports: { sse: { enabled: false } }, // Disable SSE transport // ...other MCP options }), ], }) export class AppModule {} ``` ### `McpModule.forRootAsync` Registers the MCP Server globally using asynchronous options, useful for integrating with configuration modules like `@nestjs/config`. > **Note:** > > - The `imports` array should include any modules that provide dependencies required by your `useFactory` (e.g., `ConfigModule` if you inject `ConfigService`). > - Use `forRootAsync` only once in your root module (`AppModule`). > - See `McpModuleAsyncOptions` for all available options. **Parameters:** - `options: McpModuleAsyncOptions` — Asynchronous configuration object: - `imports?: any[]`: Optional modules to import before the factory runs. - `useFactory: (...args: any[]) => Promise<McpModuleOptions> | McpModuleOptions`: A factory function that returns the `McpModuleOptions`. - `inject?: any[]`: Optional providers to inject into the `useFactory`. **Returns:** - A dynamic NestJS module. **Example (with ConfigModule):** ```ts import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { McpModule } from '@nestjs-mcp/server'; @Module({ imports: [ ConfigModule.forRoot(), // Make sure ConfigModule is imported McpModule.forRootAsync({ imports: [ConfigModule], // Import ConfigModule here too useFactory: (configService: ConfigService) => ({ name: configService.get<string>('MCP_SERVER_NAME', 'Default Server'), version: configService.get<string>('MCP_SERVER_VERSION', '1.0.0'), instructions: configService.get<string>('MCP_SERVER_DESC'), logging: { level: configService.get('MCP_LOG_LEVEL', 'verbose'), }, // ... other options from configService }), inject: [ConfigService], // Inject ConfigService into the factory }), ], }) export class AppModule {} ``` ### `McpModule.forFeature` Registers additional MCP resources, tools, or prompts within a feature module. Use this to organize large servers into multiple modules. Resolvers containing MCP capabilities must be included in the `providers` array of the feature module. **Parameters:** - `options?: McpFeatureOptions` (Currently unused, reserved for future enhancements). **Returns:** - A dynamic module. **Example:** ```ts // src/status/status.resolver.ts import { Resolver, Tool } from '@nestjs-mcp/server'; import { CallToolResult } from '@modelcontextprotocol/sdk/types'; @Resolver('status') export class StatusResolver { @Tool({ name: 'health_check' }) healthCheck(): CallToolResult { return { content: [{ type: 'text', text: 'OK' }] }; } } // src/status/status.module.ts import { Module } from '@nestjs/common'; import { McpModule } from '@nestjs-mcp/server'; import { StatusResolver } from './status.resolver'; @Module({ imports: [McpModule.forFeature()], // Import forFeature here providers: [StatusResolver], // Register your resolver }) export class StatusModule {} ``` --- ## Module Usage This library provides two main ways to register MCP capabilities in your NestJS application: ### 1. Global Registration with `McpModule.forRoot` Use `McpModule.forRoot` in your root application module to configure and register the MCP server globally. This is required for every MCP server application. ```ts import { Module } from '@nestjs/common'; import { McpModule } from '@nestjs-mcp/server'; import { PromptsResolver } from './prompts.resolver'; @Module({ imports: [ McpModule.forRoot({ name: 'My MCP Server', version: '1.0.0', // ...other MCP options }), ], providers: [PromptsResolver], }) export class AppModule {} ``` ### 2. Feature Module Registration with `McpModule.forFeature` Use `McpModule.forFeature` in feature modules to register additional resolvers, tools, or resources. This is useful for organizing large servers into multiple modules. ```ts import { Module } from '@nestjs/common'; import { McpModule } from '@nestjs-mcp/server'; import { ToolsResolver } from './tools.resolver'; @Module({ imports: [McpModule.forFeature()], providers: [ToolsResolver], }) export class ToolsModule {} ``` - Use `forRoot` or `forRootAsync` **only once** in your root module (`AppModule`). - Use `forFeature` in any feature module where you define MCP capabilities (`@Resolver` classes). - Ensure all Resolvers are listed in the `providers` array of their respective modules. --- ## Capabilities This library provides a set of decorators to define MCP capabilities and apply cross-cutting concerns such as guards. Decorators can be used at both the Resolver (class) level and the method level. ### Resolver Decorator A Resolver is a class that groups related MCP capabilities. **All** MCP capability methods (`@Prompt`, `@Resource`, `@Tool`) **must** belong to a class decorated with `@Resolver`. - **No `@Injectable()` Needed:** Resolver classes are automatically treated as providers by the MCP module and **do not** require the `@Injectable()` decorator. - **Dependency Injection:** Standard NestJS dependency injection works within Resolver constructors. - **Namespacing:** You can optionally provide a string argument to `@Resolver('my_namespace')` to namespace the capabilities within that resolver. - **Guards:** Guards can be applied at the class level using `@UseGuards()`. **Example:** ```ts import { Resolver, Prompt, Resource, Tool } from '@nestjs-mcp/server'; // Import any services you need to inject import { SomeService } from '../some.service'; @Resolver('workspace') // No @Injectable() export class MyResolver { // Inject dependencies as usual constructor(private readonly someService: SomeService) {} @Prompt({ name: 'greet_user' }) // Capabilities must be inside a Resolver greetPrompt(/*...args...*/) { const greeting = this.someService.getGreeting(); /* ... */ } @Resource({ name: 'user_profile', uri: 'user://{id}' }) getUserResource(/*...args...*/) { /* ... */ } @Tool({ name: 'calculate_sum' }) sumTool(/*...args...*/) { /* ... */ } } ``` You can also apply guards at the resolver level: ```ts import { UseGuards, Resolver } from '@nestjs-mcp/server'; import { MyGuard } from './guards/my.guard'; @UseGuards(MyGuard) // Applied to all capabilities in this Resolver @Resolver('secure') // No @Injectable() export class SecureResolver { // All capabilities in this resolver will use MyGuard } ``` ### Prompt Decorator Decorate methods within a Resolver class to expose them as MCP Prompts. Accepts options compatible with `server.prompt()` from `@modelcontextprotocol/sdk`. **The `name` should use `snake_case`.** ```ts import { Prompt, Resolver } from '@nestjs-mcp/server'; import { RequestHandlerExtra } from '@nestjs-mcp/server'; // Import type for extra info import { z } from 'zod'; // Example if using Zod schema // Optional: Define schema if needed // const SummaryArgs = z.object({ topic: z.string() }); @Resolver('prompts') // Must be in a Resolver class export class MyPrompts { @Prompt({ name: 'generate_summary', description: 'Generates a summary for the given text.', // argsSchema: SummaryArgs }) generateSummaryPrompt( // params: z.infer<typeof SummaryArgs>, // Arguments based on argsSchema (if defined) extra: RequestHandlerExtra, // Contains sessionId and other metadata ) { console.log(`Generating summary for session: ${extra.sessionId}`); /* ... return CallPromptResult ... */ return { content: [{ type: 'text', text: 'Summary generated.' }] }; } } ``` ### Resource Decorator Decorate methods within a Resolver class to expose them as MCP Resources. Accepts options compatible with `server.resource()` from `@modelcontextprotocol/sdk`. **The `name` should use `snake_case`.** ```ts import { Resource, Resolver } from '@nestjs-mcp/server'; import { RequestHandlerExtra } from '@nestjs-mcp/server'; // Import type for extra info import { URL } from 'url'; // Type for URI resource import { z } from 'zod'; // Example if using Zod template // Optional: Define template schema if needed // const DocQueryTemplate = z.object({ query: z.string() }); @Resolver('data') // Must be in a Resolver class export class MyResources { @Resource({ name: 'user_profile', uri: 'user://profiles/{userId}', // metadata: { description: '...' } // Optional }) getUserProfile( uri: URL, // First argument is the parsed URI // metadata: Record<string, any> // Second argument if is defined extra: RequestHandlerExtra, // Contains sessionId and other metadata ) { const userId = uri.pathname.split('/').pop(); // Example: Extract ID from URI console.log(`Fetching profile for ${userId}, session: ${extra.sessionId}`); /* ... return CallResourceResult ... */ return { content: [{ type: 'text', text: `Profile data for ${userId}` }] }; } @Resource({ name: 'document_list', template: { type: 'string', description: 'Document content query' }, // Simple template example // metadata: { list: true } // Optional }) findDocuments( uri: URL, // First arg based on simple template type variables: Record<string, string>, // Second arg is path params (if any) extra: RequestHandlerExtra, // Contains sessionId and other metadata ) { console.log( `Finding documents matching '${query}', session: ${extra.sessionId}`, ); /* ... return CallResourceResult ... */ return { content: [{ type: 'text', text: 'List of documents.' }] }; } } ``` ### Tool Decorator Decorate methods within a Resolver class to expose them as MCP Tools. Accepts options compatible with `server.tool()` from `@modelcontextprotocol/sdk`. **The `name` should use `snake_case`.** ```ts import { Tool, Resolver } from '@nestjs-mcp/server'; import { RequestHandlerExtra } from '@nestjs-mcp/server'; import { z } from 'zod'; import { CallToolResult } from '@modelcontextprotocol/sdk/types'; @Resolver('user_tools') export class UserToolsResolver { @Tool({ name: 'delete_user', description: 'Deletes a user by ID', paramsSchema: { userId: z.string() }, annotations: { destructiveHint: true, readOnlyHint: false }, }) deleteUser( { userId }: { userId: string }, extra: RequestHandlerExtra, ): CallToolResult { // ...logic... return { content: [{ type: 'text', text: `User ${userId} deleted.` }] }; } } ``` #### Tool Annotations The `annotations` field allows you to provide protocol-level hints about the tool's behavior, such as whether it is destructive, read-only, idempotent, or has other special properties. These hints can be used by clients, UIs, or the protocol itself to display warnings, optimize calls, or enforce policies. **Common annotation keys:** - `destructiveHint` (boolean): Indicates the tool performs a destructive action (e.g., deletes data). - `readOnlyHint` (boolean): Indicates the tool does not modify any data. - `idempotentHint` (boolean): Indicates the tool can be safely called multiple times with the same effect. - `openWorldHint` (boolean): Indicates the tool may have side effects outside the current system. **Example:** ```ts @Tool({ name: 'reset_password', paramsSchema: { userId: z.string() }, annotations: { destructiveHint: true, idempotentHint: false } }) resetPassword({ userId }: { userId: string }): CallToolResult { // ... } ``` #### ToolOptions Variants | Variant | Required Fields | | ------------------------------------------------ | -------------------------------------------- | | ToolBaseOptions | name | | ToolWithDescriptionOptions | name, description | | ToolWithParamOrAnnotationsOptions | name, paramsSchemaOrAnnotations | | ToolWithParamOrAnnotationsAndDescriptionOptions | name, paramsSchemaOrAnnotations, description | | ToolWithParamAndAnnotationsOptions | name, paramsSchema, annotations | | ToolWithParamAndAnnotationsAndDescriptionOptions | name, paramsSchema, annotations, description | - `paramsSchema` and `paramsSchemaOrAnnotations` can be a Zod schema for input validation. - `annotations` is an object with protocol-level hints as described above. ### RequestHandlerExtra Argument All MCP capability methods (`@Prompt`, `@Resource`, `@Tool`) always receive a `RequestHandlerExtra` object as their last parameter. This object extends the original type from `@modelcontextprotocol/sdk` and provides essential context about the current MCP request. **Properties from SDK:** - `signal`: An `AbortSignal` used to communicate if the request was cancelled - `authInfo`: Optional information about a validated access token - `sessionId`: The session ID from the transport, if available - `sendNotification`: Function to send a notification related to the current request - `sendRequest`: Function to send a request related to the current request **Extended Properties:** - `headers`: HTTP headers from the original request (added by @nestjs-mcp/server) **Usage Example:** ```ts import { Tool, Resolver, SessionManager } from '@nestjs-mcp/server'; import { RequestHandlerExtra } from '@nestjs-mcp/server'; import { CallToolResult } from '@modelcontextprotocol/sdk/types'; @Resolver('auth') export class AuthResolver { @Tool({ name: 'authenticate_user', description: 'Authenticates a user with credentials', // ...other options }) authenticateUser( params: { username: string; password: string }, extra: RequestHandlerExtra, // Always the last parameter ): CallToolResult { // Access the session ID console.log(`Request received in session: ${extra.sessionId}`); // Access request headers (extended property) const authHeader = extra.headers.authorization; const userAgent = extra.headers['user-agent']; console.log(`Request from: ${userAgent}`); // Check if request was cancelled if (extra.signal.aborted) { return { content: [{ type: 'text', text: 'Request was cancelled' }], }; } // Implement authentication logic return { content: [{ type: 'text', text: 'Authentication successful' }], }; } } ``` **Important Notes:** - `extra` is always the last parameter in any method decorated with `@Resource`, `@Prompt`, or `@Tool` - The `headers` property is an extension added by @nestjs-mcp/server to access HTTP headers directly --- ## Guards Apply one or more guards to a Resolver, to individual methods, or globally. Guards must implement the NestJS `CanActivate` interface. ### Global-level guards This approach uses the standard NestJS global guard system (`APP_GUARD`). A global guard will protect **all** NestJS routes, including the MCP transport endpoints (like `/mcp` or `/sse`). Use this for broad authentication or checks that apply before any MCP-specific logic runs. ```ts // src/guards/global-auth.guard.ts import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Request } from 'express'; @Injectable() export class GlobalAuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest<Request>(); const apiKey = request.headers['x-api-key']; // Example: Check for a valid API key return !!apiKey && apiKey === 'EXPECTED_KEY'; } } ``` Register the guard globally in your main module: ```ts // src/app.module.ts import { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; import { McpModule } from '@nestjs-mcp/server'; import { GlobalAuthGuard } from './guards/global-auth.guard'; @Module({ imports: [McpModule.forRoot(/*...*/)], providers: [ { provide: APP_GUARD, useClass: GlobalAuthGuard, }, ], }) export class AppModule {} ``` ### Resolver-level guards This is a custom feature of this library. Resolver-level guards are applied using the `@UseGuards()` decorator (exported from `@nestjs-mcp/server`) on a Resolver class. All MCP methods (`@Prompt`, `@Resource`, `@Tool`) **within that specific resolver** will be protected by these guards. Use this to enforce logic (e.g., role checks) for a group of related capabilities. ```ts import { UseGuards, Resolver, Prompt } from '@nestjs-mcp/server'; import { RoleGuard } from './guards/role.guard'; @UseGuards(RoleGuard) @Resolver('admin') export class AdminResolver { @Prompt({ name: 'admin_action' }) adminAction(/*...*/) { /* ... */ } // ... other admin capabilities } ``` ### Method-level guards This is a custom feature of this library. Method-level guards are applied using the `@UseGuards()` decorator directly on an MCP capability method (`@Prompt`, `@Resource`, `@Tool`). Only the decorated method will be protected by these guards. Use this for fine-grained access control on specific capabilities. ```ts import { UseGuards, Resolver, Prompt, Tool } from '@nestjs-mcp/server'; import { SpecificCheckGuard } from './guards/specific-check.guard'; @Resolver('mixed') export class MixedResolver { @Prompt({ name: 'public_prompt' }) publicPrompt() { /* Publicly accessible */ } @UseGuards(SpecificCheckGuard) @Tool({ name: 'protected_tool' }) protectedTool(/*...*/) { /* Requires SpecificCheckGuard to pass */ } } ``` **Important:** Resolver and Method-level guards **only run for MCP capability invocations**, not for the initial connection establishment handled by global guards. They use the custom `McpExecutionContext`. ### Guard Example A guard for Resolver or Method-level protection: ```ts // src/guards/my-mcp.guard.ts import { CanActivate, Injectable } from '@nestjs/common'; import { McpExecutionContext, SessionManager } from '@nestjs-mcp/server'; @Injectable() export class MyMcpGuard implements CanActivate { constructor(private readonly sessionManager: SessionManager) {} canActivate(context: McpExecutionContext): boolean { const sessionId = context.getSessionId(); if (!sessionId) return false; const handlerArgs = context.getArgs(); const session = this.sessionManager.getSession(sessionId); const request = session?.request; const userAgent = request?.headers['user-agent']; console.log(`Guard activated for session ${sessionId} from ${userAgent}`); console.log('Handler args:', handlerArgs); return true; } } ``` ### MCP Execution Context When implementing **Resolver-level** or **Method-level** guards using `@UseGuards()` from this library, your `canActivate` method receives an `McpExecutionContext` instance. This context provides access to MCP-specific information: ```typescript import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { McpExecutionContext, SessionManager } from '@nestjs-mcp/server'; import { Request } from 'express'; @Injectable() export class McpAuthGuard implements CanActivate { constructor(private readonly sessionManager: SessionManager) {} canActivate(context: McpExecutionContext): boolean { const sessionId = context.getSessionId(); if (!sessionId) { console.error('Guard Error: MCP Session ID not found in context.'); return false; } const handlerArgs = context.getArgs<any>(); console.log('MCP Handler Arguments:', handlerArgs); const session = this.sessionManager.getSession(sessionId); if (!session) { console.error(`Guard Error: Session not found for ID: ${sessionId}`); return false; } const request = session.request as Request; const authHeader = request.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { console.log('Guard Denied: Missing or invalid Bearer token.'); return false; } const token = authHeader.split(' ')[1]; const isValidToken = token === 'VALID_TOKEN'; if (isValidToken) { console.log(`Guard Passed for session ${sessionId} with token.`); return true; } else { console.log(`Guard Denied: Invalid token for session ${sessionId}.`); return false; } } } ``` **Key points for `McpExecutionContext`:** - `getSessionId()`: Retrieves the unique ID for the current MCP session. **Crucial** for relating the guard check to the session state stored by `SessionManager`. - Arguments (`handlerArgs`): Provides the arguments passed specifically to the MCP handler method (`@Tool`, `@Prompt`, `@Resource`) being invoked. The structure of these arguments depends on the capability type and its definition (e.g., `params` for tools, `query`/`params` for resources). You access these via `context.getArgs()`, but be mindful of the actual structure based on the capability. - Request Data: Use the `SessionManager` injected into your guard to fetch the session details (including the original `Request`) based on the `sessionId` obtained from the context. - `switchToHttp().getResponse()` / `switchToHttp().getNext()`: These will throw errors as the Response object is not directly available or relevant in this context. Use `SessionManager` injected into your guard to fetch the session details (including the original `Request`) based on the `sessionId` obtained from the context. ### Guards with Dependency Injection Guards can inject NestJS providers like `SessionManager`. Use `@Injectable()` and register the guard as a provider: ```typescript @Injectable() export class AuthGuard implements CanActivate { constructor(private readonly sessionManager: SessionManager) {} canActivate(context: McpExecutionContext): boolean { const session = this.sessionManager.getSession(context.getSessionId()); return !!session?.request.headers.authorization; } } @Module({ imports: [McpModule.forRoot({ name: 'my-server', version: '1.0.0' })], providers: [AuthGuard, MyResolver], }) export class AppModule {} ``` > Guards without `@Injectable()` still work but won't receive injected dependencies. --- ## Session Management This library includes a `SessionManager` service responsible for tracking active MCP sessions. Each incoming MCP connection establishes a session, identified by a unique `sessionId`. The `SessionManager` typically stores the associated initial `Request` object for each session. **Why is it important?** - **Accessing Request Data:** Since MCP operations (tool calls, prompt executions) might happen independently of the initial HTTP connection (especially with streaming transports like SSE), the `SessionManager` provides a way to retrieve the original `Request` context associated with a specific `sessionId`. This is essential for guards or capability methods (within Resolvers) that need access to request headers, parameters, or other connection-specific details from the original request. - **State Management:** While currently focused on storing the request, the `SessionManager` could be extended to store additional session-specific state if needed by your application. **Usage Example (in a Resolver):** Resolvers might need access to the original request, for example, to get user information or API keys passed in headers during the initial connection. ```typescript import { Tool, Resolver, SessionManager } from '@nestjs-mcp/server'; import { RequestHandlerExtra } from '@nestjs-mcp/server'; // Provides sessionId import { Request } from 'express'; import { CallToolResult } from '@modelcontextprotocol/sdk/types'; import { z } from 'zod'; const UserToolParams = z.object({ user_id: z.string().optional(), }); @Resolver('user_tools') // No @Injectable() needed export class UserToolsResolver { // Inject SessionManager constructor(private readonly sessionManager: SessionManager) {} @Tool({ name: 'get_user_agent', description: 'Gets the user agent from the original request for the session.', paramSchema: UserToolParams, }) getUserAgent( params: z.infer<typeof UserToolParams>, extra: RequestHandlerExtra, // Get extra info, including sessionId ): CallToolResult { const sessionId = extra.sessionId; if (!sessionId) { return { content: [{ type: 'text', text: 'Error: Session ID missing.' }], }; } // Use sessionId to get the session from the manager const session = this.sessionManager.getSession(sessionId); if (!session) { return { content: [ { type: 'text', text: `Error: Session not found for ID: ${sessionId}`, }, ], }; } // Access the original request stored in the session const request = session.request as Request; const userAgent = request.headers['user-agent'] || 'Unknown'; return { content: [ { type: 'text', text: `Session ${sessionId} User Agent: ${userAgent}` }, ], }; } } ``` In this example: 1. The `@Tool` method receives `extra: RequestHandlerExtra`, which contains the `sessionId`. 2. The `SessionManager` is injected into the `UserToolsResolver`. 3. The `sessionId` is used with `sessionManager.getSession()` to retrieve the session data. 4. The original `request` object is accessed from the retrieved session data. The `SessionManager` is automatically registered as a provider when you use `McpModule.forRoot` or `McpModule.forRootAsync` and can be injected like any other NestJS provider. --- ## Transport Options The MCP server can communicate over different transport mechanisms. This library includes built-in support for: 1. **Streamable (`/mcp` endpoint):** A common transport using standard HTTP POST requests and responses. Suitable for most request/response interactions. Enabled by default. 2. **SSE (Server-Sent Events) (`/sse` endpoint):** A transport mechanism allowing the server to push updates to the client over a single HTTP connection. Useful for streaming responses or long-running operations. **Note:** This is considered a legacy transport but remains supported for compatibility. Enabled by default. You can configure which transports are enabled globally using the `transports` option in `McpModule.forRoot` or `McpModule.forRootAsync`. **Configuration:** ```typescript import { Module } from '@nestjs/common'; import { McpModule } from '@nestjs-mcp/server'; @Module({ imports: [ McpModule.forRoot({ name: 'My Server', version: '1.0.0', transports: { streamable: { enabled: true }, // Keep streamable enabled (default) sse: { enabled: false }, // Disable legacy SSE transport }, }), ], }) export class AppModule {} ``` **Default Configuration:** If the `transports` option is omitted, both `streamable` (`/mcp`) and `sse` (`/sse`) are enabled by default. ```typescript import { Module } from '@nestjs/common'; import { McpModule } from '@nestjs-mcp/server'; @Module({ imports: [ McpModule.forRoot({ name: 'My Server', version: '1.0.0', // Both streamable and sse will be enabled }), ], }) export class AppModule {} ``` Disabling unused transports can slightly reduce the application's surface area and resource usage. --- ## Session Management Options Configure session timeouts, cleanup intervals, and resource limits to optimize your server for production workloads. **Configuration:** ```typescript import { Module } from '@nestjs/common'; import { McpModule } from '@nestjs-mcp/server'; @Module({ imports: [ McpModule.forRoot({ name: 'My Server', version: '1.0.0', session: { sessionTimeoutMs: 1800000, // 30 minutes (default) cleanupIntervalMs: 300000, // 5 minutes (default) maxConcurrentSessions: 1000, // Max sessions (default) }, }), ], }) export class AppModule {} ``` **Configuration Options:** | Option | Type | Default | Description | | ------------------------- | -------- | --------------------- | ---------------------------------------------------- | | `sessionTimeoutMs` | `number` | `1800000` (30 min) | Maximum inactivity time before session cleanup | | `cleanupIntervalMs` | `number` | `300000` (5 min) | Frequency of cleanup job execution | | `maxConcurrentSessions` | `number` | `1000` | Maximum concurrent sessions allowed | **How It Works:** - **Activity Tracking**: Each session's `lastActivity` timestamp updates on every request - **Cleanup Job**: Runs every `cleanupIntervalMs` to close and remove inactive sessions - **Session Limit**: New connections are rejected (503) when `maxConcurrentSessions` is reached **Production Recommendations:** - **High-traffic servers**: Increase `maxConcurrentSessions` (2000-5000) and decrease `cleanupIntervalMs` (2-3 min) - **Low-memory environments**: Decrease `maxConcurrentSessions` (100-500) and `sessionTimeoutMs` (10-15 min) - **Long-running workflows**: Increase `sessionTimeoutMs` (60-90 min) **Example with Environment Variables:** ```typescript import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { McpModule } from '@nestjs-mcp/server'; @Module({ imports: [ ConfigModule.forRoot(), McpModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (config: ConfigService) => ({ name: 'My Server', version: '1.0.0', session: { sessionTimeoutMs: config.get('MCP_SESSION_TIMEOUT', 1800000), cleanupIntervalMs: config.get('MCP_CLEANUP_INTERVAL', 300000), maxConcurrentSessions: config.get('MCP_MAX_SESSIONS', 1000), }, }), }), ], }) export class AppModule {} ``` --- ## Inspector Playground Use the Inspector Playground to interactively test and debug your MCP server endpoints in a browser UI. This tool, powered by [`@modelcontextprotocol/inspector`](https://www.npmjs.com/package/@modelcontextprotocol/inspector), allows you to: - Explore available resources, tools, and prompts - Invoke endpoints and view responses in real time - Validate your server implementation against the MCP specification To launch the Inspector Playground (make sure your NestJS MCP server is running): ```sh npx @modelcontextprotocol/inspector ``` It will typically connect to `http://localhost:3000` by default, or you can specify a different target URL. --- ## Examples The [`examples/`](./examples/) directory contains ready-to-use scenarios demonstrating how to register and expose MCP capabilities. Each example is self-contained and follows best practices. For advanced usage, see the code and documentation in each example. --- ## Changelog See [CHANGELOG.md](./CHANGELOG.md) for release notes. --- ## License MIT — see [LICENSE](./LICENSE) for details. --- ## Contributions Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines, reporting issues, and pull request rules. Before contributing, please read our [Code of Conduct](./CODE_OF_CONDUCT.md) to understand the expectations for behavior in our community.