Ran Wei/ AI Agents/Module 9
中文
AI Agent Series — Ran Wei

Module 9: MCP — Building Your Own Servers

Building your own MCP servers.

1

What is MCP?

The Model Context Protocol (MCP) is an open standard created by Anthropic for connecting AI agents to external tools and data sources. Before MCP, every AI application had to build custom integrations for every tool — a Slack integration for one app, a completely different Slack integration for another. MCP standardises these connections so that a tool integration built once works with any MCP-compatible host.

Think of it as USB-C for AI. Just as USB-C provides a universal connector between devices and peripherals, MCP provides a universal protocol between AI agents and their tools. Anthropic donated MCP to the Linux Foundation in December 2025, and it has grown to over 97 million monthly SDK downloads across all languages.

MCP matters because it solves the N x M integration problem. Without MCP, N AI applications need M custom integrations each, yielding N*M total integrations. With MCP, each tool builds one server and each application builds one client, yielding N+M total integrations.

ANALOGY

Before USB, every printer, scanner, and keyboard had a different connector. You needed a specific cable and driver for each combination. USB unified this — any device works with any computer. MCP does the same for AI tools: build one MCP server for your database, and it works with Claude Desktop, VS Code, Cursor, and any other MCP host.

Without MCPWith MCP
Custom integration per app per toolOne server per tool, works everywhere
Different APIs, formats, auth methodsStandardised protocol and message format
Tight coupling between agent and toolsLoose coupling via protocol
Hard to share tools between projectsCommunity registry of reusable servers
2

Architecture

MCP follows a client-server architecture with three distinct roles. Understanding these roles is key to building and integrating MCP servers correctly.

MCP Host

The AI application the user interacts with. Examples: Claude Desktop, VS Code with Copilot, Cursor, or your own custom agent application. The host manages one or more MCP clients.

MCP Client

A protocol client (provided by the MCP SDK) that maintains a 1:1 connection with a single MCP server. The host creates one client per server it connects to.

MCP Server

A lightweight programme that exposes capabilities via the MCP protocol. Each server provides some combination of tools, resources, and prompts.

The communication flow is: User → Host → Client → Server → External System. The LLM inside the host decides which tools to call, the client sends the request to the appropriate server, the server executes the action and returns results, and the client passes them back to the host.

MCP Primitives

PrimitiveDescriptionControlExample
ToolsFunctions the model can call to take actionsModel-controlled (LLM decides when to call)Send email, query database, create file
ResourcesRead-only data the application can accessApplication-controlled (host decides when to read)File contents, database schemas, API docs
PromptsReusable prompt templates with argumentsUser-controlled (user selects which prompt)"Summarise this document", "Review this PR"
NOTE

MCP supports two transport mechanisms: stdio (standard input/output, for local servers) and SSE (Server-Sent Events over HTTP, for remote servers). Stdio is simpler and more common for local development; SSE enables hosting MCP servers as web services.

3

Building an MCP Server

The Python MCP SDK provides FastMCP, a high-level API that makes building servers as easy as writing decorated Python functions. Each decorated function becomes a tool, resource, or prompt that any MCP host can discover and use.

pip install mcp[cli]

A Complete Example Server

from mcp.server.fastmcp import FastMCP
import json
import sqlite3
from datetime import datetime

# Create the MCP server
mcp = FastMCP("Company Tools", version="1.0.0")

# --- TOOLS (model-controlled) ---

@mcp.tool()
def get_stock_price(symbol: str) -> str:
    """Get the current stock price for a given ticker symbol.

    Args:
        symbol: Stock ticker symbol, e.g., 'AAPL', 'GOOGL', 'MSFT'

    Returns:
        Current price and daily change as a formatted string.
    """
    # In production, call a real API like Alpha Vantage or Yahoo Finance
    prices = {
        "AAPL": {"price": 227.50, "change": +1.2},
        "GOOGL": {"price": 178.30, "change": -0.8},
        "MSFT": {"price": 445.20, "change": +2.1},
    }
    data = prices.get(symbol.upper())
    if not data:
        return f"Unknown symbol: {symbol}"
    sign = "+" if data["change"] >= 0 else ""
    return f"{symbol.upper()}: ${data['price']:.2f} ({sign}{data['change']}%)"

@mcp.tool()
def query_employees(department: str = "", role: str = "") -> str:
    """Search the employee directory by department and/or role.

    Args:
        department: Filter by department name (optional)
        role: Filter by job role (optional)

    Returns:
        JSON array of matching employees with name, role, department, email.
    """
    conn = sqlite3.connect("company.db")
    conn.row_factory = sqlite3.Row
    query = "SELECT name, role, department, email FROM employees WHERE 1=1"
    params = []
    if department:
        query += " AND department LIKE ?"
        params.append(f"%{department}%")
    if role:
        query += " AND role LIKE ?"
        params.append(f"%{role}%")
    rows = conn.execute(query, params).fetchall()
    conn.close()
    return json.dumps([dict(r) for r in rows], indent=2)

@mcp.tool()
def create_calendar_event(
    title: str, date: str, time: str, duration_minutes: int = 60
) -> str:
    """Create a new calendar event.

    Args:
        title: Event title
        date: Date in YYYY-MM-DD format
        time: Start time in HH:MM format (24-hour)
        duration_minutes: Duration in minutes (default: 60)
    """
    # In production, call Google Calendar or Outlook API
    return json.dumps({
        "status": "created",
        "event": {"title": title, "date": date, "time": time,
                  "duration": duration_minutes}
    })

