Thomas Piacentini
← Workshop

Cheat sheet

Model Context Protocol (MCP)

Original text · English

Build, call and serve stateless MCP 2026-07-28 servers over stdio and Streamable HTTP.

Tasks

TaskCode
Serve over stdiopython3 main.py
Run the stdio demopython3 main.py --demo
Serve Streamable HTTPpython3 main.py --serve
Probe the HTTP serverpython3 main.py --probe
Discover a servermake_request(1, "server/discover")
List toolsmake_request(2, "tools/list")
Call a toolmake_request(5, "tools/call", {"name": "<tool>", "arguments": <args>})
Add a legacy peerclient.add_server("<name>", <transport>, allow_legacy=True)
Sources

Define and run a tool

TOOLS = [{
"name": "notes_search",name the model sees
"description": "Search note titles and bodies by keyword.",
"inputSchema": {"type": "object",JSON Schema of arguments
"properties": {"query": {"type": "string"}},
"required": ["query"]},
"annotations": {"readOnlyHint": True},hint only
}]
def handle_tools_call(params):
executor = TOOL_EXECUTORS.get(params.get("name"))
if executor is None:unknown tool
return complete({"content": [...], "isError": True})
content = executor(params.get("arguments", {}))
return complete({"content": content, "isError": False})
Sources

One tools/call over HTTP

POST /mcpone message per POST
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28equals the _meta version
Mcp-Method: tools/callequals the JSON-RPC method
Mcp-Name: notes_searchequals params.name
{"jsonrpc": "2.0", "id": 7, "method": "tools/call",
"params": {"name": "notes_search", "arguments": {"query": "MCP"},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}}}}
Sources

Architecture

routesubprocesstokenPOSTstdin/mcphandlerAgent hostmerges tool catalogsMultiServerClientone Peer per serverstdioone JSON line eachMCP serverdispatch, HANDLERSAuthorization serverissues tokensStreamable HTTPPOST /mcpPrimitivestools, resources, prompts
Nodes and connections
NodeRoleKind
Agent hostmerges tool catalogs
MultiServerClientone Peer per server
stdioone JSON line each
MCP serverdispatch, HANDLERS
Authorization serverissues tokens
Streamable HTTPPOST /mcp
Primitivestools, resources, prompts
FromToConnection
Agent hostMultiServerClientroute
MultiServerClientstdiosubprocess
MultiServerClientAuthorization servertoken
MultiServerClientStreamable HTTPPOST
stdioMCP serverstdin
Streamable HTTPMCP server/mcp
MCP serverPrimitiveshandler
Sources

Discover, list, call

  1. Client Sends server/discover with version and capabilities in params._meta.
  2. Server Returns supportedVersions and capabilities. No session starts.
  3. Client Sends tools/list. Gets a sorted list with ttlMs and cacheScope.
  4. Client Sends tools/call with name, arguments and a new id.
  5. Server validate_request() checks params._meta. A bad version gives -32022.
  6. Server dispatch() routes by method to handle_tools_call().
  7. Server Returns resultType complete with content and isError.
Sources

Methods

CommandWhat it does
server/discoverVersions, capabilities, instructions, server identity
tools/listLists tools. Cacheable
tools/callRuns one tool. Returns content blocks
resources/listLists URI-addressed data. Cacheable
resources/readReads one resource by URI
prompts/list, prompts/getLists and renders prompt templates
subscriptions/listenLong-lived POST stream for change events
notifications/cancelledCancels a request on stdio. No response
Sources

Key concepts

Stateless core
Every request carries the metadata to interpret it.
Request metadata
Version and capabilities are required. clientInfo is recommended.
Complete result
Every success has resultType set to complete.
Cacheable result
Lists, reads and discovery carry ttlMs and cacheScope.
Tool annotations
readOnlyHint, destructiveHint are hints. The server enforces.
stdio
Client-launched subprocess. JSON on stdout, logs on stderr.
Streamable HTTP
One /mcp endpoint. Each message is a new POST.
Legacy era
Up to 2025-11-25, initialize then notifications/initialized.
Sources

Defaults

SettingDefaultNote
PROTOCOL_VERSION2026-07-28Modern revision in the lessons
server/discover ttl3_600_000ms, cacheScope public
tools/list ttl60_000ms, cacheScope public
resources/read ttl5_000ms, cacheScope private
cache_scopeprivatecomplete() default
HTTP port8017--serve binds 127.0.0.1
MAX_REQUEST_BYTES1_048_576HTTP body cap
legacy_probe_timeout_ms1_000Deadline for one initialize probe
Sources

Troubleshooting

