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