Back to TutorialsIntegration GuidesIntegrating with Claude Desktop
Integration GuidesIntermediate

Integrating with Claude Desktop

Complete guide to connecting your MCP servers with Claude Desktop

25 min read
MCPgee Team
Integration Guides

Prerequisites

  • Claude Desktop installed
  • Completed 'Setting Up Your First MCP Server' tutorial
  • Basic understanding of JSON configuration
  • Node.js or Python environment set up

Integrating with Claude Desktop

Introduction

Claude Desktop is Anthropic's native application that provides a powerful interface for interacting with Claude. One of its most compelling features is native support for the Model Context Protocol (MCP), allowing you to extend Claude's capabilities with custom tools and data sources. This comprehensive guide will walk you through every aspect of integrating your MCP servers with Claude Desktop.

Understanding Claude Desktop's MCP Architecture

How Claude Desktop Uses MCP

Claude Desktop acts as an MCP client, connecting to multiple MCP servers simultaneously. This architecture enables:

  1. Local Processing: Keep sensitive data on your machine
  2. Custom Tools: Add specialized capabilities to Claude
  3. Resource Access: Give Claude read access to your files, databases, and APIs
  4. Real-time Integration: Stream data and updates directly to Claude

Communication Flow

plaintext
┌─────────────────┐     stdio      ┌─────────────────┐
│ Claude Desktop  │ ←────────────→ │   MCP Server    │
│   (MCP Client)  │                │  (Your Tools)   │
└─────────────────┘                └─────────────────┘
         ↓                                  ↑
         └──────────────┬───────────────────┘
                        │
                  Configuration
                (claude_desktop_config.json)

Setting Up Your Environment

Locating the Configuration File

Claude Desktop stores its configuration in a platform-specific location:

macOS:
bash
~/Library/Application Support/Claude/claude_desktop_config.json
Windows:
powershell
%APPDATA%\Claude\claude_desktop_config.json
Linux:
bash
~/.config/Claude/claude_desktop_config.json

Creating the Configuration Directory

If the configuration file doesn't exist:

bash
# macOS
mkdir -p ~/Library/Application\ Support/Claude
touch ~/Library/Application\ Support/Claude/claude_desktop_config.json

Windows (PowerShell)

New-Item -ItemType Directory -Force -Path "$env:APPDATA\Claude" New-Item -ItemType File -Force -Path "$env:APPDATA\Claude\claude_desktop_config.json"

Linux

mkdir -p ~/.config/Claude touch ~/.config/Claude/claude_desktop_config.json

Configuration Deep Dive

Basic Configuration Structure

json
{
  "mcpServers": {
    "server-name": {
      "command": "executable",
      "args": ["arg1", "arg2"],
      "env": {
        "KEY": "value"
      }
    }
  }
}

Configuration Options Explained

  1. Server Name: Unique identifier for your server
  2. - Must be unique across all configured servers - Used in Claude's UI to identify the server - Best practice: use descriptive names like notes-manager or database-query
  1. Command: The executable to run
  2. - Can be node, python, npx, or any executable - Use absolute paths for reliability - On Windows, use forward slashes or escaped backslashes
  1. Args: Command-line arguments
  2. - Passed to the command in order - Include script paths and any flags - Paths should be absolute
  1. Env: Environment variables
  2. - Merged with system environment - Useful for API keys, configuration - Can reference system variables with ${VAR_NAME}

Advanced Configuration Examples

TypeScript/Node.js Server

json
{
  "mcpServers": {
    "development-tools": {
      "command": "node",
      "args": [
        "--experimental-modules",
        "/Users/username/mcp-servers/dev-tools/dist/index.js"
      ],
      "env": {
        "NODE_ENV": "production",
        "PROJECT_ROOT": "/Users/username/projects",
        "LOG_LEVEL": "info"
      }
    }
  }
}

Python Server with Virtual Environment

json
{
  "mcpServers": {
    "data-analysis": {
      "command": "/Users/username/mcp-servers/data-analysis/venv/bin/python",
      "args": [
        "/Users/username/mcp-servers/data-analysis/server.py",
        "--config",
        "/Users/username/.config/data-analysis.yaml"
      ],
      "env": {
        "PYTHONUNBUFFERED": "1",
        "DATA_DIR": "/Users/username/datasets"
      }
    }
  }
}

Using npx for Package Execution

json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/username/Documents"
      ]
    },
    "github": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-github"
      ],
      "env": {
        "GITHUB_TOKEN": "${GITHUB_TOKEN}"
      }
    }
  }
}

