Hosts, Clients and Servers

The Three Roles

RoleWhat it isExample
Hostthe AI application the user works inClaude Desktop, Claude Code, an IDE, your FastAPI chatbot
Clienta connection inside the host, one per servercreated by the host when it starts a server
Servera program that exposes tools, resources and promptsGitHub MCP server, your ShopEase orders server

Transports

  • stdio: the host starts the server as a child process and talks over stdin/stdout. Used for local servers.
  • Streamable HTTP: the server runs as a web service; clients send HTTP requests (with optional streaming). Used for remote and shared servers, usually with OAuth.

Messages are JSON-RPC 2.0: each request has jsonrpc, id, method and params; the reply has the same id and a result or error.

Example

Example

A tiny in-process imitation of the MCP message flow: initialize, tools/list and tools/call. Real servers use the mcp SDK; this shows the shape of the messages.

import json

TOOLS = [{"name": "get_order_status",
          "description": "Get the status of a ShopEase order",
          "inputSchema": {"type": "object", "properties": {"order_id": {"type": "string"}},
                          "required": ["order_id"]}}]
ORDERS = {"SE1042": "delivered"}

def server(request):
    method, params = request["method"], request.get("params", {})
    if method == "initialize":
        result = {"protocolVersion": "2025-06-18", "serverInfo": {"name": "shopease", "version": "1.0"},
                  "capabilities": {"tools": {}}}
    elif method == "tools/list":
        result = {"tools": TOOLS}
    elif method == "tools/call":
        status = ORDERS.get(params["arguments"]["order_id"], "not found")
        result = {"content": [{"type": "text", "text": status}], "isError": False}
    else:
        return {"jsonrpc": "2.0", "id": request["id"], "error": {"code": -32601, "message": "Method not found"}}
    return {"jsonrpc": "2.0", "id": request["id"], "result": result}

calls = [("initialize", {}), ("tools/list", {}),
         ("tools/call", {"name": "get_order_status", "arguments": {"order_id": "SE1042"}}),
         ("orders/delete", {})]
for i, (method, params) in enumerate(calls, start=1):
    req = {"jsonrpc": "2.0", "id": i, "method": method, "params": params}
    resp = server(req)
    print("->", json.dumps(req)[:90])
    print("<-", json.dumps(resp)[:90])

Output

-> {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}
<- {"jsonrpc": "2.0", "id": 1, "result": {"protocolVersion": "2025-06-18", "serverInfo": {"na
-> {"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}
<- {"jsonrpc": "2.0", "id": 2, "result": {"tools": [{"name": "get_order_status", "description
-> {"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "get_order_status",
<- {"jsonrpc": "2.0", "id": 3, "result": {"content": [{"type": "text", "text": "delivered"}],
-> {"jsonrpc": "2.0", "id": 4, "method": "orders/delete", "params": {}}
<- {"jsonrpc": "2.0", "id": 4, "error": {"code": -32601, "message": "Method not found"}}