@nestjs-mcp/server
Version:
Modular library for building scalable MCP servers with NestJS, providing decorators and integration patterns as a wrapper for the official MCP TypeScript SDK.
272 lines • 12.3 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RegistryService = void 0;
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
const common_1 = require("@nestjs/common");
const core_1 = require("@nestjs/core");
const decorators_1 = require("../decorators");
const discovery_service_1 = require("./discovery.service");
const logger_service_1 = require("./logger.service");
const session_manager_1 = require("./session.manager");
let RegistryService = class RegistryService {
discoveryService;
logger;
reflector;
sessionManager;
moduleRef;
constructor(discoveryService, logger, reflector, sessionManager, moduleRef) {
this.discoveryService = discoveryService;
this.logger = logger;
this.reflector = reflector;
this.sessionManager = sessionManager;
this.moduleRef = moduleRef;
}
registerAll(server) {
this.logger.log('Starting registration of all MCP capabilities...', 'registry');
this.registerResources(server);
this.registerPrompts(server);
this.registerTools(server);
}
getDecoratorType(method) {
if (!method)
return null;
if (this.reflector.get(decorators_1.MCP_TOOL, method))
return 'TOOL';
if (this.reflector.get(decorators_1.MCP_PROMPT, method))
return 'PROMPT';
if (this.reflector.get(decorators_1.MCP_RESOURCE, method))
return 'RESOURCE';
return null;
}
getHandlerArgs(method, args) {
if (!method)
throw new Error('Method not found');
switch (this.getDecoratorType(method)) {
case 'RESOURCE':
return args[0] instanceof URL
? {
type: 'resource:uri',
uri: args[0],
extra: args[1],
}
: {
type: 'resource:template',
uri: args[0],
variables: args[1],
extra: args[2],
};
case 'PROMPT':
return args.length === 1
? {
type: 'prompt',
extra: args[0],
}
: {
type: 'prompt',
args: args[0],
extra: args[1],
};
case 'TOOL':
return args.length === 1
? {
type: 'tool',
extra: args[0],
}
: {
type: 'tool',
params: args[0],
extra: args[1],
};
default:
throw new Error(`Unknown decorator type for method ${method.name}`);
}
}
async resolveGuard(Guard) {
if (typeof Guard !== 'function') {
return Guard;
}
try {
return this.moduleRef.get(Guard, { strict: false });
}
catch {
try {
return await this.moduleRef.create(Guard);
}
catch {
return new Guard();
}
}
}
runGuards(instance, methodName, sessionId, request, args) {
const classConstructor = instance.constructor;
const classGuards = Reflect.getMetadata(decorators_1.MCP_GUARDS, classConstructor) || [];
const prototype = Object.getPrototypeOf(instance);
const methodKey = prototype[methodName];
const methodGuards = (methodKey &&
Reflect.getMetadata(decorators_1.MCP_GUARDS, methodKey)) ||
[];
const allGuards = [...classGuards, ...methodGuards];
if (!allGuards.length)
return Promise.resolve();
const handlerArgs = this.getHandlerArgs(methodKey, args);
const context = {
getType: () => 'mcp',
getClass: () => instance.constructor,
getHandler: () => methodKey,
getSessionId: () => sessionId,
getArgs: () => handlerArgs,
getRequest: () => request,
};
return (async () => {
for (const Guard of allGuards) {
const guardInstance = await this.resolveGuard(Guard);
const allowed = await guardInstance.canActivate(context);
if (!allowed)
throw new Error(`Access denied by guard on ${methodName}`);
}
})();
}
async wrappedHandler(instance, handler, args) {
const isResolver = Reflect.hasMetadata(decorators_1.MCP_RESOLVER, instance.constructor);
if (!isResolver) {
throw new Error(`Class "${instance.constructor.name}" must be decorated with @Resolver to use @Prompt, @Tool, or @Resource.`);
}
const methodName = handler.name;
const { sessionId } = args[args.length - 1];
if (!sessionId) {
throw new common_1.UnauthorizedException('Session ID is required');
}
const session = this.sessionManager.getSession(sessionId);
if (!session) {
throw new common_1.ForbiddenException('Session not found');
}
args[args.length - 1] = {
...args[args.length - 1],
headers: session.request.headers,
body: session.request.body,
};
await this.runGuards(instance, methodName, sessionId, session.request, args);
return handler(...args);
}
registerResources(server) {
const resourceMethods = this.discoveryService.getAllMethodsWithMetadata(decorators_1.MCP_RESOURCE);
for (const method of resourceMethods) {
const { metadata, handler, instance } = method;
this.logger.log(`Resource "${metadata?.name || 'unnamed'}" found.`, 'resources');
const wrappedHandler = (...args) => this.wrappedHandler(instance, handler, args);
try {
if ('template' in metadata) {
if ('metadata' in metadata) {
server.resource(metadata.name, new mcp_js_1.ResourceTemplate(metadata.template, { list: undefined }), metadata.metadata, wrappedHandler);
}
else {
server.resource(metadata.name, new mcp_js_1.ResourceTemplate(metadata.template, { list: undefined }), wrappedHandler);
}
}
else if ('uri' in metadata) {
if ('metadata' in metadata) {
server.resource(metadata.name, metadata.uri, metadata.metadata, wrappedHandler);
}
else {
server.resource(metadata.name, metadata.uri, wrappedHandler);
}
}
}
catch (error) {
this.logger.error(`Error registering resource ${metadata.name}: ${error}`, undefined, 'resources');
if (error && typeof error === 'object' && 'stack' in error) {
this.logger.error(`Error stack: ${error.stack}`, undefined, 'resources');
}
}
}
}
registerPrompts(server) {
const promptMethods = this.discoveryService.getAllMethodsWithMetadata(decorators_1.MCP_PROMPT);
for (const method of promptMethods) {
const { metadata, handler, instance } = method;
this.logger.log(`Prompt "${metadata?.name || 'unnamed'}" found.`, 'prompts');
const wrappedHandler = (...args) => this.wrappedHandler(instance, handler, args);
try {
if ('description' in metadata && 'argsSchema' in metadata) {
server.prompt(metadata.name, metadata.description, metadata.argsSchema, wrappedHandler);
}
else if ('argsSchema' in metadata) {
server.prompt(metadata.name, metadata.argsSchema, wrappedHandler);
}
else if ('description' in metadata) {
server.prompt(metadata.name, metadata.description, wrappedHandler);
}
else {
server.prompt(metadata.name, wrappedHandler);
}
}
catch (error) {
this.logger.error(`Error registering prompt ${metadata.name}: ${error}`, undefined, 'prompts');
if (error && typeof error === 'object' && 'stack' in error) {
this.logger.error(`Error stack: ${error.stack}`, undefined, 'prompts');
}
}
}
}
registerTools(server) {
const toolMethods = this.discoveryService.getAllMethodsWithMetadata(decorators_1.MCP_TOOL);
for (const method of toolMethods) {
const { metadata, handler, instance } = method;
this.logger.log(`Tool "${metadata?.name || 'unnamed'}" found.`, 'tools');
const wrappedHandler = (...args) => this.wrappedHandler(instance, handler, args);
try {
if ('paramsSchema' in metadata &&
'annotations' in metadata &&
'description' in metadata) {
server.tool(metadata.name, metadata.description, metadata.paramsSchema, metadata.annotations, wrappedHandler);
}
else if ('paramsSchema' in metadata && 'annotations' in metadata) {
server.tool(metadata.name, metadata.paramsSchema, metadata.annotations, wrappedHandler);
}
else if ('paramsSchema' in metadata && 'description' in metadata) {
server.tool(metadata.name, metadata.description, metadata.paramsSchema, wrappedHandler);
}
else if ('annotations' in metadata && 'description' in metadata) {
server.tool(metadata.name, metadata.description, metadata.annotations, wrappedHandler);
}
else if ('paramsSchema' in metadata) {
server.tool(metadata.name, metadata.paramsSchema, wrappedHandler);
}
else if ('annotations' in metadata) {
server.tool(metadata.name, metadata.annotations, wrappedHandler);
}
else if ('description' in metadata) {
server.tool(metadata.name, metadata.description, wrappedHandler);
}
else {
server.tool(metadata.name, wrappedHandler);
}
}
catch (error) {
this.logger.error(`Error registering tool ${metadata.name}: ${error}`, undefined, 'tools');
if (error && typeof error === 'object' && 'stack' in error) {
this.logger.error(`Stack trace: ${error.stack}`, undefined, 'tools');
}
}
}
}
};
exports.RegistryService = RegistryService;
exports.RegistryService = RegistryService = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [discovery_service_1.DiscoveryService,
logger_service_1.McpLoggerService,
core_1.Reflector,
session_manager_1.SessionManager,
core_1.ModuleRef])
], RegistryService);
//# sourceMappingURL=registry.service.js.map