Windows-Specific Configuration

json
{
  "mcpServers": {
    "windows-tools": {
      "command": "C:/Program Files/nodejs/node.exe",
      "args": [
        "C:/Users/username/mcp-servers/windows-tools/index.js"
      ],
      "env": {
        "TEMP_DIR": "C:/Users/username/AppData/Local/Temp",
        "SHELL": "powershell.exe"
      }
    }
  }
}

Building Integration-Ready Servers

Server Requirements for Claude Desktop

  1. Stdio Transport: Must communicate via standard input/output
  2. Proper Initialization: Handle the initialization handshake
  3. Error Handling: Graceful error responses
  4. Logging: Use stderr for logs (stdout is for protocol messages)

Enhanced Server Template

typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import winston from 'winston';

// Configure logging to stderr const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.simple(), transports: [ new winston.transports.Stream({ stream: process.stderr }) ] });

class ClaudeDesktopServer { private server: Server; constructor() { this.server = new Server( { name: 'claude-desktop-integration', version: '1.0.0', }, { capabilities: { resources: {}, tools: {}, prompts: {} }, } ); this.setupHandlers(); } private setupHandlers() { // Log initialization this.server.setRequestHandler('initialize', async (request) => { logger.info('Initializing connection with Claude Desktop', { clientInfo: request.params.clientInfo }); return { protocolVersion: '0.1.0', capabilities: this.server.capabilities, serverInfo: this.server.serverInfo, }; }); // Add tools with rich descriptions for Claude this.server.setRequestHandler('tools/list', async () => { return { tools: [ { name: 'analyzeCode', description: 'Analyze code for potential improvements, bugs, and security issues', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'The code to analyze' }, language: { type: 'string', enum: ['javascript', 'typescript', 'python', 'java'], description: 'Programming language of the code' }, focusAreas: { type: 'array', items: { type: 'string', enum: ['performance', 'security', 'readability', 'bugs'] }, description: 'Specific areas to focus on' } }, required: ['code', 'language'] } } ] }; }); // Implement tool with structured output this.server.setRequestHandler('tools/call', async (request) => { const { name, arguments: args } = request.params; if (name === 'analyzeCode') { logger.info('Analyzing code', { language: args.language }); try { const analysis = await this.analyzeCode(args); return { content: [ { type: 'text', text: this.formatAnalysis(analysis) } ] }; } catch (error) { logger.error('Code analysis failed', error); throw error; } } }); } private async analyzeCode(args: any) { // Implementation here return { issues: [], suggestions: [], metrics: {} }; } private formatAnalysis(analysis: any): string { // Format for Claude's consumption return ## Code Analysis Results\n\n...; } async start() { const transport = new StdioServerTransport(); await this.server.connect(transport); logger.info('Server started and connected to Claude Desktop'); } }

// Handle graceful shutdown process.on('SIGINT', () => { logger.info('Shutting down server'); process.exit(0); });

process.on('SIGTERM', () => { logger.info('Shutting down server'); process.exit(0); });

// Start server const server = new ClaudeDesktopServer(); server.start().catch((error) => { logger.error('Failed to start server', error); process.exit(1); });

Testing Your Integration

1. Direct Server Testing

Before connecting to Claude Desktop, test your server independently:

bash
# Test initialization
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"0.1.0","capabilities":{}}}' | node your-server.js

Test tool listing

echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | node your-server.js

2. Debugging Configuration Issues

Create a debug configuration:

json
{
  "mcpServers": {
    "debug-server": {
      "command": "node",
      "args": [
        "--inspect",
        "/path/to/server.js"
      ],
      "env": {
        "DEBUG": "*",
        "LOG_LEVEL": "debug"
      }
    }
  }
}

3. Claude Desktop Debug Mode

To see detailed logs from Claude Desktop:

macOS:
bash
# Run from terminal to see console output
/Applications/Claude.app/Contents/MacOS/Claude --enable-logging
Windows:
powershell
# Run with console window
start "" "C:\Program Files\Claude\Claude.exe" --enable-logging --console

Common Integration Patterns

1. Multi-Environment Configuration

json
{
  "mcpServers": {
    "dev-server": {
      "command": "node",
      "args": ["/path/to/server.js"],
      "env": {
        "NODE_ENV": "development",
        "API_URL": "http://localhost:3000"
      }
    },
    "prod-server": {
      "command": "node",
      "args": ["/path/to/server.js"],
      "env": {
        "NODE_ENV": "production",
        "API_URL": "https://api.example.com"
      }
    }
  }
}

