npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

easy-mcp-nest

v0.5.0

Published

A NestJS-based framework for building standard Model Context Protocol (MCP) servers with tool execution via JSON-RPC 2.0

Downloads

1,044

Readme

EasyMCP Framework

A NestJS-based framework for building standard Model Context Protocol (MCP) servers with tool execution via JSON-RPC 2.0.

Description

EasyMCP simplifies the creation of MCP (Model Context Protocol) servers by providing a clean, type-safe framework that:

  • Standard MCP Protocol: Implements the Model Context Protocol specification with JSON-RPC 2.0 over stdio
  • Tool Execution: Register and execute tools that external LLM agents can discover and call
  • Type Safety: Full TypeScript support with comprehensive type definitions
  • Simple Configuration: Minimal setup required - just define your tools and run

Installation

npm install easy-mcp-nest
# or
pnpm add easy-mcp-nest
# or
yarn add easy-mcp-nest

Peer Dependencies

EasyMCP requires the following peer dependencies to be installed:

  • @nestjs/common ^11.0.1
  • @nestjs/core ^11.0.1
  • @nestjs/platform-express ^11.0.1

Optional peer dependencies (for specific features):

  • express ^4.18.0 (for Express adapter)
  • zod ^3.22.0 (for TypeScript-first validation)

Install required dependencies with:

npm install @nestjs/common@^11.0.1 @nestjs/core@^11.0.1 @nestjs/platform-express@^11.0.1

Install optional dependencies as needed:

# For Express adapter
npm install express@^4.18.0

# For Zod validation
npm install zod@^3.22.0

Quick Start

import { EasyMCP, McpConfig } from 'easy-mcp-nest';

// Define your tools
async function getUser(args: { userId: string }): Promise<string> {
  // Your tool logic here
  const user = await fetchUser(args.userId);
  return JSON.stringify(user);
}

// Configure EasyMCP
const config: McpConfig = {
  tools: [
    {
      name: 'getUser',
      description: 'Retrieves user details by ID',
      function: getUser,
      inputSchema: {
        type: 'OBJECT',
        properties: {
          userId: {
            type: 'STRING',
            description: 'The unique ID of the user',
          },
        },
        required: ['userId'],
      },
    },
  ],
};

// Initialize and run
async function bootstrap() {
  await EasyMCP.initialize(config);
  await EasyMCP.run();
}

bootstrap();

Configuration

McpConfig

The main configuration object passed to EasyMCP.initialize():

interface McpConfig {
  tools: ToolRegistrationInput[];
  resources?: ResourceRegistrationInput[]; // Optional resources
  prompts?: PromptRegistrationInput[]; // Optional prompts
  serverInfo?: ServerInfo;
}

ToolRegistrationInput

Each tool must implement:

interface ToolRegistrationInput<TArgs = Record<string, any>> {
  name: string;
  description: string;
  /**
   * Tool function that accepts args, optional cancellationToken, optional context, and optional progress callback.
   * For backward compatibility, only the args parameter is required.
   */
  function: (
    args: TArgs,
    cancellationToken?: CancellationToken,
    context?: McpContext,
    progress?: ProgressCallback
  ) => Promise<any>;
  /**
   * Input schema in JSON Schema 2020-12 format or Zod schema.
   * If a Zod schema is provided, it will be converted to JSON Schema internally.
   */
  inputSchema: JsonSchema2020_12 | z.ZodType<TArgs>;
  icon?: string; // Optional icon URI
  /** Required scopes/permissions for this tool */
  requiredScopes?: string[];
}

ServerInfo (Optional)

Optional server information for MCP initialize response:

interface ServerInfo {
  name: string;
  version: string;
}

Example Tool

Using JSON Schema

async function searchDatabase(
  args: { query: string; limit?: number },
  cancellationToken?: CancellationToken,
  context?: McpContext
): Promise<string> {
  const results = await db.search(args.query, args.limit || 10);
  return JSON.stringify(results);
}

