Chapter 37

MCP Protocol Deep Dive

Chapter 37: Deep Dive into the MCP Protocol

The Model Context Protocol (MCP) is an open protocol standard published by Anthropic in late 2024, designed to solve the fragmentation of AI system integration with external tools and data sources. Before MCP, every AI application needed custom integration code for every tool. MCP's arrival is analogous to what HTTP did for web communicationโ€”one protocol to connect all tools. This chapter dissects MCP's design principles, architecture, and complete message specification.


37.1 Design Goals and Background

Why MCP?

Before MCP (fragmented):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   AI App (Claude / GPT / Hermes)            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚Custom  โ”‚Custom  โ”‚Custom  โ”‚Custom  โ”‚ ...     โ”‚
โ”‚Search  โ”‚DB      โ”‚File    โ”‚API     โ”‚         โ”‚
โ”‚adapter โ”‚adapter โ”‚adapter โ”‚adapter โ”‚         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

After MCP (unified):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   AI App (Claude / GPT / Hermes)             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                      โ”‚ MCP Protocol (unified)
         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
         โ†“            โ†“            โ†“
   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚MCP Searchโ”‚ โ”‚MCP DB    โ”‚ โ”‚MCP File  โ”‚
   โ”‚Server    โ”‚ โ”‚Server    โ”‚ โ”‚Server    โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Three Core Design Goals

Goal Meaning Analogy
Standardization Unified tool-call format, eliminating fragmentation HTTP for web requests
Security Servers declare capabilities; clients request only what they need OAuth least-privilege principle
Interoperability Any MCP Client works with any MCP Server USB standard

Protocol Version History

Version Release Key Changes
0.1.0 2024-11 Initial release, basic Tool/Resource support
0.2.0 2024-12 Added Prompt type, improved error handling
1.0.0 2025-Q1 Stable release, SSE Transport added
1.1.0 2025-Q2 OAuth auth, batch request support

37.2 Client-Server Architecture

Core Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                MCP Host (e.g., Hermes Agent)        โ”‚
โ”‚                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      โ”‚
โ”‚  โ”‚   MCP Client 1   โ”‚    โ”‚   MCP Client 2   โ”‚      โ”‚
โ”‚  โ”‚ (one per server) โ”‚    โ”‚ (one per server) โ”‚      โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
            โ”‚ MCP Protocol         โ”‚ MCP Protocol
            โ†“                      โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   MCP Server 1       โ”‚  โ”‚   MCP Server 2       โ”‚
โ”‚   (database tools)   โ”‚  โ”‚   (filesystem tools) โ”‚
โ”‚                      โ”‚  โ”‚                      โ”‚
โ”‚   Tools:             โ”‚  โ”‚   Tools:             โ”‚
โ”‚   - query_db         โ”‚  โ”‚   - read_file        โ”‚
โ”‚   - write_db         โ”‚  โ”‚   - write_file       โ”‚
โ”‚                      โ”‚  โ”‚                      โ”‚
โ”‚   Resources:         โ”‚  โ”‚   Resources:         โ”‚
โ”‚   - db://tables      โ”‚  โ”‚   - file:///docs     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Key Role Definitions

MCP Host:
  - Creates and manages multiple MCP Client instances
  - Coordinates LLM inference and tool invocation
  - Manages the user-facing interface

MCP Client:
  - Maintains a 1:1 connection with a single MCP Server
  - Manages the protocol state machine
  - Forwards tool-call requests from the Host

MCP Server:
  - Provides specific Tools, Resources, or Prompts
  - Runs as an isolated process (security boundary)
  - Can use stdio, SSE, or HTTP transport

37.3 Transport Layer: Three Options

Transport 1: Stdio

The simplest optionโ€”the Client spawns the Server as a child process and communicates via stdin/stdout.

Client
  |โ”€โ”€ spawn โ”€โ”€โ†’ Server (child process)
  |โ”€โ”€ stdin  (JSON-RPC requests)  โ”€โ”€โ†’ Server
  |โ†โ”€ stdout (JSON-RPC responses) โ”€โ”€โ”€ Server
       stderr: Server logs (not part of protocol)
