@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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @muzikanto/nestjs-mcp
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a5c79ec: add guards and interceptors
8
+
3
9
  ## 1.2.0
4
10
 
5
11
  ### Minor Changes
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 { McpModule } from '@muzikanto/nestjs-mcp';
284
- import { Module, CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
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
- export class TestGuard implements CanActivate {
288
- canActivate(context: ExecutionContext): Promise<boolean> {
289
- const request = context.switchToHttp().getRequest();
290
- const authHeader = request.headers['authorization'];
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
- @Module({
297
- imports: [
298
- McpModule.forRoot({
299
- guard: TestGuard
300
- }),
301
- ],
302
- })
303
- export class AppModule {}
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,6 @@
1
+ import { IHttpAdapter } from "./utils/http-adapter";
2
+ export declare const MCP_CONFIG_TOKEN = "mcl:config-token";
3
+ export type IMcpConfig = {
4
+ httpAdapter: IHttpAdapter;
5
+ };
6
+ export declare const InjectMcpConfig: () => PropertyDecorator & ParameterDecorator;
@@ -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, context: IMcpToolContext): Promise<Result>;
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: () => (target: any) => void;
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 { IRequest, IResponse } from "./utils/http";
7
+ import { IMcpConfig } from "./config";
8
8
  export declare class McpController {
9
9
  private readonly service;
10
10
  private readonly guard;
11
- constructor(service: McpService, guard: CanActivate);
12
- handleSse(req: IRequest, res: IResponse): Promise<void>;
13
- handleSseMessages(req: IRequest, res: IResponse): Promise<void>;
14
- handleTool(body: McpMessageDto, request: any, context: ExecutionContext): Promise<{
15
- success: boolean;
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
- constructor(service, guard) {
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(req, res) {
33
- await this.service.handleSse(req, res);
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(req, res) {
36
- await this.service.handleSseMessage(req, res);
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
- return this.service.sendMessage(body, { request });
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 messages = await this.service.getPrompt(name, body);
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
- __metadata("design:paramtypes", [mcp_service_1.McpService, Object])
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, IMcpToolContext } from "./decorators/mcp-tool.decorator";
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(req: IRequest, res: IResponse): Promise<void>;
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: IMcpTool): void;
40
- registerPrompt(name: string, handler: IMcpPrompt): void;
41
- registerResource(name: string, handler: IMcpResource): void;
42
- getPrompt(name: string, payload: object): Promise<import("./decorators/mcp-prompt.decorator").IMcpPromptMessage[]>;
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
- sendMessage(msg: {
58
+ executeTool(msg: {
47
59
  type: string;
48
60
  payload: any;
49
- }, context: IMcpToolContext): Promise<{
50
- success: boolean;
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(req, res) {
35
+ async handleSse(_, res, context) {
25
36
  try {
26
- const transport = new sse_js_1.SSEServerTransport("/api/mcp/messages", res.raw || 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.raw.on("close", () => {
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.status(500).send("Failed to initialize session");
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.raw || req, res.raw || res, req.body);
54
+ await transport.handlePostMessage(req.original, res.original, req.body);
44
55
  }
45
56
  else {
46
- res.status(500).send("Failed to handle message");
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
- getPrompt(name, payload) {
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
- return prompt.execute(payload);
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 sendMessage(msg, context) {
97
- const tool = this.tools.get(msg.type);
98
- if (!tool) {
99
- return { success: false, error: `Unknown tool: "${msg.type}"` };
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
- return { success: false, error: this.ajv.errorsText(validate.errors) };
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, context);
112
- return { success: true, data: result };
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
- return { success: false, error: err.message };
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 (params) => {
170
+ }, async (payload) => {
126
171
  try {
127
- const result = await tool.execute({ ...params }, { request: undefined });
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 (params) => {
194
+ }, async (payload) => {
146
195
  try {
147
- const openaiMessage = await prompt.execute({ ...params });
148
- const messages = openaiMessage.map((el) => ({
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
- return { contents: resources };
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,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MCP_MODULE_REF_METADATA = void 0;
4
+ exports.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.2.0",
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
  },