12.01

MCP: servers, clients, and tools

Lesson 01 of 3 · 4:28
skillmaxing
Python with AI
Lesson 01
MCP: servers, clients, and tools
0:00 / 4:281x
Notes

Relay's three tools exist so that a model can look up a customer, search the help articles and escalate a ticket. Until now they have lived inside whichever agent you were building. The moment a second host wants them, a desktop assistant for the support team or an IDE plugin for the on-call engineer, you face a choice between copying the functions and inventing an API for them. Every team invents that API slightly differently, and every host has to be taught each one.

Every host learns every tool
Figure 1Every host learns every toolThree hosts, one set of tools, three different integrations to write and maintain.

The Model Context Protocol is the agreed API. A server offers capabilities; a client, embedded in the host application, discovers and calls them; the messages between them are JSON-RPC over a transport. Relay's tools become a Relay MCP server, and any host that speaks the protocol can use them without a line of Relay-specific glue.

The three roles and the three primitives

A host is the application with the model in it: a chat desktop app, an editor, or your own agent process. Inside the host, a client holds one connection to one server. A host with four servers has four clients. The server is a small program that knows how to do things and describes them in a way a model can read.

Host, client, server
Figure 2Host, client, serverOne client per server connection; a host with four servers holds four clients.

A server exposes three kinds of thing, and the difference is who decides to use them.

Primitive Controlled by Shape Relay example
Tool The model, during a run Named function with a JSON Schema for its arguments escalate_ticket(ticket_id, reason)
Resource The host application Read-only data at a URI ticket://4812, the full ticket text
Prompt The user, on request A named template with arguments "Draft a refund reply for ticket N"

Tools are what agents call. Resources are context the host can attach before the model even sees the question, which is why they are addressed by URI and never have side effects. Prompts are canned starting points a person picks from a menu. Most servers you write will be mostly tools, with a resource or two for the data a host would otherwise have to paste in.

What goes over the wire

Every exchange is a JSON-RPC 2.0 message. A session starts with an initialize handshake where both sides say which protocol version and which primitives they support. The client then asks tools/list, receives names, descriptions and schemas, and offers them to the model. When the model picks one, the client sends tools/call.

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "lookup_customer",
    "arguments": {"email": "ana@example.com"}
  }
}

The reply carries content blocks the model can read, and, when the server declares an output schema, a structuredContent object the calling code can use directly. Errors are reported the same way, so a failed lookup arrives as data the model can reason about rather than as a dropped connection.

Two transports carry those messages. stdio runs the server as a child process of the host and talks over its standard input and output; it is the local, zero-network choice and the one desktop hosts use most. Streamable HTTP exposes a single endpoint, conventionally /mcp, that accepts POSTed messages and may answer with a stream of events; it is the choice for a server that runs somewhere else, serves several hosts, or sits behind your usual auth.

Transport Where the server runs Auth Use it when
stdio A child process on the same machine Inherited from the user Local tools, desktop hosts, development
Streamable HTTP Any reachable host, one /mcp URL Headers, same as any API Shared servers, production, several clients

From Python the client side is short. The SDK's Client takes a URL and gives you the primitives as methods.

import asyncio
from mcp import Client


async def main():
    async with Client("http://localhost:8000/mcp") as client:
        tools = await client.list_tools()
        print([t.name for t in tools])

        result = await client.call_tool(
            "lookup_customer", {"email": "ana@example.com"}
        )
        print(result.structured_content)


asyncio.run(main())

Engineering note. Think of an MCP server as a public API with a model as its main consumer. Every tool description will be read by a model thousands of times, and every tool can be called by any host you have granted access to. Write descriptions the way you would write documentation for a careful but literal colleague, and put authorisation in the server, because you will not control the hosts.

Where it goes wrong

  • Tools that do what resources should. A get_ticket_text tool works, but the host cannot attach it as context up front, and the model spends a turn asking for it. If it is read-only data with an address, make it a resource.
  • One server for everything. A server with sixty tools gives the model sixty descriptions to read on every turn. Group by purpose; Relay's support tools and its billing tools are two servers.
  • Trusting the client for identity. On the HTTP transport the server sees a request, not a user. Authenticate it as you would any API, and decide per tool what that caller may do.
  • Descriptions that describe the code. "Calls the customers service" tells the model nothing about when to use it. Say what the tool answers and when it is the wrong choice.
  • Assuming stdio is private. A stdio server inherits the environment of the host process, including whatever credentials are in it. Give it its own scoped keys.

Try it

Sketch Relay's MCP server on paper: list the tools, decide which data belongs in resources with URIs, and write one prompt template a support lead would want. For each tool write the one-sentence description a model would read, then ask a colleague to guess from the descriptions alone which tool they would call for "the customer says they were charged twice". If they guess wrong, rewrite.

Next you will write that server, in build an MCP server in Python.

Free preview

Continue with the complete track

Keep your progress and unlock the surrounding lessons, exercises, and complete learning path.

Unlock the complete track
124 online