Chapter 34

MCP Protocol Internals: Host / Client / Server Triangular Architecture and JSON-RPC 2.0 Message Format

Chapter 34: MCP Protocol Deep Dive: Architecture, Transport Layer, and Capability Declaration

34.1 The Origins of MCP

Before MCP (Model Context Protocol) existed, every developer who wanted to give an AI model tool capabilities faced the same fragmentation problem. OpenAI had its own Function Calling format, Anthropic had its Tool Use specification, LangChain had its own Tool abstraction — and none of these were compatible with each other. A well-built database query tool that needed to support both ChatGPT and Claude required maintaining two entirely separate codebases.

In November 2024, Anthropic released MCP (Model Context Protocol): an open, vendor-neutral protocol standard designed to be the USB-C interface between AI models and external tools — a unified connection standard where tool developers implement once and every MCP-compatible AI client can use their work.

MCP's core design philosophy centers on separation of concerns:

34.2 Overall Architecture

34.2.1 Three-Layer Architecture

┌─────────────────────────────────────────┐
│           Host Application              │
│  (Claude Desktop / Claude Code / Custom)│
│                                         │
│  ┌──────────────┐  ┌──────────────┐    │
│  │  MCP Client  │  │  MCP Client  │    │
│  │  (Conn A)    │  │  (Conn B)    │    │
│  └──────┬───────┘  └──────┬───────┘    │
└─────────┼─────────────────┼─────────────┘
          │                 │
    MCP Protocol      MCP Protocol
    (stdio / HTTP)    (stdio / HTTP)
          │                 │
   ┌──────▼──────┐  ┌──────▼──────┐
   │  MCP Server │  │  MCP Server │
   │ (Filesystem)│  │ (Database)  │
   └─────────────┘  └─────────────┘

A Host can maintain multiple MCP Client connections simultaneously, with each Client connecting to a single MCP Server. The Claude model accesses Server capabilities through the Host's Client layer — the entire process is transparent to the model, which only sees an abstract tool-call interface.

34.2.2 Core Concept Reference

Concept Provided By Purpose
Tools MCP Server Executable functions the model can invoke
Resources MCP Server Readable data, like files or URLs
Prompts MCP Server Predefined prompt templates users can select
Roots MCP Client Tells Server the current working directory scope
Sampling MCP Client Server requests Host to invoke the LLM

34.3 Transport Layers

MCP defines two standard transport layers suited for different deployment scenarios.

34.3.1 stdio Transport (Local Process Communication)

stdio is the simplest and most commonly used transport, suited for scenarios where the Server and Client run on the same machine.

How it works:

Host Process
    │
    ├── stdin  →  [JSON-RPC Request]  →  MCP Server Process
    │
    └── stdout ←  [JSON-RPC Response] ←  MCP Server Process

stdio advantages:

stdio limitations:

Claude Desktop stdio configuration example (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/username/Documents"],
      "env": {}
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxxxxxxxxxx"
      }
    }
  }
}

34.3.2 HTTP + SSE Transport (Remote Service Communication)

HTTP + SSE (Server-Sent Events) is the transport layer designed for remote deployment, supporting separated Server and Client deployments — suitable for multi-user, cloud-based scenarios.

Communication model:

MCP Client                    MCP Server (remote)
    │                               │
    ├─── HTTP POST /message ────────▶│  Send request
    │                               │
    │◀── SSE /sse ──────────────────│  Receive responses/notifications (persistent)
    │                               │
    │     GET /sse  →  Establish SSE connection  │

HTTP+SSE advantages:

2025 Streamable HTTP addition: Anthropic's March 2025 specification update introduced Streamable HTTP as a more flexible HTTP transport variant, allowing multiple response messages to be returned in streaming fashion within a single HTTP request, while maintaining backward compatibility with SSE.

34.4 Message Format: JSON-RPC 2.0

MCP is built on the JSON-RPC 2.0 specification for message formatting. All messages are JSON objects in three categories:

34.4.1 Request

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "read_file",
    "arguments": {
      "path": "/home/user/document.txt"
    }
  }
}

34.4.2 Response

Successful response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "File contents..."
      }
    ],
    "isError": false
  }
}

Error response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32600,
    "message": "Invalid Request",
    "data": "File path does not exist"
  }
}

34.4.3 Notification

Notifications are one-way messages that require no response (no id field):

{
  "jsonrpc": "2.0",
  "method": "notifications/tools/list_changed",
  "params": {}
}

34.5 Capability Declaration: The Initialization Handshake

When an MCP connection is established, the Client and Server mutually declare their supported features through Capability Negotiation.

34.5.1 Initialization Flow

Client                          Server
  │                               │
  ├──── initialize ──────────────▶│
  │     (clientCapabilities)      │
  │                               │
  │◀─── initialize response ──────│
  │     (serverCapabilities)      │
  │                               │
  ├──── initialized ─────────────▶│
  │     (notification: init done) │
  │                               │
  │         [Normal operation]    │

34.5.2 Complete Initialization Messages

Client's initialize request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "roots": {
        "listChanged": true
      },
      "sampling": {}
    },
    "clientInfo": {
      "name": "claude-code",
      "version": "1.0.0"
    }
  }
}

Server's initialization response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": {
        "listChanged": true
      },
      "resources": {
        "subscribe": true,
        "listChanged": true
      },
      "prompts": {
        "listChanged": false
      },
      "logging": {}
    },
    "serverInfo": {
      "name": "my-filesystem-server",
      "version": "2.1.0"
    }
  }
}

