What is MCP?

The Model Context Protocol (MCP) is an open standard that enables seamless integration between AI applications and external tools. It provides a unified way for AI models to interact with databases, APIs, and local services through a standardized protocol.

MCP Architecture Overview

🤖
AI Application

Claude, VS Code, etc.

🔌
MCP Client

Protocol Handler

⚙️
MCP Server

Tool Provider

🛠️
Resources

DBs, APIs, Files

🔄 Standardized Protocol

JSON-RPC 2.0 based communication for reliable message passing between clients and servers.

🛠️ Tool Discovery

Automatic discovery and registration of available tools and their capabilities.

🔒 Security First

Built-in authentication, authorization, and sandboxing for safe tool execution.

📊 Resource Management

Efficient handling of files, databases, and API connections with proper lifecycle management.

Core Concepts

1. MCP Servers

MCP servers expose tools and resources to AI applications. They handle requests and execute actions on behalf of the AI.

// Basic MCP Server Structure (TypeScript)
import { Server } from '@modelcontextprotocol/sdk';

class MyMCPServer {
    private server: Server;
    
    constructor() {
        this.server = new Server({
            name: "my-tools-server",
            version: "1.0.0",
            capabilities: {
                tools: true,
                resources: true,
                prompts: true
            }
        });
        
        this.registerTools();
        this.registerResources();
    }
    
    private registerTools() {
        // Register a tool for web searching
        this.server.registerTool({
            name: "web_search",
            description: "Search the web for information",
            inputSchema: {
                type: "object",
                properties: {
                    query: {
                        type: "string",
                        description: "Search query"
                    },
                    limit: {
                        type: "number",
                        description: "Number of results",
                        default: 5
                    }
                },
                required: ["query"]
            },
            handler: async (params) => {
                const results = await this.performWebSearch(params.query, params.limit);
                return {
                    results: results
                };
            }
        });
        
        // Register a database query tool
        this.server.registerTool({
            name: "query_database",
            description: "Execute SQL queries on the database",
            inputSchema: {
                type: "object",
                properties: {
                    query: {
                        type: "string",
                        description: "SQL query to execute"
                    },
                    database: {
                        type: "string",
                        description: "Database name",
                        default: "main"
                    }
                },
                required: ["query"]
            },
            handler: async (params) => {
                return await this.executeSQLQuery(params.query, params.database);
            }
        });
    }
    
    private registerResources() {
        // Register file resources
        this.server.registerResource({
            uri: "file:///workspace",
            name: "Workspace Files",
            description: "Access to project files",
            mimeType: "text/plain",
            handler: async (uri) => {
                return await this.readFile(uri);
            }
        });
    }
    
    async start(port: number = 3000) {
        await this.server.listen(port);
        console.log(`MCP Server running on port ${port}`);
    }
}

// Start the server
const server = new MyMCPServer();
server.start();
                

2. MCP Clients

Clients connect to MCP servers and invoke their tools. They handle the protocol communication and response processing.

// MCP Client Implementation
import { Client } from '@modelcontextprotocol/sdk';

class MCPClient {
    private client: Client;
    
    async connect(serverUrl: string) {
        this.client = new Client();
        await this.client.connect(serverUrl);
        
        // Discover available tools
        const tools = await this.client.listTools();
        console.log("Available tools:", tools);
        
        // Discover available resources
        const resources = await this.client.listResources();
        console.log("Available resources:", resources);
    }
    
    async callTool(toolName: string, params: any) {
        try {
            const result = await this.client.callTool({
                name: toolName,
                arguments: params
            });
            return result;
        } catch (error) {
            console.error(`Tool execution failed: ${error}`);
            throw error;
        }
    }
    
    async readResource(uri: string) {
        try {
            const content = await this.client.readResource(uri);
            return content;
        } catch (error) {
            console.error(`Resource read failed: ${error}`);
            throw error;
        }
    }
}

// Usage example
const client = new MCPClient();
await client.connect("ws://localhost:3000");

// Use web search tool
const searchResults = await client.callTool("web_search", {
    query: "latest AI developments",
    limit: 3
});

// Query database
const dbResults = await client.callTool("query_database", {
    query: "SELECT * FROM users WHERE active = true"
});
                

