@nestjs-mcp/server 0.1.0-alpha.10

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.
Files changed (100) hide show
  1. package/.copilotignore +38 -0
  2. package/.devcontainer/Dockerfile.dev +28 -0
  3. package/.devcontainer/devcontainer.json +56 -0
  4. package/.devcontainer/docker-compose.yml +15 -0
  5. package/.dockerignore +37 -0
  6. package/.prettierrc +4 -0
  7. package/LICENSE +21 -0
  8. package/README.md +540 -0
  9. package/dist/controllers/sse/index.d.ts +2 -0
  10. package/dist/controllers/sse/index.js +19 -0
  11. package/dist/controllers/sse/index.js.map +1 -0
  12. package/dist/controllers/sse/sse.controller.d.ts +10 -0
  13. package/dist/controllers/sse/sse.controller.js +57 -0
  14. package/dist/controllers/sse/sse.controller.js.map +1 -0
  15. package/dist/controllers/sse/sse.service.d.ts +16 -0
  16. package/dist/controllers/sse/sse.service.js +78 -0
  17. package/dist/controllers/sse/sse.service.js.map +1 -0
  18. package/dist/controllers/streamable/index.d.ts +2 -0
  19. package/dist/controllers/streamable/index.js +19 -0
  20. package/dist/controllers/streamable/index.js.map +1 -0
  21. package/dist/controllers/streamable/streamable.controller.d.ts +9 -0
  22. package/dist/controllers/streamable/streamable.controller.js +62 -0
  23. package/dist/controllers/streamable/streamable.controller.js.map +1 -0
  24. package/dist/controllers/streamable/streamable.service.d.ts +24 -0
  25. package/dist/controllers/streamable/streamable.service.js +118 -0
  26. package/dist/controllers/streamable/streamable.service.js.map +1 -0
  27. package/dist/decorators/capabilities.constants.d.ts +4 -0
  28. package/dist/decorators/capabilities.constants.js +8 -0
  29. package/dist/decorators/capabilities.constants.js.map +1 -0
  30. package/dist/decorators/capabilities.decorators.d.ts +8 -0
  31. package/dist/decorators/capabilities.decorators.js +49 -0
  32. package/dist/decorators/capabilities.decorators.js.map +1 -0
  33. package/dist/decorators/index.d.ts +2 -0
  34. package/dist/decorators/index.js +19 -0
  35. package/dist/decorators/index.js.map +1 -0
  36. package/dist/index.d.ts +4 -0
  37. package/dist/index.js +21 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/interceptors/message.interceptor.d.ts +10 -0
  40. package/dist/interceptors/message.interceptor.js +61 -0
  41. package/dist/interceptors/message.interceptor.js.map +1 -0
  42. package/dist/interfaces/capabilities.interface.d.ts +52 -0
  43. package/dist/interfaces/capabilities.interface.js +3 -0
  44. package/dist/interfaces/capabilities.interface.js.map +1 -0
  45. package/dist/interfaces/context.interface.d.ts +6 -0
  46. package/dist/interfaces/context.interface.js +3 -0
  47. package/dist/interfaces/context.interface.js.map +1 -0
  48. package/dist/interfaces/index.d.ts +2 -0
  49. package/dist/interfaces/index.js +19 -0
  50. package/dist/interfaces/index.js.map +1 -0
  51. package/dist/interfaces/mcp-server-options.interface.d.ts +42 -0
  52. package/dist/interfaces/mcp-server-options.interface.js +3 -0
  53. package/dist/interfaces/mcp-server-options.interface.js.map +1 -0
  54. package/dist/interfaces/message.types.d.ts +8 -0
  55. package/dist/interfaces/message.types.js +3 -0
  56. package/dist/interfaces/message.types.js.map +1 -0
  57. package/dist/mcp.module.d.ts +13 -0
  58. package/dist/mcp.module.js +193 -0
  59. package/dist/mcp.module.js.map +1 -0
  60. package/dist/registry/discovery.service.d.ts +16 -0
  61. package/dist/registry/discovery.service.js +85 -0
  62. package/dist/registry/discovery.service.js.map +1 -0
  63. package/dist/registry/index.d.ts +2 -0
  64. package/dist/registry/index.js +19 -0
  65. package/dist/registry/index.js.map +1 -0
  66. package/dist/registry/logger.service.d.ts +16 -0
  67. package/dist/registry/logger.service.js +97 -0
  68. package/dist/registry/logger.service.js.map +1 -0
  69. package/dist/registry/registry.service.d.ts +16 -0
  70. package/dist/registry/registry.service.js +170 -0
  71. package/dist/registry/registry.service.js.map +1 -0
  72. package/dist/services/message.service.d.ts +7 -0
  73. package/dist/services/message.service.js +25 -0
  74. package/dist/services/message.service.js.map +1 -0
  75. package/dist/tsconfig.build.tsbuildinfo +1 -0
  76. package/eslint.config.mjs +40 -0
  77. package/package.json +109 -0
  78. package/src/controllers/sse/index.ts +2 -0
  79. package/src/controllers/sse/sse.controller.ts +25 -0
  80. package/src/controllers/sse/sse.service.ts +90 -0
  81. package/src/controllers/streamable/index.ts +2 -0
  82. package/src/controllers/streamable/streamable.controller.ts +24 -0
  83. package/src/controllers/streamable/streamable.service.ts +169 -0
  84. package/src/decorators/capabilities.constants.ts +7 -0
  85. package/src/decorators/capabilities.decorators.ts +150 -0
  86. package/src/decorators/index.ts +2 -0
  87. package/src/index.ts +11 -0
  88. package/src/interceptors/message.interceptor.ts +70 -0
  89. package/src/interfaces/capabilities.interface.ts +95 -0
  90. package/src/interfaces/context.interface.ts +18 -0
  91. package/src/interfaces/index.ts +2 -0
  92. package/src/interfaces/mcp-server-options.interface.ts +105 -0
  93. package/src/interfaces/message.types.ts +13 -0
  94. package/src/mcp.module.ts +250 -0
  95. package/src/mcp.service.spec.ts +28 -0
  96. package/src/registry/discovery.service.ts +116 -0
  97. package/src/registry/index.ts +2 -0
  98. package/src/registry/logger.service.ts +143 -0
  99. package/src/registry/registry.service.ts +282 -0
  100. package/src/services/message.service.ts +18 -0