2. Tool Composition

typescript
// Server that combines multiple tools
class CompositeServer {
  private toolHandlers = new Map<string, ToolHandler>();
  
  registerTool(name: string, handler: ToolHandler) {
    this.toolHandlers.set(name, handler);
  }
  
  async handleToolCall(request: any) {
    const handler = this.toolHandlers.get(request.params.name);
    if (!handler) {
      throw new Error(Unknown tool: ${request.params.name});
    }
    
    return handler.execute(request.params.arguments);
  }
}

// Register specialized tools server.registerTool('queryDatabase', new DatabaseQueryTool()); server.registerTool('generateReport', new ReportGeneratorTool()); server.registerTool('sendEmail', new EmailTool());

3. Resource Aggregation

typescript
// Aggregate resources from multiple sources
class ResourceAggregator {
  private providers: ResourceProvider[] = [];
  
  addProvider(provider: ResourceProvider) {
    this.providers.push(provider);
  }
  
  async listResources() {
    const allResources = await Promise.all(
      this.providers.map(p => p.listResources())
    );
    
    return {
      resources: allResources.flat()
    };
  }
}

// Add different resource types aggregator.addProvider(new FileSystemProvider('/docs')); aggregator.addProvider(new DatabaseProvider('postgresql://...')); aggregator.addProvider(new APIProvider('https://api.example.com'));

Advanced Integration Features

1. Dynamic Tool Registration

typescript
// Load tools based on user configuration
class DynamicToolServer {
  async loadUserTools() {
    const configPath = path.join(os.homedir(), '.claude-tools.json');
    const config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
    
    for (const toolConfig of config.tools) {
      if (toolConfig.enabled) {
        await this.loadTool(toolConfig);
      }
    }
  }
  
  async loadTool(config: ToolConfig) {
    const module = await import(config.path);
    this.registerTool({
      name: config.name,
      description: config.description,
      handler: module.default
    });
  }
}

2. Conditional Capabilities

typescript
// Enable features based on environment
class ConditionalServer {
  getCapabilities() {
    const capabilities: any = {
      tools: {}
    };
    
    // Only enable file access in development
    if (process.env.NODE_ENV === 'development') {
      capabilities.resources = {};
    }
    
    // Enable experimental features with flag
    if (process.env.ENABLE_EXPERIMENTAL === 'true') {
      capabilities.experimental = {
        streaming: {},
        sampling: {}
      };
    }
    
    return capabilities;
  }
}

3. Performance Monitoring

typescript
// Monitor and optimize server performance
class MonitoredServer {
  private metrics = {
    requestCount: 0,
    totalDuration: 0,
    errors: 0
  };
  
  async handleRequest(method: string, handler: Function) {
    const start = Date.now();
    this.metrics.requestCount++;
    
    try {
      const result = await handler();
      const duration = Date.now() - start;
      this.metrics.totalDuration += duration;
      
      // Log slow requests
      if (duration > 1000) {
        logger.warn(Slow request: ${method} took ${duration}ms);
      }
      
      return result;
    } catch (error) {
      this.metrics.errors++;
      logger.error(Request failed: ${method}, error);
      throw error;
    }
  }
  
  getMetrics() {
    return {
      ...this.metrics,
      avgDuration: this.metrics.totalDuration / this.metrics.requestCount
    };
  }
}

Troubleshooting Integration Issues

Server Not Appearing in Claude

  1. Check Configuration Syntax
  2. bash
    # Validate JSON
       python -m json.tool < claude_desktop_config.json
  1. Verify Paths
  2. - Use absolute paths - Check file permissions - Ensure executables exist
  1. Test Server Manually
  2. bash
    # Run the exact command from config
       /usr/bin/node /path/to/server.js

Connection Failures

  1. Check Server Output
  2. typescript
    // Add diagnostic logging
       process.stderr.write(Server starting at ${new Date()}\n);
       process.stderr.write(Working directory: ${process.cwd()}\n);
       process.stderr.write(Environment: ${JSON.stringify(process.env)}\n);
  1. Verify Protocol Implementation
  2. - Ensure proper JSON-RPC format - Handle all required methods - Return correct response structure

Performance Issues

  1. Optimize Startup Time
  2. typescript
    // Lazy load heavy dependencies
       let heavyModule: any;
    
