Module 2: Host-Client-Server Architecture

Complete Request-Response Flow + Mini-Project

Complete Request-Response Flow + Mini-Project

Capsule description

In the 3 previous capsules you learned each layer separately: the Host orchestrates, the Client communicates, the Server provides. Now you're going to bring it all together. In this capsule you're going to trace complete requests from the moment you type something in Claude Code until you receive the response, passing through each layer with the real JSON-RPC messages that travel between them.

Tracing complete flows is the most practical skill of this module. When you build MCP servers in modules 4-6, every bug will have a location in this flow. When something doesn't work, you'll be able to say "the problem is between the Client and the Server in the tools/call phase" instead of "I don't know why it doesn't work." And when you design your capstone project server (module 8), you'll know exactly what each layer expects from the others.

At the end of this capsule you'll complete the module's mini-project: diagramming the Host-Client-Server architecture of your current Claude Code setup.


Flow 1: The simplest request

Scenario: "What files are in my project?"

We start with the most basic flow — a user asks a question that requires a single tool from a single server.

COMPLETE FLOW — Simple request

User → Claude Code:
"What files are in my project?"

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│  ① USER                                                        │
│     "What files are in my project?"                             │
│     │                                                           │
│     ▼                                                           │
│  ② HOST (Claude Code)                                           │
│     Claude (model) analyzes the request:                        │
│     → Needs to list the filesystem's files                      │
│     → Has the "list_directory" tool available                   │
│     → Decides to use: filesystem.list_directory                 │
│     │                                                           │
│     ▼                                                           │
│  ③ CLIENT (Filesystem Server's MCP Client)                      │
│     Sends JSON-RPC:                                             │
│     { "method": "tools/call",                                   │
│       "params": { "name": "list_directory",                     │
│                   "arguments": { "path": "/Users/dev/project" } │
│       }, "id": 5 }                                              │
│     │                                                           │
│     ▼                                                           │
│  ④ SERVER (Filesystem MCP Server)                               │
│     Receives request → Executes fs.readdir("/Users/dev/project")│
│     Result: ["src/", "package.json", "README.md", "test/"]      │
│     │                                                           │
│     ▼                                                           │
│  ⑤ SERVER responds to the CLIENT                                │
│     { "result": { "content": [{ "type": "text",                │
│       "text": "src/\npackage.json\nREADME.md\ntest/" }] },     │
│       "id": 5 }                                                 │
│     │                                                           │
│     ▼                                                           │
│  ⑥ CLIENT delivers to the HOST                                  │
│     Passes the result to the Host                               │
│     │                                                           │
│     ▼                                                           │
│  ⑦ HOST presents to the USER                                   │
│     "Your project has these files:                              │
│      📁 src/                                                    │
│      📄 package.json                                            │
│      📄 README.md                                               │
│      📁 test/"                                                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

The 7 steps in detail

StepWhoDoes whatData that travels
①UserTypes the request in the terminalNatural text
②HostClaude analyzes and decides which tool to useInternal decision
③ClientSends a JSON-RPC request to the ServerJSON-RPC request
④ServerExecutes the actual operationFilesystem operation
⑤ServerReturns the result to the ClientJSON-RPC response
⑥ClientPasses the result to the HostInternal data
⑦HostFormats and presents to the userFormatted text

Total time: Milliseconds. The slowest step is ④ (the actual operation — in this case, reading the filesystem).


Flow 2: Request with multiple Servers

Scenario: "Read my README and create an issue in GitHub with a summary"

This flow involves 2 MCP Servers in sequence.

COMPLETE FLOW — Multi-server request

User: "Read my README.md and create an issue in GitHub with a summary"

① USER → request

② HOST analyzes:
   → Step A: Needs to read README.md → Filesystem Server
   → Step B: Needs to create an issue → GitHub Server
   → Orchestrates in sequence (B depends on A)

③-A CLIENT 1 → Filesystem Server:
    { "method": "tools/call",
      "params": { "name": "read_file",
                  "arguments": { "path": "README.md" } },
      "id": 10 }

④-A Filesystem Server executes → reads README.md

⑤-A Filesystem Server responds:
    { "result": { "content": [{ "type": "text",
      "text": "# My Project\nA management app..." }] },
      "id": 10 }

⑥-A CLIENT 1 → HOST: delivers the README's content

② HOST processes the content:
   → Claude generates a summary of the README
   → Prepares the arguments to create the issue

③-B CLIENT 2 → GitHub Server:
    { "method": "tools/call",
      "params": { "name": "create_issue",
                  "arguments": {
                    "repo": "my-user/my-project",
                    "title": "README Summary",
                    "body": "## Summary\nThis management app..."
                  } },
      "id": 11 }

④-B GitHub Server executes → POST https://api.github.com/repos/.../issues

⑤-B GitHub Server responds:
    { "result": { "content": [{ "type": "text",
      "text": "Issue #42 created successfully" }] },
      "id": 11 }

⑥-B CLIENT 2 → HOST: delivers the confirmation

⑦ HOST → USER:
   "I read your README.md and created issue #42 in GitHub with a summary
    of the content. You can see it at github.com/my-user/my-project/issues/42"

Timeline sequence diagram

Time →

User       Host         Client1      FileSrv      Client2      GitHubSrv
  │         │              │            │             │             │
  │─"Read   │              │            │             │             │
  │ README  │              │            │             │             │
  │ and     │              │            │             │             │
  │ create  │              │            │             │             │
  │ issue"─▶│              │            │             │             │
  │         │──tools/call─▶│            │             │             │
  │         │  read_file   │──request──▶│             │             │
  │         │              │            │──reads fs── │             │
  │         │              │            │          │  │             │
  │         │              │◀─response──│◀─────────   │             │
  │         │◀─result──────│            │             │             │
  │         │                                         │             │
  │         │──generates summary (Claude)──           │             │
  │         │                                         │             │
  │         │──tools/call────────────────────────────▶│             │
  │         │  create_issue                           │──request──▶│
  │         │                                         │            │──POST─▶
  │         │                                         │            │  GitHub
  │         │                                         │            │  API
  │         │                                         │            │◀─200──
  │         │                                         │◀─response──│
  │         │◀─result─────────────────────────────────│             │
  │         │                                         │             │
  │◀─"I     │                                         │             │
  │  created│                                         │             │
  │  issue  │                                         │             │
  │  #42"───│                                         │             │

Key observations:

  • The Host orchestrates the sequence — Client1 and Client2 don't know each other
  • Client1 and Client2 are independent — they operate with different Servers
  • The Host (Claude) processes between the two calls: it generates the summary
  • The JSON-RPC ids are different for each request (10, 11)

Flow 3: The complete lifecycle (from startup)

Scenario: Claude Code starts up, connects with a server, and serves a request

This flow shows everything — from when Claude Code starts until it responds to the user.

COMPLETE LIFECYCLE

═══════════════════════════════════════════
PHASE 1: STARTUP (Claude Code starts up)
═══════════════════════════════════════════

Host reads configuration:
├── ~/.claude/settings.json
│   { "mcpServers": { "filesystem": { "command": "npx", "args": [...] } } }
│
Host launches process:
├── $ npx -y @modelcontextprotocol/server-filesystem /Users/dev/project
│   └── Process started (PID: 12345)
│
Host creates an MCP Client for this Server

═══════════════════════════════════════════
PHASE 2: INITIALIZE (handshake)
═══════════════════════════════════════════

Client → Server (via stdin):
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-26",
    "capabilities": { "roots": { "listChanged": true } },
    "clientInfo": { "name": "claude-code", "version": "1.0.0" }
  }
}