const searchTool: ToolRegistrationInput = {
  name: 'searchDatabase',
  description: 'Searches the database for matching records',
  function: searchDatabase,
  inputSchema: {
    type: 'object', // JSON Schema 2020-12 format
    properties: {
      query: {
        type: 'string',
        description: 'The search query',
      },
      limit: {
        type: 'integer',
        description: 'Maximum number of results to return',
      },
    },
    required: ['query'],
  },
  requiredScopes: ['database:read'], // Optional: require specific permissions
};

Using Zod Schema (Recommended)

import { z } from 'zod';

const SearchSchema = z.object({
  query: z.string().describe('The search query'),
  limit: z.number().int().positive().optional().describe('Maximum number of results to return'),
});

type SearchParams = z.infer<typeof SearchSchema>;

async function searchDatabase(
  args: SearchParams,
  cancellationToken?: CancellationToken,
  context?: McpContext
): Promise<string> {
  // args is fully typed as SearchParams
  const results = await db.search(args.query, args.limit || 10);
  return JSON.stringify(results);
}

const searchTool: ToolRegistrationInput<SearchParams> = {
  name: 'searchDatabase',
  description: 'Searches the database for matching records',
  function: searchDatabase,
  inputSchema: SearchSchema, // Direct Zod schema - no conversion needed!
  requiredScopes: ['database:read'],
};

ResourceRegistrationInput

Resources can now access context (user info, scopes, etc.):

interface ResourceRegistrationInput {
  uri: string;
  name: string;
  description?: string;
  mimeType?: string;
  icon?: string;
  /**
   * Function that returns the resource content.
   * Context is provided when the resource is accessed.
   */
  getContent: (context?: McpContext) => Promise<string | { type: string; data: string; mimeType?: string }>;
}

Example Resource

const buildingListResource: ResourceRegistrationInput = {
  uri: 'building://list',
  name: 'Building List',
  description: 'List of buildings accessible to the user',
  getContent: async (context) => {
    // Context is now available!
    const buildings = await getBuildingService().getBuildings(context?.userId);
    return JSON.stringify(buildings);
  },
};

API Reference

EasyMCP Class

static initialize(config: McpConfig): Promise<void>

Initializes the EasyMCP framework with the provided configuration. Must be called before run().

static run(): Promise<void>

Starts the EasyMCP server and begins listening for JSON-RPC requests via stdio.

static getService<T>(token: string | symbol): T

Retrieves a service from the NestJS application context. Useful for advanced use cases.

static shutdown(): Promise<void>

Gracefully shuts down the EasyMCP framework, closing the NestJS application context and cleaning up resources. Should be called when the application is terminating (e.g., on SIGTERM, SIGINT signals).

// Example: Handle graceful shutdown
process.on('SIGTERM', async () => {
  await EasyMCP.shutdown();
  process.exit(0);
});

process.on('SIGINT', async () => {
  await EasyMCP.shutdown();
  process.exit(0);
});

Types

Core Types:

  • McpConfig - Main configuration interface
  • ToolRegistrationInput - Tool definition interface
  • ServerInfo - Optional server information
  • JsonRpcRequest, JsonRpcResponse, JsonRpcError - JSON-RPC 2.0 types
  • JsonRpcErrorCode - JSON-RPC 2.0 error code enum
  • InitializeParams, InitializeResult - MCP initialize types
  • ListToolsResult, McpTool - MCP tools types
  • CallToolParams, CallToolResult - MCP tool call types
  • ToolDefinition, ToolParameter, ToolFunction - Tool interfaces
  • IInterfaceLayer - Interface layer interface
  • McpErrorCode - MCP error code enum
  • VERSION, PACKAGE_NAME, getVersion(), getPackageName() - Version information utilities
  • INTERFACE_LAYER_TOKEN - Token for accessing the interface layer (advanced use cases)

