loading…
Search for a command to run...
loading…
Model Context Protocol (MCP) server implementation demonstrating production-ready patterns in TypeScript, with tools for calculation, weather, and filesystem ac
Model Context Protocol (MCP) server implementation demonstrating production-ready patterns in TypeScript, with tools for calculation, weather, and filesystem access.
Model Context Protocol (MCP) server implementation demonstrating production-ready patterns in TypeScript. Four parallel implementations comparing bare-metal and framework approaches.
This project showcases MCP server implementations with:
*Note: The EasyMCP implementation may not run due to unstable npm package (v0.0.0-development).
# Clone and install
git clone <repository-url>
cd mcp-demo
pnpm install
# Configure
cp .env.example .env
# Edit .env and add your WEATHER_API_KEY
# Build the server
pnpm build
# Add to Claude Code
claude mcp add demo-server node -- $(pwd)/dist/servers/bare-metal/index.js
# Verify connection
claude mcp list
# Development
TRANSPORT_MODE=http pnpm dev
# Production
pnpm build
TRANSPORT_MODE=http pnpm start
Note: For Claude Code (stdio mode), you don't need to manually run the server - it auto-starts!
┌─────────────┐
│ Client │
└──────┬──────┘
│
┌──────▼───────────────────┐
│ Transport Layer │
│ • Stdio (stdin/stdout) │
│ • HTTP/SSE (Express) │
└──────┬───────────────────┘
│
┌──────▼───────────────┐
│ Middleware Pipeline │
│ • Auth │
│ • Rate Limiting │
│ • Logging/Tracing │
│ • Metrics │
└──────┬───────────────┘
│
┌──────▼────────────┐
│ Message Router │
│ • tools/list │
│ • tools/call │
│ • resources/list │
│ • resources/read │
└──────┬────────────┘
│
┌──────▼──────────────────┐
│ Handler Registries │
│ • ToolRegistry │
│ • ResourceRegistry │
└──────┬──────────────────┘
│
┌──────▼────────────────────┐
│ Capability Handlers │
│ • CalculateTool │
│ • WeatherTool │
│ • FileResourceHandler │
└───────────────────────────┘
All capabilities implement a common interface:
interface CapabilityHandler<TParams, TResult> {
validate(input: unknown): ValidationResult<TParams>;
execute(params: TParams, context: ExecutionContext): Promise<Result<TResult>>;
handleError(error: Error): MCPError;
}
This eliminates repetition and makes adding new capabilities simple:
This project includes four parallel implementations of the same MCP server capabilities, allowing you to compare approaches:
| Implementation | Lines of Code | Pattern | Best For |
|---|---|---|---|
Bare-metal (/servers/bare-metal) |
~2,000 | Direct MCP SDK | Learning MCP internals, full control, custom needs |
FastMCP (/servers/fastmcp-impl) |
~420 | Builder API | Production apps, Express-like familiarity |
EasyMCP (/servers/easymcp-impl) ⚠️ |
~400 | Decorators | Rapid prototyping, minimal boilerplate (package unstable) |
mcp-framework (/servers/mcp-framework-impl) |
~455 | Auto-discovery | Large projects with many capabilities |
⚠️ EasyMCP implementation may not run - the easy-mcp npm package has broken exports in v0.0.0-development
| Feature | Bare-metal | FastMCP | EasyMCP | mcp-framework |
|---|---|---|---|---|
| Transport: stdio | ✓ | ✓ | ✓ | ✓ |
| Transport: HTTP/SSE | ✓ | ✓ | ✗ | ✓ |
| Input validation | Manual Zod | Zod schemas | Type inference | Zod + helpers |
| Auto-discovery | ✗ | ✗ | ✗ | ✓ (from /tools) |
| Type safety | Manual | Schema-based | Decorator inference | MCPInput<this> |
| Setup complexity | High | Medium | Low | Medium |
| Boilerplate | High | Low | Minimal | Low-Medium |
| Framework dependency | None | FastMCP | EasyMCP | mcp-framework |
| Learning curve | Steep | Gentle | Gentle | Medium |
Bare-metal (must implement full interface):
class MyTool implements CapabilityHandler {
validate(input: unknown): ValidationResult { /* ... */ }
execute(params: MyParams, ctx: ExecutionContext): Promise<Result> { /* ... */ }
handleError(error: Error): MCPError { /* ... */ }
}
// Register manually
toolRegistry.register('my_tool', new MyTool());
FastMCP (builder pattern):
server.addTool({
name: 'my_tool',
parameters: z.object({ input: z.string() }),
execute: async (args) => processInput(args.input),
});
EasyMCP (decorators):
class MyMCP extends EasyMCP {
@Tool({ description: 'Process input' })
async myTool(input: string) {
return processInput(input);
}
}
mcp-framework (auto-discovery):
// In /tools/my_tool.ts
class MyTool extends MCPTool {
name = 'my_tool';
schema = defineSchema({ input: z.string().describe('Input') });
async execute(input: MCPInput<this>) {
return processInput(input.input);
}
}
export default MyTool;
// Automatically discovered - no registration needed
Choose Bare-metal when:
Choose FastMCP when:
Choose EasyMCP when:
Choose mcp-framework when:
mcp validate, mcp add)Each implementation is in its own directory under /servers:
# Bare-metal
cd servers/bare-metal
pnpm build
node dist/index.js
# FastMCP
cd servers/fastmcp-impl
npm install
npm run dev
# EasyMCP (⚠️ may not work - package unstable)
cd servers/easymcp-impl
npm install
npm run dev
# mcp-framework
cd servers/mcp-framework-impl
npm install
npm run dev
All implementations support the same three capabilities (calculate, get_weather, file access) with identical APIs.
Evaluates mathematical expressions with security controls.
Method: tools/call
Parameters:
{
"name": "calculate",
"arguments": {
"expression": "2 + 2 * 10"
}
}
Returns:
{
"expression": "2 + 2 * 10",
"result": 22
}
Security: Expression sanitization prevents code injection. Only mathematical operators allowed.
Fetches current weather data from OpenWeatherMap.
Method: tools/call
Parameters:
{
"name": "get_weather",
"arguments": {
"location": "San Francisco"
}
}
Returns:
{
"location": "San Francisco, US",
"temperature": 18.5,
"conditions": "partly cloudy",
"timestamp": "2025-10-20T10:37:00Z"
}
Features: 10-minute result caching, configurable timeout, error handling for API failures.
Provides safe read access to files in allowed directories.
List resources:
{
"method": "resources/list"
}
Read resource:
{
"method": "resources/read",
"params": {
"uri": "file:///tmp/example.txt"
}
}
Security: Path traversal protection, restricted to ALLOWED_FILE_PATHS, MIME type detection.
All configuration via environment variables. See .env.example for details.
# Transport
TRANSPORT_MODE=stdio # or "http"
HTTP_PORT=3000 # HTTP mode only
# Weather API
WEATHER_API_KEY=your_key # Required for weather tool
WEATHER_CACHE_TTL=600 # Seconds (10 min default)
# Security
ALLOWED_FILE_PATHS=/tmp # Colon-separated paths
API_KEYS=key1,key2 # HTTP mode only
RATE_LIMIT_REQUESTS=60 # Requests/min per client
# Observability
LOG_LEVEL=info # debug|info|warn|error
LOG_FORMAT=pretty # json|pretty
# Stdio mode with auto-reload
pnpm dev
# HTTP mode with auto-reload
TRANSPORT_MODE=http pnpm dev
# Build image
docker build -t mcp-demo .
# Run container
docker run -p 3000:3000 \
-e WEATHER_API_KEY=your_key \
-e API_KEYS=your_api_key \
mcp-demo
Claude Code CLI (recommended):
# Build the server first
pnpm build
# Add to Claude Code (stdio mode)
claude mcp add demo-server node -- /path/to/mcp-demo/dist/servers/bare-metal/index.js
# Verify it's connected
claude mcp list
# The server will auto-start when Claude Code needs it
# No need to manually run pnpm start!
Claude Desktop (stdio):
{
"mcpServers": {
"demo-server": {
"command": "node",
"args": ["/path/to/mcp-demo/dist/servers/bare-metal/index.js"]
}
}
}
HTTP Client:
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your_api_key" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "calculate",
"arguments": {"expression": "2 + 2"}
}
}'
Stdio Mode (Local, Single User):
HTTP Mode (Shared, Multi-User):
Quick Test (ngrok):
# Terminal 1: Start server
TRANSPORT_MODE=http pnpm start
# Terminal 2: Create tunnel
ngrok http 3000
# Share URL: Others add with
# claude mcp add --transport http your-server https://abc123.ngrok.io/mcp
Production (Cloud):
# Deploy to Railway, Render, Fly.io, AWS, etc.
# Set environment:
# TRANSPORT_MODE=http
# API_KEYS=key1,key2,key3
# Users connect:
claude mcp add --transport http your-server \
https://your-domain.com/mcp \
--header "Authorization: Bearer user_api_key"
Self-Host (GitHub):
# Share repo - users run their own instance
git clone your-repo
pnpm install && pnpm build
claude mcp add demo-server node -- $(pwd)/dist/servers/bare-metal/index.js
This implementation uses the official MCP SDK minimally, implementing core patterns manually:
Advantages:
Components:
Lines of Code: ~2500 (excluding tests)
Production Checklist:
Adding a new tool is straightforward:
1. Create handler (servers/bare-metal/handlers/MyTool.ts):
export class MyTool implements CapabilityHandler<MyParams, MyResult> {
validate(input: unknown): ValidationResult<MyParams> {
return validateWithSchema(MyParamsSchema, input);
}
async execute(params: MyParams, context: ExecutionContext): Promise<Result<MyResult>> {
// Your logic here
return { success: true, data: result };
}
handleError(error: Error): MCPError {
return { code: MCPErrorCode.InternalError, message: error.message };
}
}
2. Register in index.ts:
toolRegistry.register('my_tool', new MyTool(), {
name: 'my_tool',
description: 'Does something useful',
inputSchema: { /* JSON schema */ }
});
Done! Transport, routing, logging, and metrics work automatically.
# Run all tests
pnpm test
# Watch mode
pnpm test:watch
# Coverage report
pnpm test:coverage
Tests cover:
/mcp-demo
/shared # Common utilities
/types # TypeScript interfaces
/security # Validation, sanitization
/observability # Logging, metrics, tracing
/config # Configuration management
/utils # HTTP client, helpers
/servers/bare-metal # Bare-metal implementation
/handlers # Tool and resource handlers
/transport # Stdio and HTTP transports
/middleware # Auth, rate limiting
/registry # Capability registration
/core # Message routing
index.ts # Main entry point
/tests # Test suites
/docs # Specifications
MIT
Contributions welcome! This project is designed as a learning resource and template for production MCP servers.
Focus areas:
Выполни в терминале:
claude mcp add mcp-demo-server -- npx Безопасность
Низкий рискАвтоматическая эвристика по публичным данным — не гарантия безопасности.