3. Protocol Messages

Request: tools/list
{
    "jsonrpc": "2.0",
    "method": "tools/list",
    "params": {},
    "id": "1"
}
                    
Response: tools/list
{
    "jsonrpc": "2.0",
    "result": {
        "tools": [
            {
                "name": "web_search",
                "description": "Search the web for information",
                "inputSchema": {...}
            },
            {
                "name": "query_database",
                "description": "Execute SQL queries",
                "inputSchema": {...}
            }
        ]
    },
    "id": "1"
}
                    
Request: tools/call
{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
        "name": "web_search",
        "arguments": {
            "query": "MCP protocol documentation",
            "limit": 5
        }
    },
    "id": "2"
}
                    

Building Your First MCP Server

Step 1: Setup

# Initialize project
mkdir my-mcp-server
cd my-mcp-server
npm init -y

# Install MCP SDK
npm install @modelcontextprotocol/sdk

# Install development dependencies
npm install --save-dev typescript @types/node tsx

# Create TypeScript config
cat > tsconfig.json << EOF
{
    "compilerOptions": {
        "target": "ES2022",
        "module": "commonjs",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,
        "outDir": "./dist"
    }
}
EOF
                

Step 2: Implement Server

// src/server.ts
import { Server, Tool, Resource } from '@modelcontextprotocol/sdk';
import * as fs from 'fs/promises';
import * as path from 'path';

class FileSystemMCPServer {
    private server: Server;
    private rootPath: string;
    
    constructor(rootPath: string = process.cwd()) {
        this.rootPath = rootPath;
        this.server = new Server({
            name: "filesystem-server",
            version: "1.0.0",
            description: "MCP server for file system operations"
        });
        
        this.setupTools();
        this.setupResources();
    }
    
    private setupTools() {
        // List files tool
        this.server.registerTool({
            name: "list_files",
            description: "List files in a directory",
            inputSchema: {
                type: "object",
                properties: {
                    path: {
                        type: "string",
                        description: "Directory path"
                    }
                },
                required: ["path"]
            },
            handler: async ({ path: dirPath }) => {
                const fullPath = path.join(this.rootPath, dirPath);
                const files = await fs.readdir(fullPath);
                
                const fileInfo = await Promise.all(
                    files.map(async (file) => {
                        const filePath = path.join(fullPath, file);
                        const stat = await fs.stat(filePath);
                        return {
                            name: file,
                            type: stat.isDirectory() ? "directory" : "file",
                            size: stat.size,
                            modified: stat.mtime
                        };
                    })
                );
                
                return { files: fileInfo };
            }
        });
        
        // Read file tool
        this.server.registerTool({
            name: "read_file",
            description: "Read contents of a file",
            inputSchema: {
                type: "object",
                properties: {
                    path: {
                        type: "string",
                        description: "File path"
                    }
                },
                required: ["path"]
            },
            handler: async ({ path: filePath }) => {
                const fullPath = path.join(this.rootPath, filePath);
                const content = await fs.readFile(fullPath, 'utf-8');
                return { content };
            }
        });
        
        // Write file tool
        this.server.registerTool({
            name: "write_file",
            description: "Write content to a file",
            inputSchema: {
                type: "object",
                properties: {
                    path: {
                        type: "string",
                        description: "File path"
                    },
                    content: {
                        type: "string",
                        description: "File content"
                    }
                },
                required: ["path", "content"]
            },
            handler: async ({ path: filePath, content }) => {
                const fullPath = path.join(this.rootPath, filePath);
                await fs.writeFile(fullPath, content, 'utf-8');
                return { success: true, path: filePath };
            }
        });
    }
    
    private setupResources() {
        // Register workspace as a resource
        this.server.registerResource({
            uri: `file://${this.rootPath}`,
            name: "Workspace",
            description: "The workspace root directory",
            mimeType: "inode/directory"
        });
    }
    
    async start() {
        const port = process.env.MCP_PORT || 3000;
        await this.server.listen(port);
        console.log(`File System MCP Server running on port ${port}`);
    }
}

// Start server
const server = new FileSystemMCPServer();
server.start().catch(console.error);
                

