Claude Academy
Sign in

Vault / course/courses/mcp-advanced-topics.md

updated 2026-06-25

Course: Model Context Protocol — Advanced Topics

Mirrors: Anthropic Academy — Model Context Protocol: Advanced Topics · https://anthropic.skilljar.com/model-context-protocol-advanced-topics Audience: Developers, intermediate. You've built a basic MCP server. · Time: ~80 min + project Prereqs: introduction-to-mcp and its project. · Backing notes: mcp-advanced, mcp-trust-config, tool-design-principles, error-handling-in-tools Project: p15-multi-agent-orchestrator

A basic server exposes tools, resources, and prompts. A production server also lets the server ask the model for help, respects boundaries, reports progress on slow work, authenticates remote users, and is deployed and scaled safely. This course covers the advanced surface of MCP and the trust model that holds it together.

Learning objectives

After this course you can:

  • Use sampling to let a server request an LLM completion from the client.
  • Scope a server's filesystem access with roots.
  • Emit notifications, progress, and logs for long-running operations.
  • Add OAuth authentication to a remote Streamable HTTP server.
  • Deploy and scale a remote server statelessly behind a load balancer.
  • Apply the MCP trust/security model and least-privilege design to tools.

Module 1 — Server asks the model: sampling & roots

🎞 Frame 1 · Sampling — the server borrows the LLM · ⏱ ~3 min

🎬 Scene — A server needs to summarize 1,000 documents but holds no API key. It asks the client to run the model instead.

🧠 ConceptSampling lets an MCP server ask the client to perform an LLM completion on its behalf (sampling/createMessage). The server stays model-independent and key-less; the call runs on the user's model and billing, with the user able to audit and approve. (Deeper: mcp-advanced.)

🖼 On screen

Server → sampling/createMessage  (prompt, maxTokens, modelPreferences)
Client → asks user for consent → calls Claude → returns completion to server

Checkpoint — Why is sampling preferable to the server holding its own API key?

🎞 Frame 2 · The human stays in the loop · ⏱ ~2 min

🎬 Scene — Before the sampled call runs, a consent dialog shows the prompt the server wants to send.

🧠 Concept — Sampling is client-mediated by design: the client (and usually the user) can inspect, edit, or reject the server's request. A server can express modelPreferences, but the client picks the actual model and enforces consent.

Checkpoint — Who ultimately chooses which model a sampling request runs on? (The client.)

🎞 Frame 3 · Roots — telling the server its boundaries · ⏱ ~3 min

🎬 Scene — The client hands the server a list of allowed directories; everything outside is off-limits.

🧠 ConceptRoots are the directories / project boundaries the client declares in-scope. A well-behaved server treats resources outside the roots as inaccessible. For filesystem servers, resolve every path and check it against the root allowlist — never trust a user-supplied path verbatim (deny traversal). (Deeper: mcp-advanced, mcp-trust-config.)

🖼 On screen

