- 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
443 lines
10 KiB
Markdown
443 lines
10 KiB
Markdown
# 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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
1. **Always validate input** - Use Zod schemas and additional validation
|
|
2. **Check permissions** - Implement checkPermissions for sensitive operations
|
|
3. **Sanitize paths** - Prevent directory traversal attacks
|
|
4. **Limit resource usage** - Implement timeouts and size limits
|
|
5. **Log security events** - Track permission denials and suspicious activity
|
|
6. **Use prepared statements** - Prevent SQL injection in database tools
|
|
7. **Validate URLs** - Block internal/private IP ranges in HTTP tools
|
|
|
|
## Debugging Tools
|
|
|
|
### Logging
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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();
|
|
}
|
|
}
|
|
``` |