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
| Step | Who | Does what | Data that travels |
|---|---|---|---|
| ① | User | Types the request in the terminal | Natural text |
| ② | Host | Claude analyzes and decides which tool to use | Internal decision |
| ③ | Client | Sends a JSON-RPC request to the Server | JSON-RPC request |
| ④ | Server | Executes the actual operation | Filesystem operation |
| ⑤ | Server | Returns the result to the Client | JSON-RPC response |
| ⑥ | Client | Passes the result to the Host | Internal data |
| ⑦ | Host | Formats and presents to the user | Formatted 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:
- Claude Code shows "0 MCP servers connected" when you run
/mcp - The
querytool appears in the list but Claude never uses it - The tool is invoked but returns
{ "content": [] }(empty array) - The Server responds correctly but Claude Code shows "Error parsing response"
See solution
-
Layer: Host — There are no configured servers, or all the processes fail to start.
- Check:
claude mcp listandcat ~/.claude/settings.json - Cause: empty settings.json, incorrect commands, or node/npx not installed
- Check:
-
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"
-
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
-
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
contentfield is missing, orcontentisn't an array, or thetypeisn'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:
- The Host (Claude Code) as the main box
- One Client per connected Server (inside the Host)
- Each Server as an external box with its tools listed
- Arrows of communication between Client and Server
- 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:
- What you typed (user input)
- Which Server and tool Claude Code used
- What data traveled (you can observe the tool call in Claude Code's UI)
- 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:
- What MCP server would you add to your setup and why?
- How would it fit into your diagram?
- What tools would it need?
- 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
- MCP Architecture Overview - Complete view of the official architecture
- MCP Connection Lifecycle - Detail of the connection lifecycle
- JSON-RPC 2.0 Specification - Specification of the base protocol
- MCP Transports - Documentation of stdio and HTTP/SSE
- MCP Inspector - Visualize MCP messages in real time
- Claude Code MCP Documentation - Configuring and using MCP in Claude Code
- MCP Debugging Guide - Official debugging guide
- 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.