@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,233 @@
|
|
|
1
|
+
import { Implementation } from '@modelcontextprotocol/sdk/types';
|
|
2
|
+
import { DynamicModule, Module, Provider, Type } from '@nestjs/common';
|
|
3
|
+
import { DiscoveryModule } from '@nestjs/core';
|
|
4
|
+
|
|
5
|
+
import { SseController, SseService } from './controllers/sse';
|
|
6
|
+
import {
|
|
7
|
+
StreamableController,
|
|
8
|
+
StreamableService,
|
|
9
|
+
} from './controllers/streamable';
|
|
10
|
+
import {
|
|
11
|
+
McpFeatureOptions,
|
|
12
|
+
McpLoggingOptions,
|
|
13
|
+
McpModuleOptions,
|
|
14
|
+
McpModuleTransportOptions,
|
|
15
|
+
ServerOptions,
|
|
16
|
+
} from './interfaces/mcp-server-options.interface';
|
|
17
|
+
import { DiscoveryService } from './registry/discovery.service';
|
|
18
|
+
import { McpLoggerService } from './registry/logger.service';
|
|
19
|
+
import { RegistryService } from './registry/registry.service';
|
|
20
|
+
|
|
21
|
+
@Module({
|
|
22
|
+
imports: [DiscoveryModule],
|
|
23
|
+
providers: [RegistryService, DiscoveryService, McpLoggerService],
|
|
24
|
+
})
|
|
25
|
+
export class McpModule {
|
|
26
|
+
/**
|
|
27
|
+
* Helper: Get active transport controllers and providers
|
|
28
|
+
*/
|
|
29
|
+
private static getActiveTransportControllersAndProviders(
|
|
30
|
+
transports?: McpModuleTransportOptions,
|
|
31
|
+
) {
|
|
32
|
+
const controllers = new Set<Type<any>>();
|
|
33
|
+
const providers = new Set<Provider>();
|
|
34
|
+
|
|
35
|
+
// Transport configurations
|
|
36
|
+
const STREAMABLE_TRANSPORT = {
|
|
37
|
+
controller: StreamableController,
|
|
38
|
+
service: StreamableService,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const SSE_TRANSPORT = {
|
|
42
|
+
controller: SseController,
|
|
43
|
+
service: SseService,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Default configuration
|
|
47
|
+
const defaultTransports: McpModuleTransportOptions = {
|
|
48
|
+
streamable: { enabled: true },
|
|
49
|
+
sse: { enabled: true },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Merge default with provided transports
|
|
53
|
+
const config = {
|
|
54
|
+
streamable: {
|
|
55
|
+
...defaultTransports.streamable,
|
|
56
|
+
...(transports?.streamable ?? {}),
|
|
57
|
+
},
|
|
58
|
+
sse: {
|
|
59
|
+
...defaultTransports.sse,
|
|
60
|
+
...(transports?.sse ?? {}),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Add controllers and providers based on enabled transports
|
|
65
|
+
if (config.streamable.enabled) {
|
|
66
|
+
controllers.add(STREAMABLE_TRANSPORT.controller);
|
|
67
|
+
providers.add(STREAMABLE_TRANSPORT.service);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (config.sse.enabled) {
|
|
71
|
+
controllers.add(SSE_TRANSPORT.controller);
|
|
72
|
+
providers.add(SSE_TRANSPORT.service);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
controllers: Array.from(controllers),
|
|
77
|
+
providers: Array.from(providers),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Helper to build server info, options, and logging config
|
|
83
|
+
*/
|
|
84
|
+
private static buildServerConfig(options: McpModuleOptions) {
|
|
85
|
+
const serverInfo: Implementation = {
|
|
86
|
+
name: options.name,
|
|
87
|
+
version: options.version,
|
|
88
|
+
};
|
|
89
|
+
const serverOptions: ServerOptions = {
|
|
90
|
+
instructions: options?.instructions,
|
|
91
|
+
capabilities: options?.capabilities,
|
|
92
|
+
...(options?.protocolOptions || {}),
|
|
93
|
+
};
|
|
94
|
+
const loggingOptions: McpLoggingOptions = {
|
|
95
|
+
enabled: options.logging?.enabled !== false,
|
|
96
|
+
level: options.logging?.level || 'verbose',
|
|
97
|
+
};
|
|
98
|
+
return { serverInfo, serverOptions, loggingOptions };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Configures the MCP module with global options
|
|
103
|
+
*
|
|
104
|
+
* @param options Configuration options for the MCP server
|
|
105
|
+
* @returns Dynamic module configuration
|
|
106
|
+
*/
|
|
107
|
+
static forRoot(options: McpModuleOptions): DynamicModule {
|
|
108
|
+
const imports = options.imports || [];
|
|
109
|
+
const { controllers, providers } =
|
|
110
|
+
this.getActiveTransportControllersAndProviders(options.transports);
|
|
111
|
+
const allProviders = [...(options.providers || []), ...providers];
|
|
112
|
+
const { serverInfo, serverOptions, loggingOptions } =
|
|
113
|
+
this.buildServerConfig(options);
|
|
114
|
+
return {
|
|
115
|
+
module: McpModule,
|
|
116
|
+
imports,
|
|
117
|
+
controllers,
|
|
118
|
+
providers: [
|
|
119
|
+
...allProviders,
|
|
120
|
+
{
|
|
121
|
+
provide: 'MCP_SERVER_OPTIONS',
|
|
122
|
+
useValue: {
|
|
123
|
+
serverInfo,
|
|
124
|
+
options: serverOptions,
|
|
125
|
+
logging: loggingOptions,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
provide: 'MCP_LOGGING_OPTIONS',
|
|
130
|
+
useValue: loggingOptions,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
provide: 'MCP_TRANSPORT_OPTIONS',
|
|
134
|
+
useValue: options.transports,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
global: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Configures the MCP module with global options and ConfigModule support
|
|
143
|
+
* Allows using environment variables and centralized configurations
|
|
144
|
+
*
|
|
145
|
+
* @param options Configuration options for the MCP server
|
|
146
|
+
* @returns Dynamic module configuration
|
|
147
|
+
*/
|
|
148
|
+
static forRootAsync(options: {
|
|
149
|
+
imports?: any[];
|
|
150
|
+
useFactory: (
|
|
151
|
+
...args: unknown[]
|
|
152
|
+
) => Promise<McpModuleOptions> | McpModuleOptions;
|
|
153
|
+
inject?: any[];
|
|
154
|
+
}): DynamicModule {
|
|
155
|
+
const { imports = [], useFactory, inject = [] } = options;
|
|
156
|
+
const safeInject = Array.isArray(inject) ? inject : [];
|
|
157
|
+
const safeImports = Array.isArray(imports) ? imports : [];
|
|
158
|
+
const providers = [
|
|
159
|
+
{
|
|
160
|
+
provide: 'MCP_SERVER_OPTIONS',
|
|
161
|
+
useFactory: async (...args: unknown[]) => {
|
|
162
|
+
const mcpOptions = await useFactory(...args);
|
|
163
|
+
const { serverInfo, serverOptions, loggingOptions } =
|
|
164
|
+
this.buildServerConfig(mcpOptions);
|
|
165
|
+
return {
|
|
166
|
+
serverInfo,
|
|
167
|
+
options: serverOptions,
|
|
168
|
+
logging: loggingOptions,
|
|
169
|
+
};
|
|
170
|
+
},
|
|
171
|
+
inject: safeInject,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
provide: 'MCP_LOGGING_OPTIONS',
|
|
175
|
+
useFactory: async (...args: unknown[]) => {
|
|
176
|
+
const mcpOptions = await useFactory(...args);
|
|
177
|
+
return {
|
|
178
|
+
enabled: mcpOptions.logging?.enabled !== false,
|
|
179
|
+
level: mcpOptions.logging?.level || 'verbose',
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
inject: safeInject,
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
const asyncControllersFactory = async (...args: unknown[]) => {
|
|
186
|
+
const mcpOptions = await useFactory(...args);
|
|
187
|
+
return this.getActiveTransportControllersAndProviders(
|
|
188
|
+
mcpOptions.transports,
|
|
189
|
+
).controllers;
|
|
190
|
+
};
|
|
191
|
+
const asyncProvidersFactory = async (...args: unknown[]) => {
|
|
192
|
+
const mcpOptions = await useFactory(...args);
|
|
193
|
+
const { providers } = this.getActiveTransportControllersAndProviders(
|
|
194
|
+
mcpOptions.transports,
|
|
195
|
+
);
|
|
196
|
+
return [...(mcpOptions.providers || []), ...providers];
|
|
197
|
+
};
|
|
198
|
+
return {
|
|
199
|
+
module: McpModule,
|
|
200
|
+
imports: safeImports,
|
|
201
|
+
controllers: [], // Will be resolved at runtime by NestJS
|
|
202
|
+
providers: [
|
|
203
|
+
...providers,
|
|
204
|
+
{
|
|
205
|
+
provide: '__MCP_ASYNC_CONTROLLERS__',
|
|
206
|
+
useFactory: asyncControllersFactory,
|
|
207
|
+
inject: safeInject,
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
provide: '__MCP_ASYNC_PROVIDERS__',
|
|
211
|
+
useFactory: asyncProvidersFactory,
|
|
212
|
+
inject: safeInject,
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
global: true,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Registers feature-specific capabilities like tools, prompts, and resources
|
|
221
|
+
* through dedicated service providers
|
|
222
|
+
*
|
|
223
|
+
* @param options Configuration options for the feature module
|
|
224
|
+
* @returns A dynamic module configuration
|
|
225
|
+
*/
|
|
226
|
+
// TODO: Implement specific Module options
|
|
227
|
+
|
|
228
|
+
static forFeature(_options?: McpFeatureOptions): DynamicModule {
|
|
229
|
+
return {
|
|
230
|
+
module: McpModule,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
+
|
|
3
|
+
import { SseService } from './controllers/sse';
|
|
4
|
+
import { StreamableService } from './controllers/streamable';
|
|
5
|
+
import { McpModule } from './mcp.module';
|
|
6
|
+
describe('NestjsMcpServerService', () => {
|
|
7
|
+
let streamableService: StreamableService;
|
|
8
|
+
let sseService: SseService;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
12
|
+
imports: [
|
|
13
|
+
McpModule.forRoot({
|
|
14
|
+
name: 'test',
|
|
15
|
+
version: '1.0.0',
|
|
16
|
+
}),
|
|
17
|
+
],
|
|
18
|
+
}).compile();
|
|
19
|
+
|
|
20
|
+
streamableService = module.get<StreamableService>(StreamableService);
|
|
21
|
+
sseService = module.get<SseService>(SseService);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should be defined', () => {
|
|
25
|
+
expect(streamableService).toBeDefined();
|
|
26
|
+
expect(sseService).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
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
|
+
}
|