Model Context Protocol, MCP Tutorial
Model Context Protocol, MCP Tutorial

Model Context Protocol (MCP) – The USB-C for AI | Tutorials Dojo

🔌 Model Context Protocol (MCP)

The USB-C for AI: Standardizing AI Agent Connections

Anthropic Open Source AI Agents

1. Introduction to MCP

Model Context Protocol (MCP) is an open protocol developed by Anthropic that standardizes how AI applications connect to data sources and tools. Think of it as the “USB-C” of AI – just as USB-C created a universal standard for device connections, MCP creates a universal standard for AI agent connections.

🎯 What is MCP?

MCP is an open-source protocol that enables seamless integration between Large Language Models (LLMs) and external data sources. It eliminates the need to write custom integrations for every data source and AI model combination.

🔄 Universal Standard

One protocol to connect any AI model to any data source

🔌 Plug & Play

Connect to databases, files, APIs without custom code

🔐 Secure

Built-in security and permission management

📈 Scalable

From local files to enterprise databases

2. Why MCP? The Problem It Solves

The Problem Before MCP

Before MCP, connecting AI models to data sources required:

  • Writing custom integration code for each data source
  • Maintaining separate connectors for different AI models
  • Complex authentication and security implementations
  • No standardization across different systems

Before MCP: The Integration Nightmare

AI Model A
Custom Code
Database 1


AI Model A
Custom Code
Database 2


AI Model B
Custom Code
Database 1

The MCP Solution

With MCP: Standardized Connections

Any AI Model
MCP Protocol
Any Data Source

✅ Benefits of MCP

  • Interoperability: Works with any LLM that supports MCP
  • Simplicity: No custom integration code needed
  • Reusability: Write once, use with any compatible AI model
  • Community-Driven: Growing ecosystem of pre-built servers

3. MCP Architecture

MCP follows a client-server architecture with three main components:

MCP Architecture Overview

MCP Host
(Claude Desktop, IDEs, etc.)
MCP Client
(Protocol Implementation)
MCP Server
(Database, Files, APIs)

Components Explained

Component Role Examples
MCP Host Application that wants to access data through MCP Claude Desktop, Custom AI Apps, IDEs
MCP Client Maintains 1:1 connections with MCP servers Protocol implementation in your app
MCP Server Exposes data and capabilities to clients Database connectors, File systems, APIs

Key Primitives

📚 Resources

Data that servers expose (files, database records, API responses)

🛠️ Tools

Functions that LLMs can invoke (queries, file operations, API calls)

💬 Prompts

Pre-defined templates for common interactions

🔔 Sampling

Servers can request LLM completions through the client

4. Core Concepts

Resources

Resources represent any kind of data that an MCP server wants to make available. Each resource is identified by a unique URI.

Example: File System Resources

{
  "resources": [
    {
      "uri": "file:///home/user/documents/report.pdf",
      "name": "Q4 Financial Report",
      "mimeType": "application/pdf",
      "description": "Quarterly financial analysis"
    },
    {
      "uri": "file:///home/user/data/sales.csv",
      "name": "Sales Data",
      "mimeType": "text/csv",
      "description": "2024 sales records"
    }
  ]
}

Tools

Tools are functions that the LLM can call to perform actions or retrieve information. Each tool has a defined schema describing its inputs.

Example: Database Query Tool

{
  "name": "query_database",
  "description": "Execute a SQL query on the database",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "SQL query to execute"
      },
      "database": {
        "type": "string",
        "description": "Database name"
      }
    },
    "required": ["query", "database"]
  }
}

Prompts

Prompts are reusable templates that help structure interactions with your data.

Example: Analysis Prompt Template

{
  "name": "analyze_sales",
  "description": "Analyze sales data for insights",
  "arguments": [
    {
      "name": "time_period",
      "description": "Period to analyze (e.g., 'Q4 2024')",
      "required": true
    },
    {
      "name": "region",
      "description": "Geographic region",
      "required": false
    }
  ]
}

5. Installation & Setup

Prerequisites

  • Node.js 18 or higher (for TypeScript/JavaScript servers)
  • Python 3.10 or higher (for Python servers)
  • Claude Desktop app (for testing)

Installing MCP SDK

For TypeScript/JavaScript:

# Using npm
npm install @modelcontextprotocol/sdk

# Using yarn
yarn add @modelcontextprotocol/sdk

For Python:

# Using pip
pip install mcp

