Files
Lupul-Augmentat/docs/api-reference.md
Claude (Lupul Augmentat) 475f89af74 🐺 Initial commit - Lupul Augmentat MCP Server
- 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
2025-10-09 06:24:58 +02:00

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

  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

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();
  }
}