# Run the server
if __name__ == "__main__":
    mcp.run(transport="stdio")
TIP

Write detailed docstrings for every tool. The MCP SDK extracts these as the tool description that the LLM reads. Include argument descriptions, expected formats, and return value documentation. The quality of your docstrings directly determines how well the LLM uses your tools.

PITFALL

MCP tools must return strings. If your function returns a dict or list, serialise it to JSON first. The MCP protocol communicates via text, so non-string return values will cause errors.

4

Resources and Prompts

Beyond tools, MCP servers expose resources (read-only data) and prompts (reusable templates). Resources let the host application browse and read data without the LLM deciding to do so. Prompts provide pre-built interaction patterns that users can trigger.

Resources

@mcp.resource("company://docs/{doc_name}")
def get_document(doc_name: str) -> str:
    """Read a company document by name.

    Exposes documents like:
      company://docs/vacation-policy
      company://docs/expense-guidelines
    """
    docs_dir = "./company_docs"
    path = f"{docs_dir}/{doc_name}.md"
    try:
        with open(path, "r") as f:
            return f.read()
    except FileNotFoundError:
        return f"Document '{doc_name}' not found."

@mcp.resource("company://metrics/dashboard")
def get_dashboard_metrics() -> str:
    """Get current company metrics dashboard data."""
    # In production, query your metrics database
    return json.dumps({
        "revenue_mtd": "$2.4M",
        "active_users": 15420,
        "uptime": "99.97%",
        "open_tickets": 23
    }, indent=2)

Prompts

from mcp.server.fastmcp import Context

@mcp.prompt()
def review_code(language: str, code: str) -> str:
    """Review code for bugs, style issues, and improvements."""
    return f"""Please review the following {language} code for:
1. Bugs and potential errors
2. Style and readability issues
3. Performance improvements
4. Security concerns

Code to review:
```{language}
{code}
```

Provide specific, actionable feedback with code examples for each issue found."""

@mcp.prompt()
def summarise_meeting(notes: str) -> str:
    """Summarise meeting notes into action items and decisions."""
    return f"""Analyse these meeting notes and produce:
1. A 2-3 sentence summary
2. Key decisions made
3. Action items with owners and deadlines

Meeting notes:
{notes}"""
NOTE

The difference between tools, resources, and prompts is about who controls them. Tools are model-controlled (the LLM decides to call them). Resources are application-controlled (the host reads them as needed). Prompts are user-controlled (the user selects them from a menu).

5

Connecting to Claude Desktop

Claude Desktop is the most common MCP host for testing and personal use. To connect your server, you edit the Claude Desktop configuration file and add your server definition.

Configuration File Location

Configuration Format

{
  "mcpServers": {
    "company-tools": {
      "command": "python",
      "args": ["/absolute/path/to/server.py"],
      "env": {
        "DATABASE_URL": "sqlite:///company.db",
        "API_KEY": "your-api-key-here"
      }
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxxx"
      }
    }
  }
}

After saving the configuration and restarting Claude Desktop, your tools will appear in the tools menu (the hammer icon). Claude will automatically discover all tools, resources, and prompts exposed by your server.

TIP

Use the MCP Inspector to debug your server before connecting it to Claude Desktop. Run mcp dev server.py to launch an interactive web UI that lets you test every tool, resource, and prompt directly.

Testing with the MCP Inspector

# Install the CLI tools
pip install "mcp[cli]"

# Launch the inspector (opens a web UI)
mcp dev server.py

# Or test from the command line
mcp run server.py
6

The MCP Ecosystem

The MCP ecosystem has grown rapidly since the protocol's release. There is a large library of pre-built servers for common integrations, SDKs in multiple languages, and a growing community of developers building and sharing servers.

Pre-Built Servers

GitHub, Slack, PostgreSQL, SQLite, filesystem, Google Drive, Brave Search, Puppeteer, and dozens more available from the official registry.

Multi-Language SDKs

Official SDKs for Python, TypeScript, Java, C#, Go, and Kotlin. Build MCP servers in your language of choice.

MCP Inspector

Built-in debugging tool. Interact with your server's tools, resources, and prompts through a web UI before connecting to a host.

Community Registry

Browse and share MCP servers at the official registry. Install community servers with a single command.

Pre-Built ServerCapabilitiesInstall
FilesystemRead, write, search files in allowed directoriesnpx @modelcontextprotocol/server-filesystem
GitHubRepos, issues, PRs, code searchnpx @modelcontextprotocol/server-github
PostgreSQLQuery databases, inspect schemasnpx @modelcontextprotocol/server-postgres
Brave SearchWeb search via Brave APInpx @modelcontextprotocol/server-brave-search
PuppeteerBrowser automation, screenshotsnpx @modelcontextprotocol/server-puppeteer
NOTE

When building production MCP servers, always validate inputs, handle errors gracefully, and consider security implications. An MCP server is essentially an API that an AI can call — apply the same security practices you would to any API (authentication, rate limiting, input sanitisation, principle of least privilege).

Up Next

Module 10 — LangChain & CrewAI