mcp-server-boilerplate

abraxas93/mcp-server-boilerplate

3.3

If you are the rightful owner of mcp-server-boilerplate and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to henry@mcphub.com.

A production-ready TypeScript boilerplate for building Model Context Protocol (MCP) servers.

Tools
3
Resources
0
Prompts
0

MCP Server Boilerplate

๐Ÿš€ Overview

A production-ready TypeScript boilerplate for building Model Context Protocol (MCP) servers. This template provides a solid foundation for creating MCP servers with clean architecture, comprehensive error handling, and extensible tool/resource management.

โœจ Features

  • ๐Ÿ”ง MCP Protocol Support - Full MCP server implementation with tools and resources
  • โšก TypeScript - Type-safe development with modern ES features
  • ๐Ÿ› ๏ธ Tool System - Easy-to-extend tool architecture with Zod validation
  • ๐Ÿ“ Resource Management - File and configuration resource support
  • ๐ŸŽฏ Clean Architecture - Dependency injection, handler binding, and service layer separation
  • ๐Ÿšจ Error Handling - Comprehensive error management with proper MCP responses
  • ๐Ÿงช Testing Ready - Jest configuration for unit and integration tests
  • ๐Ÿณ Docker Support - Container-ready deployment
  • ๐Ÿ“ Code Quality - ESLint + Prettier for consistent code style

๐Ÿ› ๏ธ Technology Stack

  • Runtime: Node.js >= 22.0.0
  • Language: TypeScript 5.x
  • MCP SDK: @modelcontextprotocol/sdk 1.18.2
  • Validation: Zod 3.x with JSON Schema generation
  • Testing: Jest 30.x
  • Linting: ESLint 8.x + Prettier 3.x
  • Build: TypeScript compiler with path aliases

๐Ÿšฆ Quick Start

Prerequisites

  • Node.js >= 22.0.0
  • Git

Installation

# Clone the template
git clone <your-repo-url>
cd mcp-server-template

# Install dependencies
npm install

# Build the project
npm run build

# Start the MCP server
npm start

Development Mode

# Start development server with hot reload
npm run dev

๐Ÿ“‹ Available Scripts

# Development
npm run dev          # Start development server with hot reload
npm run build        # Build TypeScript to JavaScript
npm run start        # Start production server

# Testing
npm test             # Run Jest test suite
npm run test:watch   # Run tests in watch mode

# Code Quality
npm run lint         # Run ESLint
npm run lint:fix     # Fix ESLint issues

๐Ÿ—๏ธ Project Structure

src/
โ”œโ”€โ”€ config/               # Configuration management
โ”œโ”€โ”€ container/            # Dependency injection container
โ”œโ”€โ”€ handlers/            # MCP request handlers
โ”‚   โ”œโ”€โ”€ call-tool-handler.ts     # Tool execution handler
โ”‚   โ”œโ”€โ”€ get-prompt-handler.ts    # Prompt retrieval handler
โ”‚   โ”œโ”€โ”€ list-prompts-handler.ts  # Prompt listing handler
โ”‚   โ”œโ”€โ”€ list-resources-handler.ts # Resource listing handler
โ”‚   โ”œโ”€โ”€ list-tools-handler.ts    # Tool listing handler
โ”‚   โ””โ”€โ”€ read-resource-handler.ts # Resource reading handler
โ”œโ”€โ”€ prompts/             # MCP prompts implementation
โ”‚   โ”œโ”€โ”€ prompt-example.ts # System instructions prompt
โ”‚   โ””โ”€โ”€ index.ts         # Prompt exports
โ”œโ”€โ”€ services/            # Business logic services
โ”‚   โ”œโ”€โ”€ MathService.ts   # Mathematical operations
โ”‚   โ””โ”€โ”€ ErrorService.ts  # Error handling and formatting
โ”œโ”€โ”€ tools/               # MCP tools implementation
โ”‚   โ”œโ”€โ”€ echo.ts          # Echo tool
โ”‚   โ”œโ”€โ”€ add-two-numbers.ts # Math tool
โ”‚   โ””โ”€โ”€ get-time.ts      # Time tool
โ”œโ”€โ”€ __tests__/           # Test suites
โ”‚   โ””โ”€โ”€ add-two-numbers.test.ts # Comprehensive tool tests
โ”œโ”€โ”€ types/               # TypeScript type definitions
โ”œโ”€โ”€ index.ts            # Application entry point & handler binding
โ””โ”€โ”€ server.ts           # MCP server setup & protocol configuration

๐Ÿ”ง Available Tools

The boilerplate includes three example tools to demonstrate the architecture:

1. Echo Tool

Name: echo
Description: Echo back the input text
Parameters:

  • text (string): Text to echo back

Example Usage:

{
  "name": "echo",
  "arguments": {
    "text": "Hello, MCP!"
  }
}

2. Add Two Numbers Tool

Name: add
Description: Add two numbers together
Parameters:

  • a (number): First number
  • b (number): Second number

Example Usage:

{
  "name": "add",
  "arguments": {
    "a": 5,
    "b": 3
  }
}

3. Get Time Tool