New Feature Types:

  • McpContext - Context interface for user information
  • CreateMcpExpressRouterOptions - Express adapter options
  • OAuthProviderConfig, OAuthConfig - OAuth configuration
  • CreateMcpServerOptions - Standalone server options
  • StandaloneTransport - Transport type ('stdio' | 'http')

Architecture

EasyMCP uses a simplified architecture for standard MCP servers:

  1. Interface Layer: Handles JSON-RPC 2.0 communication over stdio
  2. Core Layer: Implements MCP protocol methods (initialize, tools/list, tools/call)
  3. Tool Registry: Manages tool registration and execution

Architecture Diagram

graph TB
    MCPClient[MCP Client<br/>Claude Desktop, Cline, etc.] -->|stdio JSON-RPC| Interface[Interface Layer<br/>StdioGatewayService]
    Interface -->|JsonRpcRequest| Core[McpServerService<br/>Core Orchestrator]
    Core -->|getTools| Registry[ToolRegistryService]
    Core -->|executeTool| Tools[User Tools]
    Core -->|JsonRpcResponse| Interface
    Interface -->|Response| MCPClient

MCP Protocol Compliance

EasyMCP implements the standard Model Context Protocol (MCP) specification version 2025-11-25.

Protocol Version

  • Supported Version: 2025-11-25
  • Validation: The framework validates that clients use the supported protocol version during initialization
  • Error Handling: Unsupported protocol versions are rejected with a clear error message

Supported Methods

EasyMCP implements the following MCP protocol methods:

  • initialize - Server/client handshake, returns server capabilities and protocol version

    • Validates client protocol version
    • Returns server capabilities (currently supports tools)
    • Returns server information (name and version)
  • tools/list - Returns all registered tools with their JSON Schema 2020-12 definitions

    • Returns array of tool definitions in MCP format
    • Each tool includes name, description, inputSchema, and optional icon
  • tools/call - Executes a tool with provided arguments and returns the result

    • Validates tool arguments against JSON Schema 2020-12
    • Executes tool function
    • Returns result in MCP content format
    • Handles errors according to MCP error code specification
    • Supports cancellation tokens for long-running operations
  • resources/list - Returns all registered resources

    • Returns array of resource definitions with URI, name, description, mimeType, and optional icon
  • resources/read - Reads the content of a resource by URI

    • Returns resource content in MCP format
  • prompts/list - Returns all registered prompts

    • Returns array of prompt definitions with name, description, arguments, and optional icon
  • prompts/get - Generates prompt content from arguments

    • Returns prompt messages in MCP format

Error Codes

EasyMCP uses standard JSON-RPC 2.0 and MCP error codes:

  • -32700 - Parse error
  • -32600 - Invalid request
  • -32601 - Method not found
  • -32602 - Invalid params
  • -32603 - Internal error
  • -32001 - Tool not found (MCP-specific)
  • -32002 - Tool execution error (MCP-specific)

Transport

The server communicates via JSON-RPC 2.0 over stdio (standard input/output), which is the standard transport for MCP servers. This allows the server to be used with MCP clients like Claude Desktop, Cursor, Cline, and other MCP-compatible tools.

Default Mode: Newline-Delimited JSON

By default, the server uses newline-delimited JSON (one JSON-RPC message per line) for maximum compatibility with MCP clients. This mode works seamlessly with Cursor, Claude Desktop, and other popular MCP clients.

Content-Length Framing Mode (Optional)

For strict MCP protocol compliance with Content-Length framing, set the environment variable:

MCP_USE_CONTENT_LENGTH=1

This enables the MCP-specified Content-Length header format, which some clients may not parse correctly.

Features

  • Tools: Full support for tool registration and execution with JSON Schema 2020-12
  • Resources: Support for resource registration and reading
  • Prompts: Support for prompt templates and generation
  • Client Features: Basic support for Sampling, Roots, and Elicitation (client-initiated)
  • Metadata/Icons: Optional icon support for tools, resources, and prompts
  • Progress & Cancellation: Basic support for cancellation tokens in tool execution
  • Tool Naming: Validation according to MCP 2025-11-25 naming guidelines