SymptomCauseFix
-32022Version not supportedPick from data.supported. Retry, new id
-32602Missing request metadataStamp _meta on every request
HTTP 400 -32020Header disagrees with bodyMirror version, method and name exactly
HTTP 403 on POSTOrigin not allowedAdd the exact origin to the allowlist
HTTP 405GET or DELETE on /mcpPOST, and subscriptions/listen for events
stdio client fails to parseLogs on stdoutWrite diagnostics to stderr
HTTP 401Invalid, expired or wrong-audience tokenGet a token for this resource
403 insufficient_scopeTool needs more scopeStep up with the challenge scope
Sources

Glossary

JSON-RPC 2.0
Envelope of every MCP request, response and notification.
SSE
text/event-stream response. Notifications, then the final response.
MRTR
Multi Round-Trip Request. input_required, then a fresh retry.
PKCE
Verifier and S256 challenge. Protects an intercepted code.
CIMD
HTTPS metadata document whose URL is the client id.
DCR
Deprecated dynamic client enrollment for compatibility.
RFC 9728
Protected-resource metadata. Names the authorization servers.
Sources

Questions and answers

Tool error or protocol error?
isError: true for a valid call that fails. JSON-RPC error for a bad request.
Can a server call the client?
No. It returns input_required. The client retries with a new id.
How do I cancel?
stdio: send notifications/cancelled. HTTP: close the response stream.
Does stdio need OAuth?
No. Authorization applies to HTTP transports.
When do I send initialize?
Only to an allowlisted legacy peer. Never by default.
Sources

Tips

  • Sort list items. Stable order gives stable cache keys and model context.
  • Bind local servers to 127.0.0.1. Origin checks are not authentication.
  • Match origins exactly. Prefix checks accept attacker suffixes.
  • Send the bearer token in Authorization. Never in the URL.
  • Return an explicit state handle. Sticky routing breaks on failover.
  • On a tool name collision, prefix or reject. Never overwrite.
Sources
Original source
---
id: mcp
title: Model Context Protocol (MCP)
code: MC
subtitle: Build, call and serve stateless MCP 2026-07-28 servers over stdio and Streamable HTTP.
hue: 160
sources: phases/13-tools-and-protocols/06-mcp-fundamentals, phases/13-tools-and-protocols/07-building-an-mcp-server, phases/13-tools-and-protocols/08-building-an-mcp-client, phases/13-tools-and-protocols/09-mcp-transports, phases/13-tools-and-protocols/12-mcp-roots-and-elicitation, phases/13-tools-and-protocols/16-mcp-security-oauth-2-1, phases/13-tools-and-protocols/29-mcp-reliability-cancellation-and-flow-control
source_hash: 971c475a75b5a0e9
---

## Tasks
type: task-table
from: phases/13-tools-and-protocols/07-building-an-mcp-server/docs/en.md, phases/13-tools-and-protocols/07-building-an-mcp-server/code/main.py, phases/13-tools-and-protocols/08-building-an-mcp-client/code/main.py, phases/13-tools-and-protocols/09-mcp-transports/docs/en.md, phases/13-tools-and-protocols/09-mcp-transports/code/main.py

| Task | Code |
|---|---|
| Serve over stdio | `python3 main.py` |
| Run the stdio demo | `python3 main.py --demo` |
| Serve Streamable HTTP | `python3 main.py --serve` |
| Probe the HTTP server | `python3 main.py --probe` |
| Discover a server | `make_request(1, "server/discover")` |
| List tools | `make_request(2, "tools/list")` |
| Call a tool | `make_request(5, "tools/call", {"name": "<tool>", "arguments": <args>})` |
| Add a legacy peer | `client.add_server("<name>", <transport>, allow_legacy=True)` |

## Define and run a tool
type: annotated-code
lang: python
from: phases/13-tools-and-protocols/07-building-an-mcp-server/code/main.py

```python
TOOLS = [{
    "name": "notes_search",                    # name the model sees
    "description": "Search note titles and bodies by keyword.",
    "inputSchema": {"type": "object",          # JSON Schema of arguments
        "properties": {"query": {"type": "string"}},
        "required": ["query"]},
    "annotations": {"readOnlyHint": True},     # hint only
}]
def handle_tools_call(params):
    executor = TOOL_EXECUTORS.get(params.get("name"))
    if executor is None:                       # unknown tool
        return complete({"content": [...], "isError": True})
    content = executor(params.get("arguments", {}))
    return complete({"content": content, "isError": False})
```

## One tools/call over HTTP
type: annotated-code
lang: text
from: phases/13-tools-and-protocols/09-mcp-transports/docs/en.md, phases/13-tools-and-protocols/06-mcp-fundamentals/docs/en.md

