Enables the transformation of NestJS services, providers, and controllers into MCP tools, resources, and prompts using a decorator-driven approach with automatic discovery.
Integrates with Zod for automatic schema detection and validation of tool input arguments, ensuring type-safe interactions between the AI and the server.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-nestjsshow me how to create a tool using the @McpTool decorator"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-nestjs
NestJS module for building Model Context Protocol (MCP) servers. Expose your NestJS services as MCP tools, resources, and prompts using decorators — with auto-discovery, multiple transports, and a built-in playground UI.
Features
Decorator-driven —
@McpTool(),@McpResource(),@McpPrompt(),@McpGuard(),@McpToolGroup()Auto-discovery — decorated methods on any provider or controller are registered automatically
Schema adapters — auto-detect Zod, Joi, class-validator, or inline schemas (zero config)
Multiple transports — SSE, Streamable HTTP, and Stdio (JSON-RPC 2.0)
Built-in playground — interactive UI at
/mcp-playgroundto browse and test toolsGuards — per-tool or per-class authorization
Session management — configurable timeout, cleanup, and max sessions
No SDK dependency — custom JSON-RPC 2.0 implementation
Installation
npm install mcp-nestjsPeer dependencies (your NestJS app already has these):
npm install @nestjs/common @nestjs/core reflect-metadata rxjsOptional schema libraries (auto-detected):
npm install zod # for Zod schemas
npm install joi # for Joi schemas
npm install class-validator class-transformer # for DTO schemasQuick Start
// app.module.ts
import { Module } from '@nestjs/common';
import { McpModule } from 'mcp-nestjs';
import { GreetingService } from './greeting.service';
@Module({
imports: [
McpModule.forRoot({
name: 'my-mcp-server',
version: '1.0.0',
transports: { sse: { enabled: true } },
playground: true,
}),
],
providers: [GreetingService],
})
export class AppModule {}// greeting.service.ts
import { Injectable } from '@nestjs/common';
import { McpTool } from 'mcp-nestjs';
@Injectable()
export class GreetingService {
@McpTool({
description: 'Say hello to someone',
schema: {
name: { type: 'string', description: 'Name to greet' },
},
})
async greet(args: { name: string }) {
return { message: `Hello, ${args.name}!` };
}
}Start your app and visit http://localhost:3000/mcp-playground.
Module Configuration
McpModule.forRoot(options)
McpModule.forRoot({
name: 'my-server', // Server name (required)
version: '1.0.0', // Server version (required)
transports: {
sse: { enabled: true, path: '/sse' }, // SSE transport
http: { enabled: true, path: '/mcp' }, // Streamable HTTP transport
stdio: { enabled: false }, // Stdio transport
},
session: {
timeout: 30 * 60 * 1000, // Session timeout (default: 30min)
cleanupInterval: 5 * 60 * 1000, // Cleanup interval (default: 5min)
maxSessions: 1000, // Max concurrent sessions
},
playground: true, // Enable playground UI
tools: [], // Manual tool registrations
guards: [], // Global guards
})McpModule.forRootAsync(options)
McpModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
name: config.get('MCP_SERVER_NAME'),
version: config.get('MCP_VERSION'),
transports: { sse: { enabled: true } },
playground: true,
}),
inject: [ConfigService],
})McpModule.forFeature(providers)
Register additional providers in feature modules:
McpModule.forFeature([MyFeatureService])Decorators
@McpTool(options) — Method Decorator
Marks a method as an MCP tool.
@McpTool({
name?: string, // Override tool name (default: [group_]methodName)
description: string, // Required description
schema?: ZodType | JoiSchema | Record<string, InlinePropertyDef>, // Input schema
transform?: 'auto' | 'raw' | ((result) => ToolResult), // Response mode
excludeProperties?: string[], // Hide fields from schema
requiredProperties?: string[], // Override required fields
})Schema options:
// Inline (always available)
@McpTool({
description: 'Search users',
schema: {
query: { type: 'string', description: 'Search term' },
limit: { type: 'number', default: 10, required: false },
role: { type: 'string', enum: ['admin', 'user'] },
tags: { type: 'array', items: { type: 'string' }, required: false },
},
})
// Zod (if installed)
import { z } from 'zod';
@McpTool({
description: 'Create user',
schema: z.object({
name: z.string(),
email: z.string(),
role: z.enum(['admin', 'user']).optional(),
}),
})
// Joi (if installed)
import Joi from 'joi';
@McpTool({
description: 'Create user',
schema: Joi.object({
name: Joi.string().required(),
email: Joi.string().required(),
}),
})Transform modes:
// 'auto' (default) — return value auto-wrapped in ToolResult
@McpTool({ description: 'Get data' })
async getData() {
return { key: 'value' }; // → { content: [{ type: 'text', text: '{"key":"value"}' }] }
}
// 'raw' — you return a ToolResult directly
@McpTool({ description: 'Get data', transform: 'raw' })
async getData(): Promise<ToolResult> {
return { content: [{ type: 'text', text: 'hello' }] };
}
// Custom function
@McpTool({
description: 'Get user',
transform: (user) => ({
content: [{ type: 'text', text: `User: ${user.name}` }],
}),
})@McpToolGroup(prefix?) — Class Decorator
Prefixes all tool names in a class:
@McpToolGroup('files') // tools: files_list, files_read, files_write
@Injectable()
export class FilesService {
@McpTool({ description: 'List files' })
async list() { ... }
@McpTool({ description: 'Read file' })
async read(args: { path: string }) { ... }
}
@McpToolGroup() // Auto-derive from @Controller path@McpResource(options) — Method Decorator
Exposes data as MCP resources:
// Static resource
@McpResource({
uri: 'config://app',
name: 'app_config',
description: 'Application config',
mimeType: 'application/json',
})
async getConfig() {
return {
contents: [{
uri: 'config://app',
mimeType: 'application/json',
text: JSON.stringify({ debug: true }),
}],
};
}
// Parameterized resource (URI template)
@McpResource({
uriTemplate: 'users://{userId}',
name: 'user_detail',
mimeType: 'application/json',
})
async getUser(uri: string) {
const id = uri.match(/users:\/\/(.+)/)?.[1];
return { contents: [{ uri, text: JSON.stringify(user) }] };
}@McpPrompt(options) — Method Decorator
Reusable prompt templates:
@McpPrompt({
name: 'code_review',
description: 'Generate a code review prompt',
schema: {
code: { type: 'string', description: 'Code to review' },
language: { type: 'string', description: 'Programming language' },
},
})
async codeReview(args: { code: string; language: string }) {
return {
messages: [{
role: 'user',
content: {
type: 'text',
text: `Review this ${args.language} code:\n\`\`\`\n${args.code}\n\`\`\``,
},
}],
};
}@McpGuard(...guards) — Class or Method Decorator
Per-tool or per-class authorization:
import { IMcpGuard, McpExecutionContext } from 'mcp-nestjs';
export class ApiKeyGuard implements IMcpGuard {
canActivate(context: McpExecutionContext): boolean {
const args = context.getArgs();
return args.apiKey === 'secret';
}
}
// Class-level: all tools require auth
@McpGuard(ApiKeyGuard)
@Injectable()
export class AdminService { ... }
// Method-level: only this tool requires auth
@McpGuard(RateLimitGuard)
@McpTool({ description: 'Sensitive operation' })
async sensitiveOp() { ... }McpExecutionContext
getSessionId()— current session IDgetArgs()— tool/prompt argumentsgetRequest()— raw transport requestgetToolName()— tool/resource/prompt namegetType()—'tool' | 'resource' | 'prompt'
Manual Tool Registration
// Via forRoot options
McpModule.forRoot({
...config,
tools: [{
definition: {
name: 'echo',
description: 'Echo input',
inputSchema: {
type: 'object',
properties: { message: { type: 'string' } },
required: ['message'],
},
},
handler: async (args) => ({
content: [{ type: 'text', text: args.message }],
}),
}],
})
// Via ToolRegistryService at runtime
@Injectable()
export class DynamicTools implements OnModuleInit {
constructor(private registry: ToolRegistryService) {}
onModuleInit() {
this.registry.registerTool({ definition: {...}, handler: async (args) => {...} });
}
}Transports
SSE (Server-Sent Events)
GET /sse → Opens SSE stream, returns session endpoint
POST /messages?sessionId=X → Send JSON-RPC requestsStreamable HTTP
POST /mcp → Single JSON-RPC request/response
Session via `mcp-session-id` headerStdio
Reads JSON-RPC from stdin, writes responses to stdout. For CLI subprocess spawning.
Playground
Enable with playground: true in module options. Visit /mcp-playground to:
Browse all registered tools, resources, and prompts
View input schemas with types, required fields, and descriptions
Auto-generated forms for each tool
Execute tools and see results with timing
Read resources by URI
Test prompts with arguments
Examples
See the examples/ directory for complete working examples:
Example | What it shows |
| Minimal setup — one service, two tools |
| All inline schema options — enums, arrays, defaults, optionals |
| Zod schema integration |
|
|
| Static URIs and URI templates |
| Reusable prompt templates |
| API key auth, rate limiting, logging guards |
|
|
| SSE + HTTP + Stdio with session config |
|
|
| Everything combined |
Run any example:
npx ts-node -r reflect-metadata examples/01-basic/main.tsLicense
MIT
This server cannot be installed
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.