async function getHeavyModule() { if (!heavyModule) { heavyModule = await import('heavy-module'); } return heavyModule; }
  1. Implement Caching
  2. typescript
    class CachedServer {
         private cache = new Map<string, { data: any; expires: number }>();
    
async getCached(key: string, fetcher: Function) { const cached = this.cache.get(key); if (cached && cached.expires > Date.now()) { return cached.data; } const data = await fetcher(); this.cache.set(key, { data, expires: Date.now() + 60000 // 1 minute }); return data; } }

Best Practices for Claude Desktop Integration

1. User Experience

  • Clear Tool Names: Use descriptive, action-oriented names
  • Helpful Descriptions: Write descriptions that help Claude understand when to use each tool
  • Structured Output: Format responses for clarity
  • Progress Updates: Use streaming for long-running operations

2. Error Handling

typescript
// Provide helpful error messages
class UserFriendlyErrors {
  async handleToolCall(request: any) {
    try {
      return await this.executeTool(request);
    } catch (error) {
      if (error instanceof ValidationError) {
        return {
          content: [{
            type: 'text',
            text: ⚠️ Invalid input: ${error.message}\n\nPlease check your parameters and try again.
          }]
        };
      }
      
      if (error instanceof NetworkError) {
        return {
          content: [{
            type: 'text',
            text: 🌐 Network error: Unable to reach external service.\n\nPlease check your internet connection.
          }]
        };
      }
      
      // Log unexpected errors
      logger.error('Unexpected error', error);
      throw new Error('An unexpected error occurred. Please try again.');
    }
  }
}

3. Security Considerations

typescript
// Implement security best practices
class SecureServer {
  private sanitizer = new Sanitizer();
  
  async handleFileAccess(filepath: string) {
    // Validate path is within allowed directory
    const resolved = path.resolve(filepath);
    const allowed = path.resolve(this.config.rootDir);
    
    if (!resolved.startsWith(allowed)) {
      throw new Error('Access denied: Path outside allowed directory');
    }
    
    // Check file size before reading
    const stats = await fs.stat(resolved);
    if (stats.size > this.config.maxFileSize) {
      throw new Error('File too large');
    }
    
    return fs.readFile(resolved, 'utf-8');
  }
  
  sanitizeInput(input: any) {
    // Remove potential security risks
    return this.sanitizer.clean(input);
  }
}

Real-World Integration Examples

1. Development Assistant

json
{
  "mcpServers": {
    "dev-assistant": {
      "command": "node",
      "args": ["/Users/dev/mcp-servers/dev-assistant/index.js"],
      "env": {
        "PROJECT_ROOT": "/Users/dev/projects",
        "GIT_AUTHOR": "dev@example.com",
        "JIRA_API_TOKEN": "${JIRA_TOKEN}",
        "GITHUB_TOKEN": "${GITHUB_TOKEN}"
      }
    }
  }
}

2. Personal Knowledge Base

json
{
  "mcpServers": {
    "obsidian-vault": {
      "command": "python",
      "args": [
        "/Users/user/mcp-servers/obsidian-mcp/server.py",
        "--vault", "/Users/user/Documents/ObsidianVault"
      ]
    },
    "research-papers": {
      "command": "node",
      "args": ["/Users/user/mcp-servers/arxiv-server/dist/index.js"],
      "env": {
        "CACHE_DIR": "/Users/user/.cache/arxiv"
      }
    }
  }
}

3. Business Tools

json
{
  "mcpServers": {
    "sales-crm": {
      "command": "node",
      "args": ["/opt/mcp-servers/salesforce-mcp/index.js"],
      "env": {
        "SALESFORCE_CLIENT_ID": "${SF_CLIENT_ID}",
        "SALESFORCE_CLIENT_SECRET": "${SF_CLIENT_SECRET}",
        "SALESFORCE_USERNAME": "${SF_USERNAME}"
      }
    },
    "analytics": {
      "command": "python",
      "args": ["/opt/mcp-servers/analytics/server.py"],
      "env": {
        "BIGQUERY_PROJECT": "company-analytics",
        "GOOGLE_APPLICATION_CREDENTIALS": "/Users/user/.config/gcloud/credentials.json"
      }
    }
  }
}

Conclusion

Integrating MCP servers with Claude Desktop opens up powerful possibilities for extending Claude's capabilities with your own tools and data sources. By following the patterns and best practices in this guide, you can create robust integrations that enhance your productivity while maintaining security and performance.

Key takeaways:

  • Claude Desktop configuration is straightforward but requires attention to detail
  • Proper error handling and logging are crucial for debugging
  • Security should be a primary consideration
  • Performance optimization improves user experience
  • Multiple servers can work together to provide comprehensive functionality

With this foundation, you're ready to build sophisticated MCP integrations that make Claude an even more powerful assistant for your specific needs.

Code Examples

Complete Claude Desktop Configuration Examplejson
{
  "mcpServers": {
    "notes-manager": {
      "command": "node",
      "args": [
        "/Users/username/mcp-servers/notes/dist/index.js",
        "--storage",
        "/Users/username/Documents/notes"
      ],
      "env": {
        "NODE_ENV": "production",
        "LOG_LEVEL": "info"
      }
    },
    "code-analyzer": {
      "command": "/Users/username/mcp-servers/code-analyzer/venv/bin/python",
      "args": [
        "/Users/username/mcp-servers/code-analyzer/server.py"
      ],
      "env": {
        "PYTHONUNBUFFERED": "1",
        "WORKSPACE": "/Users/username/projects"
      }
    },
    "database-query": {
      "command": "npx",
      "args": [
        "-y",
        "@company/mcp-database-server@latest"
      ],
      "env": {
        "DATABASE_URL": "${DATABASE_URL}",
        "DB_READONLY": "true",
        "QUERY_TIMEOUT": "30000"
      }
    },
    "system-monitor": {
      "command": "deno",
      "args": [
        "run",
        "--allow-read",
        "--allow-env",
        "--allow-sys",
        "/Users/username/mcp-servers/system-monitor/mod.ts"
      ]
    }
  },
  "globalEnv": {
    "MCP_TIMEOUT": "60000",
    "USER_HOME": "/Users/username"
  }
}
Production-Ready MCP Server for Claude Desktoptypescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListResourcesRequestSchema,
  ListToolsRequestSchema,
  ReadResourceRequestSchema,
  ListPromptsRequestSchema,
  GetPromptRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import pino from 'pino';

// Logger configuration - outputs to stderr
const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  transport: {
    target: 'pino-pretty',
    options: {
      destination: 2, // stderr
      colorize: true,
      ignore: 'pid,hostname'
    }
  }
});

// Health monitoring
class HealthMonitor {
  private startTime = Date.now();
  private requestCount = 0;
  private errorCount = 0;
  
  recordRequest() { this.requestCount++; }
  recordError() { this.errorCount++; }
  
  getStatus() {
    return {
      uptime: Date.now() - this.startTime,
      requests: this.requestCount,
      errors: this.errorCount,
      memory: process.memoryUsage(),
    };
  }
}

class ProductionServer {
  private server: Server;
  private health = new HealthMonitor();
  
  constructor() {
    this.server = new Server(
      {
        name: 'production-claude-server',
        version: '2.0.0',
      },
      {
        capabilities: {
          resources: {},
          tools: {},
          prompts: {},
        },
      }
    );
    
    this.setupHandlers();
    this.setupProcessHandlers();
  }
  
  private setupHandlers() {
    // Enhanced initialization with client detection
    this.server.setRequestHandler('initialize', async (request) => {
      const clientName = request.params.clientInfo?.name || 'unknown';
      logger.info({ clientName }, 'Client connected');
      
      // Adjust capabilities based on client
      const capabilities = { ...this.server.capabilities };
      
      if (clientName === 'claude-desktop') {
        // Enable all features for Claude Desktop
        capabilities.experimental = { streaming: {} };
      }
      
      return {
        protocolVersion: '0.1.0',
        capabilities,
        serverInfo: {
          ...this.server.serverInfo,
          environment: process.env.NODE_ENV,
        },
      };
    });
    
    // Resource management with pagination
    this.server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
      this.health.recordRequest();
      const cursor = request.params?.cursor;
      const pageSize = 50;
      
      try {
        const allResources = await this.getAllResources();
        const startIndex = cursor ? parseInt(cursor) : 0;
        const endIndex = startIndex + pageSize;
        
        const page = allResources.slice(startIndex, endIndex);
        const nextCursor = endIndex < allResources.length ? endIndex.toString() : undefined;
        
        return {
          resources: page,
          nextCursor,
        };
      } catch (error) {
        this.health.recordError();
        logger.error({ error }, 'Failed to list resources');
        throw error;
      }
    });
    
    // Tool definitions with detailed schemas
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      this.health.recordRequest();
      
      return {
        tools: [
          {
            name: 'searchKnowledgeBase',
            description: 'Search through your personal knowledge base using natural language queries',
            inputSchema: {
              type: 'object',
              properties: {
                query: {
                  type: 'string',
                  description: 'Natural language search query',
                  minLength: 1,
                  maxLength: 500,
                },
                filters: {
                  type: 'object',
                  properties: {
                    tags: {
                      type: 'array',
                      items: { type: 'string' },
                      description: 'Filter by tags',
                    },
                    dateRange: {
                      type: 'object',
                      properties: {
                        start: { type: 'string', format: 'date' },
                        end: { type: 'string', format: 'date' },
                      },
                    },
                    fileTypes: {
                      type: 'array',
                      items: {
                        type: 'string',
                        enum: ['markdown', 'pdf', 'txt', 'docx'],
                      },
                    },
                  },
                },
                limit: {
                  type: 'number',
                  minimum: 1,
                  maximum: 50,
                  default: 10,
                  description: 'Maximum number of results',
                },
              },
              required: ['query'],
            },
          },
          {
            name: 'executeCommand',
            description: 'Execute system commands safely with proper sandboxing',
            inputSchema: {
              type: 'object',
              properties: {
                command: {
                  type: 'string',
                  enum: ['git-status', 'git-log', 'npm-list', 'system-info'],
                  description: 'Pre-approved safe commands',
                },
                args: {
                  type: 'array',
                  items: { type: 'string' },
                  maxItems: 10,
                },
                workingDirectory: {
                  type: 'string',
                  description: 'Working directory (must be within project root)',
                },
              },
              required: ['command'],
            },
          },
          {
            name: 'analyzeMetrics',
            description: 'Analyze system and application metrics',
            inputSchema: {
              type: 'object',
              properties: {
                metric: {
                  type: 'string',
                  enum: ['performance', 'errors', 'usage', 'costs'],
                },
                timeframe: {
                  type: 'string',
                  enum: ['1h', '24h', '7d', '30d'],
                  default: '24h',
                },
                aggregation: {
                  type: 'string',
                  enum: ['sum', 'avg', 'max', 'min', 'count'],
                  default: 'avg',
                },
              },
              required: ['metric'],
            },
          },
        ],
      };
    });
    
    // Prompt templates for common workflows
    this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
      return {
        prompts: [
          {
            name: 'code-review',
            description: 'Comprehensive code review template',
            arguments: [
              {
                name: 'language',
                description: 'Programming language',
                required: true,
              },
              {
                name: 'focus',
                description: 'Review focus areas',
                required: false,
              },
            ],
          },
          {
            name: 'debug-assistant',
            description: 'Debugging help template',
            arguments: [
              {
                name: 'error',
                description: 'Error message or description',
                required: true,
              },
            ],
          },
        ],
      };
    });
    
    // Tool execution with comprehensive error handling
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      this.health.recordRequest();
      const { name, arguments: args } = request.params;
      
      logger.info({ tool: name, args }, 'Executing tool');
      
      try {
        const result = await this.executeTool(name, args);
        return result;
      } catch (error) {
        this.health.recordError();
        logger.error({ tool: name, error }, 'Tool execution failed');
        
        // User-friendly error messages
        if (error instanceof z.ZodError) {
          return this.formatValidationError(error);
        }
        
        if (error instanceof PermissionError) {
          return this.formatPermissionError(error);
        }
        
        // Generic error
        return {
          content: [{
            type: 'text',
            text: `❌ Failed to execute ${name}: ${error.message}`,
          }],
        };
      }
    });
  }
  
  private setupProcessHandlers() {
    // Graceful shutdown
    process.on('SIGINT', async () => {
      logger.info('Received SIGINT, shutting down gracefully');
      await this.shutdown();
    });
    
    process.on('SIGTERM', async () => {
      logger.info('Received SIGTERM, shutting down gracefully');
      await this.shutdown();
    });
    
    // Error handling
    process.on('uncaughtException', (error) => {
      logger.fatal({ error }, 'Uncaught exception');
      process.exit(1);
    });
    
    process.on('unhandledRejection', (reason, promise) => {
      logger.fatal({ reason, promise }, 'Unhandled rejection');
      process.exit(1);
    });
  }
  
  private async shutdown() {
    logger.info('Shutting down server');
    logger.info({ health: this.health.getStatus() }, 'Final health status');
    process.exit(0);
  }
  
  async start() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    logger.info('Server started successfully');
    
    // Log health status periodically
    setInterval(() => {
      logger.debug({ health: this.health.getStatus() }, 'Health check');
    }, 60000); // Every minute
  }
}

