LevelUpWith

Explore / MCP in Depth: Build, Secure and Run Servers and Clients / Lesson 2

Lesson 2: Hosts, Clients and Servers

Maps the three roles in every MCP deployment and who trusts whom, so every later design decision has a place to land.

Free to read, no account needed. Part of MCP in Depth: Build, Secure and Run Servers and Clients.

By the end of this level you'll be able to

  • Identify the host, the client and the server in a given setup, and explain why a host runs one client per server
  • Describe what each role owns: the model and user consent in the host, the connection in the client, the capabilities in the server
  • Sketch the path of a single tool call from the user's prompt to the server and back

Three roles, every time

Every MCP deployment has the same three roles, whether it is a desktop chat app, a coding agent in your terminal, or a backend job with no UI.

  • Host: the AI application the user actually runs. It owns the model, the conversation and the user's consent.
  • Client: a component inside the host that speaks MCP to exactly one server. It owns the connection.
  • Server: a program that exposes capabilities (tools, resources and prompts). It owns what those capabilities do.

If you have written function-calling code against a model API, you have already built a crude host. Your loop sent tool definitions to the model, caught the tool request and ran a Python function. MCP moves that function out of your process into a server, and puts a standard protocol between the two.

Naming gotcha: people often say "client" when they mean the whole application ("which MCP clients support X?"). In the specification, the application is the host and the client is the connector inside it. This course uses the precise terms, because the security reasoning depends on them.

One client per server

A host connected to three servers runs three clients. The specification states it as a 1:1 relationship: each client communicates with exactly one server.

+-------------------- Host (the AI app) ---------------------+
|  model access - conversation history - consent UI          |
|                                                            |
|   [Client A]         [Client B]          [Client C]        |
+-------|------------------|-------------------|-------------+
        | stdio            | stdio             | HTTP
   [Server A]         [Server B]          [Server C]
   local files        local database      remote issue tracker

The host does not use one shared connection because the client doubles as an isolation boundary:

  • Servers cannot see each other. The files server never receives the issue tracker's traffic. Anything that crosses from one to the other goes through the host, which can apply policy.
  • Each connection has its own settings. Protocol version, capabilities and credentials can differ per server.
  • Failures stay local. If Server B crashes, the host drops Client B and the others carry on.

The mapping is not symmetrical. A local server launched over stdio typically serves the one client that started it. A remote server over HTTP typically serves many clients from many hosts. So it is one client to one server, but often one server to many clients.

Most hosts let you declare servers in configuration. The exact file and keys vary by host, so treat this as an illustration:

{
  "mcpServers": {
    // the host starts this process and creates one client for it
    "files": { "command": "python", "args": ["files_server.py"] },
    // the host creates a second, separate client for this URL
    "issues": { "url": "https://issues.example.com/mcp" }
  }
}

Two entries give you two clients and two isolated connections.

Who owns what, and who trusts whom

Role Owns Does not own
Host The model, the full conversation, user consent, security policy, which servers are connected What a tool does when it runs
Client One connection: framing messages, attaching protocol version and capabilities, routing replies Any decision about whether to call
Server Its capabilities and the systems behind them The model, the conversation, the user interface

The trust relationships follow from that split:

  • The user trusts the host. It is the only component that sees everything.
  • The host should not blindly trust a server. Tool descriptions and tool results are text written by someone else, and they end up in front of your model. Treat them as untrusted input unless you trust the server's author.
  • The server should not trust its callers' arguments. They were written by a model. Validate every input and enforce your own access control.
  • The server sees very little. By design it gets the arguments of a call, not the conversation, and cannot see into other servers.

The specification says there should always be a human able to deny a tool invocation. The host is where that happens, because it is the only role with a UI and the user's attention.

One tool call, end to end

