Course · Model Context Protocol · revision 2026-07-28
MCP servers that still behave when the request lands on the other replica.
A hands-on course for backend and platform engineers. You build Model Context Protocol servers in Python, put them behind a gateway, give them real authorization and durable work, and test the ugly cases: the retry after a timeout, the approval that never came back, the token meant for someone else.
- 57lessons
- 6h58of video
- 14Docker labs
- 2026-07-28spec revision

The timeout that already wrote the record.
A tool call times out. The server had already changed the record before the connection dropped. Retry, and you might finish the job or do it twice. The client can't tell which from what it got back.
Most MCP tutorials stop at the first green tool call on one process. This course starts where that demo stops being true: two replicas, a gateway that routes on headers, a human approval in the middle of a call, a token issued for another service. Each of those turns into behavior you write down and prove with a test.
- Client → gateway → replica A
tools/call publish_note - Replica Awrites the note
- Replica A → clientthe response is lost
- Clienttimes out, and can't tell whether the write happened
- Client → gateway → replica Bsame call, new request id
- Replica Bwrite it again, or detect it?
The request id changed. The business operation didn't.
Fourteen labs. One command each.
Every module has a lab, and every lab runs with one command: bash verify.sh. Docker Compose is the only thing you install. Images are pinned by digest, no lab asks for an account or an API key, and each one ships with checks that break the code on purpose, so you can see the tests notice the defect they were written for.
The request contractlab-1-stateless
TypeScript and Python servers answer the same contract, directly and through a proxy.
State and retrieslab-2-handles
Handles carry ownership and expiry, and a repeated business operation gets caught.
Multi round-trip requestslab-3-mrtr
An approval is bound to the exact operation it authorizes. Refusal and replay both fail closed.
Cacheable resultslab-4-caching
Cached results stay partitioned by caller, measured on a workload you can rerun.
Gateway policylab-5-gateway
Routing headers have to match the body, and tenant limits use identity the gateway verified.
Authorization boundarieslab-6-auth
Issuer, audience and scope checks reject exactly what they should.
Durable taskslab-7-tasks
A long-running task survives a restart and its whole lifecycle stays observable.
Operate the servicelab-8-operations
Readiness, replica replacement and a rehearsed rollback behind a load balancer.
Migrationlab-9-migration
Compatibility fixtures decide what a staged rollout is allowed to promote.
Agent securitylab-10-trust
A host refuses an injected export, pins the tools it approved and ignores a stranger's annotations.
Resources and promptslab-11-resources
A hidden resource and a missing one look the same from outside. Completion gives nothing away.
Production gapslab-12-production
One W3C trace crosses the gateway, JWTs validate in the right order, a limit holds across processes.
Decisions and costslab-13-decisions
An executable guide maps each need to a pattern, what it costs and the lab that measured it.
The reference serverlab-14-reference
Two replicas hold every requirement at once, plus a conformance suite you can point at your own server.
Point it at your own server.
The last module ships conformance.py, a black-box check of the revision's requirements. It only sends requests a client is allowed to send, so it doesn't need your code. This is the reference server from lab 14 passing it on two local replicas.
14 modules, 57 lessons.
Nine core modules take one server from its first request to a rehearsed rollback. Five extra modules go past the core contract. Open any module to see its lessons.
01
The Request Contract
- 1.0Welcome, and how the labs in this course runThe kind of failure the course is built around, what you will build for revision 2026-07-28, and how every lab runs locally with Docker Compose.3 min
- 1.1Why a working tool call can fail on another replicaDiagnose a request whose result depends on which replica receives it. Identify the evidence needed to separate request context from application state.9 min
- 1.2Read an MCP request from headers to tool argumentsInspect a self-contained request and distinguish routing metadata from tool arguments. Locate the boundaries where contradictory input can appear.10 min
- 1.3Diagnose a rejected call without guessing at the serverRead request and response examples to distinguish transport, protocol, and tool failures. Establish what a client trace can prove and what needs server evidence.10 min
- 1.4Build the TypeScript server and run its contract testsLabStart the local Compose lab and implement tool discovery and invocation in TypeScript. Exercise both valid messages and malformed requests.11 min
- 1.5Build the Python server against the same contractLabImplement the request contract in Python and run the shared behavioral fixtures. Compare responses across both language implementations.10 min
- 1.6Test discovery and version handling across replicasLabImplement server discovery and capability checks, then exercise supported and unsupported request versions. Send calls through alternating replicas to test request independence.10 min
02
State and Retries
- 2.1A timeout does not tell you whether the write happenedFollow an ambiguous timeout through a business operation and its response. Identify why a request identifier alone cannot establish whether retrying will repeat the effect.10 min
- 2.2Implement handles with ownership and expiry checksLabBuild a tool workflow that returns an explicit handle and validates each later use. Test unknown handles, expired handles, and requests from another owner.12 min
- 2.3Move application state beyond a single processLabCompare process-local state with a shared persistence implementation. Restart a replica and test which workflow guarantees the storage design preserves.11 min
- 2.4Make duplicate business operations detectableLabImplement application-level idempotency with atomic persistence and a defined retention window. Test duplicate submissions, conflicting payloads, and concurrent attempts.11 min
03
Multi Round-Trip Requests
- 3.1The missing approval that blocks a tool callTrace a tool operation that needs user input before it can finish. Identify the approval boundary and the failure cases a resumable workflow must address.10 min
- 3.2Implement input requests and the client retryLabBuild the input-required response and the client retry carrying input responses. Test request correlation and the distinction between request IDs and workflow state.12 min
- 3.3Bind approval to the operation it authorizesLabValidate continuation state against the caller and proposed operation. Reject altered arguments, expired approval, and reuse after completion.11 min
- 3.4Test refusal, abandonment, and interrupted approvalLabExercise approval refusal and abandoned workflows with a local client. Restart the service during a round trip and verify that unapproved actions remain uncommitted.11 min
04
Cacheable Results
- 4.1Implement cache hints on lists and resource readsLabAdd cache metadata to tool, prompt, and resource results in the local server. Test how the client interprets expiry and cache scope.10 min
- 4.2Partition cached results by caller and permissionsLabBuild cache keys that reflect the authorization context of a result. Exercise two callers and a permission change to detect inappropriate reuse.11 min
- 4.3Keep list ordering and pagination predictableLabImplement deterministic list ordering and pagination fixtures. Test repeated requests and catalog changes without confusing stable ordering with a frozen dataset.10 min
- 4.4Measure cache behavior without inventing a speedupLabCompare cached and uncached runs under a documented workload. Report latency, upstream calls, and stale-result behavior alongside the limits of the measurement.11 min
05
Gateway Policy
- 5.1Route by method and name, then check the bodyLabRoute requests through a local gateway using method and name headers. Test server rejection when routing metadata disagrees with the message body.9 min
- 5.2Expose selected tool arguments through routing headersLabDeclare a routing parameter with x-mcp-header and implement its corresponding header handling. Check missing values and inconsistent representations.11 min
- 5.3Test header encoding and sensitive parameter boundariesLabExercise parameter encoding with a fixture table and reject invalid cases. Audit which argument values would become visible to gateway logs.11 min
- 5.4Apply tenant limits using verified request identityLabImplement per-tenant rate limits and regional routing in the local gateway. Test spoofed tenant metadata and calls that exceed a configured limit.11 min
06
Authorization Boundaries
- 6.1Run a local authorization flow for an MCP clientLabStart a local authorization server, client, and protected resource server. Trace resource discovery and the authorization-code flow with PKCE.10 min
- 6.2Validate issuer identity before exchanging a codeLabCheck the authorization response issuer against the expected server. Test missing or mismatched issuer data and credentials bound to another issuer.12 min
- 6.3Configure client identity with metadata documentsLabServe a client metadata document in the local environment and validate its identity and redirect configuration. Distinguish local fixture setup from deployment requirements.11 min
- 6.4Enforce audience and scopes at the resource boundaryLabValidate tokens and apply permission checks to tool discovery and invocation. Reject wrong-audience, expired, and insufficient-scope requests with targeted tests.11 min
07
Durable Tasks and Subscriptions
- 7.1Check extension support before starting a taskLabImplement explicit capability checks for the tasks extension. Test unsupported clients and separate protocol requirements from application policy.10 min
- 7.2Persist a task and poll its lifecycleLabCreate an export task with persisted state and a polling client. Verify status transitions and result retrieval across a worker restart.11 min
- 7.3Handle task input, cancellation, and competing workersLabImplement task updates and cancellation with explicit transition checks. Test repeated input and competing workers against the persisted task state.11 min
- 7.4Subscribe to selected notifications and recover disconnectsLabBuild an opt-in notification listener and filter events by subscription and caller. Recover authoritative task state after disconnecting the stream.12 min
08
Operate the Service
- 8.1The healthy replica that still fails a tool callDiagnose a service that passes a basic health check while a tool dependency fails. Identify the evidence needed to distinguish process health from request readiness.10 min
- 8.2Assemble the service behind a local load balancerLabRun an independent capstone stack with authorization, caching, gateway policy, and durable work. Exercise the end-to-end request path through multiple replicas.12 min
- 8.3Test readiness, graceful shutdown, and replica replacementLabIntroduce dependency failures and replace a running replica during a workload. Test readiness decisions, draining behavior, and bounded request timeouts.13 min
- 8.4Trace tool latency, failures, and resource costLabAdd correlated logs and per-tool metrics with controlled label cardinality. Calculate workload cost from measured resource usage and explicitly supplied rates.12 min
- 8.5Rehearse a release and prove the rollback pathLabRun a local deployment rehearsal with acceptance checks and a deliberately failing candidate. Restore the previous service version and verify application state compatibility.13 min
09
Migration and Revision Watch
- 9.1What the 2026-07-28 revision changes in a running serverCompare the earlier handshake and session model with the revision's request contract. Distinguish removed mechanisms from deprecated features that still have a compatibility window.8 min
- 9.2Inventory transport and client compatibility before migrationLabRun compatibility fixtures against an earlier server and its clients. Record transport, session, and capability assumptions before changing the implementation.9 min
- 9.3Migrate session state and callbacks with regression testsLabConvert a provided implementation to explicit handles and request-driven input handling. Preserve business behavior while replacing protocol-specific fixtures.11 min
- 9.4Review registration, sampling, roots, and logging dependenciesLabInventory dependencies on deprecated registration and client features. Test a client metadata migration and distinguish interim compatibility from the target architecture.11 min
- 9.5Plan staged rollout against the published deprecation policyLabBuild a rollout plan from tested client compatibility and dated primary sources. Distinguish the minimum deprecation window from an announced removal date and define rollback triggers.11 min
- 9.6What you can now show, and where to take it nextFrom a call that failed on one replica to a rollout plan with a stop condition: the habit that connects them, and how it carries to a service this course never saw.2 min
10
Agent SecurityExtra
- 10.1Tool output is data, not instructionsLabWatch an injected document drive a naive host into an export, then stop it in the host: catalog, schema, egress allowlist and a person's confirmation, in that order.6 min
- 10.2Pin the tool definitions you approvedLabYou approved a server's tools last week, and this morning one description asks the model to export the notebook first. Pin what you approved, quarantine what changed and keep two servers' same-named tools apart.6 min
- 10.3Least privilege on the server you runLabYour server can't choose the host in front of it, so its guarantees can't depend on one. Validate inputs, grant by scope, rate limit, confine reads to a root and sanitize what goes back out.6 min
- 10.4Prove each control by removing itLabA green suite can pass with a control deleted. Remove each check in a throwaway copy, run only the attack written for it and watch it fail on an assertion, not a crash.5 min
11
Resources and PromptsExtra
- 11.1Resources a caller can read safelyLabData the host reads shouldn't hide behind a tool the model has to call. Expose it as a resource, check access on every read and never confirm a URI the caller isn't allowed to see.5 min
- 11.2Hear about a resource change, then read it againLabA cached runbook can be wrong for half a minute. Listen for changes on a request that stays open, then read the resource again instead of trusting what the notification said.5 min
- 11.3Prompts and completion that don't leakLabA slash command that pastes a note into its instructions rebuilds prompt injection by hand. Keep the note apart from the instruction, and filter completion so typing a prefix can't reveal a note you can't read.5 min
12
Production GapsExtra
- 12.1Carry one trace across the gatewayLabNine seconds, two logs and nothing tying them together. Carry W3C trace context through the request metadata so the gateway and the server land in one trace your existing tools already read.5 min
- 12.2Validate tokens from a real identity providerLabA hosted provider hands you signed JWTs and nobody to ask. Check the signature first, pin the algorithm, bind the audience and refresh keys after a rotation without turning your server into a load generator.5 min
- 12.3A limit that holds across replicasLabFive calls a minute becomes ten the day you add a replica. Move the window into shared storage, take the write lock first and prove it with four processes racing for the same tenant.4 min
- 12.4Progress and cancellation on one streamLabSomeone hits cancel on a long export. Send progress on the request's own stream, treat a closed stream as cancellation and roll back half a job so nothing is left behind.4 min
13
Decisions and CostsExtra
- 13.1Choose a pattern by what it costsLab"It felt right" doesn't survive a design review. Map each need to the pattern this course built for it, what it costs and the test that shows it holds.4 min
14
The Reference ServerExtra
- 14.1One path for every requestLabThirteen labs, one server. Every request walks the same ten checks in the same order, and the cheap refusals come before anything that touches a secret or your data.4 min
- 14.2Any replica, any requestLabA person confirms a delete and the answer lands on the other replica. Keep nothing in the process, sign the approval and make it single-use so it works anywhere and can't be replayed.4 min
- 14.3A client you didn't writeLabYour tests share every assumption your server makes. Point the official MCP Inspector at the reference server and see what a stranger's client sends, what it gets refused and what that costs.4 min
Who it's for
- Backend engineers building MCP servers that expose real tools and data
- Platform engineers who own the gateway, the auth and the uptime around those servers
- Engineers chasing a bug that only shows up on the second replica or after a retry
- Developers with an MCP integration on revision 2025-11-25 who need a tested way to move to 2026-07-28
What you need
- Python you're comfortable with: running tests and reading a traceback. TypeScript helps in module 1, but you can follow the Python side only
- Reading HTTP requests, JSON and command-line output
- A computer that can run Docker Compose with a few containers at once
- Internet access the first time each lab runs, to pull images and pinned dependencies
Checked before it's taught
This course contains the use of artificial intelligence. The technical side is held to a stricter rule: 224 claim records name the spec section they come from and the date they were checked, and a lesson's lab has to pass before its narration is written.
No cloud bill
Everything runs on your machine in Docker. No cloud account, no API key, no model provider. You'll want a computer that can run a few containers at once.
Dated, on purpose
MCP changes. The course says which revision it covers, which claims were checked on which date, and has a whole module on migrating from 2025-11-25.
Before you enroll
Which version of MCP does the course cover?
Revision 2026-07-28, the one without the initialization handshake or protocol sessions. Module 9 covers migrating a server written for 2025-11-25, including the features that are deprecated but still in the spec.
Do I need to know TypeScript?
No. Python carries the course. Module 1 builds the same request contract in TypeScript too, so you can see which rules come from the protocol and which from a language, but you can follow it reading the Python side only.
Do I need an API key or a cloud account?
No. Every lab runs locally with Docker Compose. The authorization labs bring their own local authorization server, and nothing calls a model provider.
Is this an intro to MCP?
It's intermediate. You should be comfortable reading HTTP requests, JSON and test output. If you've built one MCP tool and wondered what happens behind a load balancer, you're the person it was written for.
How long is it?
57 lessons and about seven hours of video, plus the labs, which take longer than the videos if you do the challenges. Nine core modules, then five extra modules on agent security, resources and prompts, production gaps, design trade-offs and a reference server.
Who's behind Runs Clean?
Runs Clean is a small publisher of engineering courses. There's no invented instructor with a stock photo. The courses are published under the brand, and what we put forward instead of a résumé is the standard: tested labs, sourced claims, dated material.
Build the server you'd trust behind a load balancer.
The course is on Udemy, with lifetime access and Udemy's refund window.