// Custom error classes
class PermissionError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'PermissionError';
  }
}

// Start the server
const server = new ProductionServer();
server.start().catch((error) => {
  logger.fatal({ error }, 'Failed to start server');
  process.exit(1);
});
Cross-Platform Path Configurationjavascript
// Helper script to generate platform-specific configurations
import os from 'os';
import path from 'path';
import fs from 'fs/promises';

class ConfigGenerator {
  static async generateConfig() {
    const platform = os.platform();
    const homeDir = os.homedir();
    
    // Platform-specific paths
    const configPaths = {
      darwin: path.join(homeDir, 'Library', 'Application Support', 'Claude'),
      win32: path.join(process.env.APPDATA || '', 'Claude'),
      linux: path.join(homeDir, '.config', 'Claude'),
    };
    
    const configDir = configPaths[platform] || configPaths.linux;
    const configFile = path.join(configDir, 'claude_desktop_config.json');
    
    // Ensure directory exists
    await fs.mkdir(configDir, { recursive: true });
    
    // Generate platform-appropriate config
    const config = {
      mcpServers: {
        'example-server': {
          command: platform === 'win32' ? 'node.exe' : 'node',
          args: [
            path.join(homeDir, 'mcp-servers', 'example', 'index.js')
          ],
          env: {
            HOME: homeDir,
            PATH: process.env.PATH,
            ...(platform === 'win32' && {
              SYSTEMROOT: process.env.SYSTEMROOT,
              TEMP: process.env.TEMP,
            }),
          },
        },
      },
    };
    
    // Platform-specific server configurations
    if (platform === 'darwin') {
      config.mcpServers['macos-specific'] = {
        command: '/usr/bin/swift',
        args: [path.join(homeDir, 'mcp-servers', 'swift-server', 'main.swift')],
      };
    } else if (platform === 'win32') {
      config.mcpServers['windows-specific'] = {
        command: 'powershell.exe',
        args: [
          '-ExecutionPolicy', 'Bypass',
          '-File', path.join(homeDir, 'mcp-servers', 'ps-server', 'server.ps1'),
        ],
      };
    } else {
      config.mcpServers['linux-specific'] = {
        command: '/usr/bin/python3',
        args: [path.join(homeDir, 'mcp-servers', 'py-server', 'server.py')],
      };
    }
    
    // Write configuration
    await fs.writeFile(
      configFile,
      JSON.stringify(config, null, 2),
      'utf-8'
    );
    
    console.log(`Configuration generated at: ${configFile}`);
    console.log('\nNext steps:');
    console.log('1. Review and modify the configuration as needed');
    console.log('2. Ensure all referenced scripts exist');
    console.log('3. Restart Claude Desktop');
    
    return configFile;
  }
}