class StdioTransport:
    def __init__(self, command: list):
        import subprocess
        self.process = subprocess.Popen(
            command,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
    
    def send(self, message: dict):
        import json
        line = json.dumps(message) + "\n"
        self.process.stdin.write(line.encode())
        self.process.stdin.flush()
    
    def receive(self) -> dict:
        import json
        line = self.process.stdout.readline()
        return json.loads(line.decode())

Best for: Local development, CLI tool integration, process-level security isolation

Transport 2: SSE (Server-Sent Events)

The server pushes messages via HTTP SSE; the client sends requests via HTTP POST.

Client                        Server (HTTP service)
  |โ”€โ”€ GET /sse โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ |
  |   (establish SSE stream)  |
  |โ†โ”€ text/event-stream โ”€โ”€โ”€โ”€โ”€ |
  |                           |
  |โ”€โ”€ POST /message โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ |
  |   {"jsonrpc":"2.0",...}   |
  |โ†โ”€ 200 OK โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ |
  |โ†โ”€ SSE: {"result":...} โ”€โ”€โ”€ |

Best for: Remote Servers, web integration, server-initiated push events

Transport 3: Streamable HTTP (MCP 1.1+)

POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc":"2.0","method":"tools/call",...}

---

HTTP/1.1 200 OK
Content-Type: text/event-stream

data: {"jsonrpc":"2.0","result":...}

Transport Comparison

Feature Stdio SSE Streamable HTTP
Setup complexity Simple (local process) Medium (needs HTTP server) Medium
Network traversal No Yes Yes
Real-time push No Yes Yes
State management In-process Session-based Stateless
Security isolation Process-level Network-level Network-level
Best for Local tools Remote services Cloud APIs

37.4 Three Capability Types: Tool, Resource, Prompt

Capability 1: Tool

A Tool is an executable functionโ€”the LLM decides when and how to call it.

{
  "name": "search_database",
  "description": "Search records in the database by keyword",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "Search keyword"
      },
      "table": {
        "type": "string",
        "enum": ["users", "products", "orders"]
      },
      "limit": {
        "type": "integer",
        "default": 10
      }
    },
    "required": ["query", "table"]
  }
}

Capability 2: Resource

A Resource is read-only data or content the Server can provideโ€”the LLM includes it in context.

{
  "uri": "db://customers/recent",
  "name": "Recent customers",
  "description": "Customer records added in the past 30 days",
  "mimeType": "application/json"
}

Resource URIs support template syntax:

{
  "uriTemplate": "file:///documents/{category}/{filename}",
  "name": "Document resource",
  "description": "Access documents by category and filename"
}

Capability 3: Prompt

A Prompt is a predefined prompt template the Server offers to help the LLM use its capabilities optimally.

{
  "name": "analyze_sales_data",
  "description": "Optimal prompt for analyzing sales data and generating insight reports",
  "arguments": [
    {
      "name": "time_period",
      "description": "Analysis period (e.g., Q4 2024)",
      "required": true
    },
    {
      "name": "focus_area",
      "description": "Focus area (e.g., region, product line)",
      "required": false
    }
  ]
}

Capability Comparison

Type Semantics LLM Role Side Effects
Tool Executable function Decides when/how to call Yes (stateful)
Resource Read-only data Includes in context No
Prompt Prompt template Gets optimized prompt text No

37.5 Complete Message Specification (JSON-RPC 2.0)

MCP is built on JSON-RPC 2.0. All messages are JSON objects.

JSON-RPC 2.0 Message Types

// Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "method_name",
  "params": {}
}

// Success response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {}
}

// Error response
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32600,
    "message": "Invalid Request",
    "data": {}
  }
}

// Notification (no response expected โ€” no "id" field)
{
  "jsonrpc": "2.0",
  "method": "notifications/progress",
  "params": {}
}

Full Lifecycle Message Flow

Client                                   Server
  |โ”€โ”€ initialize โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ |
  |   {protocolVersion: "2024-11-05",     |
  |    capabilities: {...},               |
  |    clientInfo: {name:"hermes",...}}   |
  |                                       |
  |โ†โ”€ initialize result โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ |
  |   {protocolVersion: "2024-11-05",     |
  |    capabilities: {tools:{},           |
  |      resources:{}, prompts:{}},       |
  |    serverInfo: {name:"db-server"}}    |
  |                                       |
  |โ”€โ”€ initialized (notification) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ |
  |โ”€โ”€ tools/list โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ†’ |
  |โ†โ”€ tools/list result โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ |
  |โ”€โ”€ tools/call โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ†’ |
  |   {name:"search_database",            |
  |    arguments:{query:"...",table:"users"}} |
  |โ†โ”€ tools/call result โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ |
  |   {content:[{type:"text",text:"..."}],|
  |    isError: false}                    |

Complete Tool Call Request/Response

// Request
{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "name": "search_database",
    "arguments": {
      "query": "VIP customer",
      "table": "users",
      "limit": 5
    }
  }
}

// Success response
{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Found 3 VIP customer records:\n1. Alice (ID: 1001)\n2. Bob (ID: 1002)"
      },
      {
        "type": "resource",
        "resource": {
          "uri": "db://users/vip-list",
          "mimeType": "application/json",
          "text": "[{\"id\":1001,\"name\":\"Alice\",...}]"
        }
      }
    ],
    "isError": false
  }
}

