Join our Discord Server
Collabnix Team The Collabnix Team is a diverse collective of Docker, Kubernetes, and IoT experts united by a passion for cloud-native technologies. With backgrounds spanning across DevOps, platform engineering, cloud architecture, and container orchestration, our contributors bring together decades of combined experience from various industries and technical domains.

Claude Desktop Extensions: Building Custom MCP Servers Guide

5 min read

The Model Context Protocol (MCP) represents a paradigm shift in how AI assistants interact with external tools and data sources. Claude Desktop’s MCP server architecture enables developers to extend Claude’s capabilities through custom integrations, transforming it from a conversational AI into a powerful automation platform. This comprehensive guide walks you through building, deploying, and optimizing custom MCP servers for Claude Desktop.

Understanding the Model Context Protocol (MCP)

MCP is Anthropic’s open protocol that standardizes how AI applications connect to data sources and tools. Unlike traditional API integrations, MCP servers provide a bidirectional communication channel that allows Claude to discover capabilities, request data, and execute operations in real-time.

Core MCP Architecture Components

An MCP server consists of three primary components:

  • Resources: Expose data sources like files, databases, or API endpoints that Claude can read
  • Tools: Define executable functions that Claude can invoke to perform actions
  • Prompts: Pre-configured prompt templates that guide Claude’s interactions

The protocol operates over standard transport layers (stdio, HTTP, or WebSocket), making it language-agnostic and highly portable across different environments.

Setting Up Your Development Environment

Before building custom MCP servers, ensure your development environment meets the following prerequisites:

Prerequisites and Dependencies

# Install Node.js 18+ (for TypeScript-based servers)
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs

# Install Python 3.10+ (for Python-based servers)
sudo apt-get update
sudo apt-get install -y python3.10 python3-pip

# Install Claude Desktop (macOS/Windows)
# Download from: https://claude.ai/download

# Verify installations
node --version
python3 --version

Installing the MCP SDK

Anthropic provides official SDKs for TypeScript and Python. Here’s how to set up both:

# TypeScript/Node.js SDK
npm install @modelcontextprotocol/sdk

# Python SDK
pip3 install mcp

Building Your First Custom MCP Server

Let’s create a practical MCP server that integrates with a PostgreSQL database, exposing query capabilities to Claude Desktop.

Python-Based MCP Server Example

Create a new directory and initialize your project:

mkdir claude-postgres-mcp
cd claude-postgres-mcp
python3 -m venv venv
source venv/bin/activate
pip install mcp psycopg2-binary

Now create the server implementation:

#!/usr/bin/env python3
import asyncio
import psycopg2
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent, Resource, INVALID_PARAMS
import json

class PostgresMCPServer:
    def __init__(self, connection_string):
        self.conn_string = connection_string
        self.server = Server("postgres-mcp")
        self.setup_handlers()
    
    def setup_handlers(self):
        @self.server.list_tools()
        async def list_tools():
            return [
                Tool(
                    name="execute_query",
                    description="Execute a SELECT query on the PostgreSQL database",
                    inputSchema={
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "string",
                                "description": "SQL SELECT query to execute"
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Maximum rows to return",
                                "default": 100
                            }
                        },
                        "required": ["query"]
                    }
                )
            ]
        
        @self.server.call_tool()
        async def call_tool(name: str, arguments: dict):
            if name == "execute_query":
                return await self.execute_query(
                    arguments.get("query"),
                    arguments.get("limit", 100)
                )
            raise ValueError(f"Unknown tool: {name}")
        
        @self.server.list_resources()
        async def list_resources():
            return [
                Resource(
                    uri="postgres://schema",
                    name="Database Schema",
                    mimeType="application/json",
                    description="Current database schema information"
                )
            ]
    
    async def execute_query(self, query: str, limit: int):
        try:
            conn = psycopg2.connect(self.conn_string)
            cursor = conn.cursor()
            
            # Security: Ensure only SELECT queries
            if not query.strip().upper().startswith("SELECT"):
                raise ValueError("Only SELECT queries are allowed")
            
            cursor.execute(f"{query} LIMIT {limit}")
            results = cursor.fetchall()
            columns = [desc[0] for desc in cursor.description]
            
            cursor.close()
            conn.close()
            
            formatted_results = [
                dict(zip(columns, row)) for row in results
            ]
            
            return [
                TextContent(
                    type="text",
                    text=json.dumps(formatted_results, indent=2, default=str)
                )
            ]
        except Exception as e:
            return [TextContent(type="text", text=f"Error: {str(e)}")]

async def main():
    conn_string = "postgresql://user:password@localhost:5432/mydb"
    server = PostgresMCPServer(conn_string)
    
    async with stdio_server() as (read_stream, write_stream):
        await server.server.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="postgres-mcp",
                server_version="1.0.0"
            )
        )

if __name__ == "__main__":
    asyncio.run(main())

Configuring Claude Desktop to Use Your MCP Server

Once your server is built, configure Claude Desktop to recognize and use it.

Claude Desktop Configuration File

Edit the Claude Desktop configuration file located at:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "postgres": {
      "command": "python3",
      "args": [
        "/path/to/claude-postgres-mcp/server.py"
      ],
      "env": {
        "DATABASE_URL": "postgresql://user:password@localhost:5432/mydb"
      }
    }
  }
}

Using Environment Variables for Security

For production deployments, never hardcode credentials. Use environment variables:

# Create .env file
cat <<EOF > .env
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
EOF

