Why this page exists
Tool use (deep dive 6.3) taught you to define tools inside ONE application. MCP moves those tools into a standalone server that ANY client can connect to: Claude Code, Claude.ai, your own scripts, a teammate's agent. Write the warehouse integration once, and every AI surface in the company gets it. This page covers the full build-level MCP course plus the complete advanced topics course.
Building servers 15 min
A server wraps a system you own and exposes three kinds of things: tools (functions the model calls), resources (context the client loads), and prompts (templates the user invokes).
CoreArchitecture recap: client, server, three primitives3 min▶
Two roles, one protocol:
- The client is whatever hosts the model: Claude Code, the Claude apps, or your own program. It discovers what the server offers and brokers everything between the model and the server.
- The server wraps your system - a warehouse, a catalog, a filesystem, an internal API - and exposes capabilities in a standard shape. It never talks to the model directly; it only answers the client.
- Three primitives, by who initiates: tools are model-controlled (Claude decides to call them mid-task), resources are application-controlled (the client loads them as context, like attaching a file), prompts are user-controlled (a person picks a template, like a slash command).
Most teams ship tools first and forget the other two exist. That's a mistake we'll fix in this page: schemas and configs usually belong in resources, and your team's standard workflows belong in prompts.
CoreYour first server with FastMCP: a read-only warehouse5 min▶
The Python mcp package ships FastMCP: decorate typed functions and they become tools. The type hints become the JSON schema; the docstring becomes the description the model reads. You never hand-write schema JSON like you did in deep dive 6.3.
- Notice the validation: table names are checked against an allowlist before touching SQL, and the sample size is capped. The model is a user - validate its input like any user's.
- Docstrings are the interface. The model chooses tools by reading them, exactly like tool descriptions in 6.3. "Return the row count for one table. Read-only." beats "counts rows".
- Connect it to Claude Code with
claude mcp add warehouse -- python warehouse_server.pyand your whole team's Claude can now profile the warehouse - through only these three doors.
CoreThe inspector: test before you connect anything3 min▶
Do not debug a server through a model. The inspector is a browser UI that connects to your server directly, so you can see and call everything yourself first.
- What you check, in order: do all three tools appear? Are the descriptions and schemas what you meant? Does
row_countwith a real table return the right number? Does it fail cleanly with a fake table name? - The habit: inspector first, client second, model last. When a model misuses your tool, you then know the server itself is fine and the fix is in the description - a two-line change, not an evening of guessing.
CoreResources and prompts: the other two primitives4 min▶
Resources expose read-only context at a URI - things the client should be able to LOAD rather than have the model fetch through a tool call. Prompts are parameterised templates a user invokes deliberately.
- Resources suit schemas, configs, docs, catalog entries - stable reference material. The client attaches them as context up front, which costs no tool-call round trips and keeps the model from "exploring" for basics.
- Prompts encode workflows. In Claude Code they surface as slash commands, so
quality_checkbecomes the team standard instead of everyone improvising their own version - the SOP-to-skill idea from Session 3, one layer down.
CoreImplementing a client: calling servers from Python4 min▶
Claude Code IS a client, so when do you write your own? When MCP servers need to plug into YOUR pipeline: a nightly job that calls the warehouse server, a Slack bot, an eval harness. The client speaks the protocol; there's no model involved unless you add one.
- The lifecycle: connect over a transport,
initialize()(a capability handshake), then list and call. Same pattern forlist_resources/read_resourceandlist_prompts/get_prompt. - Wiring in a model: feed
list_tools()output to the Messages API as tool definitions, and route Claude's tool_use blocks tocall_tool. That loop is deep dive 6.3's agent loop with MCP as the tool backend.
A data platform team wrapped their metric store in one MCP server. Claude Code users got it via config, the on-call Slack bot used a Python client, and the eval harness called the same tools directly with no model at all. One integration, three consumers, and when a metric definition changed they fixed it in exactly one place.
The advanced layer 12 min
Everything above works for a local demo. Sampling, notifications, roots, and transports are what the advanced course adds - and they are what production servers actually use.
AdvancedSampling: the server borrows the client's model4 min▶
Sampling inverts the usual direction: the SERVER asks the CLIENT to run a model call on its behalf. The server gets intelligence without owning an API key, choosing a model, or paying the bill - all of that stays with the client, which can also show the user what's being requested and let them approve it.
- The flow: client calls the tool, the tool sends a
create_messagerequest BACK up the wire, the client (optionally with user approval) runs the model, and the completion returns to the tool, which finishes its work. - The use case that sells it: a catalog server that auto-describes tables. Shipped as sampling, it works for every team that connects it, whatever model or key they use - the server stays a dumb, cheap process.
- Design note: sampling calls are per-request and reviewable by the client. That's the governance posture you want: the server can ask for intelligence but never owns it.
AdvancedNotifications and roots3 min▶
Two smaller capabilities that make long-running and file-touching servers behave like good citizens.
- Notifications are one-way messages, no reply expected. Log notifications (
ctx.info,ctx.warning) surface what a tool is doing; progress notifications drive the client's progress display. Without them a 3-minute tool is indistinguishable from a hung one, and users kill it at 40 seconds. - Roots flow the other way: the client tells the server which directories it may operate in (as
file://URIs). A filesystem server callssession.list_roots()and confines itself to those paths. It's client-granted scope: Claude Code working in your project grants the project directory, not your home folder.
AdvancedTransports: STDIO vs StreamableHTTP, and the wire underneath4 min▶
Everything in MCP is JSON-RPC 2.0 messages: requests (id, expects a response), responses (result or error for an id), and notifications (no id, no reply). The transport is just how those messages travel.
- STDIO: the client launches the server as a subprocess and speaks over stdin/stdout. Zero network setup, one client per server process, dies with the client. The default and the right answer for local tools.
- StreamableHTTP: the server is a real web service on an HTTP endpoint. Multiple clients, sessions via an
Mcp-Session-Idheader, server-to-client streaming over SSE, and resumability - a dropped connection can resume its event stream instead of losing state mid-task. - State lives with the session. Once a server is remote and multi-client, per-session state has to be keyed by session id, and horizontal scaling needs sessions pinned or shared - the same discipline as any stateful web service. Getting to one line of code, though, is easy:
mcp.run(transport="streamable-http").
| STDIO | StreamableHTTP | |
|---|---|---|
| Runs as | Subprocess of the client | Standalone web service |
| Clients | One, local | Many, remote, session ids |
| Setup cost | None | Hosting, TLS, auth |
| Resumability | Not needed (local pipe) | Yes, via SSE event replay |
| Use when | Personal and dev tools, anything on your machine | Team-shared servers, hosted integrations |
AdvancedProduction notes: governance lives at this layer3 min▶
- Auth on remote servers is non-negotiable. A StreamableHTTP server is an API to your systems; put OAuth or token auth in front of it and scope tokens per team. STDIO dodges this only because it never leaves the machine.
- Least-privilege tools. Expose
row_countandsample_rows, notrun_sql. The tool surface IS the permission model: the model can only do what a tool allows, so design tools like you'd design IAM policies. - Version your servers. Tool names and schemas are a contract with every connected client. Additive changes are safe; renames and signature changes break agents silently, so version and announce like any API.
- The sys-admin story from Session 2, resolved: "how do we let Claude touch the warehouse safely?" The answer is this layer. One audited server, read-only tools, client-granted roots, logged calls - governance enforced in code, not in a policy doc nobody reads.
Three exercises ◐ 35-45 min
- 1 · Build and inspect a filesystem-stats server. FastMCP server with two tools:
dir_summary(path)returning file count and total size, andlargest_files(path, n). Test both in the inspector before connecting any client, including a path that doesn't exist - make the error message something a model could act on. - 2 · Add a resource exposing your data catalog index. Add
@mcp.resource("catalog://index")returning your table list with one-line descriptions (a CSV or markdown file is fine). Connect to Claude Code and compare: with the resource loaded as context, how many tool calls does "which table has customer churn data?" take versus without it? - 3 · Convert it to StreamableHTTP. Switch exercise 1's server with
mcp.run(transport="streamable-http"), connect the inspector over HTTP, and watch the session id appear. Write down what you would now need before a teammate could use it (auth, hosting, TLS) - that list is the real cost of "remote".
Official courses covered
This page teaches the MCP module shared by the three engineering courses at full build level, plus the complete standalone advanced course.