@muzikanto/nestjs-mcp 1.2.0 → 1.3.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 +6 -0
- package/README.md +24 -18
- package/dist/mcp-server/config.d.ts +6 -0
- package/dist/mcp-server/config.js +7 -0
- package/dist/mcp-server/decorators/mcp-tool.decorator.d.ts +2 -5
- package/dist/mcp-server/mcp.controller.d.ts +6 -13
- package/dist/mcp-server/mcp.controller.js +26 -10
- package/dist/mcp-server/mcp.explorer.js +3 -3
- package/dist/mcp-server/mcp.module.d.ts +4 -0
- package/dist/mcp-server/mcp.module.js +9 -0
- package/dist/mcp-server/mcp.service.d.ts +23 -18
- package/dist/mcp-server/mcp.service.js +86 -32
- package/dist/mcp-server/utils/http-adapter.d.ts +18 -0
- package/dist/mcp-server/utils/http-adapter.js +18 -0
- package/dist/mcp-server/utils/ref.d.ts +1 -0
- package/dist/mcp-server/utils/ref.js +4 -0
- package/dist/mcp-server/utils/run-guards.d.ts +6 -0
- package/dist/mcp-server/utils/run-guards.js +34 -0
- package/dist/mcp-server/utils/run-interceprots.d.ts +11 -0
- package/dist/mcp-server/utils/run-interceprots.js +43 -0
- package/dist/mcp-server/utils/run-interceptors.d.ts +7 -0
- package/dist/mcp-server/utils/run-interceptors.js +40 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@ NestJS MCP (Model Context Protocol) module — allows you to create “tools”,
|
|
|
36
36
|
- Endpoint for calling prompt (`POST /mcp/prompts/:name`)
|
|
37
37
|
- Endpoint for a list of all prompts (`GET /mcp/prompts`)
|
|
38
38
|
- Easy integration with LLM (OpenAI Function Calls)
|
|
39
|
+
- Support http adapters (default fastify)
|
|
39
40
|
- Full TypeScript typing
|
|
40
41
|
|
|
41
42
|
---
|
|
@@ -46,7 +47,7 @@ NestJS MCP (Model Context Protocol) module — allows you to create “tools”,
|
|
|
46
47
|
yarn add @muzikanto/nestjs-mcp
|
|
47
48
|
```
|
|
48
49
|
|
|
49
|
-
Peer dependencies: `@nestjs/common, @nestjs/core, reflect-metadata`
|
|
50
|
+
Peer dependencies: `@nestjs/common, @nestjs/core, @nestjs/swagger, reflect-metadata`
|
|
50
51
|
|
|
51
52
|
## Usage
|
|
52
53
|
|
|
@@ -280,27 +281,32 @@ export class TestResource implements IMcpResource<{ userId: string }> {
|
|
|
280
281
|
|
|
281
282
|
### Auth guard
|
|
282
283
|
```ts
|
|
283
|
-
import {
|
|
284
|
-
import {
|
|
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';
|
|
285
288
|
|
|
286
289
|
@Injectable()
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
return true;
|
|
293
|
-
}
|
|
290
|
+
class TestGuard implements CanActivate {
|
|
291
|
+
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
|
|
292
|
+
console.log('TestGuard called');
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
294
295
|
}
|
|
295
296
|
|
|
296
|
-
@
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
297
|
+
@UseGuard(TestGuard)
|
|
298
|
+
@McpTool()
|
|
299
|
+
export class TelegramSendMessageTool implements IMcpTool<
|
|
300
|
+
{ chatId: string; text: string },
|
|
301
|
+
{ success: boolean }
|
|
302
|
+
> {
|
|
303
|
+
name = 'telegram.sendMessage';
|
|
304
|
+
|
|
305
|
+
async execute() {
|
|
306
|
+
await this.bot.telegram.sendMessage(input.chatId, input.text);
|
|
307
|
+
return { success: true };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
304
310
|
```
|
|
305
311
|
|
|
306
312
|
### Integration with OpenAI Function Calls
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InjectMcpConfig = exports.MCP_CONFIG_TOKEN = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
exports.MCP_CONFIG_TOKEN = "mcl:config-token";
|
|
6
|
+
const InjectMcpConfig = () => (0, common_1.Inject)(exports.MCP_CONFIG_TOKEN);
|
|
7
|
+
exports.InjectMcpConfig = InjectMcpConfig;
|
|
@@ -2,19 +2,16 @@ import "reflect-metadata";
|
|
|
2
2
|
import type * as z3 from "zod/v3";
|
|
3
3
|
type AnySchema = z3.ZodTypeAny;
|
|
4
4
|
type ZodRawShapeCompat = Record<string, AnySchema>;
|
|
5
|
-
export type IMcpToolContext = {
|
|
6
|
-
request: any;
|
|
7
|
-
};
|
|
8
5
|
export interface IMcpTool<Payload = any, Result = any> {
|
|
9
6
|
name: string;
|
|
10
7
|
title?: string;
|
|
11
8
|
description?: string;
|
|
12
9
|
inputSchema?: ZodRawShapeCompat;
|
|
13
|
-
execute(input: Payload
|
|
10
|
+
execute(input: Payload): Promise<Result>;
|
|
14
11
|
}
|
|
15
12
|
export declare const MCP_TOOL_METADATA = "mcp:tool-class";
|
|
16
13
|
/**
|
|
17
14
|
* Декоратор класса для MCP тулзы
|
|
18
15
|
*/
|
|
19
|
-
export declare const McpTool: () =>
|
|
16
|
+
export declare const McpTool: () => ClassDecorator;
|
|
20
17
|
export {};
|
|
@@ -4,22 +4,15 @@ import { McpMessageDto } from "./dto/McpMessage.dto";
|
|
|
4
4
|
import { McpToolsDto } from "./dto/McpTools.dto";
|
|
5
5
|
import { McpPromptsDto } from "./dto/McpPrompts.dto";
|
|
6
6
|
import { McpPromptMessagesDto } from "./dto/McpPromptMessages.dto";
|
|
7
|
-
import {
|
|
7
|
+
import { IMcpConfig } from "./config";
|
|
8
8
|
export declare class McpController {
|
|
9
9
|
private readonly service;
|
|
10
10
|
private readonly guard;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
data: any;
|
|
17
|
-
error?: undefined;
|
|
18
|
-
} | {
|
|
19
|
-
success: boolean;
|
|
20
|
-
error: any;
|
|
21
|
-
data?: undefined;
|
|
22
|
-
}>;
|
|
11
|
+
private readonly config;
|
|
12
|
+
constructor(service: McpService, guard: CanActivate, config: IMcpConfig);
|
|
13
|
+
handleSse(rawReq: any, rawRes: any, context: ExecutionContext): Promise<void>;
|
|
14
|
+
handleSseMessages(rawReq: any, rawRes: any, context: ExecutionContext): Promise<void>;
|
|
15
|
+
handleTool(body: McpMessageDto, request: any, context: ExecutionContext): Promise<any>;
|
|
23
16
|
getTools(context: ExecutionContext): Promise<McpToolsDto>;
|
|
24
17
|
getPrompts(context: ExecutionContext): Promise<McpPromptsDto>;
|
|
25
18
|
getPrompt(name: string, body: object, context: ExecutionContext): Promise<McpPromptMessagesDto>;
|
|
@@ -22,22 +22,34 @@ const McpPrompts_dto_1 = require("./dto/McpPrompts.dto");
|
|
|
22
22
|
const McpPromptMessages_dto_1 = require("./dto/McpPromptMessages.dto");
|
|
23
23
|
const context_decorator_1 = require("./utils/context.decorator");
|
|
24
24
|
const inject_tokens_1 = require("./utils/inject-tokens");
|
|
25
|
+
const config_1 = require("./config");
|
|
26
|
+
const rxjs_1 = require("rxjs");
|
|
25
27
|
let McpController = class McpController {
|
|
26
28
|
service;
|
|
27
29
|
guard;
|
|
28
|
-
|
|
30
|
+
config;
|
|
31
|
+
constructor(service, guard, config) {
|
|
29
32
|
this.service = service;
|
|
30
33
|
this.guard = guard;
|
|
34
|
+
this.config = config;
|
|
31
35
|
}
|
|
32
|
-
async handleSse(
|
|
33
|
-
await this.
|
|
36
|
+
async handleSse(rawReq, rawRes, context) {
|
|
37
|
+
await this.checkGuard(context);
|
|
38
|
+
const request = this.config.httpAdapter.getRequest(rawReq);
|
|
39
|
+
const response = this.config.httpAdapter.getResponse(rawRes);
|
|
40
|
+
await this.service.handleSse(request, response, context);
|
|
34
41
|
}
|
|
35
|
-
async handleSseMessages(
|
|
36
|
-
await this.
|
|
42
|
+
async handleSseMessages(rawReq, rawRes, context) {
|
|
43
|
+
await this.checkGuard(context);
|
|
44
|
+
const request = this.config.httpAdapter.getRequest(rawReq);
|
|
45
|
+
const response = this.config.httpAdapter.getResponse(rawRes);
|
|
46
|
+
await this.service.handleSseMessage(request, response);
|
|
37
47
|
}
|
|
38
48
|
async handleTool(body, request, context) {
|
|
39
49
|
await this.checkGuard(context);
|
|
40
|
-
|
|
50
|
+
const observable = await this.service.executeTool(body, context);
|
|
51
|
+
const result = await (0, rxjs_1.firstValueFrom)(observable);
|
|
52
|
+
return result;
|
|
41
53
|
}
|
|
42
54
|
async getTools(context) {
|
|
43
55
|
await this.checkGuard(context);
|
|
@@ -51,7 +63,8 @@ let McpController = class McpController {
|
|
|
51
63
|
}
|
|
52
64
|
async getPrompt(name, body, context) {
|
|
53
65
|
await this.checkGuard(context);
|
|
54
|
-
const
|
|
66
|
+
const observable = await this.service.executePrompt(name, body, context);
|
|
67
|
+
const messages = await (0, rxjs_1.firstValueFrom)(observable);
|
|
55
68
|
return {
|
|
56
69
|
messages,
|
|
57
70
|
};
|
|
@@ -70,8 +83,9 @@ __decorate([
|
|
|
70
83
|
}),
|
|
71
84
|
__param(0, (0, common_1.Req)()),
|
|
72
85
|
__param(1, (0, common_1.Res)()),
|
|
86
|
+
__param(2, (0, context_decorator_1.Context)()),
|
|
73
87
|
__metadata("design:type", Function),
|
|
74
|
-
__metadata("design:paramtypes", [Object, Object]),
|
|
88
|
+
__metadata("design:paramtypes", [Object, Object, Object]),
|
|
75
89
|
__metadata("design:returntype", Promise)
|
|
76
90
|
], McpController.prototype, "handleSse", null);
|
|
77
91
|
__decorate([
|
|
@@ -81,8 +95,9 @@ __decorate([
|
|
|
81
95
|
}),
|
|
82
96
|
__param(0, (0, common_1.Req)()),
|
|
83
97
|
__param(1, (0, common_1.Res)()),
|
|
98
|
+
__param(2, (0, context_decorator_1.Context)()),
|
|
84
99
|
__metadata("design:type", Function),
|
|
85
|
-
__metadata("design:paramtypes", [Object, Object]),
|
|
100
|
+
__metadata("design:paramtypes", [Object, Object, Object]),
|
|
86
101
|
__metadata("design:returntype", Promise)
|
|
87
102
|
], McpController.prototype, "handleSseMessages", null);
|
|
88
103
|
__decorate([
|
|
@@ -177,5 +192,6 @@ __decorate([
|
|
|
177
192
|
exports.McpController = McpController = __decorate([
|
|
178
193
|
(0, common_1.Controller)("mcp"),
|
|
179
194
|
__param(1, (0, common_1.Inject)(inject_tokens_1.MCP_GUARD)),
|
|
180
|
-
|
|
195
|
+
__param(2, (0, config_1.InjectMcpConfig)()),
|
|
196
|
+
__metadata("design:paramtypes", [mcp_service_1.McpService, Object, Object])
|
|
181
197
|
], McpController);
|
|
@@ -35,7 +35,7 @@ let McpExplorer = class McpExplorer {
|
|
|
35
35
|
if (typeof instance.execute !== "function") {
|
|
36
36
|
throw new Error(`MCP Tool ${instance.name} must implement IMcpTool with execute() method`);
|
|
37
37
|
}
|
|
38
|
-
this.mcpService.registerTool(instance.name, instance);
|
|
38
|
+
this.mcpService.registerTool(instance.name, { instance, metatype });
|
|
39
39
|
});
|
|
40
40
|
providers.forEach(({ instance, metatype }) => {
|
|
41
41
|
if (!instance || !metatype)
|
|
@@ -47,7 +47,7 @@ let McpExplorer = class McpExplorer {
|
|
|
47
47
|
if (typeof instance.execute !== "function") {
|
|
48
48
|
throw new Error(`MCP Prompt ${instance.name} must implement IMcpPrompt with execute() method`);
|
|
49
49
|
}
|
|
50
|
-
this.mcpService.registerPrompt(instance.name, instance);
|
|
50
|
+
this.mcpService.registerPrompt(instance.name, { instance, metatype });
|
|
51
51
|
});
|
|
52
52
|
providers.forEach(({ instance, metatype }) => {
|
|
53
53
|
if (!instance || !metatype)
|
|
@@ -59,7 +59,7 @@ let McpExplorer = class McpExplorer {
|
|
|
59
59
|
if (typeof instance.execute !== "function") {
|
|
60
60
|
throw new Error(`MCP Resource ${instance.name} must implement IMcpResource with execute() method`);
|
|
61
61
|
}
|
|
62
|
-
this.mcpService.registerResource(instance.name, instance);
|
|
62
|
+
this.mcpService.registerResource(instance.name, { instance, metatype });
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
65
|
};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { CanActivate, DynamicModule, ModuleMetadata, Provider } from "@nestjs/common";
|
|
2
|
+
import { IHttpAdapter } from "./utils/http-adapter";
|
|
2
3
|
type Metadata = Pick<ModuleMetadata, "providers" | "imports" | "exports"> & {
|
|
3
4
|
guard?: Provider<CanActivate>;
|
|
5
|
+
name?: string;
|
|
6
|
+
version?: string;
|
|
7
|
+
httpAdapter?: IHttpAdapter;
|
|
4
8
|
};
|
|
5
9
|
export declare class McpModule {
|
|
6
10
|
static forRoot(metadata?: Metadata): DynamicModule;
|
|
@@ -14,9 +14,17 @@ const mcp_service_1 = require("./mcp.service");
|
|
|
14
14
|
const mcp_explorer_1 = require("./mcp.explorer");
|
|
15
15
|
const mcp_controller_1 = require("./mcp.controller");
|
|
16
16
|
const inject_tokens_1 = require("./utils/inject-tokens");
|
|
17
|
+
const config_1 = require("./config");
|
|
18
|
+
const http_adapter_1 = require("./utils/http-adapter");
|
|
17
19
|
const publicGuard = { canActivate: () => Promise.resolve(true) };
|
|
18
20
|
let McpModule = McpModule_1 = class McpModule {
|
|
19
21
|
static forRoot(metadata = {}) {
|
|
22
|
+
const configProviver = {
|
|
23
|
+
provide: config_1.MCP_CONFIG_TOKEN,
|
|
24
|
+
useValue: {
|
|
25
|
+
httpAdapter: metadata.httpAdapter || http_adapter_1.DEFAULT_FASTIFY_ADAPTER,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
20
28
|
const guardProvider = metadata.guard
|
|
21
29
|
? { provide: inject_tokens_1.MCP_GUARD, useExisting: metadata.guard }
|
|
22
30
|
: {
|
|
@@ -29,6 +37,7 @@ let McpModule = McpModule_1 = class McpModule {
|
|
|
29
37
|
providers: [
|
|
30
38
|
mcp_service_1.McpService,
|
|
31
39
|
mcp_explorer_1.McpExplorer,
|
|
40
|
+
configProviver,
|
|
32
41
|
guardProvider,
|
|
33
42
|
...(metadata.guard ? [metadata.guard] : []),
|
|
34
43
|
...(metadata.providers || []),
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { OnModuleInit } from "@nestjs/common";
|
|
2
|
-
import { IMcpTool
|
|
1
|
+
import { ExecutionContext, OnModuleInit } from "@nestjs/common";
|
|
2
|
+
import { IMcpTool } from "./decorators/mcp-tool.decorator";
|
|
3
3
|
import { IMcpPrompt } from "./decorators/mcp-prompt.decorator";
|
|
4
|
-
import { IRequest, IResponse } from "./utils/http";
|
|
4
|
+
import { IRequest, IResponse } from "./utils/http-adapter";
|
|
5
5
|
import { IMcpResource } from "./decorators/mcp-resource.decorator";
|
|
6
|
+
import { ModuleRef } from "@nestjs/core";
|
|
6
7
|
export interface McpMessage {
|
|
7
8
|
type: string;
|
|
8
9
|
payload: any;
|
|
@@ -13,13 +14,15 @@ export interface McpResponse {
|
|
|
13
14
|
error?: string;
|
|
14
15
|
}
|
|
15
16
|
export declare class McpService implements OnModuleInit {
|
|
17
|
+
protected readonly moduleRef: ModuleRef;
|
|
16
18
|
private tools;
|
|
17
19
|
private prompts;
|
|
18
20
|
private resources;
|
|
19
21
|
private ajv;
|
|
20
22
|
private sessions;
|
|
23
|
+
constructor(moduleRef: ModuleRef);
|
|
21
24
|
onModuleInit(): void;
|
|
22
|
-
handleSse(
|
|
25
|
+
handleSse(_: IRequest, res: IResponse, context: ExecutionContext): Promise<void>;
|
|
23
26
|
handleSseMessage(req: IRequest, res: IResponse): Promise<void>;
|
|
24
27
|
/**
|
|
25
28
|
* Возвращает список зарегистрированных тулз
|
|
@@ -36,24 +39,26 @@ export declare class McpService implements OnModuleInit {
|
|
|
36
39
|
title: string | undefined;
|
|
37
40
|
inputSchema: any;
|
|
38
41
|
}[];
|
|
39
|
-
registerTool(name: string, handler:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
registerTool(name: string, handler: {
|
|
43
|
+
instance: IMcpTool;
|
|
44
|
+
metatype: Function;
|
|
45
|
+
}): void;
|
|
46
|
+
registerPrompt(name: string, handler: {
|
|
47
|
+
instance: IMcpPrompt;
|
|
48
|
+
metatype: Function;
|
|
49
|
+
}): void;
|
|
50
|
+
registerResource(name: string, handler: {
|
|
51
|
+
instance: IMcpResource;
|
|
52
|
+
metatype: Function;
|
|
53
|
+
}): void;
|
|
54
|
+
executePrompt(name: string, payload: object, context: ExecutionContext): Promise<import("rxjs").Observable<any>>;
|
|
43
55
|
/**
|
|
44
56
|
* Отправить сообщение в MCP "сервер"
|
|
45
57
|
*/
|
|
46
|
-
|
|
58
|
+
executeTool(msg: {
|
|
47
59
|
type: string;
|
|
48
60
|
payload: any;
|
|
49
|
-
}, context:
|
|
50
|
-
|
|
51
|
-
data: any;
|
|
52
|
-
error?: undefined;
|
|
53
|
-
} | {
|
|
54
|
-
success: boolean;
|
|
55
|
-
error: any;
|
|
56
|
-
data?: undefined;
|
|
57
|
-
}>;
|
|
61
|
+
}, context: ExecutionContext): Promise<import("rxjs").Observable<any>>;
|
|
62
|
+
executeResource(name: string, uri: URL, vars: Record<string, any>, context: ExecutionContext): Promise<import("rxjs").Observable<any>>;
|
|
58
63
|
private createServer;
|
|
59
64
|
}
|
|
@@ -5,6 +5,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
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
6
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
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
|
+
};
|
|
8
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
12
|
exports.McpService = void 0;
|
|
10
13
|
const common_1 = require("@nestjs/common");
|
|
@@ -12,45 +15,53 @@ const ajv_1 = require("ajv");
|
|
|
12
15
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
13
16
|
const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
14
17
|
const zod_1 = require("./utils/zod");
|
|
18
|
+
const run_guards_1 = require("./utils/run-guards");
|
|
19
|
+
const core_1 = require("@nestjs/core");
|
|
20
|
+
const run_interceptors_1 = require("./utils/run-interceptors");
|
|
21
|
+
const rxjs_1 = require("rxjs");
|
|
15
22
|
let McpService = class McpService {
|
|
23
|
+
moduleRef;
|
|
16
24
|
tools = new Map();
|
|
17
25
|
prompts = new Map();
|
|
18
26
|
resources = new Map();
|
|
19
27
|
ajv = new ajv_1.default();
|
|
20
28
|
sessions = new Map();
|
|
29
|
+
constructor(moduleRef) {
|
|
30
|
+
this.moduleRef = moduleRef;
|
|
31
|
+
}
|
|
21
32
|
onModuleInit() {
|
|
22
33
|
// console.log('MCP Service initialized');
|
|
23
34
|
}
|
|
24
|
-
async handleSse(
|
|
35
|
+
async handleSse(_, res, context) {
|
|
25
36
|
try {
|
|
26
|
-
const transport = new sse_js_1.SSEServerTransport("/api/mcp/messages", res.
|
|
37
|
+
const transport = new sse_js_1.SSEServerTransport("/api/mcp/messages", res.original);
|
|
27
38
|
const sessionId = transport.sessionId;
|
|
28
39
|
this.sessions.set(sessionId, transport);
|
|
29
|
-
res.
|
|
40
|
+
res.original.on("close", () => {
|
|
30
41
|
this.sessions.delete(sessionId);
|
|
31
42
|
});
|
|
32
|
-
const server = this.createServer();
|
|
43
|
+
const server = this.createServer(context);
|
|
33
44
|
await server.connect(transport);
|
|
34
45
|
}
|
|
35
46
|
catch (e) {
|
|
36
|
-
res.
|
|
47
|
+
res.send(500, "Failed to initialize session");
|
|
37
48
|
}
|
|
38
49
|
}
|
|
39
50
|
async handleSseMessage(req, res) {
|
|
40
51
|
const sessionId = req.query.sessionId;
|
|
41
52
|
const transport = this.sessions.get(sessionId);
|
|
42
53
|
if (transport) {
|
|
43
|
-
await transport.handlePostMessage(req.
|
|
54
|
+
await transport.handlePostMessage(req.original, res.original, req.body);
|
|
44
55
|
}
|
|
45
56
|
else {
|
|
46
|
-
res.
|
|
57
|
+
res.send(500, "Failed to handle message");
|
|
47
58
|
}
|
|
48
59
|
}
|
|
49
60
|
/**
|
|
50
61
|
* Возвращает список зарегистрированных тулз
|
|
51
62
|
*/
|
|
52
63
|
listTools() {
|
|
53
|
-
return Array.from(this.tools.values()).map((t) => ({
|
|
64
|
+
return Array.from(this.tools.values()).map(({ instance: t }) => ({
|
|
54
65
|
name: t.name,
|
|
55
66
|
title: t.title,
|
|
56
67
|
description: t.description,
|
|
@@ -58,7 +69,7 @@ let McpService = class McpService {
|
|
|
58
69
|
}));
|
|
59
70
|
}
|
|
60
71
|
listPrompts() {
|
|
61
|
-
return Array.from(this.prompts.values()).map((t) => ({
|
|
72
|
+
return Array.from(this.prompts.values()).map(({ instance: t }) => ({
|
|
62
73
|
name: t.name,
|
|
63
74
|
description: t.description,
|
|
64
75
|
title: t.title,
|
|
@@ -74,11 +85,15 @@ let McpService = class McpService {
|
|
|
74
85
|
registerResource(name, handler) {
|
|
75
86
|
this.resources.set(name, handler);
|
|
76
87
|
}
|
|
77
|
-
|
|
88
|
+
async executePrompt(name, payload, context) {
|
|
78
89
|
if (!this.prompts.has(name)) {
|
|
79
90
|
throw new common_1.NotFoundException("Not found prompt");
|
|
80
91
|
}
|
|
81
|
-
const prompt = this.prompts.get(name);
|
|
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);
|
|
82
97
|
// Валидация через AJV, если есть inputSchema
|
|
83
98
|
if (prompt.inputSchema) {
|
|
84
99
|
const zodSchema = (0, zod_1.zodToJsonSchema)(prompt.inputSchema);
|
|
@@ -88,43 +103,77 @@ let McpService = class McpService {
|
|
|
88
103
|
throw new common_1.NotFoundException("Invalid prompt arguments");
|
|
89
104
|
}
|
|
90
105
|
}
|
|
91
|
-
|
|
106
|
+
try {
|
|
107
|
+
// const result = await prompt.execute(payload);
|
|
108
|
+
const result = await (0, run_interceptors_1.runInterceptors)(this.moduleRef, metatype, context, () => {
|
|
109
|
+
return (0, rxjs_1.from)(prompt.execute(payload));
|
|
110
|
+
});
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
throw new common_1.InternalServerErrorException("Failed to execute prompt");
|
|
115
|
+
}
|
|
92
116
|
}
|
|
93
117
|
/**
|
|
94
118
|
* Отправить сообщение в MCP "сервер"
|
|
95
119
|
*/
|
|
96
|
-
async
|
|
97
|
-
const tool = this.tools.get(msg.type);
|
|
98
|
-
if (!tool) {
|
|
99
|
-
|
|
120
|
+
async executeTool(msg, context) {
|
|
121
|
+
const { instance: tool, metatype } = this.tools.get(msg.type) || {};
|
|
122
|
+
if (!tool || !metatype) {
|
|
123
|
+
throw new common_1.NotFoundException(`Unknown tool: "${msg.type}"`);
|
|
100
124
|
}
|
|
125
|
+
await (0, run_guards_1.runGuards)(this.moduleRef, metatype, context);
|
|
101
126
|
// Валидация через AJV, если есть inputSchema
|
|
102
127
|
if (tool.inputSchema) {
|
|
103
128
|
const zodSchema = (0, zod_1.zodToJsonSchema)(tool.inputSchema);
|
|
104
129
|
const validate = this.ajv.compile(zodSchema);
|
|
105
130
|
const valid = validate(msg.payload);
|
|
106
131
|
if (!valid) {
|
|
107
|
-
|
|
132
|
+
throw new common_1.BadRequestException(this.ajv.errorsText(validate.errors));
|
|
108
133
|
}
|
|
109
134
|
}
|
|
110
135
|
try {
|
|
111
|
-
const result = await tool.execute(msg.payload
|
|
112
|
-
|
|
136
|
+
// const result = await tool.execute(msg.payload);
|
|
137
|
+
const result = await (0, run_interceptors_1.runInterceptors)(this.moduleRef, metatype, context, () => {
|
|
138
|
+
return (0, rxjs_1.from)(tool.execute(msg.payload));
|
|
139
|
+
});
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
throw new common_1.InternalServerErrorException("Failed to execute tool");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async executeResource(name, uri, vars, context) {
|
|
147
|
+
const { instance: resource, metatype } = this.resources.get(name) || {};
|
|
148
|
+
if (!resource || !metatype) {
|
|
149
|
+
throw new common_1.NotFoundException(`Unknown resource: "${name}"`);
|
|
150
|
+
}
|
|
151
|
+
await (0, run_guards_1.runGuards)(this.moduleRef, metatype, context);
|
|
152
|
+
try {
|
|
153
|
+
// const result = await resource.execute(uri, vars);
|
|
154
|
+
const result = await (0, run_interceptors_1.runInterceptors)(this.moduleRef, metatype, context, () => {
|
|
155
|
+
return (0, rxjs_1.from)(resource.execute(uri, vars));
|
|
156
|
+
});
|
|
157
|
+
return result;
|
|
113
158
|
}
|
|
114
159
|
catch (err) {
|
|
115
|
-
|
|
160
|
+
throw new common_1.InternalServerErrorException("Failed to execute tool");
|
|
116
161
|
}
|
|
117
162
|
}
|
|
118
|
-
createServer() {
|
|
163
|
+
createServer(context) {
|
|
119
164
|
const server = new mcp_js_1.McpServer({ version: "1", name: "test" }, {});
|
|
120
|
-
for (const [toolName, tool] of this.tools.entries()) {
|
|
165
|
+
for (const [toolName, { instance: tool }] of this.tools.entries()) {
|
|
121
166
|
server.registerTool(tool.name, {
|
|
122
167
|
title: tool.title,
|
|
123
168
|
description: tool.description,
|
|
124
169
|
inputSchema: tool.inputSchema,
|
|
125
|
-
}, async (
|
|
170
|
+
}, async (payload) => {
|
|
126
171
|
try {
|
|
127
|
-
const result = await tool.execute(
|
|
172
|
+
// const result = await tool.execute(
|
|
173
|
+
// { ...payload },
|
|
174
|
+
// );
|
|
175
|
+
const observable = await this.executeTool({ type: tool.name, payload }, context);
|
|
176
|
+
const result = await (0, rxjs_1.firstValueFrom)(observable);
|
|
128
177
|
return {
|
|
129
178
|
content: [
|
|
130
179
|
{ text: JSON.stringify(result), type: "text" },
|
|
@@ -137,15 +186,17 @@ let McpService = class McpService {
|
|
|
137
186
|
}
|
|
138
187
|
});
|
|
139
188
|
}
|
|
140
|
-
for (const [promptName, prompt] of this.prompts.entries()) {
|
|
189
|
+
for (const [promptName, { instance: prompt }] of this.prompts.entries()) {
|
|
141
190
|
server.registerPrompt(prompt.name, {
|
|
142
191
|
title: prompt.title,
|
|
143
192
|
description: prompt.description,
|
|
144
193
|
argsSchema: prompt.inputSchema,
|
|
145
|
-
}, async (
|
|
194
|
+
}, async (payload) => {
|
|
146
195
|
try {
|
|
147
|
-
const
|
|
148
|
-
const
|
|
196
|
+
// const result = await prompt.execute({ ...params });
|
|
197
|
+
const observable = await this.executePrompt(prompt.name, payload, context);
|
|
198
|
+
const result = await (0, rxjs_1.firstValueFrom)(observable);
|
|
199
|
+
const messages = result.map((el) => ({
|
|
149
200
|
role: (el.role === "system" ? "assistant" : el.role),
|
|
150
201
|
content: el.tool_call
|
|
151
202
|
? {
|
|
@@ -161,7 +212,7 @@ let McpService = class McpService {
|
|
|
161
212
|
}
|
|
162
213
|
});
|
|
163
214
|
}
|
|
164
|
-
for (const [_, resource] of this.resources.entries()) {
|
|
215
|
+
for (const [_, { instance: resource }] of this.resources.entries()) {
|
|
165
216
|
const resourceList = resource.list;
|
|
166
217
|
server.registerResource(resource.name, new mcp_js_1.ResourceTemplate(resource.uri, {
|
|
167
218
|
list: resourceList
|
|
@@ -176,8 +227,10 @@ let McpService = class McpService {
|
|
|
176
227
|
description: resource.description,
|
|
177
228
|
}, async (url, variables) => {
|
|
178
229
|
try {
|
|
179
|
-
const resources = await resource.execute(url, variables);
|
|
180
|
-
|
|
230
|
+
// const resources = await resource.execute(url, variables);
|
|
231
|
+
const observable = await this.executeResource(resource.name, url, variables, context);
|
|
232
|
+
const result = await (0, rxjs_1.firstValueFrom)(observable);
|
|
233
|
+
return { contents: result };
|
|
181
234
|
}
|
|
182
235
|
catch (e) {
|
|
183
236
|
throw new Error(`Faild to execute tool ${prompt.name}`);
|
|
@@ -189,5 +242,6 @@ let McpService = class McpService {
|
|
|
189
242
|
};
|
|
190
243
|
exports.McpService = McpService;
|
|
191
244
|
exports.McpService = McpService = __decorate([
|
|
192
|
-
(0, common_1.Injectable)()
|
|
245
|
+
(0, common_1.Injectable)(),
|
|
246
|
+
__metadata("design:paramtypes", [core_1.ModuleRef])
|
|
193
247
|
], McpService);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types";
|
|
2
|
+
import { IncomingMessage, ServerResponse } from "http";
|
|
3
|
+
export type IRequest = {
|
|
4
|
+
original: IncomingMessage & {
|
|
5
|
+
auth?: AuthInfo;
|
|
6
|
+
};
|
|
7
|
+
query?: object;
|
|
8
|
+
body?: object;
|
|
9
|
+
};
|
|
10
|
+
export type IResponse = {
|
|
11
|
+
original: ServerResponse;
|
|
12
|
+
send: (statusCode: number, result: string) => void;
|
|
13
|
+
};
|
|
14
|
+
export type IHttpAdapter = {
|
|
15
|
+
getRequest: (req: any) => IRequest;
|
|
16
|
+
getResponse: (res: any) => IResponse;
|
|
17
|
+
};
|
|
18
|
+
export declare const DEFAULT_FASTIFY_ADAPTER: IHttpAdapter;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_FASTIFY_ADAPTER = void 0;
|
|
4
|
+
exports.DEFAULT_FASTIFY_ADAPTER = {
|
|
5
|
+
getRequest: (fastifyRequest) => {
|
|
6
|
+
return {
|
|
7
|
+
original: fastifyRequest.raw,
|
|
8
|
+
body: fastifyRequest.body,
|
|
9
|
+
query: fastifyRequest.query,
|
|
10
|
+
};
|
|
11
|
+
},
|
|
12
|
+
getResponse: (fastifyResponse) => {
|
|
13
|
+
return {
|
|
14
|
+
original: fastifyResponse.raw,
|
|
15
|
+
send: (code, result) => fastifyResponse.status(code).send(result),
|
|
16
|
+
};
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const MCP_MODULE_REF_METADATA = "mcp:module-ref";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ExecutionContext } from "@nestjs/common";
|
|
2
|
+
import { ModuleRef } from "@nestjs/core";
|
|
3
|
+
/**
|
|
4
|
+
* Универсальный helper для проверки массивов NestJS Guards
|
|
5
|
+
*/
|
|
6
|
+
export declare function runGuards(moduleRef: ModuleRef, metatype: Function, context: ExecutionContext): Promise<void>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runGuards = runGuards;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
const constants_1 = require("@nestjs/common/constants");
|
|
6
|
+
/**
|
|
7
|
+
* Универсальный helper для проверки массивов NestJS Guards
|
|
8
|
+
*/
|
|
9
|
+
async function runGuards(moduleRef, metatype, context) {
|
|
10
|
+
const resolveGuard = async (Guard) => {
|
|
11
|
+
if (typeof Guard !== "function") {
|
|
12
|
+
return Guard;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return moduleRef.get(Guard, { strict: false });
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
try {
|
|
19
|
+
return await moduleRef.create(Guard);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return new Guard();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const guards = Reflect.getMetadata(constants_1.GUARDS_METADATA, metatype) || [];
|
|
27
|
+
for (const Guard of guards) {
|
|
28
|
+
const guardInstance = await resolveGuard(Guard);
|
|
29
|
+
const can = await guardInstance.canActivate(context);
|
|
30
|
+
if (!can) {
|
|
31
|
+
throw new common_1.UnauthorizedException("Guard blocked execution");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ExecutionContext, NestInterceptor, Type } from '@nestjs/common';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
import 'reflect-metadata';
|
|
4
|
+
/**
|
|
5
|
+
* Helper для запуска цепочки NestJS Interceptors полностью через Observable
|
|
6
|
+
*/
|
|
7
|
+
export declare function runInterceptorsObservable(interceptors: Type<NestInterceptor>[] | undefined, context: ExecutionContext, nextFn: () => any): Observable<any>;
|
|
8
|
+
/**
|
|
9
|
+
* Декоратор класса, который оборачивает execute и запускает interceptors через Observable
|
|
10
|
+
*/
|
|
11
|
+
export declare function WithNestInterceptors(): ClassDecorator;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runInterceptorsObservable = runInterceptorsObservable;
|
|
4
|
+
exports.WithNestInterceptors = WithNestInterceptors;
|
|
5
|
+
const rxjs_1 = require("rxjs");
|
|
6
|
+
require("reflect-metadata");
|
|
7
|
+
const constants_1 = require("@nestjs/common/constants");
|
|
8
|
+
/**
|
|
9
|
+
* Helper для запуска цепочки NestJS Interceptors полностью через Observable
|
|
10
|
+
*/
|
|
11
|
+
function runInterceptorsObservable(interceptors = [], context, nextFn) {
|
|
12
|
+
let handler = nextFn;
|
|
13
|
+
// Применяем interceptors в обратном порядке
|
|
14
|
+
for (const InterceptorClass of interceptors.reverse()) {
|
|
15
|
+
const interceptorInstance = new InterceptorClass();
|
|
16
|
+
const originalHandler = handler;
|
|
17
|
+
// handler = функция, которая возвращает Observable
|
|
18
|
+
handler = () => {
|
|
19
|
+
const result = interceptorInstance.intercept(context, {
|
|
20
|
+
handle: () => (0, rxjs_1.of)(originalHandler()), // handle всегда Observable
|
|
21
|
+
});
|
|
22
|
+
return result;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// Возвращаем результат как Observable
|
|
26
|
+
return handler();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Декоратор класса, который оборачивает execute и запускает interceptors через Observable
|
|
30
|
+
*/
|
|
31
|
+
function WithNestInterceptors() {
|
|
32
|
+
return (target) => {
|
|
33
|
+
const originalExecute = target.prototype.execute;
|
|
34
|
+
if (originalExecute) {
|
|
35
|
+
target.prototype.execute = async function (input, context) {
|
|
36
|
+
const appliedInterceptors = Reflect.getMetadata(constants_1.INTERCEPTORS_METADATA, target) || [];
|
|
37
|
+
// Превращаем результат execute в Observable
|
|
38
|
+
const executeObservable = () => (originalExecute.call(this, input));
|
|
39
|
+
return runInterceptorsObservable(appliedInterceptors, context, executeObservable);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ExecutionContext } from "@nestjs/common";
|
|
2
|
+
import { ModuleRef } from "@nestjs/core";
|
|
3
|
+
import { Observable } from "rxjs";
|
|
4
|
+
/**
|
|
5
|
+
* Универсальный helper для запуска массива NestJS Interceptors через DI
|
|
6
|
+
*/
|
|
7
|
+
export declare function runInterceptors(moduleRef: ModuleRef, metatype: Function, context: ExecutionContext, nextFn: () => Observable<any>): Promise<Observable<any>>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runInterceptors = runInterceptors;
|
|
4
|
+
const rxjs_1 = require("rxjs");
|
|
5
|
+
const constants_1 = require("@nestjs/common/constants");
|
|
6
|
+
/**
|
|
7
|
+
* Универсальный helper для запуска массива NestJS Interceptors через DI
|
|
8
|
+
*/
|
|
9
|
+
async function runInterceptors(moduleRef, metatype, context, nextFn) {
|
|
10
|
+
const resolveInterceptor = async (Interceptor) => {
|
|
11
|
+
if (typeof Interceptor !== "function") {
|
|
12
|
+
return Interceptor;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return moduleRef.get(Interceptor, { strict: false });
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
try {
|
|
19
|
+
return await moduleRef.create(Interceptor);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return new Interceptor();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const interceptors = Reflect.getMetadata(constants_1.INTERCEPTORS_METADATA, metatype) || [];
|
|
27
|
+
// Применяем interceptors в обратном порядке (как NestJS)
|
|
28
|
+
let handler = nextFn;
|
|
29
|
+
for (const InterceptorClass of interceptors.reverse()) {
|
|
30
|
+
const interceptorInstance = await resolveInterceptor(InterceptorClass);
|
|
31
|
+
const originalHandler = handler;
|
|
32
|
+
handler = () => {
|
|
33
|
+
const result = interceptorInstance.intercept(context, {
|
|
34
|
+
handle: () => originalHandler(),
|
|
35
|
+
});
|
|
36
|
+
return result instanceof rxjs_1.Observable ? result : (0, rxjs_1.from)(result);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return handler();
|
|
40
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muzikanto/nestjs-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
37
37
|
"ajv": "^8.18.0",
|
|
38
|
+
"rxjs": "^7.8.2",
|
|
38
39
|
"zod": "^4.3.6",
|
|
39
40
|
"zod-to-json-schema": "^3.25.1"
|
|
40
41
|
},
|