- MCP server cu stdio transport pentru performanță maximă
- Tool-uri pentru file operations, HTTP requests, system commands
- Suport NATS pentru comunicare inter-module
- Configurare nginx cu API key auth și SSL
- Arhitectură modulară și extensibilă
🤖 Generated with Claude Code
10 KiB
10 KiB
MCP Server API Reference
Tool Handler Base Class
All tools extend the ToolHandler abstract class which provides lifecycle management, validation, and error handling.
Class Definition
abstract class ToolHandler<TInput = any, TOutput = any> {
constructor(
config: ToolConfig,
inputSchema: z.ZodSchema<TInput>,
outputSchema: z.ZodSchema<TOutput>
);
// Main execution method
async execute(input: unknown, context: ToolContext): Promise<TOutput>;
// Lifecycle hooks (override in subclass)
protected async initialize(): Promise<void>;
protected async validate(input: TInput): Promise<TInput>;
protected abstract handle(input: TInput, context: ToolContext): Promise<TOutput>;
protected async cleanup(): Promise<void>;
protected async checkPermissions(context: ToolContext): Promise<void>;
}
Tool Configuration
interface ToolConfig {
name: string; // Unique tool identifier
description: string; // Human-readable description
timeout?: number; // Execution timeout in ms (default: 30000)
permissions?: string[]; // Required permissions
}
Tool Context
interface ToolContext {
requestId: string; // Unique request identifier
permissions: string[]; // Granted permissions
userId?: string; // Optional user identifier
metadata?: Record<string, any>; // Additional context
}
Creating a Custom Tool
Basic Example
import { z } from 'zod';
import { ToolHandler, ToolContext } from '@mcp/tools';
// Define schemas
const InputSchema = z.object({
message: z.string(),
uppercase: z.boolean().optional(),
});
const OutputSchema = z.object({
result: z.string(),
length: z.number(),
});
type Input = z.infer<typeof InputSchema>;
type Output = z.infer<typeof OutputSchema>;
// Implement tool
export class EchoTool extends ToolHandler<Input, Output> {
constructor() {
super(
{
name: 'echo',
description: 'Echo a message with transformations',
timeout: 5000,
},
InputSchema,
OutputSchema
);
}
protected async handle(input: Input, context: ToolContext): Promise<Output> {
let result = input.message;
if (input.uppercase) {
result = result.toUpperCase();
}
this.logger.info({ message: result }, 'Echoing message');
return {
result,
length: result.length,
};
}
}
Advanced Example with Permissions
export class DatabaseQueryTool extends ToolHandler<QueryInput, QueryOutput> {
private db: Database;
constructor() {
super(
{
name: 'db_query',
description: 'Execute database queries',
timeout: 60000,
permissions: ['database:read', 'database:write'],
},
QueryInputSchema,
QueryOutputSchema
);
}
protected async initialize(): Promise<void> {
// Connect to database
this.db = await Database.connect(process.env.DATABASE_URL);
}
protected async checkPermissions(context: ToolContext): Promise<void> {
const isWrite = this.isWriteQuery(this.currentInput.query);
if (isWrite && !context.permissions.includes('database:write')) {
throw new Error('Permission denied: database:write required');
}
if (!context.permissions.includes('database:read')) {
throw new Error('Permission denied: database:read required');
}
}
protected async validate(input: QueryInput): Promise<QueryInput> {
// Additional validation beyond schema
if (this.containsSqlInjection(input.query)) {
throw new Error('Invalid query: potential SQL injection');
}
return input;
}
protected async handle(input: QueryInput, context: ToolContext): Promise<QueryOutput> {
const startTime = Date.now();
try {
const results = await this.db.query(input.query, input.params);
return {
rows: results.rows,
rowCount: results.rowCount,
duration: Date.now() - startTime,
};
} catch (error) {
this.logger.error({ error, query: input.query }, 'Query failed');
throw new Error(`Database query failed: ${error.message}`);
}
}
protected async cleanup(): Promise<void> {
// Close database connection
if (this.db) {
await this.db.close();
}
}
}
Tool Registration
Registering Built-in Tools
import { ToolRegistry } from '@mcp/registry';
import { FileReadTool, FileWriteTool } from './tools';
const registry = new ToolRegistry(natsClient);
// Built-in tools are registered automatically
// But you can also register manually
registry.registerBuiltinTool('file_read', new FileReadTool());
registry.registerBuiltinTool('file_write', new FileWriteTool());
Registering External Module Tools
// External tools are discovered via NATS
// Modules announce their tools on startup
natsClient.publish('tools.discovery', {
module: 'my-module',
tools: [
{
name: 'my_tool',
description: 'Custom tool from module',
inputSchema: { /* ... */ },
permissions: ['custom:permission'],
}
]
});
Error Handling
Error Types
// Base error class
export class ToolError extends Error {
constructor(
message: string,
public code: string,
public details?: any
) {
super(message);
this.name = 'ToolError';
}
}
// Specific error types
export class ValidationError extends ToolError {
constructor(message: string, details?: any) {
super(message, 'VALIDATION_ERROR', details);
}
}
export class PermissionError extends ToolError {
constructor(message: string, required: string[]) {
super(message, 'PERMISSION_ERROR', { required });
}
}
export class TimeoutError extends ToolError {
constructor(timeout: number) {
super(`Operation timed out after ${timeout}ms`, 'TIMEOUT_ERROR');
}
}
Error Handling in Tools
protected async handle(input: Input, context: ToolContext): Promise<Output> {
try {
// Tool logic
} catch (error) {
if (error.code === 'ENOENT') {
throw new ToolError('File not found', 'FILE_NOT_FOUND', { path: input.path });
}
// Re-throw unknown errors
throw error;
}
}
Testing Tools
Unit Testing
import { MyTool } from './MyTool';
import { ToolContext } from '@mcp/tools';
describe('MyTool', () => {
let tool: MyTool;
beforeEach(() => {
tool = new MyTool();
});
const createContext = (permissions: string[] = []): ToolContext => ({
requestId: 'test-request',
permissions,
});
it('should execute successfully', async () => {
const result = await tool.execute(
{ input: 'test' },
createContext(['required:permission'])
);
expect(result).toEqual({ output: 'expected' });
});
it('should require permissions', async () => {
await expect(
tool.execute({ input: 'test' }, createContext())
).rejects.toThrow('Permission denied');
});
});
Integration Testing
import { ToolRegistry } from '@mcp/registry';
import { NatsClient } from '@mcp/nats';
describe('Tool Integration', () => {
let registry: ToolRegistry;
let nats: NatsClient;
beforeAll(async () => {
nats = new NatsClient();
await nats.connect();
registry = new ToolRegistry(nats);
});
afterAll(async () => {
await nats.close();
});
it('should execute tool via registry', async () => {
const result = await registry.executeTool(
'my_tool',
{ input: 'test' }
);
expect(result).toBeDefined();
});
});
Performance Considerations
Timeout Management
protected async handle(input: Input, context: ToolContext): Promise<Output> {
// Use AbortController for cancellable operations
const controller = new AbortController();
const timeoutId = setTimeout(
() => controller.abort(),
this.config.timeout || 30000
);
try {
const result = await fetch(input.url, {
signal: controller.signal,
});
return processResult(result);
} finally {
clearTimeout(timeoutId);
}
}
Resource Management
export class ResourceIntensiveTool extends ToolHandler {
private pool: ResourcePool;
protected async initialize(): Promise<void> {
// Initialize resource pool
this.pool = new ResourcePool({ max: 10 });
}
protected async handle(input: Input, context: ToolContext): Promise<Output> {
// Acquire resource from pool
const resource = await this.pool.acquire();
try {
return await this.processWithResource(resource, input);
} finally {
// Always release resource
this.pool.release(resource);
}
}
protected async cleanup(): Promise<void> {
// Drain pool on cleanup
await this.pool.drain();
}
}
Security Best Practices
- Always validate input - Use Zod schemas and additional validation
- Check permissions - Implement checkPermissions for sensitive operations
- Sanitize paths - Prevent directory traversal attacks
- Limit resource usage - Implement timeouts and size limits
- Log security events - Track permission denials and suspicious activity
- Use prepared statements - Prevent SQL injection in database tools
- Validate URLs - Block internal/private IP ranges in HTTP tools
Debugging Tools
Logging
protected async handle(input: Input, context: ToolContext): Promise<Output> {
this.logger.debug({ input }, 'Processing request');
try {
const result = await this.process(input);
this.logger.info({ result }, 'Request successful');
return result;
} catch (error) {
this.logger.error({ error, input }, 'Request failed');
throw error;
}
}
Metrics
protected async handle(input: Input, context: ToolContext): Promise<Output> {
const timer = this.metrics.startTimer('tool_execution_duration', {
tool: this.config.name,
});
try {
const result = await this.process(input);
this.metrics.increment('tool_execution_success', {
tool: this.config.name,
});
return result;
} catch (error) {
this.metrics.increment('tool_execution_error', {
tool: this.config.name,
error: error.code,
});
throw error;
} finally {
timer.end();
}
}