@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.
- 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/.prettierrc +4 -0
- package/LICENSE +21 -0
- package/README.md +540 -0
- package/dist/controllers/sse/index.d.ts +2 -0
- package/dist/controllers/sse/index.js +19 -0
- package/dist/controllers/sse/index.js.map +1 -0
- package/dist/controllers/sse/sse.controller.d.ts +10 -0
- package/dist/controllers/sse/sse.controller.js +57 -0
- package/dist/controllers/sse/sse.controller.js.map +1 -0
- package/dist/controllers/sse/sse.service.d.ts +16 -0
- package/dist/controllers/sse/sse.service.js +78 -0
- package/dist/controllers/sse/sse.service.js.map +1 -0
- package/dist/controllers/streamable/index.d.ts +2 -0
- package/dist/controllers/streamable/index.js +19 -0
- package/dist/controllers/streamable/index.js.map +1 -0
- package/dist/controllers/streamable/streamable.controller.d.ts +9 -0
- package/dist/controllers/streamable/streamable.controller.js +62 -0
- package/dist/controllers/streamable/streamable.controller.js.map +1 -0
- package/dist/controllers/streamable/streamable.service.d.ts +24 -0
- package/dist/controllers/streamable/streamable.service.js +118 -0
- package/dist/controllers/streamable/streamable.service.js.map +1 -0
- package/dist/decorators/capabilities.constants.d.ts +4 -0
- package/dist/decorators/capabilities.constants.js +8 -0
- package/dist/decorators/capabilities.constants.js.map +1 -0
- package/dist/decorators/capabilities.decorators.d.ts +8 -0
- package/dist/decorators/capabilities.decorators.js +49 -0
- package/dist/decorators/capabilities.decorators.js.map +1 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/decorators/index.js +19 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptors/message.interceptor.d.ts +10 -0
- package/dist/interceptors/message.interceptor.js +61 -0
- package/dist/interceptors/message.interceptor.js.map +1 -0
- package/dist/interfaces/capabilities.interface.d.ts +52 -0
- package/dist/interfaces/capabilities.interface.js +3 -0
- package/dist/interfaces/capabilities.interface.js.map +1 -0
- 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 -0
- package/dist/interfaces/index.js +19 -0
- package/dist/interfaces/index.js.map +1 -0
- package/dist/interfaces/mcp-server-options.interface.d.ts +42 -0
- package/dist/interfaces/mcp-server-options.interface.js +3 -0
- package/dist/interfaces/mcp-server-options.interface.js.map +1 -0
- package/dist/interfaces/message.types.d.ts +8 -0
- package/dist/interfaces/message.types.js +3 -0
- package/dist/interfaces/message.types.js.map +1 -0
- package/dist/mcp.module.d.ts +13 -0
- package/dist/mcp.module.js +193 -0
- package/dist/mcp.module.js.map +1 -0
- package/dist/registry/discovery.service.d.ts +16 -0
- package/dist/registry/discovery.service.js +85 -0
- package/dist/registry/discovery.service.js.map +1 -0
- package/dist/registry/index.d.ts +2 -0
- package/dist/registry/index.js +19 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/logger.service.d.ts +16 -0
- package/dist/registry/logger.service.js +97 -0
- package/dist/registry/logger.service.js.map +1 -0
- package/dist/registry/registry.service.d.ts +16 -0
- package/dist/registry/registry.service.js +170 -0
- package/dist/registry/registry.service.js.map +1 -0
- package/dist/services/message.service.d.ts +7 -0
- package/dist/services/message.service.js +25 -0
- package/dist/services/message.service.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/eslint.config.mjs +40 -0
- package/package.json +109 -0
- package/src/controllers/sse/index.ts +2 -0
- package/src/controllers/sse/sse.controller.ts +25 -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 +169 -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/interceptors/message.interceptor.ts +70 -0
- package/src/interfaces/capabilities.interface.ts +95 -0
- package/src/interfaces/context.interface.ts +18 -0
- package/src/interfaces/index.ts +2 -0
- package/src/interfaces/mcp-server-options.interface.ts +105 -0
- package/src/interfaces/message.types.ts +13 -0
- package/src/mcp.module.ts +250 -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 +282 -0
- 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,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,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
|
+
}
|