@muzikanto/nestjs-mcp 1.3.0 → 1.5.1
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 +18 -0
- package/README.md +256 -72
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mcp-server/exceptions/index.d.ts +31 -0
- package/dist/mcp-server/exceptions/index.js +40 -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 +64 -45
- package/dist/mcp-server/utils/run-fillters.d.ts +3 -0
- package/dist/mcp-server/utils/run-fillters.js +37 -0
- package/dist/mcp-server/utils/run-guards.js +2 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @muzikanto/nestjs-mcp
|
|
2
2
|
|
|
3
|
+
## 1.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- custom mcp errors for filters
|
|
8
|
+
|
|
9
|
+
## 1.5.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- add UseFilters support
|
|
14
|
+
|
|
15
|
+
## 1.4.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- Dynamic tools, prompts, resources initialization
|
|
20
|
+
|
|
3
21
|
## 1.3.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
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
|
+
### Filters
|
|
460
|
+
|
|
461
|
+
```ts
|
|
462
|
+
import { IMcpTool, McpTool, McpUnauthorizedException } from "@muzikanto/nestjs-mcp";
|
|
463
|
+
import {
|
|
464
|
+
ExceptionFilter,
|
|
465
|
+
Catch,
|
|
466
|
+
ArgumentsHost,
|
|
467
|
+
UseFilters,
|
|
468
|
+
} from "@nestjs/common";
|
|
469
|
+
|
|
470
|
+
@Catch(McpUnauthorizedException)
|
|
471
|
+
class ExampleFilter implements ExceptionFilter {
|
|
472
|
+
catch(exception: unknown, host: ArgumentsHost) {
|
|
473
|
+
return {
|
|
474
|
+
isError: true,
|
|
475
|
+
text: (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,5 @@ 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";
|
|
8
|
+
export * from "./mcp-server/exceptions";
|
package/dist/index.js
CHANGED
|
@@ -20,3 +20,5 @@ __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);
|
|
24
|
+
__exportStar(require("./mcp-server/exceptions"), exports);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { HttpException, HttpStatus } from "@nestjs/common";
|
|
2
|
+
export declare class McpException extends HttpException {
|
|
3
|
+
constructor(message: string, status?: HttpStatus, options?: {
|
|
4
|
+
cause?: unknown;
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
export declare class McpBadRequestException extends McpException {
|
|
8
|
+
constructor(message: string, options?: {
|
|
9
|
+
cause?: unknown;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export declare class McpNotFoundException extends McpException {
|
|
13
|
+
constructor(message: string, options?: {
|
|
14
|
+
cause?: unknown;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export declare class McpInternalServerErrorException extends McpException {
|
|
18
|
+
constructor(message: string, options?: {
|
|
19
|
+
cause?: unknown;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export declare class McpUnauthorizedException extends McpException {
|
|
23
|
+
constructor(message: string, options?: {
|
|
24
|
+
cause?: unknown;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export declare class McpNotImplementedException extends McpException {
|
|
28
|
+
constructor(message: string, options?: {
|
|
29
|
+
cause?: unknown;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.McpNotImplementedException = exports.McpUnauthorizedException = exports.McpInternalServerErrorException = exports.McpNotFoundException = exports.McpBadRequestException = exports.McpException = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
class McpException extends common_1.HttpException {
|
|
6
|
+
constructor(message, status = common_1.HttpStatus.INTERNAL_SERVER_ERROR, options) {
|
|
7
|
+
super({ message, ...(options?.cause ? { cause: options.cause } : {}) }, status);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.McpException = McpException;
|
|
11
|
+
class McpBadRequestException extends McpException {
|
|
12
|
+
constructor(message, options) {
|
|
13
|
+
super(message, common_1.HttpStatus.BAD_REQUEST, options);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.McpBadRequestException = McpBadRequestException;
|
|
17
|
+
class McpNotFoundException extends McpException {
|
|
18
|
+
constructor(message, options) {
|
|
19
|
+
super(message, common_1.HttpStatus.NOT_FOUND, options);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.McpNotFoundException = McpNotFoundException;
|
|
23
|
+
class McpInternalServerErrorException extends McpException {
|
|
24
|
+
constructor(message, options) {
|
|
25
|
+
super(message, common_1.HttpStatus.INTERNAL_SERVER_ERROR, options);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.McpInternalServerErrorException = McpInternalServerErrorException;
|
|
29
|
+
class McpUnauthorizedException extends McpException {
|
|
30
|
+
constructor(message, options) {
|
|
31
|
+
super(message, common_1.HttpStatus.UNAUTHORIZED, options);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.McpUnauthorizedException = McpUnauthorizedException;
|
|
35
|
+
class McpNotImplementedException extends McpException {
|
|
36
|
+
constructor(message, options) {
|
|
37
|
+
super(message, common_1.HttpStatus.NOT_IMPLEMENTED, options);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.McpNotImplementedException = McpNotImplementedException;
|
|
@@ -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,8 @@ 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");
|
|
23
|
+
const exceptions_1 = require("./exceptions");
|
|
22
24
|
let McpService = class McpService {
|
|
23
25
|
moduleRef;
|
|
24
26
|
tools = new Map();
|
|
@@ -87,77 +89,89 @@ let McpService = class McpService {
|
|
|
87
89
|
}
|
|
88
90
|
async executePrompt(name, payload, context) {
|
|
89
91
|
if (!this.prompts.has(name)) {
|
|
90
|
-
throw new
|
|
91
|
-
}
|
|
92
|
-
const { instance: prompt, metatype } = this.prompts.get(name) || {};
|
|
93
|
-
if (!prompt || !metatype) {
|
|
94
|
-
throw new common_1.NotFoundException(`Unknown prompt: "${name}"`);
|
|
95
|
-
}
|
|
96
|
-
await (0, run_guards_1.runGuards)(this.moduleRef, metatype, context);
|
|
97
|
-
// Валидация через AJV, если есть inputSchema
|
|
98
|
-
if (prompt.inputSchema) {
|
|
99
|
-
const zodSchema = (0, zod_1.zodToJsonSchema)(prompt.inputSchema);
|
|
100
|
-
const validate = this.ajv.compile(zodSchema);
|
|
101
|
-
const valid = validate(payload);
|
|
102
|
-
if (!valid) {
|
|
103
|
-
throw new common_1.NotFoundException("Invalid prompt arguments");
|
|
104
|
-
}
|
|
92
|
+
throw new exceptions_1.McpNotFoundException("Not found prompt");
|
|
105
93
|
}
|
|
94
|
+
const { instance: prompt, metatype } = this.prompts.get(name);
|
|
106
95
|
try {
|
|
96
|
+
await (0, run_guards_1.runGuards)(this.moduleRef, metatype, context);
|
|
97
|
+
// Валидация через AJV, если есть inputSchema
|
|
98
|
+
if (prompt.inputSchema) {
|
|
99
|
+
const zodSchema = (0, zod_1.zodToJsonSchema)(prompt.inputSchema);
|
|
100
|
+
const validate = this.ajv.compile(zodSchema);
|
|
101
|
+
const valid = validate(payload);
|
|
102
|
+
if (!valid) {
|
|
103
|
+
throw new exceptions_1.McpBadRequestException("Invalid prompt arguments");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
107
106
|
// const result = await prompt.execute(payload);
|
|
108
|
-
const
|
|
107
|
+
const stream = await (0, run_interceptors_1.runInterceptors)(this.moduleRef, metatype, context, () => {
|
|
109
108
|
return (0, rxjs_1.from)(prompt.execute(payload));
|
|
110
109
|
});
|
|
111
|
-
return
|
|
110
|
+
return stream.pipe((0, rxjs_1.catchError)((err) => (0, rxjs_1.from)((0, run_fillters_1.runFilters)(this.moduleRef, metatype, err, context))));
|
|
112
111
|
}
|
|
113
112
|
catch (err) {
|
|
114
|
-
|
|
113
|
+
const result = await (0, run_fillters_1.runFilters)(this.moduleRef, metatype, err, context);
|
|
114
|
+
if (result) {
|
|
115
|
+
return (0, rxjs_1.of)(result);
|
|
116
|
+
}
|
|
117
|
+
// Пробрасываем дальше, чтобы Observable корректно завершился
|
|
118
|
+
return (0, rxjs_1.throwError)(() => err);
|
|
115
119
|
}
|
|
116
120
|
}
|
|
117
121
|
/**
|
|
118
122
|
* Отправить сообщение в MCP "сервер"
|
|
119
123
|
*/
|
|
120
124
|
async executeTool(msg, context) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
throw new common_1.NotFoundException(`Unknown tool: "${msg.type}"`);
|
|
124
|
-
}
|
|
125
|
-
await (0, run_guards_1.runGuards)(this.moduleRef, metatype, context);
|
|
126
|
-
// Валидация через AJV, если есть inputSchema
|
|
127
|
-
if (tool.inputSchema) {
|
|
128
|
-
const zodSchema = (0, zod_1.zodToJsonSchema)(tool.inputSchema);
|
|
129
|
-
const validate = this.ajv.compile(zodSchema);
|
|
130
|
-
const valid = validate(msg.payload);
|
|
131
|
-
if (!valid) {
|
|
132
|
-
throw new common_1.BadRequestException(this.ajv.errorsText(validate.errors));
|
|
133
|
-
}
|
|
125
|
+
if (!this.tools.has(msg.type)) {
|
|
126
|
+
throw new exceptions_1.McpNotFoundException("Not found tool");
|
|
134
127
|
}
|
|
128
|
+
const { instance: tool, metatype } = this.tools.get(msg.type);
|
|
135
129
|
try {
|
|
130
|
+
await (0, run_guards_1.runGuards)(this.moduleRef, metatype, context);
|
|
131
|
+
// Валидация через AJV, если есть inputSchema
|
|
132
|
+
if (tool.inputSchema) {
|
|
133
|
+
const zodSchema = (0, zod_1.zodToJsonSchema)(tool.inputSchema);
|
|
134
|
+
const validate = this.ajv.compile(zodSchema);
|
|
135
|
+
const valid = validate(msg.payload);
|
|
136
|
+
if (!valid) {
|
|
137
|
+
throw new exceptions_1.McpBadRequestException(this.ajv.errorsText(validate.errors));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
136
140
|
// const result = await tool.execute(msg.payload);
|
|
137
|
-
const
|
|
141
|
+
const stream = await (0, run_interceptors_1.runInterceptors)(this.moduleRef, metatype, context, () => {
|
|
138
142
|
return (0, rxjs_1.from)(tool.execute(msg.payload));
|
|
139
143
|
});
|
|
140
|
-
return
|
|
144
|
+
return stream.pipe((0, rxjs_1.catchError)((err) => (0, rxjs_1.from)((0, run_fillters_1.runFilters)(this.moduleRef, metatype, err, context))));
|
|
141
145
|
}
|
|
142
146
|
catch (err) {
|
|
143
|
-
|
|
147
|
+
const result = await (0, run_fillters_1.runFilters)(this.moduleRef, metatype, err, context);
|
|
148
|
+
if (result) {
|
|
149
|
+
return (0, rxjs_1.of)(result);
|
|
150
|
+
}
|
|
151
|
+
// Пробрасываем дальше, чтобы Observable корректно завершился
|
|
152
|
+
return (0, rxjs_1.throwError)(() => err);
|
|
144
153
|
}
|
|
145
154
|
}
|
|
146
155
|
async executeResource(name, uri, vars, context) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
throw new common_1.NotFoundException(`Unknown resource: "${name}"`);
|
|
156
|
+
if (!this.resources.has(name)) {
|
|
157
|
+
throw new exceptions_1.McpNotFoundException("Not found prompt");
|
|
150
158
|
}
|
|
151
|
-
|
|
159
|
+
const { instance: resource, metatype } = this.resources.get(name);
|
|
152
160
|
try {
|
|
161
|
+
await (0, run_guards_1.runGuards)(this.moduleRef, metatype, context);
|
|
153
162
|
// const result = await resource.execute(uri, vars);
|
|
154
|
-
const
|
|
163
|
+
const stream = await (0, run_interceptors_1.runInterceptors)(this.moduleRef, metatype, context, () => {
|
|
155
164
|
return (0, rxjs_1.from)(resource.execute(uri, vars));
|
|
156
165
|
});
|
|
157
|
-
return
|
|
166
|
+
return stream.pipe((0, rxjs_1.catchError)((err) => (0, rxjs_1.from)((0, run_fillters_1.runFilters)(this.moduleRef, metatype, err, context))));
|
|
158
167
|
}
|
|
159
168
|
catch (err) {
|
|
160
|
-
|
|
169
|
+
const result = await (0, run_fillters_1.runFilters)(this.moduleRef, metatype, err, context);
|
|
170
|
+
if (result) {
|
|
171
|
+
return (0, rxjs_1.of)(result);
|
|
172
|
+
}
|
|
173
|
+
// Пробрасываем дальше, чтобы Observable корректно завершился
|
|
174
|
+
return (0, rxjs_1.throwError)(() => err);
|
|
161
175
|
}
|
|
162
176
|
}
|
|
163
177
|
createServer(context) {
|
|
@@ -181,8 +195,9 @@ let McpService = class McpService {
|
|
|
181
195
|
};
|
|
182
196
|
}
|
|
183
197
|
catch (e) {
|
|
184
|
-
|
|
185
|
-
|
|
198
|
+
throw new exceptions_1.McpInternalServerErrorException(`Faild to execute tool ${tool.name}`, {
|
|
199
|
+
cause: e,
|
|
200
|
+
});
|
|
186
201
|
}
|
|
187
202
|
});
|
|
188
203
|
}
|
|
@@ -208,7 +223,9 @@ let McpService = class McpService {
|
|
|
208
223
|
return { messages: messages };
|
|
209
224
|
}
|
|
210
225
|
catch (e) {
|
|
211
|
-
throw new
|
|
226
|
+
throw new exceptions_1.McpInternalServerErrorException(`Faild to execute tool ${prompt.name}`, {
|
|
227
|
+
cause: e,
|
|
228
|
+
});
|
|
212
229
|
}
|
|
213
230
|
});
|
|
214
231
|
}
|
|
@@ -233,7 +250,9 @@ let McpService = class McpService {
|
|
|
233
250
|
return { contents: result };
|
|
234
251
|
}
|
|
235
252
|
catch (e) {
|
|
236
|
-
throw new
|
|
253
|
+
throw new exceptions_1.McpInternalServerErrorException(`Faild to execute tool ${prompt.name}`, {
|
|
254
|
+
cause: e,
|
|
255
|
+
});
|
|
237
256
|
}
|
|
238
257
|
});
|
|
239
258
|
}
|
|
@@ -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
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runGuards = runGuards;
|
|
4
|
-
const common_1 = require("@nestjs/common");
|
|
5
4
|
const constants_1 = require("@nestjs/common/constants");
|
|
5
|
+
const exceptions_1 = require("../exceptions");
|
|
6
6
|
/**
|
|
7
7
|
* Универсальный helper для проверки массивов NestJS Guards
|
|
8
8
|
*/
|
|
@@ -28,7 +28,7 @@ async function runGuards(moduleRef, metatype, context) {
|
|
|
28
28
|
const guardInstance = await resolveGuard(Guard);
|
|
29
29
|
const can = await guardInstance.canActivate(context);
|
|
30
30
|
if (!can) {
|
|
31
|
-
throw new
|
|
31
|
+
throw new exceptions_1.McpUnauthorizedException("Guard blocked execution");
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muzikanto/nestjs-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
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"
|