// Run if called directly
if (import.meta.url === `file://${process.argv[1]}`) {
  ConfigGenerator.generateConfig().catch(console.error);
}

export default ConfigGenerator;
Debug Wrapper for Troubleshootingtypescript
#!/usr/bin/env node
// debug-wrapper.js - Wraps MCP server for debugging

import { spawn } from 'child_process';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const logDir = path.join(__dirname, 'logs');

// Ensure log directory exists
if (!fs.existsSync(logDir)) {
  fs.mkdirSync(logDir, { recursive: true });
}

const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const logFile = path.join(logDir, `mcp-debug-${timestamp}.log`);
const logStream = fs.createWriteStream(logFile);

// Get the actual server script from arguments
const [,, serverScript, ...serverArgs] = process.argv;

if (!serverScript) {
  console.error('Usage: debug-wrapper.js <server-script> [args...]');
  process.exit(1);
}

logStream.write(`=== MCP Debug Session Started ===\n`);
logStream.write(`Time: ${new Date().toISOString()}\n`);
logStream.write(`Server: ${serverScript}\n`);
logStream.write(`Args: ${JSON.stringify(serverArgs)}\n`);
logStream.write(`Environment: ${JSON.stringify(process.env, null, 2)}\n`);
logStream.write(`=================================\n\n`);

