Skip to content
SDB
Agentic AI

Chapter 06 · intermediate · 30 min

MCP — Model Context Protocol

The open standard for connecting AI agents to tools, data, and context securely

Subhendu Datta BhowmikAI Tutorials

The Problem MCP Solves

Every AI framework has invented its own way to connect LLMs to external tools:

  • OpenAI has "Function Calling"
  • LangChain has its Tool class
  • CrewAI has BaseTool
  • AutoGen has its own tool registration

This means every tool integration must be rewritten for every framework. A Slack integration for LangChain doesn't work in AutoGen. A database connector for CrewAI can't be reused in your custom agent.

Model Context Protocol (MCP) is Anthropic's answer: an open protocol that standardizes how AI models connect to tools, data sources, and context — the same way HTTP standardized how clients and servers communicate on the web.

What Is MCP?

MCP defines a standard interface between:

  • MCP Clients — AI applications (Claude Desktop, your agent code)
  • MCP Servers — lightweight processes that expose capabilities

An MCP Server can expose three types of capabilities:

CapabilityDescriptionExample
ToolsFunctions the AI can callsearch_database, send_email
ResourcesData the AI can readFiles, database rows, API responses
PromptsReusable prompt templatesStructured workflows, system prompts

MCP Architecture

┌─────────────────────────────────────────────────────┐
│                   Your Application                   │
│                                                      │
│  ┌─────────────────┐      ┌──────────────────────┐  │
│  │   MCP Client    │<────>│    Claude / LLM      │  │
│  │  (your code or  │      │  (decides when to    │  │
│  │  Claude Desktop)│      │   call MCP tools)    │  │
│  └────────┬────────┘      └──────────────────────┘  │
│           │                                          │
└───────────│──────────────────────────────────────────┘
            │ JSON-RPC (stdio / HTTP / WebSocket)
            │
   ┌────────┴────────────────────────────────┐
   │              MCP Servers                │
   │                                         │
   │  ┌──────────┐  ┌──────────┐  ┌───────┐ │
   │  │  GitHub  │  │Postgres  │  │Slack  │ │
   │  │  Server  │  │  Server  │  │Server │ │
   │  └──────────┘  └──────────┘  └───────┘ │
   └─────────────────────────────────────────┘

The client manages connections to one or more servers. The LLM sees all server tools as if they were locally defined — but the implementation lives in isolated server processes.

Installation

pip install mcp          # MCP SDK for Python
npm install @modelcontextprotocol/sdk  # TypeScript SDK
Building an MCP Server in Pythonpython
# mcp_server.py
from mcp.server import Server
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from mcp import types
import asyncio
import sqlite3
import json

# Initialize the MCP server
app = Server("company-data-server")

# -------------------------------------------------------
# TOOLS — functions the AI can call
# -------------------------------------------------------
@app.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="query_employees",
            description=(
                "Query the employee database. Returns employee records matching "
                "the given filters. Use this to look up staff information."
            ),
            inputSchema={
                "type": "object",
                "properties": {
                    "department": {
                        "type": "string",
                        "description": "Filter by department (e.g. Engineering, Marketing)"
                    },
                    "min_years": {
                        "type": "integer",
                        "description": "Minimum years at company"
                    }
                },
            }
        ),
        types.Tool(
            name="send_notification",
            description="Send a Slack notification to a channel or user.",
            inputSchema={
                "type": "object",
                "properties": {
                    "channel": {"type": "string", "description": "Slack channel name"},
                    "message": {"type": "string", "description": "Message to send"},
                },
                "required": ["channel", "message"]
            }
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "query_employees":
        # Mock database query
        conn = sqlite3.connect(":memory:")
        # ... run query with arguments ...
        results = [{"name": "Alice", "dept": "Engineering", "years": 3}]
        return [types.TextContent(type="text", text=json.dumps(results, indent=2))]

    elif name == "send_notification":
        # Mock Slack call
        channel = arguments["channel"]
        message = arguments["message"]
        print(f"[Slack] #{channel}: {message}")
        return [types.TextContent(type="text", text=f"Notification sent to #{channel}")]

    raise ValueError(f"Unknown tool: {name}")

# -------------------------------------------------------
# RESOURCES — data the AI can read
# -------------------------------------------------------
@app.list_resources()
async def list_resources() -> list[types.Resource]:
    return [
        types.Resource(
            uri="company://docs/onboarding",
            name="Onboarding Guide",
            description="Employee onboarding documentation",
            mimeType="text/markdown",
        ),
        types.Resource(
            uri="company://policies/expense",
            name="Expense Policy",
            description="Company expense reimbursement policy",
            mimeType="text/markdown",
        )
    ]

@app.read_resource()
async def read_resource(uri: str) -> str:
    if uri == "company://docs/onboarding":
        return "# Onboarding Guide\n\nWelcome to the team!..."
    elif uri == "company://policies/expense":
        return "# Expense Policy\n\nReimbursable expenses include..."
    raise ValueError(f"Unknown resource: {uri}")

# -------------------------------------------------------
# Run the server over stdio transport
# -------------------------------------------------------
async def main():
    async with stdio_server() as (read_stream, write_stream):
        await app.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="company-data-server",
                server_version="1.0.0",
                capabilities=app.get_capabilities(
                    notification_options=None,
                    experimental_capabilities={}
                )
            )
        )