// Tool execution error (protocol succeeded, tool failed)
{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "content": [
      {"type": "text", "text": "Database query failed: connection timeout. Please retry."}
    ],
    "isError": true
  }
}

Error Code Reference

Standard JSON-RPC error codes:
  -32700  Parse error        Invalid JSON received
  -32600  Invalid Request    Request not conforming to JSON-RPC spec
  -32601  Method not found   Method does not exist
  -32602  Invalid params     Invalid method parameters
  -32603  Internal error     Internal server error

MCP application-layer error codes:
  -32001  Resource not found
  -32002  Tool execution error
  -32003  Unauthorized
  -32004  Rate limit exceeded

37.6 MCP vs OpenAPI

Dimension MCP OpenAPI
Design goal AI-native tool invocation RESTful API documentation
Transport JSON-RPC 2.0 HTTP REST
Schema format JSON Schema (subset) JSON Schema (full)
Capability types Tool + Resource + Prompt Endpoint
State management Stateful (session) Stateless
Streaming Native (SSE) Requires extensions
AI integration First-class citizen Retrofitted
Ecosystem maturity Emerging (2024) Mature (2011)

Why Not Just Use OpenAPI?

OpenAPI limitations for LLM tool-calling:
1. Designed for human developers; LLMs struggle with complex REST semantics
2. Cannot express "what side effects does this tool have"
3. No mechanism for LLM-specific prompt templates
4. Inconsistent auth and capability discovery standards

MCP advantages:
1. Explicit semantics (Tool vs Resource vs Prompt each have clear roles)
2. Built-in capability discovery (all capabilities returned at initialize)
3. Designed for streaming LLM output (native SSE support)
4. LLM-friendly error handling with isError flag

37.7 MCP Integration in Hermes

# hermes/mcp/client.py
import asyncio
from typing import Any, Dict, List

class HermesMCPClient:
    def __init__(self, transport):
        self.transport = transport
        self._request_id = 0
        self._tools: List[Dict] = []
    
    async def initialize(self) -> Dict:
        result = await self._request("initialize", {
            "protocolVersion": "2024-11-05",
            "capabilities": {"roots": {"listChanged": True}, "sampling": {}},
            "clientInfo": {"name": "hermes-agent", "version": "1.7.0"}
        })
        await self._notify("notifications/initialized")
        tools_result = await self._request("tools/list")
        self._tools = tools_result.get("tools", [])
        return result
    
    async def call_tool(self, name: str, arguments: Dict) -> Dict:
        return await self._request("tools/call", {
            "name": name,
            "arguments": arguments
        })
    
    async def read_resource(self, uri: str) -> Dict:
        return await self._request("resources/read", {"uri": uri})
    
    async def _request(self, method: str, params: Dict = None) -> Any:
        request_id = self._next_id()
        message = {"jsonrpc": "2.0", "id": request_id, "method": method}
        if params:
            message["params"] = params
        result = await self.transport.send_and_receive(message)
        if "error" in result:
            raise MCPError(result["error"]["message"], result["error"]["code"])
        return result.get("result")
    
    async def _notify(self, method: str, params: Dict = None):
        message = {"jsonrpc": "2.0", "method": method}
        if params:
            message["params"] = params
        await self.transport.send(message)
    
    def _next_id(self) -> int:
        self._request_id += 1
        return self._request_id

class MCPError(Exception):
    def __init__(self, message: str, code: int):
        super().__init__(message)
        self.code = code

Chapter Summary

MCP provides a unified language for AI Agent integration with external tools through standardized JSON-RPC 2.0 messaging:

  1. Design goals: Standardization, security, and interoperability together solve AI tool integration fragmentation
  2. Client-Server architecture: Host manages multiple Clients; each Client connects to exactly one Serverโ€”clear separation of concerns
  3. Three transports: Stdio (local process), SSE (remote streaming), Streamable HTTP (stateless cloud)
  4. Three capability types: Tool (execute actions), Resource (provide data), Prompt (prompt templates)โ€”each with dedicated semantics
  5. JSON-RPC 2.0: Mature message format with unified error handling and clear request/response/notification types
  6. vs OpenAPI: MCP is AI-native by designโ€”not a replacement for OpenAPI but a complement for LLM tool-calling

Review Questions

  1. MCP's Stdio transport requires Server and Client to be on the same machine. How would you deploy a truly cross-node MCP Server in a Kubernetes cluster?
  2. MCP's initialize response returns all Tools. If a Server has 500 Tools, can an LLM's context window fit all their descriptions? How would you solve this?
  3. MCP messages use JSON encoding. For tools returning large binary data (images, PDFs), is JSON the optimal choice? What improvements would you propose?
  4. If two MCP Servers both provide a Tool named search, how should Hermes handle the conflict?
Rate this chapter
4.7  / 5  (3 ratings)

๐Ÿ’ฌ Comments