Name: get_time
Description: Get current timestamp
Parameters:

  • random_string (string, optional): Dummy parameter for no-parameter tools

Example Usage:

{
  "name": "get_time",
  "arguments": {
    "random_string": "dummy"
  }
}

๐Ÿ“ Available Resources

The server provides two example resources:

1. Example Text File

URI: file:///example.txt
Type: text/plain
Description: A simple example text resource

2. Server Information

URI: config://server-info
Type: application/json
Description: Information about this MCP server including capabilities and available tools

๐Ÿ’ฌ Available Prompts

The server includes prompt support for system instructions:

System Instructions Prompt

Name: system-instructions
Description: Plain instructions to be used as a future system prompt
Content: Role and policy definitions for AI assistants

Example Usage:

{
  "name": "system-instructions",
  "arguments": {}
}

๐Ÿ› ๏ธ Adding New Tools

1. Create Tool Implementation

Create a new file in src/tools/:

// src/tools/my-tool.ts
import { zodToJsonSchema } from 'zod-to-json-schema';
import { z } from 'zod';
import { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';
import { IErrorService } from '../services';

export const myToolInputSchema = z.object({
  input: z.string().describe('Input parameter'),
});

export type MyToolParams = z.infer<typeof myToolInputSchema>;

export const MY_TOOL: Tool = {
  name: 'my_tool',
  description: 'Description of what this tool does',
  inputSchema: zodToJsonSchema(myToolInputSchema, {
    target: 'jsonSchema7',
  }) as Tool['inputSchema'],
};

export default function myTool(
  this: { errorService: IErrorService },
  params: MyToolParams,
): CallToolResult {
  try {
    // Your tool logic here
    return {
      content: [
        {
          type: 'text',
          text: `Result: ${params.input}`,
        },
      ],
    };
  } catch (error) {
    return this.errorService.handleError(error, {
      operation: 'myTool',
      params,
      timestamp: new Date().toISOString(),
    });
  }
}

2. Register Tool

Update src/tools/index.ts:

export * from './my-tool.js';

3. Add to Tools Handler

Update src/handlers/tools-handler.ts:

import { MY_TOOL } from '../tools';

export async function handleListTools(
  _request: ListToolsRequest,
): Promise<{ tools: Tool[] }> {
  return {
    tools: [ECHO_TOOL, ADD_TWO_NUMBERS_TOOL, GET_TIME_TOOL, MY_TOOL],
  };
}

4. Add to Tool Call Handler

Update src/handlers/tool-call-handler.ts:

import myTool from '../tools/my-tool.js';

export async function handleToolCall(
  this: IContainer,
  request: CallToolRequest,
): Promise<CallToolResult> {
  const { name, arguments: args } = request.params;

  switch (name) {
    case 'my_tool':
      return myTool.call(this, args);
    // ... other cases
  }
}

๐Ÿ“ Adding New Resources

1. Update Resources Handler

Add your resource to src/handlers/list-resources-handler.ts:

export async function handleListResources(
  _request: ListResourcesRequest,
): Promise<{ resources: Resource[] }> {
  return {
    resources: [
      // ... existing resources
      {
        uri: 'my-resource://data',
        name: 'My Resource',
        description: 'Description of my resource',
        mimeType: 'application/json',
      },
    ],
  };
}

2. Update Read Resource Handler

Add handling in src/handlers/read-resource-handler.ts:

export async function handleReadResource(
  request: ReadResourceRequest,
): Promise<{ contents: Resource[] }> {
  const { uri } = request.params;

  switch (uri) {
    case 'my-resource://data':
      return {
        contents: [
          {
            uri,
            mimeType: 'application/json',
            text: JSON.stringify({ data: 'your data here' }),
          },
        ],
      };
    // ... other cases
  }
}

๐Ÿ’ฌ Adding New Prompts

1. Create Prompt Implementation

Create a new file in src/prompts/:

// src/prompts/my-prompt.ts
export const MY_PROMPT = {
  description: 'Description of what this prompt does',
  messages: [
    {
      role: 'assistant',
      content: {
        type: 'text',
        text: 'Your prompt content here',
      },
    },
  ],
};

2. Register Prompt

Update src/prompts/index.ts:

export * from './my-prompt.js';

3. Add to Prompts Handler

Update src/handlers/list-prompts-handler.ts:

export const PROMPTS = {
  'system-instructions': {
    name: 'system-instructions',
    description: 'Plain instructions to be used as a future system prompt',
    arguments: [],
  },
  'my-prompt': {
    name: 'my-prompt',
    description: 'Description of my prompt',
    arguments: [],
  },
};

4. Add to Get Prompt Handler

Update src/handlers/get-prompt-handler.ts:

import { MY_PROMPT } from '../prompts';

export async function handleGetPrompt(
  request: GetPromptRequest,
): Promise<Prompt> {
  const { name, arguments: _args } = request.params;
  const prompt = PROMPTS[name as keyof typeof PROMPTS];
  if (!prompt) throw new Error(`Prompt not found: ${name}`);

  switch (name) {
    case 'system-instructions': {
      return SYSTEM_INSTRUCTIONS;
    }
    case 'my-prompt': {
      return MY_PROMPT;
    }
    default:
      throw new Error('Prompt implementation not found');
  }
}

๐Ÿงช Testing

The boilerplate includes comprehensive Jest testing with extensive coverage:

# Run all tests
npm test

# Run tests in watch mode
npm run test:watch

# Run specific test file
npm test add-two-numbers.test.ts

Test Coverage

The project includes comprehensive test suites covering:

  • Schema Validation: Input parameter validation with Zod schemas
  • Happy Path Scenarios: Normal operation testing
  • Error Handling: Comprehensive error scenario coverage
  • Service Integration: Mock service testing
  • Return Format Validation: MCP-compliant response testing
  • Edge Cases: NaN, Infinity, and boundary value testing
  • Context Binding: Proper this context handling

Example Test Structure

The add-two-numbers.test.ts demonstrates comprehensive testing patterns:

// src/__tests__/add-two-numbers.test.ts
import addTwoNumbers from '../tools/add-two-numbers';
import { IMathService, IErrorService } from '../services';

describe('addTwoNumbers', () => {
  // Schema validation tests
  // Happy path tests
  // Error handling tests
  // Service integration tests
  // Return format tests
  // Edge case tests
  // Context binding tests
});

Test Categories

  1. Schema Validation Tests: Ensure input parameters are properly validated
  2. Happy Path Tests: Verify normal operation scenarios
  3. Error Handling Tests: Test error scenarios and proper error responses
  4. Service Integration Tests: Mock service interactions
  5. Return Format Tests: Ensure MCP-compliant responses
  6. Edge Cases: Handle special values like NaN, Infinity
  7. Context Binding: Verify proper this context usage

๐Ÿณ Docker Support

Build Docker Image

docker build -t mcp-server-template .

Run Container

docker run -it mcp-server-template

Dockerfile

The included Dockerfile creates an optimized production image:

FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist ./dist
CMD ["node", "dist/index.js"]

๐Ÿ”ง Configuration

Environment Variables

Create a .env file for configuration:

NODE_ENV=production
LOG_LEVEL=info

TypeScript Configuration

The project uses strict TypeScript configuration with:

  • ES2022 target
  • Node16 module resolution
  • Strict type checking
  • Source maps for debugging

๐Ÿšจ Error Handling

The boilerplate includes comprehensive error handling:

  • ErrorService: Centralized error management
  • MCP-compliant responses: Proper error flags and messages
  • Error categorization: Different handling for TypeError, RangeError, etc.
  • Context preservation: Operation context and parameters in error logs

๐Ÿ›๏ธ Architecture Overview

Handler-Based Architecture

The server uses a clean handler-based architecture with dependency injection:

  1. Application Entry Point (index.ts):

    • Creates dependency injection container
    • Binds handlers with container context
    • Passes bound handlers to server
  2. Server Setup (server.ts):

    • Focuses purely on MCP protocol configuration
    • Accepts pre-bound handlers as constructor parameters
    • No knowledge of handler implementations
  3. Benefits:

    • Single Responsibility: Server only handles MCP protocol
    • Testability: Easy to inject mock handlers for testing
    • Flexibility: Handler composition at application level
    • Type Safety: Full TypeScript support with proper MCP types

Handler Binding Flow

// index.ts - Application wiring
const container = createContainer();
const handlers = {
  toolCall: handleToolCall.bind(container),
  listTools: handleListTools.bind(container),
  // ... other handlers
};
await startServer(handlers);

// server.ts - Protocol setup
export async function startServer(handlers: ServerHandlers) {
  // MCP server configuration only
  server.setRequestHandler(CallToolRequestSchema, handlers.toolCall);
  // ... other request handlers
}

๐Ÿ”Œ MCP Protocol Features

Supported Capabilities

  • โœ… Tools: Execute custom functions with parameter validation
  • โœ… Resources: Read files and configuration data
  • โœ… Prompts: Retrieve system instructions and prompt templates
  • โœ… Stdio Transport: Standard input/output communication
  • โœ… JSON Schema: Automatic schema generation from Zod schemas
  • โœ… Type Safety: Full TypeScript support with MCP SDK types

Request/Response Flow

  1. List Tools: Client requests available tools
  2. Call Tool: Client executes tool with parameters
  3. List Resources: Client requests available resources
  4. Read Resource: Client reads resource content
  5. List Prompts: Client requests available prompts
  6. Get Prompt: Client retrieves specific prompt content

๐Ÿค Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-tool)
  3. Follow the existing code style (ESLint + Prettier)
  4. Add tests for new functionality
  5. Commit your changes (git commit -m 'Add amazing tool')
  6. Push to the branch (git push origin feature/amazing-tool)
  7. Open a Pull Request

Development Guidelines

  • Use TypeScript strict mode
  • Follow the existing architecture patterns
  • Add comprehensive error handling
  • Include unit tests for new tools
  • Update documentation for new features

๐Ÿ“„ License

This project is licensed under the ISC License.

๐Ÿ†˜ Support

For support and questions:


Built with โค๏ธ for the MCP Community

Ready to power your next MCP server! ๐Ÿš€