@nestjs-mcp/server 0.1.0-alpha.4
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/.copilotignore +38 -0
- package/.devcontainer/Dockerfile.dev +28 -0
- package/.devcontainer/devcontainer.json +56 -0
- package/.devcontainer/docker-compose.yml +15 -0
- package/.dockerignore +37 -0
- package/.github/codeql-config.yml +4 -0
- package/.github/copilot-instructions.md +138 -0
- package/.github/prompts/memory.prompt.md +120 -0
- package/.github/workflows/auto-tag-release.yml +84 -0
- package/.github/workflows/codeql-analysis.yml +56 -0
- package/.github/workflows/npm-publish.yml +58 -0
- package/.github/workflows/pr-branch-validation.yml +78 -0
- package/.github/workflows/run-tests.yml +41 -0
- package/.github/workflows/sync-main-to-develop.yml +53 -0
- package/.handbook/GIT_GUIDELINES.md +250 -0
- package/.handbook/PACKAGE_VERSIONING.md +140 -0
- package/.handbook/STACK.md +75 -0
- package/.prettierrc +4 -0
- package/.vscode/extensions.json +44 -0
- package/.vscode/settings.json +40 -0
- package/CONTRIBUTING.md +261 -0
- package/LICENSE +21 -0
- package/README.md +490 -0
- package/dist/examples/async-import/app.module.d.ts +2 -0
- package/dist/examples/async-import/app.module.js +33 -0
- package/dist/examples/async-import/app.module.js.map +1 -0
- package/dist/examples/async-import/main.d.ts +1 -0
- package/dist/examples/async-import/main.js +17 -0
- package/dist/examples/async-import/main.js.map +1 -0
- package/dist/examples/guards/app.module.d.ts +6 -0
- package/dist/examples/guards/app.module.js +48 -0
- package/dist/examples/guards/app.module.js.map +1 -0
- package/dist/examples/guards/guards.resolver.d.ts +13 -0
- package/dist/examples/guards/guards.resolver.js +61 -0
- package/dist/examples/guards/guards.resolver.js.map +1 -0
- package/dist/examples/guards/main.d.ts +1 -0
- package/dist/examples/guards/main.js +11 -0
- package/dist/examples/guards/main.js.map +1 -0
- package/dist/examples/mixed/app.module.d.ts +2 -0
- package/dist/examples/mixed/app.module.js +31 -0
- package/dist/examples/mixed/app.module.js.map +1 -0
- package/dist/examples/mixed/main.d.ts +1 -0
- package/dist/examples/mixed/main.js +11 -0
- package/dist/examples/mixed/main.js.map +1 -0
- package/dist/examples/mixed/mixed.resolver.d.ts +6 -0
- package/dist/examples/mixed/mixed.resolver.js +78 -0
- package/dist/examples/mixed/mixed.resolver.js.map +1 -0
- package/dist/examples/prompts/app.module.d.ts +2 -0
- package/dist/examples/prompts/app.module.js +31 -0
- package/dist/examples/prompts/app.module.js.map +1 -0
- package/dist/examples/prompts/main.d.ts +1 -0
- package/dist/examples/prompts/main.js +11 -0
- package/dist/examples/prompts/main.js.map +1 -0
- package/dist/examples/prompts/prompts.resolver.d.ts +14 -0
- package/dist/examples/prompts/prompts.resolver.js +165 -0
- package/dist/examples/prompts/prompts.resolver.js.map +1 -0
- package/dist/examples/resources/app.module.d.ts +2 -0
- package/dist/examples/resources/app.module.js +31 -0
- package/dist/examples/resources/app.module.js.map +1 -0
- package/dist/examples/resources/main.d.ts +1 -0
- package/dist/examples/resources/main.js +11 -0
- package/dist/examples/resources/main.js.map +1 -0
- package/dist/examples/resources/resources.resolver.d.ts +12 -0
- package/dist/examples/resources/resources.resolver.js +114 -0
- package/dist/examples/resources/resources.resolver.js.map +1 -0
- package/dist/examples/tools/app.module.d.ts +2 -0
- package/dist/examples/tools/app.module.js +31 -0
- package/dist/examples/tools/app.module.js.map +1 -0
- package/dist/examples/tools/main.d.ts +1 -0
- package/dist/examples/tools/main.js +11 -0
- package/dist/examples/tools/main.js.map +1 -0
- package/dist/examples/tools/tools.resolver.d.ts +23 -0
- package/dist/examples/tools/tools.resolver.js +175 -0
- package/dist/examples/tools/tools.resolver.js.map +1 -0
- package/dist/src/controllers/sse/index.d.ts +2 -0
- package/dist/src/controllers/sse/index.js +19 -0
- package/dist/src/controllers/sse/index.js.map +1 -0
- package/dist/src/controllers/sse/sse.controller.d.ts +8 -0
- package/dist/src/controllers/sse/sse.controller.js +51 -0
- package/dist/src/controllers/sse/sse.controller.js.map +1 -0
- package/dist/src/controllers/sse/sse.service.d.ts +16 -0
- package/dist/src/controllers/sse/sse.service.js +78 -0
- package/dist/src/controllers/sse/sse.service.js.map +1 -0
- package/dist/src/controllers/streamable/index.d.ts +2 -0
- package/dist/src/controllers/streamable/index.js +19 -0
- package/dist/src/controllers/streamable/index.js.map +1 -0
- package/dist/src/controllers/streamable/streamable.controller.d.ts +9 -0
- package/dist/src/controllers/streamable/streamable.controller.js +62 -0
- package/dist/src/controllers/streamable/streamable.controller.js.map +1 -0
- package/dist/src/controllers/streamable/streamable.service.d.ts +24 -0
- package/dist/src/controllers/streamable/streamable.service.js +117 -0
- package/dist/src/controllers/streamable/streamable.service.js.map +1 -0
- package/dist/src/decorators/capabilities.constants.d.ts +4 -0
- package/dist/src/decorators/capabilities.constants.js +8 -0
- package/dist/src/decorators/capabilities.constants.js.map +1 -0
- package/dist/src/decorators/capabilities.decorators.d.ts +8 -0
- package/dist/src/decorators/capabilities.decorators.js +49 -0
- package/dist/src/decorators/capabilities.decorators.js.map +1 -0
- package/dist/src/decorators/index.d.ts +2 -0
- package/dist/src/decorators/index.js +19 -0
- package/dist/src/decorators/index.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +21 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interfaces/capabilities.interface.d.ts +52 -0
- package/dist/src/interfaces/capabilities.interface.js +3 -0
- package/dist/src/interfaces/capabilities.interface.js.map +1 -0
- package/dist/src/interfaces/guards.interface.d.ts +4 -0
- package/dist/src/interfaces/guards.interface.js +3 -0
- package/dist/src/interfaces/guards.interface.js.map +1 -0
- package/dist/src/interfaces/index.d.ts +2 -0
- package/dist/src/interfaces/index.js +19 -0
- package/dist/src/interfaces/index.js.map +1 -0
- package/dist/src/interfaces/mcp-server-options.interface.d.ts +42 -0
- package/dist/src/interfaces/mcp-server-options.interface.js +3 -0
- package/dist/src/interfaces/mcp-server-options.interface.js.map +1 -0
- package/dist/src/mcp.module.d.ts +13 -0
- package/dist/src/mcp.module.js +176 -0
- package/dist/src/mcp.module.js.map +1 -0
- package/dist/src/registry/discovery.service.d.ts +16 -0
- package/dist/src/registry/discovery.service.js +85 -0
- package/dist/src/registry/discovery.service.js.map +1 -0
- package/dist/src/registry/index.d.ts +2 -0
- package/dist/src/registry/index.js +19 -0
- package/dist/src/registry/index.js.map +1 -0
- package/dist/src/registry/logger.service.d.ts +16 -0
- package/dist/src/registry/logger.service.js +97 -0
- package/dist/src/registry/logger.service.js.map +1 -0
- package/dist/src/registry/registry.service.d.ts +14 -0
- package/dist/src/registry/registry.service.js +165 -0
- package/dist/src/registry/registry.service.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/eslint.config.mjs +40 -0
- package/examples/README.md +56 -0
- package/examples/async-import/app.module.ts +22 -0
- package/examples/async-import/main.ts +15 -0
- package/examples/guards/app.module.ts +44 -0
- package/examples/guards/guards.resolver.ts +52 -0
- package/examples/guards/main.ts +11 -0
- package/examples/mixed/app.module.ts +20 -0
- package/examples/mixed/main.ts +11 -0
- package/examples/mixed/mixed.resolver.ts +56 -0
- package/examples/prompts/app.module.ts +20 -0
- package/examples/prompts/main.ts +11 -0
- package/examples/prompts/prompts.resolver.ts +184 -0
- package/examples/resources/app.module.ts +19 -0
- package/examples/resources/main.ts +11 -0
- package/examples/resources/resources.resolver.ts +123 -0
- package/examples/tools/app.module.ts +20 -0
- package/examples/tools/main.ts +11 -0
- package/examples/tools/tools.resolver.ts +205 -0
- package/nest-cli.json +8 -0
- package/package.json +106 -0
- package/scripts/npm-publish.js +301 -0
- package/src/controllers/sse/index.ts +2 -0
- package/src/controllers/sse/sse.controller.ts +19 -0
- package/src/controllers/sse/sse.service.ts +90 -0
- package/src/controllers/streamable/index.ts +2 -0
- package/src/controllers/streamable/streamable.controller.ts +24 -0
- package/src/controllers/streamable/streamable.service.ts +168 -0
- package/src/decorators/capabilities.constants.ts +7 -0
- package/src/decorators/capabilities.decorators.ts +150 -0
- package/src/decorators/index.ts +2 -0
- package/src/index.ts +11 -0
- package/src/interfaces/capabilities.interface.ts +95 -0
- package/src/interfaces/guards.interface.ts +13 -0
- package/src/interfaces/index.ts +2 -0
- package/src/interfaces/mcp-server-options.interface.ts +105 -0
- package/src/mcp.module.ts +233 -0
- package/src/mcp.service.spec.ts +28 -0
- package/src/registry/discovery.service.ts +116 -0
- package/src/registry/index.ts +2 -0
- package/src/registry/logger.service.ts +143 -0
- package/src/registry/registry.service.ts +281 -0
- package/test/base.e2e-spec.ts +74 -0
- package/test/jest-e2e.json +9 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +23 -0
|
@@ -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,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,168 @@
|
|
|
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
|
+
|
|
15
|
+
// TODO: Stateless mode should be handled here or in another service
|
|
16
|
+
|
|
17
|
+
@Injectable()
|
|
18
|
+
export class StreamableService implements OnModuleInit {
|
|
19
|
+
private server: McpServer;
|
|
20
|
+
|
|
21
|
+
private transports = {} as Record<string, StreamableHTTPServerTransport>;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
@Inject('MCP_SERVER_OPTIONS')
|
|
25
|
+
private readonly options: McpServerOptions,
|
|
26
|
+
@Inject('MCP_TRANSPORT_OPTIONS')
|
|
27
|
+
private readonly transportOptions: McpModuleTransportOptions,
|
|
28
|
+
private readonly registry: RegistryService,
|
|
29
|
+
private readonly logger: McpLoggerService,
|
|
30
|
+
) {
|
|
31
|
+
this.server = new McpServer(this.options.serverInfo, this.options.options);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async onModuleInit() {
|
|
35
|
+
await this.registry.registerAll(this.server);
|
|
36
|
+
|
|
37
|
+
this.logger.log('MCP STREAMEABLE initialization completed');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Handle a streamable HTTP POST request from a client
|
|
42
|
+
*
|
|
43
|
+
* - Uses sessionId from query or generates a new one if missing
|
|
44
|
+
* - Stores the transport by sessionId for later retrieval
|
|
45
|
+
* - Cleans up transport on connection close
|
|
46
|
+
*
|
|
47
|
+
* @param req Express Request object (expects sessionId in query)
|
|
48
|
+
* @param res Express Response object
|
|
49
|
+
*/
|
|
50
|
+
async handlePostRequest(
|
|
51
|
+
req: Request<any, any, any, { sessionId?: string }>,
|
|
52
|
+
res: Response,
|
|
53
|
+
) {
|
|
54
|
+
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
55
|
+
let transport: StreamableHTTPServerTransport;
|
|
56
|
+
|
|
57
|
+
const { options } = this.transportOptions?.streamable || {};
|
|
58
|
+
|
|
59
|
+
if (sessionId && this.transports[sessionId]) {
|
|
60
|
+
transport = this.transports[sessionId];
|
|
61
|
+
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
62
|
+
transport = new StreamableHTTPServerTransport({
|
|
63
|
+
sessionIdGenerator: () =>
|
|
64
|
+
options?.sessionIdGenerator?.() || randomUUID(),
|
|
65
|
+
onsessioninitialized: (sessionId) => {
|
|
66
|
+
this.transports[sessionId] = transport;
|
|
67
|
+
},
|
|
68
|
+
enableJsonResponse: options?.enableJsonResponse,
|
|
69
|
+
eventStore: options?.eventStore,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
transport.onclose = () => {
|
|
73
|
+
if (transport.sessionId) {
|
|
74
|
+
delete this.transports[transport.sessionId];
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
} else {
|
|
78
|
+
// Invalid request
|
|
79
|
+
res.status(400).json({
|
|
80
|
+
jsonrpc: '2.0',
|
|
81
|
+
error: {
|
|
82
|
+
code: -32000,
|
|
83
|
+
message: 'Bad Request: No valid session ID provided',
|
|
84
|
+
},
|
|
85
|
+
id: null,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await transport.handleRequest(req, res, req.body);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Handle a streamable HTTP GET request from a client
|
|
96
|
+
*
|
|
97
|
+
* This method retrieves the existing streamable transport for the session and delegates the request.
|
|
98
|
+
*
|
|
99
|
+
* @param req Express Request object (expects sessionId in query)
|
|
100
|
+
* @param res Express Response object
|
|
101
|
+
*/
|
|
102
|
+
async handleGetRequest(
|
|
103
|
+
req: Request<any, any, any, { sessionId?: string }>,
|
|
104
|
+
res: Response,
|
|
105
|
+
) {
|
|
106
|
+
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
107
|
+
|
|
108
|
+
if (!sessionId || !this.transports[sessionId]) {
|
|
109
|
+
res.status(400).send('Invalid or missing session ID');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const transport = this.transports[sessionId];
|
|
114
|
+
|
|
115
|
+
await transport.handleRequest(req, res);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Handle a streamable HTTP DELETE request to clean up a session
|
|
120
|
+
*
|
|
121
|
+
* - Accepts sessionId from query or x-mcp-session-id header
|
|
122
|
+
* - Closes and removes the transport if found
|
|
123
|
+
* - Always sends a response
|
|
124
|
+
*
|
|
125
|
+
* @param req Express Request object
|
|
126
|
+
* @param res Express Response object
|
|
127
|
+
*/
|
|
128
|
+
async handleDeleteRequest(
|
|
129
|
+
req: Request<any, any, any, { sessionId?: string }>,
|
|
130
|
+
res: Response,
|
|
131
|
+
) {
|
|
132
|
+
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
133
|
+
|
|
134
|
+
if (!sessionId) {
|
|
135
|
+
res.status(400).json({ error: 'Missing sessionId' });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const transport = this.transports[sessionId];
|
|
140
|
+
|
|
141
|
+
if (transport) {
|
|
142
|
+
this.logger.debug(
|
|
143
|
+
`Closing streamable transport for sessionId: ${sessionId}`,
|
|
144
|
+
'STREAMABLE',
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
await transport.close();
|
|
148
|
+
|
|
149
|
+
const uuidV4Regex =
|
|
150
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
151
|
+
|
|
152
|
+
if (!uuidV4Regex.test(sessionId)) {
|
|
153
|
+
res.status(400).json({ error: 'Invalid sessionId format' });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
delete this.transports[sessionId];
|
|
158
|
+
|
|
159
|
+
res.status(200).json({ success: true, sessionId });
|
|
160
|
+
} else {
|
|
161
|
+
this.logger.debug(
|
|
162
|
+
`No streamable transport found for sessionId: ${sessionId}`,
|
|
163
|
+
'STREAMABLE',
|
|
164
|
+
);
|
|
165
|
+
res.status(404).json({ error: 'Transport not found', sessionId });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -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
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CompleteResourceTemplateCallback,
|
|
3
|
+
ListResourcesCallback,
|
|
4
|
+
} from '@modelcontextprotocol/sdk/server/mcp';
|
|
5
|
+
import { ZodOptional, ZodRawShape, ZodType, ZodTypeDef } from 'zod';
|
|
6
|
+
|
|
7
|
+
export interface ResourceBaseOptions {
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ResourceUriOptions extends ResourceBaseOptions {
|
|
12
|
+
uri: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ResourceUriWithMetadataOptions extends ResourceUriOptions {
|
|
16
|
+
metadata: Record<string, any>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ResourceTemplateOptions extends ResourceBaseOptions {
|
|
20
|
+
template: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ResourceTemplateWithMetadataOptions
|
|
24
|
+
extends ResourceTemplateOptions {
|
|
25
|
+
metadata: Record<string, any>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ResourceOptions =
|
|
29
|
+
| ResourceUriOptions
|
|
30
|
+
| ResourceUriWithMetadataOptions
|
|
31
|
+
| ResourceTemplateOptions
|
|
32
|
+
| ResourceTemplateWithMetadataOptions;
|
|
33
|
+
|
|
34
|
+
export interface ToolNameOptions {
|
|
35
|
+
name: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ToolWithDescriptionOptions extends ToolNameOptions {
|
|
39
|
+
description: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ToolWithParamSchemaOptions extends ToolNameOptions {
|
|
43
|
+
paramSchema: ZodRawShape;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ToolWithDescriptionAndParamSchemaOptions
|
|
47
|
+
extends ToolWithDescriptionOptions,
|
|
48
|
+
ToolWithParamSchemaOptions {}
|
|
49
|
+
|
|
50
|
+
export type ToolOptions =
|
|
51
|
+
| ToolNameOptions
|
|
52
|
+
| ToolWithDescriptionOptions
|
|
53
|
+
| ToolWithParamSchemaOptions
|
|
54
|
+
| ToolWithDescriptionAndParamSchemaOptions;
|
|
55
|
+
|
|
56
|
+
export interface PromptBaseOptions {
|
|
57
|
+
name: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface PromptWithDescriptionOptions extends PromptBaseOptions {
|
|
61
|
+
description: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type PromptArgsRawShape = {
|
|
65
|
+
[k: string]:
|
|
66
|
+
| ZodType<string, ZodTypeDef, string>
|
|
67
|
+
| ZodOptional<ZodType<string, ZodTypeDef, string>>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export interface PromptWithArgsSchemaOptions extends PromptBaseOptions {
|
|
71
|
+
argsSchema: PromptArgsRawShape;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface PromptWithDescriptionAndArgsSchemaOptions
|
|
75
|
+
extends PromptWithDescriptionOptions,
|
|
76
|
+
PromptWithArgsSchemaOptions {}
|
|
77
|
+
|
|
78
|
+
export type PromptOptions =
|
|
79
|
+
| PromptBaseOptions
|
|
80
|
+
| PromptWithDescriptionOptions
|
|
81
|
+
| PromptWithArgsSchemaOptions
|
|
82
|
+
| PromptWithDescriptionAndArgsSchemaOptions;
|
|
83
|
+
|
|
84
|
+
export interface TemplateCallbacks {
|
|
85
|
+
/**
|
|
86
|
+
* A callback to list all resources matching this template. This is required to specified, even if `undefined`, to avoid accidentally forgetting resource listing.
|
|
87
|
+
*/
|
|
88
|
+
list: ListResourcesCallback | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* An optional callback to autocomplete variables within the URI template. Useful for clients and users to discover possible values.
|
|
91
|
+
*/
|
|
92
|
+
complete?: {
|
|
93
|
+
[variable: string]: CompleteResourceTemplateCallback;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ExecutionContext } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Custom execution context for MCP guards.
|
|
5
|
+
* Extends NestJS ExecutionContext and adds args for MCP method arguments.
|
|
6
|
+
*
|
|
7
|
+
* @property args - The arguments passed to the MCP method
|
|
8
|
+
*/
|
|
9
|
+
// TODO: Type Args correctly
|
|
10
|
+
export interface McpExecutionContext extends ExecutionContext {
|
|
11
|
+
/** The arguments passed to the MCP method */
|
|
12
|
+
args: unknown[];
|
|
13
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { StreamableHTTPServerTransportOptions } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
2
|
+
import { ProtocolOptions } from '@modelcontextprotocol/sdk/shared/protocol';
|
|
3
|
+
import {
|
|
4
|
+
Implementation,
|
|
5
|
+
ServerCapabilities,
|
|
6
|
+
} from '@modelcontextprotocol/sdk/types';
|
|
7
|
+
import { Provider, Type } from '@nestjs/common';
|
|
8
|
+
|
|
9
|
+
export type ServerOptions = {
|
|
10
|
+
instructions?: string;
|
|
11
|
+
capabilities?: ServerCapabilities;
|
|
12
|
+
protocolOptions?: ProtocolOptions;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type McpServerOptions = {
|
|
16
|
+
serverInfo: Implementation;
|
|
17
|
+
options?: ServerOptions;
|
|
18
|
+
logging?: McpLoggingOptions;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Opciones para configurar el logging del servidor MCP
|
|
23
|
+
*/
|
|
24
|
+
export interface McpLoggingOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Habilitar o deshabilitar el logging
|
|
27
|
+
* @default true
|
|
28
|
+
*/
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Nivel de detalle del logging
|
|
33
|
+
* @default 'verbose'
|
|
34
|
+
*/
|
|
35
|
+
level?: 'debug' | 'verbose' | 'log' | 'warn' | 'error';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Options for configuring the global MCP server module
|
|
40
|
+
*/
|
|
41
|
+
export interface McpModuleOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Additional modules to import
|
|
44
|
+
*/
|
|
45
|
+
imports?: Type<any>[];
|
|
46
|
+
/**
|
|
47
|
+
* Providers to register in the module
|
|
48
|
+
* These will be available globally
|
|
49
|
+
*/
|
|
50
|
+
providers?: Provider[];
|
|
51
|
+
/**
|
|
52
|
+
* Name of the MCP server
|
|
53
|
+
*/
|
|
54
|
+
name: string;
|
|
55
|
+
/**
|
|
56
|
+
* Version of the MCP server
|
|
57
|
+
*/
|
|
58
|
+
version: string;
|
|
59
|
+
/**
|
|
60
|
+
* Description to give the AI about the server
|
|
61
|
+
*/
|
|
62
|
+
instructions?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Describes the server's purpose or behavior for the AI
|
|
65
|
+
*/
|
|
66
|
+
capabilities?: ServerCapabilities;
|
|
67
|
+
/**
|
|
68
|
+
* Protocol-specific options
|
|
69
|
+
*/
|
|
70
|
+
protocolOptions?: ProtocolOptions;
|
|
71
|
+
/**
|
|
72
|
+
* Options for configuring MCP server logging
|
|
73
|
+
*/
|
|
74
|
+
logging?: McpLoggingOptions;
|
|
75
|
+
/**
|
|
76
|
+
* Options for configuring a feature module with MCP capabilities
|
|
77
|
+
*/
|
|
78
|
+
transports?: McpModuleTransportOptions;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type McpModuleTransportOptions = {
|
|
82
|
+
streamable?: {
|
|
83
|
+
enabled: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Streamable transport options. sessionIdGenerator is optional here, even if required in the SDK type.
|
|
86
|
+
*/
|
|
87
|
+
options?: Omit<
|
|
88
|
+
StreamableHTTPServerTransportOptions,
|
|
89
|
+
'onsessioninitialized' | 'sessionIdGenerator'
|
|
90
|
+
> & {
|
|
91
|
+
sessionIdGenerator?: () => string | undefined;
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
sse?: {
|
|
95
|
+
enabled: boolean;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Options for configuring a feature module with MCP capabilities
|
|
101
|
+
*/
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
103
|
+
export interface McpFeatureOptions {
|
|
104
|
+
// TODO: Maybe its needed to implement Guards for all capabilities in this module o a specific logger configuration
|
|
105
|
+
}
|