Server → Client (via stdout):
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": { "tools": { "listChanged": true } },
    "serverInfo": { "name": "filesystem-server", "version": "0.5.0" }
  }
}

Client → Server (notification):
{ "jsonrpc": "2.0", "method": "notifications/initialized" }

═══════════════════════════════════════════
PHASE 3: DISCOVER (discover capabilities)
═══════════════════════════════════════════

Client → Server:
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }

Server → Client:
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "read_file",
        "description": "Read the complete contents of a file",
        "inputSchema": {
          "type": "object",
          "properties": {
            "path": { "type": "string", "description": "Path to file" }
          },
          "required": ["path"]
        }
      },
      {
        "name": "list_directory",
        "description": "List directory contents",
        "inputSchema": {
          "type": "object",
          "properties": {
            "path": { "type": "string", "description": "Path to directory" }
          },
          "required": ["path"]
        }
      }
    ]
  }
}

Host records: "filesystem server has 2 tools: read_file, list_directory"
Status: filesystem ✅ connected

═══════════════════════════════════════════
PHASE 4: READY (waiting for the user's requests)
═══════════════════════════════════════════

Claude Code shows the prompt to the user: >

═══════════════════════════════════════════
PHASE 5: OPERATION (user makes a request)
═══════════════════════════════════════════

User types: "Read the package.json file"

Host (Claude) decides: use filesystem.read_file

Client → Server:
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "read_file",
    "arguments": { "path": "/Users/dev/project/package.json" }
  }
}

