Module 2: Host-Client-Server Architecture
The Host: MCP's Orchestrator
The Host: MCP's Orchestrator
Capsule description
When you use Claude Code and ask it to "read my package.json file," you don't stop to think about who decides to use the filesystem MCP server, who verifies you have permission, or who presents the response. All of that is done by the Host — the first layer of the MCP architecture and the one closest to you as a user.
In this capsule you're going to understand what an MCP Host is, what responsibilities it has, and why Claude Code as a Host is more than a pretty interface — it's the orchestrator that coordinates multiple MCP servers, handles permissions, and decides when and how to use each available capability.
The restaurant analogy applies perfectly: the Host is the maître. It doesn't cook (the Server does that), it doesn't carry plates (the Client does that), but without it, the restaurant doesn't work. It decides which kitchen can satisfy each order, verifies that the customer has a reservation, and coordinates the whole service.
What is an MCP Host?
Definition
An MCP Host is the application the user interacts with directly and that orchestrates the connections with MCP Servers through MCP Clients.
In concrete terms:
Current MCP Hosts:
├── Claude Code (CLI) — What you use in this guide
├── Claude Desktop (desktop app)
├── Cursor (IDE with AI)
├── Windsurf (Codeium's IDE)
├── Zed (text editor)
├── Continue.dev (open source extension)
└── Any application that implements the MCP protocol
The Host is the entry point to the MCP ecosystem. Everything starts and ends here.
The Host in the architecture
┌─────────────────────────────────────────────────┐
│ MCP HOST │
│ (Claude Code) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ MCP │ │ MCP │ │ MCP │ │
│ │ Client 1 │ │ Client 2 │ │ Client 3 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
└───────┼──────────────┼──────────────┼────────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ MCP │ │ MCP │ │ MCP │
│ Server │ │ Server │ │ Server │
│ (files) │ │ (GitHub)│ │ (DB) │
└─────────┘ └─────────┘ └─────────┘
Notice a key detail: the Host contains multiple Clients, one per connected Server. There isn't a single Client that talks with all the Servers — each connection has its own dedicated Client.
The Host's 5 responsibilities
1. Connection management
The Host is responsible for initiating and maintaining the connections with MCP Servers. When Claude Code starts up, it reads its configuration and launches each defined MCP server:
// ~/.claude/settings.json
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/dev/projects"]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "ghp_xxxxxxxxxxxx"
}
},
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"]
}
}
}
On startup, Claude Code:
- Reads the configuration
- Launches each server as a separate process
- Creates an MCP Client for each server
- Initiates the MCP connection with each one
Claude Code startup:
├── Reads settings.json
├── Finds 3 configured MCP servers
├── Launches process: npx @modelcontextprotocol/server-filesystem ...
│ └── Creates MCP Client 1 → Connects with the Filesystem Server
├── Launches process: npx @modelcontextprotocol/server-github ...
│ └── Creates MCP Client 2 → Connects with the GitHub Server
└── Launches process: npx @modelcontextprotocol/server-memory ...
└── Creates MCP Client 3 → Connects with the Memory Server
2. Capability discovery
Once connected, the Host needs to know what each Server can do. This happens during the initialization phase — the Client asks the Server what capabilities it exposes:
Host → Client 1 → Filesystem Server:
"What can you do?"
Filesystem Server responds:
"I have these tools:
- read_file(path)
- write_file(path, content)
- list_directory(path)
- search_files(pattern)
..."
Host records: Filesystem Server has 11 tools available
Host → Client 2 → GitHub Server:
"What can you do?"
GitHub Server responds:
"I have these tools:
- search_repositories(query)
- create_issue(repo, title, body)
- list_pull_requests(repo)
..."
Host records: GitHub Server has 8 tools available
After this phase, the Host has a complete catalog of all the available capabilities from all its connected Servers.
3. Request routing
When you type something in Claude Code, the Host decides which Server (or Servers) can respond. This is the Host's most important responsibility:
User: "Read the README.md file and create an issue in GitHub with its content"
Host analyzes:
├── "Read the README.md file"
│ → Needs: read_file
│ → Server: Filesystem ✅
│
└── "Create an issue in GitHub with its content"
→ Needs: create_issue
→ Server: GitHub ✅
Host orchestrates:
1. Asks Client 1 → Filesystem: read_file("README.md")
2. Receives the file's content
3. Asks Client 2 → GitHub: create_issue(repo, title, content)
4. Receives confirmation of the created issue
5. Presents the result to the user
The Host can use multiple Servers in a single operation. That orchestration ability is what makes the Host powerful.
4. Permission management
The Host controls what each Server can and cannot do. When an MCP Server tries to execute an operation, Claude Code can ask the user for confirmation:
User: "Create a file called config.json"
Host → Client → Filesystem Server: write_file("config.json", ...)
Claude Code shows:
┌─────────────────────────────────────────┐
│ ⚠️ MCP tool: filesystem.write_file │
│ │
│ Path: /Users/dev/projects/config.json │
│ Content: { "key": "value" } │
│ │
│ Allow this operation? [Y/n] │
└─────────────────────────────────────────┘
The Host's permission model includes:
Host permissions:
├── Access scope
│ ├── Which directories the Filesystem server can see
│ ├── Which repos the GitHub server can access
│ └── Which databases the DB server can query
│
├── Operation confirmation
│ ├── Read → Generally automatic
│ ├── Write → Asks for confirmation
│ └── Destructive → Requires explicit confirmation
│
└── Environment variables
├── GITHUB_TOKEN → Only available to the GitHub server
├── DB_CONNECTION → Only available to the DB server
└── Each server has access only to ITS variables
This isolation is critical for security: a filesystem MCP server doesn't have access to the GitHub token, and the GitHub server can't read files from the filesystem.
5. Presenting results
The Host receives the Servers' responses and presents them to the user coherently. It doesn't show the raw JSON — it processes it, formats it, and integrates it into the conversation:
MCP Server returns (raw JSON):
{
"content": [
{
"type": "text",
"text": "Found 3 files matching pattern '*.py':\n- main.py\n- utils.py\n- test_main.py"
}
]
}
Host presents to the user:
"I found 3 Python files in your project:
- main.py
- utils.py
- test_main.py"
The Host also decides how to combine results from multiple Servers into a coherent response when an operation involves several.
Claude Code as a Host: specific details
How Claude Code manages MCP Servers
Claude Code has 3 configuration levels for MCP servers:
Scope levels:
├── user (-s user)
│ └── Available in ALL Claude Code sessions
│ File: ~/.claude/settings.json
│
├── project (-s project)
│ └── Available only in the current project
│ File: .claude/settings.json (in the repo)
│
└── local (-s local, default)
└── Available only in the current session
File: .claude/settings.local.json
This enables configurations like:
# Filesystem server: always available (user scope)
claude mcp add filesystem -s user -- npx -y @modelcontextprotocol/server-filesystem ~/projects
# Database server: only for this project (project scope)
claude mcp add db -s project -- npx -y @modelcontextprotocol/server-postgres $DB_URL
# Experimental server: only this session (local scope)
claude mcp add test-server -s local -- node ./my-server.js
Host management commands
Claude Code exposes commands to manage MCP servers at runtime:
# See all servers and their status
claude mcp list
# See detailed status inside a session
/mcp
# Add a server
claude mcp add <name> -s <scope> -- <command> <args>
# Remove a server
claude mcp remove <name> -s <scope>
# See the raw configuration
cat ~/.claude/settings.json
The /mcp command inside an active session is especially useful because it shows:
MCP Servers:
filesystem: connected ✅
Tools: read_file, write_file, list_directory, ...
github: connected ✅
Tools: search_repositories, create_issue, ...
memory: disconnected ❌
Error: ENOENT - npx not found
The Host's decision flow
When you ask Claude Code for something, the model (Claude) acts as the Host's "brain" that decides which tools to use:
User input: "How many .ts files are in my project?"
Host's decision process:
│
├── 1. Claude processes the natural language
│ └── Identifies: needs to count files with the .ts extension
│
├── 2. Reviews the available capabilities
│ ├── Filesystem server: search_files(pattern) ✅ Can search files
│ ├── GitHub server: search_code(query) — Not applicable (searches GitHub, not local)
│ └── Memory server: retrieve() — Not applicable
│
├── 3. Selects the most appropriate tool
│ └── filesystem.search_files with pattern "*.ts"
│
├── 4. Executes via the Client
│ └── Client 1 → Filesystem Server → search_files("*.ts")
│
├── 5. Receives the result
│ └── [list of .ts files]
│
└── 6. Presents to the user
└── "There are 47 TypeScript files in your project..."
Multiple Servers: the Host's power
One Host, many Servers
A typical Claude Code setup for a professional developer includes multiple MCP servers:
Claude Code (Host)
├── Client 1 → Filesystem Server (local files)
├── Client 2 → GitHub Server (repos, PRs, issues)
├── Client 3 → PostgreSQL Server (database)
├── Client 4 → Slack Server (communication)
└── Client 5 → Memory Server (persistence)
Each Server is an independent process. If one fails, the others keep working:
Connection status:
├── filesystem: connected ✅
├── github: connected ✅
├── postgres: disconnected ❌ (DB not available)
├── slack: connected ✅
└── memory: connected ✅
→ Claude Code keeps working with 4 of 5 servers
→ Only the database operations fail
Cross-server orchestration
The Host's most powerful ability is coordinating operations across multiple Servers:
User: "Read the latest commits from GitHub and save them in a local file"
Host orchestrates:
│
├── Step 1: GitHub Server
│ └── list_commits(repo="my-project", limit=10)
│ └── Result: [list of 10 commits]
│
├── Step 2: Filesystem Server
│ └── write_file(path="commits.md", content=formatted)
│ └── Result: file created
│
└── Step 3: Presents the result
└── "I saved the latest 10 commits in commits.md"
Without the Host as orchestrator, each Server operates in isolation. The Host is the one who connects the dots.
The Host vs the other layers
What the Host DOES (and DOESN'T do)
The Host DOES:
├── ✅ Initiate connections with Servers
├── ✅ Discover each Server's capabilities
├── ✅ Decide which Server/tool to use for each request
├── ✅ Handle permissions and confirmations
├── ✅ Present results to the user
└── ✅ Coordinate cross-server operations
The Host does NOT:
├── ❌ Execute operations (the Server does that)
├── ❌ Handle the communication protocol (the Client does that)
├── ❌ Connect directly with external APIs (via MCP, not directly)
├── ❌ Maintain the Servers' state (each Server manages its own)
└── ❌ Implement the business logic of the capabilities
Comparison with the Client and Server
| Aspect | Host | Client | Server |
|---|---|---|---|
| Role | Orchestrates | Communicates | Provides |
| Who builds it | Vendor (Anthropic, Cursor) | Vendor (inside the Host) | You (developer) |
| Interacts with | User + Clients | Host + Server | Client |
| Example | Claude Code | Claude Code's MCP component | Your program that exposes tools |
| How many there are | 1 per application | 1 per connected Server | 1 per service |
Troubleshooting
"Claude Code doesn't detect my MCP server"
Most likely cause: The server's startup command fails silently.
Solution:
# 1. Test the command manually
npx -y @modelcontextprotocol/server-filesystem /your/directory
# 2. If it fails, verify that npx/node are installed
which npx
node --version
# 3. Verify the configuration
claude mcp list
# 4. Re-add with the correct scope
claude mcp remove my-server
claude mcp add my-server -s user -- npx -y @modelcontextprotocol/server-filesystem /your/directory
"The server appears as disconnected"
Most likely cause: The server's process crashes on startup.
Solution:
# 1. Check for errors at startup
# Open a new Claude Code session and watch the initial messages
# 2. Verify that the environment variables are configured
# For servers that require tokens:
echo $GITHUB_TOKEN
# 3. Check the permissions of the configured directory
ls -la /your/configured/directory
"Claude Code doesn't use the correct tool"
Cause: Claude (the model) decides which tool to use based on your request. If your question is ambiguous, it may choose a different tool than the one you expect.
Solution: Be specific in your request:
- ❌ "Look up information about my project"
- ✅ "Use the filesystem server to list the files in ./src"
"One server works but the others don't"
Cause: Each server is an independent process. One can fail without affecting the others.
Solution:
# See the status of each server
/mcp
# Identify which one fails and re-verify its configuration
claude mcp list
"The server is slow to respond"
Probable cause: The server starts with npx, which downloads the package every time.
Solution:
# Install globally for a faster startup
npm install -g @modelcontextprotocol/server-filesystem
# Reconfigure to use the global binary
claude mcp remove filesystem
claude mcp add filesystem -s user -- server-filesystem /your/directory
Exercises
Exercise 1: Identify Host responsibilities (Easy)
Classify each action as a responsibility of the Host, the Client, or the Server:
- Asking the user for confirmation before writing a file
- Sending a JSON-RPC message to the server
- Executing a SQL query on the database
- Deciding that the
read_filetool is needed to answer a question - Discovering which tools a server exposes during initialization
- Formatting the server's response to show it to the user
See solution
- Host — Permission management is the Host's responsibility
- Client — The Client handles the communication protocol
- Server — The Server executes the actual operation
- Host — The Host decides which capabilities to use (routing)
- Client (initiated by the Host) — The Client sends the initialization request, but the Host starts the process
- Host — Presentation to the user is the Host's responsibility
Pattern: The Host decides and presents, the Client communicates, the Server executes.
Exercise 2: Design a Host configuration (Medium)
Your team works on a project that needs:
- Access to the local filesystem
- A connection with GitHub to manage PRs
- Access to a PostgreSQL database
- Integration with Slack for notifications
Write the Claude Code JSON configuration (settings.json) that connects these 4 MCP servers. Define which scope you'd use for each one and why.
See solution
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/dev/my-project"]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "ghp_xxxxxxxxxxxx"
}
},
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"DATABASE_URL": "postgresql://user:pass@localhost:5432/mydb"
}
},
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": {
"SLACK_BOT_TOKEN": "xoxb-xxxxxxxxxxxx"
}
}
}
}
Recommended scopes:
filesystem→ user (you use it in all your projects, changing the path)github→ user (you always need GitHub)postgres→ project (each project has its own database)slack→ user (always the same Slack workspace)
claude mcp add filesystem -s user -- npx -y @modelcontextprotocol/server-filesystem ~/projects
claude mcp add github -s user -- npx -y @modelcontextprotocol/server-github
claude mcp add postgres -s project -- npx -y @modelcontextprotocol/server-postgres
claude mcp add slack -s user -- npx -y @modelcontextprotocol/server-slack
Reasoning: postgres is the only one with project scope because the database changes between projects. The others are general tools you always use.
Exercise 3: Trace the Host's routing (Medium)
The user types: "Read my .env file, look for the DATABASE_URL variable, and tell me how many tables that database has."
Trace the Host's routing flow: which Servers it uses, in what order, and which tools it invokes. Assume it has the Filesystem server and PostgreSQL server configured.
See solution
Input: "Read my .env file, look for DATABASE_URL, and tell me how many tables that database has"
Host routing:
│
├── Step 1: Filesystem Server
│ Tool: read_file(path=".env")
│ Result: "DATABASE_URL=postgresql://user:pass@localhost:5432/mydb\nAPI_KEY=abc..."
│
├── Step 2: Host processes (Claude extracts)
│ Extracts: DATABASE_URL = postgresql://user:pass@localhost:5432/mydb
│
├── Step 3: PostgreSQL Server
│ Tool: query(sql="SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public'")
│ Result: { "count": 12 }
│
└── Step 4: Host presents
"Your .env file contains DATABASE_URL pointing to mydb.
That database has 12 tables in the public schema."
Key point: The Host coordinates 2 Servers sequentially, using the output of the first as input for the second. The filesystem Client and the PostgreSQL Client operate independently — the Host is the one who connects the results.
Exercise 4: Analyze the permission model (Medium)
Explain why it's important for each MCP server to have its own isolated environment variables. What would happen if a filesystem MCP server had access to the GITHUB_TOKEN?
See solution
Why the isolation matters:
-
Principle of least privilege: Each server only needs access to the resources it uses. The filesystem server needs directory paths, not GitHub tokens.
-
Reduced attack surface: If a malicious MCP server (or one with a bug) could read all the environment variables, it would have access to tokens, database passwords, and API keys it doesn't need.
-
Security by design: The Host guarantees that
envin each server's configuration only passes the specified variables to THAT server:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["server-filesystem", "/dir"]
// Does NOT have access to GITHUB_TOKEN
},
"github": {
"command": "npx",
"args": ["server-github"],
"env": {
"GITHUB_TOKEN": "ghp_xxx"
// Only the github server sees this token
}
}
}
}
What would happen without isolation?
A filesystem MCP server with access to GITHUB_TOKEN could:
- Write the token to a file (exfiltration)
- Use it to make unauthorized requests to GitHub
- Expose it if the server has verbose logs
The Host's isolation prevents these scenarios.
Exercise 5: Simulate a Host with multiple Servers (Hard)
Draw the diagram of a Host (Claude Code) connected to 3 MCP servers. For each server, list at least 3 tools it would expose. Then, write 3 user requests that require the Host to use 2 or more servers in a single operation.
See solution
Diagram:
Claude Code (Host)
│
├── Client 1 → Filesystem Server
│ ├── read_file(path)
│ ├── write_file(path, content)
│ └── list_directory(path)
│
├── Client 2 → GitHub Server
│ ├── list_pull_requests(repo)
│ ├── create_issue(repo, title, body)
│ └── get_commit_history(repo, limit)
│
└── Client 3 → Slack Server
├── send_message(channel, text)
├── list_channels()
└── search_messages(query)
3 cross-server requests:
-
"Read my project's README.md and create an issue in GitHub asking for it to be updated"
- Filesystem:
read_file("README.md") - GitHub:
create_issue(repo, "Update README", content)
- Filesystem:
-
"Find all files modified today and send the list to Slack's #dev channel"
- Filesystem:
search_files(modified_today=true) - Slack:
send_message(channel="#dev", text=file_list)
- Filesystem:
-
"Review the open PRs in GitHub, read the CHANGELOG.md file, and send a summary to the team on Slack"
- GitHub:
list_pull_requests(repo, state="open") - Filesystem:
read_file("CHANGELOG.md") - Slack:
send_message(channel="#team", text=summary)
- GitHub:
Key point: The Host is the only one that can orchestrate these cross-server operations. No Server knows the others exist.
Exercise 6: Debugging the Host (Hard)
Your Claude Code shows this status when you run /mcp:
MCP Servers:
filesystem: connected ✅
Tools: read_file, write_file, ...
github: disconnected ❌
postgres: connected ✅
Tools: query, list_tables, ...
The user reports: "Claude Code won't let me create issues in GitHub." Describe your debugging process step by step, identifying which layer (Host, Client, Server) you'd look at for the problem.
See solution
Debugging process:
Step 1: Identify the problem's layer
└── The github server appears as "disconnected"
└── This is a CONNECTION problem, not a usage one
└── Layer: Host → Client (the connection fails before reaching the Server)
Step 2: Verify the Host's configuration
$ claude mcp list
→ Does github appear in the list?
→ If it does NOT appear: the server isn't configured (Host problem)
→ If it DOES appear: the configuration exists but the connection fails
Step 3: Verify the server's command
$ npx -y @modelcontextprotocol/server-github
→ Does it run correctly?
→ If it fails: a problem with the Server or its dependencies
→ Common error: GITHUB_TOKEN not configured
Step 4: Verify environment variables
$ echo $GITHUB_TOKEN
→ Is it defined?
→ Is it in the MCP server's configuration?
Step 5: Reconfigure
$ claude mcp remove github
$ claude mcp add github -s user -- npx -y @modelcontextprotocol/server-github
→ With env: GITHUB_TOKEN configured
Step 6: Verify the connection
$ /mcp
→ github: connected ✅
Most likely diagnosis: The GITHUB_TOKEN isn't configured in the server's environment variables, causing the server to fail on startup (Server layer), which the Host reports as "disconnected."
Summary
In this capsule you learned:
- The Host is the application the user uses directly (Claude Code) and acts as the orchestrator
- It has 5 key responsibilities: connection management, capability discovery, request routing, permission management, presenting results
- Claude Code manages servers in 3 scopes: user, project, local
- A Host can have multiple Clients, one per connected Server
- The Host's power is in cross-server orchestration — coordinating multiple Servers in one operation
- The Host doesn't execute operations directly — it delegates them to Servers via Clients
- The Host isolates each Server with its own environment variables for security
Next capsule: The Client: the connector — the invisible component that handles the MCP protocol, the connection lifecycle, and the communication between Host and Server.
Additional resources
- MCP Architecture — Hosts - Official documentation on the Host's role
- Claude Code MCP Configuration - Guide to configuring MCP in Claude Code
- MCP Server Scopes in Claude Code - Documentation of scopes (user, project, local)
- MCP Security Considerations - The Host's security and permission model
- Claude Code CLI Reference - Full reference of CLI commands including
claude mcp - Awesome MCP Servers - Directory of servers to configure in your Host
Next capsule: The Client: the connector — how the MCP communication protocol works, the lifecycle of a connection, and what happens between the Host and the Server.