A NestJS library for integrating the Model Context Protocol (MCP) into your applications. Built on top of the official @modelcontextprotocol/sdk 1.21.1, this package provides a decorator-based approach to building MCP servers with NestJS.
@MCPTool, @MCPToolWithParams, @MCPResource, @MCPResourceTemplate, and @MCPPrompt decorators/mcp/playground for testing tools, resources, and prompts without curl@modelcontextprotocol/sdk 1.21.1 with modern McpServer APInpm install @hmake98/nestjs-mcp
# or
yarn add @hmake98/nestjs-mcp
# or
pnpm add @hmake98/nestjs-mcp
This package requires the following peer dependencies:
{
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0"
}
The library uses lazy loading for transport adapters - dependencies are only loaded when you actually use a transport. This significantly reduces installation size and startup time.
Install additional packages only for the transports you need:
# WebSocket transport
npm install ws
# Redis transport
npm install ioredis
# gRPC transport
npm install @grpc/grpc-js @grpc/proto-loader
Note:
- HTTP and stdio transports are built-in and require no additional dependencies
- SSE transport uses
rxjs, which is already a peer dependency- Transport dependencies are declared as optional peer dependencies
- If you try to use a transport without its dependencies, you'll get a helpful error message with installation instructions
Import and configure the MCPModule in your application module:
import { Module } from '@nestjs/common';
import { MCPModule } from '@hmake98/nestjs-mcp';
@Module({
imports: [
MCPModule.forRoot({
serverInfo: {
name: 'my-mcp-server',
version: '1.0.0',
},
autoDiscoverTools: true, // Default: true
autoDiscoverResources: true, // Default: true
autoDiscoverPrompts: true, // Default: true
logLevel: 'info', // Default: 'info'
}),
],
})
export class AppModule {}
Create a service with MCP tool methods using decorators:
import { Injectable } from '@nestjs/common';
import { MCPTool, MCPToolWithParams } from '@hmake98/nestjs-mcp';
@Injectable()
export class CalculatorService {
// Simple tool with automatic parameter inference
@MCPTool({
name: 'add',
description: 'Add two numbers',
})
async add(params: { a: number; b: number }): Promise<number> {
return params.a + params.b;
}
// Tool with explicit parameter definitions and validation
@MCPToolWithParams({
name: 'multiply',
description: 'Multiply two numbers',
parameters: [
{
name: 'a',
type: 'number',
description: 'First number',
required: true,
},
{
name: 'b',
type: 'number',
description: 'Second number',
required: true,
},
],
})
async multiply(params: { a: number; b: number }): Promise<number> {
return params.a * params.b;
}
// Return structured tool results
@MCPTool({
name: 'format-result',
description: 'Format calculation result',
})
async formatResult(params: { value: number }) {
return {
content: [
{
type: 'text' as const,
text: `The result is: ${params.value}`,
},
],
};
}
}
Important: Register the service in your module's providers:
@Module({
imports: [
MCPModule.forRoot({
/* ... */
}),
],
providers: [CalculatorService],
})
export class AppModule {}
Resources provide access to data or content. Create static or dynamic resources:
import { Injectable } from '@nestjs/common';
import { MCPResource, MCPResourceTemplate } from '@hmake98/nestjs-mcp';
@Injectable()
export class FileService {
// Static resource - fixed URI
@MCPResource({
uri: 'file:///readme.txt',
name: 'README',
description: 'Application README file',
mimeType: 'text/plain',
})
async getReadme() {
return {
uri: 'file:///readme.txt',
mimeType: 'text/plain',
text: 'Welcome to my MCP server!',
};
}
// Dynamic resource with URI template
// Matches URIs like: file:///documents/report.pdf
@MCPResourceTemplate({
uriTemplate: 'file:///{filename}',
name: 'File',
description: 'Access any file by filename',
mimeType: 'text/plain',
})
async getFile(variables: { filename: string }) {
// Variables are automatically extracted from the URI
const content = await this.readFile(variables.filename);
return {
uri: `file:///${variables.filename}`,
mimeType: 'text/plain',
text: content,
};
}
// Advanced template with multiple variables
// Matches URIs like: resource:///user/123/profile
@MCPResourceTemplate({
uriTemplate: 'resource:///{type}/{id}',
name: 'Dynamic Resource',
description: 'Get resource by type and ID',
mimeType: 'application/json',
})
async getResource(variables: { type: string; id: string }) {
const data = await this.fetchResourceData(variables.type, variables.id);
return {
uri: `resource:///${variables.type}/${variables.id}`,
mimeType: 'application/json',
text: JSON.stringify(data),
};
}
private async readFile(filename: string): Promise<string> {
// Your file reading logic here
return `Content of ${filename}`;
}
private async fetchResourceData(type: string, id: string) {
// Your data fetching logic here
return { type, id, data: '...' };
}
}
Prompts provide reusable message templates for AI interactions:
import { Injectable } from '@nestjs/common';
import { MCPPrompt } from '@hmake98/nestjs-mcp';
@Injectable()
export class PromptService {
@MCPPrompt({
name: 'code-review',
description: 'Generate a code review prompt',
arguments: [
{
name: 'language',
description: 'Programming language',
required: true,
},
{
name: 'code',
description: 'Code to review',
required: true,
},
],
})
async codeReview(args: { language: string; code: string }) {
return [
{
role: 'user' as const,
content: {
type: 'text' as const,
text: `Please review this ${args.language} code:\n\n${args.code}`,
},
},
];
}
@MCPPrompt({
name: 'summarize',
description: 'Summarize content with custom length',
arguments: [
{
name: 'content',
description: 'Content to summarize',
required: true,
},
{
name: 'length',
description: 'Summary length (short, medium, long)',
required: false,
},
],
})
async summarize(args: { content: string; length?: string }) {
const lengthInstruction = args.length
? `in ${args.length} form`
: 'concisely';
return [
{
role: 'user' as const,
content: {
type: 'text' as const,
text: `Please summarize the following content ${lengthInstruction}:\n\n${args.content}`,
},
},
];
}
}
Want to see a complete working example?
Check out the nestjs-mcp-example repository, which demonstrates all the features of this library in a real NestJS application:
@MCPTool, @MCPResource, @MCPPrompt)Visit the repo to clone and run it locally: github.com/hmake98/nestjs-mcp-example
Interactive web UI for testing your MCP server without curl or external tools.
The built-in playground provides a browser-based interface for discovering and testing tools, resources, and prompts. Access it at:
http://localhost:3000/mcp/playground
Start your NestJS app with MCPModule.forRoot():
@Module({
imports: [
MCPModule.forRoot({
serverInfo: { name: 'my-app', version: '1.0.0' },
}),
],
providers: [YourMCPProviders],
})
export class AppModule {}
Navigate to http://localhost:3000/mcp/playground in your browser
Select a tool/resource/prompt from the list in the left panel
Fill out the form with required parameters
Click "Execute" to test and view results
๐ก Tip: The playground uses the same
/mcpJSON-RPC endpoint as external clients, so behavior is identical to production usage.
Automatically generate a fully type-safe TypeScript client from your running MCP server.
The CLI tool introspects your server and generates a complete client library with 100% type safety, zero boilerplate, and full IntelliSense support.
# Start your MCP server
npm run start
# Generate the client
npx @hmake98/nestjs-mcp client:generate \
--url http://localhost:3000/mcp \
--out ./mcp \
--name my-mcp-client
import { createClient } from './mcp';
const mcp = createClient('http://localhost:3000/mcp');
// Fully typed tool calls
await mcp.tools['user.get']({ id: '123' });
// Resource access
await mcp.resources.read('file:///config/app.json');
// Prompt execution
await mcp.prompts['review-code']({
language: 'typescript',
code: 'function add(a, b) { return a + b; }',
});
mcp/
โโโ index.ts # Main entry with createClient()
โโโ client.ts # Base MCPClient class
โโโ package.json # Client metadata
โโโ tools/
โ โโโ index.ts # Type-safe tool wrappers
โโโ resources/
โ โโโ index.ts # Resource access functions
โโโ prompts/
โ โโโ index.ts # Prompt execution functions
โโโ types/
โโโ index.ts # Generated TypeScript interfaces
Frontend (React)
const mcp = createClient(process.env.REACT_APP_MCP_URL);
export function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
useEffect(() => {
mcp.tools['user.get']({ id: userId }).then(setUser);
}, [userId]);
return <div>{user?.name}</div>;
}
Worker / Background Job
const mcp = createClient('http://mcp-server:3000/mcp');
export async function processVideo(videoId: string) {
await mcp.tools['video.transcode']({
id: videoId,
format: 'mp4',
quality: 'high',
});
}
Microservice
const mcp = createClient('http://mcp-server.internal:3000/mcp');
app.get('/analyze', async (req, res) => {
const analysis = await mcp.tools['analyze-sentiment']({
text: req.body.text,
});
res.json(analysis);
});
npx @hmake98/nestjs-mcp client:generate [options]
Options:
--url <url> MCP server URL (required)
--out <directory> Output directory (default: ./mcp)
--name <name> Client package name (default: mcp)
-V, --version Output version number
-h, --help Display help
When you update your MCP server (add/remove/modify tools), regenerate the client:
npx @hmake98/nestjs-mcp client:generate --url http://localhost:3000/mcp --out ./mcp
The client will be updated with the latest server definitions.
Production-ready execution pipeline control for authentication, authorization, logging, and error handling.
Guards and interceptors provide powerful control over your MCP tool/resource/prompt execution:
import { Injectable } from '@nestjs/common';
import {
MCPTool,
UseMCPGuards,
UseMCPInterceptors,
AuthGuard,
RateLimitGuard,
LoggingInterceptor,
TimeoutInterceptor,
} from '@hmake98/nestjs-mcp';
@Injectable()
export class SecureToolProvider {
@UseMCPGuards(AuthGuard, RateLimitGuard)
@UseMCPInterceptors(LoggingInterceptor, new TimeoutInterceptor(5000))
@MCPTool({
name: 'production_ready_tool',
description: 'Secured, rate-limited, logged, and timed',
})
async productionTool() {
return 'Protected and monitored data';
}
}
AuthGuard: Authentication (extend for JWT, OAuth, etc.)RateLimitGuard: Rate limiting (10 req/min default, configurable)PermissionGuard: Role-based access controlLoggingInterceptor: Execution logging with timingTimeoutInterceptor: Prevent long-running operationsErrorMappingInterceptor: Consistent error responses// Authentication only
@UseMCPGuards(AuthGuard)
@MCPTool({ name: 'auth_tool', description: 'Requires auth' })
async authTool() { return 'data'; }
// Full production stack
@UseMCPGuards(AuthGuard, RateLimitGuard, PermissionGuard)
@UseMCPInterceptors(
LoggingInterceptor,
new TimeoutInterceptor(10000),
ErrorMappingInterceptor,
)
@MCPTool({ name: 'enterprise_tool', description: 'Enterprise ready' })
async enterpriseTool() { return 'data'; }
import { Injectable } from '@nestjs/common';
import {
MCPGuard,
MCPExecutionContext,
MCPUnauthorizedException,
} from '@hmake98/nestjs-mcp';
@Injectable()
export class JWTAuthGuard implements MCPGuard {
async canActivate(context: MCPExecutionContext): Promise<boolean> {
const request = context.getRequest();
const token = request.params?.auth?.token;
if (!(await this.validateToken(token))) {
throw new MCPUnauthorizedException('Invalid token');
}
return true;
}
private async validateToken(token: string): Promise<boolean> {
// Your JWT validation logic
return true;
}
}
import { Injectable } from '@nestjs/common';
import {
MCPInterceptor,
MCPExecutionContext,
MCPCallHandler,
} from '@hmake98/nestjs-mcp';
@Injectable()
export class CachingInterceptor implements MCPInterceptor {
private cache = new Map();
async intercept(
context: MCPExecutionContext,
next: MCPCallHandler,
): Promise<unknown> {
const key = context.switchToMcp().getOperationName();
if (this.cache.has(key)) return this.cache.get(key);
const result = await next.handle();
this.cache.set(key, result);
return result;
}
}
๐ Complete Guards & Interceptors Documentation โ
The MCPModuleOptions interface provides the following configuration options:
interface MCPModuleOptions {
// Server information (required)
serverInfo: {
name: string; // Server name
version: string; // Server version
capabilities?: {
// Server capabilities (optional)
tools?: {
listChanged?: boolean;
};
resources?: {
subscribe?: boolean;
listChanged?: boolean;
};
prompts?: {
listChanged?: boolean;
};
logging?: {};
experimental?: {};
completions?: {};
};
};
// Auto-discovery settings
autoDiscoverTools?: boolean; // Default: true
autoDiscoverResources?: boolean; // Default: true
autoDiscoverPrompts?: boolean; // Default: true
// HTTP endpoint configuration
globalPrefix?: string; // Default: 'mcp'
// Logging configuration
logLevel?: 'error' | 'warn' | 'info' | 'debug' | 'verbose'; // Default: 'info'
enableLogging?: boolean; // Deprecated: use logLevel instead
// Error handling
errorHandler?: (error: Error) => any;
}
Basic module registration with inline options:
import { Module } from '@nestjs/common';
import { MCPModule } from '@hmake98/nestjs-mcp';
@Module({
imports: [
MCPModule.forRoot({
serverInfo: {
name: 'my-mcp-server',
version: '1.0.0',
capabilities: {
tools: { listChanged: true },
resources: { subscribe: true, listChanged: true },
prompts: { listChanged: true },
},
},
logLevel: 'debug',
globalPrefix: 'api/mcp', // Changes endpoint to /api/mcp
}),
],
})
export class AppModule {}
For dynamic configuration using environment variables or config services:
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MCPModule } from '@hmake98/nestjs-mcp';
@Module({
imports: [
ConfigModule.forRoot(),
MCPModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
serverInfo: {
name: configService.get(
'MCP_SERVER_NAME',
'default-server',
),
version: configService.get('MCP_SERVER_VERSION', '1.0.0'),
},
logLevel: configService.get('MCP_LOG_LEVEL', 'info'),
autoDiscoverTools: true,
autoDiscoverResources: true,
autoDiscoverPrompts: true,
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}
useClass for Async Configurationimport { Injectable } from '@nestjs/common';
import { MCPOptionsFactory, MCPModuleOptions } from '@hmake98/nestjs-mcp';
@Injectable()
export class MCPConfigService implements MCPOptionsFactory {
createMCPOptions(): MCPModuleOptions {
return {
serverInfo: {
name: 'my-mcp-server',
version: '1.0.0',
},
logLevel: 'info',
};
}
}
@Module({
imports: [
MCPModule.forRootAsync({
useClass: MCPConfigService,
}),
],
})
export class AppModule {}
Use forFeature() when you want to use MCP services without exposing HTTP endpoints:
import { Module } from '@nestjs/common';
import { MCPModule } from '@hmake98/nestjs-mcp';
@Module({
imports: [MCPModule.forFeature()],
providers: [MyService],
})
export class FeatureModule {
// This module can inject MCPService, MCPRegistryService, etc.
// but won't expose the /mcp HTTP endpoints
}
@MCPToolMarks a method as an MCP tool. Parameters are passed as a single object.
@MCPTool({
name: string; // Tool name (required)
description: string; // Tool description (required)
})
async myTool(params: { [key: string]: any }) {
// Implementation
}
Example:
@MCPTool({
name: 'get-weather',
description: 'Get weather information for a location',
})
async getWeather(params: { location: string }) {
const weather = await this.weatherService.fetch(params.location);
return `Weather in ${params.location}: ${weather}`;
}
@MCPToolWithParamsDefines a tool with explicit parameter schemas and automatic validation using Zod.
@MCPToolWithParams({
name: string; // Tool name (required)
description: string; // Tool description (required)
parameters: Array<{ // Parameter definitions (required)
name: string;
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
description?: string;
required?: boolean;
default?: any;
enum?: any[];
}>;
})
async myTool(params: { [key: string]: any }) {
// Implementation
}
Example:
@MCPToolWithParams({
name: 'search',
description: 'Search for items',
parameters: [
{
name: 'query',
type: 'string',
description: 'Search query',
required: true,
},
{
name: 'limit',
type: 'number',
description: 'Maximum results',
required: false,
default: 10,
},
{
name: 'sortBy',
type: 'string',
description: 'Sort field',
required: false,
enum: ['name', 'date', 'relevance'],
},
],
})
async search(params: { query: string; limit?: number; sortBy?: string }) {
return this.searchService.search(params);
}
@MCPResourceDefines a static resource with a fixed URI.
@MCPResource({
uri: string; // Resource URI (required)
name: string; // Resource name (required)
description?: string; // Resource description
mimeType?: string; // MIME type
})
async getResource(): Promise<{
uri: string;
mimeType?: string;
text?: string;
blob?: string;
}> {
// Return resource content
}
Example:
@MCPResource({
uri: 'config://app/settings',
name: 'App Settings',
description: 'Application configuration',
mimeType: 'application/json',
})
async getSettings() {
const settings = await this.configService.getAll();
return {
uri: 'config://app/settings',
mimeType: 'application/json',
text: JSON.stringify(settings, null, 2),
};
}
@MCPResourceTemplateDefines a dynamic resource with URI template for pattern matching.
@MCPResourceTemplate({
uriTemplate: string; // URI template with {variables} (required)
name: string; // Resource name (required)
description?: string; // Resource description
mimeType?: string; // MIME type
})
async getResource(variables: { [key: string]: string }): Promise<{
uri: string;
mimeType?: string;
text?: string;
blob?: string;
}> {
// Variables are automatically extracted from the URI
}
Example:
@MCPResourceTemplate({
uriTemplate: 'user:///{userId}/profile',
name: 'User Profile',
description: 'Get user profile by ID',
mimeType: 'application/json',
})
async getUserProfile(variables: { userId: string }) {
const profile = await this.userService.findById(variables.userId);
return {
uri: `user:///${variables.userId}/profile`,
mimeType: 'application/json',
text: JSON.stringify(profile),
};
}
URI Template Syntax:
{variableName} to define variablesfile:///{folder}/{filename} matches file:///documents/report.pdf@MCPPromptDefines a prompt template for AI interactions.
@MCPPrompt({
name: string; // Prompt name (required)
description?: string; // Prompt description
arguments?: Array<{ // Prompt arguments
name: string;
description?: string;
required?: boolean;
}>;
})
async getPrompt(args: { [key: string]: any }): Promise<Array<{
role: 'user' | 'assistant';
content: {
type: 'text' | 'image' | 'resource';
text?: string;
data?: string;
mimeType?: string;
};
}>> {
// Return prompt messages
}
Example:
@MCPPrompt({
name: 'explain-code',
description: 'Generate a prompt to explain code',
arguments: [
{ name: 'code', description: 'Code to explain', required: true },
{ name: 'language', description: 'Programming language', required: true },
{ name: 'level', description: 'Explanation level', required: false },
],
})
async explainCode(args: { code: string; language: string; level?: string }) {
const level = args.level || 'intermediate';
return [
{
role: 'user' as const,
content: {
type: 'text' as const,
text: `Explain this ${args.language} code at a ${level} level:\n\n${args.code}`,
},
},
];
}
The library supports versioning and deprecation tracking for tools, resources, and prompts. This helps you manage API evolution and provide clear migration paths for consumers.
Add a version field to any decorator to track the version of your MCP items:
import { z } from 'zod';
@MCPTool({
name: 'data-processor',
description: 'Process data with advanced algorithms',
schema: z.object({ data: z.string() }),
version: '2.0.0', // Add version information
})
async processData({ data }: { data: string }) {
return { processed: this.processV2(data) };
}
Mark items as deprecated with detailed migration information:
import { z } from 'zod';
@MCPTool({
name: 'legacy-calculator',
description: 'Old calculation method',
schema: z.object({ value: z.number() }),
version: '1.0.0',
deprecation: {
deprecated: true,
message: 'This tool uses outdated algorithms',
since: '1.5.0',
removeIn: '3.0.0',
replacedBy: 'advanced-calculator',
},
})
async legacyCalculate({ value }: { value: number }) {
return value * 2;
}
deprecation?: {
deprecated: boolean; // Whether the item is deprecated (required)
message?: string; // Custom deprecation message
since?: string; // Version when deprecated
removeIn?: string; // Version when it will be removed
replacedBy?: string; // Name of replacement item
}
When deprecated items are used:
Example Deprecation Warning:
[Warn] Tool 'legacy-calculator' is deprecated. This tool uses outdated algorithms. Deprecated since 1.5.0. Will be removed in 3.0.0. Use 'advanced-calculator' instead.
When clients list tools, resources, or prompts, they receive version and deprecation information:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "advanced-calculator",
"description": "Modern calculation algorithms",
"inputSchema": {...},
"version": "2.0.0"
},
{
"name": "legacy-calculator",
"description": "Old calculation method",
"inputSchema": {...},
"version": "1.0.0",
"deprecated": true,
"deprecationMessage": "This tool uses outdated algorithms. Deprecated since 1.5.0. Will be removed in 3.0.0. Use 'advanced-calculator' instead."
}
]
}
}
import { Injectable } from '@nestjs/common';
import { MCPTool, MCPResource, MCPPrompt } from '@hmake98/nestjs-mcp';
import { z } from 'zod';
@Injectable()
export class VersionedService {
// Current tool
@MCPTool({
name: 'current-api',
description: 'Latest API version',
schema: z.object({ input: z.string() }),
version: '3.0.0',
})
async currentApi({ input }: { input: string }) {
return { result: this.processV3(input) };
}
// Deprecated tool
@MCPTool({
name: 'legacy-api',
description: 'Legacy API endpoint',
schema: z.object({ input: z.string() }),
version: '1.0.0',
deprecation: {
deprecated: true,
message: 'Use the new API for better performance',
since: '2.0.0',
removeIn: '4.0.0',
replacedBy: 'current-api',
},
})
async legacyApi({ input }: { input: string }) {
return { result: this.processV1(input) };
}
// Deprecated resource
@MCPResource({
uri: 'file://old-config',
name: 'Old Configuration',
description: 'Legacy configuration format',
version: '1.0.0',
deprecation: {
deprecated: true,
message: 'Configuration format has been updated',
since: '2.0.0',
replacedBy: 'file://new-config',
},
})
async getOldConfig() {
return {
uri: 'file://old-config',
mimeType: 'application/json',
text: JSON.stringify({ legacy: true }),
};
}
// Deprecated prompt
@MCPPrompt({
name: 'old-prompt-template',
description: 'Legacy prompt structure',
schema: z.object({ topic: z.string() }),
version: '1.0.0',
deprecation: {
deprecated: true,
message: 'Prompt format has been improved',
since: '1.5.0',
removeIn: '2.0.0',
replacedBy: 'new-prompt-template',
},
})
async getOldPrompt({ topic }: { topic: string }) {
return [
{
role: 'user' as const,
content: {
type: 'text' as const,
text: `Tell me about ${topic}`,
},
},
];
}
private processV1(input: string) {
return `v1: ${input}`;
}
private processV3(input: string) {
return `v3: ${input}`;
}
}
1.0.0, 2.1.3) for consistencyremoveIn versionreplacedBy when there's a direct replacementAll transport adapters use lazy loading - dependencies are dynamically imported only when a transport is first used. This provides several benefits:
When you enable a transport, the adapter automatically checks for and loads its dependencies. If a dependency is missing, you'll see a helpful error message with exact installation instructions.
Once configured, your NestJS application exposes JSON-RPC endpoints for MCP communication:
Endpoints:
/mcp - Main MCP JSON-RPC endpoint/mcp/batch - Batch request endpoint (multiple requests in one call)Available Methods:
initialize - Initialize the MCP connectiontools/list - List all available toolstools/call - Call a specific toolresources/list - List all available resourcesresources/read - Read a specific resourceprompts/list - List all available promptsprompts/get - Get a specific promptping - Health checkExample Requests:
# List all tools
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}'
# Call a tool
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "add",
"arguments": {
"a": 5,
"b": 3
}
}
}'
# Batch request
curl -X POST http://localhost:3000/mcp/batch \
-H "Content-Type: application/json" \
-d '[
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
},
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/list"
}
]'
Real-time bidirectional communication using WebSockets. Perfect for:
Installation:
npm install ws
Note: The WebSocket adapter uses lazy loading - the
wspackage is only imported when you enable WebSocket transport. If the package is missing, you'll get a clear error message with installation instructions.
Configuration:
import { MCPModule, MCPTransportType } from '@hmake98/nestjs-mcp';
@Module({
imports: [
MCPModule.forRoot({
serverInfo: {
name: 'my-server',
version: '1.0.0',
},
transports: [
{
type: MCPTransportType.WEBSOCKET,
enabled: true,
options: {
port: 3001,
host: '0.0.0.0',
path: '/mcp-ws',
maxConnections: 100,
perMessageDeflate: false,
maxPayload: 10 * 1024 * 1024, // 10MB
},
},
],
}),
],
})
export class AppModule {}
Client Example (JavaScript):
const ws = new WebSocket('ws://localhost:3001/mcp-ws');
ws.onopen = () => {
// Send MCP request
ws.send(
JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/list',
}),
);
};
ws.onmessage = (event) => {
const response = JSON.parse(event.data);
console.log('Response:', response);
};
One-way streaming from server to client. Perfect for:
Note: SSE transport uses
rxjs, which is already included as a peer dependency. No additional installation required.
Configuration:
import { MCPModule, MCPTransportType } from '@hmake98/nestjs-mcp';
@Module({
imports: [
MCPModule.forRoot({
serverInfo: {
name: 'my-server',
version: '1.0.0',
},
transports: [
{
type: MCPTransportType.SSE,
enabled: true,
options: {
path: '/mcp-sse',
heartbeatInterval: 30000, // 30s
retryInterval: 3000, // 3s
},
},
],
}),
],
})
export class AppModule {}
Client Example (JavaScript):
const eventSource = new EventSource('http://localhost:3000/mcp-sse');
eventSource.addEventListener('mcp-response', (event) => {
const response = JSON.parse(event.data);
console.log('Response:', response);
});
eventSource.addEventListener('heartbeat', (event) => {
console.log('Heartbeat:', event.data);
});
// Send request via POST to trigger response
fetch('http://localhost:3000/mcp-sse', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/list',
}),
});
Pub/Sub communication via Redis. Perfect for:
Installation:
npm install ioredis
Note: The Redis adapter uses lazy loading -
ioredisis only imported when you enable Redis transport. If the package is missing, you'll get a clear error message with installation instructions.
Configuration:
import { MCPModule, MCPTransportType } from '@hmake98/nestjs-mcp';
@Module({
imports: [
MCPModule.forRoot({
serverInfo: {
name: 'my-server',
version: '1.0.0',
},
transports: [
{
type: MCPTransportType.REDIS,
enabled: true,
options: {
host: 'localhost',
port: 6379,
password: 'your-password', // Optional
db: 0,
channelPrefix: 'mcp',
requestChannel: 'requests',
responseChannel: 'responses',
},
},
],
}),
],
})
export class AppModule {}
Usage:
The Redis adapter uses pub/sub channels:
mcp:requestsmcp:responses:{clientId}mcp:broadcastClient Example (Node.js with ioredis):
import Redis from 'ioredis';
const publisher = new Redis();
const subscriber = new Redis();
const clientId = 'client-123';
// Subscribe to responses
await subscriber.subscribe(`mcp:responses:${clientId}`);
subscriber.on('message', (channel, message) => {
const response = JSON.parse(message);
console.log('Response:', response);
});
// Send request
await publisher.publish(
'mcp:requests',
JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/list',
clientId: clientId,
}),
);
High-performance RPC with bidirectional streaming. Perfect for:
Installation:
npm install @grpc/grpc-js @grpc/proto-loader
Note: The gRPC adapter uses lazy loading -
@grpc/grpc-jsand@grpc/proto-loaderare only imported when you enable gRPC transport. If packages are missing, you'll get a clear error message with installation instructions.
Configuration:
import { MCPModule, MCPTransportType } from '@hmake98/nestjs-mcp';
@Module({
imports: [
MCPModule.forRoot({
serverInfo: {
name: 'my-server',
version: '1.0.0',
},
transports: [
{
type: MCPTransportType.GRPC,
enabled: true,
options: {
port: 50051,
host: '0.0.0.0',
secure: false, // Set true for TLS
// For TLS:
// secure: true,
// credentials: {
// rootCerts: fs.readFileSync('ca.pem'),
// privateKey: fs.readFileSync('server-key.pem'),
// certChain: fs.readFileSync('server-cert.pem'),
// },
},
},
],
}),
],
})
export class AppModule {}
Proto File Location:
The proto file is included at node_modules/@hmake98/nestjs-mcp/dist/transports/proto/mcp.proto
Client Example (Node.js):
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
const packageDefinition = protoLoader.loadSync('mcp.proto');
const proto = grpc.loadPackageDefinition(packageDefinition);
const client = new proto.mcp.MCPService(
'localhost:50051',
grpc.credentials.createInsecure(),
);
// Unary call
client.Call(
{
jsonrpc: '2.0',
string_id: '1',
method: 'tools/list',
params_json: '{}',
},
(err, response) => {
if (err) {
console.error(err);
} else {
const result = JSON.parse(response.result_json);
console.log('Tools:', result);
}
},
);
// Bidirectional streaming
const stream = client.Stream();
stream.on('data', (response) => {
console.log('Response:', response);
});
stream.write({
jsonrpc: '2.0',
string_id: '1',
method: 'tools/list',
params_json: '{}',
});
You can enable multiple transports simultaneously:
import { MCPModule, MCPTransportType } from '@hmake98/nestjs-mcp';
@Module({
imports: [
MCPModule.forRoot({
serverInfo: {
name: 'multi-transport-server',
version: '1.0.0',
},
transports: [
{
type: MCPTransportType.WEBSOCKET,
enabled: true,
options: { port: 3001 },
},
{
type: MCPTransportType.SSE,
enabled: true,
options: { path: '/mcp-sse' },
},
{
type: MCPTransportType.REDIS,
enabled: true,
options: {
host: 'localhost',
port: 6379,
},
},
{
type: MCPTransportType.GRPC,
enabled: true,
options: { port: 50051 },
},
],
}),
],
})
export class AppModule {}
| Transport | Direction | Use Case | Performance | Complexity |
|---|---|---|---|---|
| HTTP | Request/Response | REST APIs, Simple integration | Good | Low |
| WebSocket | Bidirectional | Real-time apps, Browser clients | Excellent | Medium |
| SSE | Server โ Client | Live updates, Notifications | Good | Low |
| Redis | Pub/Sub | Multi-process, Distributed | Very Good | Medium |
| gRPC | Bidirectional | Microservices, High-throughput | Excellent | High |
| stdio | Bidirectional | CLI tools, Desktop apps | Good | Low |
For CLI tools or desktop applications (like Claude Desktop), use stdio transport:
import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
import { MCPSDKService } from '@hmake98/nestjs-mcp';
@Injectable()
export class AppService implements OnApplicationBootstrap {
constructor(private readonly mcpSdkService: MCPSDKService) {}
async onApplicationBootstrap() {
// Connect to stdio transport for CLI/desktop app usage
await this.mcpSdkService.connectStdio();
}
}
This enables your MCP server to communicate via standard input/output, which is useful for:
Core service for handling MCP protocol requests.
import { MCPService } from '@hmake98/nestjs-mcp';
@Injectable()
export class MyService {
constructor(private readonly mcpService: MCPService) {}
async handleCustomRequest(request: MCPRequest) {
return this.mcpService.handleRequest(request);
}
}
Methods:
handleRequest(request: MCPRequest): Promise<MCPResponse> - Process MCP protocol requestshandleBatchRequest(requests: MCPRequest[]): Promise<MCPResponse[]> - Process multiple requestsRegistry for managing tools, resources, and prompts.
import { MCPRegistryService } from '@hmake98/nestjs-mcp';
@Injectable()
export class MyService {
constructor(private readonly registryService: MCPRegistryService) {}
listTools() {
return this.registryService.getTools();
}
getTool(name: string) {
return this.registryService.getTool(name);
}
}
Methods:
registerTool(tool: MCPToolDefinition): void - Register a new toolgetTool(name: string): MCPToolDefinition | undefined - Get a tool by namegetTools(): MCPToolDefinition[] - Get all registered toolsregisterResource(resource: DiscoveredMCPResource): void - Register a new resourcegetResource(uri: string): DiscoveredMCPResource | undefined - Get a resource by URIgetResources(): DiscoveredMCPResource[] - Get all registered resourcesregisterPrompt(prompt: DiscoveredMCPPrompt): void - Register a new promptgetPrompt(name: string): DiscoveredMCPPrompt | undefined - Get a prompt by namegetPrompts(): DiscoveredMCPPrompt[] - Get all registered promptsLow-level service wrapping the official @modelcontextprotocol/sdk.
import { MCPSDKService } from '@hmake98/nestjs-mcp';
@Injectable()
export class MyService {
constructor(private readonly sdkService: MCPSDKService) {}
async connectToStdio() {
await this.sdkService.connectStdio();
}
}
Methods:
connectStdio(): Promise<void> - Connect to stdio transportgetServer(): McpServer - Get the underlying McpServer instanceService for discovering decorated methods.
import { MCPDiscoveryService } from '@hmake98/nestjs-mcp';
@Injectable()
export class MyService {
constructor(private readonly discoveryService: MCPDiscoveryService) {}
discoverAll() {
const tools = this.discoveryService.discoverTools();
const resources = this.discoveryService.discoverResources();
const prompts = this.discoveryService.discoverPrompts();
return { tools, resources, prompts };
}
}
Methods:
discoverTools(): MCPToolDefinition[] - Discover all toolsdiscoverResources(): DiscoveredMCPResource[] - Discover all resourcesdiscoverPrompts(): DiscoveredMCPPrompt[] - Discover all promptsinterface MCPRequest<P = JSONObject> {
jsonrpc: '2.0';
id: string | number;
method: string;
params?: P;
}
interface MCPResponse {
jsonrpc: '2.0';
id: string | number;
result?: unknown;
error?: MCPError;
}
interface MCPToolResult {
content: Array<{
type: 'text' | 'image' | 'resource';
text?: string;
data?: string;
mimeType?: string;
}>;
isError?: boolean;
}
This package integrates the official @modelcontextprotocol/sdk 1.21.1 with NestJS using a layered architecture:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ NestJS Application โ
โ (Your controllers, services, etc.) โ
โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโ
โ MCPModule โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ @MCPTool โ โ
โ โ @MCPResource โ โ
โ โ @MCPPrompt โ โ
โ โ (Decorators) โ โ
โ โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโ โ
โ โ MCPDiscoveryService โ โ
โ โ (Auto-discover decorated โ โ
โ โ methods at startup) โ โ
โ โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโ โ
โ โ MCPRegistryService โ โ
โ โ (Central registry for tools, โ โ
โ โ resources, and prompts) โ โ
โ โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโ โ
โ โ MCPSDKService โ โ
โ โ (Wraps @modelcontextprotocol/ โ โ
โ โ sdk McpServer) โ โ
โ โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโ โ
โ โ MCPService โ โ
โ โ (HTTP/JSON-RPC handler) โ โ
โ โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโ โ
โ โ MCPController โ โ
โ โ (HTTP endpoints: /mcp, โ โ
โ โ /mcp/batch) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโ
โ @modelcontextprotocol/sdk โ
โ (Official MCP SDK 1.21.1) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
DiscoveryService, MetadataScanner, and ReflectorMcpServer class, handles Zod schema conversion and validation/mcp, /mcp/batch) for protocol communicationThe package uses the modern McpServer API from @modelcontextprotocol/sdk:
Quick Start:
# Build the library
npm install && npm run build
# Run the example
cd examples/basic
npm install
npm start
Then test it:
# List all tools
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
// app.module.ts
import { Module } from '@nestjs/common';
import { MCPModule } from '@hmake98/nestjs-mcp';
import { ToolsService } from './tools.service';
import { ResourcesService } from './resources.service';
import { PromptsService } from './prompts.service';
@Module({
imports: [
MCPModule.forRoot({
serverInfo: {
name: 'my-awesome-mcp-server',
version: '1.0.0',
capabilities: {
tools: { listChanged: true },
resources: { subscribe: true, listChanged: true },
prompts: { listChanged: true },
},
},
logLevel: 'debug',
}),
],
providers: [ToolsService, ResourcesService, PromptsService],
})
export class AppModule {}
// tools.service.ts
import { Injectable } from '@nestjs/common';
import { MCPToolWithParams } from '@hmake98/nestjs-mcp';
@Injectable()
export class ToolsService {
@MCPToolWithParams({
name: 'calculate',
description: 'Perform basic calculations',
parameters: [
{
name: 'operation',
type: 'string',
required: true,
enum: ['add', 'subtract', 'multiply', 'divide'],
},
{ name: 'a', type: 'number', required: true },
{ name: 'b', type: 'number', required: true },
],
})
async calculate(params: { operation: string; a: number; b: number }) {
switch (params.operation) {
case 'add':
return params.a + params.b;
case 'subtract':
return params.a - params.b;
case 'multiply':
return params.a * params.b;
case 'divide':
return params.a / params.b;
default:
throw new Error('Invalid operation');
}
}
}
// resources.service.ts
import { Injectable } from '@nestjs/common';
import { MCPResourceTemplate } from '@hmake98/nestjs-mcp';
@Injectable()
export class ResourcesService {
@MCPResourceTemplate({
uriTemplate: 'data:///{collection}/{id}',
name: 'Data Item',
description: 'Get data item by collection and ID',
mimeType: 'application/json',
})
async getDataItem(variables: { collection: string; id: string }) {
// Fetch from database
const item = await this.fetchFromDB(variables.collection, variables.id);
return {
uri: `data:///${variables.collection}/${variables.id}`,
mimeType: 'application/json',
text: JSON.stringify(item),
};
}
private async fetchFromDB(collection: string, id: string) {
// Your database logic here
return { collection, id, data: 'example' };
}
}
// prompts.service.ts
import { Injectable } from '@nestjs/common';
import { MCPPrompt } from '@hmake98/nestjs-mcp';
@Injectable()
export class PromptsService {
@MCPPrompt({
name: 'analyze-data',
description: 'Generate a data analysis prompt',
arguments: [
{ name: 'dataset', description: 'Dataset name', required: true },
{
name: 'metric',
description: 'Metric to analyze',
required: true,
},
],
})
async analyzeData(args: { dataset: string; metric: string }) {
return [
{
role: 'user' as const,
content: {
type: 'text' as const,
text: `Analyze the ${args.metric} metric from the ${args.dataset} dataset.`,
},
},
];
}
}
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
console.log('MCP Server running on http://localhost:3000/mcp');
}
bootstrap();
# List all tools
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# Call a tool
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"id":2,
"method":"tools/call",
"params":{
"name":"calculate",
"arguments":{"operation":"add","a":10,"b":5}
}
}'
# List resources
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"resources/list"}'
# Read a resource
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"id":4,
"method":"resources/read",
"params":{"uri":"data:///users/123"}
}'
Contributions are welcome! Please follow these guidelines:
git checkout -b feature/amazing-feature)npm test)npm run lint)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)# Clone the repository
git clone https://github.com/hmake98/nestjs-mcp.git
cd nestjs-mcp
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:ci
# Build the package
npm run build
# Lint and format
npm run fix-all
MIT