MCP in Practice

Course wrap-up

Key takeaways

The through-line

MCP's whole value is that a system describes itself well enough for something else to act on the description. The transport, the JSON-RPC and the decorators are mechanism, and mechanism changes; the description is the product. Everything in this course is either a way to make that description more precise — schemas, annotations, output types — or a way to handle the cases where description alone is not enough, which is what elicitation, tasks and interfaces exist for.

The ideas that carry

  • Integration cost without a standard is a product, M x N. MCP makes it a sum by putting one agreed interface between the two sides, so a system describes itself once.
  • MCP is a specification, not a library. The SDK is one implementation; the document is the authority, and the version string is a date rather than a number.
  • The three primitives are separated by who initiates them — tools by the model, resources by the application, prompts by the user. That question settles nearly every design argument on the spot.
  • A client is a connection, not a program, and the server never sees a model — which is why your descriptions are the entire briefing.
  • 2026-07-28 made MCP stateless, and everything it deprecated shared one cause: the server may no longer speak first. Sampling, roots and protocol logging all required it; elicitation was redesigned rather than removed.
  • Your signature is the contract. Type hints become the input schema, the return annotation becomes the output schema, and a fact belongs in the schema whenever the schema can hold it.
  • Two kinds of failure, and the difference decides whether the model can recover. A domain failure returns as data with is_error; a protocol failure raises MCPError.
  • Annotation hints are unverified and load-bearing — a host skipping a confirmation is trusting you, so label honestly and leave hints unset when no claim holds.
  • Client(mcp) connects in-process, which makes the whole test suite fast, fixture-free and portless.
  • Elicitation is return-and-retry, not push. The handler runs more than once per logical operation, so do the irreversible thing after the questions.
  • A form answer lands in the model's context, and contexts get written down — so credentials go through URL mode, never a form.
  • A task is a durable handle, not a longer timeout, and cancellation is cooperative, so your loop must actually check.
  • A connection is a supply-chain decision, and tool results and tool descriptions are both untrusted text arriving where text is acted on.
  • stdio inherits the operating system's boundary; HTTP inherits nothing — a listening port is reachable from any page the user visits.
  • Validate the token's audience, not merely its signature. A genuine token issued for another service is not a token for you.

What you can now do

Connect and judge servers other people wrote, across four client dialects, and read a tool list before trusting it. Build a server with all three primitives, honest annotations, a shared connection and a test suite that runs in-process. Handle the cases that separate a real server from a demo — ambiguity through a resolver, credentials out of band, long work through a task handle, and an answer that is a grid rather than a sentence. Ship it over HTTP inside an application you already have, with host validation, audience-checked tokens, and the four standard attacks drawn well enough to catch in review.

If one thing sticks

The protocol will happily carry a bad description to every client in the world. When a tool goes unused, or gets called with the wrong arguments, or does something the user did not want — read what you told it before you read your code. That is almost always where the bug is, and it is the part that stays true whatever the next version deletes.