Server executes: fs.readFile("/Users/dev/project/package.json")

Server → Client:
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [{
      "type": "text",
      "text": "{\n  \"name\": \"my-app\",\n  \"version\": \"1.0.0\"\n}"
    }],
    "isError": false
  }
}

Host presents to the user:
"The package.json file contains:
 name: my-app
 version: 1.0.0"

═══════════════════════════════════════════
PHASE 6: SHUTDOWN (user closes the session)
═══════════════════════════════════════════

User: Ctrl+C

Host closes the connection with the Client
Client closes the communication with the Server
The Server's process ends (PID 12345 killed)

Flow 4: Error handling

Scenario: The user asks to read a file that doesn't exist

FLOW WITH AN ERROR

User: "Read the config.secret.json file"

② Host decides: use filesystem.read_file

③ Client → Server:
  { "method": "tools/call",
    "params": { "name": "read_file",
                "arguments": { "path": "config.secret.json" } },
    "id": 6 }

④ Server executes: fs.readFile("config.secret.json")
  → ERROR: ENOENT - file not found

⑤ Server → Client:
  {
    "id": 6,
    "result": {
      "content": [{
        "type": "text",
        "text": "Error: File not found: config.secret.json\nThe file does not exist in /Users/dev/project/"
      }],
      "isError": true
    }
  }

⑥ Client → Host: delivers the error

⑦ Host → User:
  "I couldn't read config.secret.json — the file doesn't exist in your project.
   Did you mean config.json or settings.json?"

Note: The Host (Claude) doesn't just present the error — it interprets it and offers alternatives. That ability to handle errors intelligently is part of the Host's value.


Comparison: where to look based on the symptom

When something fails, the symptom tells you which layer to look at:

Diagnosis map:

SYMPTOM                              → LAYER → PROBABLE CAUSE
─────────────────────────────────────────────────────────────
Server doesn't appear in /mcp        → Host  → Incorrect configuration
Server appears as "disconnected"     → Host/Client → Process doesn't start
Server connected but no tools        → Client/Server → Initialize fails
Tool exists but isn't used           → Host  → Claude doesn't select it
Tool is invoked but errors           → Server → Handler logic
Empty response or weird format       → Server → Malformed response
Timeout in the response              → Server → Operation takes too long
Everything works but bad result      → Server → Incorrect logic

Debugging flowchart

Does the server appear in /mcp?
├── NO → Check settings.json and the command
│        $ claude mcp list
│        Does the command work manually?
│        $ npx -y @modelcontextprotocol/server-xxx
│
├── Appears as "disconnected"
│   → The process crashes on startup
│   → Check dependencies and environment variables
│   → Run the command manually to see errors
│
└── YES, "connected" with tools
    ├── Does Claude use the correct tool?
    │   ├── NO → Be more specific in your request
    │   └── YES → Is the result correct?
    │       ├── NO → Problem in the Server's handler
    │       └── Error → Check the error message
    │           ├── isError: true → Operation error (your code)
    │           └── JSON-RPC error → Protocol error