```text
POST /mcp                                  # one message per POST
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28           # equals the _meta version
Mcp-Method: tools/call                     # equals the JSON-RPC method
Mcp-Name: notes_search                     # equals params.name
{"jsonrpc": "2.0", "id": 7, "method": "tools/call",
 "params": {"name": "notes_search", "arguments": {"query": "MCP"},
  "_meta": {
   "io.modelcontextprotocol/protocolVersion": "2026-07-28",
   "io.modelcontextprotocol/clientCapabilities": {}}}}
```

## Architecture
type: architecture
from: phases/13-tools-and-protocols/08-building-an-mcp-client/docs/en.md, phases/13-tools-and-protocols/08-building-an-mcp-client/code/main.py, phases/13-tools-and-protocols/07-building-an-mcp-server/code/main.py, phases/13-tools-and-protocols/09-mcp-transports/docs/en.md, phases/13-tools-and-protocols/16-mcp-security-oauth-2-1/docs/en.md

```arch
node | host | 1,1 | Agent host | merges tool catalogs
node | client | 2,1 | MultiServerClient | one Peer per server
node | stdio | 3,1 | stdio | one JSON line each
node | server | 4,1 | MCP server | dispatch, HANDLERS
node | authz | 2,2 | Authorization server | issues tokens
node | http | 3,2 | Streamable HTTP | POST /mcp
node | prims | 4,2 | Primitives | tools, resources, prompts
edge | host | client | route
edge | client | stdio | subprocess
edge | client | authz | token
edge | client | http | POST
edge | stdio | server | stdin
edge | http | server | /mcp
edge | server | prims | handler
```

## Discover, list, call
type: flow
from: phases/13-tools-and-protocols/06-mcp-fundamentals/docs/en.md, phases/13-tools-and-protocols/07-building-an-mcp-server/code/main.py, phases/13-tools-and-protocols/07-building-an-mcp-server/docs/en.md

1. **Client**: Sends `server/discover` with version and capabilities in `params._meta`.
2. **Server**: Returns `supportedVersions` and `capabilities`. No session starts.
3. **Client**: Sends `tools/list`. Gets a sorted list with `ttlMs` and `cacheScope`.
4. **Client**: Sends `tools/call` with `name`, `arguments` and a new `id`.
5. **Server**: `validate_request()` checks `params._meta`. A bad version gives `-32022`.
6. **Server**: `dispatch()` routes by `method` to `handle_tools_call()`.
7. **Server**: Returns `resultType` complete with `content` and `isError`.

## Methods
type: command-table
from: phases/13-tools-and-protocols/06-mcp-fundamentals/docs/en.md, phases/13-tools-and-protocols/09-mcp-transports/docs/en.md, phases/13-tools-and-protocols/29-mcp-reliability-cancellation-and-flow-control/docs/en.md

| Command | What it does |
|---|---|
| `server/discover` | Versions, capabilities, instructions, server identity |
| `tools/list` | Lists tools. Cacheable |
| `tools/call` | Runs one tool. Returns content blocks |
| `resources/list` | Lists URI-addressed data. Cacheable |
| `resources/read` | Reads one resource by URI |
| `prompts/list`, `prompts/get` | Lists and renders prompt templates |
| `subscriptions/listen` | Long-lived POST stream for change events |
| `notifications/cancelled` | Cancels a request on stdio. No response |

## Key concepts
type: concepts
from: phases/13-tools-and-protocols/06-mcp-fundamentals/docs/en.md, phases/13-tools-and-protocols/07-building-an-mcp-server/docs/en.md, phases/13-tools-and-protocols/09-mcp-transports/docs/en.md

- **Stateless core**: Every request carries the metadata to interpret it.
- **Request metadata**: Version and capabilities are required. `clientInfo` is recommended.
- **Complete result**: Every success has `resultType` set to complete.
- **Cacheable result**: Lists, reads and discovery carry `ttlMs` and `cacheScope`.
- **Tool annotations**: `readOnlyHint`, `destructiveHint` are hints. The server enforces.
- **stdio**: Client-launched subprocess. JSON on stdout, logs on stderr.
- **Streamable HTTP**: One `/mcp` endpoint. Each message is a new POST.
- **Legacy era**: Up to `2025-11-25`, `initialize` then `notifications/initialized`.

## Defaults
type: defaults
from: phases/13-tools-and-protocols/07-building-an-mcp-server/code/main.py, phases/13-tools-and-protocols/08-building-an-mcp-client/code/main.py, phases/13-tools-and-protocols/09-mcp-transports/code/main.py

