# @rekog/mcp-nest A NestJS module that transforms NestJS applications into Model Context Protocol (MCP) servers. It exposes tools, resources, and prompts for AI consumption via decorators, supporting multiple transport protocols (HTTP+SSE, Streamable HTTP, STDIO) with optional OAuth 2.1 authentication. With `@rekog/mcp-nest`, you define tools, resources, and prompts using familiar NestJS patterns and leverage the full power of dependency injection to utilize your existing codebase in building complex, enterprise-ready MCP servers. The module provides automatic discovery of decorated methods, Zod-based parameter validation, progress reporting, elicitation support, and fine-grained per-tool authorization. ## Installation ```bash npm install @rekog/mcp-nest @modelcontextprotocol/sdk zod ``` ## McpModule.forRoot - Core Module Configuration The primary module for creating MCP servers with configurable transport types, authentication guards, and endpoint customization. ```typescript import { Module } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { McpModule, McpTransportType } from '@rekog/mcp-nest'; import { randomUUID } from 'crypto'; @Module({ imports: [ McpModule.forRoot({ name: 'my-mcp-server', version: '1.0.0', // Transport options: SSE, STREAMABLE_HTTP, STDIO (all enabled by default) transport: [McpTransportType.SSE, McpTransportType.STREAMABLE_HTTP], // Optional: Custom endpoint paths apiPrefix: 'api/v1', sseEndpoint: 'events', messagesEndpoint: 'chat', mcpEndpoint: 'mcp', // Optional: Authentication guards guards: [AuthGuard], // Optional: Logging configuration logging: { level: ['error', 'warn'] }, // Optional: Streamable HTTP configuration streamableHttp: { enableJsonResponse: false, sessionIdGenerator: () => randomUUID(), statelessMode: false, }, }), ], providers: [MyTools, MyResources], }) class AppModule {} async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3030); console.log('MCP Server running at http://localhost:3030/mcp'); } bootstrap(); // Exposed endpoints (with default config): // POST /mcp - Main MCP operations // GET /mcp - SSE stream for real-time updates // DELETE /mcp - Session termination // GET /sse - SSE connection (SSE transport) // POST /messages - Tool execution (SSE transport) ``` ## @Tool Decorator - Define MCP Tools Tools are functions that AI agents can execute. Define them using the `@Tool()` decorator with Zod parameter validation, output schemas, and progress reporting support. ```typescript import { Injectable } from '@nestjs/common'; import { Tool, Context } from '@rekog/mcp-nest'; import { z } from 'zod'; import type { Request } from 'express'; @Injectable() export class GreetingTools { @Tool({ name: 'greet-user', description: "Returns a personalized greeting in the user's preferred language", parameters: z.object({ name: z.string().describe('The name of the person to greet'), language: z.string().describe('Language code (e.g., "en", "es", "fr")'), }), // Optional: Validate and structure the output outputSchema: z.object({ greeting: z.string(), languageName: z.string(), }), // Optional: Metadata hints for AI agents annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, }, }) async greetUser( { name, language }, // Validated args from parameters schema context: Context, // MCP context with reportProgress, mcpServer request: Request // HTTP request (undefined for STDIO) ) { const greetings = { en: 'Hello', es: 'Hola', fr: 'Bonjour' }; // Report progress to client await context.reportProgress({ progress: 50, total: 100 }); return { greeting: `${greetings[language] || greetings.en}, ${name}!`, languageName: language, }; } } // Test with MCP Inspector: // npx @modelcontextprotocol/inspector --cli http://localhost:3030/mcp --transport http --method tools/call --tool-name greet-user --tool-arg name=Alice --tool-arg language=es // Output: { "content": [{ "type": "text", "text": "Hola, Alice!" }] } ``` ## @Resource Decorator - Serve Static Content Resources are read-only data sources that AI agents can access. Define them using the `@Resource()` decorator with a fixed URI. ```typescript import { Injectable, Scope } from '@nestjs/common'; import { Resource } from '@rekog/mcp-nest'; @Injectable({ scope: Scope.REQUEST }) export class AppResources { @Resource({ name: 'app-config', description: 'Application configuration and settings', mimeType: 'application/json', uri: 'mcp://config/app', }) getAppConfig({ uri }) { const config = { version: '2.0.0', environment: 'production', features: ['auth', 'analytics', 'notifications'], }; return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(config, null, 2), }], }; } @Resource({ name: 'help-docs', description: 'Help documentation', mimeType: 'text/markdown', uri: 'mcp://docs/help', }) getHelpDocs({ uri }) { return { contents: [{ uri, mimeType: 'text/markdown', text: '# Help\n\nThis is the help documentation...', }], }; } } // Test: // npx @modelcontextprotocol/inspector --cli http://localhost:3030/mcp --transport http --method resources/read --uri "mcp://config/app" ``` ## @ResourceTemplate Decorator - Dynamic Parameterized Resources Resource templates support URI parameters using `path-to-regexp` patterns for dynamic content retrieval. ```typescript import { Injectable, Scope } from '@nestjs/common'; import { ResourceTemplate } from '@rekog/mcp-nest'; @Injectable({ scope: Scope.REQUEST }) export class UserResources { @ResourceTemplate({ name: 'user-profile', description: 'Get user profile by ID', mimeType: 'application/json', uriTemplate: 'mcp://users/{userId}', }) async getUserProfile({ uri, userId }) { // userId is extracted from the URI pattern const user = await this.userService.findById(userId); return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(user || { error: 'User not found' }), }], }; } @ResourceTemplate({ name: 'file-content', description: 'Read file by path', mimeType: 'text/plain', uriTemplate: 'mcp://files/{path*}', // Wildcard for nested paths }) getFileContent({ uri, path }) { // path captures: docs/readme.md from mcp://files/docs/readme.md return { contents: [{ uri, mimeType: 'text/plain', text: `Content of: ${path}`, }], }; } } // Test: // npx @modelcontextprotocol/inspector --cli http://localhost:3030/mcp --transport http --method resources/read --uri "mcp://users/123" ``` ## @Prompt Decorator - Reusable Prompt Templates Prompts define reusable instruction templates for AI conversations with parameterized message generation. ```typescript import { Injectable, Scope } from '@nestjs/common'; import { Prompt } from '@rekog/mcp-nest'; import { z } from 'zod'; @Injectable({ scope: Scope.REQUEST }) export class PromptTemplates { @Prompt({ name: 'code-review', description: 'Generate a code review prompt for the specified language', parameters: z.object({ language: z.string().describe('Programming language'), focusArea: z.string().describe('Area to focus on (security, performance, style)'), }), }) getCodeReviewPrompt({ language, focusArea }) { return { description: `Code review guide for ${language}`, messages: [ { role: 'system', content: { type: 'text', text: `You are an expert ${language} code reviewer focusing on ${focusArea}.`, }, }, { role: 'user', content: { type: 'text', text: `Please review this ${language} code with emphasis on ${focusArea}. Provide specific, actionable feedback.`, }, }, ], }; } @Prompt({ name: 'task-planner', description: 'Create a task planning prompt based on complexity', parameters: z.object({ task: z.string(), complexity: z.enum(['simple', 'medium', 'complex']), }), }) getTaskPlannerPrompt({ task, complexity }) { const instructions = { simple: 'Keep it straightforward with 2-3 steps.', medium: 'Break it down into clear phases with dependencies.', complex: 'Create a detailed plan with milestones, risks, and alternatives.', }; return { description: `Task planning for ${complexity} task`, messages: [ { role: 'system', content: { type: 'text', text: 'You are a project planning expert.' } }, { role: 'user', content: { type: 'text', text: `Plan: ${task}\n\n${instructions[complexity]}` } }, ], }; } } // Test: // npx @modelcontextprotocol/inspector --cli http://localhost:3030/mcp --transport http --method prompts/get --prompt-name code-review --prompt-args language=TypeScript --prompt-args focusArea=security ``` ## Interactive Tools with Elicitation Tools can request additional input from users during execution using the elicitation API. ```typescript import { Injectable } from '@nestjs/common'; import { Tool, Context } from '@rekog/mcp-nest'; import { z } from 'zod'; @Injectable() export class InteractiveTools { @Tool({ name: 'configure-settings', description: 'Interactive configuration with user input', parameters: z.object({ category: z.string().describe('Settings category'), }), }) async configureSettings({ category }, context: Context) { // Check if client supports elicitation const capabilities = context.mcpServer.server.getClientCapabilities(); if (!capabilities?.elicitation) { return { content: [{ type: 'text', text: 'Elicitation not supported by client.' }] }; } // Request user input const response = await context.mcpServer.server.elicitInput({ message: `Configure ${category} settings`, requestedSchema: { type: 'object', properties: { theme: { type: 'string', enum: ['light', 'dark', 'auto'], description: 'UI theme preference', }, notifications: { type: 'boolean', description: 'Enable notifications', }, }, }, }); if (response.action === 'accept') { return { content: [{ type: 'text', text: `Settings updated: ${JSON.stringify(response.content)}`, }], }; } return { content: [{ type: 'text', text: 'Configuration cancelled.' }] }; } } ``` ## McpRegistryService - Dynamic Runtime Registration Register tools, resources, and prompts programmatically at runtime without decorators. ```typescript import { Injectable, OnModuleInit } from '@nestjs/common'; import { McpRegistryService } from '@rekog/mcp-nest'; import { z } from 'zod'; @Injectable() export class DynamicCapabilitiesService implements OnModuleInit { constructor(private readonly registry: McpRegistryService) {} async onModuleInit() { // Register a tool dynamically this.registry.registerTool({ name: 'dynamic-search', description: 'Dynamically registered search tool', parameters: z.object({ query: z.string(), limit: z.number().default(10), }), handler: async (args, context, request) => { return { content: [{ type: 'text', text: `Results for: ${args.query}` }], }; }, // Optional authorization isPublic: false, requiredScopes: ['read'], requiredRoles: ['user'], }); // Register a resource dynamically this.registry.registerResource({ uri: 'mcp://dynamic/config', name: 'dynamic-config', description: 'Runtime configuration', mimeType: 'application/json', handler: async () => ({ contents: [{ uri: 'mcp://dynamic/config', mimeType: 'application/json', text: JSON.stringify({ dynamicKey: 'value' }), }], }), }); // Register a prompt dynamically this.registry.registerPrompt({ name: 'dynamic-prompt', description: 'A dynamically registered prompt', parameters: z.object({ topic: z.string() }), handler: async (args) => ({ description: 'Dynamic prompt template', messages: [ { role: 'user', content: { type: 'text', text: `Discuss: ${args?.topic}` } }, ], }), }); } // Remove capabilities at runtime cleanup() { this.registry.removeTool('dynamic-search'); this.registry.removeResource('mcp://dynamic/config'); this.registry.removePrompt('dynamic-prompt'); } } ``` ## Per-Tool Authorization - Fine-Grained Access Control Control access to individual tools using `@PublicTool()`, `@ToolScopes()`, `@ToolRoles()`, and `@ToolGuards()` decorators. ```typescript import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Tool, PublicTool, ToolScopes, ToolRoles, ToolGuards } from '@rekog/mcp-nest'; import { z } from 'zod'; @Injectable() class AdminGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); return request?.user?.role === 'admin'; } } @Injectable() export class SecureTools { // Public tool - no authentication required @Tool({ name: 'public-status', description: 'Public health check endpoint', }) @PublicTool() getPublicStatus() { return { content: [{ type: 'text', text: 'Service is running' }] }; } // Protected tool - requires authentication (default when guards are set on module) @Tool({ name: 'user-data', description: 'Get authenticated user data', }) getUserData(args, context, request: any) { return { content: [{ type: 'text', text: `User: ${request.user.name}` }] }; } // Requires specific OAuth scopes @Tool({ name: 'write-data', description: 'Write data (requires write scope)', parameters: z.object({ data: z.string() }), }) @ToolScopes(['write', 'data']) writeData({ data }) { return { content: [{ type: 'text', text: `Written: ${data}` }] }; } // Requires specific user roles @Tool({ name: 'admin-action', description: 'Admin-only action', }) @ToolRoles(['admin']) adminAction() { return { content: [{ type: 'text', text: 'Admin action completed' }] }; } // Custom guard-based protection (hidden from unauthorized users) @Tool({ name: 'guarded-tool', description: 'Protected by custom guard', parameters: z.object({ target: z.string() }), }) @ToolGuards([AdminGuard]) guardedTool({ target }) { return { content: [{ type: 'text', text: `Action on ${target}` }] }; } // Combined scopes AND roles @Tool({ name: 'super-admin-tool', description: 'Requires both scopes and roles', }) @ToolScopes(['admin', 'write', 'delete']) @ToolRoles(['super-admin']) superAdminTool() { return { content: [{ type: 'text', text: 'Super admin action' }] }; } } ``` ## McpAuthModule - Built-in OAuth 2.1 Authorization Server Complete OAuth 2.1 compliant Identity Provider with GitHub, Google, and custom provider support. ```typescript import { Module } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; import cookieParser from 'cookie-parser'; import { randomUUID } from 'crypto'; import { McpAuthModule, McpModule, GitHubOAuthProvider, GoogleOAuthProvider, McpAuthJwtGuard, } from '@rekog/mcp-nest'; @Module({ imports: [ McpAuthModule.forRoot({ // Required: OAuth provider configuration provider: GitHubOAuthProvider, // or GoogleOAuthProvider clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, jwtSecret: process.env.JWT_SECRET!, // min 32 characters // Optional: Server configuration serverUrl: 'http://localhost:3030', resource: 'http://localhost:3030/mcp', apiPrefix: 'auth', // Optional: JWT configuration jwtAccessTokenExpiresIn: '1d', jwtRefreshTokenExpiresIn: '30d', enableRefreshTokens: true, // Optional: Storage backend (default: in-memory) storeConfiguration: { type: 'typeorm', // or 'memory', 'custom' options: { type: 'postgres', host: 'localhost', port: 5432, database: 'oauth_db', synchronize: true, }, }, // Optional: Disable specific endpoints disableEndpoints: { wellKnownAuthorizationServerMetadata: false, wellKnownProtectedResourceMetadata: false, }, }), McpModule.forRoot({ name: 'secure-mcp-server', version: '1.0.0', guards: [McpAuthJwtGuard], allowUnauthenticatedAccess: true, // Allow @PublicTool() access streamableHttp: { sessionIdGenerator: () => randomUUID(), statelessMode: false, }, }), ], providers: [McpAuthJwtGuard, MyTools], }) class AppModule {} async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(cookieParser()); app.enableCors({ origin: true, credentials: true }); await app.listen(3030); } bootstrap(); // OAuth Endpoints exposed (with apiPrefix: 'auth'): // GET /.well-known/oauth-authorization-server - OAuth server metadata (RFC 8414) // GET /.well-known/oauth-protected-resource - Protected resource metadata (RFC 9728) // POST /auth/register - Dynamic client registration (RFC 7591) // GET /auth/authorize - Authorization endpoint // GET /auth/callback - OAuth callback // POST /auth/token - Token endpoint // POST /auth/revoke - Token revocation ``` ## Exception Handling with @UseFilters Apply NestJS exception filters to tools, resources, and prompts for custom error handling. ```typescript import { Injectable, Catch, ExceptionFilter, UseFilters } from '@nestjs/common'; import { Tool, Resource, Prompt } from '@rekog/mcp-nest'; import { z } from 'zod'; class ValidationError extends Error { constructor(message: string, public readonly code: string) { super(message); } } @Catch(ValidationError) class ValidationErrorFilter implements ExceptionFilter { catch(exception: ValidationError) { return `Validation failed [${exception.code}]: ${exception.message}`; } } @Catch() class GlobalErrorFilter implements ExceptionFilter { catch(exception: Error) { return `Error: ${exception.message}`; } } @Injectable() @UseFilters(GlobalErrorFilter) // Class-level filter export class FilteredTools { @Tool({ name: 'validated-tool', description: 'Tool with validation error handling', parameters: z.object({ input: z.string() }), }) @UseFilters(ValidationErrorFilter) // Method-level filter async validatedTool({ input }) { if (!input || input.length < 3) { throw new ValidationError('Input too short', 'MIN_LENGTH'); } return { content: [{ type: 'text', text: `Processed: ${input}` }] }; } } // Tool errors return: { content: [{ type: 'text', text: filterResult }], isError: true } // Resource/Prompt errors throw MCP internal error (code -32603) with filter result ``` ## STDIO Transport - Command Line MCP Server Create MCP servers for CLI tools and desktop applications using STDIO transport. ```typescript import { Module } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { McpModule, McpTransportType } from '@rekog/mcp-nest'; @Module({ imports: [ McpModule.forRoot({ name: 'cli-mcp-server', version: '1.0.0', transport: McpTransportType.STDIO, }), ], providers: [MyTools, MyResources], }) class AppModule {} async function bootstrap() { const app = await NestFactory.createApplicationContext(AppModule, { logger: false, // Disable logging for STDIO }); return app.close(); } bootstrap(); // Configure in MCP client (e.g., Claude Desktop): // { // "mcpServers": { // "my-cli-tool": { // "command": "node", // "args": ["dist/cli-server.js"] // } // } // } ``` ## Dependency Injection - Leverage Existing Services Use NestJS dependency injection to integrate existing services, repositories, and business logic into MCP capabilities. ```typescript import { Injectable, Scope } from '@nestjs/common'; import { Tool, Resource, Context } from '@rekog/mcp-nest'; import { z } from 'zod'; // Your existing services @Injectable() class UserRepository { async findByName(name: string) { return { id: '123', name, email: `${name}@example.com` }; } } @Injectable() class NotificationService { async send(userId: string, message: string) { return { sent: true, timestamp: new Date().toISOString() }; } } // MCP Tool with injected dependencies @Injectable({ scope: Scope.REQUEST }) // Use REQUEST scope for request-scoped services export class UserTools { constructor( private readonly userRepo: UserRepository, private readonly notifications: NotificationService, ) {} @Tool({ name: 'get-user', description: 'Get user by name', parameters: z.object({ name: z.string() }), }) async getUser({ name }) { const user = await this.userRepo.findByName(name); return { content: [{ type: 'text', text: JSON.stringify(user) }] }; } @Tool({ name: 'notify-user', description: 'Send notification to user', parameters: z.object({ userId: z.string(), message: z.string(), }), }) async notifyUser({ userId, message }) { const result = await this.notifications.send(userId, message); return { content: [{ type: 'text', text: `Notification sent: ${result.timestamp}` }] }; } } ``` ## Testing MCP Servers Test your MCP servers using the MCP Inspector CLI or curl commands. ```bash # Start the server npx ts-node-dev --respawn playground/servers/server-stateful.ts # Interactive testing with MCP Inspector UI npx @modelcontextprotocol/inspector # CLI testing - List tools npx @modelcontextprotocol/inspector --cli http://localhost:3030/mcp --transport http --method tools/list # CLI testing - Call a tool npx @modelcontextprotocol/inspector --cli http://localhost:3030/mcp --transport http --method tools/call --tool-name greet-user --tool-arg name=Alice --tool-arg language=es # CLI testing - List resources npx @modelcontextprotocol/inspector --cli http://localhost:3030/mcp --transport http --method resources/list # CLI testing - Read a resource npx @modelcontextprotocol/inspector --cli http://localhost:3030/mcp --transport http --method resources/read --uri "mcp://config/app" # CLI testing - Get a prompt npx @modelcontextprotocol/inspector --cli http://localhost:3030/mcp --transport http --method prompts/get --prompt-name code-review --prompt-args language=TypeScript --prompt-args focusArea=security # Testing with curl curl -X POST http://localhost:3030/mcp \ -H "Content-Type: application/json" \ -d '{"method": "tools/list"}' curl -X POST http://localhost:3030/mcp \ -H "Content-Type: application/json" \ -d '{ "method": "tools/call", "params": { "name": "greet-user", "arguments": {"name": "Alice", "language": "en"} } }' ``` ## Summary `@rekog/mcp-nest` is ideal for enterprise applications that need to expose business logic to AI agents through a standardized protocol. The primary use cases include: building AI-powered APIs that leverage existing NestJS services and repositories; creating secure, authenticated MCP servers with OAuth 2.1 support for production deployments; developing multi-transport servers that can serve web applications (HTTP/SSE), desktop clients (STDIO), and real-time streaming scenarios; and implementing fine-grained access control with per-tool authorization using scopes, roles, and custom guards. Integration patterns center around the decorator-based approach (`@Tool`, `@Resource`, `@Prompt`, `@ResourceTemplate`) for static capability definitions, combined with `McpRegistryService` for dynamic runtime registration. The module seamlessly integrates with the NestJS ecosystem, supporting guards, interceptors, exception filters, and full dependency injection. For authentication, combine `McpAuthModule` with `McpModule` using `McpAuthJwtGuard`, or integrate external OAuth providers like Keycloak or Auth0. The multi-server pattern allows running multiple isolated MCP servers within a single NestJS application, each with its own capabilities and authentication configuration.