
MCP Utils
A comprehensive utilities library for building Model Context Protocol servers, providing standardized tools for response formatting, caching, rate limiting, logging, and WebSocket management.
README
@haakco/mcp-utils
Comprehensive shared utilities library for MCP (Model Context Protocol) servers
Eliminates code duplication and provides standardized, production-ready utilities for building robust MCP servers.
Installation
npm install @haakco/mcp-utils
Quick Start
import {
BaseToolHandler,
ResponseBuilder,
createTextResponse,
formatSuccess,
createLogger
} from '@haakco/mcp-utils';
class MyMCPServer extends BaseToolHandler {
private logger = createLogger('my-server');
async handleTool(args: unknown) {
try {
const result = await this.performOperation(args);
this.logger.info('Operation completed successfully');
return createTextResponse(formatSuccess(result));
} catch (error) {
this.logger.error('Operation failed:', error);
return createErrorResponse(error);
}
}
}
🚀 Core Features
🏗️ MCP Architecture Components
- BaseToolHandler - Standardized tool execution patterns
- ResponseBuilder - Consistent MCP response formatting
- InstanceManager - Multi-instance server support
- ToolRegistry - Tool registration and management
🎨 Formatting & Presentation
- Message Formatters - ✅ ❌ ⚠️ ℹ️ status indicators
- Size Formatters - Bytes, storage units (KB, MB, GB, Ki, Mi, Gi)
- Table Formatters - Structured data presentation
- JSON/Text Utilities - Safe serialization and truncation
📅 DateTime Operations
- Duration Formatting - Human-readable time spans
- Age Calculation - "2 hours ago", "3 days old"
- Relative Time - "in 5 minutes", "1 week ago"
- Duration Parsing - "1h30m" → milliseconds
🧠 Advanced Caching
- SimpleCache - TTL-based caching
- LRUCache - Least Recently Used eviction
- TTLCache - Combined TTL + LRU strategies
- DebouncedCache - Batched operation caching
- Memoization - Function result caching
🚦 Rate Limiting
- Token Bucket - Burst capacity with refill
- Sliding Window - Rolling time-based limits
- Fixed Window - Period-based rate limiting
- Leaky Bucket - Smooth rate control
- Multi-Tier - Multiple simultaneous limits
- Keyed Limiters - Per-user/per-resource limits
🔌 WebSocket Utilities
- ReconnectingWebSocket - Auto-reconnection with backoff
- Message Router - Type-based message handling
- RPC Client - Request/response over WebSocket
- Connection Pool - Managed connection lifecycle
📊 Logging & Monitoring
- Structured Logging - Context-aware log entries
- Performance Timing - Operation duration tracking
- Environment-based - Debug/production configurations
- Multiple Outputs - Console, silent, custom loggers
📚 API Reference
Core MCP Utilities
Response Creation
import { createTextResponse, createErrorResponse, createSuccessResponse } from '@haakco/mcp-utils';
// Basic responses
createTextResponse('Operation completed');
createErrorResponse(new Error('Failed to connect'));
createSuccessResponse('User created successfully');
// Multi-part responses
createMultipartResponse(['Header', 'Content', 'Footer']);
// Progress indicators
createProgressResponse(75, 100, 'Processing data');
Argument Validation
import { validateToolArgs, validateRequiredArgs, extractPagination } from '@haakco/mcp-utils';
// Schema validation
const args = validateToolArgs(input, (data) => userSchema.parse(data), 'create_user');
// Required field validation
const validated = validateRequiredArgs(input, ['name', 'email'], 'User creation');
// Pagination extraction
const { page, perPage, offset, limit } = extractPagination(args);
Formatting Utilities
Message Formatting
import { formatSuccess, formatError, formatWarning, formatInfo } from '@haakco/mcp-utils';
formatSuccess('Operation completed'); // ✅ Operation completed
formatError('Connection failed'); // ❌ Error: Connection failed
formatWarning('Low disk space'); // ⚠️ Warning: Low disk space
formatInfo('System status'); // ℹ️ System status
Size & Number Formatting
import { formatBytes, formatStorageSize, formatPercentage, formatCPU } from '@haakco/mcp-utils';
formatBytes(1048576); // 1 MB
formatStorageSize(1048576); // 1Mi (Kubernetes format)
formatPercentage(75, 100); // 75.00%
formatCPU(0.75); // 75.00%
Table & List Formatting
import { formatTable, formatSimpleTable, formatList, formatBulletList } from '@haakco/mcp-utils';
// Object array to table
const data = [
{ name: 'John', age: 30, city: 'NYC' },
{ name: 'Jane', age: 25, city: 'LA' }
];
formatTable(data);
// name | age | city
// -----|-----|-----
// John | 30 | NYC
// Jane | 25 | LA
// Key-value table
formatSimpleTable({ name: 'Server1', status: 'Running', cpu: '45%' });
// name : Server1
// status : Running
// cpu : 45%
// Lists
formatList(['item1', 'item2', 'item3'], ' | '); // item1 | item2 | item3
formatBulletList(['item1', 'item2']); // • item1\n• item2
DateTime Utilities
Duration Formatting
import { formatDuration, formatDurationFromSeconds, formatAge } from '@haakco/mcp-utils';
formatDuration(90061000); // 1d 1h 1m 1s
formatDurationFromSeconds(3661); // 1h 1m 1s
formatAge(Date.now() - 3600000); // 1h (age from timestamp)
Relative Time
import { formatRelativeTime, parseDuration } from '@haakco/mcp-utils';
formatRelativeTime(new Date(Date.now() - 3600000)); // 1 hour ago
formatRelativeTime(new Date(Date.now() + 1800000)); // in 30 minutes
parseDuration('1h30m'); // 5400000 (milliseconds)
parseDuration('2d12h'); // 216000000
Caching System
Simple TTL Cache
import { SimpleCache } from '@haakco/mcp-utils';
const cache = new SimpleCache<string>(60000); // 1 minute TTL
cache.set('key1', 'value1');
cache.get('key1'); // 'value1'
cache.has('key1'); // true
// Custom TTL for specific items
cache.set('key2', 'value2', 30000); // 30 second TTL
LRU Cache
import { LRUCache } from '@haakco/mcp-utils';
const cache = new LRUCache<string>(100); // Max 100 items
cache.set('key1', 'value1');
cache.get('key1'); // Marks as recently used
cache.size(); // Current cache size
Combined TTL + LRU Cache
import { TTLCache } from '@haakco/mcp-utils';
const cache = new TTLCache<string>({
ttl: 300000, // 5 minutes
maxSize: 1000, // Max 1000 items
onEvict: (key, value) => console.log(`Evicted ${key}`)
});
cache.set('key1', 'value1');
cache.cleanup(); // Manual cleanup of expired items
Function Memoization
import { memoize } from '@haakco/mcp-utils';
const expensiveOperation = async (id: string) => {
// Expensive API call or computation
return await fetch(`/api/data/${id}`).then(r => r.json());
};
const memoized = memoize(expensiveOperation, {
ttl: 300000, // 5 minute cache
keyGenerator: (id) => `data:${id}`
});
// First call - executes function
await memoized('user1');
// Second call - returns cached result
await memoized('user1');
Rate Limiting
Token Bucket Rate Limiter
import { TokenBucketRateLimiter } from '@haakco/mcp-utils';
const limiter = new TokenBucketRateLimiter(
10, // Bucket capacity (max burst)
2 // Refill rate (tokens per second)
);
// Try to acquire tokens
if (await limiter.acquire(1)) {
// Request allowed
await processRequest();
}
// Wait until tokens available
await limiter.acquireOrWait(1);
Sliding Window Rate Limiter
import { SlidingWindowRateLimiter } from '@haakco/mcp-utils';
const limiter = new SlidingWindowRateLimiter(
60000, // 1 minute window
100 // Max 100 requests per window
);
if (await limiter.acquire()) {
// Request allowed
} else {
const waitTime = limiter.getTimeUntilNextRequest();
console.log(`Rate limited. Wait ${waitTime}ms`);
}
Multi-Tier Rate Limiting
import { MultiTierRateLimiter } from '@haakco/mcp-utils';
const limiter = new MultiTierRateLimiter([
{ windowMs: 1000, maxRequests: 10, name: 'per-second' },
{ windowMs: 60000, maxRequests: 100, name: 'per-minute' },
{ windowMs: 3600000, maxRequests: 1000, name: 'per-hour' }
]);
const result = await limiter.acquire();
if (!result.allowed) {
console.log(`Limited by: ${result.limitedBy}`);
}
Rate-Limited Functions
import { rateLimitFunction, debounce, throttle } from '@haakco/mcp-utils';
// Rate-limited function wrapper
const limitedAPI = rateLimitFunction(apiCall, {
requestsPerSecond: 5,
burst: 10
});
// Debounced function (delays execution)
const debouncedSave = debounce(saveData, 1000);
// Throttled function (limits execution frequency)
const throttledUpdate = throttle(updateUI, 100);
WebSocket Utilities
Reconnecting WebSocket
import { ReconnectingWebSocket } from '@haakco/mcp-utils';
const ws = new ReconnectingWebSocket({
url: 'wss://api.example.com/ws',
maxReconnectAttempts: 5,
reconnectInterval: 5000,
reconnectBackoff: 1.5,
pingInterval: 30000
});
ws.on('open', () => console.log('Connected'));
ws.on('message', (data) => console.log('Received:', data));
ws.on('reconnecting', (attempt) => console.log(`Reconnecting... attempt ${attempt}`));
ws.send({ type: 'subscribe', channel: 'updates' });
Message Router
import { WebSocketRouter } from '@haakco/mcp-utils';
const router = new WebSocketRouter<{ type: string; payload?: any }>();
router.on('user.created', async (message) => {
console.log('New user:', message.payload);
});
router.on('notification', async (message) => {
await showNotification(message.payload);
});
router.setDefaultHandler((message) => {
console.log('Unhandled message:', message);
});
// Handle incoming messages
ws.on('message', (data) => router.handle(data));
WebSocket RPC
import { WebSocketRPC } from '@haakco/mcp-utils';
const rpc = new WebSocketRPC(ws, {
timeout: 30000,
idGenerator: () => `req-${Date.now()}`
});
// Make RPC calls
const user = await rpc.call('getUser', { id: '123' });
const result = await rpc.call('updateProfile', { name: 'New Name' });
Logging System
Basic Logger
import { createLogger, createStructuredLogger } from '@haakco/mcp-utils';
const logger = createLogger('my-module');
logger.info('Application started');
logger.warn('Low memory warning');
logger.error('Database connection failed');
logger.debug('Debug information');
Structured Logger with Context
const logger = createStructuredLogger('api', 'app', {
service: 'user-service',
version: '1.2.3'
});
logger.info('User login successful');
// Output: INFO: User login successful [{"service":"user-service","version":"1.2.3"}]
// Add context for child logger
const requestLogger = logger.child({ requestId: 'req-123', userId: 'user-456' });
requestLogger.info('Processing request');
Performance Logger
import { createPerformanceLogger } from '@haakco/mcp-utils';
const perfLogger = createPerformanceLogger('performance');
// Manual timing
perfLogger.start('database-query');
await queryDatabase();
perfLogger.end('database-query', { query: 'SELECT * FROM users' });
// Automatic timing
const result = await perfLogger.measure(
'api-call',
() => fetch('/api/data').then(r => r.json()),
{ endpoint: '/api/data' }
);
Environment-based Logger
import { createLoggerFromEnv, LogLevel, createLevelLogger } from '@haakco/mcp-utils';
// Automatically configures based on DEBUG and LOG_LEVEL env vars
const logger = createLoggerFromEnv('my-app');
// Or explicit level configuration
const logger = createLevelLogger('my-app', LogLevel.INFO);
🏗️ Advanced Usage
Custom Tool Handler
import { BaseToolHandler, createTextResponse, formatSuccess } from '@haakco/mcp-utils';
import { z } from 'zod';
class CustomToolHandler extends BaseToolHandler {
constructor() {
super('my-custom-server');
}
getTools() {
return [
this.createTool(
'process_data',
'Process data with validation and formatting',
z.object({
data: z.array(z.string()),
format: z.enum(['json', 'table', 'list']).default('json')
}),
async (args) => {
const processed = await this.processData(args.data);
switch (args.format) {
case 'table':
return createTextResponse(formatTable(processed));
case 'list':
return createTextResponse(formatBulletList(processed.map(String)));
default:
return createTextResponse(formatSuccess(JSON.stringify(processed)));
}
}
)
];
}
private async processData(data: string[]): Promise<any[]> {
// Your custom processing logic
return data.map(item => ({ original: item, processed: item.toUpperCase() }));
}
}
Batch Response Builder
import { BatchResponseBuilder } from '@haakco/mcp-utils';
const response = new BatchResponseBuilder()
.addSuccess('Connection established')
.addInfo('Processing 150 items')
.addSeparator()
.add('Results:')
.addSuccess('Created 145 items')
.addWarning('Skipped 3 duplicates')
.addError('Failed to create 2 items')
.addEmptyLine()
.addInfo('Operation completed in 2.3 seconds')
.build();
return response; // Returns proper CallToolResult
Resource Operation Mixin
import { ResourceOperationMixin } from '@haakco/mcp-utils';
import { z } from 'zod';
interface User {
id: string;
name: string;
email: string;
}
class UserTools extends ResourceOperationMixin<User> {
constructor(private userClient: UserClient) {
super('user-management');
}
getTools() {
return this.createResourceTools({
resourceName: 'User',
client: this.userClient,
formatItem: (user) => `${user.name} <${user.email}>`,
validateCreate: z.object({
name: z.string().min(1),
email: z.string().email()
}),
validateUpdate: z.object({
name: z.string().min(1).optional(),
email: z.string().email().optional()
})
});
}
}
📦 Integration Examples
Express.js Integration
import express from 'express';
import { createLogger, rateLimitFunction, SimpleCache } from '@haakco/mcp-utils';
const app = express();
const logger = createLogger('api-server');
const cache = new SimpleCache<any>(300000); // 5 minute cache
// Rate-limited endpoint
const rateLimitedHandler = rateLimitFunction(
async (req, res) => {
const data = await fetchData(req.params.id);
res.json(data);
},
{ requestsPerSecond: 10, burst: 20 }
);
app.get('/api/data/:id', async (req, res) => {
const cached = cache.get(req.params.id);
if (cached) {
logger.info('Cache hit', { id: req.params.id });
return res.json(cached);
}
await rateLimitedHandler(req, res);
});
WebSocket Server Integration
import WebSocket from 'ws';
import { WebSocketRouter, createLogger, TokenBucketRateLimiter } from '@haakco/mcp-utils';
const logger = createLogger('ws-server');
const rateLimiter = new TokenBucketRateLimiter(10, 1); // 10 burst, 1/sec refill
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
const router = new WebSocketRouter();
router.on('message', async (data) => {
if (await rateLimiter.acquire()) {
await handleMessage(data);
} else {
ws.send(JSON.stringify({ error: 'Rate limit exceeded' }));
}
});
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
router.handle(message);
} catch (error) {
logger.error('Invalid message format', error);
}
});
});
🧪 Testing
The library includes comprehensive test coverage (85/85 tests passing):
# Run all tests
npm test
# Run tests with coverage
npm run test:coverage
# Run specific test suites
npm test -- --testNamePattern="Cache"
npm test -- --testNamePattern="Rate.*Limiter"
Test Structure
tests/
├── base-handler.test.ts # Core MCP functionality
├── cache.test.ts # All caching strategies
├── datetime.test.ts # DateTime utilities
├── formatters.test.ts # Formatting functions
├── response-builder.test.ts # Response building
├── task-helpers.test.ts # Task execution
└── validators.test.ts # Validation utilities
🚀 Development
# Install dependencies
npm install
# Run tests in watch mode
npm run test:watch
# Build the library
npm run build
# Lint and format code
npm run lint
npm run lint:fix
# Type checking
npm run type-check
📄 License
MIT License - see LICENSE file for details.
🤝 Contributing
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Ensure all tests pass and linting is clean
- Submit a pull request
Built with TypeScript, tested with Jest, and designed for production MCP servers.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.