34.6 Tools Capability in Detail

34.6.1 Tool List Query

// Request
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {}
}

// Response
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "read_file",
        "description": "Read the contents of a file at the specified path",
        "inputSchema": {
          "type": "object",
          "properties": {
            "path": {
              "type": "string",
              "description": "Absolute or relative file path"
            },
            "encoding": {
              "type": "string",
              "enum": ["utf-8", "base64"],
              "default": "utf-8"
            }
          },
          "required": ["path"]
        }
      },
      {
        "name": "write_file",
        "description": "Write content to a file at the specified path",
        "inputSchema": {
          "type": "object",
          "properties": {
            "path": {"type": "string"},
            "content": {"type": "string"},
            "create_dirs": {
              "type": "boolean",
              "default": false,
              "description": "Automatically create missing parent directories"
            }
          },
          "required": ["path", "content"]
        }
      }
    ]
  }
}

34.6.2 Tool Invocation

// Call request
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "read_file",
    "arguments": {
      "path": "/Users/alice/notes.md",
      "encoding": "utf-8"
    }
  }
}

// Success response
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "# My Notes\n\nToday I learned about the MCP protocol..."
      }
    ],
    "isError": false
  }
}

Tool response content arrays support multiple content types: text (plain text), image (base64-encoded image data), and resource (embedded resource references).

34.7 Resources Capability in Detail

Resources expose data from the MCP Server, functioning like a "virtual file system." Each Resource has a URI identifier, and Clients can read its contents.

34.7.1 Resource List Query

{
  "result": {
    "resources": [
      {
        "uri": "file:///home/user/project/README.md",
        "name": "Project README",
        "description": "Project documentation",
        "mimeType": "text/markdown"
      },
      {
        "uri": "db://myapp/users/schema",
        "name": "Users table schema",
        "description": "Complete DDL for the users table",
        "mimeType": "application/sql"
      }
    ]
  }
}

34.7.2 Resource Subscription and Change Notifications

If the Server declares resources.subscribe capability, Clients can subscribe to resource changes:

// Subscription request
{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "resources/subscribe",
  "params": { "uri": "file:///home/user/project/config.json" }
}

// Server-pushed change notification
{
  "jsonrpc": "2.0",
  "method": "notifications/resources/updated",
  "params": { "uri": "file:///home/user/project/config.json" }
}

34.8 Prompts Capability in Detail

Prompts are Server-predefined prompt templates that users can select and use directly in the Host application. This is a frequently overlooked but highly practical MCP capability.

// Prompt list response example
{
  "result": {
    "prompts": [
      {
        "name": "code_review",
        "description": "Perform a comprehensive code review of selected code",
        "arguments": [
          {"name": "code", "description": "Code to review", "required": true},
          {"name": "language", "description": "Programming language", "required": false}
        ]
      }
    ]
  }
}

When a Client calls prompts/get with argument values, the Server returns a fully assembled message array ready to be sent to the LLM — complete with the user's code interpolated into the template.

34.9 Sampling: Server-Initiated LLM Calls

Sampling is MCP's most advanced capability — it allows the MCP Server to request the Host to invoke the LLM. This implements a reverse call pattern: normally the Client calls the Server, but with Sampling the Server can initiate an LLM inference request back through the Client.

// Server sends sampling/createMessage request to Client
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "sampling/createMessage",
  "params": {
    "messages": [
      {
        "role": "user",
        "content": {
          "type": "text",
          "text": "Summarize the error patterns in this log: ERROR: connection timeout..."
        }
      }
    ],
    "modelPreferences": {
      "hints": [{"name": "claude-sonnet-4-5"}],
      "intelligencePriority": 0.8,
      "speedPriority": 0.5
    },
    "maxTokens": 500
  }
}

A typical Sampling use case: a filesystem Server that detects code errors can request the Host's LLM to generate fix suggestions, then return them as part of the tool call result to the user.

34.10 Protocol Versioning and Backward Compatibility

MCP uses date-based version identifiers (e.g., 2024-11-05). The current stable version is 2024-11-05, with an updated specification including Streamable HTTP support released in March 2025.

Version negotiation rules:

  1. Client declares its maximum supported protocol version in the initialize request
  2. Server responds with the protocol version it will use (no higher than what the Client declared)
  3. If versions are incompatible, the connection fails

When implementing an MCP Server or Client, use the official SDK rather than implementing the protocol manually — you get automatic version negotiation and future compatibility guarantees for free.


Summary

MCP provides a unified standard interface for the AI tool ecosystem through a clear three-layer architecture (Host/Client/Server) and two transport layers (stdio/HTTP+SSE). Its core design philosophy — separation of concerns, capability declaration, and protocol neutrality — means tool developers implement once and their work is immediately available across all MCP-compatible AI applications.

Key takeaways:

  1. stdio suits local tools; HTTP+SSE suits remote services
  2. The initialization handshake establishes mutual capability sets between both parties
  3. Three core capability types: Tools (executable), Resources (readable data), Prompts (templates)
  4. JSON-RPC 2.0 is the message foundation for all protocol communication
  5. Sampling enables the unusual but powerful pattern of Server-initiated LLM inference

The next chapter moves into hands-on practice: building a complete custom MCP Server from scratch.

Rate this chapter
4.5  / 5  (3 ratings)

💬 Comments