# MCP Toolkit Best Practices

This document outlines best practices for using the MCP Toolkit in your applications.

## Client Implementation

### Connection Management

1. **Proper Initialization and Cleanup**

```typescript
// Always use try/finally to ensure proper cleanup
try {
  await client.connect(transport);
  // Use the client
} finally {
  await client.close();
}
```

2. **Handle Connection Errors**

```typescript
try {
  await client.connect(transport);
} catch (error) {
  console.error('Failed to connect:', error);
  // Implement fallback or retry logic
}
```

3. **Enable Auto-Reconnect for Long-Running Applications**

```typescript
const client = new McpClient(clientInfo, {
  autoReconnect: true,
  maxReconnects: 5,
  reconnectDelay: 1000
});
```

### Tool Execution

1. **Validate Tool Availability Before Calling**

```typescript
const tools = await client.listTools();
const toolExists = tools.some(tool => tool.name === 'tool_name');

if (toolExists) {
  const result = await client.callTool('tool_name', params);
}
```

2. **Handle Tool Execution Errors**

```typescript
try {
  const result = await client.callTool('tool_name', params);
} catch (error) {
  if (error.code === 'tool_not_found') {
    // Handle missing tool
  } else if (error.code === 'invalid_parameters') {
    // Handle parameter validation errors
  } else {
    // Handle other execution errors
  }
}
```

3. **Use Typed Parameters and Results**

```typescript
interface GreetParams {
  name: string;
}

interface GreetResult {
  greeting: string;
}

const params: GreetParams = { name: 'World' };
const result = await client.callTool<GreetResult>('greet', params);
console.log(result.greeting); // Type-safe access
```

### Resource Management

1. **Cache Resource URIs**

```typescript
// Cache resource URIs at startup
const resourceCache = new Map();

async function initializeResourceCache() {
  const resources = await client.listResources();
  resources.forEach(resource => {
    resourceCache.set(resource.uri, resource);
  });
}
```

2. **Subscribe to Important Resources**

```typescript
// Subscribe to frequently accessed resources
await client.subscribeResource('mcp://server/important-resource');

// Listen for updates
client.onResourceUpdated((event) => {
  // Update local cache or UI
  updateLocalData(event.uri, event.content);
});
```

3. **Handle Resource Not Found**

```typescript
try {
  const content = await client.readResource(uri);
} catch (error) {
  if (error.code === 'resource_not_found') {
    // Handle missing resource
    return getDefaultContent();
  }
  throw error;
}
```

## Multi-Server Management

### Centralized Configuration

1. **Use a Configuration File**

```typescript
import { readFileSync } from 'fs';
import { registerServices } from '@modelcontextprotocol/toolkit';

// Load configuration from a file
const config = JSON.parse(readFileSync('./mcp-config.json', 'utf8'));
registerServices(config);
```

2. **Environment-Specific Configuration**

```typescript
const config = {
  servers: {
    'data-server': {
      command: process.env.NODE_ENV === 'production' 
        ? 'data-server-prod' 
        : 'data-server-dev',
      // Other config...
    }
  }
};
```

### Server Lifecycle Management

1. **Graceful Startup and Shutdown**

```typescript
process.on('SIGINT', async () => {
  console.log('Shutting down...');
  await toolkit.stop();
  process.exit(0);
});

// Start servers
await toolkit.start();
```

2. **Monitor Server Health**

```typescript
toolkit.on('serverStatus', (event) => {
  if (event.status === 'crashed') {
    // Log the crash
    logger.error(`Server ${event.serverName} crashed`);
    
    // Attempt recovery if auto-restart is disabled
    if (!config.servers[event.serverName].autoRestart) {
      toolkit.restartServer(event.serverName).catch(error => {
        logger.error(`Failed to restart ${event.serverName}:`, error);
      });
    }
  }
});
```

## Error Handling and Recovery

### Centralized Error Handling

1. **Register Global Error Handlers**

```typescript
import { container } from '@modelcontextprotocol/toolkit';
import { ErrorServiceService } from '@modelcontextprotocol/toolkit';

const errorService = container.resolve<ErrorServiceService>('IErrorServiceService');

// Register handlers for different error types
errorService.registerErrorHandler('ConnectionError', (error) => {
  logger.error('Connection error:', error);
  notifyAdmin('Connection error occurred', error);
  return false; // Let the error propagate
});
```

2. **Implement Recovery Strategies**

```typescript
// Define a custom recovery strategy
const reconnectStrategy = async (error, context) => {
  if (context.retryCount < 3) {
    logger.info(`Attempting reconnect (${context.retryCount + 1}/3)...`);
    await new Promise(resolve => setTimeout(resolve, 1000));
    return true; // Retry the operation
  }
  return false; // Give up after 3 attempts
};

// Register the strategy
errorService.registerRecoveryStrategy('connection', reconnectStrategy);
```