Client → roots/list → [ file:///home/me/project ]
Server → must reject  file:///etc/passwd   (outside roots, path traversal)

Checkpoint — A request asks for ../../etc/passwd. What should a roots-respecting server do? (Resolve, see it's outside roots, deny.)


Module 2 — Long-running work: notifications, progress, logging

🎞 Frame 4 · Notifications keep both sides in sync · ⏱ ~2 min

🎬 Scene — A server's tool list changes at runtime; it fires a notification and the client refreshes.

🧠 Concept — Either side can send notifications (one-way JSON-RPC messages). Servers commonly emit notifications/tools/list_changed, resources/updated, prompts/list_changed, and cancelled. Notifications only flow if the matching capability was declared at init. (Deeper: mcp-advanced.)

🖼 On screen

notifications/tools/list_changed     → client re-runs tools/list
notifications/resources/updated      → client re-reads a watched resource
notifications/cancelled              → a long op was cancelled

Checkpoint — What must be declared at initialization for list_changed notifications to be allowed?

🎞 Frame 5 · Progress for slow tools · ⏱ ~3 min

🎬 Scene — A 90-second indexing tool streams "12% … 38% … 71%" instead of freezing the UI.

🧠 Concept — A slow tool can emit notifications/progress against a request's progress token so the host shows a live bar. Pair this with cancellation: the client sends notifications/cancelled with the request ID and the server should stop work and return.

🖼 On screen

await ctx.report_progress(progress=71, total=100)
# ... and honor cancellation:
if ctx.cancelled:
    return

Checkpoint — Which two mechanisms make a long-running tool feel responsive and controllable? (Progress notifications + cancellation.)

🎞 Frame 6 · Structured logging over the protocol · ⏱ ~2 min

🎬 Scene — The host's debug panel shows server log lines with levels.

🧠 Concept — A server emits structured log messages over the protocol; the host displays them in a debug panel. This is how you observe a remote server without SSHing in. (Remember: with stdio you still log to stderr, not stdout.)

🖼 On screen

await ctx.session.send_log_message(level="info", data="Indexed 1,200 documents")

Checkpoint — Why send logs as protocol messages rather than printing them? (The host can surface them; stdout would corrupt a stdio wire.)


Module 3 — Remote servers: auth & deployment

🎞 Frame 7 · Why remote servers need auth · ⏱ ~2 min

🎬 Scene — One hosted server, many users; without auth, anyone could read anyone's data.

🧠 Concept — stdio servers run as your local subprocess and need no auth. The moment a server is remote (Streamable HTTP), it's multi-tenant and exposed — it must authenticate and authorize each caller. (Deeper: mcp-transports, mcp-trust-config.)

Checkpoint — Why does a stdio server skip auth while a Streamable HTTP server can't?

🎞 Frame 8 · OAuth 2.1 for remote MCP · ⏱ ~3 min

🎬 Scene — The host kicks off an OAuth flow in the browser; the server hands back tokens it attaches to every request.

🧠 Concept — Remote MCP uses OAuth 2.1 with dynamic client registration. The server exposes a discovery endpoint advertising auth metadata; the host runs the flow, persists tokens, and attaches them on each request, refreshing as needed.

🖼 On screen

GET /.well-known/oauth-authorization-server   → auth metadata
Host  → OAuth authorize + token exchange       → access + refresh tokens
Host  → every request carries  Authorization: Bearer <token>

Checkpoint — What lets a host register itself with a server it has never seen before? (Dynamic client registration.)

🎞 Frame 9 · Deploying & scaling a remote server · ⏱ ~3 min

🎬 Scene — Several stateless server instances sit behind a load balancer; per-session state lives in Redis.

🧠 Concept — Production servers: Streamable HTTP transport, stateless request handlers with per-session state in Redis (or similar), OAuth refresh tokens, and horizontal scale behind a load balancer with sticky sessions keyed by the session ID header. (Deeper: mcp-advanced.)

🖼 On screen

flowchart LR
    H[Hosts] --> LB[Load balancer<br/>sticky by session_id]
    LB --> S1[Server inst 1]
    LB --> S2[Server inst 2]
    S1 --> R[(Redis: session state)]
    S2 --> R

Checkpoint — Why keep request handlers stateless and push session state to Redis? (So any instance can serve any request and you can scale horizontally.)


Module 4 — Trust, security, and composition

🎞 Frame 10 · The trust model & least privilege · ⏱ ~3 min

🎬 Scene — A consent screen lists exactly which tools and scopes a server is requesting before it connects.

🧠 Concept — MCP's security rests on explicit user consent and least privilege: read tools default safe, write tools stay narrow, and sensitive ops (delete, transfer, refund) require a confirmation flow. Mark behavior with annotations so clients can set auto-approval policy. (Deeper: mcp-trust-config, tool-design-principles.)

🖼 On screen

AnnotationMeaning
readOnlyHintNo side effects
destructiveHintMay delete/modify — default to confirmation
idempotentHintSafe to retry
openWorldHintTouches external systems

Checkpoint — A refund_order tool — which annotation, and what host behavior should it trigger? (destructiveHint; require user confirmation, no auto-call.)

🎞 Frame 11 · Errors are first-class · ⏱ ~3 min

🎬 Scene — A tool returns a clean is_error payload; Claude reads it and retries with corrected input instead of crashing.

🧠 Concept — Never let an unstructured exception bubble up — the model can't reason about a stack trace. Return is_error: true with a typed, useful message and distinguish user / transient / permanent errors. (Deeper: error-handling-in-tools, tool-design-principles.)

🖼 On screen

User error     → "ValidationError: customer_id must be a UUID"
Transient      → "TransientError: rate limited, retry after 5s"
Permanent      → "NotFoundError: ticket 1234 was deleted"

Checkpoint — Why return structured errors instead of raising? (Claude reads the message and adapts; a stack trace is unusable.)

🎞 Frame 12 · Scaling & composition of servers · ⏱ ~3 min

🎬 Scene — A host orchestrates several focused servers (filesystem, GitHub, database), composing their tools into one workflow.

🧠 Concept — Compose many small, focused servers rather than one mega-server — each with a clear scope, its own auth, and narrow tools Claude can chain (find_customerget_ordersrefund_order). This mirrors good tool design and sets up multi-agent orchestration where each subagent gets its own scoped server set.

Checkpoint — Why prefer several focused servers over one that does everything? (Clearer scope, tighter least-privilege, composable tools, easier to reason about.)


🛠 Project

Complete p15-multi-agent-orchestrator — Multi-Agent Orchestrator over MCP. You'll stand up a remote MCP server with OAuth and progress reporting, then drive it from an orchestrator that delegates to scoped workers — applying sampling, roots, least-privilege annotations, and structured errors end to end.

🧪 Self-check quiz

  1. What does sampling let a server do, and who runs the actual model call?
  2. What are roots, and what attack do they help prevent?
  3. Name two notifications a server might emit during a long operation.
  4. Which OAuth feature lets a host register with an unfamiliar server automatically?
  5. For a scalable remote server, where should per-session state live and why?
  6. Match each annotation: readOnlyHint, destructiveHint, idempotentHint.
  7. Give the three error categories a tool should distinguish.
  8. Why compose several focused servers instead of one mega-server?
<details><summary>Answers</summary>
  1. Request an LLM completion from the client; the client (user's model + billing) runs it. 2. Allowed directory boundaries; they prevent path-traversal / out-of-scope access. 3. notifications/progress and notifications/cancelled (also resources/updated, list_changed). 4. Dynamic client registration. 5. In Redis (or similar) so handlers stay stateless and scale horizontally. 6. read-only = no side effects; destructive = may delete/modify, confirm; idempotent = safe to retry. 7. User (validation), transient (retryable), permanent (gone). 8. Clearer scope, least privilege, composable chainable tools, easier to secure and reason about.
</details>

🎓 Certificate criteria

You've "passed" MCP: Advanced Topics when you can:

  • Explain sampling and roots and why each keeps the user in control.
  • Add OAuth to a remote server and deploy it statelessly behind a load balancer.
  • Apply least-privilege annotations and structured errors to your tools.
  • Complete p15-multi-agent-orchestrator with progress, auth, and scoped delegation working.

Tick this course off in progress and record the date you earned Anthropic's official certificate.

🔗 Sources & deeper notes