MCP Learning Path: Choose by Role and by Spec Revision
The shortest route through MCP is not a fixed list of chapters. It depends on two things you can decide in a minute: which role you play, and which protocol era your peers speak. The two decisions below become tables, and every stage ends with a check that you can run and that either passes or does not.
Read in this order: pick your role (section 1), pick the protocol era you build for (section 2), migrate if you already have something on an older revision (section 3), follow the stages (section 4), and come back to section 5 when something does not work.
1. Pick your role
| Role | You want to | Read, in this order | Run first | You are done when |
|---|---|---|---|---|
| Evaluating MCP | Decide whether MCP fits and what it covers | Introduction, Architecture, Server concepts | Nothing yet | You can say which of tools, resources and prompts each of your use cases needs (prompts, section 1) |
| Server developer | Build a server other people’s clients can call | Build a server, Tools, Resources, Prompts, Best practices | The SDK example from “Build a server”, then lint_tools.py against it | lint_tools.py (and the resources and prompts linters if you offer them) exits with code 0 |
| Client or host developer | Connect your application to servers you did not write | Build a client, Client best practices, Versioning | A request with the protocol version in _meta, or server/discover first | An UnsupportedProtocolVersionError makes your client retry with a version from supported, and a legacy server is reported, not hung on |
| Integrator | Wire existing servers into a host such as an IDE or chat application | Connect to local servers, Connect to remote servers, Inspector guide | inspector_smoke.py against the server, before the host sees it | The Inspector lists the tools you expect, and the smoke test exits with code 0 |
| Security or platform owner | Put servers behind authentication, a gateway or a firewall | Authorization, Security best practices, Best practices | probe_http.py against a staging endpoint | probe_http.py exits with code 0, and you know why each WARN is acceptable |
| Publisher | List a server so that others can find and install it | Publishing, Consuming | preflight.py on your server.json | preflight.py exits with code 0 before you run mcp-publisher publish |
2. Pick the protocol era
MCP has had five revisions. The table lists what each one added that shows up in your code; the last row is where you should start today.
| Revision | What it added that you will notice | Official changelog |
|---|---|---|
| 2024-11-05 | The first revision documented here; the next changelog is written relative to it | 2025-03-26 changelog |
| 2025-03-26 | An OAuth 2.1 authorization framework, Streamable HTTP replacing HTTP+SSE, JSON-RPC batching, tool annotations, audio content, the completions capability | Key changes |
| 2025-06-18 | Batching removed again, structured tool output, servers as OAuth resource servers with protected resource metadata, Resource Indicators (RFC 8707), elicitation, resource links in tool results, the MCP-Protocol-Version header, title fields | Key changes |
| 2025-11-25 | OpenID Connect Discovery, icons, incremental scope consent, URL mode elicitation, tool calling in sampling, client ID metadata documents, experimental tasks, JSON Schema 2020-12 as the default dialect | Key changes |
| 2026-07-28 | No sessions and no initialize handshake: every request carries its version and capabilities. server/discover, subscriptions/listen, multi round-trip requests, a required resultType, caching hints, the Mcp-Method and Mcp-Name headers | Key changes |
The 2026-07-28 revision calls versions up to 2025-11-25 legacy (they open a session with an initialize handshake) and 2026-07-28 and later modern (version, identity and capabilities travel in each request). An implementation that speaks both is dual-era. What happens when two of them meet is defined by the specification’s compatibility matrix:
| Client | Server | Outcome | What to do |
|---|---|---|---|
| Modern | Modern | Works | server/discover is optional. On UnsupportedProtocolVersionError, retry with a version from supported |
| Modern | Legacy | Fails | On stdio send server/discover first, so that the failure is deterministic, and show the user an actionable error |
| Dual-era | Modern | Works | The client stays modern |
| Dual-era | Legacy | Works | The probe fails with a non-modern error (or times out) and the client falls back to initialize |
| Legacy | Modern | Fails | A modern-only server SHOULD name the versions it supports in its error to initialize: that message may be all a legacy client can show |
| Legacy | Dual-era | Works | The server answers initialize and serves the client by the negotiated legacy revision |
Legacy against legacy works by the legacy revision and is out of scope of the 2026-07-28 text. Which era to build follows from the table:
- A new server whose clients you control: modern only. It is the smaller implementation, and the clients are yours to upgrade.
- A server with clients you do not control, such as hosts that still send
initialize: dual-era. A server MAY implement both behaviors and serve both eras on one endpoint. - A client for servers you do not control: dual-era, because a modern client against a legacy server fails. On stdio, probe with
server/discoverand fall back on any error that is not a recognized modern error; on Streamable HTTP, attempt a modern request and read the body of a400before falling back. Cache the answer per server process or origin. - A client for servers you control: modern only.
3. Moving from 2025-11-25 to 2026-07-28
Each row is one change from the official 2026-07-28 changelog, with what you do about it and where the site covers it.
| What changed | What to do | Read |
|---|---|---|
Protocol-level sessions and the Mcp-Session-Id header are removed; list endpoints no longer vary per connection | Stop keying state or lists on a session; keep state in your own storage or carry it in requestState | Best practices, section 2 |
The initialize handshake is removed; every request carries its protocol version and client capabilities in _meta | Send the _meta fields on every request; stop waiting for an initialization result | Versioning |
server/discover is added, and servers MUST implement it | Implement it; call it first only when you want the supported versions up front | Discover |
subscriptions/listen replaces the GET endpoint and resources/subscribe | Open one stream with the notification types you want; list again after a change | Resources, section 4 |
Multi round-trip requests replace server-initiated requests such as roots/list, sampling/createMessage and elicitation/create | Answer an input_required result and retry with inputResponses and requestState | Multi round-trip requests |
Every result carries a required resultType | Send "complete" for ordinary results; read it before anything else on the client | Prompts, section 5 |
ttlMs and cacheScope are required on the list results and on resources/read | Send ttlMs: 0 and "private" if you do not know better; use "public" only for lists identical for all users | Tools, Prompts, section 4 |
Mcp-Method and Mcp-Name are required on Streamable HTTP POST requests | Send both; Base64-encode a non-ASCII Mcp-Name; make your gateway forward them | Prompts, section 6 |
The error code for a missing resource changes from -32002 to -32602 | Return -32602; clients should still accept -32002 from older servers | Resources, section 7 |
ping, logging/setLevel and notifications/roots/list_changed are removed | Set the log level per request in _meta; stop sending the removed messages | Changelog |
| Experimental tasks move out of the core protocol into an official extension | Treat tasks as an extension you negotiate through extensions | Extensions |
SSE stream resumability and the Last-Event-ID header are removed | A broken response stream loses the request: the client re-issues it | Changelog |
4. The path in stages
Each stage ends with something you can show. The scripts named below are the ones embedded in the pages they come from. Each exits with code 0 when no check FAILs and with code 1 when at least one does.
| Stage | Do | Checkpoint | Page |
|---|---|---|---|
| 1. Understand | Read the architecture and the server and client concepts. Learn who controls each primitive: the user (prompts), the application (resources), the model (tools) | You can name the controller of each primitive in your design | Architecture, Prompts, section 1 |
| 2. Build and run | Build a server with an official SDK, following the guide for your language | The Inspector connects, negotiates a version and lists your tools: inspector_smoke.py exits with code 0 | Build a server, Inspector guide |
| 3. Primitives | Define tools, resources and prompts the way their pages describe, including the failure cases | lint_tools.py, lint_resources.py and lint_prompts.py each exit with code 0 on your endpoint | Tools, Resources, Prompts |
| 4. Remote and secure | Serve over Streamable HTTP, add authorization, check Origin, token audience and the headers | probe_http.py exits with code 0 against staging | Best practices, Authorization |
| 5. Ship | Describe the server in server.json, prove ownership, publish | preflight.py exits with code 0 | Publishing |
| 6. Stay compatible | Decide the era (section 2), test the rows of the compatibility table that apply to you | Each applicable row ends as the table says, not as a hang | Versioning |
5. When something does not work
| Symptom | Most likely cause | Go to |
|---|---|---|
| A 2026-07-28 client rejects your list result | The result lacks resultType, ttlMs or cacheScope (the 2025-11-25 shape) | The list-envelope check of the three linters; Prompts, section 9 |
HTTP 400 with -32020 on every request | Mcp-Method or Mcp-Name is missing, differs from the body, or a non-ASCII value was not Base64-encoded | Prompts, section 6 |
-32022 Unsupported protocol version | The server does not implement the version you sent | Retry with a version from data.supported; Versioning |
| A legacy client cannot connect to your new server | A modern-only server rejects initialize | The Legacy and Modern row of section 2; build dual-era or upgrade the client |
resources/subscribe answers 404 with -32601 | The method no longer exists in 2026-07-28 | Use subscriptions/listen; Resources, section 4 |
A missing resource answers -32002 | The server is on 2025-11-25 or earlier | Accept both codes in the client; Resources, section 7 |
| The model keeps calling the wrong tool, or sends invalid arguments | The definition does not let the model recover | Tools, Best practices, section 4 |
| The server works locally and fails behind a gateway | A gateway drops or rewrites Mcp-Method, Mcp-Name or MCP-Protocol-Version | probe_http.py; Best practices, section 5 |
Versions and Verification
Everything above is about the 2026-07-28 revision and the four before it; the facts come from the official changelogs and the versioning page of the specification, pinned to one commit.
Sources
- The official changelogs: 2025-03-26, 2025-06-18, 2025-11-25, 2026-07-28
- The official versioning and compatibility page, including the compatibility matrix
- This site’s MCP tools explained, MCP resources explained, MCP prompts explained, MCP server best practices, MCP Inspector and Publishing to the registry