// Spawn the actual server
const server = spawn(process.execPath, [serverScript, ...serverArgs], {
  stdio: ['pipe', 'pipe', 'pipe'],
  env: {
    ...process.env,
    DEBUG: '*',
    LOG_LEVEL: 'debug',
  },
});

// Log all JSON-RPC communication
let buffer = '';

process.stdin.on('data', (data) => {
  const input = data.toString();
  logStream.write(`[${new Date().toISOString()}] CLIENT -> SERVER:\n${input}\n`);
  server.stdin.write(data);
  
  // Try to parse and pretty-print JSON
  try {
    buffer += input;
    const lines = buffer.split('\n');
    buffer = lines.pop() || '';
    
    for (const line of lines) {
      if (line.trim()) {
        const json = JSON.parse(line);
        logStream.write(`[PARSED REQUEST]: ${JSON.stringify(json, null, 2)}\n\n`);
      }
    }
  } catch (e) {
    // Not valid JSON yet, continue buffering
  }
});

server.stdout.on('data', (data) => {
  const output = data.toString();
  logStream.write(`[${new Date().toISOString()}] SERVER -> CLIENT:\n${output}\n`);
  process.stdout.write(data);
  
  // Try to parse and pretty-print JSON
  try {
    const lines = output.split('\n');
    for (const line of lines) {
      if (line.trim()) {
        const json = JSON.parse(line);
        logStream.write(`[PARSED RESPONSE]: ${JSON.stringify(json, null, 2)}\n\n`);
      }
    }
  } catch (e) {
    // Not valid JSON, ignore
  }
});

