@nestjs-mcp/server 0.1.1 → 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/dist/controllers/sse/sse.controller.d.ts +3 -1
- package/dist/controllers/sse/sse.controller.js +8 -2
- package/dist/controllers/sse/sse.controller.js.map +1 -1
- package/dist/controllers/sse/sse.service.d.ts +3 -2
- package/dist/controllers/sse/sse.service.js +21 -6
- package/dist/controllers/sse/sse.service.js.map +1 -1
- package/dist/controllers/streamable/streamable.service.d.ts +6 -11
- package/dist/controllers/streamable/streamable.service.js +36 -11
- package/dist/controllers/streamable/streamable.service.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces/capabilities.interface.d.ts +32 -2
- package/dist/interfaces/capabilities.interface.js +51 -0
- package/dist/interfaces/capabilities.interface.js.map +1 -1
- package/dist/interfaces/context.interface.d.ts +6 -0
- package/dist/interfaces/context.interface.js +3 -0
- package/dist/interfaces/context.interface.js.map +1 -0
- package/dist/interfaces/index.d.ts +2 -1
- package/dist/interfaces/index.js +2 -1
- package/dist/interfaces/index.js.map +1 -1
- package/dist/interfaces/mcp-server-options.interface.d.ts +2 -2
- package/dist/interfaces/message.types.d.ts +8 -0
- package/dist/interfaces/{guards.interface.js → message.types.js} +1 -1
- package/dist/interfaces/message.types.js.map +1 -0
- package/dist/mcp.module.js +13 -1
- package/dist/mcp.module.js.map +1 -1
- package/dist/registry/registry.service.d.ts +7 -1
- package/dist/registry/registry.service.js +72 -6
- package/dist/registry/registry.service.js.map +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +18 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/session.manager.d.ts +14 -0
- package/dist/services/session.manager.js +27 -0
- package/dist/services/session.manager.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +23 -16
- package/.copilotignore +0 -38
- package/.devcontainer/Dockerfile.dev +0 -28
- package/.devcontainer/devcontainer.json +0 -56
- package/.devcontainer/docker-compose.yml +0 -15
- package/.dockerignore +0 -37
- package/.prettierrc +0 -4
- package/coverage/clover.xml +0 -507
- package/coverage/coverage-final.json +0 -19
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -206
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -196
- package/coverage/lcov-report/src/controllers/sse/index.html +0 -146
- package/coverage/lcov-report/src/controllers/sse/index.ts.html +0 -91
- package/coverage/lcov-report/src/controllers/sse/sse.controller.ts.html +0 -160
- package/coverage/lcov-report/src/controllers/sse/sse.service.ts.html +0 -403
- package/coverage/lcov-report/src/controllers/streamable/index.html +0 -146
- package/coverage/lcov-report/src/controllers/streamable/index.ts.html +0 -91
- package/coverage/lcov-report/src/controllers/streamable/streamable.controller.ts.html +0 -157
- package/coverage/lcov-report/src/controllers/streamable/streamable.service.ts.html +0 -655
- package/coverage/lcov-report/src/decorators/capabilities.constants.ts.html +0 -106
- package/coverage/lcov-report/src/decorators/capabilities.decorators.ts.html +0 -535
- package/coverage/lcov-report/src/decorators/index.html +0 -146
- package/coverage/lcov-report/src/decorators/index.ts.html +0 -91
- package/coverage/lcov-report/src/index.html +0 -131
- package/coverage/lcov-report/src/index.ts.html +0 -118
- package/coverage/lcov-report/src/interfaces/capabilities.interface.ts.html +0 -703
- package/coverage/lcov-report/src/interfaces/index.html +0 -131
- package/coverage/lcov-report/src/interfaces/index.ts.html +0 -91
- package/coverage/lcov-report/src/mcp.module.ts.html +0 -817
- package/coverage/lcov-report/src/registry/discovery.service.ts.html +0 -433
- package/coverage/lcov-report/src/registry/index.html +0 -161
- package/coverage/lcov-report/src/registry/index.ts.html +0 -91
- package/coverage/lcov-report/src/registry/logger.service.ts.html +0 -514
- package/coverage/lcov-report/src/registry/registry.service.ts.html +0 -1183
- package/coverage/lcov-report/src/services/index.html +0 -116
- package/coverage/lcov-report/src/services/session.manager.ts.html +0 -163
- package/coverage/lcov.info +0 -912
- package/dist/interfaces/guards.interface.d.ts +0 -4
- package/dist/interfaces/guards.interface.js.map +0 -1
- package/eslint.config.mjs +0 -40
- package/src/controllers/sse/index.ts +0 -2
- package/src/controllers/sse/sse.controller.ts +0 -25
- package/src/controllers/sse/sse.service.ts +0 -106
- package/src/controllers/streamable/index.ts +0 -2
- package/src/controllers/streamable/streamable.controller.ts +0 -24
- package/src/controllers/streamable/streamable.service.ts +0 -190
- package/src/decorators/capabilities.constants.ts +0 -7
- package/src/decorators/capabilities.decorators.ts +0 -150
- package/src/decorators/index.ts +0 -2
- package/src/index.ts +0 -11
- package/src/interfaces/capabilities.interface.ts +0 -206
- package/src/interfaces/context.interface.ts +0 -33
- package/src/interfaces/index.ts +0 -2
- package/src/interfaces/mcp-server-options.interface.ts +0 -105
- package/src/interfaces/message.types.ts +0 -13
- package/src/mcp.module.ts +0 -244
- package/src/mcp.service.spec.ts +0 -28
- package/src/registry/discovery.service.ts +0 -116
- package/src/registry/index.ts +0 -2
- package/src/registry/logger.service.ts +0 -143
- package/src/registry/registry.service.ts +0 -366
- package/src/services/session.manager.ts +0 -26
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { Injectable } from '@nestjs/common';
|
|
2
|
-
import {
|
|
3
|
-
MetadataScanner,
|
|
4
|
-
DiscoveryService as NestDiscoveryService,
|
|
5
|
-
Reflector,
|
|
6
|
-
} from '@nestjs/core';
|
|
7
|
-
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
|
|
8
|
-
|
|
9
|
-
export interface MethodWithMetadata<T = unknown> {
|
|
10
|
-
method: string;
|
|
11
|
-
metadata: T;
|
|
12
|
-
handler: (...args: any[]) => any;
|
|
13
|
-
instance: object;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type ProviderInstance = Record<string, unknown>;
|
|
17
|
-
|
|
18
|
-
@Injectable()
|
|
19
|
-
export class DiscoveryService {
|
|
20
|
-
constructor(
|
|
21
|
-
private readonly discoveryService: NestDiscoveryService,
|
|
22
|
-
private readonly metadataScanner: MetadataScanner,
|
|
23
|
-
private readonly reflector: Reflector,
|
|
24
|
-
) {}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Get all methods with specific metadata from all providers (now scans all @Injectable, not just @McpProvider)
|
|
28
|
-
*/
|
|
29
|
-
public getAllMethodsWithMetadata<T = unknown>(
|
|
30
|
-
metadataKey: string,
|
|
31
|
-
): MethodWithMetadata<T>[] {
|
|
32
|
-
const providers = this.discoveryService.getProviders();
|
|
33
|
-
const result: MethodWithMetadata<T>[] = [];
|
|
34
|
-
|
|
35
|
-
// Scan ALL providers, not just those with MCP_PROVIDER
|
|
36
|
-
for (const provider of providers) {
|
|
37
|
-
if (!provider.instance) {
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const methods = this.getMethodsWithMetadataFromProvider<T>(
|
|
42
|
-
provider,
|
|
43
|
-
metadataKey,
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
if (methods.length > 0) {
|
|
47
|
-
result.push(...methods);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return result;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Get all methods with a specific metadata from a single provider.
|
|
56
|
-
*/
|
|
57
|
-
public getMethodsWithMetadataFromProvider<T = unknown>(
|
|
58
|
-
provider: InstanceWrapper,
|
|
59
|
-
metadataKey: string,
|
|
60
|
-
): MethodWithMetadata<T>[] {
|
|
61
|
-
if (!provider.instance) {
|
|
62
|
-
return [];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const instance = provider.instance as ProviderInstance;
|
|
66
|
-
const instancePrototype = Object.getPrototypeOf(instance) as Record<
|
|
67
|
-
string,
|
|
68
|
-
unknown
|
|
69
|
-
>;
|
|
70
|
-
|
|
71
|
-
if (!instancePrototype) {
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const methodNames =
|
|
76
|
-
this.metadataScanner.getAllMethodNames(instancePrototype);
|
|
77
|
-
const result: MethodWithMetadata<T>[] = [];
|
|
78
|
-
|
|
79
|
-
for (const methodName of methodNames) {
|
|
80
|
-
// Removed hasOwnProperty check to allow inherited methods
|
|
81
|
-
const methodFunction = instancePrototype[methodName];
|
|
82
|
-
if (typeof methodFunction !== 'function') {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const metadata = this.reflector.get<T>(metadataKey, methodFunction);
|
|
87
|
-
|
|
88
|
-
if (metadata) {
|
|
89
|
-
const handlerProperty = instance[methodName];
|
|
90
|
-
|
|
91
|
-
if (typeof handlerProperty !== 'function') {
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const handler = ((...args: unknown[]): unknown => {
|
|
96
|
-
return handlerProperty.apply(instance, args);
|
|
97
|
-
}) as (...args: any[]) => any;
|
|
98
|
-
|
|
99
|
-
// Preserve the original method name
|
|
100
|
-
Object.defineProperty(handler, 'name', {
|
|
101
|
-
value: methodName,
|
|
102
|
-
writable: false,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
result.push({
|
|
106
|
-
method: methodName,
|
|
107
|
-
metadata: metadata as T,
|
|
108
|
-
handler,
|
|
109
|
-
instance,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return result;
|
|
115
|
-
}
|
|
116
|
-
}
|
package/src/registry/index.ts
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Inject,
|
|
3
|
-
Injectable,
|
|
4
|
-
Logger,
|
|
5
|
-
LoggerService,
|
|
6
|
-
Optional,
|
|
7
|
-
} from '@nestjs/common';
|
|
8
|
-
|
|
9
|
-
import { McpLoggingOptions } from '../interfaces/mcp-server-options.interface';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Servicio especializado de logging para el servidor MCP
|
|
13
|
-
*/
|
|
14
|
-
@Injectable()
|
|
15
|
-
export class McpLoggerService implements LoggerService {
|
|
16
|
-
private readonly logger: Logger;
|
|
17
|
-
private readonly options: Required<McpLoggingOptions>;
|
|
18
|
-
|
|
19
|
-
constructor(
|
|
20
|
-
@Optional() @Inject('MCP_LOGGING_OPTIONS') options?: McpLoggingOptions,
|
|
21
|
-
) {
|
|
22
|
-
this.options = {
|
|
23
|
-
enabled: options?.enabled !== false, // Habilitado por defecto
|
|
24
|
-
level: options?.level || 'verbose',
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
this.logger = new Logger('MCP');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Registra un mensaje de nivel debug
|
|
32
|
-
*/
|
|
33
|
-
debug(message: string, context?: string): void {
|
|
34
|
-
if (
|
|
35
|
-
!this.options.enabled ||
|
|
36
|
-
this.getLevelValue(this.options.level) > this.getLevelValue('debug')
|
|
37
|
-
) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const formattedContext = this.formatContext(context);
|
|
42
|
-
this.logger.debug(message, formattedContext);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Registra un mensaje de nivel verbose (detallado)
|
|
47
|
-
*/
|
|
48
|
-
verbose(message: string, context?: string): void {
|
|
49
|
-
if (
|
|
50
|
-
!this.options.enabled ||
|
|
51
|
-
this.getLevelValue(this.options.level) > this.getLevelValue('verbose')
|
|
52
|
-
) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const formattedContext = this.formatContext(context);
|
|
57
|
-
this.logger.verbose(message, formattedContext);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Registra un mensaje de nivel log (información)
|
|
62
|
-
*/
|
|
63
|
-
log(message: string, context?: string): void {
|
|
64
|
-
if (
|
|
65
|
-
!this.options.enabled ||
|
|
66
|
-
this.getLevelValue(this.options.level) > this.getLevelValue('log')
|
|
67
|
-
) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const formattedContext = this.formatContext(context);
|
|
72
|
-
this.logger.log(message, formattedContext);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Registra un mensaje de nivel warn (advertencia)
|
|
77
|
-
*/
|
|
78
|
-
warn(message: string, context?: string): void {
|
|
79
|
-
if (
|
|
80
|
-
!this.options.enabled ||
|
|
81
|
-
this.getLevelValue(this.options.level) > this.getLevelValue('warn')
|
|
82
|
-
) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const formattedContext = this.formatContext(context);
|
|
87
|
-
this.logger.warn(message, formattedContext);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Registra un mensaje de nivel error
|
|
92
|
-
*/
|
|
93
|
-
error(message: string, trace?: string, context?: string): void {
|
|
94
|
-
if (
|
|
95
|
-
!this.options.enabled ||
|
|
96
|
-
this.getLevelValue(this.options.level) > this.getLevelValue('error')
|
|
97
|
-
) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const formattedContext = this.formatContext(context);
|
|
102
|
-
this.logger.error(message, trace, formattedContext);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Método para determinar si el logging está habilitado
|
|
107
|
-
*/
|
|
108
|
-
isEnabled(): boolean {
|
|
109
|
-
return this.options.enabled;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Método para obtener el nivel actual de logging
|
|
114
|
-
*/
|
|
115
|
-
getLevel(): string {
|
|
116
|
-
return this.options.level;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Formatea el contexto para añadirle el prefijo '@mcp'
|
|
121
|
-
*/
|
|
122
|
-
private formatContext(context?: string): string {
|
|
123
|
-
if (!context) {
|
|
124
|
-
return '@mcp';
|
|
125
|
-
}
|
|
126
|
-
return `@mcp:${context}`;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Convierte un nivel de log a un valor numérico para comparaciones
|
|
131
|
-
*/
|
|
132
|
-
private getLevelValue(level: string): number {
|
|
133
|
-
const levels: Record<string, number> = {
|
|
134
|
-
debug: 0,
|
|
135
|
-
verbose: 1,
|
|
136
|
-
log: 2,
|
|
137
|
-
warn: 3,
|
|
138
|
-
error: 4,
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
return levels[level] ?? 2; // Por defecto, nivel 'log'
|
|
142
|
-
}
|
|
143
|
-
}
|
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
McpServer,
|
|
3
|
-
ResourceTemplate,
|
|
4
|
-
} from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
|
-
import type { CanActivate, Type } from '@nestjs/common';
|
|
6
|
-
import { Injectable } from '@nestjs/common';
|
|
7
|
-
import { Reflector } from '@nestjs/core';
|
|
8
|
-
|
|
9
|
-
import { MCP_RESOLVER } from '../decorators';
|
|
10
|
-
import {
|
|
11
|
-
MCP_PROMPT,
|
|
12
|
-
MCP_RESOURCE,
|
|
13
|
-
MCP_TOOL,
|
|
14
|
-
} from '../decorators/capabilities.constants';
|
|
15
|
-
import { MCP_GUARDS } from '../decorators/capabilities.decorators';
|
|
16
|
-
import {
|
|
17
|
-
PromptHandlerParams,
|
|
18
|
-
PromptOptions,
|
|
19
|
-
RequestHandlerExtra,
|
|
20
|
-
ResourceOptions,
|
|
21
|
-
ResourceTemplateHandlerParams,
|
|
22
|
-
ResourceUriHandlerParams,
|
|
23
|
-
ToolHandlerParams,
|
|
24
|
-
ToolOptions,
|
|
25
|
-
} from '../interfaces/capabilities.interface';
|
|
26
|
-
import { McpExecutionContext } from '../interfaces/context.interface';
|
|
27
|
-
import { SessionManager } from '../services/session.manager';
|
|
28
|
-
import { DiscoveryService } from './discovery.service';
|
|
29
|
-
import { McpLoggerService } from './logger.service';
|
|
30
|
-
|
|
31
|
-
@Injectable()
|
|
32
|
-
export class RegistryService {
|
|
33
|
-
constructor(
|
|
34
|
-
private readonly discoveryService: DiscoveryService,
|
|
35
|
-
private readonly logger: McpLoggerService,
|
|
36
|
-
private readonly reflector: Reflector,
|
|
37
|
-
private readonly sessionManager: SessionManager,
|
|
38
|
-
) {}
|
|
39
|
-
|
|
40
|
-
async registerAll(server: McpServer): Promise<void> {
|
|
41
|
-
this.logger.log(
|
|
42
|
-
'Starting registration of all MCP capabilities...',
|
|
43
|
-
'registry',
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
await Promise.all([
|
|
47
|
-
this.registerResources(server),
|
|
48
|
-
this.registerPrompts(server),
|
|
49
|
-
this.registerTools(server),
|
|
50
|
-
]);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
private getDecoratorType(method: Type<any> | undefined): string | null {
|
|
54
|
-
if (!method) return null;
|
|
55
|
-
|
|
56
|
-
if (this.reflector.get(MCP_TOOL, method)) return 'TOOL';
|
|
57
|
-
if (this.reflector.get(MCP_PROMPT, method)) return 'PROMPT';
|
|
58
|
-
if (this.reflector.get(MCP_RESOURCE, method)) return 'RESOURCE';
|
|
59
|
-
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
private getHandlerArgs(
|
|
64
|
-
method: Type<any> | undefined,
|
|
65
|
-
args: unknown[],
|
|
66
|
-
):
|
|
67
|
-
| ResourceUriHandlerParams
|
|
68
|
-
| ResourceTemplateHandlerParams
|
|
69
|
-
| PromptHandlerParams
|
|
70
|
-
| ToolHandlerParams {
|
|
71
|
-
if (!method) throw new Error('Method not found');
|
|
72
|
-
|
|
73
|
-
switch (this.getDecoratorType(method)) {
|
|
74
|
-
case 'RESOURCE':
|
|
75
|
-
return args[0] instanceof URL
|
|
76
|
-
? ResourceUriHandlerParams.from(
|
|
77
|
-
args[0],
|
|
78
|
-
args[1] as RequestHandlerExtra,
|
|
79
|
-
)
|
|
80
|
-
: ResourceTemplateHandlerParams.from(
|
|
81
|
-
args[0] as any,
|
|
82
|
-
args[2] as RequestHandlerExtra,
|
|
83
|
-
args[1] as Record<string, string>,
|
|
84
|
-
);
|
|
85
|
-
case 'PROMPT':
|
|
86
|
-
return args.length === 1
|
|
87
|
-
? PromptHandlerParams.from(args[0] as RequestHandlerExtra)
|
|
88
|
-
: PromptHandlerParams.from(
|
|
89
|
-
args[1] as RequestHandlerExtra,
|
|
90
|
-
args[0] as any,
|
|
91
|
-
);
|
|
92
|
-
case 'TOOL':
|
|
93
|
-
return args.length === 1
|
|
94
|
-
? ToolHandlerParams.from(args[0] as RequestHandlerExtra)
|
|
95
|
-
: ToolHandlerParams.from(
|
|
96
|
-
args[1] as RequestHandlerExtra,
|
|
97
|
-
args[0] as any,
|
|
98
|
-
);
|
|
99
|
-
default:
|
|
100
|
-
throw new Error(`Unknown decorator type for method ${method.name}`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Executes all guards attached to the resolver class and method.
|
|
106
|
-
* Throws an error if any guard denies access.
|
|
107
|
-
*
|
|
108
|
-
* @param instance The resolver instance
|
|
109
|
-
* @param methodName The method name being invoked
|
|
110
|
-
* @param args The arguments passed to the method
|
|
111
|
-
* @throws Error if any guard denies access
|
|
112
|
-
*/
|
|
113
|
-
private runGuards(
|
|
114
|
-
instance: object,
|
|
115
|
-
methodName: string,
|
|
116
|
-
args: unknown[],
|
|
117
|
-
): Promise<void> {
|
|
118
|
-
// Retrieve class-level guards
|
|
119
|
-
const classConstructor = instance.constructor;
|
|
120
|
-
|
|
121
|
-
const classGuards: (CanActivate | { new (): CanActivate })[] =
|
|
122
|
-
(Reflect.getMetadata(MCP_GUARDS, classConstructor) as (
|
|
123
|
-
| CanActivate
|
|
124
|
-
| { new (): CanActivate }
|
|
125
|
-
)[]) || [];
|
|
126
|
-
|
|
127
|
-
// Retrieve method-level guards
|
|
128
|
-
const prototype = Object.getPrototypeOf(instance) as Record<
|
|
129
|
-
string,
|
|
130
|
-
unknown
|
|
131
|
-
>;
|
|
132
|
-
|
|
133
|
-
const methodKey = prototype[methodName] as Type<any> | undefined;
|
|
134
|
-
|
|
135
|
-
const methodGuards: (CanActivate | { new (): CanActivate })[] =
|
|
136
|
-
(methodKey &&
|
|
137
|
-
(Reflect.getMetadata(MCP_GUARDS, methodKey) as (
|
|
138
|
-
| CanActivate
|
|
139
|
-
| { new (): CanActivate }
|
|
140
|
-
)[])) ||
|
|
141
|
-
[];
|
|
142
|
-
|
|
143
|
-
// Combine guards: class-level first, then method-level
|
|
144
|
-
const allGuards = [...classGuards, ...methodGuards];
|
|
145
|
-
|
|
146
|
-
if (!allGuards.length) return Promise.resolve();
|
|
147
|
-
|
|
148
|
-
const handlerArgs = this.getHandlerArgs(methodKey, args);
|
|
149
|
-
|
|
150
|
-
const { sessionId } = handlerArgs.extra;
|
|
151
|
-
|
|
152
|
-
if (!sessionId) {
|
|
153
|
-
throw new Error('Session not found');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const session = this.sessionManager.getSession(sessionId);
|
|
157
|
-
|
|
158
|
-
if (!session) {
|
|
159
|
-
throw new Error('Session not found');
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const context: McpExecutionContext = {
|
|
163
|
-
args: handlerArgs,
|
|
164
|
-
// @ts-expect-error: Default types are 'http' | 'ws' | 'rpc' but in our case, we are using 'mcp'
|
|
165
|
-
getType: () => 'mcp',
|
|
166
|
-
getClass: () => instance.constructor as Type<any>,
|
|
167
|
-
getArgs: <T = any>() => args as T,
|
|
168
|
-
getArgByIndex: <T = any>(index: number) => args[index] as T,
|
|
169
|
-
getSessionId: () => sessionId,
|
|
170
|
-
getHandler: () => methodKey as unknown as Type<any>,
|
|
171
|
-
switchToHttp: () => ({
|
|
172
|
-
getRequest: <R = Request>() => session.request as R,
|
|
173
|
-
getResponse: () => {
|
|
174
|
-
throw new Error('Response not available in MCP context');
|
|
175
|
-
},
|
|
176
|
-
getNext: () => {
|
|
177
|
-
throw new Error('Next not available in MCP context');
|
|
178
|
-
},
|
|
179
|
-
}),
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
return (async () => {
|
|
183
|
-
for (const Guard of allGuards) {
|
|
184
|
-
const guardInstance: CanActivate =
|
|
185
|
-
typeof Guard === 'function' ? new Guard() : Guard;
|
|
186
|
-
const allowed = await guardInstance.canActivate(context);
|
|
187
|
-
|
|
188
|
-
if (!allowed)
|
|
189
|
-
throw new Error(`Access denied by guard on ${methodName}`);
|
|
190
|
-
}
|
|
191
|
-
})();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
private async wrappedHandler<TArgs extends unknown[], TResult>(
|
|
195
|
-
instance: object,
|
|
196
|
-
handler: (...args: TArgs) => TResult,
|
|
197
|
-
args: unknown[],
|
|
198
|
-
) {
|
|
199
|
-
const isResolver = Reflect.hasMetadata(MCP_RESOLVER, instance.constructor);
|
|
200
|
-
|
|
201
|
-
if (!isResolver) {
|
|
202
|
-
throw new Error(
|
|
203
|
-
`Class "${instance.constructor.name}" must be decorated with @Resolver to use @Prompt, @Tool, or @Resource.`,
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const methodName = handler.name;
|
|
208
|
-
|
|
209
|
-
await this.runGuards(instance, methodName, args);
|
|
210
|
-
|
|
211
|
-
return handler(...(args as TArgs));
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
private registerResources(server: McpServer) {
|
|
215
|
-
const resourceMethods =
|
|
216
|
-
this.discoveryService.getAllMethodsWithMetadata<ResourceOptions>(
|
|
217
|
-
MCP_RESOURCE,
|
|
218
|
-
);
|
|
219
|
-
for (const method of resourceMethods) {
|
|
220
|
-
const { metadata, handler, instance } = method;
|
|
221
|
-
|
|
222
|
-
this.logger.log(
|
|
223
|
-
`Resource "${metadata?.name || 'unnamed'}" found.`,
|
|
224
|
-
'resources',
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
const wrappedHandler = (...args: unknown[]) =>
|
|
228
|
-
this.wrappedHandler(instance, handler, args);
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
if ('template' in metadata) {
|
|
232
|
-
if ('metadata' in metadata) {
|
|
233
|
-
server.resource(
|
|
234
|
-
metadata.name,
|
|
235
|
-
new ResourceTemplate(metadata.template, { list: undefined }),
|
|
236
|
-
metadata.metadata,
|
|
237
|
-
wrappedHandler,
|
|
238
|
-
);
|
|
239
|
-
} else {
|
|
240
|
-
server.resource(
|
|
241
|
-
metadata.name,
|
|
242
|
-
new ResourceTemplate(metadata.template, { list: undefined }),
|
|
243
|
-
wrappedHandler,
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
} else if ('uri' in metadata) {
|
|
247
|
-
if ('metadata' in metadata) {
|
|
248
|
-
server.resource(
|
|
249
|
-
metadata.name,
|
|
250
|
-
metadata.uri,
|
|
251
|
-
metadata.metadata,
|
|
252
|
-
wrappedHandler,
|
|
253
|
-
);
|
|
254
|
-
} else {
|
|
255
|
-
server.resource(metadata.name, metadata.uri, wrappedHandler);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
} catch (error) {
|
|
259
|
-
this.logger.error(
|
|
260
|
-
`Error registering resource ${metadata.name}: ${error}`,
|
|
261
|
-
undefined,
|
|
262
|
-
'resources',
|
|
263
|
-
);
|
|
264
|
-
if (error && typeof error === 'object' && 'stack' in error) {
|
|
265
|
-
this.logger.error(
|
|
266
|
-
`Error stack: ${(error as Error).stack}`,
|
|
267
|
-
undefined,
|
|
268
|
-
'resources',
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
private registerPrompts(server: McpServer) {
|
|
276
|
-
const promptMethods =
|
|
277
|
-
this.discoveryService.getAllMethodsWithMetadata<PromptOptions>(
|
|
278
|
-
MCP_PROMPT,
|
|
279
|
-
);
|
|
280
|
-
for (const method of promptMethods) {
|
|
281
|
-
const { metadata, handler, instance } = method;
|
|
282
|
-
|
|
283
|
-
this.logger.log(
|
|
284
|
-
`Prompt "${metadata?.name || 'unnamed'}" found.`,
|
|
285
|
-
'prompts',
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
const wrappedHandler = (...args: unknown[]) =>
|
|
289
|
-
this.wrappedHandler(instance, handler, args);
|
|
290
|
-
|
|
291
|
-
try {
|
|
292
|
-
if ('description' in metadata && 'argsSchema' in metadata) {
|
|
293
|
-
server.prompt(
|
|
294
|
-
metadata.name,
|
|
295
|
-
metadata.description,
|
|
296
|
-
metadata.argsSchema,
|
|
297
|
-
wrappedHandler,
|
|
298
|
-
);
|
|
299
|
-
} else if ('argsSchema' in metadata) {
|
|
300
|
-
server.prompt(metadata.name, metadata.argsSchema, wrappedHandler);
|
|
301
|
-
} else if ('description' in metadata) {
|
|
302
|
-
server.prompt(metadata.name, metadata.description, wrappedHandler);
|
|
303
|
-
} else {
|
|
304
|
-
server.prompt(metadata.name, wrappedHandler);
|
|
305
|
-
}
|
|
306
|
-
} catch (error) {
|
|
307
|
-
this.logger.error(
|
|
308
|
-
`Error registering prompt ${metadata.name}: ${error}`,
|
|
309
|
-
undefined,
|
|
310
|
-
'prompts',
|
|
311
|
-
);
|
|
312
|
-
if (error && typeof error === 'object' && 'stack' in error) {
|
|
313
|
-
this.logger.error(
|
|
314
|
-
`Error stack: ${(error as Error).stack}`,
|
|
315
|
-
undefined,
|
|
316
|
-
'prompts',
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
private registerTools(server: McpServer) {
|
|
324
|
-
const toolMethods =
|
|
325
|
-
this.discoveryService.getAllMethodsWithMetadata<ToolOptions>(MCP_TOOL);
|
|
326
|
-
|
|
327
|
-
for (const method of toolMethods) {
|
|
328
|
-
const { metadata, handler, instance } = method;
|
|
329
|
-
|
|
330
|
-
this.logger.log(`Tool "${metadata?.name || 'unnamed'}" found.`, 'tools');
|
|
331
|
-
|
|
332
|
-
const wrappedHandler = (...args: unknown[]) =>
|
|
333
|
-
this.wrappedHandler(instance, handler, args);
|
|
334
|
-
|
|
335
|
-
try {
|
|
336
|
-
if ('description' in metadata && 'paramSchema' in metadata) {
|
|
337
|
-
server.tool(
|
|
338
|
-
metadata.name,
|
|
339
|
-
metadata.description,
|
|
340
|
-
metadata.paramSchema,
|
|
341
|
-
wrappedHandler,
|
|
342
|
-
);
|
|
343
|
-
} else if ('paramSchema' in metadata) {
|
|
344
|
-
server.tool(metadata.name, metadata.paramSchema, wrappedHandler);
|
|
345
|
-
} else if ('description' in metadata) {
|
|
346
|
-
server.tool(metadata.name, metadata.description, wrappedHandler);
|
|
347
|
-
} else {
|
|
348
|
-
server.tool(metadata.name, wrappedHandler);
|
|
349
|
-
}
|
|
350
|
-
} catch (error) {
|
|
351
|
-
this.logger.error(
|
|
352
|
-
`Error registering tool ${metadata.name}: ${error}`,
|
|
353
|
-
undefined,
|
|
354
|
-
'tools',
|
|
355
|
-
);
|
|
356
|
-
if (error && typeof error === 'object' && 'stack' in error) {
|
|
357
|
-
this.logger.error(
|
|
358
|
-
`Stack trace: ${(error as Error).stack}`,
|
|
359
|
-
undefined,
|
|
360
|
-
'tools',
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse';
|
|
2
|
-
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
3
|
-
import { Injectable } from '@nestjs/common';
|
|
4
|
-
import { Request } from 'express';
|
|
5
|
-
|
|
6
|
-
type Session = {
|
|
7
|
-
transport: StreamableHTTPServerTransport | SSEServerTransport;
|
|
8
|
-
request: Request;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
@Injectable()
|
|
12
|
-
export class SessionManager {
|
|
13
|
-
private sessions = new Map<string, Session>();
|
|
14
|
-
|
|
15
|
-
public getSession(sessionId: string): Session | undefined {
|
|
16
|
-
return this.sessions.get(sessionId);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
public setSession(sessionId: string, session: Session): void {
|
|
20
|
-
this.sessions.set(sessionId, session);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
public deleteSession(sessionId: string): void {
|
|
24
|
-
this.sessions.delete(sessionId);
|
|
25
|
-
}
|
|
26
|
-
}
|