| Setting | Default | Note |
|---|---|---|
| `PROTOCOL_VERSION` | 2026-07-28 | Modern revision in the lessons |
| `server/discover` ttl | 3_600_000 | ms, `cacheScope` public |
| `tools/list` ttl | 60_000 | ms, `cacheScope` public |
| `resources/read` ttl | 5_000 | ms, `cacheScope` private |
| `cache_scope` | private | `complete()` default |
| HTTP port | 8017 | `--serve` binds 127.0.0.1 |
| `MAX_REQUEST_BYTES` | 1_048_576 | HTTP body cap |
| `legacy_probe_timeout_ms` | 1_000 | Deadline for one `initialize` probe |

## Troubleshooting
type: troubleshooting
from: phases/13-tools-and-protocols/06-mcp-fundamentals/docs/en.md, phases/13-tools-and-protocols/07-building-an-mcp-server/docs/en.md, phases/13-tools-and-protocols/08-building-an-mcp-client/docs/en.md, phases/13-tools-and-protocols/09-mcp-transports/docs/en.md, phases/13-tools-and-protocols/16-mcp-security-oauth-2-1/docs/en.md

| Symptom | Cause | Fix |
|---|---|---|
| `-32022` | Version not supported | Pick from `data.supported`. Retry, new id |
| `-32602` | Missing request metadata | Stamp `_meta` on every request |
| HTTP 400 `-32020` | Header disagrees with body | Mirror version, method and name exactly |
| HTTP 403 on POST | `Origin` not allowed | Add the exact origin to the allowlist |
| HTTP 405 | GET or DELETE on `/mcp` | POST, and `subscriptions/listen` for events |
| stdio client fails to parse | Logs on stdout | Write diagnostics to stderr |
| HTTP 401 | Invalid, expired or wrong-audience token | Get a token for this resource |
| 403 `insufficient_scope` | Tool needs more scope | Step up with the challenge scope |

## Glossary
type: glossary
from: phases/13-tools-and-protocols/06-mcp-fundamentals/docs/en.md, phases/13-tools-and-protocols/09-mcp-transports/docs/en.md, phases/13-tools-and-protocols/12-mcp-roots-and-elicitation/docs/en.md, phases/13-tools-and-protocols/16-mcp-security-oauth-2-1/docs/en.md

- **JSON-RPC 2.0**: Envelope of every MCP request, response and notification.
- **SSE**: `text/event-stream` response. Notifications, then the final response.
- **MRTR**: Multi Round-Trip Request. `input_required`, then a fresh retry.
- **PKCE**: Verifier and S256 challenge. Protects an intercepted code.
- **CIMD**: HTTPS metadata document whose URL is the client id.
- **DCR**: Deprecated dynamic client enrollment for compatibility.
- **RFC 9728**: Protected-resource metadata. Names the authorization servers.

## Questions and answers
type: qa
from: phases/13-tools-and-protocols/07-building-an-mcp-server/docs/en.md, phases/13-tools-and-protocols/08-building-an-mcp-client/docs/en.md, phases/13-tools-and-protocols/16-mcp-security-oauth-2-1/docs/en.md, phases/13-tools-and-protocols/29-mcp-reliability-cancellation-and-flow-control/docs/en.md, phases/13-tools-and-protocols/06-mcp-fundamentals/docs/en.md

- **Tool error or protocol error?** `isError: true` for a valid call that fails. JSON-RPC error for a bad request.
- **Can a server call the client?** No. It returns `input_required`. The client retries with a new id.
- **How do I cancel?** stdio: send `notifications/cancelled`. HTTP: close the response stream.
- **Does stdio need OAuth?** No. Authorization applies to HTTP transports.
- **When do I send `initialize`?** Only to an allowlisted legacy peer. Never by default.

## Tips
type: tips
from: phases/13-tools-and-protocols/06-mcp-fundamentals/docs/en.md, phases/13-tools-and-protocols/08-building-an-mcp-client/docs/en.md, phases/13-tools-and-protocols/09-mcp-transports/docs/en.md, phases/13-tools-and-protocols/16-mcp-security-oauth-2-1/docs/en.md

- Sort list items. Stable order gives stable cache keys and model context.
- Bind local servers to `127.0.0.1`. Origin checks are not authentication.
- Match origins exactly. Prefix checks accept attacker suffixes.
- Send the bearer token in `Authorization`. Never in the URL.
- Return an explicit state handle. Sticky routing breaks on failover.
- On a tool name collision, prefix or reject. Never overwrite.
Code and artifacts 1
What you take away

Saved in your notebook on this device.

Sources and limits

Reading checkpoints are self-reported. Test results are recorded separately and apply to the executed cases.

AI Engineering from Scratch · 3f9715c · MIT

Download this file ↓