Anatomy of the data in each layer

What format the data has at each point of the flow

POINT IN THE FLOW          DATA FORMAT
──────────────────────────────────────────────

① User → Host             Natural language
                          "Read the README.md file"

② Host (decision)         Model's internal structure
                          { tool: "read_file", server: "filesystem",
                            args: { path: "README.md" } }

③ Client → Server         JSON-RPC 2.0 over stdio
                          {"jsonrpc":"2.0","id":1,"method":"tools/call",
                           "params":{"name":"read_file",
                           "arguments":{"path":"README.md"}}}

④ Server (execution)      Native operation
                          fs.readFileSync("README.md", "utf-8")

⑤ Server → Client         JSON-RPC 2.0 over stdio
                          {"jsonrpc":"2.0","id":1,"result":
                           {"content":[{"type":"text",
                           "text":"# README content..."}]}}

⑥ Client → Host          Internally parsed data
                          { content: "# README content..." }

⑦ Host → User             Formatted natural language
                          "The README contains..."

Pattern: The data is transformed at each layer — from natural language to JSON-RPC, from JSON-RPC to a native operation, and back.


Flow with multiple simultaneous Clients

How the Host manages parallel connections

When Claude Code has 3 MCP servers configured, the startup lifecycle is parallel:

Claude Code startup (parallel):

Time →
────────────────────────────────────────────

Host    ──launches process 1──▶  Filesystem Server
        ──launches process 2──▶  GitHub Server
        ──launches process 3──▶  Memory Server

Client1 ──initialize──▶  Filesystem  ──response──▶  Client1
Client2 ──initialize──▶  GitHub      ──response──▶  Client2
Client3 ──initialize──▶  Memory      ──response──▶  Client3

Client1 ──tools/list──▶  Filesystem  ──response──▶  Client1
Client2 ──tools/list──▶  GitHub      ──response──▶  Client2
Client3 ──tools/list──▶  Memory      ──response──▶  Client3

Host records all the capabilities:
├── filesystem: read_file, write_file, list_directory, ...
├── github: search_repos, create_issue, list_prs, ...
└── memory: store, retrieve, search, ...

Status: READY
All servers: connected ✅

When a server fails, the others aren't affected:

Startup with a server that fails:

Client1 ──initialize──▶  Filesystem  ──response──▶  ✅
Client2 ──initialize──▶  GitHub      ──TIMEOUT──    ❌ (invalid token)
Client3 ──initialize──▶  Memory      ──response──▶  ✅

Host records:
├── filesystem: connected ✅
├── github: disconnected ❌
└── memory: connected ✅

Claude Code works with filesystem and memory.
GitHub operations aren't available.

Troubleshooting

"The flow is interrupted in the initialize phase"

Cause: The Server doesn't respond to the Client's initialize message, or it responds with an incorrect format.

Diagnosis:

# Simulate the initialize manually
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | npx -y @modelcontextprotocol/server-filesystem /tmp

If you don't get a valid JSON response, the Server has an initialization problem.

"The Host doesn't orchestrate well between servers"

Cause: Claude (the model) has difficulty deciding which server to use when the capabilities overlap.

Solution: Use more specific requests:

  • ❌ "Search for information" (ambiguous: filesystem? GitHub? memory?)
  • ✅ "Search for .py files in my project" (clearly filesystem)
  • ✅ "Search for open issues in my GitHub repo" (clearly GitHub)

"The Server's response arrives but the Host doesn't show it well"

Cause: The Server returns data in a format that the Host can't present cleanly.

Solution: Ensure your responses are readable text:

// ✅ Good response (readable)
{ "content": [{ "type": "text", "text": "I found 3 files:\n- main.py\n- utils.py\n- test.py" }] }

// ❌ Bad response (raw JSON hard to read)
{ "content": [{ "type": "text", "text": "[{\"name\":\"main.py\"},{\"name\":\"utils.py\"},{\"name\":\"test.py\"}]" }] }

Exercises

Exercise 1: Trace a simple flow (Easy)

Trace the complete flow for this request: "Count how many lines the main.py file has." Identify each step (①-⑦), which layer acts, and what data travels.

