@muzikanto/nestjs-mcp 1.3.0 → 1.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @muzikanto/nestjs-mcp
2
2
 
3
+ ## 1.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - add UseFilters support
8
+
9
+ ## 1.4.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Dynamic tools, prompts, resources initialization
14
+
3
15
  ## 1.3.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @muzikanto/nestjs-mcp
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@muzikanto/nestjs-mcp)](https://www.npmjs.com/package/@muzikanto/nestjs-mcp)
4
- [![downloads](https://img.shields.io/npm/dt/@muzikanto/nestjs-mcp)]((https://www.npmjs.com/package/@muzikanto/nestjs-mcp))
4
+ [![downloads](https://img.shields.io/npm/dt/@muzikanto/nestjs-mcp)](<(https://www.npmjs.com/package/@muzikanto/nestjs-mcp)>)
5
5
  [![GitHub stars](https://img.shields.io/github/stars/Muzikanto/nestjs-mcp?style=social)](https://github.com/Muzikanto/nestjs-mcp)
6
6
  [![License](https://img.shields.io/npm/l/@muzikanto/nestjs-mcp)](https://github.com/Muzikanto/nestjs-mcp/blob/main/LICENSE)
7
7
 
@@ -10,6 +10,7 @@ NestJS MCP (Model Context Protocol) module — allows you to create “tools”,
10
10
  ---
11
11
 
12
12
  ## Contents
13
+
13
14
  - [Features](#features)
14
15
  - [Installation](#installation)
15
16
  - [Usage](#usage)
@@ -17,11 +18,18 @@ NestJS MCP (Model Context Protocol) module — allows you to create “tools”,
17
18
  - [Create MCP tool](#create-mcp-tool)
18
19
  - [Create MCP prompt](#create-mcp-prompt)
19
20
  - [Create MCP resource](#create-mcp-resource)
21
+ - [Dynamic creation](#dynamic-creation)
20
22
  - [Calling MCP tools via HTTP](#calling-mcp-tools-via-http)
21
23
  - [Calling MCP prompt via HTTP](#obtain-prompt)
22
24
  - [Obtaining all tools](#obtaining-all-tools)
23
25
  - [Obtaining all prompts](#obtaining-all-prompts)
24
- - [Auth guard](#auth-guard)
26
+ - [Guards](#guards)
27
+ - [For one](#for-one)
28
+ - [For all](#for-all)
29
+ - [Interceptors](#interceptors)
30
+ - [For one](#for-one-1)
31
+ - [For all](#for-all-1)
32
+ - [Filters](#filters)
25
33
  - [Integration with OpenAI Function Calls](#integration-with-openai-function-calls)
26
34
 
27
35
  ## Features
@@ -30,14 +38,15 @@ NestJS MCP (Model Context Protocol) module — allows you to create “tools”,
30
38
  - Register MCP prompts using the `@McpPrompt()` decorator
31
39
  - Automatic detection of all providers (tools) in the module
32
40
  - Input data validation
33
- - SSE endpoints for MCP (`GET /mcp/sse, POST /mcp/messages`)
34
- - Endpoint for calling tool (`POST /mcp/toos`)
35
- - Endpoint for a list of all tools (`GET /mcp/tools`)
41
+ - SSE endpoints for MCP (`GET /mcp/sse, POST /mcp/messages`)
42
+ - Endpoint for calling tool (`POST /mcp/toos`)
43
+ - Endpoint for a list of all tools (`GET /mcp/tools`)
36
44
  - Endpoint for calling prompt (`POST /mcp/prompts/:name`)
37
- - Endpoint for a list of all prompts (`GET /mcp/prompts`)
38
- - Easy integration with LLM (OpenAI Function Calls)
45
+ - Endpoint for a list of all prompts (`GET /mcp/prompts`)
46
+ - Easy integration with LLM (OpenAI Function Calls)
39
47
  - Support http adapters (default fastify)
40
- - Full TypeScript typing
48
+ - Full TypeScript typing
49
+ - Support `@UseGuards`, `@UseInterceptors`, `@UseFilters`
41
50
 
42
51
  ---
43
52
 
@@ -54,10 +63,10 @@ Peer dependencies: `@nestjs/common, @nestjs/core, @nestjs/swagger, reflect-metad
54
63
  ### Connecting the MCP module
55
64
 
56
65
  ```ts
57
- import { Module } from '@nestjs/common';
58
- import { McpModule } from '@muzikanto/nestjs-mcp';
59
- import { TelegramSendMessageTool } from './tools/telegram-send-message.tool';
60
- import { TelegramAutoReplyPrompt } from './prompts/telegram-auto-reply.prompt';
66
+ import { Module } from "@nestjs/common";
67
+ import { McpModule } from "@muzikanto/nestjs-mcp";
68
+ import { TelegramSendMessageTool } from "./tools/telegram-send-message.tool";
69
+ import { TelegramAutoReplyPrompt } from "./prompts/telegram-auto-reply.prompt";
61
70
 
62
71
  @Module({
63
72
  imports: [
@@ -72,13 +81,13 @@ export class AppModule {}
72
81
  ### Create MCP tool
73
82
 
74
83
  ```ts
75
- import { IMcpTool, McpTool } from '@muzikanto/nestjs-mcp';
76
- import { Telegraf } from 'telegraf';
77
- import z from 'zod';
84
+ import { IMcpTool, McpTool } from "@muzikanto/nestjs-mcp";
85
+ import { Telegraf } from "telegraf";
86
+ import z from "zod";
78
87
 
79
88
  const schema = {
80
- chatId: z.string().describe('Telegram chat id'), // строка с описанием
81
- text: z.string().describe('Message text'), // строка с описанием
89
+ chatId: z.string().describe("Telegram chat id"), // строка с описанием
90
+ text: z.string().describe("Message text"), // строка с описанием
82
91
  };
83
92
 
84
93
  @McpTool()
@@ -86,7 +95,7 @@ export class TelegramSendMessageTool implements IMcpTool<
86
95
  { chatId: string; text: string },
87
96
  { success: boolean }
88
97
  > {
89
- name = 'telegram.sendMessage';
98
+ name = "telegram.sendMessage";
90
99
 
91
100
  inputSchema = schema;
92
101
 
@@ -113,7 +122,8 @@ POST /mcp
113
122
  }
114
123
  ```
115
124
 
116
- Ответ
125
+ Ответ
126
+
117
127
  ```json
118
128
  {
119
129
  "data": {
@@ -134,7 +144,7 @@ GET /mcp/tool
134
144
  "inputSchema": {
135
145
  "type": "object",
136
146
  "properties": {
137
- "chatId": {
147
+ "chatId": {
138
148
  "type": "number",
139
149
  "description": "Telegram chat ID"
140
150
  },
@@ -143,10 +153,7 @@ GET /mcp/tool
143
153
  "description": "Message text"
144
154
  }
145
155
  },
146
- "required": [
147
- "chatId",
148
- "text"
149
- ]
156
+ "required": ["chatId", "text"]
150
157
  }
151
158
  }
152
159
  ]
@@ -155,29 +162,33 @@ GET /mcp/tool
155
162
  ### Create MCP prompt
156
163
 
157
164
  ```ts
158
- import { IMcpPrompt, McpPrompt } from '@muzikanto/nestjs-mcp';
159
- import z from 'zod';
165
+ import { IMcpPrompt, McpPrompt } from "@muzikanto/nestjs-mcp";
166
+ import z from "zod";
160
167
 
161
168
  const schema = {
162
- chatId: z.string().describe('Telegram chat id'), // строка с описанием
163
- text: z.string().describe('Message text'), // строка с описанием
169
+ chatId: z.string().describe("Telegram chat id"), // строка с описанием
170
+ text: z.string().describe("Message text"), // строка с описанием
164
171
  };
165
172
 
166
173
  @McpPrompt()
167
- export class TelegramAutoReplyPrompt implements IMcpPrompt<{ text: string; chatId: number; }> {
168
- name = 'telegram_auto_reply';
169
- description = 'Generate a short, fiendly reply to an incoming Telegram message and send it back to the same chat using teegram.sendMessage tool';
174
+ export class TelegramAutoReplyPrompt implements IMcpPrompt<{
175
+ text: string;
176
+ chatId: number;
177
+ }> {
178
+ name = "telegram_auto_reply";
179
+ description =
180
+ "Generate a short, fiendly reply to an incoming Telegram message and send it back to the same chat using teegram.sendMessage tool";
170
181
  schema = schema;
171
182
 
172
183
  async execute({ text, chatId }: { text: string; chatId: number }) {
173
184
  return [
174
185
  {
175
186
  role: "system",
176
- content: `You are a friendly Telegram bot. Reply briefly and to the point.`
187
+ content: `You are a friendly Telegram bot. Reply briefly and to the point.`,
177
188
  },
178
189
  {
179
190
  role: "user",
180
- content: text
191
+ content: text,
181
192
  },
182
193
  {
183
194
  role: "assistant",
@@ -185,10 +196,10 @@ export class TelegramAutoReplyPrompt implements IMcpPrompt<{ text: string; chatI
185
196
  name: "telegram.sendMessage",
186
197
  arguments: {
187
198
  chatId,
188
- text: "{{model_output}}"
189
- }
190
- }
191
- }
199
+ text: "{{model_output}}",
200
+ },
201
+ },
202
+ },
192
203
  ];
193
204
  }
194
205
  }
@@ -206,7 +217,7 @@ GET /mcp/prompts
206
217
  "inputSchema": {
207
218
  "type": "object",
208
219
  "properties": {
209
- "chatId": {
220
+ "chatId": {
210
221
  "type": "number",
211
222
  "description": "Telegram chat ID"
212
223
  },
@@ -215,10 +226,7 @@ GET /mcp/prompts
215
226
  "description": "Message text"
216
227
  }
217
228
  },
218
- "required": [
219
- "chatId",
220
- "text"
221
- ]
229
+ "required": ["chatId", "text"]
222
230
  }
223
231
  }
224
232
  ]
@@ -236,42 +244,43 @@ POST /mcp/prompts/telegram_auto_reply
236
244
  ```
237
245
 
238
246
  Response
247
+
239
248
  ```json
240
249
  {
241
250
  "messages": [
242
- {
243
- "role": "system",
244
- "content": "You are a friendly Telegram bot. Reply briefly and to the point."
245
- },
246
- {
247
- "role": "user",
248
- "content": "Hi. How are you ?"
249
- },
250
- {
251
- "role": "assistant",
252
- "tool_call": {
253
- "name": "telegram.sendMessage",
254
- "arguments": {
255
- "chatId": 100000000,
256
- "text": "{{model_output}}"
257
- }
251
+ {
252
+ "role": "system",
253
+ "content": "You are a friendly Telegram bot. Reply briefly and to the point."
254
+ },
255
+ {
256
+ "role": "user",
257
+ "content": "Hi. How are you ?"
258
+ },
259
+ {
260
+ "role": "assistant",
261
+ "tool_call": {
262
+ "name": "telegram.sendMessage",
263
+ "arguments": {
264
+ "chatId": 100000000,
265
+ "text": "{{model_output}}"
258
266
  }
259
267
  }
260
- ]
268
+ }
269
+ ]
261
270
  }
262
271
  ```
263
272
 
264
273
  ### Create MCP resource
265
274
 
266
275
  ```ts
267
- import { IMcpResource, McpResource } from '@muzikanto/nestjs-mcp';
276
+ import { IMcpResource, McpResource } from "@muzikanto/nestjs-mcp";
268
277
 
269
278
  @McpResource()
270
279
  export class TestResource implements IMcpResource<{ userId: string }> {
271
- name = 'users.get';
272
- uri = 'users://{userId}';
273
- title = 'Get test user';
274
- description = 'Get user by id';
280
+ name = "users.get";
281
+ uri = "users://{userId}";
282
+ title = "Get test user";
283
+ description = "Get user by id";
275
284
 
276
285
  async execute(url: URL, vars: { userId: string }) {
277
286
  return [{ uri: url.href, text: `Hello ${vars.userId}` }];
@@ -279,17 +288,64 @@ export class TestResource implements IMcpResource<{ userId: string }> {
279
288
  }
280
289
  ```
281
290
 
282
- ### Auth guard
291
+ ### Dynamic creation
292
+
283
293
  ```ts
284
- import { IMcpTool, McpTool } from '@muzikanto/nestjs-mcp';
285
- import { UseGuards } from '@nestjs/common';
286
- import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
287
- import { Observable } from 'rxjs';
294
+ import { McpDynamicService } from "@muzikanto/nestjs-mcp";
295
+ import { Injectable } from "@nestjs/common";
296
+ import { ExampleInterceptor } from "./example.interceptor";
297
+ import { ExampleGuard } from "./example.guard";
298
+
299
+ @Injectable()
300
+ export class McpDynamic {
301
+ constructor(protected readonly mcpDynamicService: McpDynamicService) {}
302
+
303
+ onModuleInit() {
304
+ this.mcpDynamicService.registerTool({
305
+ name: "dynamic_tool",
306
+ title: "Dynamic tool",
307
+ execute: () => Promise.resolve("test"),
308
+ guards: [ExampleGuard],
309
+ interceptors: [ExampleInterceptor],
310
+ });
311
+
312
+ this.mcpDynamicService.registerPrompt({
313
+ name: "dynamic_prompt",
314
+ title: "Dynamic prompt",
315
+ execute: () => Promise.resolve([{ role: "assistant", content: "test" }]),
316
+ guards: [ExampleGuard],
317
+ interceptors: [ExampleInterceptor],
318
+ });
319
+
320
+ this.mcpDynamicService.registerResource<{ testId: string }>({
321
+ name: "dynamic_resource",
322
+ title: "Dynamic resource",
323
+ uri: "dynamic://test/{testId}",
324
+ execute: (uri, input) =>
325
+ Promise.resolve([{ uri: uri.href, text: `ID: ${input.testId}` }]),
326
+ guards: [ExampleGuard],
327
+ interceptors: [ExampleInterceptor],
328
+ });
329
+ }
330
+ }
331
+ ```
332
+
333
+ ### Guards
334
+
335
+ ### For one
336
+
337
+ ```ts
338
+ import { IMcpTool, McpTool } from "@muzikanto/nestjs-mcp";
339
+ import { UseGuards } from "@nestjs/common";
340
+ import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
341
+ import { Observable } from "rxjs";
288
342
 
289
343
  @Injectable()
290
344
  class TestGuard implements CanActivate {
291
- canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
292
- console.log('TestGuard called');
345
+ canActivate(
346
+ context: ExecutionContext,
347
+ ): boolean | Promise<boolean> | Observable<boolean> {
348
+ console.log("TestGuard called");
293
349
  return true;
294
350
  }
295
351
  }
@@ -300,7 +356,75 @@ export class TelegramSendMessageTool implements IMcpTool<
300
356
  { chatId: string; text: string },
301
357
  { success: boolean }
302
358
  > {
303
- name = 'telegram.sendMessage';
359
+ name = "telegram.sendMessage";
360
+
361
+ async execute() {
362
+ await this.bot.telegram.sendMessage(input.chatId, input.text);
363
+ return { success: true };
364
+ }
365
+ }
366
+ ```
367
+
368
+ #### For all
369
+
370
+ ```ts
371
+ import { McpModule } from "@muzikanto/nestjs-mcp";
372
+ import { Module } from "@nestjs/common";
373
+ import { TestGuard } from "./other/test.guard";
374
+ import { ToolWithoutGuards } from "./test.tool";
375
+ import { APP_GUARD } from "@nestjs/core";
376
+
377
+ @Module({
378
+ imports: [
379
+ McpModule.forRoot({
380
+ providers: [
381
+ ToolWithoutGuards,
382
+ TestGuard,
383
+ { provide: APP_GUARD, useExisting: TestGuard },
384
+ ],
385
+ }),
386
+ ],
387
+ })
388
+ export class TestModule {}
389
+ ```
390
+
391
+ ### Interceptors
392
+
393
+ #### For one
394
+
395
+ ```ts
396
+ import { IMcpTool, McpTool } from "@muzikanto/nestjs-mcp";
397
+ import {
398
+ ExecutionContext,
399
+ NestInterceptor,
400
+ CallHandler,
401
+ Injectable,
402
+ UseInterceptors,
403
+ } from "@nestjs/common";
404
+ import { map } from "rxjs";
405
+
406
+ @Injectable()
407
+ export class ExampleInterceptor implements NestInterceptor {
408
+ intercept(context: ExecutionContext, next: CallHandler) {
409
+ console.log("Before execute");
410
+
411
+ return next.handle().pipe(
412
+ map((data: unknown) => {
413
+ console.log("After execute", data);
414
+
415
+ return data;
416
+ }),
417
+ );
418
+ }
419
+ }
420
+
421
+ @UseInterceptors(TestInterceptor)
422
+ @McpTool()
423
+ export class TelegramSendMessageTool implements IMcpTool<
424
+ { chatId: string; text: string },
425
+ { success: boolean }
426
+ > {
427
+ name = "telegram.sendMessage";
304
428
 
305
429
  async execute() {
306
430
  await this.bot.telegram.sendMessage(input.chatId, input.text);
@@ -309,6 +433,64 @@ export class TelegramSendMessageTool implements IMcpTool<
309
433
  }
310
434
  ```
311
435
 
436
+ #### For all
437
+
438
+ ```ts
439
+ import { McpModule } from "@muzikanto/nestjs-mcp";
440
+ import { Module } from "@nestjs/common";
441
+ import { TestInterceptor } from "./other/test.interceptor";
442
+ import { ToolWithoutInterceptors } from "./test.tool";
443
+ import { APP_INTERCEPTOR } from "@nestjs/core";
444
+
445
+ @Module({
446
+ imports: [
447
+ McpModule.forRoot({
448
+ providers: [
449
+ ToolWithoutInterceptors,
450
+ TestGuard,
451
+ { provide: APP_INTERCEPTOR, useExisting: TestInterceptor },
452
+ ],
453
+ }),
454
+ ],
455
+ })
456
+ export class TestModule {}
457
+ ```
458
+
459
+ ### Fitlers
460
+
461
+ ```ts
462
+ import { IMcpTool, McpTool } from "@muzikanto/nestjs-mcp";
463
+ import {
464
+ ExceptionFilter,
465
+ Catch,
466
+ ArgumentsHost,
467
+ NotImplementedException,
468
+ UseFilters,
469
+ } from "@nestjs/common";
470
+
471
+ @Catch(NotImplementedException)
472
+ class ExampleFilter implements ExceptionFilter {
473
+ catch(exception: unknown, host: ArgumentsHost) {
474
+ return {
475
+ message: (exception as Error).message,
476
+ };
477
+ }
478
+ }
479
+
480
+ @UseFilters(ExampleFilter)
481
+ @McpTool()
482
+ export class TelegramSendMessageTool implements IMcpTool<
483
+ { chatId: string; text: string },
484
+ { success: boolean }
485
+ > {
486
+ name = "telegram.sendMessage";
487
+
488
+ async execute() {
489
+ throw new NotImplementedException();
490
+ }
491
+ }
492
+ ```
493
+
312
494
  ### Integration with OpenAI Function Calls
313
495
 
314
496
  ```ts
@@ -374,7 +556,9 @@ async function callMcpTool(toolName: string, payload: Record<string, any>) {
374
556
  ```
375
557
 
376
558
  ## Contributing
559
+
377
560
  Contributions are welcome! Please open issues or submit PRs.
378
561
 
379
562
  ## Changelog
563
+
380
564
  See [CHANGELOG](https://github.com/Muzikanto/nestjs-mcp/blob/main/CHANGELOG.md) for detailed version history and updates.
package/dist/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export * from "./mcp-server/decorators/mcp-prompt.decorator";
4
4
  export * from "./mcp-server/decorators/mcp-resource.decorator";
5
5
  export * from "./mcp-client/mcp-client.module";
6
6
  export * from "./mcp-client/mcp-client.service";
7
+ export * from "./mcp-server/mcp-dynamic.service";
package/dist/index.js CHANGED
@@ -20,3 +20,4 @@ __exportStar(require("./mcp-server/decorators/mcp-prompt.decorator"), exports);
20
20
  __exportStar(require("./mcp-server/decorators/mcp-resource.decorator"), exports);
21
21
  __exportStar(require("./mcp-client/mcp-client.module"), exports);
22
22
  __exportStar(require("./mcp-client/mcp-client.service"), exports);
23
+ __exportStar(require("./mcp-server/mcp-dynamic.service"), exports);
@@ -0,0 +1,31 @@
1
+ import { CanActivate, NestInterceptor, Type } from "@nestjs/common";
2
+ import { ModuleRef } from "@nestjs/core";
3
+ import { McpService } from "./mcp.service";
4
+ import { IMcpTool } from "./decorators/mcp-tool.decorator";
5
+ import { IMcpResource } from "./decorators/mcp-resource.decorator";
6
+ import { IMcpPrompt } from "./decorators/mcp-prompt.decorator";
7
+ export type IMcpDynamicTool<Input, Result> = IMcpTool<Input, Result> & {
8
+ guards?: Type<CanActivate>[];
9
+ interceptors?: Type<NestInterceptor>[];
10
+ };
11
+ export type IMcpDynamicPrompt<Input> = IMcpPrompt<Input> & {
12
+ guards?: Type<CanActivate>[];
13
+ interceptors?: Type<NestInterceptor>[];
14
+ };
15
+ export type IMcpDynamicResource<Input> = IMcpResource<Input> & {
16
+ guards?: Type<CanActivate>[];
17
+ interceptors?: Type<NestInterceptor>[];
18
+ };
19
+ export declare class McpDynamicService {
20
+ private readonly moduleRef;
21
+ private readonly mcpService;
22
+ constructor(moduleRef: ModuleRef, mcpService: McpService);
23
+ /** Динамически регистрирует MCP tools */
24
+ registerTool<Input, Result>(tool: IMcpDynamicTool<Input, Result>): Promise<void>;
25
+ /** Динамически регистрирует MCP prompts */
26
+ registerPrompt<Input>(prompt: IMcpDynamicPrompt<Input>): Promise<void>;
27
+ /** Динамически регистрирует MCP resources */
28
+ registerResource<Input>(resource: IMcpDynamicResource<Input>): Promise<void>;
29
+ protected applyGuards(item: Function, guards: Type<CanActivate>[]): void;
30
+ protected applyInterceptors(item: Function, interceptors: Type<NestInterceptor>[]): void;
31
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.McpDynamicService = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const core_1 = require("@nestjs/core");
15
+ const mcp_service_1 = require("./mcp.service");
16
+ const constants_1 = require("@nestjs/common/constants");
17
+ let McpDynamicService = class McpDynamicService {
18
+ moduleRef;
19
+ mcpService;
20
+ constructor(moduleRef, mcpService) {
21
+ this.moduleRef = moduleRef;
22
+ this.mcpService = mcpService;
23
+ }
24
+ /** Динамически регистрирует MCP tools */
25
+ async registerTool(tool) {
26
+ const DynamicToolClass = class {
27
+ name = tool.name;
28
+ title = tool.title;
29
+ description = tool.description;
30
+ execute = tool.execute;
31
+ inputSchema = tool.inputSchema;
32
+ };
33
+ if (tool.guards) {
34
+ this.applyGuards(DynamicToolClass, tool.guards);
35
+ }
36
+ if (tool.interceptors) {
37
+ this.applyInterceptors(DynamicToolClass, tool.interceptors);
38
+ }
39
+ const instance = await this.moduleRef.create(DynamicToolClass);
40
+ this.mcpService.registerTool(instance.name, {
41
+ instance,
42
+ metatype: DynamicToolClass,
43
+ });
44
+ }
45
+ /** Динамически регистрирует MCP prompts */
46
+ async registerPrompt(prompt) {
47
+ const DynamicPromptClass = class {
48
+ name = prompt.name;
49
+ title = prompt.title;
50
+ description = prompt.description;
51
+ execute = prompt.execute;
52
+ inputSchema = prompt.inputSchema;
53
+ };
54
+ if (prompt.guards) {
55
+ this.applyGuards(DynamicPromptClass, prompt.guards);
56
+ }
57
+ if (prompt.interceptors) {
58
+ this.applyInterceptors(DynamicPromptClass, prompt.interceptors);
59
+ }
60
+ const instance = await this.moduleRef.create(DynamicPromptClass);
61
+ this.mcpService.registerPrompt(instance.name, {
62
+ instance,
63
+ metatype: DynamicPromptClass,
64
+ });
65
+ }
66
+ /** Динамически регистрирует MCP resources */
67
+ async registerResource(resource) {
68
+ const DynamicResourceClass = class {
69
+ name = resource.name;
70
+ title = resource.title;
71
+ uri = resource.uri;
72
+ description = resource.description;
73
+ execute = resource.execute;
74
+ list = resource.list;
75
+ };
76
+ if (resource.guards) {
77
+ this.applyGuards(DynamicResourceClass, resource.guards);
78
+ }
79
+ if (resource.interceptors) {
80
+ this.applyInterceptors(DynamicResourceClass, resource.interceptors);
81
+ }
82
+ const instance = await this.moduleRef.create(DynamicResourceClass);
83
+ this.mcpService.registerResource(instance.name, {
84
+ instance,
85
+ metatype: DynamicResourceClass,
86
+ });
87
+ }
88
+ applyGuards(item, guards) {
89
+ Reflect.defineMetadata(constants_1.GUARDS_METADATA, guards, item);
90
+ }
91
+ applyInterceptors(item, interceptors) {
92
+ Reflect.defineMetadata(constants_1.INTERCEPTORS_METADATA, interceptors, item);
93
+ }
94
+ };
95
+ exports.McpDynamicService = McpDynamicService;
96
+ exports.McpDynamicService = McpDynamicService = __decorate([
97
+ (0, common_1.Injectable)(),
98
+ __metadata("design:paramtypes", [core_1.ModuleRef,
99
+ mcp_service_1.McpService])
100
+ ], McpDynamicService);
@@ -16,6 +16,7 @@ const mcp_controller_1 = require("./mcp.controller");
16
16
  const inject_tokens_1 = require("./utils/inject-tokens");
17
17
  const config_1 = require("./config");
18
18
  const http_adapter_1 = require("./utils/http-adapter");
19
+ const mcp_dynamic_service_1 = require("./mcp-dynamic.service");
19
20
  const publicGuard = { canActivate: () => Promise.resolve(true) };
20
21
  let McpModule = McpModule_1 = class McpModule {
21
22
  static forRoot(metadata = {}) {
@@ -37,12 +38,13 @@ let McpModule = McpModule_1 = class McpModule {
37
38
  providers: [
38
39
  mcp_service_1.McpService,
39
40
  mcp_explorer_1.McpExplorer,
41
+ mcp_dynamic_service_1.McpDynamicService,
40
42
  configProviver,
41
43
  guardProvider,
42
44
  ...(metadata.guard ? [metadata.guard] : []),
43
45
  ...(metadata.providers || []),
44
46
  ],
45
- exports: [mcp_service_1.McpService, ...(metadata.exports || [])],
47
+ exports: [mcp_dynamic_service_1.McpDynamicService, ...(metadata.exports || [])],
46
48
  controllers: [mcp_controller_1.McpController],
47
49
  };
48
50
  }
@@ -19,6 +19,7 @@ const run_guards_1 = require("./utils/run-guards");
19
19
  const core_1 = require("@nestjs/core");
20
20
  const run_interceptors_1 = require("./utils/run-interceptors");
21
21
  const rxjs_1 = require("rxjs");
22
+ const run_fillters_1 = require("./utils/run-fillters");
22
23
  let McpService = class McpService {
23
24
  moduleRef;
24
25
  tools = new Map();
@@ -105,10 +106,10 @@ let McpService = class McpService {
105
106
  }
106
107
  try {
107
108
  // const result = await prompt.execute(payload);
108
- const result = await (0, run_interceptors_1.runInterceptors)(this.moduleRef, metatype, context, () => {
109
+ const stream = await (0, run_interceptors_1.runInterceptors)(this.moduleRef, metatype, context, () => {
109
110
  return (0, rxjs_1.from)(prompt.execute(payload));
110
111
  });
111
- return result;
112
+ return stream.pipe((0, rxjs_1.catchError)((err) => (0, rxjs_1.from)((0, run_fillters_1.runFilters)(this.moduleRef, metatype, err, context))));
112
113
  }
113
114
  catch (err) {
114
115
  throw new common_1.InternalServerErrorException("Failed to execute prompt");
@@ -134,10 +135,10 @@ let McpService = class McpService {
134
135
  }
135
136
  try {
136
137
  // const result = await tool.execute(msg.payload);
137
- const result = await (0, run_interceptors_1.runInterceptors)(this.moduleRef, metatype, context, () => {
138
+ const stream = await (0, run_interceptors_1.runInterceptors)(this.moduleRef, metatype, context, () => {
138
139
  return (0, rxjs_1.from)(tool.execute(msg.payload));
139
140
  });
140
- return result;
141
+ return stream.pipe((0, rxjs_1.catchError)((err) => (0, rxjs_1.from)((0, run_fillters_1.runFilters)(this.moduleRef, metatype, err, context))));
141
142
  }
142
143
  catch (err) {
143
144
  throw new common_1.InternalServerErrorException("Failed to execute tool");
@@ -151,10 +152,10 @@ let McpService = class McpService {
151
152
  await (0, run_guards_1.runGuards)(this.moduleRef, metatype, context);
152
153
  try {
153
154
  // const result = await resource.execute(uri, vars);
154
- const result = await (0, run_interceptors_1.runInterceptors)(this.moduleRef, metatype, context, () => {
155
+ const stream = await (0, run_interceptors_1.runInterceptors)(this.moduleRef, metatype, context, () => {
155
156
  return (0, rxjs_1.from)(resource.execute(uri, vars));
156
157
  });
157
- return result;
158
+ return stream.pipe((0, rxjs_1.catchError)((err) => (0, rxjs_1.from)((0, run_fillters_1.runFilters)(this.moduleRef, metatype, err, context))));
158
159
  }
159
160
  catch (err) {
160
161
  throw new common_1.InternalServerErrorException("Failed to execute tool");
@@ -0,0 +1,3 @@
1
+ import { ArgumentsHost } from "@nestjs/common";
2
+ import { ModuleRef } from "@nestjs/core";
3
+ export declare function runFilters(moduleRef: ModuleRef, metatype: Function, exception: unknown, host: ArgumentsHost): Promise<any>;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runFilters = runFilters;
4
+ const constants_1 = require("@nestjs/common/constants");
5
+ async function runFilters(moduleRef, metatype, exception, host) {
6
+ const resolveFilter = async (Filter) => {
7
+ if (typeof Filter !== "function") {
8
+ return Filter;
9
+ }
10
+ try {
11
+ return moduleRef.get(Filter, { strict: false });
12
+ }
13
+ catch {
14
+ try {
15
+ return await moduleRef.create(Filter);
16
+ }
17
+ catch {
18
+ return new Filter();
19
+ }
20
+ }
21
+ };
22
+ const filters = Reflect.getMetadata(constants_1.EXCEPTION_FILTERS_METADATA, metatype) || [];
23
+ for (const Filter of filters) {
24
+ const filterInstance = await resolveFilter(Filter);
25
+ const filterExceptions = (Reflect.getMetadata(constants_1.FILTER_CATCH_EXCEPTIONS, Filter) || []);
26
+ if (!filterExceptions ||
27
+ !filterExceptions.some((ex) => exception instanceof ex)) {
28
+ continue;
29
+ }
30
+ // ВАЖНО: filter сам обрабатывает exception
31
+ const result = await filterInstance.catch(exception, host);
32
+ // Nest прекращает выполнение после первого подходящего filter
33
+ return result;
34
+ }
35
+ // если фильтров нет — пробрасываем ошибку дальше
36
+ throw exception;
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muzikanto/nestjs-mcp",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "NestJS MCP (Model Context Protocol) module for creating tools for LLM or HTTP with validation, decorators, and OpenAI Function Calls integration.",
5
5
  "keywords": [
6
6
  "nestjs",
@@ -20,7 +20,7 @@
20
20
  "build": "tsc",
21
21
  "prepublishOnly": "npm run build",
22
22
  "lint": "prettier --check 'src/**/*.ts'",
23
- "format": "prettier --write 'src/**/*.ts'",
23
+ "format": "prettier --write 'src/**/*.ts' 'example/**/*.ts' 'README.md'",
24
24
  "ch": "changeset",
25
25
  "vers": "changeset version",
26
26
  "pub": "changeset publish"