# Using uv (recommended)
uv pip install mcp

Configuring Claude Desktop

To use MCP servers with Claude Desktop, edit the configuration file:

macOS:

~/Library/Application Support/Claude/claude_desktop_config.json

Windows:

%APPDATA%\Claude\claude_desktop_config.json

Example Configuration:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/username/Documents"
      ]
    },
    "postgres": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-postgres",
        "postgresql://localhost/mydb"
      ]
    }
  }
}

6. Practical Examples

Example 1: Simple File Server (TypeScript)

This example creates an MCP server that exposes text files from a directory:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListResourcesRequestSchema,
  ListToolsRequestSchema,
  ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import fs from "fs/promises";
import path from "path";

const DOCUMENTS_DIR = process.argv[2] || "./documents";

// Create server instance
const server = new Server(
  {
    name: "file-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      resources: {},
      tools: {},
    },
  }
);

// List available resources (files)
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  const files = await fs.readdir(DOCUMENTS_DIR);
  const resources = files
    .filter(file => file.endsWith('.txt'))
    .map(file => ({
      uri: `file:///${path.join(DOCUMENTS_DIR, file)}`,
      name: file,
      mimeType: "text/plain",
      description: `Text file: ${file}`,
    }));

  return { resources };
});

// Read resource content
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const url = new URL(request.params.uri);
  const filePath = url.pathname;
  
  const content = await fs.readFile(filePath, "utf-8");
  
  return {
    contents: [
      {
        uri: request.params.uri,
        mimeType: "text/plain",
        text: content,
      },
    ],
  };
});

// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "search_files",
        description: "Search for text in files",
        inputSchema: {
          type: "object",
          properties: {
            query: {
              type: "string",
              description: "Text to search for",
            },
          },
          required: ["query"],
        },
      },
    ],
  };
});

// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "search_files") {
    const query = request.params.arguments.query as string;
    const files = await fs.readdir(DOCUMENTS_DIR);
    const results = [];

    for (const file of files) {
      if (file.endsWith('.txt')) {
        const content = await fs.readFile(
          path.join(DOCUMENTS_DIR, file),
          "utf-8"
        );
        if (content.includes(query)) {
          results.push({
            file,
            matches: content.split('\n').filter(line => 
              line.includes(query)
            ),
          });
        }
      }
    }

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(results, null, 2),
        },
      ],
    };
  }

  throw new Error(`Unknown tool: ${request.params.name}`);
});

// Start the server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("File MCP server running on stdio");
}

main().catch(console.error);

Example 2: SQLite Database Server (Python)

This example creates an MCP server that allows querying a SQLite database:

import asyncio
import sqlite3
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import (
    Resource,
    Tool,
    TextContent,
    ImageContent,
    EmbeddedResource,
)

# Initialize database connection
DB_PATH = "example.db"