# Load in your script
export $(cat .env | xargs)

Advanced MCP Server Patterns

Implementing Resource Subscriptions

For real-time data updates, implement resource subscriptions:

@self.server.subscribe_resource()
async def subscribe(uri: str):
    if uri == "postgres://live_metrics":
        while True:
            # Fetch live metrics
            metrics = await self.get_database_metrics()
            yield Resource(
                uri=uri,
                name="Live Database Metrics",
                mimeType="application/json",
                text=json.dumps(metrics)
            )
            await asyncio.sleep(5)  # Update every 5 seconds

Multi-Tool MCP Server with Docker Integration

Here’s an advanced example that combines Docker management with file system operations:

import docker
import os
from pathlib import Path

class DevOpsMCPServer:
    def __init__(self):
        self.docker_client = docker.from_env()
        self.server = Server("devops-mcp")
        self.setup_tools()
    
    def setup_tools(self):
        @self.server.list_tools()
        async def list_tools():
            return [
                Tool(
                    name="list_containers",
                    description="List all Docker containers",
                    inputSchema={"type": "object", "properties": {}}
                ),
                Tool(
                    name="read_config",
                    description="Read Kubernetes YAML configuration",
                    inputSchema={
                        "type": "object",
                        "properties": {
                            "path": {"type": "string"}
                        },
                        "required": ["path"]
                    }
                )
            ]
        
        @self.server.call_tool()
        async def call_tool(name: str, arguments: dict):
            if name == "list_containers":
                containers = self.docker_client.containers.list(all=True)
                result = [{
                    "id": c.id[:12],
                    "name": c.name,
                    "status": c.status,
                    "image": c.image.tags[0] if c.image.tags else "none"
                } for c in containers]
                return [TextContent(type="text", text=json.dumps(result, indent=2))]
            
            elif name == "read_config":
                path = Path(arguments["path"])
                if path.exists() and path.suffix in [".yaml", ".yml"]:
                    content = path.read_text()
                    return [TextContent(type="text", text=content)]
                return [TextContent(type="text", text="File not found or invalid format")]

Containerizing Your MCP Server

For production deployments, containerize your MCP server:

FROM python:3.11-slim

WORKDIR /app

RUN apt-get update && apt-get install -y \
    postgresql-client \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY server.py .

ENV PYTHONUNBUFFERED=1

CMD ["python3", "server.py"]
# Build and run
docker build -t claude-postgres-mcp .
docker run -e DATABASE_URL="postgresql://..." claude-postgres-mcp

Testing and Debugging MCP Servers

Using the MCP Inspector

# Install MCP Inspector
npm install -g @modelcontextprotocol/inspector

# Test your server
mcp-inspector python3 /path/to/server.py

Logging and Error Handling

Implement comprehensive logging:

import logging

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('/var/log/mcp-server.log'),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)

# Use in your handlers
logger.info(f"Executing query: {query}")
logger.error(f"Database error: {str(e)}")

Best Practices and Security Considerations

Security Hardening Checklist

  • Input Validation: Always sanitize and validate user inputs before executing operations
  • Least Privilege: Run MCP servers with minimal required permissions
  • Rate Limiting: Implement rate limiting to prevent abuse
  • Audit Logging: Log all operations for security auditing
  • Secrets Management: Use environment variables or secret managers, never hardcode credentials

Performance Optimization

# Implement connection pooling
from psycopg2 import pool

class OptimizedPostgresServer:
    def __init__(self, conn_string):
        self.connection_pool = pool.SimpleConnectionPool(
            minconn=1,
            maxconn=10,
            dsn=conn_string
        )
    
    async def execute_query(self, query: str):
        conn = self.connection_pool.getconn()
        try:
            cursor = conn.cursor()
            cursor.execute(query)
            results = cursor.fetchall()
            return results
        finally:
            self.connection_pool.putconn(conn)

Troubleshooting Common Issues

Server Not Appearing in Claude Desktop

  • Verify the configuration file path is correct
  • Check JSON syntax in claude_desktop_config.json
  • Ensure the Python/Node.js executable is in your PATH
  • Restart Claude Desktop after configuration changes

Connection Errors

# Test server independently
python3 server.py

# Check server logs
tail -f /var/log/mcp-server.log

# Verify network connectivity
telnet localhost 5432  # For database connections

Permission Issues

# Make server executable
chmod +x server.py

# Check file ownership
ls -la server.py

# Run with proper user context
sudo -u claude-user python3 server.py

Conclusion

Custom MCP servers unlock Claude Desktop’s full potential as a DevOps automation platform. By following this guide, you can build secure, performant integrations that connect Claude to your infrastructure, databases, and tools. The Model Context Protocol’s standardized approach ensures your extensions remain maintainable and scalable as your requirements evolve.

Start with simple resource and tool implementations, then gradually add complexity as you understand your use cases better. Remember to prioritize security, implement comprehensive logging, and thoroughly test your servers before production deployment.

The examples provided here serve as foundational templates—adapt them to your specific infrastructure needs and watch as Claude becomes an intelligent orchestrator across your entire DevOps ecosystem.

Have Queries? Join https://launchpass.com/collabnix

Collabnix Team The Collabnix Team is a diverse collective of Docker, Kubernetes, and IoT experts united by a passion for cloud-native technologies. With backgrounds spanning across DevOps, platform engineering, cloud architecture, and container orchestration, our contributors bring together decades of combined experience from various industries and technical domains.
Join our Discord Server
Index