Step 3: Configure for Claude Desktop

# Claude Desktop configuration (~/Library/Application Support/Claude/claude_desktop_config.json)
{
    "mcpServers": {
        "filesystem": {
            "command": "node",
            "args": ["/path/to/your/mcp-server/dist/server.js"],
            "env": {
                "MCP_PORT": "3000"
            }
        },
        "database": {
            "command": "python",
            "args": ["/path/to/database-server.py"],
            "env": {
                "DATABASE_URL": "postgresql://localhost/mydb"
            }
        }
    }
}
                

MCP Capabilities

Capability Description Use Cases
Tools Execute functions and actions API calls, calculations, data processing
Resources Access and manage data Files, databases, remote content
Prompts Reusable prompt templates Common queries, structured outputs
Sampling LLM text generation Content creation, completions
Roots Project root directories Workspace management, file navigation

Advanced MCP Features

Authentication & Security

// Implementing authentication in MCP server
class SecureMCPServer {
    private server: Server;
    private validTokens: Set;
    
    constructor() {
        this.validTokens = new Set(process.env.VALID_TOKENS?.split(',') || []);
        
        this.server = new Server({
            name: "secure-server",
            version: "1.0.0",
            authenticate: async (token: string) => {
                // Validate token
                if (!this.validTokens.has(token)) {
                    throw new Error("Invalid authentication token");
                }
                return true;
            }
        });
    }
    
    // Rate limiting implementation
    private rateLimiter = new Map();
    
    private checkRateLimit(clientId: string): boolean {
        const now = Date.now();
        const windowMs = 60000; // 1 minute window
        const maxRequests = 100;
        
        if (!this.rateLimiter.has(clientId)) {
            this.rateLimiter.set(clientId, []);
        }
        
        const requests = this.rateLimiter.get(clientId)!;
        const recentRequests = requests.filter(time => now - time < windowMs);
        
        if (recentRequests.length >= maxRequests) {
            return false;
        }
        
        recentRequests.push(now);
        this.rateLimiter.set(clientId, recentRequests);
        return true;
    }
}
                

Resource Streaming

// Streaming large resources
this.server.registerResource({
    uri: "stream://large-file",
    name: "Large Dataset",
    description: "Stream large dataset in chunks",
    handler: async function* (uri: string) {
        const chunkSize = 1024 * 1024; // 1MB chunks
        const fileStream = fs.createReadStream('large-file.dat', {
            highWaterMark: chunkSize
        });
        
        for await (const chunk of fileStream) {
            yield {
                data: chunk.toString('base64'),
                encoding: 'base64',
                done: false
            };
        }
        
        yield { done: true };
    }
});
                

Error Handling

// Comprehensive error handling
this.server.registerTool({
    name: "safe_operation",
    description: "Operation with proper error handling",
    handler: async (params) => {
        try {
            // Validate input
            if (!params.input || typeof params.input !== 'string') {
                return {
                    error: {
                        code: "INVALID_INPUT",
                        message: "Input must be a non-empty string"
                    }
                };
            }
            
            // Perform operation
            const result = await this.performOperation(params.input);
            
            return {
                success: true,
                result: result
            };
            
        } catch (error) {
            // Log error for debugging
            console.error('Operation failed:', error);
            
            // Return structured error
            return {
                error: {
                    code: error.code || "OPERATION_FAILED",
                    message: error.message || "An unexpected error occurred",
                    details: process.env.NODE_ENV === 'development' ? error.stack : undefined
                }
            };
        }
    }
});
                

MCP Ecosystem

📱 Claude Desktop

Native integration with Claude Desktop app for local tool access.

💻 VS Code

Use MCP servers in VS Code with Cline and Continue extensions.

🌐 Web Applications

Integrate MCP servers with web-based AI applications.

🤖 Custom Clients

Build your own MCP clients for specific use cases.

Best Practices:
  • Always validate input parameters before processing
  • Implement proper error handling and logging
  • Use rate limiting to prevent abuse
  • Keep sensitive data secure with encryption
  • Version your MCP servers for compatibility
  • Document all tools and their parameters clearly
  • Test thoroughly with different clients