def init_database():
    """Initialize a sample database"""
    conn = sqlite3.connect(DB_PATH)
    cursor = conn.cursor()
    
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            email TEXT NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS orders (
            id INTEGER PRIMARY KEY,
            user_id INTEGER,
            product TEXT NOT NULL,
            amount DECIMAL(10, 2),
            order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id)
        )
    """)
    
    conn.commit()
    conn.close()

# Initialize the server
app = Server("sqlite-mcp-server")

@app.list_resources()
async def list_resources() -> list[Resource]:
    """List available database tables as resources"""
    conn = sqlite3.connect(DB_PATH)
    cursor = conn.cursor()
    
    cursor.execute(
        "SELECT name FROM sqlite_master WHERE type='table'"
    )
    tables = cursor.fetchall()
    conn.close()
    
    return [
        Resource(
            uri=f"sqlite:///{table[0]}",
            name=f"Table: {table[0]}",
            mimeType="application/json",
            description=f"Database table: {table[0]}",
        )
        for table in tables
    ]

@app.read_resource()
async def read_resource(uri: str) -> str:
    """Read all rows from a table"""
    table_name = uri.split("///")[1]
    
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    cursor.execute(f"SELECT * FROM {table_name}")
    rows = [dict(row) for row in cursor.fetchall()]
    conn.close()
    
    return str(rows)

@app.list_tools()
async def list_tools() -> list[Tool]:
    """List available database tools"""
    return [
        Tool(
            name="query_database",
            description="Execute a SELECT query on the database",
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "SQL SELECT query to execute",
                    }
                },
                "required": ["query"],
            },
        ),
        Tool(
            name="get_schema",
            description="Get the schema of a table",
            inputSchema={
                "type": "object",
                "properties": {
                    "table": {
                        "type": "string",
                        "description": "Table name",
                    }
                },
                "required": ["table"],
            },
        ),
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    """Handle tool calls"""
    
    if name == "query_database":
        query = arguments["query"]
        
        # Security: Only allow SELECT queries
        if not query.strip().upper().startswith("SELECT"):
            return [
                TextContent(
                    type="text",
                    text="Error: Only SELECT queries are allowed",
                )
            ]
        
        conn = sqlite3.connect(DB_PATH)
        conn.row_factory = sqlite3.Row
        cursor = conn.cursor()
        
        try:
            cursor.execute(query)
            rows = [dict(row) for row in cursor.fetchall()]
            result = str(rows)
        except sqlite3.Error as e:
            result = f"Database error: {str(e)}"
        finally:
            conn.close()
        
        return [TextContent(type="text", text=result)]
    
    elif name == "get_schema":
        table = arguments["table"]
        
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        
        cursor.execute(
            f"PRAGMA table_info({table})"
        )
        schema = cursor.fetchall()
        conn.close()
        
        schema_text = "\n".join(
            [f"{col[1]} {col[2]}" for col in schema]
        )
        
        return [
            TextContent(
                type="text",
                text=f"Schema for {table}:\n{schema_text}",
            )
        ]
    
    raise ValueError(f"Unknown tool: {name}")

async def main():
    """Run the MCP server"""
    init_database()
    
    async with stdio_server() as (read_stream, write_stream):
        await app.run(
            read_stream,
            write_stream,
            app.create_initialization_options(),
        )

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

Example 3: REST API Server (TypeScript)

This example creates an MCP server that connects to a REST API:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

const API_BASE_URL = "https://jsonplaceholder.typicode.com";

const server = new Server(
  {
    name: "api-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "get_posts",
        description: "Get blog posts from the API",
        inputSchema: {
          type: "object",
          properties: {
            userId: {
              type: "number",
              description: "Filter by user ID (optional)",
            },
            limit: {
              type: "number",
              description: "Maximum number of posts to return",
              default: 10,
            },
          },
        },
      },
      {
        name: "get_user",
        description: "Get user information by ID",
        inputSchema: {
          type: "object",
          properties: {
            userId: {
              type: "number",
              description: "User ID",
            },
          },
          required: ["userId"],
        },
      },
      {
        name: "create_post",
        description: "Create a new blog post",
        inputSchema: {
          type: "object",
          properties: {
            title: {
              type: "string",
              description: "Post title",
            },
            body: {
              type: "string",
              description: "Post content",
            },
            userId: {
              type: "number",
              description: "Author user ID",
            },
          },
          required: ["title", "body", "userId"],
        },
      },
    ],
  };
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  try {
    if (name === "get_posts") {
      let url = `${API_BASE_URL}/posts`;
      
      if (args.userId) {
        url += `?userId=${args.userId}`;
      }

      const response = await fetch(url);
      let posts = await response.json();

      if (args.limit) {
        posts = posts.slice(0, args.limit);
      }

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(posts, null, 2),
          },
        ],
      };
    }

    if (name === "get_user") {
      const response = await fetch(
        `${API_BASE_URL}/users/${args.userId}`
      );
      const user = await response.json();

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(user, null, 2),
          },
        ],
      };
    }

    if (name === "create_post") {
      const response = await fetch(`${API_BASE_URL}/posts`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          title: args.title,
          body: args.body,
          userId: args.userId,
        }),
      });

      const newPost = await response.json();

      return {
        content: [
          {
            type: "text",
            text: `Post created successfully!\n${JSON.stringify(
              newPost,
              null,
              2
            )}`,
          },
        ],
      };
    }

    throw new Error(`Unknown tool: ${name}`);
  } catch (error) {
    return {
      content: [
        {
          type: "text",
          text: `Error: ${error.message}`,
        },
      ],
      isError: true,
    };
  }
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("API MCP server running on stdio");
}

main().catch(console.error);

7. Real-World Use Cases

1. Enterprise Data Integration

Scenario: Financial Analysis System

Connect Claude to multiple enterprise databases to analyze financial data:

  • PostgreSQL for transaction records
  • MongoDB for customer data
  • Redis for real-time metrics
  • S3 for financial reports

Result: AI can query any data source seamlessly to provide comprehensive financial insights.

2. Development Tools Integration

Scenario: Code Review Assistant

Connect to development tools:

  • GitHub for code repositories
  • Jira for issue tracking
  • Confluence for documentation
  • Local file system for project files

Result: AI can review code, suggest improvements, and update documentation automatically.

3. Customer Support Automation

Scenario: Intelligent Support Bot

Connect to customer data sources:

  • CRM database (Salesforce, HubSpot)
  • Knowledge base articles
  • Support ticket history
  • Product documentation

Result: AI provides personalized support based on customer history and product knowledge.

4. Research and Analysis

Scenario: Research Assistant

Connect to research databases:

  • Academic paper databases
  • Patent databases
  • News APIs
  • Statistical databases

Result: AI can perform comprehensive research across multiple sources simultaneously.

5. Personal Productivity

Scenario: Personal AI Assistant

Connect to personal data:

  • Email (Gmail, Outlook)
  • Calendar
  • Note-taking apps (Notion, Evernote)
  • Task managers (Todoist, Asana)
  • Local documents and files

Result: AI manages your schedule, drafts emails, and organizes information seamlessly.

8. Best Practices

Security Considerations

⚠️ Important Security Practices

  • Validate all inputs: Always validate and sanitize user inputs before executing queries or operations
  • Use environment variables: Never hardcode credentials in your MCP servers
  • Implement least privilege: Grant only necessary permissions to MCP servers
  • Audit logging: Log all operations for security monitoring
  • Rate limiting: Implement rate limits to prevent abuse

Performance Optimization

  • Caching: Cache frequently accessed data to reduce database load
  • Pagination: Return large datasets in pages rather than all at once
  • Connection pooling: Reuse database connections efficiently
  • Async operations: Use async/await for I/O operations
  • Limit data transfer: Only return necessary fields and data

Error Handling

Example: Robust Error Handling

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  try {
    // Your tool logic here
    const result = await performOperation(request.params.arguments);
    
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  } catch (error) {
    console.error(`Error in ${request.params.name}:`, error);
    
    return {
      content: [
        {
          type: "text",
          text: `Error: ${error.message}\n\nPlease check your input and try again.`,
        },
      ],
      isError: true,
    };
  }
});

Documentation

  • Clear descriptions: Provide detailed descriptions for all tools and resources
  • Example usage: Include examples in your tool descriptions
  • Schema validation: Use JSON Schema to define clear input requirements
  • Error messages: Provide helpful error messages that guide users

Testing

Testing Your MCP Server

// Example test using Node.js
import { describe, it, expect } from 'vitest';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';

describe('MCP Server Tests', () => {
  it('should list available tools', async () => {
    const client = new Client({
      name: 'test-client',
      version: '1.0.0',
    });

    // Connect to your server
    await client.connect(transport);

    // List tools
    const tools = await client.listTools();
    
    expect(tools.tools.length).toBeGreaterThan(0);
    expect(tools.tools[0]).toHaveProperty('name');
    expect(tools.tools[0]).toHaveProperty('inputSchema');
  });

  it('should execute tool correctly', async () => {
    // Test tool execution
    const result = await client.callTool({
      name: 'query_database',
      arguments: {
        query: 'SELECT * FROM users LIMIT 1',
      },
    });

    expect(result.content).toBeDefined();
    expect(result.isError).toBeFalsy();
  });
});

Deployment Checklist

✅ Before Deploying Your MCP Server

  • ✓ All credentials stored securely in environment variables
  • ✓ Input validation implemented for all tools
  • ✓ Error handling in place for all operations
  • ✓ Logging configured for monitoring
  • ✓ Rate limiting implemented
  • ✓ Documentation complete
  • ✓ Tests passing
  • ✓ Performance tested with realistic data

📚 Additional Resources

  • Official Documentation: modelcontextprotocol.io
  • GitHub Repository: github.com/modelcontextprotocol
  • SDK Documentation: TypeScript and Python SDKs available
  • Community Servers: Browse pre-built MCP servers for common use cases
  • Discord Community: Join the MCP community for support and discussions

🚀 Getting Started

  1. Choose your programming language (TypeScript or Python)
  2. Install the MCP SDK
  3. Start with a simple example (file server or database)
  4. Configure Claude Desktop to use your server
  5. Test your integration
  6. Expand to more complex use cases

Leave a Reply

Your email address will not be published. Required fields are marked *