The user types: "What's the weather in Leeds?" The host has a weather server connected.

  1. Discovery (earlier). The client sent tools/list, and the host stored the tool definitions.
  2. Prompt. The host sends the user's message and the tool definitions to the model.
  3. Model decides. The model replies with a request to call get_weather with {"location": "Leeds"}. The model only produces text asking for a call. It cannot reach the server.
  4. Consent. The host applies its policy: auto-approve, or ask the user.
  5. Routing. The host picks the client that owns that tool, and the client sends:
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": { "name": "get_weather", "arguments": { "location": "Leeds" } }
}
  1. Execution. The server validates the input, runs its code and replies with the same id:
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "resultType": "complete",
    "content": [{ "type": "text", "text": "Leeds: 11°C, light rain" }],
    "isError": false
  }
}
  1. Back to the model. The client hands the result to the host, the host adds it to the conversation, and the model writes the answer the user sees.

(Real requests also carry a _meta block with the protocol version and client capabilities. It is left out here to keep the shape visible.)

A host's core loop looks like this in Python. It is a sketch of the shape, not SDK code to copy; the real calls come when you build one.

# one client per configured server, keyed by server name
clients = {name: connect(cfg) for name, cfg in config.items()}

# discovery: remember which client owns which tool
owner = {}
for name, client in clients.items():
    for tool in await client.list_tools():
        owner[f"{name}.{tool.name}"] = (client, tool.name)  # prefix avoids clashes

# when the model asks for a tool
client, tool_name = owner[requested_name]
if await user_approves(requested_name, args):        # the host's job, nobody else's
    result = await client.call_tool(tool_name, args)  # client -> server -> client
    conversation.add_tool_result(result)              # the model sees it next turn

The prefix matters because tool names are only unique within a single server. Two servers can both expose search, so the host has to disambiguate. The TypeScript SDK follows the same pattern: one client object per server connection, managed by the host.

Key takeaways

  • The host is the application. It owns the model, the conversation and consent.
  • A client lives inside the host and talks to exactly one server. Three servers means three clients.
  • A server owns its capabilities and must validate what it receives. It does not see the conversation or other servers.
  • The model never contacts a server. It asks, the host decides, the client sends, the server runs, and the result comes back the same way.
  • Tool descriptions and results cross a trust boundary into your model's context, so the host should treat them as untrusted input.

A 6-question knowledge check follows this lesson in the app, free and without an account.

Keep going: levels 3 and 4 are free

Create a free account to open the next two levels, save your progress and pick up where you left off. No card needed.

Next: Level 3, Where MCP Runs Today.

Create a free account →See the whole course

More like this

  • Claude Code Unsupervised: Hours of Work You Can Trust

    Hand Claude Code hours of work across terminal, cloud and CI, then prove the result is right before you merge it. You will build a least-privilege, hook-gated,…

    Course · Claude · AI & Agents · Expert · 56 levels · 4 free · Professionally curated by levelupwith.com

  • Building on the Claude Platform: API to Production

    Go from your first Messages API call to a production-grade Claude application in Python, with TypeScript where it differs. You will learn to pick models, contr…

    Course · Claude · AI & Agents · Expert · 64 levels · 4 free · Professionally curated by levelupwith.com

  • Claude at Work: No-Code Skills for Knowledge Workers

    Learn to brief Claude clearly, check what it gives you, and set up Projects, Artifacts, Skills, memory, connectors and Research for real work in sales, marketi…

    Course · Claude · AI & Agents · Capable · 18 levels · 4 free · Professionally curated by levelupwith.com

  • Delegating Real Work to Claude: Tasks, Files and Apps

    Learn to hand Claude multi-step work on your real files, browser and Office apps, with clear briefs, firm limits and approval gates, then review the results so…

    Course · Claude · AI & Agents · Capable · 18 levels · 4 free · Professionally curated by levelupwith.com

  • Prompting Claude: Instructions, Context and Testing

    Write prompts that Claude follows reliably, manage what goes into its context window, and test and improve your prompts with evidence instead of guesswork. Eac…

    Course · Claude · AI & Agents · Capable · 20 levels · 4 free · Professionally curated by levelupwith.com

  • Finished Work with Claude Artifacts, No Code

    Brief, refine, brand, check, share and export finished designs, decks, documents, dashboards and motion pieces in Claude without writing code. Every level ends…

    Course · Claude · AI & Agents · Capable · 18 levels · 4 free · Professionally curated by levelupwith.com