See solution
① User → Host:
   "Count how many lines the main.py file has"

② Host (Claude) analyzes:
   → Needs to read the file to count lines
   → Tool: filesystem.read_file
   → Server: Filesystem

③ Client → Server (JSON-RPC):
   { "jsonrpc": "2.0", "id": 7,
     "method": "tools/call",
     "params": { "name": "read_file",
                 "arguments": { "path": "main.py" } } }

④ Server executes:
   → fs.readFile("main.py") → the file's content

⑤ Server → Client (JSON-RPC):
   { "jsonrpc": "2.0", "id": 7,
     "result": { "content": [{ "type": "text",
       "text": "import os\nimport sys\n\ndef main():\n    print('hello')\n\nif __name__ == '__main__':\n    main()" }] } }

⑥ Client → Host:
   Delivers the file's content

⑦ Host → User:
   "The main.py file has 8 lines."
   (Claude counts the lines of the received content)

Note: The Server doesn't count lines — it returns the content. It's the Host (Claude) that counts the lines of the received text.

Exercise 2: Trace a flow with an error (Medium)

Trace the flow when the user asks "Run the query SELECT * FROM users" but the database isn't available (connection refused).

See solution
① User → Host:
   "Run the query SELECT * FROM users"

② Host decides:
   → Tool: postgres.query
   → Server: PostgreSQL

③ Client → Server:
   { "jsonrpc": "2.0", "id": 15,
     "method": "tools/call",
     "params": { "name": "query",
                 "arguments": { "sql": "SELECT * FROM users" } } }

④ Server tries to execute:
   → connection.query("SELECT * FROM users")
   → ERROR: Connection refused at localhost:5432

⑤ Server → Client:
   { "jsonrpc": "2.0", "id": 15,
     "result": {
       "content": [{ "type": "text",
         "text": "Error: Could not connect to PostgreSQL at localhost:5432. Connection refused. Ensure PostgreSQL is running." }],
       "isError": true
     } }

⑥ Client → Host:
   Delivers the error

⑦ Host → User:
   "I couldn't run the query because PostgreSQL isn't available
    at localhost:5432. Verify that the service is running:

    sudo systemctl start postgresql

    or if you use Docker:
    docker start postgres-container"

