@candide/nestjs-mcp-server 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1065 -0
- package/dist/decorators/index.d.ts +5 -0
- package/dist/decorators/index.js +22 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/prompt.decorator.d.ts +15 -0
- package/dist/decorators/prompt.decorator.js +16 -0
- package/dist/decorators/prompt.decorator.js.map +1 -0
- package/dist/decorators/resolver.decorator.d.ts +2 -0
- package/dist/decorators/resolver.decorator.js +14 -0
- package/dist/decorators/resolver.decorator.js.map +1 -0
- package/dist/decorators/resource.decorator.d.ts +18 -0
- package/dist/decorators/resource.decorator.js +16 -0
- package/dist/decorators/resource.decorator.js.map +1 -0
- package/dist/decorators/tool.decorator.d.ts +30 -0
- package/dist/decorators/tool.decorator.js +16 -0
- package/dist/decorators/tool.decorator.js.map +1 -0
- package/dist/decorators/user-guard.decorator.d.ts +2 -0
- package/dist/decorators/user-guard.decorator.js +10 -0
- package/dist/decorators/user-guard.decorator.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/context.interface.d.ts +11 -0
- package/dist/interfaces/context.interface.js +3 -0
- package/dist/interfaces/context.interface.js.map +1 -0
- package/dist/interfaces/index.d.ts +1 -0
- package/dist/interfaces/index.js +18 -0
- package/dist/interfaces/index.js.map +1 -0
- package/dist/mcp-core.module.d.ts +11 -0
- package/dist/mcp-core.module.js +218 -0
- package/dist/mcp-core.module.js.map +1 -0
- package/dist/mcp.constants.d.ts +6 -0
- package/dist/mcp.constants.js +10 -0
- package/dist/mcp.constants.js.map +1 -0
- package/dist/mcp.module.d.ts +11 -0
- package/dist/mcp.module.js +40 -0
- package/dist/mcp.module.js.map +1 -0
- package/dist/mcp.types.d.ts +62 -0
- package/dist/mcp.types.js +3 -0
- package/dist/mcp.types.js.map +1 -0
- package/dist/services/discovery.service.d.ts +16 -0
- package/dist/services/discovery.service.js +85 -0
- package/dist/services/discovery.service.js.map +1 -0
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.js +20 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/logger.service.d.ts +16 -0
- package/dist/services/logger.service.js +98 -0
- package/dist/services/logger.service.js.map +1 -0
- package/dist/services/registry.service.d.ts +23 -0
- package/dist/services/registry.service.js +284 -0
- package/dist/services/registry.service.js.map +1 -0
- package/dist/services/session.manager.d.ts +18 -0
- package/dist/services/session.manager.js +46 -0
- package/dist/services/session.manager.js.map +1 -0
- package/dist/transports/sse/index.d.ts +2 -0
- package/dist/transports/sse/index.js +19 -0
- package/dist/transports/sse/index.js.map +1 -0
- package/dist/transports/sse/sse.controller.d.ts +3 -0
- package/dist/transports/sse/sse.controller.js +61 -0
- package/dist/transports/sse/sse.controller.js.map +1 -0
- package/dist/transports/sse/sse.service.d.ts +29 -0
- package/dist/transports/sse/sse.service.js +174 -0
- package/dist/transports/sse/sse.service.js.map +1 -0
- package/dist/transports/streamable/index.d.ts +2 -0
- package/dist/transports/streamable/index.js +19 -0
- package/dist/transports/streamable/index.js.map +1 -0
- package/dist/transports/streamable/streamable.controller.d.ts +3 -0
- package/dist/transports/streamable/streamable.controller.js +66 -0
- package/dist/transports/streamable/streamable.controller.js.map +1 -0
- package/dist/transports/streamable/streamable.service.d.ts +30 -0
- package/dist/transports/streamable/streamable.service.js +238 -0
- package/dist/transports/streamable/streamable.service.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/types/handler-args.types.d.ts +23 -0
- package/dist/types/handler-args.types.js +3 -0
- package/dist/types/handler-args.types.js.map +1 -0
- package/package.json +151 -0
package/README.md
ADDED
|
@@ -0,0 +1,1065 @@
|
|
|
1
|
+
# MCP Server NestJS Module Library <!-- omit in toc -->
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@nestjs-mcp/server)
|
|
4
|
+
[](https://github.com/semantic-release/semantic-release)
|
|
5
|
+
[](https://www.npmjs.com/package/@nestjs-mcp/server)
|
|
6
|
+
[](https://github.com/adrian-d-hidalgo/nestjs-mcp-server/actions/workflows/ci.yml)
|
|
7
|
+
[](https://codecov.io/gh/adrian-d-hidalgo/nestjs-mcp-server)
|
|
8
|
+
[](https://snyk.io/test/github/adrian-d-hidalgo/nestjs-mcp-server)
|
|
9
|
+
[](./LICENSE)
|
|
10
|
+
[](./CONTRIBUTING.md)
|
|
11
|
+
[](CODE_OF_CONDUCT.md)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Overview <!-- omit in toc -->
|
|
16
|
+
|
|
17
|
+
**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.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Table of Contents <!-- omit in toc -->
|
|
22
|
+
|
|
23
|
+
- [Installation](#installation)
|
|
24
|
+
- [Quickstart](#quickstart)
|
|
25
|
+
- [What is MCP?](#what-is-mcp)
|
|
26
|
+
- [Core Concepts](#core-concepts)
|
|
27
|
+
- [Server](#server)
|
|
28
|
+
- [Resource](#resource)
|
|
29
|
+
- [Tool](#tool)
|
|
30
|
+
- [Prompt](#prompt)
|
|
31
|
+
- [Module API](#module-api)
|
|
32
|
+
- [`McpModule.forRoot`](#mcpmoduleforroot)
|
|
33
|
+
- [`McpModule.forRootAsync`](#mcpmoduleforrootasync)
|
|
34
|
+
- [`McpModule.forFeature`](#mcpmoduleforfeature)
|
|
35
|
+
- [Module Usage](#module-usage)
|
|
36
|
+
- [1. Global Registration with `McpModule.forRoot`](#1-global-registration-with-mcpmoduleforroot)
|
|
37
|
+
- [2. Feature Module Registration with `McpModule.forFeature`](#2-feature-module-registration-with-mcpmoduleforfeature)
|
|
38
|
+
- [Capabilities](#capabilities)
|
|
39
|
+
- [Resolver Decorator](#resolver-decorator)
|
|
40
|
+
- [Prompt Decorator](#prompt-decorator)
|
|
41
|
+
- [Resource Decorator](#resource-decorator)
|
|
42
|
+
- [Tool Decorator](#tool-decorator)
|
|
43
|
+
- [Tool Annotations](#tool-annotations)
|
|
44
|
+
- [ToolOptions Variants](#tooloptions-variants)
|
|
45
|
+
- [RequestHandlerExtra Argument](#requesthandlerextra-argument)
|
|
46
|
+
- [Guards](#guards)
|
|
47
|
+
- [Global-level guards](#global-level-guards)
|
|
48
|
+
- [Resolver-level guards](#resolver-level-guards)
|
|
49
|
+
- [Method-level guards](#method-level-guards)
|
|
50
|
+
- [Guard Example](#guard-example)
|
|
51
|
+
- [MCP Execution Context](#mcp-execution-context)
|
|
52
|
+
- [Guards with Dependency Injection](#guards-with-dependency-injection)
|
|
53
|
+
- [Session Management](#session-management)
|
|
54
|
+
- [Session Management Options](#session-management-options)
|
|
55
|
+
- [Transport Options](#transport-options)
|
|
56
|
+
- [Inspector Playground](#inspector-playground)
|
|
57
|
+
- [Examples](#examples)
|
|
58
|
+
- [Changelog](#changelog)
|
|
59
|
+
- [License](#license)
|
|
60
|
+
- [Contributions](#contributions)
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
npm install @nestjs-mcp/server @modelcontextprotocol/sdk zod
|
|
68
|
+
# or
|
|
69
|
+
yarn add @nestjs-mcp/server @modelcontextprotocol/sdk zod
|
|
70
|
+
# or
|
|
71
|
+
pnpm add @nestjs-mcp/server @modelcontextprotocol/sdk zod
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Quickstart
|
|
77
|
+
|
|
78
|
+
Register the MCP module in your NestJS app and expose a simple tool:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { Module } from '@nestjs/common';
|
|
82
|
+
|
|
83
|
+
import { CallToolResult } from '@modelcontextprotocol/sdk/types';
|
|
84
|
+
|
|
85
|
+
import { Resolver, Tool, McpModule } from '@nestjs-mcp/server';
|
|
86
|
+
|
|
87
|
+
@Resolver()
|
|
88
|
+
export class HealthResolver {
|
|
89
|
+
/**
|
|
90
|
+
* Simple health check tool
|
|
91
|
+
*/
|
|
92
|
+
@Tool({ name: 'server_health_check' })
|
|
93
|
+
healthCheck(): CallToolResult {
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: 'text',
|
|
98
|
+
text: 'Server is operational. All systems running normally.',
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@Module({
|
|
106
|
+
imports: [
|
|
107
|
+
McpModule.forRoot({
|
|
108
|
+
name: 'My MCP Server',
|
|
109
|
+
version: '1.0.0',
|
|
110
|
+
}),
|
|
111
|
+
],
|
|
112
|
+
providers: [HealthResolver],
|
|
113
|
+
})
|
|
114
|
+
export class AppModule {}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## What is MCP?
|
|
120
|
+
|
|
121
|
+
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.
|
|
122
|
+
|
|
123
|
+
- See the [Anthropic announcement](https://www.anthropic.com/news/model-context-protocol) for more background.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Core Concepts
|
|
128
|
+
|
|
129
|
+
### Server
|
|
130
|
+
|
|
131
|
+
The MCP Server is the main entry point for exposing capabilities to LLMs. It manages the registration and discovery of resources, tools, and prompts.
|
|
132
|
+
|
|
133
|
+
### Resource
|
|
134
|
+
|
|
135
|
+
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.
|
|
136
|
+
|
|
137
|
+
- Learn more: [MCP Resources documentation](https://modelcontextprotocol.io/docs/concepts/resources)
|
|
138
|
+
|
|
139
|
+
### Tool
|
|
140
|
+
|
|
141
|
+
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.
|
|
142
|
+
|
|
143
|
+
- Learn more: [MCP Tools documentation](https://modelcontextprotocol.io/docs/concepts/tools)
|
|
144
|
+
|
|
145
|
+
### Prompt
|
|
146
|
+
|
|
147
|
+
A Prompt defines a conversational flow, template, or interaction pattern for LLMs. Prompts help guide the model's behavior in specific scenarios.
|
|
148
|
+
|
|
149
|
+
- Learn more: [MCP Prompts documentation](https://modelcontextprotocol.io/docs/concepts/prompts)
|
|
150
|
+
|
|
151
|
+
> **See the [Capabilities](#capabilities) section for implementation details and code examples.**
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Module API
|
|
156
|
+
|
|
157
|
+
### `McpModule.forRoot`
|
|
158
|
+
|
|
159
|
+
Registers the MCP Server globally in your NestJS application.
|
|
160
|
+
|
|
161
|
+
**Parameters:**
|
|
162
|
+
|
|
163
|
+
- `options: McpModuleOptions` — Main server configuration object:
|
|
164
|
+
- `name: string`: The name of your MCP server.
|
|
165
|
+
- `version: string`: The version of your MCP server.
|
|
166
|
+
- `instructions?: string`: Optional description of the MCP server for the client.
|
|
167
|
+
- `capabilities?: Record<string, unknown>`: Optional additional capabilities metadata.
|
|
168
|
+
- `providers?: Provider[]`: Optional array of NestJS providers to include in the module.
|
|
169
|
+
- `imports?: any[]`: Optional array of NestJS modules to import.
|
|
170
|
+
- `logging?: McpLoggingOptions`: Optional logging configuration:
|
|
171
|
+
- `enabled?: boolean` (default: `true`): Enable/disable logging.
|
|
172
|
+
- `level?: 'error' | 'warn' | 'log' | 'debug' | 'verbose'` (default: `'verbose'`): Set the logging level.
|
|
173
|
+
- `transports?: McpModuleTransportOptions`: Optional transport configuration (see [Transport Options](#transport-options)).
|
|
174
|
+
- `protocolOptions?: Record<string, unknown>`: Optional parameters passed directly to the underlying `@modelcontextprotocol/sdk` server instance.
|
|
175
|
+
|
|
176
|
+
**Returns:**
|
|
177
|
+
|
|
178
|
+
- A dynamic NestJS module with all MCP providers registered.
|
|
179
|
+
|
|
180
|
+
**Example:**
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
import { Module } from '@nestjs/common';
|
|
184
|
+
import { McpModule } from '@nestjs-mcp/server';
|
|
185
|
+
|
|
186
|
+
@Module({
|
|
187
|
+
imports: [
|
|
188
|
+
McpModule.forRoot({
|
|
189
|
+
name: 'My Server',
|
|
190
|
+
version: '1.0.0',
|
|
191
|
+
instructions: 'A server providing utility tools and data.',
|
|
192
|
+
logging: { level: 'log' },
|
|
193
|
+
transports: { sse: { enabled: false } }, // Disable SSE transport
|
|
194
|
+
// ...other MCP options
|
|
195
|
+
}),
|
|
196
|
+
],
|
|
197
|
+
})
|
|
198
|
+
export class AppModule {}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### `McpModule.forRootAsync`
|
|
202
|
+
|
|
203
|
+
Registers the MCP Server globally using asynchronous options, useful for integrating with configuration modules like `@nestjs/config`.
|
|
204
|
+
|
|
205
|
+
> **Note:**
|
|
206
|
+
>
|
|
207
|
+
> - The `imports` array should include any modules that provide dependencies required by your `useFactory` (e.g., `ConfigModule` if you inject `ConfigService`).
|
|
208
|
+
> - Use `forRootAsync` only once in your root module (`AppModule`).
|
|
209
|
+
> - See `McpModuleAsyncOptions` for all available options.
|
|
210
|
+
|
|
211
|
+
**Parameters:**
|
|
212
|
+
|
|
213
|
+
- `options: McpModuleAsyncOptions` — Asynchronous configuration object:
|
|
214
|
+
- `imports?: any[]`: Optional modules to import before the factory runs.
|
|
215
|
+
- `useFactory: (...args: any[]) => Promise<McpModuleOptions> | McpModuleOptions`: A factory function that returns the `McpModuleOptions`.
|
|
216
|
+
- `inject?: any[]`: Optional providers to inject into the `useFactory`.
|
|
217
|
+
|
|
218
|
+
**Returns:**
|
|
219
|
+
|
|
220
|
+
- A dynamic NestJS module.
|
|
221
|
+
|
|
222
|
+
**Example (with ConfigModule):**
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { Module } from '@nestjs/common';
|
|
226
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
227
|
+
import { McpModule } from '@nestjs-mcp/server';
|
|
228
|
+
|
|
229
|
+
@Module({
|
|
230
|
+
imports: [
|
|
231
|
+
ConfigModule.forRoot(), // Make sure ConfigModule is imported
|
|
232
|
+
McpModule.forRootAsync({
|
|
233
|
+
imports: [ConfigModule], // Import ConfigModule here too
|
|
234
|
+
useFactory: (configService: ConfigService) => ({
|
|
235
|
+
name: configService.get<string>('MCP_SERVER_NAME', 'Default Server'),
|
|
236
|
+
version: configService.get<string>('MCP_SERVER_VERSION', '1.0.0'),
|
|
237
|
+
instructions: configService.get<string>('MCP_SERVER_DESC'),
|
|
238
|
+
logging: {
|
|
239
|
+
level: configService.get('MCP_LOG_LEVEL', 'verbose'),
|
|
240
|
+
},
|
|
241
|
+
// ... other options from configService
|
|
242
|
+
}),
|
|
243
|
+
inject: [ConfigService], // Inject ConfigService into the factory
|
|
244
|
+
}),
|
|
245
|
+
],
|
|
246
|
+
})
|
|
247
|
+
export class AppModule {}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### `McpModule.forFeature`
|
|
251
|
+
|
|
252
|
+
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.
|
|
253
|
+
|
|
254
|
+
**Parameters:**
|
|
255
|
+
|
|
256
|
+
- `options?: McpFeatureOptions` (Currently unused, reserved for future enhancements).
|
|
257
|
+
|
|
258
|
+
**Returns:**
|
|
259
|
+
|
|
260
|
+
- A dynamic module.
|
|
261
|
+
|
|
262
|
+
**Example:**
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
// src/status/status.resolver.ts
|
|
266
|
+
import { Resolver, Tool } from '@nestjs-mcp/server';
|
|
267
|
+
import { CallToolResult } from '@modelcontextprotocol/sdk/types';
|
|
268
|
+
|
|
269
|
+
@Resolver('status')
|
|
270
|
+
export class StatusResolver {
|
|
271
|
+
@Tool({ name: 'health_check' })
|
|
272
|
+
healthCheck(): CallToolResult {
|
|
273
|
+
return { content: [{ type: 'text', text: 'OK' }] };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/status/status.module.ts
|
|
278
|
+
import { Module } from '@nestjs/common';
|
|
279
|
+
import { McpModule } from '@nestjs-mcp/server';
|
|
280
|
+
import { StatusResolver } from './status.resolver';
|
|
281
|
+
|
|
282
|
+
@Module({
|
|
283
|
+
imports: [McpModule.forFeature()], // Import forFeature here
|
|
284
|
+
providers: [StatusResolver], // Register your resolver
|
|
285
|
+
})
|
|
286
|
+
export class StatusModule {}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Module Usage
|
|
292
|
+
|
|
293
|
+
This library provides two main ways to register MCP capabilities in your NestJS application:
|
|
294
|
+
|
|
295
|
+
### 1. Global Registration with `McpModule.forRoot`
|
|
296
|
+
|
|
297
|
+
Use `McpModule.forRoot` in your root application module to configure and register the MCP server globally. This is required for every MCP server application.
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
import { Module } from '@nestjs/common';
|
|
301
|
+
import { McpModule } from '@nestjs-mcp/server';
|
|
302
|
+
import { PromptsResolver } from './prompts.resolver';
|
|
303
|
+
|
|
304
|
+
@Module({
|
|
305
|
+
imports: [
|
|
306
|
+
McpModule.forRoot({
|
|
307
|
+
name: 'My MCP Server',
|
|
308
|
+
version: '1.0.0',
|
|
309
|
+
// ...other MCP options
|
|
310
|
+
}),
|
|
311
|
+
],
|
|
312
|
+
providers: [PromptsResolver],
|
|
313
|
+
})
|
|
314
|
+
export class AppModule {}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### 2. Feature Module Registration with `McpModule.forFeature`
|
|
318
|
+
|
|
319
|
+
Use `McpModule.forFeature` in feature modules to register additional resolvers, tools, or resources. This is useful for organizing large servers into multiple modules.
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
import { Module } from '@nestjs/common';
|
|
323
|
+
import { McpModule } from '@nestjs-mcp/server';
|
|
324
|
+
|
|
325
|
+
import { ToolsResolver } from './tools.resolver';
|
|
326
|
+
|
|
327
|
+
@Module({
|
|
328
|
+
imports: [McpModule.forFeature()],
|
|
329
|
+
providers: [ToolsResolver],
|
|
330
|
+
})
|
|
331
|
+
export class ToolsModule {}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
- Use `forRoot` or `forRootAsync` **only once** in your root module (`AppModule`).
|
|
335
|
+
- Use `forFeature` in any feature module where you define MCP capabilities (`@Resolver` classes).
|
|
336
|
+
- Ensure all Resolvers are listed in the `providers` array of their respective modules.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Capabilities
|
|
341
|
+
|
|
342
|
+
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.
|
|
343
|
+
|
|
344
|
+
### Resolver Decorator
|
|
345
|
+
|
|
346
|
+
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`.
|
|
347
|
+
|
|
348
|
+
- **No `@Injectable()` Needed:** Resolver classes are automatically treated as providers by the MCP module and **do not** require the `@Injectable()` decorator.
|
|
349
|
+
- **Dependency Injection:** Standard NestJS dependency injection works within Resolver constructors.
|
|
350
|
+
- **Namespacing:** You can optionally provide a string argument to `@Resolver('my_namespace')` to namespace the capabilities within that resolver.
|
|
351
|
+
- **Guards:** Guards can be applied at the class level using `@UseGuards()`.
|
|
352
|
+
|
|
353
|
+
**Example:**
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
import { Resolver, Prompt, Resource, Tool } from '@nestjs-mcp/server';
|
|
357
|
+
// Import any services you need to inject
|
|
358
|
+
import { SomeService } from '../some.service';
|
|
359
|
+
|
|
360
|
+
@Resolver('workspace') // No @Injectable()
|
|
361
|
+
export class MyResolver {
|
|
362
|
+
// Inject dependencies as usual
|
|
363
|
+
constructor(private readonly someService: SomeService) {}
|
|
364
|
+
|
|
365
|
+
@Prompt({ name: 'greet_user' }) // Capabilities must be inside a Resolver
|
|
366
|
+
greetPrompt(/*...args...*/) {
|
|
367
|
+
const greeting = this.someService.getGreeting();
|
|
368
|
+
/* ... */
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
@Resource({ name: 'user_profile', uri: 'user://{id}' })
|
|
372
|
+
getUserResource(/*...args...*/) {
|
|
373
|
+
/* ... */
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
@Tool({ name: 'calculate_sum' })
|
|
377
|
+
sumTool(/*...args...*/) {
|
|
378
|
+
/* ... */
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
You can also apply guards at the resolver level:
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
import { UseGuards, Resolver } from '@nestjs-mcp/server';
|
|
387
|
+
import { MyGuard } from './guards/my.guard';
|
|
388
|
+
|
|
389
|
+
@UseGuards(MyGuard) // Applied to all capabilities in this Resolver
|
|
390
|
+
@Resolver('secure') // No @Injectable()
|
|
391
|
+
export class SecureResolver {
|
|
392
|
+
// All capabilities in this resolver will use MyGuard
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Prompt Decorator
|
|
397
|
+
|
|
398
|
+
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`.**
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
import { Prompt, Resolver } from '@nestjs-mcp/server';
|
|
402
|
+
import { RequestHandlerExtra } from '@nestjs-mcp/server'; // Import type for extra info
|
|
403
|
+
import { z } from 'zod'; // Example if using Zod schema
|
|
404
|
+
|
|
405
|
+
// Optional: Define schema if needed
|
|
406
|
+
// const SummaryArgs = z.object({ topic: z.string() });
|
|
407
|
+
|
|
408
|
+
@Resolver('prompts') // Must be in a Resolver class
|
|
409
|
+
export class MyPrompts {
|
|
410
|
+
@Prompt({
|
|
411
|
+
name: 'generate_summary',
|
|
412
|
+
description: 'Generates a summary for the given text.',
|
|
413
|
+
// argsSchema: SummaryArgs
|
|
414
|
+
})
|
|
415
|
+
generateSummaryPrompt(
|
|
416
|
+
// params: z.infer<typeof SummaryArgs>, // Arguments based on argsSchema (if defined)
|
|
417
|
+
extra: RequestHandlerExtra, // Contains sessionId and other metadata
|
|
418
|
+
) {
|
|
419
|
+
console.log(`Generating summary for session: ${extra.sessionId}`);
|
|
420
|
+
/* ... return CallPromptResult ... */
|
|
421
|
+
return { content: [{ type: 'text', text: 'Summary generated.' }] };
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Resource Decorator
|
|
427
|
+
|
|
428
|
+
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`.**
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
import { Resource, Resolver } from '@nestjs-mcp/server';
|
|
432
|
+
import { RequestHandlerExtra } from '@nestjs-mcp/server'; // Import type for extra info
|
|
433
|
+
import { URL } from 'url'; // Type for URI resource
|
|
434
|
+
import { z } from 'zod'; // Example if using Zod template
|
|
435
|
+
|
|
436
|
+
// Optional: Define template schema if needed
|
|
437
|
+
// const DocQueryTemplate = z.object({ query: z.string() });
|
|
438
|
+
|
|
439
|
+
@Resolver('data') // Must be in a Resolver class
|
|
440
|
+
export class MyResources {
|
|
441
|
+
@Resource({
|
|
442
|
+
name: 'user_profile',
|
|
443
|
+
uri: 'user://profiles/{userId}',
|
|
444
|
+
// metadata: { description: '...' } // Optional
|
|
445
|
+
})
|
|
446
|
+
getUserProfile(
|
|
447
|
+
uri: URL, // First argument is the parsed URI
|
|
448
|
+
// metadata: Record<string, any> // Second argument if is defined
|
|
449
|
+
extra: RequestHandlerExtra, // Contains sessionId and other metadata
|
|
450
|
+
) {
|
|
451
|
+
const userId = uri.pathname.split('/').pop(); // Example: Extract ID from URI
|
|
452
|
+
console.log(`Fetching profile for ${userId}, session: ${extra.sessionId}`);
|
|
453
|
+
/* ... return CallResourceResult ... */
|
|
454
|
+
return { content: [{ type: 'text', text: `Profile data for ${userId}` }] };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
@Resource({
|
|
458
|
+
name: 'document_list',
|
|
459
|
+
template: { type: 'string', description: 'Document content query' }, // Simple template example
|
|
460
|
+
// metadata: { list: true } // Optional
|
|
461
|
+
})
|
|
462
|
+
findDocuments(
|
|
463
|
+
uri: URL, // First arg based on simple template type
|
|
464
|
+
variables: Record<string, string>, // Second arg is path params (if any)
|
|
465
|
+
extra: RequestHandlerExtra, // Contains sessionId and other metadata
|
|
466
|
+
) {
|
|
467
|
+
console.log(
|
|
468
|
+
`Finding documents matching '${query}', session: ${extra.sessionId}`,
|
|
469
|
+
);
|
|
470
|
+
/* ... return CallResourceResult ... */
|
|
471
|
+
return { content: [{ type: 'text', text: 'List of documents.' }] };
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Tool Decorator
|
|
477
|
+
|
|
478
|
+
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`.**
|
|
479
|
+
|
|
480
|
+
```ts
|
|
481
|
+
import { Tool, Resolver } from '@nestjs-mcp/server';
|
|
482
|
+
import { RequestHandlerExtra } from '@nestjs-mcp/server';
|
|
483
|
+
import { z } from 'zod';
|
|
484
|
+
import { CallToolResult } from '@modelcontextprotocol/sdk/types';
|
|
485
|
+
|
|
486
|
+
@Resolver('user_tools')
|
|
487
|
+
export class UserToolsResolver {
|
|
488
|
+
@Tool({
|
|
489
|
+
name: 'delete_user',
|
|
490
|
+
description: 'Deletes a user by ID',
|
|
491
|
+
paramsSchema: { userId: z.string() },
|
|
492
|
+
annotations: { destructiveHint: true, readOnlyHint: false },
|
|
493
|
+
})
|
|
494
|
+
deleteUser(
|
|
495
|
+
{ userId }: { userId: string },
|
|
496
|
+
extra: RequestHandlerExtra,
|
|
497
|
+
): CallToolResult {
|
|
498
|
+
// ...logic...
|
|
499
|
+
return { content: [{ type: 'text', text: `User ${userId} deleted.` }] };
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
#### Tool Annotations
|
|
505
|
+
|
|
506
|
+
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.
|
|
507
|
+
|
|
508
|
+
**Common annotation keys:**
|
|
509
|
+
|
|
510
|
+
- `destructiveHint` (boolean): Indicates the tool performs a destructive action (e.g., deletes data).
|
|
511
|
+
- `readOnlyHint` (boolean): Indicates the tool does not modify any data.
|
|
512
|
+
- `idempotentHint` (boolean): Indicates the tool can be safely called multiple times with the same effect.
|
|
513
|
+
- `openWorldHint` (boolean): Indicates the tool may have side effects outside the current system.
|
|
514
|
+
|
|
515
|
+
**Example:**
|
|
516
|
+
|
|
517
|
+
```ts
|
|
518
|
+
@Tool({
|
|
519
|
+
name: 'reset_password',
|
|
520
|
+
paramsSchema: { userId: z.string() },
|
|
521
|
+
annotations: { destructiveHint: true, idempotentHint: false }
|
|
522
|
+
})
|
|
523
|
+
resetPassword({ userId }: { userId: string }): CallToolResult {
|
|
524
|
+
// ...
|
|
525
|
+
}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
#### ToolOptions Variants
|
|
529
|
+
|
|
530
|
+
| Variant | Required Fields |
|
|
531
|
+
| ------------------------------------------------ | -------------------------------------------- |
|
|
532
|
+
| ToolBaseOptions | name |
|
|
533
|
+
| ToolWithDescriptionOptions | name, description |
|
|
534
|
+
| ToolWithParamOrAnnotationsOptions | name, paramsSchemaOrAnnotations |
|
|
535
|
+
| ToolWithParamOrAnnotationsAndDescriptionOptions | name, paramsSchemaOrAnnotations, description |
|
|
536
|
+
| ToolWithParamAndAnnotationsOptions | name, paramsSchema, annotations |
|
|
537
|
+
| ToolWithParamAndAnnotationsAndDescriptionOptions | name, paramsSchema, annotations, description |
|
|
538
|
+
|
|
539
|
+
- `paramsSchema` and `paramsSchemaOrAnnotations` can be a Zod schema for input validation.
|
|
540
|
+
- `annotations` is an object with protocol-level hints as described above.
|
|
541
|
+
|
|
542
|
+
### RequestHandlerExtra Argument
|
|
543
|
+
|
|
544
|
+
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.
|
|
545
|
+
|
|
546
|
+
**Properties from SDK:**
|
|
547
|
+
|
|
548
|
+
- `signal`: An `AbortSignal` used to communicate if the request was cancelled
|
|
549
|
+
- `authInfo`: Optional information about a validated access token
|
|
550
|
+
- `sessionId`: The session ID from the transport, if available
|
|
551
|
+
- `sendNotification`: Function to send a notification related to the current request
|
|
552
|
+
- `sendRequest`: Function to send a request related to the current request
|
|
553
|
+
|
|
554
|
+
**Extended Properties:**
|
|
555
|
+
|
|
556
|
+
- `headers`: HTTP headers from the original request (added by @nestjs-mcp/server)
|
|
557
|
+
|
|
558
|
+
**Usage Example:**
|
|
559
|
+
|
|
560
|
+
```ts
|
|
561
|
+
import { Tool, Resolver, SessionManager } from '@nestjs-mcp/server';
|
|
562
|
+
import { RequestHandlerExtra } from '@nestjs-mcp/server';
|
|
563
|
+
import { CallToolResult } from '@modelcontextprotocol/sdk/types';
|
|
564
|
+
|
|
565
|
+
@Resolver('auth')
|
|
566
|
+
export class AuthResolver {
|
|
567
|
+
@Tool({
|
|
568
|
+
name: 'authenticate_user',
|
|
569
|
+
description: 'Authenticates a user with credentials',
|
|
570
|
+
// ...other options
|
|
571
|
+
})
|
|
572
|
+
authenticateUser(
|
|
573
|
+
params: { username: string; password: string },
|
|
574
|
+
extra: RequestHandlerExtra, // Always the last parameter
|
|
575
|
+
): CallToolResult {
|
|
576
|
+
// Access the session ID
|
|
577
|
+
console.log(`Request received in session: ${extra.sessionId}`);
|
|
578
|
+
|
|
579
|
+
// Access request headers (extended property)
|
|
580
|
+
const authHeader = extra.headers.authorization;
|
|
581
|
+
const userAgent = extra.headers['user-agent'];
|
|
582
|
+
console.log(`Request from: ${userAgent}`);
|
|
583
|
+
|
|
584
|
+
// Check if request was cancelled
|
|
585
|
+
if (extra.signal.aborted) {
|
|
586
|
+
return {
|
|
587
|
+
content: [{ type: 'text', text: 'Request was cancelled' }],
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Implement authentication logic
|
|
592
|
+
return {
|
|
593
|
+
content: [{ type: 'text', text: 'Authentication successful' }],
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
**Important Notes:**
|
|
600
|
+
|
|
601
|
+
- `extra` is always the last parameter in any method decorated with `@Resource`, `@Prompt`, or `@Tool`
|
|
602
|
+
- The `headers` property is an extension added by @nestjs-mcp/server to access HTTP headers directly
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
606
|
+
## Guards
|
|
607
|
+
|
|
608
|
+
Apply one or more guards to a Resolver, to individual methods, or globally. Guards must implement the NestJS `CanActivate` interface.
|
|
609
|
+
|
|
610
|
+
### Global-level guards
|
|
611
|
+
|
|
612
|
+
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.
|
|
613
|
+
|
|
614
|
+
```ts
|
|
615
|
+
// src/guards/global-auth.guard.ts
|
|
616
|
+
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
|
617
|
+
import { Request } from 'express';
|
|
618
|
+
|
|
619
|
+
@Injectable()
|
|
620
|
+
export class GlobalAuthGuard implements CanActivate {
|
|
621
|
+
canActivate(context: ExecutionContext): boolean {
|
|
622
|
+
const request = context.switchToHttp().getRequest<Request>();
|
|
623
|
+
const apiKey = request.headers['x-api-key'];
|
|
624
|
+
// Example: Check for a valid API key
|
|
625
|
+
return !!apiKey && apiKey === 'EXPECTED_KEY';
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
Register the guard globally in your main module:
|
|
631
|
+
|
|
632
|
+
```ts
|
|
633
|
+
// src/app.module.ts
|
|
634
|
+
import { Module } from '@nestjs/common';
|
|
635
|
+
import { APP_GUARD } from '@nestjs/core';
|
|
636
|
+
import { McpModule } from '@nestjs-mcp/server';
|
|
637
|
+
import { GlobalAuthGuard } from './guards/global-auth.guard';
|
|
638
|
+
|
|
639
|
+
@Module({
|
|
640
|
+
imports: [McpModule.forRoot(/*...*/)],
|
|
641
|
+
providers: [
|
|
642
|
+
{
|
|
643
|
+
provide: APP_GUARD,
|
|
644
|
+
useClass: GlobalAuthGuard,
|
|
645
|
+
},
|
|
646
|
+
],
|
|
647
|
+
})
|
|
648
|
+
export class AppModule {}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### Resolver-level guards
|
|
652
|
+
|
|
653
|
+
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.
|
|
654
|
+
|
|
655
|
+
```ts
|
|
656
|
+
import { UseGuards, Resolver, Prompt } from '@nestjs-mcp/server';
|
|
657
|
+
import { RoleGuard } from './guards/role.guard';
|
|
658
|
+
|
|
659
|
+
@UseGuards(RoleGuard)
|
|
660
|
+
@Resolver('admin')
|
|
661
|
+
export class AdminResolver {
|
|
662
|
+
@Prompt({ name: 'admin_action' })
|
|
663
|
+
adminAction(/*...*/) {
|
|
664
|
+
/* ... */
|
|
665
|
+
}
|
|
666
|
+
// ... other admin capabilities
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### Method-level guards
|
|
671
|
+
|
|
672
|
+
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.
|
|
673
|
+
|
|
674
|
+
```ts
|
|
675
|
+
import { UseGuards, Resolver, Prompt, Tool } from '@nestjs-mcp/server';
|
|
676
|
+
import { SpecificCheckGuard } from './guards/specific-check.guard';
|
|
677
|
+
|
|
678
|
+
@Resolver('mixed')
|
|
679
|
+
export class MixedResolver {
|
|
680
|
+
@Prompt({ name: 'public_prompt' })
|
|
681
|
+
publicPrompt() {
|
|
682
|
+
/* Publicly accessible */
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
@UseGuards(SpecificCheckGuard)
|
|
686
|
+
@Tool({ name: 'protected_tool' })
|
|
687
|
+
protectedTool(/*...*/) {
|
|
688
|
+
/* Requires SpecificCheckGuard to pass */
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
**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`.
|
|
694
|
+
|
|
695
|
+
### Guard Example
|
|
696
|
+
|
|
697
|
+
A guard for Resolver or Method-level protection:
|
|
698
|
+
|
|
699
|
+
```ts
|
|
700
|
+
// src/guards/my-mcp.guard.ts
|
|
701
|
+
import { CanActivate, Injectable } from '@nestjs/common';
|
|
702
|
+
import { McpExecutionContext, SessionManager } from '@nestjs-mcp/server';
|
|
703
|
+
|
|
704
|
+
@Injectable()
|
|
705
|
+
export class MyMcpGuard implements CanActivate {
|
|
706
|
+
constructor(private readonly sessionManager: SessionManager) {}
|
|
707
|
+
|
|
708
|
+
canActivate(context: McpExecutionContext): boolean {
|
|
709
|
+
const sessionId = context.getSessionId();
|
|
710
|
+
if (!sessionId) return false;
|
|
711
|
+
|
|
712
|
+
const handlerArgs = context.getArgs();
|
|
713
|
+
|
|
714
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
715
|
+
const request = session?.request;
|
|
716
|
+
const userAgent = request?.headers['user-agent'];
|
|
717
|
+
|
|
718
|
+
console.log(`Guard activated for session ${sessionId} from ${userAgent}`);
|
|
719
|
+
console.log('Handler args:', handlerArgs);
|
|
720
|
+
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### MCP Execution Context
|
|
727
|
+
|
|
728
|
+
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:
|
|
729
|
+
|
|
730
|
+
```typescript
|
|
731
|
+
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
|
732
|
+
import { McpExecutionContext, SessionManager } from '@nestjs-mcp/server';
|
|
733
|
+
import { Request } from 'express';
|
|
734
|
+
|
|
735
|
+
@Injectable()
|
|
736
|
+
export class McpAuthGuard implements CanActivate {
|
|
737
|
+
constructor(private readonly sessionManager: SessionManager) {}
|
|
738
|
+
|
|
739
|
+
canActivate(context: McpExecutionContext): boolean {
|
|
740
|
+
const sessionId = context.getSessionId();
|
|
741
|
+
if (!sessionId) {
|
|
742
|
+
console.error('Guard Error: MCP Session ID not found in context.');
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const handlerArgs = context.getArgs<any>();
|
|
747
|
+
console.log('MCP Handler Arguments:', handlerArgs);
|
|
748
|
+
|
|
749
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
750
|
+
if (!session) {
|
|
751
|
+
console.error(`Guard Error: Session not found for ID: ${sessionId}`);
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
const request = session.request as Request;
|
|
755
|
+
|
|
756
|
+
const authHeader = request.headers.authorization;
|
|
757
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
758
|
+
console.log('Guard Denied: Missing or invalid Bearer token.');
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
761
|
+
const token = authHeader.split(' ')[1];
|
|
762
|
+
const isValidToken = token === 'VALID_TOKEN';
|
|
763
|
+
|
|
764
|
+
if (isValidToken) {
|
|
765
|
+
console.log(`Guard Passed for session ${sessionId} with token.`);
|
|
766
|
+
return true;
|
|
767
|
+
} else {
|
|
768
|
+
console.log(`Guard Denied: Invalid token for session ${sessionId}.`);
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
**Key points for `McpExecutionContext`:**
|
|
776
|
+
|
|
777
|
+
- `getSessionId()`: Retrieves the unique ID for the current MCP session. **Crucial** for relating the guard check to the session state stored by `SessionManager`.
|
|
778
|
+
- 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.
|
|
779
|
+
- 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.
|
|
780
|
+
- `switchToHttp().getResponse()` / `switchToHttp().getNext()`: These will throw errors as the Response object is not directly available or relevant in this context.
|
|
781
|
+
|
|
782
|
+
Use `SessionManager` injected into your guard to fetch the session details (including the original `Request`) based on the `sessionId` obtained from the context.
|
|
783
|
+
|
|
784
|
+
### Guards with Dependency Injection
|
|
785
|
+
|
|
786
|
+
Guards can inject NestJS providers like `SessionManager`. Use `@Injectable()` and register the guard as a provider:
|
|
787
|
+
|
|
788
|
+
```typescript
|
|
789
|
+
@Injectable()
|
|
790
|
+
export class AuthGuard implements CanActivate {
|
|
791
|
+
constructor(private readonly sessionManager: SessionManager) {}
|
|
792
|
+
|
|
793
|
+
canActivate(context: McpExecutionContext): boolean {
|
|
794
|
+
const session = this.sessionManager.getSession(context.getSessionId());
|
|
795
|
+
return !!session?.request.headers.authorization;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
@Module({
|
|
800
|
+
imports: [McpModule.forRoot({ name: 'my-server', version: '1.0.0' })],
|
|
801
|
+
providers: [AuthGuard, MyResolver],
|
|
802
|
+
})
|
|
803
|
+
export class AppModule {}
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
> Guards without `@Injectable()` still work but won't receive injected dependencies.
|
|
807
|
+
|
|
808
|
+
---
|
|
809
|
+
|
|
810
|
+
## Session Management
|
|
811
|
+
|
|
812
|
+
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.
|
|
813
|
+
|
|
814
|
+
**Why is it important?**
|
|
815
|
+
|
|
816
|
+
- **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.
|
|
817
|
+
- **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.
|
|
818
|
+
|
|
819
|
+
**Usage Example (in a Resolver):**
|
|
820
|
+
|
|
821
|
+
Resolvers might need access to the original request, for example, to get user information or API keys passed in headers during the initial connection.
|
|
822
|
+
|
|
823
|
+
```typescript
|
|
824
|
+
import { Tool, Resolver, SessionManager } from '@nestjs-mcp/server';
|
|
825
|
+
import { RequestHandlerExtra } from '@nestjs-mcp/server'; // Provides sessionId
|
|
826
|
+
import { Request } from 'express';
|
|
827
|
+
import { CallToolResult } from '@modelcontextprotocol/sdk/types';
|
|
828
|
+
import { z } from 'zod';
|
|
829
|
+
|
|
830
|
+
const UserToolParams = z.object({
|
|
831
|
+
user_id: z.string().optional(),
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
@Resolver('user_tools') // No @Injectable() needed
|
|
835
|
+
export class UserToolsResolver {
|
|
836
|
+
// Inject SessionManager
|
|
837
|
+
constructor(private readonly sessionManager: SessionManager) {}
|
|
838
|
+
|
|
839
|
+
@Tool({
|
|
840
|
+
name: 'get_user_agent',
|
|
841
|
+
description:
|
|
842
|
+
'Gets the user agent from the original request for the session.',
|
|
843
|
+
paramSchema: UserToolParams,
|
|
844
|
+
})
|
|
845
|
+
getUserAgent(
|
|
846
|
+
params: z.infer<typeof UserToolParams>,
|
|
847
|
+
extra: RequestHandlerExtra, // Get extra info, including sessionId
|
|
848
|
+
): CallToolResult {
|
|
849
|
+
const sessionId = extra.sessionId;
|
|
850
|
+
if (!sessionId) {
|
|
851
|
+
return {
|
|
852
|
+
content: [{ type: 'text', text: 'Error: Session ID missing.' }],
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Use sessionId to get the session from the manager
|
|
857
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
858
|
+
if (!session) {
|
|
859
|
+
return {
|
|
860
|
+
content: [
|
|
861
|
+
{
|
|
862
|
+
type: 'text',
|
|
863
|
+
text: `Error: Session not found for ID: ${sessionId}`,
|
|
864
|
+
},
|
|
865
|
+
],
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Access the original request stored in the session
|
|
870
|
+
const request = session.request as Request;
|
|
871
|
+
const userAgent = request.headers['user-agent'] || 'Unknown';
|
|
872
|
+
|
|
873
|
+
return {
|
|
874
|
+
content: [
|
|
875
|
+
{ type: 'text', text: `Session ${sessionId} User Agent: ${userAgent}` },
|
|
876
|
+
],
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
In this example:
|
|
883
|
+
|
|
884
|
+
1. The `@Tool` method receives `extra: RequestHandlerExtra`, which contains the `sessionId`.
|
|
885
|
+
2. The `SessionManager` is injected into the `UserToolsResolver`.
|
|
886
|
+
3. The `sessionId` is used with `sessionManager.getSession()` to retrieve the session data.
|
|
887
|
+
4. The original `request` object is accessed from the retrieved session data.
|
|
888
|
+
|
|
889
|
+
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.
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
893
|
+
## Transport Options
|
|
894
|
+
|
|
895
|
+
The MCP server can communicate over different transport mechanisms. This library includes built-in support for:
|
|
896
|
+
|
|
897
|
+
1. **Streamable (`/mcp` endpoint):** A common transport using standard HTTP POST requests and responses. Suitable for most request/response interactions. Enabled by default.
|
|
898
|
+
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.
|
|
899
|
+
|
|
900
|
+
You can configure which transports are enabled globally using the `transports` option in `McpModule.forRoot` or `McpModule.forRootAsync`.
|
|
901
|
+
|
|
902
|
+
**Configuration:**
|
|
903
|
+
|
|
904
|
+
```typescript
|
|
905
|
+
import { Module } from '@nestjs/common';
|
|
906
|
+
import { McpModule } from '@nestjs-mcp/server';
|
|
907
|
+
|
|
908
|
+
@Module({
|
|
909
|
+
imports: [
|
|
910
|
+
McpModule.forRoot({
|
|
911
|
+
name: 'My Server',
|
|
912
|
+
version: '1.0.0',
|
|
913
|
+
transports: {
|
|
914
|
+
streamable: { enabled: true }, // Keep streamable enabled (default)
|
|
915
|
+
sse: { enabled: false }, // Disable legacy SSE transport
|
|
916
|
+
},
|
|
917
|
+
}),
|
|
918
|
+
],
|
|
919
|
+
})
|
|
920
|
+
export class AppModule {}
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
**Default Configuration:**
|
|
924
|
+
|
|
925
|
+
If the `transports` option is omitted, both `streamable` (`/mcp`) and `sse` (`/sse`) are enabled by default.
|
|
926
|
+
|
|
927
|
+
```typescript
|
|
928
|
+
import { Module } from '@nestjs/common';
|
|
929
|
+
import { McpModule } from '@nestjs-mcp/server';
|
|
930
|
+
|
|
931
|
+
@Module({
|
|
932
|
+
imports: [
|
|
933
|
+
McpModule.forRoot({
|
|
934
|
+
name: 'My Server',
|
|
935
|
+
version: '1.0.0',
|
|
936
|
+
// Both streamable and sse will be enabled
|
|
937
|
+
}),
|
|
938
|
+
],
|
|
939
|
+
})
|
|
940
|
+
export class AppModule {}
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
Disabling unused transports can slightly reduce the application's surface area and resource usage.
|
|
944
|
+
|
|
945
|
+
---
|
|
946
|
+
|
|
947
|
+
## Session Management Options
|
|
948
|
+
|
|
949
|
+
Configure session timeouts, cleanup intervals, and resource limits to optimize your server for production workloads.
|
|
950
|
+
|
|
951
|
+
**Configuration:**
|
|
952
|
+
|
|
953
|
+
```typescript
|
|
954
|
+
import { Module } from '@nestjs/common';
|
|
955
|
+
import { McpModule } from '@nestjs-mcp/server';
|
|
956
|
+
|
|
957
|
+
@Module({
|
|
958
|
+
imports: [
|
|
959
|
+
McpModule.forRoot({
|
|
960
|
+
name: 'My Server',
|
|
961
|
+
version: '1.0.0',
|
|
962
|
+
session: {
|
|
963
|
+
sessionTimeoutMs: 1800000, // 30 minutes (default)
|
|
964
|
+
cleanupIntervalMs: 300000, // 5 minutes (default)
|
|
965
|
+
maxConcurrentSessions: 1000, // Max sessions (default)
|
|
966
|
+
},
|
|
967
|
+
}),
|
|
968
|
+
],
|
|
969
|
+
})
|
|
970
|
+
export class AppModule {}
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
**Configuration Options:**
|
|
974
|
+
|
|
975
|
+
| Option | Type | Default | Description |
|
|
976
|
+
| ------------------------- | -------- | --------------------- | ---------------------------------------------------- |
|
|
977
|
+
| `sessionTimeoutMs` | `number` | `1800000` (30 min) | Maximum inactivity time before session cleanup |
|
|
978
|
+
| `cleanupIntervalMs` | `number` | `300000` (5 min) | Frequency of cleanup job execution |
|
|
979
|
+
| `maxConcurrentSessions` | `number` | `1000` | Maximum concurrent sessions allowed |
|
|
980
|
+
|
|
981
|
+
**How It Works:**
|
|
982
|
+
|
|
983
|
+
- **Activity Tracking**: Each session's `lastActivity` timestamp updates on every request
|
|
984
|
+
- **Cleanup Job**: Runs every `cleanupIntervalMs` to close and remove inactive sessions
|
|
985
|
+
- **Session Limit**: New connections are rejected (503) when `maxConcurrentSessions` is reached
|
|
986
|
+
|
|
987
|
+
**Production Recommendations:**
|
|
988
|
+
|
|
989
|
+
- **High-traffic servers**: Increase `maxConcurrentSessions` (2000-5000) and decrease `cleanupIntervalMs` (2-3 min)
|
|
990
|
+
- **Low-memory environments**: Decrease `maxConcurrentSessions` (100-500) and `sessionTimeoutMs` (10-15 min)
|
|
991
|
+
- **Long-running workflows**: Increase `sessionTimeoutMs` (60-90 min)
|
|
992
|
+
|
|
993
|
+
**Example with Environment Variables:**
|
|
994
|
+
|
|
995
|
+
```typescript
|
|
996
|
+
import { Module } from '@nestjs/common';
|
|
997
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
998
|
+
import { McpModule } from '@nestjs-mcp/server';
|
|
999
|
+
|
|
1000
|
+
@Module({
|
|
1001
|
+
imports: [
|
|
1002
|
+
ConfigModule.forRoot(),
|
|
1003
|
+
McpModule.forRootAsync({
|
|
1004
|
+
imports: [ConfigModule],
|
|
1005
|
+
inject: [ConfigService],
|
|
1006
|
+
useFactory: (config: ConfigService) => ({
|
|
1007
|
+
name: 'My Server',
|
|
1008
|
+
version: '1.0.0',
|
|
1009
|
+
session: {
|
|
1010
|
+
sessionTimeoutMs: config.get('MCP_SESSION_TIMEOUT', 1800000),
|
|
1011
|
+
cleanupIntervalMs: config.get('MCP_CLEANUP_INTERVAL', 300000),
|
|
1012
|
+
maxConcurrentSessions: config.get('MCP_MAX_SESSIONS', 1000),
|
|
1013
|
+
},
|
|
1014
|
+
}),
|
|
1015
|
+
}),
|
|
1016
|
+
],
|
|
1017
|
+
})
|
|
1018
|
+
export class AppModule {}
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
---
|
|
1022
|
+
|
|
1023
|
+
## Inspector Playground
|
|
1024
|
+
|
|
1025
|
+
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:
|
|
1026
|
+
|
|
1027
|
+
- Explore available resources, tools, and prompts
|
|
1028
|
+
- Invoke endpoints and view responses in real time
|
|
1029
|
+
- Validate your server implementation against the MCP specification
|
|
1030
|
+
|
|
1031
|
+
To launch the Inspector Playground (make sure your NestJS MCP server is running):
|
|
1032
|
+
|
|
1033
|
+
```sh
|
|
1034
|
+
npx @modelcontextprotocol/inspector
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
It will typically connect to `http://localhost:3000` by default, or you can specify a different target URL.
|
|
1038
|
+
|
|
1039
|
+
---
|
|
1040
|
+
|
|
1041
|
+
## Examples
|
|
1042
|
+
|
|
1043
|
+
The [`examples/`](./examples/) directory contains ready-to-use scenarios demonstrating how to register and expose MCP capabilities.
|
|
1044
|
+
|
|
1045
|
+
Each example is self-contained and follows best practices. For advanced usage, see the code and documentation in each example.
|
|
1046
|
+
|
|
1047
|
+
---
|
|
1048
|
+
|
|
1049
|
+
## Changelog
|
|
1050
|
+
|
|
1051
|
+
See [CHANGELOG.md](./CHANGELOG.md) for release notes.
|
|
1052
|
+
|
|
1053
|
+
---
|
|
1054
|
+
|
|
1055
|
+
## License
|
|
1056
|
+
|
|
1057
|
+
MIT — see [LICENSE](./LICENSE) for details.
|
|
1058
|
+
|
|
1059
|
+
---
|
|
1060
|
+
|
|
1061
|
+
## Contributions
|
|
1062
|
+
|
|
1063
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines, reporting issues, and pull request rules.
|
|
1064
|
+
|
|
1065
|
+
Before contributing, please read our [Code of Conduct](./CODE_OF_CONDUCT.md) to understand the expectations for behavior in our community.
|