### Logging and Monitoring

1. **Log All Tool Calls**

```typescript
// Create a wrapper for tool execution
async function executeToolWithLogging(name, params) {
  const startTime = Date.now();
  try {
    const result = await client.callTool(name, params);
    const duration = Date.now() - startTime;
    logger.info(`Tool ${name} executed successfully in ${duration}ms`);
    return result;
  } catch (error) {
    const duration = Date.now() - startTime;
    logger.error(`Tool ${name} failed after ${duration}ms:`, error);
    throw error;
  }
}
```

2. **Monitor Performance**

```typescript
// Track execution times for performance monitoring
const executionTimes = new Map();

async function trackToolPerformance(name, params) {
  const start = Date.now();
  try {
    return await client.callTool(name, params);
  } finally {
    const duration = Date.now() - start;
    
    if (!executionTimes.has(name)) {
      executionTimes.set(name, []);
    }
    
    const times = executionTimes.get(name);
    times.push(duration);
    
    // Keep only the last 100 executions
    if (times.length > 100) {
      times.shift();
    }
  }
}

// Get average execution time for a tool
function getAverageExecutionTime(name) {
  const times = executionTimes.get(name) || [];
  if (times.length === 0) return 0;
  
  const sum = times.reduce((acc, time) => acc + time, 0);
  return sum / times.length;
}
```

## Testing with MCP Toolkit

### Unit Testing

1. **Mock the MCP Client**

```typescript
import { vi } from 'vitest';
import { McpClient } from '@modelcontextprotocol/toolkit';

// Create a mock of the McpClient
vi.mock('@modelcontextprotocol/toolkit', async () => {
  const actual = await vi.importActual('@modelcontextprotocol/toolkit');
  return {
    ...actual,
    McpClient: vi.fn().mockImplementation(() => ({
      connect: vi.fn().mockResolvedValue(undefined),
      close: vi.fn().mockResolvedValue(undefined),
      listTools: vi.fn().mockResolvedValue([
        { name: 'mock_tool', description: 'A mock tool' }
      ]),
      callTool: vi.fn().mockResolvedValue({ result: 'mock result' }),
      listResources: vi.fn().mockResolvedValue([
        { uri: 'mcp://mock-server/resource', description: 'A mock resource' }
      ]),
      readResource: vi.fn().mockResolvedValue({ content: 'mock content' }),
    }))
  };
});

// Use the mock in tests
test('my function uses MCP client', async () => {
  const client = new McpClient();
  
  await myFunction(client);
  
  expect(client.callTool).toHaveBeenCalledWith(
    'expected_tool', 
    { expected: 'params' }
  );
});
```

2. **Use the Test Fixtures**

```typescript
import { mockMcpServer } from '@modelcontextprotocol/toolkit/test';

// Set up a mock server for testing
const server = mockMcpServer({
  tools: [
    {
      name: 'test_tool',
      handler: (params) => ({ result: `Processed ${params.input}` })
    }
  ],
  resources: [
    {
      uri: '/test-resource',
      handler: () => ({ content: 'test content' })
    }
  ]
});

// Connect to the mock server
const transport = server.createTransport();
await client.connect(transport);
```

### Integration Testing

1. **Use the Echo Server**

```typescript
import { StdioClientTransport } from '@modelcontextprotocol/toolkit';

// Create a transport to the echo server
const transport = new StdioClientTransport({
  command: 'node',
  args: ['./node_modules/@modelcontextprotocol/toolkit/dist/tools/echo-server.js']
});

await client.connect(transport);
```

2. **Set Up Test-Specific Configuration**

```typescript
// Create a test-specific configuration
const testConfig = {
  servers: {
    'test-server': {
      command: 'node',
      args: ['./test/fixtures/test-server.js'],
      env: { NODE_ENV: 'test' }
    }
  }
};

// Use in tests
registerServices(testConfig);
```

## Performance Optimization

### Minimize Network Round-trips

1. **Batch Tool Calls**

```typescript
// Execute multiple tool calls in parallel
const results = await toolkit.executeToolCalls([
  { function_name: 'tool1', parameters: { param1: 'value1' } },
  { function_name: 'tool2', parameters: { param2: 'value2' } }
]);
```

2. **Cache Resource Content**

```typescript
// Implement a simple resource cache
const resourceCache = new Map();
const cacheTtl = 60000; // 1 minute

async function getCachedResource(uri) {
  const cacheEntry = resourceCache.get(uri);
  
  if (cacheEntry && Date.now() - cacheEntry.timestamp < cacheTtl) {
    return cacheEntry.content;
  }
  
  const content = await client.readResource(uri);
  resourceCache.set(uri, {
    content,
    timestamp: Date.now()
  });
  
  return content;
}
```

