9Model Context Protocol

MCP clients

6 min read1,184 words

Your application needs to use tools from an MCP server. Something has to handle the protocol — the handshake, the message exchanges, the serialisation, the transport. That something is the MCP client. This lesson covers what the client is, how it talks to servers (over whatever wire you prefer), and the exact message flow that makes "user asks a question" turn into "GitHub API call" turn into "Claude's final answer."

What the MCP client is

The MCP client is a communication bridge between your application and an MCP server. Think of it as a thin library sitting between your server code and the MCP server process — your code makes high-level calls like "give me the list of tools" or "call this tool with these arguments," and the client handles all the low-level protocol details underneath.

You don't write the wire format. You don't parse protocol messages. You call methods on the client and the client takes care of the rest.

Transport agnostic

MCP is transport-agnostic — a formal way of saying the client and server can talk over whatever communication channel makes sense for the deployment. The three you'll see most:

  • stdio — the client and server run as processes on the same machine, communicating through standard input and output. This is by far the most common for local development and personal tooling. The server is a subprocess of the client; messages flow through pipes.
  • HTTP — the server is a long-running web service and the client hits it over HTTP. Useful for shared services, remote MCP servers, and multi-tenant deployments.
  • WebSockets and other network protocols — for persistent bidirectional connections over the network.

The message format is identical regardless of transport. Switching from stdio to HTTP doesn't change the tools, the schemas, or the logic — only the layer that ships bytes between client and server.

The two message patterns you'll use constantly

MCP defines many message types, but there are two you'll interact with on nearly every request: listing tools and calling tools.

ListTools

ListToolsRequestListToolsResult is how the client asks the server "what tools do you provide?" and gets back a list of available tools with their schemas.

Typical flow:

client -> server: ListToolsRequest
server -> client: ListToolsResult(tools=[tool_schema_1, tool_schema_2, ...])

Each tool in the result has a name, a description, and an input schema — the same shape you wrote by hand for custom tools in Module 6. The client exposes this result to your application so you can forward the schemas to Claude in the tools=[...] parameter of client.messages.create().

CallTool

CallToolRequestCallToolResult is how the client asks the server to actually execute a tool with specific arguments and returns the result.

client -> server: CallToolRequest(name="list_repos", arguments={"owner": "me"})
server -> client: CallToolResult(content=[...])

The server runs its tool function, returns the output, and the client exposes that output to your application. Your application then packages the output into a tool_result block and sends it back to Claude in the next API request — exactly the shape you learned in Module 6.

The complete end-to-end flow

Let's trace a real request. The user types "What repositories do I have?" into a chat interface powered by Claude plus a GitHub MCP server.

Step 1. User submits query to your server. The user's message arrives at your application.

Step 2. Your server asks the MCP client for the tool list. Before calling Claude, your code needs to know what tools are available.

Step 3. MCP client sends ListToolsRequest to the server. The client sends the request over whatever transport it's using — stdio, HTTP, or other.

Step 4. MCP server returns ListToolsResult. A list of available GitHub tools comes back, each with its schema.

Step 5. Your server calls Claude with the user's question and the tools list from step 4. The first client.messages.create() call of the request.

Step 6. Claude responds with a tool_use block. The model decides it needs to call, say, list_repos with {"owner": "me"}.

Step 7. Your server asks the MCP client to execute the tool. Your server sees the tool_use block and tells the MCP client "run this one with these arguments."

Step 8. MCP client sends CallToolRequest to the MCP server. The server receives the request.

Step 9. MCP server makes the actual GitHub API call. GitHub returns repository data to the MCP server.

Step 10. MCP server returns CallToolResult to the client. The repo data flows back up through the MCP layers.

Step 11. Your server sends the tool result to Claude. A follow-up client.messages.create() call with the tool_result block matching the original tool_use_id.

Step 12. Claude returns the final text response. With the repo data in context, Claude formulates a natural-language answer.

Step 13. Your server sends the answer to the user.

Thirteen numbered steps. Many messages. Many processes. And every one of them has a clear, single responsibility — your server orchestrates, the MCP client handles the protocol, the MCP server wraps the actual API, Claude does the reasoning and language, the user sees only the final answer.

Why this shape is the right shape

If you squint at that flow, you might notice it's more work than just writing the tool yourself. That's true for a single tool. What the architecture buys you is:

  • The MCP server can be used by ten applications, not one. Your team's work amortises across every Claude project in the company.
  • The MCP server can be used by thousands of applications if it's published. An open-source GitHub MCP server benefits everyone.
  • Your application doesn't have to track upstream API changes. When GitHub's API evolves, the MCP server maintainer handles it. Your app just reconnects and picks up the new schemas.
  • Security and auth live in one place. The MCP server holds the GitHub credentials. Your application never sees them.

The MCP client abstracts away the complexity of server communication so you can focus on building your application logic. You'll see exactly how much complexity that abstraction hides when you implement both sides in the next few lessons.

Key Takeaways

  • 1The MCP client is a thin library that handles the protocol and transport details so your application can make high-level calls like list_tools() and call_tool() without touching wire formats.
  • 2MCP is transport-agnostic: the same protocol runs over stdio (most common for local), HTTP (for shared services), WebSockets, or other channels without changing tool definitions or logic.
  • 3Two message pairs dominate everyday use: ListToolsRequest/ListToolsResult for discovering tools, and CallToolRequest/CallToolResult for executing them.
  • 4The end-to-end flow is: user → your server → MCP client → MCP server → external API → back up through the chain → Claude → user, with each layer having a single clear responsibility.
  • 5The architecture buys amortisation: an MCP server written once is reusable across many applications, and upstream API changes are handled by the server maintainer rather than every consumer.