Limitations

  • Transport: stdio transport is fully supported. HTTP transport is available via Express adapter. WebSocket transport is not available.
  • Protocol Version: Only protocol version 2025-11-25 is supported. Older or newer versions will be rejected.
  • Resources Subscribe/Unsubscribe: Basic resource subscription is not yet implemented (resources/list and resources/read are supported)

For more information on testing EasyMCP with real MCP clients, see Integration Testing Guide.

Error Handling

EasyMCP provides comprehensive error handling with custom error classes and clear error messages.

Error Classes

  • EasyMcpError - Base error class for all framework errors
  • ConfigurationError - Configuration validation errors (thrown during initialize())
  • ToolExecutionError - Tool execution failures (wrapped and returned as MCP error)
  • ToolNotFoundError - Tool not found in registry (returned as MCP error code -32001)

Error Handling Examples

import {
  EasyMCP,
  ConfigurationError,
  ToolExecutionError,
  ToolNotFoundError
} from 'easy-mcp-nest';

// Configuration errors - caught during initialization
try {
  await EasyMCP.initialize(config);
} catch (error) {
  if (error instanceof ConfigurationError) {
    console.error('Configuration error:', error.message);
    // Example: "Tool 'myTool': name must be a non-empty string"
  }
}

// Tool execution errors - handled automatically by MCP protocol
// When a tool throws an error, it's automatically converted to an MCP error response
// The error is logged to stderr and returned to the client with error code -32002

Common Error Scenarios

Configuration Errors:

  • Missing required fields (tools array, tool name, description, etc.)
  • Invalid tool schemas (unsupported types, missing properties)
  • Invalid serverInfo format

Tool Execution Errors:

  • Tool function throws an exception → Returns MCP error code -32002
  • Tool not found → Returns MCP error code -32001
  • Invalid tool arguments → Returns JSON-RPC error code -32602 (Invalid Params)

Protocol Errors:

  • Unsupported protocol version → Returns error code -32602 with clear message
  • Invalid JSON-RPC request → Returns error code -32600 (Invalid Request)
  • Unknown method → Returns error code -32601 (Method Not Found)

Error Message Format

All error messages are designed to be helpful for debugging:

  • Include the context (tool name, parameter name, etc.)
  • Provide suggestions when possible
  • Never expose internal implementation details to clients

Example error messages:

  • "Tool 'getUser': property 'userId' must have a description"
  • "Unsupported protocol version: 2024-10-01. Supported version: 2025-11-25"
  • "Missing required parameter: userId"

Troubleshooting

Tools Not Executing

If tools are registered but not being called:

  1. Check Tool Registration: Verify tools appear in console log during initialization
  2. Tool Schema: Ensure inputSchema matches JSON Schema format
  3. Tool Description: Make tool descriptions clear so LLM agents know when to use them
  4. MCP Client: Verify your MCP client (Claude Desktop, etc.) is properly configured
  5. Enable Debug Logging: Set DEBUG=1 environment variable to see detailed protocol messages

Build/Import Issues

If you encounter TypeScript or import errors:

  1. Peer Dependencies: Ensure all peer dependencies are installed (see Installation section)
  2. Type Exports: Verify you're importing from the main package: import { EasyMCP } from 'easy-mcp-nest'
  3. Build Output: Check that dist/index.js and dist/index.d.ts exist after building
  4. Module Resolution: Ensure your tsconfig.json has proper module resolution settings

Protocol Version Errors

If you see "Unsupported protocol version" errors:

  1. Check Client Version: Ensure your MCP client uses protocol version 2025-11-25
  2. Update Client: Update your MCP client to the latest version
  3. Debug Mode: Set DEBUG=1 to see detailed protocol version information