package/package.json ADDED
@@ -0,0 +1,109 @@
1
+ {
2
+ "name": "@nestjs-mcp/server",
3
+ "version": "0.1.0-alpha.10",
4
+ "description": "Modular library for building scalable MCP servers with NestJS, providing decorators and integration patterns as a wrapper for the official MCP TypeScript SDK.",
5
+ "author": "Adrián Darío Hidalgo Flores",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=22",
9
+ "pnpm": ">=10"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "main": "dist/index.js",
15
+ "types": "dist/index.d.ts",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/adrian-d-hidalgo/nestjs-mcp-server.git"
19
+ },
20
+ "homepage": "https://github.com/adrian-d-hidalgo/nestjs-mcp-server#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/adrian-d-hidalgo/nestjs-mcp-server/issues"
23
+ },
24
+ "keywords": [
25
+ "decorators",
26
+ "integration",
27
+ "large-language-models",
28
+ "llm",
29
+ "mcp",
30
+ "model-context-protocol",
31
+ "module",
32
+ "nestjs",
33
+ "sdk",
34
+ "server",
35
+ "typescript"
36
+ ],
37
+ "scripts": {
38
+ "build": "nest build",
39
+ "prepare": "husky install",
40
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
41
+ "start:example": "npx -y ts-node-dev --respawn examples/$EXAMPLE/main.ts",
42
+ "start:inspector": "npx -y @modelcontextprotocol/inspector",
43
+ "lint": "eslint \"{src,test,examples}/**/*.ts\" --fix",
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "jest",
46
+ "test:watch": "jest --watch",
47
+ "test:cov": "jest --coverage",
48
+ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
49
+ "test:e2e": "jest --config ./test/jest-e2e.json",
50
+ "npm:publish": "node scripts/npm-publish.js"
51
+ },
52
+ "peerDependencies": {
53
+ "@nestjs/common": "^11.0.1",
54
+ "@nestjs/core": "^11.0.1",
55
+ "@nestjs/platform-express": "^11.0.1",
56
+ "reflect-metadata": "^0.2.2",
57
+ "rxjs": "^7.8.1"
58
+ },
59
+ "dependencies": {
60
+ "@modelcontextprotocol/sdk": "^1.10.2",
61
+ "zod": "^3.24.3"
62
+ },
63
+ "devDependencies": {
64
+ "@eslint/eslintrc": "^3.2.0",
65
+ "@eslint/js": "^9.18.0",
66
+ "@nestjs/cli": "^11.0.0",
67
+ "@nestjs/config": "^4.0.2",
68
+ "@nestjs/schematics": "^11.0.0",
69
+ "@nestjs/testing": "^11.0.1",
70
+ "@swc/cli": "^0.6.0",
71
+ "@swc/core": "^1.10.7",
72
+ "@types/express": "^5.0.0",
73
+ "@types/jest": "^29.5.14",
74
+ "@types/node": "^22.10.7",
75
+ "@types/supertest": "^6.0.2",
76
+ "eslint": "^9.18.0",
77
+ "eslint-config-prettier": "^10.0.1",
78
+ "eslint-plugin-prettier": "^5.2.2",
79
+ "globals": "^16.0.0",
80
+ "husky": "^9.1.7",
81
+ "jest": "^29.7.0",
82
+ "prettier": "^3.4.2",
83
+ "source-map-support": "^0.5.21",
84
+ "supertest": "^7.0.0",
85
+ "ts-jest": "^29.2.5",
86
+ "ts-loader": "^9.5.2",
87
+ "ts-node": "^10.9.2",
88
+ "tsconfig-paths": "^4.2.0",
89
+ "typescript": "^5.7.3",
90
+ "typescript-eslint": "^8.20.0"
91
+ },
92
+ "jest": {
93
+ "moduleFileExtensions": [
94
+ "js",
95
+ "json",
96
+ "ts"
97
+ ],
98
+ "rootDir": "src",
99
+ "testRegex": ".*\\.spec\\.ts$",
100
+ "transform": {
101
+ "^.+\\.(t|j)s$": "ts-jest"
102
+ },
103
+ "collectCoverageFrom": [
104
+ "**/*.(t|j)s"
105
+ ],
106
+ "coverageDirectory": "../coverage",
107
+ "testEnvironment": "node"
108
+ }
109
+ }
@@ -0,0 +1,2 @@
1
+ export * from './sse.controller';
2
+ export * from './sse.service';
@@ -0,0 +1,25 @@
1
+ import { Controller, Get, Post, Req, Res } from '@nestjs/common';
2
+ import { Request, Response } from 'express';
3
+
4
+ import { McpLoggerService } from '../../registry/logger.service';
5
+ import { SseService } from './sse.service';
6
+
7
+ @Controller()
8
+ export class SseController {
9
+ constructor(
10
+ private readonly logger: McpLoggerService,
11
+ private readonly service: SseService,
12
+ ) {}
13
+
14
+ @Get('sse')
15
+ async handleSse(@Req() req: Request, @Res() res: Response) {
16
+ this.logger.log('[SSE] Connection established');
17
+ await this.service.handleSse(req, res);
18
+ }
19
+
20
+ @Post('messages')
21
+ async handleMessages(@Req() req: Request, @Res() res: Response) {
22
+ this.logger.log('[SSE] Message received');
23
+ await this.service.handleMessage(req, res);
24
+ }
25
+ }
@@ -0,0 +1,90 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
3
+ import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
4
+ import { Request, Response } from 'express';
5
+
6
+ import { McpServerOptions } from '../../interfaces/mcp-server-options.interface';
7
+ import { McpLoggerService } from '../../registry/logger.service';
8
+ import { RegistryService } from '../../registry/registry.service';
9
+
10
+ @Injectable()
11
+ export class SseService implements OnModuleInit {
12
+ private server: McpServer;
13
+
14
+ private transports = {} as Record<string, SSEServerTransport>;
15
+
16
+ constructor(
17
+ @Inject('MCP_SERVER_OPTIONS')
18
+ private readonly options: McpServerOptions,
19
+ private readonly registry: RegistryService,
20
+ private readonly logger: McpLoggerService,
21
+ ) {
22
+ this.server = new McpServer(this.options.serverInfo, this.options.options);
23
+ }
24
+
25
+ async onModuleInit() {
26
+ this.logger.log('Starting MCP controller registration', 'MCP_SERVER');
27
+ await this.registry.registerAll(this.server);
28
+ this.logger.log('MCP initialization completed', 'MCP_SERVER');
29
+ }
30
+
31
+ /**
32
+ * Handle an SSE request for server-sent events
33
+ *
34
+ * This establishes a connection for server-to-client notifications
35
+ */
36
+ async handleSse(req: Request, res: Response) {
37
+ // Create SSE transport for legacy clients
38
+ const transport = new SSEServerTransport('/messages', res);
39
+ this.transports[transport.sessionId] = transport;
40
+
41
+ this.logger.debug(
42
+ `Starting SSE for sessionId: ${transport.sessionId}`,
43
+ 'api',
44
+ );
45
+
46
+ res.on('close', () => {
47
+ delete this.transports[transport.sessionId];
48
+ });
49
+
50
+ await this.server.connect(transport);
51
+ }
52
+
53
+ /**
54
+ * Handle SSE messages sent from client to server
55
+ */
56
+ async handleMessage(req: Request, res: Response) {
57
+ const sessionId = req.query.sessionId as string;
58
+ const transport = this.transports[sessionId];
59
+
60
+ this.logger.debug(
61
+ `Receiving SSE message for sessionId: ${sessionId}`,
62
+ 'api',
63
+ );
64
+
65
+ this.logger.debug(`SSE message: ${JSON.stringify(req.body)}`, 'MCP_SERVER');
66
+
67
+ try {
68
+ if (transport) {
69
+ await transport.handlePostMessage(req, res, req.body);
70
+ } else {
71
+ res.status(400).send('No transport found for sessionId');
72
+ }
73
+ } catch (error) {
74
+ const errorMessage =
75
+ error instanceof Error ? error.message : 'Unknown error';
76
+
77
+ this.logger.error(
78
+ 'Error al manejar mensaje SSE',
79
+ errorMessage,
80
+ 'MCP_SERVER',
81
+ );
82
+
83
+ res.status(500).send({
84
+ statusCode: 500,
85
+ error: 'Internal Server Error',
86
+ message: errorMessage,
87
+ });
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,2 @@
1
+ export * from './streamable.controller';
2
+ export * from './streamable.service';
@@ -0,0 +1,24 @@
1
+ import { Controller, Delete, Get, Post, Req, Res } from '@nestjs/common';
2
+ import { Request, Response } from 'express';
3
+
4
+ import { StreamableService } from './streamable.service';
5
+
6
+ @Controller()
7
+ export class StreamableController {
8
+ constructor(private readonly service: StreamableService) {}
9
+
10
+ @Post('mcp')
11
+ async handleMcpPost(@Req() req: Request, @Res() res: Response) {
12
+ await this.service.handlePostRequest(req, res);
13
+ }
14
+
15
+ @Get('mcp')
16
+ async handleMcpGet(@Req() req: Request, @Res() res: Response) {
17
+ await this.service.handleGetRequest(req, res);
18
+ }
19
+
20
+ @Delete('mcp')
21
+ async handleMcpDelete(@Req() req: Request, @Res() res: Response) {
22
+ await this.service.handleDeleteRequest(req, res);
23
+ }
24
+ }
@@ -0,0 +1,169 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
+ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
4
+ import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
5
+ import { randomUUID } from 'crypto';
6
+ import { Request, Response } from 'express';
7
+
8
+ import {
9
+ McpModuleTransportOptions,
10
+ McpServerOptions,
11
+ } from '../../interfaces/mcp-server-options.interface';
12
+ import { McpLoggerService } from '../../registry/logger.service';
13
+ import { RegistryService } from '../../registry/registry.service';
14
+ // TODO: Stateless mode should be handled here or in another service
15
+
16
+ @Injectable()
17
+ export class StreamableService implements OnModuleInit {
18
+ private server: McpServer;
19
+
20
+ private transports = {} as Record<string, StreamableHTTPServerTransport>;
21
+
22
+ constructor(
23
+ @Inject('MCP_SERVER_OPTIONS')
24
+ private readonly options: McpServerOptions,
25
+ @Inject('MCP_TRANSPORT_OPTIONS')
26
+ private readonly transportOptions: McpModuleTransportOptions,
27
+ private readonly registry: RegistryService,
28
+ private readonly logger: McpLoggerService,
29
+ ) {
30
+ this.server = new McpServer(this.options.serverInfo, this.options.options);
31
+ }
32
+
33
+ async onModuleInit() {
34
+ await this.registry.registerAll(this.server);
35
+
36
+ this.logger.log('MCP STREAMEABLE initialization completed');
37
+ }
38
+
39
+ /**
40
+ * Handle a streamable HTTP POST request from a client
41
+ *
42
+ * - Uses sessionId from query or generates a new one if missing
43
+ * - Stores the transport by sessionId for later retrieval
44
+ * - Cleans up transport on connection close
45
+ *
46
+ * @param req Express Request object (expects sessionId in query)
47
+ * @param res Express Response object
48
+ */
49
+ async handlePostRequest(
50
+ req: Request<any, any, any, { sessionId?: string }>,
51
+ res: Response,
52
+ ) {
53
+ const sessionId = req.headers['mcp-session-id'] as string | undefined;
54
+ let transport: StreamableHTTPServerTransport;
55
+
56
+ const { options } = this.transportOptions?.streamable || {};
57
+
58
+ if (sessionId && this.transports[sessionId]) {
59
+ transport = this.transports[sessionId];
60
+ } else if (!sessionId && isInitializeRequest(req.body)) {
61
+ transport = new StreamableHTTPServerTransport({
62
+ sessionIdGenerator: () =>
63
+ options?.sessionIdGenerator?.() || randomUUID(),
64
+ onsessioninitialized: (sessionId) => {
65
+ this.transports[sessionId] = transport;
66
+ },
67
+ enableJsonResponse: options?.enableJsonResponse,
68
+ eventStore: options?.eventStore,
69
+ });
70
+
71
+ transport.onclose = () => {
72
+ if (transport.sessionId) {
73
+ delete this.transports[transport.sessionId];
74
+ }
75
+ };
76
+
77
+ await this.server.connect(transport);
78
+ } else {
79
+ // Invalid request
80
+ res.status(400).json({
81
+ jsonrpc: '2.0',
82
+ error: {
83
+ code: -32000,
84
+ message: 'Bad Request: No valid session ID provided',
85
+ },
86
+ id: null,
87
+ });
88
+
89
+ return;
90
+ }
91
+
92
+ await transport.handleRequest(req, res, req.body);
93
+ }
94
+
95
+ /**
96
+ * Handle a streamable HTTP GET request from a client
97
+ *
98
+ * This method retrieves the existing streamable transport for the session and delegates the request.
99
+ *
100
+ * @param req Express Request object (expects sessionId in query)
101
+ * @param res Express Response object
102
+ */
103
+ async handleGetRequest(
104
+ req: Request<any, any, any, { sessionId?: string }>,
105
+ res: Response,
106
+ ) {
107
+ const sessionId = req.headers['mcp-session-id'] as string | undefined;
108
+
109
+ if (!sessionId || !this.transports[sessionId]) {
110
+ res.status(400).send('Invalid or missing session ID');
111
+ return;
112
+ }
113
+
114
+ const transport = this.transports[sessionId];
115
+
116
+ await transport.handleRequest(req, res);
117
+ }
118
+
119
+ /**
120
+ * Handle a streamable HTTP DELETE request to clean up a session
121
+ *
122
+ * - Accepts sessionId from query or x-mcp-session-id header
123
+ * - Closes and removes the transport if found
124
+ * - Always sends a response
125
+ *
126
+ * @param req Express Request object
127
+ * @param res Express Response object
128
+ */
129
+ async handleDeleteRequest(
130
+ req: Request<any, any, any, { sessionId?: string }>,
131
+ res: Response,
132
+ ) {
133
+ const sessionId = req.headers['mcp-session-id'] as string | undefined;
134
+
135
+ if (!sessionId) {
136
+ res.status(400).json({ error: 'Missing sessionId' });
137
+ return;
138
+ }
139
+
140
+ const transport = this.transports[sessionId];
141
+
142
+ if (transport) {
143
+ this.logger.debug(
144
+ `Closing streamable transport for sessionId: ${sessionId}`,
145
+ 'STREAMABLE',
146
+ );
147
+
148
+ await transport.close();
149
+
150
+ const uuidV4Regex =
151
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
152
+
153
+ if (!uuidV4Regex.test(sessionId)) {
154
+ res.status(400).json({ error: 'Invalid sessionId format' });
155
+ return;
156
+ }
157
+
158
+ delete this.transports[sessionId];
159
+
160
+ res.status(200).json({ success: true, sessionId });
161
+ } else {
162
+ this.logger.debug(
163
+ `No streamable transport found for sessionId: ${sessionId}`,
164
+ 'STREAMABLE',
165
+ );
166
+ res.status(404).json({ error: 'Transport not found', sessionId });
167
+ }
168
+ }
169
+ }
@@ -0,0 +1,7 @@
1
+ // Prefijo para todas las constantes de metadatos
2
+ export const PREFIX = 'MCP';
3
+
4
+ // Constantes de metadatos para decoradores de método
5
+ export const MCP_TOOL = `${PREFIX}:tool`;
6
+ export const MCP_PROMPT = `${PREFIX}:prompt`;
7
+ export const MCP_RESOURCE = `${PREFIX}:resource`;
@@ -0,0 +1,150 @@
1
+ import { Injectable, SetMetadata } from '@nestjs/common';
2
+
3
+ import {
4
+ PromptOptions,
5
+ ResourceOptions,
6
+ ToolOptions,
7
+ } from '../interfaces/capabilities.interface';
8
+ import { MCP_PROMPT, MCP_RESOURCE, MCP_TOOL } from './capabilities.constants';
9
+
10
+ /**
11
+ * Metadata key to mark a class as an MCP Resolver.
12
+ */
13
+ export const MCP_RESOLVER = '__mcp_resolver__';
14
+
15
+ /**
16
+ * Metadata key to attach guards to a Resolver class or method.
17
+ */
18
+ export const MCP_GUARDS = '__mcp_guards__';
19
+
20
+ /**
21
+ * Decorator for marking a class as an MCP Resolver.
22
+ * Enables dependency injection and workspace grouping for MCP capabilities.
23
+ *
24
+ * @param workspace Optional workspace/namespace for grouping capabilities
25
+ * @example
26
+ * @Resolver('my-workspace')
27
+ * export class MyResolver { ... }
28
+ */
29
+ export function Resolver(workspace?: string): ClassDecorator {
30
+ return function (target: any) {
31
+ Injectable()(target);
32
+ SetMetadata(MCP_RESOLVER, workspace || true)(target);
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Decorator to attach one or more guards to a Resolver class or method.
38
+ * Accepts guard classes or instances implementing CanActivate.
39
+ *
40
+ * @param guards One or more guard classes or instances
41
+ * @example
42
+ * @UseGuards(MyGuard)
43
+ * @Resolver('workspace')
44
+ * export class MyResolver { ... }
45
+ */
46
+ export function UseGuards(...guards: any[]): ClassDecorator & MethodDecorator {
47
+ return SetMetadata(MCP_GUARDS, guards);
48
+ }
49
+
50
+ /**
51
+ * Decorator for marking a method as an MCP Tool.
52
+ * Use with @McpProvider.
53
+ *
54
+ * La herramienta debe devolver un objeto con el formato:
55
+ * {
56
+ * content: [
57
+ * {
58
+ * type: 'text', // Puede ser 'text', 'image', 'video', 'audio', etc.
59
+ * text: 'Texto de la respuesta',
60
+ * }
61
+ * ]
62
+ * }
63
+ *
64
+ * @param options Tool configuration
65
+ */
66
+ export function Tool(options: ToolOptions) {
67
+ return function (
68
+ target: object,
69
+ propertyKey: string,
70
+ descriptor: PropertyDescriptor,
71
+ ) {
72
+ SetMetadata(MCP_TOOL, {
73
+ ...options,
74
+ methodName: propertyKey,
75
+ })(target, propertyKey, descriptor);
76
+
77
+ return descriptor;
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Decorator for marking a method as an MCP Prompt.
83
+ * Use with @McpProvider.
84
+ *
85
+ * El prompt debe devolver un objeto con el formato:
86
+ * {
87
+ * messages: [
88
+ * {
89
+ * role: 'assistant',
90
+ * content: {
91
+ * type: 'text',
92
+ * text: 'Texto del mensaje'
93
+ * }
94
+ * }
95
+ * ]
96
+ * }
97
+ *
98
+ * @param options Prompt configuration
99
+ */
100
+ export function Prompt(options: PromptOptions) {
101
+ return function (
102
+ target: object,
103
+ propertyKey: string,
104
+ descriptor: PropertyDescriptor,
105
+ ) {
106
+ SetMetadata(MCP_PROMPT, {
107
+ ...options,
108
+ methodName: propertyKey,
109
+ })(target, propertyKey, descriptor);
110
+
111
+ return descriptor;
112
+ };
113
+ }
114
+
115
+ /**
116
+ * Decorator for marking a method as an MCP Resource provider.
117
+ * Use with @McpProvider.
118
+ *
119
+ * Hay dos modos de uso para los recursos:
120
+ *
121
+ * 1. Recurso con URI fija:
122
+ * @Resource({
123
+ * name: 'nombreRecurso',
124
+ * uri: 'resource://midominio/recurso'
125
+ * })
126
+ *
127
+ * 2. Recurso con plantilla (para parámetros dinámicos):
128
+ * @Resource({
129
+ * name: 'nombreRecurso',
130
+ * template: 'resource://midominio/recurso/{parametro}'
131
+ * })
132
+ *
133
+ * También se puede proporcionar solo el nombre como string:
134
+ * @Resource('nombreRecurso')
135
+ *
136
+ * @param options Resource configuration or just the name as a string
137
+ */
138
+ export function Resource(options: ResourceOptions) {
139
+ return function (
140
+ target: object,
141
+ propertyKey: string,
142
+ descriptor: PropertyDescriptor,
143
+ ) {
144
+ SetMetadata(MCP_RESOURCE, {
145
+ ...options,
146
+ methodName: propertyKey,
147
+ })(target, propertyKey, descriptor);
148
+ return descriptor;
149
+ };
150
+ }
@@ -0,0 +1,2 @@
1
+ export * from './capabilities.constants';
2
+ export * from './capabilities.decorators';
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ // Core components
2
+ export * from './mcp.module';
3
+
4
+ // Interfaces
5
+ export * from './interfaces';
6
+
7
+ // Registry components
8
+ export * from './registry';
9
+
10
+ // Decorators
11
+ export * from './decorators';
@@ -0,0 +1,70 @@
1
+ import {
2
+ CallHandler,
3
+ ExecutionContext,
4
+ Injectable,
5
+ NestInterceptor,
6
+ } from '@nestjs/common';
7
+ import { Request, Response } from 'express';
8
+ import { Observable } from 'rxjs';
9
+
10
+ import { McpMessage } from '../interfaces/message.types';
11
+ import { McpLoggerService } from '../registry/logger.service';
12
+ import { MessageService } from '../services/message.service';
13
+ @Injectable()
14
+ export class RequestContextInterceptor implements NestInterceptor {
15
+ constructor(
16
+ private readonly logger: McpLoggerService,
17
+ private readonly messageService: MessageService,
18
+ ) {}
19
+
20
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
21
+ const request = context.switchToHttp().getRequest<Request>();
22
+ const response = context.switchToHttp().getResponse<Response>();
23
+ let message: McpMessage | undefined;
24
+
25
+ this.logger.log(
26
+ `Request path: ${request.path}`,
27
+ '[RequestContextInterceptor]',
28
+ );
29
+
30
+ switch (request.path) {
31
+ case '/sse':
32
+ this.logger.log(
33
+ 'SSE connection detected',
34
+ '[RequestContextInterceptor]',
35
+ );
36
+
37
+ message = {
38
+ req: request,
39
+ res: response,
40
+ };
41
+
42
+ this.messageService.set(message);
43
+
44
+ break;
45
+ case '/messages':
46
+ this.logger.log('SSE message received', '[RequestContextInterceptor]');
47
+
48
+ message = {
49
+ req: request,
50
+ res: response,
51
+ };
52
+
53
+ this.messageService.set(message);
54
+
55
+ break;
56
+ case '/mcp':
57
+ this.logger.log('MCP request detected', '[RequestContextInterceptor]');
58
+ // TODO: Handle MCP request
59
+ break;
60
+ default:
61
+ this.logger.log(
62
+ `Regular request: ${request.method} ${request.url}`,
63
+ '[RequestContextInterceptor]',
64
+ );
65
+ break;
66
+ }
67
+
68
+ return next.handle();
69
+ }
70
+ }