Key points:

  • The Server handles the error (doesn't crash) and returns isError: true
  • The Host (Claude) interprets the error and suggests solutions
  • The complete flow works — only the result is an error

Exercise 3: Trace a multi-server flow (Medium)

Trace the flow for: "Search for the .test.ts files in my project and send the list to Slack's #testing channel." Assume the Filesystem server and Slack server are configured.

See solution
① User → Host:
   "Search for the .test.ts files and send the list to Slack's #testing channel"

② Host analyzes:
   → Step A: search files → Filesystem server
   → Step B: send to Slack → Slack server
   → Sequential: B depends on the result of A

③-A Client 1 → Filesystem Server:
   { "jsonrpc": "2.0", "id": 20,
     "method": "tools/call",
     "params": { "name": "search_files",
                 "arguments": { "path": "/project", "pattern": "*.test.ts" } } }

④-A Server executes the filesystem search

⑤-A Filesystem Server → Client 1:
   { "jsonrpc": "2.0", "id": 20,
     "result": { "content": [{ "type": "text",
       "text": "Found 4 files:\n- src/auth.test.ts\n- src/api.test.ts\n- src/utils.test.ts\n- src/db.test.ts" }] } }

⑥-A Client 1 → Host: delivers the list of files

② Host processes:
   → Claude formats the list for Slack

③-B Client 2 → Slack Server:
   { "jsonrpc": "2.0", "id": 21,
     "method": "tools/call",
     "params": { "name": "send_message",
                 "arguments": {
                   "channel": "#testing",
                   "text": "📋 Test files in the project:\n• src/auth.test.ts\n• src/api.test.ts\n• src/utils.test.ts\n• src/db.test.ts"
                 } } }

④-B Slack Server executes: POST to the Slack API

⑤-B Slack Server → Client 2:
   { "jsonrpc": "2.0", "id": 21,
     "result": { "content": [{ "type": "text",
       "text": "Message sent to #testing successfully" }] } }

⑥-B Client 2 → Host: confirms the send

⑦ Host → User:
   "I found 4 test files and sent the list to Slack's #testing channel:
    - src/auth.test.ts
    - src/api.test.ts
    - src/utils.test.ts
    - src/db.test.ts"

Exercise 4: Diagnose by symptoms (Hard)

For each symptom, identify the problem's layer (Host, Client, Server) and the probable cause:

  1. Claude Code shows "0 MCP servers connected" when you run /mcp
  2. The query tool appears in the list but Claude never uses it
  3. The tool is invoked but returns { "content": [] } (empty array)
  4. The Server responds correctly but Claude Code shows "Error parsing response"
See solution
  1. Layer: Host — There are no configured servers, or all the processes fail to start.

    • Check: claude mcp list and cat ~/.claude/settings.json
    • Cause: empty settings.json, incorrect commands, or node/npx not installed
  2. Layer: Host — Claude (the model) doesn't consider the tool relevant to the user's request.

    • Check: The request is specific enough
    • Cause: The tool's description doesn't communicate its purpose well, or the user's request is ambiguous
    • Fix: Be more specific: "Run a SQL query on the database" instead of "search for data"
  3. Layer: Server — The tool's handler returns an empty array instead of content.

    • Check: The Server's handler has a bug in building the response
    • Cause: The operation runs but the result isn't included in content
    • Fix: Ensure the handler always returns at least one item in content
  4. Layer: Client — The Server returns valid JSON but with an incorrect MCP format.

    • Check: The Server's response complies with the MCP spec
    • Cause: The content field is missing, or content isn't an array, or the type isn't valid
    • Fix: Validate that the response follows the schema { content: [{ type: "text", text: "..." }] }

Mini-Project: Diagram Your Claude Code Setup

Objective

Create a complete diagram of the Host-Client-Server architecture of your current Claude Code setup, including all the MCP servers you have configured.

Instructions

Step 1: Inventory your setup

Run these commands in your terminal:

# See your configured MCP servers
claude mcp list

# See the connection status (inside a Claude Code session)
/mcp

Document each server: name, command, scope, status.

Step 2: Draw the architecture

Create a diagram that shows:

  1. The Host (Claude Code) as the main box
  2. One Client per connected Server (inside the Host)
  3. Each Server as an external box with its tools listed
  4. Arrows of communication between Client and Server
  5. The transport used (stdio or HTTP)

Suggested format:

# MCP Architecture Diagram — My Setup

## Host: Claude Code

### Active connections

#### Server 1: [name]
- **Command:** [startup command]
- **Transport:** stdio
- **Scope:** user/project/local
- **Status:** connected/disconnected
- **Tools:**
  - [tool1] — [description]
  - [tool2] — [description]
  - [tool3] — [description]

#### Server 2: [name]
[...]

## Visual diagram

Claude Code (Host) │ ├── Client 1 ──stdio──▶ [Server 1] │ ├── tool_a │ ├── tool_b │ └── tool_c │ ├── Client 2 ──stdio──▶ [Server 2] │ ├── tool_d │ └── tool_e │ └── Client 3 ──stdio──▶ [Server 3] ├── tool_f └── tool_g

Step 3: Trace a complete request

Choose a real request you can make to Claude Code using one of your MCP servers. Run the request and trace the complete flow:

  1. What you typed (user input)
  2. Which Server and tool Claude Code used
  3. What data traveled (you can observe the tool call in Claude Code's UI)
  4. What result you received

Step 4: Trace a cross-server request (if applicable)

If you have 2+ servers, design a request that uses both and trace the flow.

Step 5: Reflection

Answer:

  1. What MCP server would you add to your setup and why?
  2. How would it fit into your diagram?
  3. What tools would it need?
  4. Could it be your module 8 project?

Delivery format

# Module 2 Mini-Project: My Setup's Architecture

## Inventory
[List of servers with their configuration]

## Diagram
[Visual ASCII diagram]

## Traced flow
[Complete request step by step]

## Cross-server flow (optional)
[Multi-server request step by step]

## Reflection
[Answers to the questions]
See complete example
# Module 2 Mini-Project: My Setup's Architecture

## Inventory

| Server | Command | Scope | Status |
|--------|---------|-------|--------|
| filesystem | npx server-filesystem ~/projects | user | connected ✅ |
| memory | npx server-memory | user | connected ✅ |

## Diagram

Claude Code (Host)
│
├── Client 1 ──stdio──▶ Filesystem Server
│                        ├── read_file
│                        ├── write_file
│                        ├── list_directory
│                        ├── search_files
│                        └── directory_tree
│
└── Client 2 ──stdio──▶ Memory Server
                         ├── store_memory
                         ├── retrieve_memory
                         └── search_memory

## Traced flow

Request: "Read my package.json file"

① Me → Claude Code: "Read my package.json file"
② Host: decides to use filesystem.read_file
③ Client 1 → Filesystem Server: tools/call read_file("package.json")
④ Server executes: reads package.json from disk
⑤ Server → Client 1: the file's content
⑥ Client 1 → Host: delivers the content
⑦ Host → Me: "Your package.json contains: name my-app, version 1.0.0..."

## Cross-server flow

Request: "Read my README.md and remember what my project is about"

Step A: filesystem.read_file("README.md") → content
Step B: memory.store_memory("project", "Task management app...")

Result: "I read your README and saved in memory that your project is
a task management app with React and Node.js."

## Reflection

1. I'd add a GitHub server to manage PRs without leaving Claude Code
2. It would be Client 3 connected via stdio to the GitHub MCP server
3. It would need: list_prs, create_issue, search_repos, get_commit_history
4. It could be the base of my module 8 project if I connect it with
   my work's internal API

Complete module summary

You've completed Module 2: Host-Client-Server Architecture. Here's everything you learned:

Capsule 02 — The Host

  • Claude Code as the orchestrator: manages connections, discovers capabilities, handles permissions
  • A Host has multiple Clients, one per Server
  • Configuration scopes: user, project, local

Capsule 03 — The Client

  • JSON-RPC 2.0 as the message format: Request, Response, Notification
  • Lifecycle: Initialize → Initialized → Operation → Shutdown
  • Transports: stdio (local) and HTTP/SSE (remote)
  • Capability negotiation during initialize

Capsule 04 — The Server

  • Your code: exposes capabilities (Tools, Resources, Prompts)
  • Input schemas with JSON Schema for validation
  • Patterns: API wrapper, database, filesystem, aggregation
  • Security: input validation, principle of least privilege

Capsule 05 — Complete flow

  • 7 steps of the request: user → Host → Client → Server → Client → Host → user
  • Cross-server orchestration: the Host coordinates multiple Servers
  • Debugging by layers: each symptom points to a specific layer
  • The data is transformed at each layer

What's coming: Module 3

In Module 3 (Three Primitives — Resources, Tools, Prompts) you're going to go deeper into exactly what an MCP Server can expose:

  • Resources: Contextual data with URIs, templates, subscriptions
  • Tools: Executable functions with schemas, validation, side effects
  • Prompts: Reusable templates with parameters

The transition is direct: you already know that the Server provides capabilities to the Host via the Client. Now you're going to see exactly what types of capabilities it can expose and how to design them effectively.


Additional resources

  1. MCP Architecture Overview - Complete view of the official architecture
  2. MCP Connection Lifecycle - Detail of the connection lifecycle
  3. JSON-RPC 2.0 Specification - Specification of the base protocol
  4. MCP Transports - Documentation of stdio and HTTP/SSE
  5. MCP Inspector - Visualize MCP messages in real time
  6. Claude Code MCP Documentation - Configuring and using MCP in Claude Code
  7. MCP Debugging Guide - Official debugging guide
  8. MCP Server Examples - Repository of reference servers

Next module: Module 3: Three Primitives — Resources, Tools, Prompts — what an MCP Server exposes and how to design each type of capability effectively.