if __name__ == "__main__":
    asyncio.run(main())
Connecting Claude as an MCP Clientpython
# mcp_client.py
import asyncio
from anthropic import Anthropic
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

client = Anthropic()

async def run_agent_with_mcp(user_message: str) -> str:
    """Run Claude as an agent connected to an MCP server."""

    # Connect to the MCP server via stdio
    server_params = StdioServerParameters(
        command="python",
        args=["mcp_server.py"],
    )

    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            # Initialize the connection
            await session.initialize()

            # Discover available tools from the server
            tools_response = await session.list_tools()
            tools = [
                {
                    "name": t.name,
                    "description": t.description,
                    "input_schema": t.inputSchema,
                }
                for t in tools_response.tools
            ]
            print(f"Connected to MCP server. Available tools: {[t['name'] for t in tools]}")

            # Run the agentic loop
            messages = [{"role": "user", "content": user_message}]

            while True:
                response = client.messages.create(
                    model="claude-sonnet-4-6",
                    max_tokens=4096,
                    tools=tools,
                    messages=messages,
                )

                if response.stop_reason == "end_turn":
                    for block in response.content:
                        if hasattr(block, "text"):
                            return block.text

                # Execute tool calls via MCP
                messages.append({"role": "assistant", "content": response.content})
                tool_results = []

                for block in response.content:
                    if block.type == "tool_use":
                        # Call the tool on the MCP server
                        result = await session.call_tool(block.name, block.input)
                        tool_results.append({
                            "type": "tool_result",
                            "tool_use_id": block.id,
                            "content": result.content[0].text if result.content else "",
                        })

                if tool_results:
                    messages.append({"role": "user", "content": tool_results})

async def main():
    answer = await run_agent_with_mcp(
        "List all engineers with more than 2 years at the company, "
        "then send a Slack notification to #general about the team."
    )
    print(answer)

asyncio.run(main())

Configuring MCP in Claude Desktop

Claude Desktop has built-in MCP support. Add servers to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "company-data": {
      "command": "python",
      "args": ["/path/to/mcp_server.py"]
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you/Documents"]
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxx"
      }
    }
  }
}

Claude Desktop will then show available tools from all connected servers in the UI.

Pre-Built MCP Servers

The MCP ecosystem is growing rapidly. Official servers from Anthropic include:

ServerWhat it provides
@modelcontextprotocol/server-filesystemRead/write local files
@modelcontextprotocol/server-githubGitHub repos, issues, PRs
@modelcontextprotocol/server-postgresQuery PostgreSQL databases
@modelcontextprotocol/server-slackSlack messages and channels
@modelcontextprotocol/server-google-mapsGeocoding and directions
@modelcontextprotocol/server-brave-searchWeb search via Brave

Knowledge check

What is the main advantage of MCP over framework-specific tool implementations?

Summary

MCP brings standardization to the agent tool ecosystem:

  1. Client-server architecture — agents connect to tool servers via a standard protocol
  2. Three primitives — Tools (functions), Resources (data), Prompts (templates)
  3. Any client, any server — write once, use everywhere
  4. Growing ecosystem — dozens of pre-built servers for common services
  5. Process isolation — security through separation of concerns

In the next chapter, we'll survey real-world agentic AI use cases — how production systems across industries are deploying agents today.

Agentic AI