@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 +12 -0
- package/README.md +256 -72
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp-server/mcp-dynamic.service.d.ts +31 -0
- package/dist/mcp-server/mcp-dynamic.service.js +100 -0
- package/dist/mcp-server/mcp.module.js +3 -1
- package/dist/mcp-server/mcp.service.js +7 -6
- package/dist/mcp-server/utils/run-fillters.d.ts +3 -0
- package/dist/mcp-server/utils/run-fillters.js +37 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @muzikanto/nestjs-mcp
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@muzikanto/nestjs-mcp)
|
|
4
|
-
[]((https://www.npmjs.com/package/@muzikanto/nestjs-mcp))
|
|
4
|
+
[](<(https://www.npmjs.com/package/@muzikanto/nestjs-mcp)>)
|
|
5
5
|
[](https://github.com/Muzikanto/nestjs-mcp)
|
|
6
6
|
[](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
|
-
- [
|
|
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
|
|
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
|
|
58
|
-
import { McpModule } from
|
|
59
|
-
import { TelegramSendMessageTool } from
|
|
60
|
-
import { TelegramAutoReplyPrompt } from
|
|
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
|
|
76
|
-
import { Telegraf } from
|
|
77
|
-
import z from
|
|
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(
|
|
81
|
-
text: z.string().describe(
|
|
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 =
|
|
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
|
-
|
|
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
|
|
159
|
-
import z from
|
|
165
|
+
import { IMcpPrompt, McpPrompt } from "@muzikanto/nestjs-mcp";
|
|
166
|
+
import z from "zod";
|
|
160
167
|
|
|
161
168
|
const schema = {
|
|
162
|
-
chatId: z.string().describe(
|
|
163
|
-
text: z.string().describe(
|
|
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<{
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
|
276
|
+
import { IMcpResource, McpResource } from "@muzikanto/nestjs-mcp";
|
|
268
277
|
|
|
269
278
|
@McpResource()
|
|
270
279
|
export class TestResource implements IMcpResource<{ userId: string }> {
|
|
271
|
-
name =
|
|
272
|
-
uri =
|
|
273
|
-
title =
|
|
274
|
-
description =
|
|
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
|
-
###
|
|
291
|
+
### Dynamic creation
|
|
292
|
+
|
|
283
293
|
```ts
|
|
284
|
-
import {
|
|
285
|
-
import {
|
|
286
|
-
import {
|
|
287
|
-
import {
|
|
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(
|
|
292
|
-
|
|
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 =
|
|
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: [
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,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
|
+
"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"
|