Integration Issues

If your server doesn't work with MCP clients:

  1. Check Configuration: Verify your MCP client configuration is correct
  2. Test Server: Run your server script directly to verify it starts correctly
  3. Review Logs: Check stderr output for initialization and error messages
  4. Integration Guide: See Integration Testing Guide for detailed setup instructions
  5. Examples: Check examples/claude-desktop-integration and examples/cursor-integration for client-specific setup

Debug Mode

Enable debug logging to troubleshoot issues:

DEBUG=1 node your-server.js
# or
DEBUG=true node your-server.js

Debug mode provides detailed information about:

  • Protocol message flow
  • Tool execution details
  • Protocol version validation
  • Argument validation

Note: The DEBUG environment variable accepts either '1' or 'true' (case-sensitive) to enable debug logging.

High-Value Framework Features

EasyMCP provides production-ready features out of the box:

1. Declarative Tool Registration with @McpTool

Define tools using decorators for automatic discovery and registration:

import { McpTool, McpParam, McpContext } from 'easy-mcp-nest';
import { z } from 'zod';

const CreateBuildingSchema = z.object({
  name: z.string(),
  address: z.string(),
});

@McpTool({
  name: 'create_building',
  description: 'Creates a new building',
  requiredScopes: ['building:write'],
  rateLimit: { max: 10, window: '1m' },
  retry: { maxAttempts: 3, backoff: 'exponential' }
})
async createBuilding(
  @McpParam(CreateBuildingSchema) params: z.infer<typeof CreateBuildingSchema>,
  @McpContext() context: McpContext
) {
  return this.service.createBuilding(context.userId, params);
}

2. Automatic Scope Checking

Declarative scope validation - framework handles security automatically:

@McpTool({
  name: 'delete_building',
  requiredScopes: ['building:write', 'building:delete']
})

3. Progress Notifications

Report progress for long-running operations:

@McpTool({ name: 'analyze_debts' })
async analyzeDebts(
  args: any,
  cancellationToken?: CancellationToken,
  context?: McpContext,
  progress?: ProgressCallback
) {
  progress?.({ progress: 0.1, message: 'Fetching payment data...' });
  // ... work ...
  progress?.({ progress: 0.5, message: 'Processing 5/10 apartments...' });
  // ... work ...
  progress?.({ progress: 1.0, message: 'Complete!' });
}

4. Built-in Observability

Automatic metrics collection and Prometheus-compatible endpoints:

  • Tool execution count
  • Average execution time
  • Error rate by tool
  • Request tracing
  • Performance monitoring

Access metrics at /metrics endpoint (Prometheus format).

5. Rate Limiting

Per-tool rate limiting with configurable limits:

@McpTool({
  name: 'create_building',
  rateLimit: { max: 10, window: '1m' } // 10 requests per minute
})

6. Retry Logic

Automatic retry with exponential backoff:

@McpTool({
  name: 'create_building',
  retry: {
    maxAttempts: 3,
    backoff: 'exponential',
    initialDelay: 100,
    maxDelay: 10000
  }
})

7. Circuit Breaker

Automatic circuit breaking when error rate exceeds threshold:

  • Prevents overwhelming system during outages
  • Automatic recovery with half-open state
  • Per-tool circuit breakers

8. Error Handling Hooks

Centralized error handling with decorators:

@McpErrorHandler((error, context) => {
  logErrorSecurely('MCP error', error, context);
  return sanitizeError(error);
})
export class MyErrorHandler {}

9. Middleware System

Pre/post execution hooks for cross-cutting concerns:

@McpMiddleware(async (req, context, next) => {
  const start = Date.now();
  const result = await next();
  logPerformance(req.method, Date.now() - start);
  return result;
})
export class PerformanceMiddleware {}

10. Batch Tool Execution

Execute multiple tools in parallel:

// JSON-RPC request
{
  "jsonrpc": "2.0",
  "method": "tools/batch",
  "params": {
    "tools": [
      { "name": "create_building", "arguments": {...} },
      { "name": "add_apartment", "arguments": {...} }
    ]
  }
}

11. Health Check Endpoints

Production-ready health checks:

  • GET /health - Basic health check
  • GET /health/ready - Readiness probe
  • GET /health/live - Liveness probe
  • GET /metrics - Prometheus metrics

12. Testing Utilities

Easy testing with test helpers:

import { createMcpTestApp, mockMcpContext } from 'easy-mcp-nest';

const app = await createMcpTestApp([BuildingTools]);
const context = mockMcpContext({ userId: '123', scopes: ['read', 'write'] });
const result = await app.callTool('create_building', params, context);
await app.close();

New Features

Express Adapter

Use EasyMCP in Express applications without requiring NestJS for the HTTP layer:

import express from 'express';
import { createMcpExpressRouter } from 'easy-mcp-nest';

const app = express();
app.use(express.json());

const mcpRouter = createMcpExpressRouter({
  tools: [BuildingTools, PaymentTools],
  resources: [BuildingResources],
  auth: mcpAuthMiddleware, // Optional
});

app.use('/mcp', mcpRouter);
app.listen(3000);

Context Injection

Inject user context into tools using the @McpContext() decorator:

import { McpContext, McpContextDecorator } from 'easy-mcp-nest';

@McpTool({ name: 'create_building' })
async createBuilding(
  @McpParam() params: CreateBuildingDto,
  @McpContextDecorator() context: McpContext
) {
  return this.service.createBuilding(context.userId, params);
}

Factory Pattern Support

Use factory functions instead of dependency injection:

import { McpService } from 'easy-mcp-nest';

@McpTool({ name: 'create_building' })
export class BuildingTools {
  constructor(
    @McpService(() => getBuildingService())
    private buildingService: BuildingService
  ) {}
}

OAuth Integration

Standardize OAuth integration across MCP servers:

import { createOAuthMiddleware, OAuthProviderConfig } from 'easy-mcp-nest';

const oauthConfig: OAuthProviderConfig = {
  provider: 'custom',
  validateToken: async (token) => {
    // Validate token and return payload
    return jwt.verify(token, secret);
  },
  extractContext: (payload) => ({
    userId: payload.sub,
    scopes: payload.scopes,
    buildingIds: payload.buildingIds,
  }),
};

const oauthMiddleware = createOAuthMiddleware(oauthProviderService);
app.use('/mcp', oauthMiddleware, mcpRouter);

TypeScript-First Validation with Zod

Use Zod schemas for type-safe validation:

import { z } from 'zod';
import { McpParam } from 'easy-mcp-nest';

const CreateBuildingSchema = z.object({
  name: z.string(),
  address: z.string(),
  floors: z.number().int().min(1).max(100),
});

@McpTool({ name: 'create_building' })
async createBuilding(
  @McpParam(CreateBuildingSchema) params: z.infer<typeof CreateBuildingSchema>
) {
  // params is fully typed and validated
  return this.service.createBuilding(params);
}

Standalone Mode

Use EasyMCP without NestJS:

import { createMcpServer } from 'easy-mcp-nest';

const server = createMcpServer({
  tools: [BuildingTools, PaymentTools],
  resources: [BuildingResources],
  transport: 'http', // or 'stdio'
  auth: validateMcpToken, // Optional
  port: 3000,
});

await server.start();

Testing Utilities

Test MCP tools easily:

import { createMcpTestApp, mockMcpContext } from 'easy-mcp-nest';

const app = await createMcpTestApp([BuildingTools]);
const context = mockMcpContext({
  userId: '123',
  scopes: ['read', 'write'],
});
const result = await app.callTool('create_building', params, context);
await app.close();

Examples

See the examples/ directory for complete working examples.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License - see LICENSE file for details.

Support

For issues and questions, please open an issue on GitHub.