### Efficient Event Handling

1. **Debounce Event Handlers for Frequent Updates**

```typescript
import { debounce } from 'lodash';

// Create a debounced handler for resource updates
const handleResourceUpdate = debounce((event) => {
  // Update UI or process the resource
  updateUI(event.uri, event.content);
}, 100, { maxWait: 500 });

// Register the debounced handler
client.onResourceUpdated(handleResourceUpdate);
```

## Security Considerations

### Transport Security

1. **Use Secure Connections for Remote Servers**

```typescript
const transport = new SseTransport({
  baseUrl: 'https://secure-mcp-server.com', // Always use HTTPS
  headers: {
    'Authorization': `Bearer ${apiToken}`
  }
});
```

2. **Validate Server Certificates**

```typescript
const transport = new SseTransport({
  baseUrl: 'https://mcp-server.com',
  httpOptions: {
    ca: readFileSync('./certs/ca.pem'), // Custom CA certificate
    rejectUnauthorized: true // Enforce certificate validation
  }
});
```

### Input Validation

1. **Validate Tool Parameters**

```typescript
function validateToolParams(schema, params) {
  // Implement validation logic
  if (!isValid(schema, params)) {
    throw new Error('Invalid parameters');
  }
  return params;
}

// Use the validation
const safeParams = validateToolParams(toolSchema, userProvidedParams);
const result = await client.callTool('tool_name', safeParams);
```

### Credentials Management

1. **Use Environment Variables for Sensitive Configuration**

```typescript
const transport = new SseTransport({
  baseUrl: process.env.MCP_SERVER_URL,
  headers: {
    'Authorization': `Bearer ${process.env.MCP_API_TOKEN}`
  }
});
```

2. **Avoid Hard-Coding Credentials**

```typescript
// DON'T do this
const hardcodedToken = 'secret-api-token'; // Insecure!

// DO this
import { config } from 'dotenv';
config(); // Load from .env file

const apiToken = process.env.API_TOKEN;
```

## Application Integration

### Handling State

1. **Use a State Manager**

```typescript
class McpStateManager {
  private client: McpClient;
  private state: Map<string, any> = new Map();
  private listeners: Set<Function> = new Set();
  
  constructor(client: McpClient) {
    this.client = client;
    
    // Set up resource update handling
    this.client.onResourceUpdated((event) => {
      this.updateState(event.uri, event.content);
    });
  }
  
  async initialize() {
    // Load initial resources
    const resources = await this.client.listResources();
    for (const resource of resources) {
      const content = await this.client.readResource(resource.uri);
      this.state.set(resource.uri, content);
    }
  }
  
  updateState(key: string, value: any) {
    this.state.set(key, value);
    this.notifyListeners(key, value);
  }
  
  getState(key: string) {
    return this.state.get(key);
  }
  
  subscribe(listener: Function) {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }
  
  private notifyListeners(key: string, value: any) {
    for (const listener of this.listeners) {
      listener(key, value);
    }
  }
}
```

2. **Integrate with Framework State Management**

```typescript
// React example with useState and useEffect
function useMcpResource(client, uri) {
  const [resource, setResource] = useState(null);
  
  useEffect(() => {
    let mounted = true;
    
    async function loadResource() {
      try {
        const content = await client.readResource(uri);
        if (mounted) {
          setResource(content);
        }
      } catch (error) {
        console.error('Failed to load resource:', error);
      }
    }
    
    loadResource();
    
    const unsubscribe = client.onResourceUpdated((event) => {
      if (event.uri === uri && mounted) {
        setResource(event.content);
      }
    });
    
    return () => {
      mounted = false;
      unsubscribe();
    };
  }, [client, uri]);
  
  return resource;
}
```

### UI Integration

1. **Show Loading States**

```typescript
async function executeToolWithUI(name, params, updateUI) {
  updateUI({ loading: true, result: null, error: null });
  
  try {
    const result = await client.callTool(name, params);
    updateUI({ loading: false, result, error: null });
    return result;
  } catch (error) {
    updateUI({ loading: false, result: null, error });
    throw error;
  }
}
```

2. **Handle Resource Updates in UI**

```typescript
// Vue example
export default {
  data() {
    return {
      resourceContent: null
    };
  },
  mounted() {
    this.loadResource();
    
    this.unsubscribe = this.$mcpClient.onResourceUpdated((event) => {
      if (event.uri === this.resourceUri) {
        this.resourceContent = event.content;
      }
    });
  },
  beforeUnmount() {
    if (this.unsubscribe) {
      this.unsubscribe();
    }
  },
  methods: {
    async loadResource() {
      try {
        const content = await this.$mcpClient.readResource(this.resourceUri);
        this.resourceContent = content;
      } catch (error) {
        console.error('Failed to load resource:', error);
      }
    }
  }
};
```