@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
- 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);
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
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%
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
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');
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
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.