server.stderr.on('data', (data) => {
  const error = data.toString();
  logStream.write(`[${new Date().toISOString()}] SERVER STDERR:\n${error}\n`);
  process.stderr.write(data);
});

server.on('error', (error) => {
  logStream.write(`[${new Date().toISOString()}] SERVER ERROR: ${error}\n`);
  console.error('Server error:', error);
});

server.on('exit', (code, signal) => {
  logStream.write(`\n=== Server Exited ===\n`);
  logStream.write(`Code: ${code}\n`);
  logStream.write(`Signal: ${signal}\n`);
  logStream.write(`Time: ${new Date().toISOString()}\n`);
  logStream.write(`Log saved to: ${logFile}\n`);
  logStream.end();
  
  process.exit(code || 0);
});

// Handle wrapper termination
process.on('SIGINT', () => {
  logStream.write(`\n[${new Date().toISOString()}] Received SIGINT\n`);
  server.kill('SIGINT');
});

process.on('SIGTERM', () => {
  logStream.write(`\n[${new Date().toISOString()}] Received SIGTERM\n`);
  server.kill('SIGTERM');
});

console.error(`Debug wrapper started. Logs: ${logFile}`);

Key Takeaways

  • Claude Desktop configuration uses a JSON file in platform-specific locations that must be properly formatted
  • MCP servers communicate with Claude Desktop via stdio transport, requiring careful handling of stdout/stderr
  • Proper error handling and logging to stderr (not stdout) is crucial for debugging integration issues
  • Multiple MCP servers can be configured simultaneously to provide different capabilities to Claude
  • Security and performance considerations are important when exposing local resources to AI assistants

Troubleshooting

Server doesn't appear in Claude Desktop after configuration

1. Verify JSON syntax is valid (use a JSON validator). 2. Ensure all paths are absolute, not relative. 3. Check that the command executable exists and is accessible. 4. Completely quit and restart Claude Desktop (not just close the window). 5. Look for error messages in Claude Desktop's logs. 6. Test the server manually by running the exact command from your configuration.

Server starts but immediately disconnects

1. Check that your server properly handles the 'initialize' method. 2. Ensure you're using StdioServerTransport for communication. 3. Verify that only protocol messages go to stdout (use stderr for logs). 4. Add error handling for all request handlers. 5. Test with the debug wrapper to see exact communication. 6. Make sure the server doesn't exit after initialization.

Environment variables not working in configuration

1. Use ${VAR_NAME} syntax to reference system environment variables. 2. Ensure the variables are set in your system before starting Claude Desktop. 3. On macOS, GUI apps may not inherit terminal environment variables - set them system-wide. 4. Use the 'env' section to pass variables to your server. 5. For sensitive data like API keys, ensure proper permissions on the config file.

Tools work in testing but fail in Claude Desktop

1. Ensure your inputSchema exactly matches what you're sending. 2. Check that all required fields are properly marked in the schema. 3. Verify error responses follow the JSON-RPC error format. 4. Test with various input edge cases. 5. Make sure tool names don't conflict across servers. 6. Add comprehensive error handling for all possible failure modes.

Performance issues with Claude Desktop integration

1. Implement caching for expensive operations. 2. Use streaming for long-running tasks instead of blocking. 3. Paginate large resource lists. 4. Optimize server startup time by lazy-loading dependencies. 5. Monitor memory usage and implement cleanup. 6. Consider using connection pooling for database operations. 7. Profile your server to identify bottlenecks.

Next Steps

  • Explore ChatGPT Plugin Development in the next tutorial
  • Build specialized MCP servers for your specific use cases
  • Implement advanced features like streaming and resource pagination
  • Create a suite of complementary MCP servers that work together