@nestjs-mcp/server 0.1.0-alpha.12 → 0.1.0-alpha.14
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/README.md +443 -133
- package/dist/controllers/sse/sse.service.d.ts +3 -2
- package/dist/controllers/sse/sse.service.js +21 -6
- package/dist/controllers/sse/sse.service.js.map +1 -1
- package/dist/controllers/streamable/streamable.service.d.ts +3 -2
- package/dist/controllers/streamable/streamable.service.js +35 -11
- package/dist/controllers/streamable/streamable.service.js.map +1 -1
- package/dist/interfaces/capabilities.interface.d.ts +3 -3
- package/dist/interfaces/capabilities.interface.js +5 -5
- package/dist/interfaces/capabilities.interface.js.map +1 -1
- package/dist/interfaces/context.interface.d.ts +2 -4
- package/dist/mcp.module.js +3 -8
- package/dist/mcp.module.js.map +1 -1
- package/dist/registry/registry.service.d.ts +4 -4
- package/dist/registry/registry.service.js +29 -11
- package/dist/registry/registry.service.js.map +1 -1
- package/dist/services/session.manager.d.ts +14 -0
- package/dist/services/{message.service.js → session.manager.js} +14 -12
- package/dist/services/session.manager.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/controllers/sse/sse.service.ts +21 -5
- package/src/controllers/streamable/streamable.service.ts +39 -10
- package/src/interfaces/capabilities.interface.ts +5 -6
- package/src/interfaces/context.interface.ts +11 -8
- package/src/mcp.module.ts +4 -11
- package/src/registry/registry.service.ts +30 -10
- package/src/services/session.manager.ts +26 -0
- package/dist/interceptors/message.interceptor.d.ts +0 -10
- package/dist/interceptors/message.interceptor.js +0 -61
- package/dist/interceptors/message.interceptor.js.map +0 -1
- package/dist/services/message.service.d.ts +0 -7
- package/dist/services/message.service.js.map +0 -1
- package/src/interceptors/message.interceptor.ts +0 -70
- package/src/services/message.service.ts +0 -18
package/README.md
CHANGED
|
@@ -24,18 +24,24 @@
|
|
|
24
24
|
- [Prompt](#prompt)
|
|
25
25
|
- [Module API](#module-api)
|
|
26
26
|
- [forRoot](#mcpmoduleforroot)
|
|
27
|
+
- [forRootAsync](#mcpmoduleforrootasync)
|
|
27
28
|
- [forFeature](#mcpmoduleforfeature)
|
|
28
|
-
|
|
29
|
+
- [Module Usage](#module-usage)
|
|
30
|
+
- [1. Global Registration with `McpModule.forRoot`](#1-global-registration-with-mcpmoduleforroot)
|
|
31
|
+
- [2. Feature Module Registration with `McpModule.forFeature`](#2-feature-module-registration-with-mcpmoduleforfeature)
|
|
29
32
|
- [Capabilities](#capabilities)
|
|
30
|
-
- [
|
|
31
|
-
- [
|
|
32
|
-
- [
|
|
33
|
-
- [
|
|
33
|
+
- [Resolver Decorator](#resolver-decorator)
|
|
34
|
+
- [Prompt Decorator](#prompt-decorator)
|
|
35
|
+
- [Resource Decorator](#resource-decorator)
|
|
36
|
+
- [Tool Decorator](#tool-decorator)
|
|
34
37
|
- [Guards](#guards)
|
|
35
|
-
- [Global-level guards
|
|
36
|
-
- [Resolver-level guards
|
|
37
|
-
- [Method-level guards
|
|
38
|
+
- [Global-level guards](#global-level-guards)
|
|
39
|
+
- [Resolver-level guards](#resolver-level-guards)
|
|
40
|
+
- [Method-level guards](#method-level-guards)
|
|
38
41
|
- [Guard Example](#guard-example)
|
|
42
|
+
- [MCP Execution Context](#mcp-execution-context)
|
|
43
|
+
- [Session Management](#session-management)
|
|
44
|
+
- [Transport Options](#transport-options)
|
|
39
45
|
- [Inspector Playground](#inspector-playground)
|
|
40
46
|
- [Examples](#examples)
|
|
41
47
|
- [Changelog](#changelog)
|
|
@@ -60,14 +66,13 @@ Register the MCP module in your NestJS app and expose a simple tool:
|
|
|
60
66
|
|
|
61
67
|
```ts
|
|
62
68
|
import { Module } from '@nestjs/common';
|
|
63
|
-
import { Injectable } from '@nestjs/common';
|
|
64
69
|
|
|
65
70
|
import { CallToolResult } from '@modelcontextprotocol/sdk/types';
|
|
66
71
|
|
|
67
|
-
import { Tool } from '@nestjs-mcp/server';
|
|
72
|
+
import { Resolver, Tool, McpModule } from '@nestjs-mcp/server';
|
|
68
73
|
|
|
69
|
-
@
|
|
70
|
-
export class
|
|
74
|
+
@Resolver()
|
|
75
|
+
export class HealthResolver {
|
|
71
76
|
/**
|
|
72
77
|
* Simple health check tool
|
|
73
78
|
*/
|
|
@@ -91,7 +96,7 @@ export class AppService {
|
|
|
91
96
|
version: '1.0.0',
|
|
92
97
|
}),
|
|
93
98
|
],
|
|
94
|
-
providers: [
|
|
99
|
+
providers: [HealthResolver],
|
|
95
100
|
})
|
|
96
101
|
export class AppModule {}
|
|
97
102
|
```
|
|
@@ -138,19 +143,31 @@ A Prompt defines a conversational flow, template, or interaction pattern for LLM
|
|
|
138
143
|
|
|
139
144
|
### `McpModule.forRoot`
|
|
140
145
|
|
|
141
|
-
Registers the MCP Server globally in your NestJS application.
|
|
146
|
+
Registers the MCP Server globally in your NestJS application.
|
|
142
147
|
|
|
143
148
|
**Parameters:**
|
|
144
149
|
|
|
145
|
-
- `options:
|
|
150
|
+
- `options: McpModuleOptions` — Main server configuration object:
|
|
151
|
+
- `name: string`: The name of your MCP server.
|
|
152
|
+
- `version: string`: The version of your MCP server.
|
|
153
|
+
- `instructions?: string`: Optional description of the MCP server for the client.
|
|
154
|
+
- `capabilities?: Record<string, unknown>`: Optional additional capabilities metadata.
|
|
155
|
+
- `providers?: Provider[]`: Optional array of NestJS providers to include in the module.
|
|
156
|
+
- `imports?: any[]`: Optional array of NestJS modules to import.
|
|
157
|
+
- `logging?: McpLoggingOptions`: Optional logging configuration:
|
|
158
|
+
- `enabled?: boolean` (default: `true`): Enable/disable logging.
|
|
159
|
+
- `level?: 'error' | 'warn' | 'log' | 'debug' | 'verbose'` (default: `'verbose'`): Set the logging level.
|
|
160
|
+
- `transports?: McpModuleTransportOptions`: Optional transport configuration (see [Transport Options](#transport-options)).
|
|
161
|
+
- `protocolOptions?: Record<string, unknown>`: Optional parameters passed directly to the underlying `@modelcontextprotocol/sdk` server instance.
|
|
146
162
|
|
|
147
163
|
**Returns:**
|
|
148
164
|
|
|
149
|
-
- A dynamic NestJS module with all MCP providers registered
|
|
165
|
+
- A dynamic NestJS module with all MCP providers registered.
|
|
150
166
|
|
|
151
167
|
**Example:**
|
|
152
168
|
|
|
153
169
|
```ts
|
|
170
|
+
import { Module } from '@nestjs/common';
|
|
154
171
|
import { McpModule } from '@nestjs-mcp/server';
|
|
155
172
|
|
|
156
173
|
@Module({
|
|
@@ -158,6 +175,9 @@ import { McpModule } from '@nestjs-mcp/server';
|
|
|
158
175
|
McpModule.forRoot({
|
|
159
176
|
name: 'My Server',
|
|
160
177
|
version: '1.0.0',
|
|
178
|
+
instructions: 'A server providing utility tools and data.',
|
|
179
|
+
logging: { level: 'log' },
|
|
180
|
+
transports: { sse: { enabled: false } }, // Disable SSE transport
|
|
161
181
|
// ...other MCP options
|
|
162
182
|
}),
|
|
163
183
|
],
|
|
@@ -165,28 +185,84 @@ import { McpModule } from '@nestjs-mcp/server';
|
|
|
165
185
|
export class AppModule {}
|
|
166
186
|
```
|
|
167
187
|
|
|
188
|
+
### `McpModule.forRootAsync`
|
|
189
|
+
|
|
190
|
+
Registers the MCP Server globally using asynchronous options, useful for integrating with configuration modules like `@nestjs/config`.
|
|
191
|
+
|
|
192
|
+
**Parameters:**
|
|
193
|
+
|
|
194
|
+
- `options: McpModuleAsyncOptions` — Asynchronous configuration object:
|
|
195
|
+
- `imports?: any[]`: Optional modules to import before the factory runs.
|
|
196
|
+
- `useFactory: (...args: any[]) => Promise<McpModuleOptions> | McpModuleOptions`: A factory function that returns the `McpModuleOptions`.
|
|
197
|
+
- `inject?: any[]`: Optional providers to inject into the `useFactory`.
|
|
198
|
+
|
|
199
|
+
**Returns:**
|
|
200
|
+
|
|
201
|
+
- A dynamic NestJS module.
|
|
202
|
+
|
|
203
|
+
**Example (with ConfigModule):**
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { Module } from '@nestjs/common';
|
|
207
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
208
|
+
import { McpModule } from '@nestjs-mcp/server';
|
|
209
|
+
|
|
210
|
+
@Module({
|
|
211
|
+
imports: [
|
|
212
|
+
ConfigModule.forRoot(), // Make sure ConfigModule is imported
|
|
213
|
+
McpModule.forRootAsync({
|
|
214
|
+
imports: [ConfigModule], // Import ConfigModule here too
|
|
215
|
+
useFactory: (configService: ConfigService) => ({
|
|
216
|
+
name: configService.get<string>('MCP_SERVER_NAME', 'Default Server'),
|
|
217
|
+
version: configService.get<string>('MCP_SERVER_VERSION', '1.0.0'),
|
|
218
|
+
instructions: configService.get<string>('MCP_SERVER_DESC'),
|
|
219
|
+
logging: {
|
|
220
|
+
level: configService.get('MCP_LOG_LEVEL', 'verbose'),
|
|
221
|
+
},
|
|
222
|
+
// ... other options from configService
|
|
223
|
+
}),
|
|
224
|
+
inject: [ConfigService], // Inject ConfigService into the factory
|
|
225
|
+
}),
|
|
226
|
+
],
|
|
227
|
+
})
|
|
228
|
+
export class AppModule {}
|
|
229
|
+
```
|
|
230
|
+
|
|
168
231
|
### `McpModule.forFeature`
|
|
169
232
|
|
|
170
|
-
Registers additional MCP resources, tools, or prompts
|
|
233
|
+
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.
|
|
171
234
|
|
|
172
235
|
**Parameters:**
|
|
173
236
|
|
|
174
|
-
- `
|
|
237
|
+
- `options?: McpFeatureOptions` (Currently unused, reserved for future enhancements).
|
|
175
238
|
|
|
176
239
|
**Returns:**
|
|
177
240
|
|
|
178
|
-
- A dynamic module
|
|
241
|
+
- A dynamic module.
|
|
179
242
|
|
|
180
243
|
**Example:**
|
|
181
244
|
|
|
182
245
|
```ts
|
|
246
|
+
// src/status/status.resolver.ts
|
|
247
|
+
import { Resolver, Tool } from '@nestjs-mcp/server';
|
|
248
|
+
import { CallToolResult } from '@modelcontextprotocol/sdk/types';
|
|
249
|
+
|
|
250
|
+
@Resolver('status')
|
|
251
|
+
export class StatusResolver {
|
|
252
|
+
@Tool({ name: 'health_check' })
|
|
253
|
+
healthCheck(): CallToolResult {
|
|
254
|
+
return { content: [{ type: 'text', text: 'OK' }] };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/status/status.module.ts
|
|
259
|
+
import { Module } from '@nestjs/common';
|
|
183
260
|
import { McpModule } from '@nestjs-mcp/server';
|
|
261
|
+
import { StatusResolver } from './status.resolver';
|
|
184
262
|
|
|
185
263
|
@Module({
|
|
186
|
-
imports: [McpModule.forFeature()],
|
|
187
|
-
providers: [
|
|
188
|
-
/*Your Providers with Mcp Capabilities*/
|
|
189
|
-
],
|
|
264
|
+
imports: [McpModule.forFeature()], // Import forFeature here
|
|
265
|
+
providers: [StatusResolver], // Register your resolver
|
|
190
266
|
})
|
|
191
267
|
export class StatusModule {}
|
|
192
268
|
```
|
|
@@ -236,9 +312,9 @@ import { ToolsResolver } from './tools.resolver';
|
|
|
236
312
|
export class ToolsModule {}
|
|
237
313
|
```
|
|
238
314
|
|
|
239
|
-
- Use `forRoot` only once in your root module.
|
|
240
|
-
- Use `forFeature`
|
|
241
|
-
-
|
|
315
|
+
- Use `forRoot` or `forRootAsync` **only once** in your root module (`AppModule`).
|
|
316
|
+
- Use `forFeature` in any feature module where you define MCP capabilities (`@Resolver` classes).
|
|
317
|
+
- Ensure all Resolvers are listed in the `providers` array of their respective modules.
|
|
242
318
|
|
|
243
319
|
---
|
|
244
320
|
|
|
@@ -248,27 +324,38 @@ This library provides a set of decorators to define MCP capabilities and apply c
|
|
|
248
324
|
|
|
249
325
|
### Resolver Decorator
|
|
250
326
|
|
|
251
|
-
A Resolver is a class that groups related MCP capabilities
|
|
327
|
+
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`.
|
|
328
|
+
|
|
329
|
+
- **No `@Injectable()` Needed:** Resolver classes are automatically treated as providers by the MCP module and **do not** require the `@Injectable()` decorator.
|
|
330
|
+
- **Dependency Injection:** Standard NestJS dependency injection works within Resolver constructors.
|
|
331
|
+
- **Namespacing:** You can optionally provide a string argument to `@Resolver('my_namespace')` to namespace the capabilities within that resolver.
|
|
332
|
+
- **Guards:** Guards can be applied at the class level using `@UseGuards()`.
|
|
252
333
|
|
|
253
334
|
**Example:**
|
|
254
335
|
|
|
255
336
|
```ts
|
|
256
337
|
import { Resolver, Prompt, Resource, Tool } from '@nestjs-mcp/server';
|
|
338
|
+
// Import any services you need to inject
|
|
339
|
+
import { SomeService } from '../some.service';
|
|
257
340
|
|
|
258
|
-
@Resolver('workspace')
|
|
341
|
+
@Resolver('workspace') // No @Injectable()
|
|
259
342
|
export class MyResolver {
|
|
260
|
-
|
|
261
|
-
|
|
343
|
+
// Inject dependencies as usual
|
|
344
|
+
constructor(private readonly someService: SomeService) {}
|
|
345
|
+
|
|
346
|
+
@Prompt({ name: 'greet_user' }) // Capabilities must be inside a Resolver
|
|
347
|
+
greetPrompt(/*...args...*/) {
|
|
348
|
+
const greeting = this.someService.getGreeting();
|
|
262
349
|
/* ... */
|
|
263
350
|
}
|
|
264
351
|
|
|
265
|
-
@Resource({ name: '
|
|
266
|
-
getUserResource() {
|
|
352
|
+
@Resource({ name: 'user_profile', uri: 'user://{id}' })
|
|
353
|
+
getUserResource(/*...args...*/) {
|
|
267
354
|
/* ... */
|
|
268
355
|
}
|
|
269
356
|
|
|
270
|
-
@Tool({ name: '
|
|
271
|
-
sumTool() {
|
|
357
|
+
@Tool({ name: 'calculate_sum' })
|
|
358
|
+
sumTool(/*...args...*/) {
|
|
272
359
|
/* ... */
|
|
273
360
|
}
|
|
274
361
|
}
|
|
@@ -280,57 +367,123 @@ You can also apply guards at the resolver level:
|
|
|
280
367
|
import { UseGuards, Resolver } from '@nestjs-mcp/server';
|
|
281
368
|
import { MyGuard } from './guards/my.guard';
|
|
282
369
|
|
|
283
|
-
@UseGuards(MyGuard)
|
|
284
|
-
@Resolver('secure')
|
|
370
|
+
@UseGuards(MyGuard) // Applied to all capabilities in this Resolver
|
|
371
|
+
@Resolver('secure') // No @Injectable()
|
|
285
372
|
export class SecureResolver {
|
|
286
|
-
//
|
|
373
|
+
// All capabilities in this resolver will use MyGuard
|
|
287
374
|
}
|
|
288
375
|
```
|
|
289
376
|
|
|
290
377
|
### Prompt Decorator
|
|
291
378
|
|
|
292
|
-
Decorate methods within a Resolver to expose them as MCP Prompts.
|
|
379
|
+
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`.**
|
|
293
380
|
|
|
294
381
|
```ts
|
|
295
|
-
import { Prompt } from '@nestjs-mcp/server';
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
382
|
+
import { Prompt, Resolver } from '@nestjs-mcp/server';
|
|
383
|
+
import { RequestHandlerExtra } from '@nestjs-mcp/server'; // Import type for extra info
|
|
384
|
+
import { z } from 'zod'; // Example if using Zod schema
|
|
385
|
+
|
|
386
|
+
// Optional: Define schema if needed
|
|
387
|
+
// const SummaryArgs = z.object({ topic: z.string() });
|
|
388
|
+
|
|
389
|
+
@Resolver('prompts') // Must be in a Resolver class
|
|
390
|
+
export class MyPrompts {
|
|
391
|
+
@Prompt({
|
|
392
|
+
name: 'generate_summary',
|
|
393
|
+
description: 'Generates a summary for the given text.',
|
|
394
|
+
// argsSchema: SummaryArgs
|
|
395
|
+
})
|
|
396
|
+
generateSummaryPrompt(
|
|
397
|
+
// params: z.infer<typeof SummaryArgs>, // Arguments based on argsSchema (if defined)
|
|
398
|
+
extra: RequestHandlerExtra, // Contains sessionId and other metadata
|
|
399
|
+
) {
|
|
400
|
+
console.log(`Generating summary for session: ${extra.sessionId}`);
|
|
401
|
+
/* ... return CallPromptResult ... */
|
|
402
|
+
return { content: [{ type: 'text', text: 'Summary generated.' }] };
|
|
302
403
|
}
|
|
303
404
|
}
|
|
304
405
|
```
|
|
305
406
|
|
|
306
407
|
### Resource Decorator
|
|
307
408
|
|
|
308
|
-
Decorate methods within a Resolver to expose them as MCP Resources.
|
|
409
|
+
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`.**
|
|
309
410
|
|
|
310
411
|
```ts
|
|
311
|
-
import { Resource } from '@nestjs-mcp/server';
|
|
412
|
+
import { Resource, Resolver } from '@nestjs-mcp/server';
|
|
413
|
+
import { RequestHandlerExtra } from '@nestjs-mcp/server'; // Import type for extra info
|
|
414
|
+
import { URL } from 'url'; // Type for URI resource
|
|
415
|
+
import { z } from 'zod'; // Example if using Zod template
|
|
416
|
+
|
|
417
|
+
// Optional: Define template schema if needed
|
|
418
|
+
// const DocQueryTemplate = z.object({ query: z.string() });
|
|
419
|
+
|
|
420
|
+
@Resolver('data') // Must be in a Resolver class
|
|
421
|
+
export class MyResources {
|
|
422
|
+
@Resource({
|
|
423
|
+
name: 'user_profile',
|
|
424
|
+
uri: 'user://profiles/{userId}',
|
|
425
|
+
// metadata: { description: '...' } // Optional
|
|
426
|
+
})
|
|
427
|
+
getUserProfile(
|
|
428
|
+
uri: URL, // First argument is the parsed URI
|
|
429
|
+
// metadata: Record<string, any> // Second argument if is defined
|
|
430
|
+
extra: RequestHandlerExtra, // Contains sessionId and other metadata
|
|
431
|
+
) {
|
|
432
|
+
const userId = uri.pathname.split('/').pop(); // Example: Extract ID from URI
|
|
433
|
+
console.log(`Fetching profile for ${userId}, session: ${extra.sessionId}`);
|
|
434
|
+
/* ... return CallResourceResult ... */
|
|
435
|
+
return { content: [{ type: 'text', text: `Profile data for ${userId}` }] };
|
|
436
|
+
}
|
|
312
437
|
|
|
313
|
-
@
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
438
|
+
@Resource({
|
|
439
|
+
name: 'document_list',
|
|
440
|
+
template: { type: 'string', description: 'Document content query' }, // Simple template example
|
|
441
|
+
// metadata: { list: true } // Optional
|
|
442
|
+
})
|
|
443
|
+
findDocuments(
|
|
444
|
+
uri: URL, // First arg based on simple template type
|
|
445
|
+
variables: Record<string, string>, // Second arg is path params (if any)
|
|
446
|
+
extra: RequestHandlerExtra, // Contains sessionId and other metadata
|
|
447
|
+
) {
|
|
448
|
+
console.log(
|
|
449
|
+
`Finding documents matching '${query}', session: ${extra.sessionId}`,
|
|
450
|
+
);
|
|
451
|
+
/* ... return CallResourceResult ... */
|
|
452
|
+
return { content: [{ type: 'text', text: 'List of documents.' }] };
|
|
318
453
|
}
|
|
319
454
|
}
|
|
320
455
|
```
|
|
321
456
|
|
|
322
457
|
### Tool Decorator
|
|
323
458
|
|
|
324
|
-
Decorate methods within a Resolver to expose them as MCP Tools.
|
|
459
|
+
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`.**
|
|
325
460
|
|
|
326
461
|
```ts
|
|
327
|
-
import { Tool } from '@nestjs-mcp/server';
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
462
|
+
import { Tool, Resolver } from '@nestjs-mcp/server';
|
|
463
|
+
import { RequestHandlerExtra } from '@nestjs-mcp/server'; // Import type for extra info
|
|
464
|
+
import { z } from 'zod'; // Example using Zod for schema
|
|
465
|
+
|
|
466
|
+
// Example Zod schema for parameters
|
|
467
|
+
const SumParams = z.object({
|
|
468
|
+
num1: z.number(),
|
|
469
|
+
num2: z.number(),
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
@Resolver('utils') // Must be in a Resolver class
|
|
473
|
+
export class MyTools {
|
|
474
|
+
@Tool({
|
|
475
|
+
name: 'calculate_sum',
|
|
476
|
+
description: 'Calculates the sum of two numbers.',
|
|
477
|
+
paramSchema: SumParams, // Use the Zod schema
|
|
478
|
+
})
|
|
479
|
+
sumTool(
|
|
480
|
+
params: z.infer<typeof SumParams>, // First arg is typed parameters from schema
|
|
481
|
+
extra: RequestHandlerExtra, // Contains sessionId and other metadata
|
|
482
|
+
) {
|
|
483
|
+
console.log(`Calculating sum for session: ${extra.sessionId}`);
|
|
484
|
+
const result = params.num1 + params.num2;
|
|
485
|
+
/* ... return CallToolResult ... */
|
|
486
|
+
return { content: [{ type: 'text', text: `Result: ${result}` }] };
|
|
334
487
|
}
|
|
335
488
|
}
|
|
336
489
|
```
|
|
@@ -341,20 +494,22 @@ export class MyResolver {
|
|
|
341
494
|
|
|
342
495
|
Apply one or more guards to a Resolver, to individual methods, or globally. Guards must implement the NestJS `CanActivate` interface.
|
|
343
496
|
|
|
344
|
-
### Global-level guards
|
|
497
|
+
### Global-level guards
|
|
345
498
|
|
|
346
|
-
This approach uses the standard NestJS global guard system. A global guard will protect all
|
|
499
|
+
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.
|
|
347
500
|
|
|
348
501
|
```ts
|
|
349
502
|
// src/guards/global-auth.guard.ts
|
|
350
503
|
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
|
504
|
+
import { Request } from 'express';
|
|
351
505
|
|
|
352
506
|
@Injectable()
|
|
353
507
|
export class GlobalAuthGuard implements CanActivate {
|
|
354
508
|
canActivate(context: ExecutionContext): boolean {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
509
|
+
const request = context.switchToHttp().getRequest<Request>();
|
|
510
|
+
const apiKey = request.headers['x-api-key'];
|
|
511
|
+
// Example: Check for a valid API key
|
|
512
|
+
return !!apiKey && apiKey === 'EXPECTED_KEY';
|
|
358
513
|
}
|
|
359
514
|
}
|
|
360
515
|
```
|
|
@@ -362,21 +517,15 @@ export class GlobalAuthGuard implements CanActivate {
|
|
|
362
517
|
Register the guard globally in your main module:
|
|
363
518
|
|
|
364
519
|
```ts
|
|
520
|
+
// src/app.module.ts
|
|
365
521
|
import { Module } from '@nestjs/common';
|
|
366
522
|
import { APP_GUARD } from '@nestjs/core';
|
|
367
523
|
import { McpModule } from '@nestjs-mcp/server';
|
|
368
524
|
import { GlobalAuthGuard } from './guards/global-auth.guard';
|
|
369
|
-
import { PromptsResolver } from './prompts.resolver';
|
|
370
525
|
|
|
371
526
|
@Module({
|
|
372
|
-
imports: [
|
|
373
|
-
McpModule.forRoot({
|
|
374
|
-
name: 'My MCP Server',
|
|
375
|
-
version: '1.0.0',
|
|
376
|
-
}),
|
|
377
|
-
],
|
|
527
|
+
imports: [McpModule.forRoot(/*...*/)],
|
|
378
528
|
providers: [
|
|
379
|
-
PromptsResolver,
|
|
380
529
|
{
|
|
381
530
|
provide: APP_GUARD,
|
|
382
531
|
useClass: GlobalAuthGuard,
|
|
@@ -386,116 +535,275 @@ import { PromptsResolver } from './prompts.resolver';
|
|
|
386
535
|
export class AppModule {}
|
|
387
536
|
```
|
|
388
537
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
- The guard will run for every request handled by your NestJS application, including all MCP endpoints.
|
|
392
|
-
- You can implement any logic in canActivate, such as checking headers, tokens, or user roles.
|
|
393
|
-
- This approach is fully compatible with NestJS and your MCP Server module.
|
|
538
|
+
### Resolver-level guards
|
|
394
539
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
This is a custom feature of this library. Resolver-level guards are applied using the `@UseGuards` decorator on a Resolver class. All MCP methods (`@Prompt`, `@Resource`, `@Tool`) in the resolver will be protected by these guards. Use this to enforce logic (e.g., role checks) for a specific group of capabilities.
|
|
540
|
+
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.
|
|
398
541
|
|
|
399
542
|
```ts
|
|
400
543
|
import { UseGuards, Resolver, Prompt } from '@nestjs-mcp/server';
|
|
401
|
-
import {
|
|
544
|
+
import { RoleGuard } from './guards/role.guard';
|
|
402
545
|
|
|
403
|
-
@UseGuards(
|
|
404
|
-
@Resolver('
|
|
405
|
-
export class
|
|
406
|
-
@Prompt({ name: '
|
|
407
|
-
|
|
546
|
+
@UseGuards(RoleGuard)
|
|
547
|
+
@Resolver('admin')
|
|
548
|
+
export class AdminResolver {
|
|
549
|
+
@Prompt({ name: 'admin_action' })
|
|
550
|
+
adminAction(/*...*/) {
|
|
408
551
|
/* ... */
|
|
409
552
|
}
|
|
553
|
+
// ... other admin capabilities
|
|
410
554
|
}
|
|
411
555
|
```
|
|
412
556
|
|
|
413
|
-
### Method-level guards
|
|
557
|
+
### Method-level guards
|
|
414
558
|
|
|
415
|
-
This is a custom feature of this library. Method-level guards are applied using the `@UseGuards` decorator directly on
|
|
559
|
+
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.
|
|
416
560
|
|
|
417
561
|
```ts
|
|
418
|
-
import { UseGuards, Resolver, Prompt } from '@nestjs-mcp/server';
|
|
419
|
-
import {
|
|
562
|
+
import { UseGuards, Resolver, Prompt, Tool } from '@nestjs-mcp/server';
|
|
563
|
+
import { SpecificCheckGuard } from './guards/specific-check.guard';
|
|
420
564
|
|
|
421
565
|
@Resolver('mixed')
|
|
422
566
|
export class MixedResolver {
|
|
423
|
-
@Prompt({ name: '
|
|
567
|
+
@Prompt({ name: 'public_prompt' })
|
|
424
568
|
publicPrompt() {
|
|
425
|
-
/*
|
|
569
|
+
/* Publicly accessible */
|
|
426
570
|
}
|
|
427
571
|
|
|
428
|
-
@UseGuards(
|
|
429
|
-
@
|
|
430
|
-
|
|
431
|
-
/*
|
|
572
|
+
@UseGuards(SpecificCheckGuard)
|
|
573
|
+
@Tool({ name: 'protected_tool' })
|
|
574
|
+
protectedTool(/*...*/) {
|
|
575
|
+
/* Requires SpecificCheckGuard to pass */
|
|
432
576
|
}
|
|
433
577
|
}
|
|
434
578
|
```
|
|
435
579
|
|
|
580
|
+
**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`.
|
|
581
|
+
|
|
436
582
|
### Guard Example
|
|
437
583
|
|
|
584
|
+
A guard for Resolver or Method-level protection:
|
|
585
|
+
|
|
438
586
|
```ts
|
|
439
|
-
|
|
440
|
-
import {
|
|
587
|
+
// src/guards/my-mcp.guard.ts
|
|
588
|
+
import { CanActivate, Injectable } from '@nestjs/common';
|
|
589
|
+
import { McpExecutionContext, SessionManager } from '@nestjs-mcp/server';
|
|
590
|
+
|
|
591
|
+
@Injectable()
|
|
592
|
+
export class MyMcpGuard implements CanActivate {
|
|
593
|
+
constructor(private readonly sessionManager: SessionManager) {}
|
|
441
594
|
|
|
442
|
-
export class MyGuard implements CanActivate {
|
|
443
595
|
canActivate(context: McpExecutionContext): boolean {
|
|
444
|
-
|
|
596
|
+
const sessionId = context.getSessionId();
|
|
597
|
+
if (!sessionId) return false;
|
|
598
|
+
|
|
599
|
+
const handlerArgs = context.getArgs();
|
|
600
|
+
|
|
601
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
602
|
+
const request = session?.request;
|
|
603
|
+
const userAgent = request?.headers['user-agent'];
|
|
604
|
+
|
|
605
|
+
console.log(`Guard activated for session ${sessionId} from ${userAgent}`);
|
|
606
|
+
console.log('Handler args:', handlerArgs);
|
|
607
|
+
|
|
445
608
|
return true;
|
|
446
609
|
}
|
|
447
610
|
}
|
|
448
611
|
```
|
|
449
612
|
|
|
450
|
-
###
|
|
613
|
+
### MCP Execution Context
|
|
451
614
|
|
|
452
|
-
When implementing
|
|
615
|
+
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:
|
|
453
616
|
|
|
454
|
-
```
|
|
455
|
-
import { CanActivate } from '@nestjs/common';
|
|
456
|
-
import { McpExecutionContext } from '@nestjs-mcp/server';
|
|
457
|
-
import { Request
|
|
617
|
+
```typescript
|
|
618
|
+
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
|
619
|
+
import { McpExecutionContext, SessionManager } from '@nestjs-mcp/server';
|
|
620
|
+
import { Request } from 'express';
|
|
458
621
|
|
|
459
622
|
@Injectable()
|
|
460
623
|
export class McpAuthGuard implements CanActivate {
|
|
624
|
+
constructor(private readonly sessionManager: SessionManager) {}
|
|
625
|
+
|
|
461
626
|
canActivate(context: McpExecutionContext): boolean {
|
|
462
|
-
|
|
463
|
-
|
|
627
|
+
const sessionId = context.getSessionId();
|
|
628
|
+
if (!sessionId) {
|
|
629
|
+
console.error('Guard Error: MCP Session ID not found in context.');
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const handlerArgs = context.getArgs<any>();
|
|
634
|
+
console.log('MCP Handler Arguments:', handlerArgs);
|
|
635
|
+
|
|
636
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
637
|
+
if (!session) {
|
|
638
|
+
console.error(`Guard Error: Session not found for ID: ${sessionId}`);
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
const request = session.request as Request;
|
|
642
|
+
|
|
643
|
+
const authHeader = request.headers.authorization;
|
|
644
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
645
|
+
console.log('Guard Denied: Missing or invalid Bearer token.');
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
const token = authHeader.split(' ')[1];
|
|
649
|
+
const isValidToken = token === 'VALID_TOKEN';
|
|
650
|
+
|
|
651
|
+
if (isValidToken) {
|
|
652
|
+
console.log(`Guard Passed for session ${sessionId} with token.`);
|
|
653
|
+
return true;
|
|
654
|
+
} else {
|
|
655
|
+
console.log(`Guard Denied: Invalid token for session ${sessionId}.`);
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
**Key points for `McpExecutionContext`:**
|
|
663
|
+
|
|
664
|
+
- `getSessionId()`: Retrieves the unique ID for the current MCP session. **Crucial** for relating the guard check to the session state stored by `SessionManager`.
|
|
665
|
+
- 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.
|
|
666
|
+
- 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.
|
|
667
|
+
- `switchToHttp().getResponse()` / `switchToHttp().getNext()`: These will throw errors as the Response object is not directly available or relevant in this context.
|
|
668
|
+
|
|
669
|
+
Use `SessionManager` injected into your guard to fetch the session details (including the original `Request`) based on the `sessionId` obtained from the context.
|
|
464
670
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
671
|
+
---
|
|
672
|
+
|
|
673
|
+
## Session Management
|
|
674
|
+
|
|
675
|
+
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.
|
|
676
|
+
|
|
677
|
+
**Why is it important?**
|
|
678
|
+
|
|
679
|
+
- **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.
|
|
680
|
+
- **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.
|
|
468
681
|
|
|
469
|
-
|
|
470
|
-
const request = req as Request;
|
|
471
|
-
const response = res as Response;
|
|
682
|
+
**Usage Example (in a Resolver):**
|
|
472
683
|
|
|
473
|
-
|
|
474
|
-
const authHeader = request.headers.authorization;
|
|
684
|
+
Resolvers might need access to the original request, for example, to get user information or API keys passed in headers during the initial connection.
|
|
475
685
|
|
|
476
|
-
|
|
477
|
-
|
|
686
|
+
```typescript
|
|
687
|
+
import { Tool, Resolver, SessionManager } from '@nestjs-mcp/server';
|
|
688
|
+
import { RequestHandlerExtra } from '@nestjs-mcp/server'; // Provides sessionId
|
|
689
|
+
import { Request } from 'express';
|
|
690
|
+
import { CallToolResult } from '@modelcontextprotocol/sdk/types';
|
|
691
|
+
import { z } from 'zod';
|
|
692
|
+
|
|
693
|
+
const UserToolParams = z.object({
|
|
694
|
+
user_id: z.string().optional(),
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
@Resolver('user_tools') // No @Injectable() needed
|
|
698
|
+
export class UserToolsResolver {
|
|
699
|
+
// Inject SessionManager
|
|
700
|
+
constructor(private readonly sessionManager: SessionManager) {}
|
|
701
|
+
|
|
702
|
+
@Tool({
|
|
703
|
+
name: 'get_user_agent',
|
|
704
|
+
description:
|
|
705
|
+
'Gets the user agent from the original request for the session.',
|
|
706
|
+
paramSchema: UserToolParams,
|
|
707
|
+
})
|
|
708
|
+
getUserAgent(
|
|
709
|
+
params: z.infer<typeof UserToolParams>,
|
|
710
|
+
extra: RequestHandlerExtra, // Get extra info, including sessionId
|
|
711
|
+
): CallToolResult {
|
|
712
|
+
const sessionId = extra.sessionId;
|
|
713
|
+
if (!sessionId) {
|
|
714
|
+
return {
|
|
715
|
+
content: [{ type: 'text', text: 'Error: Session ID missing.' }],
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Use sessionId to get the session from the manager
|
|
720
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
721
|
+
if (!session) {
|
|
722
|
+
return {
|
|
723
|
+
content: [
|
|
724
|
+
{
|
|
725
|
+
type: 'text',
|
|
726
|
+
text: `Error: Session not found for ID: ${sessionId}`,
|
|
727
|
+
},
|
|
728
|
+
],
|
|
729
|
+
};
|
|
478
730
|
}
|
|
479
731
|
|
|
480
|
-
// Access the
|
|
481
|
-
const
|
|
732
|
+
// Access the original request stored in the session
|
|
733
|
+
const request = session.request as Request;
|
|
734
|
+
const userAgent = request.headers['user-agent'] || 'Unknown';
|
|
482
735
|
|
|
483
|
-
return
|
|
736
|
+
return {
|
|
737
|
+
content: [
|
|
738
|
+
{ type: 'text', text: `Session ${sessionId} User Agent: ${userAgent}` },
|
|
739
|
+
],
|
|
740
|
+
};
|
|
484
741
|
}
|
|
485
742
|
}
|
|
486
743
|
```
|
|
487
744
|
|
|
488
|
-
|
|
745
|
+
In this example:
|
|
746
|
+
|
|
747
|
+
1. The `@Tool` method receives `extra: RequestHandlerExtra`, which contains the `sessionId`.
|
|
748
|
+
2. The `SessionManager` is injected into the `UserToolsResolver`.
|
|
749
|
+
3. The `sessionId` is used with `sessionManager.getSession()` to retrieve the session data.
|
|
750
|
+
4. The original `request` object is accessed from the retrieved session data.
|
|
751
|
+
|
|
752
|
+
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.
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
## Transport Options
|
|
757
|
+
|
|
758
|
+
The MCP server can communicate over different transport mechanisms. This library includes built-in support for:
|
|
759
|
+
|
|
760
|
+
1. **Streamable (`/mcp` endpoint):** A common transport using standard HTTP POST requests and responses. Suitable for most request/response interactions. Enabled by default.
|
|
761
|
+
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.
|
|
762
|
+
|
|
763
|
+
You can configure which transports are enabled globally using the `transports` option in `McpModule.forRoot` or `McpModule.forRootAsync`.
|
|
764
|
+
|
|
765
|
+
**Configuration:**
|
|
766
|
+
|
|
767
|
+
```typescript
|
|
768
|
+
import { Module } from '@nestjs/common';
|
|
769
|
+
import { McpModule } from '@nestjs-mcp/server';
|
|
770
|
+
|
|
771
|
+
@Module({
|
|
772
|
+
imports: [
|
|
773
|
+
McpModule.forRoot({
|
|
774
|
+
name: 'My Server',
|
|
775
|
+
version: '1.0.0',
|
|
776
|
+
transports: {
|
|
777
|
+
streamable: { enabled: true }, // Keep streamable enabled (default)
|
|
778
|
+
sse: { enabled: false }, // Disable legacy SSE transport
|
|
779
|
+
},
|
|
780
|
+
}),
|
|
781
|
+
],
|
|
782
|
+
})
|
|
783
|
+
export class AppModule {}
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
**Default Configuration:**
|
|
489
787
|
|
|
490
|
-
|
|
491
|
-
- `message`: The current message from the request, containing:
|
|
492
|
-
- `req`: The Express Request object
|
|
493
|
-
- `res`: The Express Response object
|
|
494
|
-
- `getType()`: Returns the type of execution context (always 'mcp' for MCP operations)
|
|
495
|
-
- `getClass()`: Returns the class of the resolver
|
|
496
|
-
- `getArgs()`: Returns the arguments passed to the method
|
|
788
|
+
If the `transports` option is omitted, both `streamable` (`/mcp`) and `sse` (`/sse`) are enabled by default.
|
|
497
789
|
|
|
498
|
-
|
|
790
|
+
```typescript
|
|
791
|
+
import { Module } from '@nestjs/common';
|
|
792
|
+
import { McpModule } from '@nestjs-mcp/server';
|
|
793
|
+
|
|
794
|
+
@Module({
|
|
795
|
+
imports: [
|
|
796
|
+
McpModule.forRoot({
|
|
797
|
+
name: 'My Server',
|
|
798
|
+
version: '1.0.0',
|
|
799
|
+
// Both streamable and sse will be enabled
|
|
800
|
+
}),
|
|
801
|
+
],
|
|
802
|
+
})
|
|
803
|
+
export class AppModule {}
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
Disabling unused transports can slightly reduce the application's surface area and resource usage.
|
|
499
807
|
|
|
500
808
|
---
|
|
501
809
|
|
|
@@ -507,17 +815,19 @@ Use the Inspector Playground to interactively test and debug your MCP server end
|
|
|
507
815
|
- Invoke endpoints and view responses in real time
|
|
508
816
|
- Validate your server implementation against the MCP specification
|
|
509
817
|
|
|
510
|
-
To launch the Inspector Playground:
|
|
818
|
+
To launch the Inspector Playground (make sure your NestJS MCP server is running):
|
|
511
819
|
|
|
512
820
|
```sh
|
|
513
821
|
npx @modelcontextprotocol/inspector
|
|
514
822
|
```
|
|
515
823
|
|
|
824
|
+
It will typically connect to `http://localhost:3000` by default, or you can specify a different target URL.
|
|
825
|
+
|
|
516
826
|
---
|
|
517
827
|
|
|
518
828
|
## Examples
|
|
519
829
|
|
|
520
|
-
The [`examples/`](./examples/) directory contains ready-to-use scenarios demonstrating how to register and expose MCP capabilities
|
|
830
|
+
The [`examples/`](./examples/) directory contains ready-to-use scenarios demonstrating how to register and expose MCP capabilities.
|
|
521
831
|
|
|
522
832
|
Each example is self-contained and follows best practices. For advanced usage, see the code and documentation in each example.
|
|
523
833
|
|