From 7fabe3c17f5fb3306da905514cf3414f0e591db3 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sat, 7 Feb 2026 13:02:12 +0100 Subject: [PATCH 1/8] docs: restructure documentation with quickstart, concepts, and new nav Set up the foundation for migrating documentation from README.v2.md into proper docs pages served by mkdocs. - Rewrite `docs/index.md` as a concise landing page with card grid - Add `docs/quickstart.md` with a complete getting-started guide - Fill `docs/concepts.md` with protocol architecture, primitives, transports, context, and lifecycle overview - Restructure `mkdocs.yml` nav into Server, Client, and top-level sections - Create `docs/server/` and `docs/client/` directories with placeholder pages for upcoming content - Move `docs/low-level-server.md` to `docs/server/low-level.md` --- docs/client/display-utilities.md | 5 + docs/client/index.md | 5 + docs/client/parsing-results.md | 5 + docs/concepts.md | 141 ++++++++++++++++-- docs/index.md | 89 ++++++----- docs/quickstart.md | 96 ++++++++++++ docs/server/completions.md | 5 + docs/server/context.md | 5 + docs/server/elicitation.md | 5 + docs/server/index.md | 5 + docs/server/logging.md | 5 + .../low-level.md} | 0 docs/server/pagination.md | 5 + docs/server/prompts.md | 5 + docs/server/resources.md | 5 + docs/server/running.md | 5 + docs/server/sampling.md | 5 + docs/server/tools.md | 5 + mkdocs.yml | 26 +++- 19 files changed, 370 insertions(+), 52 deletions(-) create mode 100644 docs/client/display-utilities.md create mode 100644 docs/client/index.md create mode 100644 docs/client/parsing-results.md create mode 100644 docs/quickstart.md create mode 100644 docs/server/completions.md create mode 100644 docs/server/context.md create mode 100644 docs/server/elicitation.md create mode 100644 docs/server/index.md create mode 100644 docs/server/logging.md rename docs/{low-level-server.md => server/low-level.md} (100%) create mode 100644 docs/server/pagination.md create mode 100644 docs/server/prompts.md create mode 100644 docs/server/resources.md create mode 100644 docs/server/running.md create mode 100644 docs/server/sampling.md create mode 100644 docs/server/tools.md diff --git a/docs/client/display-utilities.md b/docs/client/display-utilities.md new file mode 100644 index 000000000..80159c120 --- /dev/null +++ b/docs/client/display-utilities.md @@ -0,0 +1,5 @@ +# Display Utilities + +!!! warning "Under Construction" + + This page is currently being written. Check back soon for complete documentation. diff --git a/docs/client/index.md b/docs/client/index.md new file mode 100644 index 000000000..7e052582b --- /dev/null +++ b/docs/client/index.md @@ -0,0 +1,5 @@ +# Writing MCP Clients + +!!! warning "Under Construction" + + This page is currently being written. Check back soon for complete documentation. diff --git a/docs/client/parsing-results.md b/docs/client/parsing-results.md new file mode 100644 index 000000000..65fe6c26e --- /dev/null +++ b/docs/client/parsing-results.md @@ -0,0 +1,5 @@ +# Parsing Tool Results + +!!! warning "Under Construction" + + This page is currently being written. Check back soon for complete documentation. diff --git a/docs/concepts.md b/docs/concepts.md index a2d6eb8d3..c7ab42d7c 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -1,13 +1,136 @@ # Concepts -!!! warning "Under Construction" +The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data +and functionality to LLM applications in a standardized way. Think of it like a web API, but specifically +designed for LLM interactions. - This page is currently being written. Check back soon for complete documentation. +## Architecture - +MCP follows a client-server architecture: + +- **Hosts** are LLM applications (like Claude Desktop or an IDE) that initiate connections +- **Clients** maintain 1:1 connections with servers, inside the host application +- **Servers** provide context, tools, and prompts to clients + +```text +Host (e.g. Claude Desktop) +├── Client A ↔ Server A (e.g. file system) +├── Client B ↔ Server B (e.g. database) +└── Client C ↔ Server C (e.g. API wrapper) +``` + +## Primitives + +MCP servers expose three core primitives: + +### Resources + +Resources provide data to LLMs — similar to GET endpoints in a REST API. They load information into the +LLM's context without performing computation or causing side effects. + +```python +@mcp.resource("config://app") +def get_config() -> str: + """Expose application configuration.""" + return json.dumps({"theme": "dark", "version": "2.0"}) +``` + +Resources can be static (fixed URI) or use URI templates for dynamic content: + +```python +@mcp.resource("users://{user_id}/profile") +def get_profile(user_id: str) -> str: + """Get a user profile by ID.""" + return json.dumps(load_profile(user_id)) +``` + +See [Resources](server/resources.md) for full documentation. + +### Tools + +Tools let LLMs take actions — similar to POST endpoints. They perform computation, call external APIs, +or produce side effects. + +```python +@mcp.tool() +def send_email(to: str, subject: str, body: str) -> str: + """Send an email to the given recipient.""" + # ... send email logic ... + return f"Email sent to {to}" +``` + +Tools support structured output, progress reporting, and more. See [Tools](server/tools.md) for full documentation. + +### Prompts + +Prompts are reusable templates for LLM interactions. They help standardize common workflows: + +```python +@mcp.prompt() +def review_code(code: str, language: str = "python") -> str: + """Generate a code review prompt.""" + return f"Review this {language} code:\n\n```{language}\n{code}\n```" +``` + +See [Prompts](server/prompts.md) for full documentation. + +## Transports + +MCP supports multiple transport mechanisms for client-server communication: + +| Transport | Use case | How it works | +|---|---|---| +| **Streamable HTTP** | Remote servers, production deployments | HTTP POST with optional SSE streaming | +| **stdio** | Local processes, CLI tools | Communication over stdin/stdout | +| **SSE** | Legacy remote servers | Server-Sent Events over HTTP (deprecated in favor of Streamable HTTP) | + +See [Running Your Server](server/running.md) for transport configuration. + +## Context + +When handling requests, your functions can access a **context object** that provides capabilities +like logging, progress reporting, and access to the current session: + +```python +from mcp.server.mcpserver import Context + +@mcp.tool() +async def long_task(ctx: Context) -> str: + """A tool that reports progress.""" + await ctx.report_progress(0, 100) + # ... do work ... + await ctx.report_progress(100, 100) + return "Done" +``` + +Context enables [logging](server/logging.md), [elicitation](server/elicitation.md), +[sampling](server/sampling.md), and more. See [Context](server/context.md) for details. + +## Server lifecycle + +Servers support a **lifespan** pattern for managing startup and shutdown logic — for example +initializing a database connection pool on startup and closing it on shutdown: + +```python +from contextlib import asynccontextmanager + +@asynccontextmanager +async def app_lifespan(server): + db = await Database.connect() + try: + yield {"db": db} + finally: + await db.disconnect() + +mcp = MCPServer("My App", lifespan=app_lifespan) +``` + +See [Server](server/index.md) for more on lifecycle management. + +## Next steps + +- **[Quickstart](quickstart.md)** — build your first server +- **[Server](server/index.md)** — `MCPServer` configuration and lifecycle +- **[Tools](server/tools.md)**, **[Resources](server/resources.md)**, **[Prompts](server/prompts.md)** — dive into each primitive +- **[Client](client/index.md)** — writing MCP clients +- **[Authorization](authorization.md)** — securing your servers with OAuth 2.1 diff --git a/docs/index.md b/docs/index.md index e096d910b..83d633b21 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,67 +1,80 @@ # MCP Python SDK -The **Model Context Protocol (MCP)** allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. +The **Model Context Protocol (MCP)** allows applications to provide context for LLMs in a standardized way, +separating the concerns of providing context from the actual LLM interaction. This Python SDK implements the full MCP specification, making it easy to: - **Build MCP servers** that expose resources, prompts, and tools -- **Create MCP clients** that can connect to any MCP server +- **Create MCP clients** that connect to any MCP server - **Use standard transports** like stdio, SSE, and Streamable HTTP -If you want to read more about the specification, please visit the [MCP documentation](https://modelcontextprotocol.io). +## Quick example -## Quick Example - -Here's a simple MCP server that exposes a tool, resource, and prompt: - -```python title="server.py" +```python from mcp.server.mcpserver import MCPServer -mcp = MCPServer("Test Server", json_response=True) - +mcp = MCPServer("Demo") @mcp.tool() def add(a: int, b: int) -> int: - """Add two numbers""" + """Add two numbers.""" return a + b - -@mcp.resource("greeting://{name}") -def get_greeting(name: str) -> str: - """Get a personalized greeting""" - return f"Hello, {name}!" - - -@mcp.prompt() -def greet_user(name: str, style: str = "friendly") -> str: - """Generate a greeting prompt""" - return f"Write a {style} greeting for someone named {name}." - - if __name__ == "__main__": mcp.run(transport="streamable-http") ``` -Run the server: - ```bash uv run --with mcp server.py ``` -Then open the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) and connect to `http://localhost:8000/mcp`: +## Getting started -```bash -npx -y @modelcontextprotocol/inspector -``` +1. **[Install](installation.md)** the SDK +2. **[Quickstart](quickstart.md)** — build your first MCP server +3. **[Concepts](concepts.md)** — understand the protocol architecture and primitives + +## Documentation + +
+ +- **Server** + + --- + + Build MCP servers with tools, resources, and prompts. + + [:octicons-arrow-right-24: Server guide](server/index.md) + +- **Client** + + --- + + Connect to MCP servers and invoke tools. + + [:octicons-arrow-right-24: Client guide](client/index.md) + +- **Authorization** + + --- + + Secure your servers with OAuth 2.1. + + [:octicons-arrow-right-24: Authorization](authorization.md) + +- **Testing** + + --- + + Test your servers with the in-memory `Client`. -## Getting Started + [:octicons-arrow-right-24: Testing](testing.md) - -1. **[Install](installation.md)** the MCP SDK -2. **[Learn concepts](concepts.md)** - understand the three primitives and architecture -3. **[Explore authorization](authorization.md)** - add security to your servers -4. **[Use low-level APIs](low-level-server.md)** - for advanced customization +
-## API Reference +## Links -Full API documentation is available in the [API Reference](api.md). +- [MCP specification](https://modelcontextprotocol.io) +- [API Reference](api.md) +- [Migration guide](migration.md) (v1 → v2) diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 000000000..8b95a3d56 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,96 @@ +# Quickstart + +This guide will get you up and running with a simple MCP server in minutes. + +## Prerequisites + +You'll need Python 3.10+ and [uv](https://docs.astral.sh/uv/) (recommended) or pip. + +## Create a server + +Create a file called `server.py` with a tool, a resource, and a prompt: + + +```python +"""MCPServer quickstart example. + +Run from the repository root: + uv run examples/snippets/servers/mcpserver_quickstart.py +""" + +from mcp.server.mcpserver import MCPServer + +# Create an MCP server +mcp = MCPServer("Demo") + + +# Add an addition tool +@mcp.tool() +def add(a: int, b: int) -> int: + """Add two numbers""" + return a + b + + +# Add a dynamic greeting resource +@mcp.resource("greeting://{name}") +def get_greeting(name: str) -> str: + """Get a personalized greeting""" + return f"Hello, {name}!" + + +# Add a prompt +@mcp.prompt() +def greet_user(name: str, style: str = "friendly") -> str: + """Generate a greeting prompt""" + styles = { + "friendly": "Please write a warm, friendly greeting", + "formal": "Please write a formal, professional greeting", + "casual": "Please write a casual, relaxed greeting", + } + + return f"{styles.get(style, styles['friendly'])} for someone named {name}." + + +# Run with streamable HTTP transport +if __name__ == "__main__": + mcp.run(transport="streamable-http", json_response=True) +``` + +_Full example: [examples/snippets/servers/mcpserver_quickstart.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/mcpserver_quickstart.py)_ + + +## Run the server + +```bash +uv run --with mcp server.py +``` + +The server starts on `http://localhost:8000/mcp` using Streamable HTTP transport. + +## Connect a client + +=== "Claude Code" + + Add the server to [Claude Code](https://docs.claude.com/en/docs/claude-code/mcp): + + ```bash + claude mcp add --transport http my-server http://localhost:8000/mcp + ``` + +=== "MCP Inspector" + + Use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) to explore your server interactively: + + ```bash + npx -y @modelcontextprotocol/inspector + ``` + + In the inspector UI, connect to `http://localhost:8000/mcp`. + +## Next steps + +- **[Concepts](concepts.md)** — understand the protocol architecture and primitives +- **[Server](server/index.md)** — learn about `MCPServer` configuration and lifecycle +- **[Tools](server/tools.md)**, **[Resources](server/resources.md)**, **[Prompts](server/prompts.md)** — dive into each primitive +- **[Running Your Server](server/running.md)** — transport options and deployment +- **[Testing](testing.md)** — test your server with the `Client` class diff --git a/docs/server/completions.md b/docs/server/completions.md new file mode 100644 index 000000000..dc7357b29 --- /dev/null +++ b/docs/server/completions.md @@ -0,0 +1,5 @@ +# Completions + +!!! warning "Under Construction" + + This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/context.md b/docs/server/context.md new file mode 100644 index 000000000..97629d842 --- /dev/null +++ b/docs/server/context.md @@ -0,0 +1,5 @@ +# Context + +!!! warning "Under Construction" + + This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/elicitation.md b/docs/server/elicitation.md new file mode 100644 index 000000000..f8d4cd9fb --- /dev/null +++ b/docs/server/elicitation.md @@ -0,0 +1,5 @@ +# Elicitation + +!!! warning "Under Construction" + + This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/index.md b/docs/server/index.md new file mode 100644 index 000000000..4b031f0f5 --- /dev/null +++ b/docs/server/index.md @@ -0,0 +1,5 @@ +# Server + +!!! warning "Under Construction" + + This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/logging.md b/docs/server/logging.md new file mode 100644 index 000000000..d61817cd5 --- /dev/null +++ b/docs/server/logging.md @@ -0,0 +1,5 @@ +# Logging and Notifications + +!!! warning "Under Construction" + + This page is currently being written. Check back soon for complete documentation. diff --git a/docs/low-level-server.md b/docs/server/low-level.md similarity index 100% rename from docs/low-level-server.md rename to docs/server/low-level.md diff --git a/docs/server/pagination.md b/docs/server/pagination.md new file mode 100644 index 000000000..229c1d799 --- /dev/null +++ b/docs/server/pagination.md @@ -0,0 +1,5 @@ +# Pagination + +!!! warning "Under Construction" + + This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/prompts.md b/docs/server/prompts.md new file mode 100644 index 000000000..744a7df40 --- /dev/null +++ b/docs/server/prompts.md @@ -0,0 +1,5 @@ +# Prompts + +!!! warning "Under Construction" + + This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/resources.md b/docs/server/resources.md new file mode 100644 index 000000000..a9a4d7b11 --- /dev/null +++ b/docs/server/resources.md @@ -0,0 +1,5 @@ +# Resources + +!!! warning "Under Construction" + + This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/running.md b/docs/server/running.md new file mode 100644 index 000000000..cabc90154 --- /dev/null +++ b/docs/server/running.md @@ -0,0 +1,5 @@ +# Running Your Server + +!!! warning "Under Construction" + + This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/sampling.md b/docs/server/sampling.md new file mode 100644 index 000000000..12ff04745 --- /dev/null +++ b/docs/server/sampling.md @@ -0,0 +1,5 @@ +# Sampling + +!!! warning "Under Construction" + + This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/tools.md b/docs/server/tools.md new file mode 100644 index 000000000..22a0c58b3 --- /dev/null +++ b/docs/server/tools.md @@ -0,0 +1,5 @@ +# Tools + +!!! warning "Under Construction" + + This page is currently being written. Check back soon for complete documentation. diff --git a/mkdocs.yml b/mkdocs.yml index 3019f5214..d1d9f4070 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,12 +13,28 @@ site_url: https://modelcontextprotocol.github.io/python-sdk nav: - Introduction: index.md - Installation: installation.md + - Quickstart: quickstart.md + - Concepts: concepts.md + - Server: + - Overview: server/index.md + - Resources: server/resources.md + - Tools: server/tools.md + - Prompts: server/prompts.md + - Context: server/context.md + - Completions: server/completions.md + - Elicitation: server/elicitation.md + - Sampling: server/sampling.md + - Logging: server/logging.md + - Running Your Server: server/running.md + - Low-Level Server: server/low-level.md + - Pagination: server/pagination.md + - Client: + - Writing Clients: client/index.md + - Display Utilities: client/display-utilities.md + - Parsing Results: client/parsing-results.md + - Authorization: authorization.md + - Testing: testing.md - Migration Guide: migration.md - - Documentation: - - Concepts: concepts.md - - Low-Level Server: low-level-server.md - - Authorization: authorization.md - - Testing: testing.md - Experimental: - Overview: experimental/index.md - Tasks: From f4e091890d58269028ecd545f1a321b29302b0ec Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sat, 7 Feb 2026 13:05:24 +0100 Subject: [PATCH 2/8] docs: remove placeholder pages, keep only real content Remove all empty "Under Construction" placeholder pages for server/ and client/ directories. Only pages with actual content should exist. - Remove docs/server/ and docs/client/ placeholder pages - Strip mkdocs.yml nav to only pages with real content - Remove broken links from concepts.md and quickstart.md (replaced with TODO comments for when those pages are created) - Simplify index.md by removing card grid linking to non-existent pages --- docs/client/display-utilities.md | 5 ----- docs/client/index.md | 5 ----- docs/client/parsing-results.md | 5 ----- docs/concepts.md | 20 ++++++++--------- docs/index.md | 38 -------------------------------- docs/quickstart.md | 4 +--- docs/server/completions.md | 5 ----- docs/server/context.md | 5 ----- docs/server/elicitation.md | 5 ----- docs/server/index.md | 5 ----- docs/server/logging.md | 5 ----- docs/server/low-level.md | 5 ----- docs/server/pagination.md | 5 ----- docs/server/prompts.md | 5 ----- docs/server/resources.md | 5 ----- docs/server/running.md | 5 ----- docs/server/sampling.md | 5 ----- docs/server/tools.md | 5 ----- mkdocs.yml | 17 -------------- 19 files changed, 11 insertions(+), 143 deletions(-) delete mode 100644 docs/client/display-utilities.md delete mode 100644 docs/client/index.md delete mode 100644 docs/client/parsing-results.md delete mode 100644 docs/server/completions.md delete mode 100644 docs/server/context.md delete mode 100644 docs/server/elicitation.md delete mode 100644 docs/server/index.md delete mode 100644 docs/server/logging.md delete mode 100644 docs/server/low-level.md delete mode 100644 docs/server/pagination.md delete mode 100644 docs/server/prompts.md delete mode 100644 docs/server/resources.md delete mode 100644 docs/server/running.md delete mode 100644 docs/server/sampling.md delete mode 100644 docs/server/tools.md diff --git a/docs/client/display-utilities.md b/docs/client/display-utilities.md deleted file mode 100644 index 80159c120..000000000 --- a/docs/client/display-utilities.md +++ /dev/null @@ -1,5 +0,0 @@ -# Display Utilities - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/docs/client/index.md b/docs/client/index.md deleted file mode 100644 index 7e052582b..000000000 --- a/docs/client/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Writing MCP Clients - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/docs/client/parsing-results.md b/docs/client/parsing-results.md deleted file mode 100644 index 65fe6c26e..000000000 --- a/docs/client/parsing-results.md +++ /dev/null @@ -1,5 +0,0 @@ -# Parsing Tool Results - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/docs/concepts.md b/docs/concepts.md index c7ab42d7c..5a46bdf0f 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -44,7 +44,7 @@ def get_profile(user_id: str) -> str: return json.dumps(load_profile(user_id)) ``` -See [Resources](server/resources.md) for full documentation. + ### Tools @@ -59,7 +59,8 @@ def send_email(to: str, subject: str, body: str) -> str: return f"Email sent to {to}" ``` -Tools support structured output, progress reporting, and more. See [Tools](server/tools.md) for full documentation. +Tools support structured output, progress reporting, and more. + ### Prompts @@ -72,7 +73,7 @@ def review_code(code: str, language: str = "python") -> str: return f"Review this {language} code:\n\n```{language}\n{code}\n```" ``` -See [Prompts](server/prompts.md) for full documentation. + ## Transports @@ -84,7 +85,7 @@ MCP supports multiple transport mechanisms for client-server communication: | **stdio** | Local processes, CLI tools | Communication over stdin/stdout | | **SSE** | Legacy remote servers | Server-Sent Events over HTTP (deprecated in favor of Streamable HTTP) | -See [Running Your Server](server/running.md) for transport configuration. + ## Context @@ -103,8 +104,8 @@ async def long_task(ctx: Context) -> str: return "Done" ``` -Context enables [logging](server/logging.md), [elicitation](server/elicitation.md), -[sampling](server/sampling.md), and more. See [Context](server/context.md) for details. +Context enables logging, elicitation, sampling, and more. + ## Server lifecycle @@ -125,12 +126,11 @@ async def app_lifespan(server): mcp = MCPServer("My App", lifespan=app_lifespan) ``` -See [Server](server/index.md) for more on lifecycle management. + ## Next steps - **[Quickstart](quickstart.md)** — build your first server -- **[Server](server/index.md)** — `MCPServer` configuration and lifecycle -- **[Tools](server/tools.md)**, **[Resources](server/resources.md)**, **[Prompts](server/prompts.md)** — dive into each primitive -- **[Client](client/index.md)** — writing MCP clients +- **[Testing](testing.md)** — test your server with the `Client` class - **[Authorization](authorization.md)** — securing your servers with OAuth 2.1 +- **[API Reference](api.md)** — full API documentation diff --git a/docs/index.md b/docs/index.md index 83d633b21..7f25a6af4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -35,44 +35,6 @@ uv run --with mcp server.py 2. **[Quickstart](quickstart.md)** — build your first MCP server 3. **[Concepts](concepts.md)** — understand the protocol architecture and primitives -## Documentation - -
- -- **Server** - - --- - - Build MCP servers with tools, resources, and prompts. - - [:octicons-arrow-right-24: Server guide](server/index.md) - -- **Client** - - --- - - Connect to MCP servers and invoke tools. - - [:octicons-arrow-right-24: Client guide](client/index.md) - -- **Authorization** - - --- - - Secure your servers with OAuth 2.1. - - [:octicons-arrow-right-24: Authorization](authorization.md) - -- **Testing** - - --- - - Test your servers with the in-memory `Client`. - - [:octicons-arrow-right-24: Testing](testing.md) - -
- ## Links - [MCP specification](https://modelcontextprotocol.io) diff --git a/docs/quickstart.md b/docs/quickstart.md index 8b95a3d56..6b2e99a2d 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -90,7 +90,5 @@ The server starts on `http://localhost:8000/mcp` using Streamable HTTP transport ## Next steps - **[Concepts](concepts.md)** — understand the protocol architecture and primitives -- **[Server](server/index.md)** — learn about `MCPServer` configuration and lifecycle -- **[Tools](server/tools.md)**, **[Resources](server/resources.md)**, **[Prompts](server/prompts.md)** — dive into each primitive -- **[Running Your Server](server/running.md)** — transport options and deployment - **[Testing](testing.md)** — test your server with the `Client` class +- **[API Reference](api.md)** — full API documentation diff --git a/docs/server/completions.md b/docs/server/completions.md deleted file mode 100644 index dc7357b29..000000000 --- a/docs/server/completions.md +++ /dev/null @@ -1,5 +0,0 @@ -# Completions - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/context.md b/docs/server/context.md deleted file mode 100644 index 97629d842..000000000 --- a/docs/server/context.md +++ /dev/null @@ -1,5 +0,0 @@ -# Context - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/elicitation.md b/docs/server/elicitation.md deleted file mode 100644 index f8d4cd9fb..000000000 --- a/docs/server/elicitation.md +++ /dev/null @@ -1,5 +0,0 @@ -# Elicitation - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/index.md b/docs/server/index.md deleted file mode 100644 index 4b031f0f5..000000000 --- a/docs/server/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Server - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/logging.md b/docs/server/logging.md deleted file mode 100644 index d61817cd5..000000000 --- a/docs/server/logging.md +++ /dev/null @@ -1,5 +0,0 @@ -# Logging and Notifications - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/low-level.md b/docs/server/low-level.md deleted file mode 100644 index a5b4f3df3..000000000 --- a/docs/server/low-level.md +++ /dev/null @@ -1,5 +0,0 @@ -# Low-Level Server - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/pagination.md b/docs/server/pagination.md deleted file mode 100644 index 229c1d799..000000000 --- a/docs/server/pagination.md +++ /dev/null @@ -1,5 +0,0 @@ -# Pagination - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/prompts.md b/docs/server/prompts.md deleted file mode 100644 index 744a7df40..000000000 --- a/docs/server/prompts.md +++ /dev/null @@ -1,5 +0,0 @@ -# Prompts - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/resources.md b/docs/server/resources.md deleted file mode 100644 index a9a4d7b11..000000000 --- a/docs/server/resources.md +++ /dev/null @@ -1,5 +0,0 @@ -# Resources - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/running.md b/docs/server/running.md deleted file mode 100644 index cabc90154..000000000 --- a/docs/server/running.md +++ /dev/null @@ -1,5 +0,0 @@ -# Running Your Server - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/sampling.md b/docs/server/sampling.md deleted file mode 100644 index 12ff04745..000000000 --- a/docs/server/sampling.md +++ /dev/null @@ -1,5 +0,0 @@ -# Sampling - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/docs/server/tools.md b/docs/server/tools.md deleted file mode 100644 index 22a0c58b3..000000000 --- a/docs/server/tools.md +++ /dev/null @@ -1,5 +0,0 @@ -# Tools - -!!! warning "Under Construction" - - This page is currently being written. Check back soon for complete documentation. diff --git a/mkdocs.yml b/mkdocs.yml index d1d9f4070..8cf0db529 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -15,23 +15,6 @@ nav: - Installation: installation.md - Quickstart: quickstart.md - Concepts: concepts.md - - Server: - - Overview: server/index.md - - Resources: server/resources.md - - Tools: server/tools.md - - Prompts: server/prompts.md - - Context: server/context.md - - Completions: server/completions.md - - Elicitation: server/elicitation.md - - Sampling: server/sampling.md - - Logging: server/logging.md - - Running Your Server: server/running.md - - Low-Level Server: server/low-level.md - - Pagination: server/pagination.md - - Client: - - Writing Clients: client/index.md - - Display Utilities: client/display-utilities.md - - Parsing Results: client/parsing-results.md - Authorization: authorization.md - Testing: testing.md - Migration Guide: migration.md From 9206abd24a66a8624eaba25fa1e585a4d7563217 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sat, 7 Feb 2026 13:09:16 +0100 Subject: [PATCH 3/8] docs: add docs pages to pytest-examples and fix code examples - Add docs/quickstart.md and docs/concepts.md to pytest-examples so all code blocks in documentation are linted by ruff - Fix f-string in concepts.md Prompts example that contained backticks causing a syntax error when parsed standalone --- docs/concepts.md | 2 +- tests/test_examples.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/concepts.md b/docs/concepts.md index 5a46bdf0f..b54828e76 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -70,7 +70,7 @@ Prompts are reusable templates for LLM interactions. They help standardize commo @mcp.prompt() def review_code(code: str, language: str = "python") -> str: """Generate a code review prompt.""" - return f"Review this {language} code:\n\n```{language}\n{code}\n```" + return f"Please review the following {language} code:\n\n{code}" ``` diff --git a/tests/test_examples.py b/tests/test_examples.py index aa9de0957..d92539e3d 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -97,7 +97,11 @@ async def test_desktop(monkeypatch: pytest.MonkeyPatch): # TODO(v2): Change back to README.md when v2 is released -@pytest.mark.parametrize("example", find_examples("README.v2.md"), ids=str) +@pytest.mark.parametrize( + "example", + find_examples("README.v2.md", "docs/quickstart.md", "docs/concepts.md"), + ids=str, +) def test_docs_examples(example: CodeExample, eval_example: EvalExample): ruff_ignore: list[str] = ["F841", "I001", "F821"] # F821: undefined names (snippets lack imports) From 3da0ebc15c914016bada826929708f293e3a816f Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sat, 7 Feb 2026 13:20:06 +0100 Subject: [PATCH 4/8] docs: make all code examples self-contained and runnable Every Python code block in docs pages is now both linted and executed by pytest-examples. - Make all concepts.md examples self-contained with proper imports - Add test_docs_examples_run that executes every doc code block - Use __name__ override to prevent `if __name__ == "__main__"` blocks from starting servers during tests - Support skip-run/skip-lint prefix tags for future use - Drop snippet-source from quickstart.md so the example is self-contained --- docs/concepts.md | 55 +++++++++++++++++++++++++++++++++--------- docs/quickstart.md | 19 +++------------ tests/test_examples.py | 43 +++++++++++++++++++++++++++++---- 3 files changed, 85 insertions(+), 32 deletions(-) diff --git a/docs/concepts.md b/docs/concepts.md index b54828e76..bcbe6b8b9 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -21,27 +21,33 @@ Host (e.g. Claude Desktop) ## Primitives -MCP servers expose three core primitives: +MCP servers expose three core primitives: **resources**, **tools**, and **prompts**. ### Resources Resources provide data to LLMs — similar to GET endpoints in a REST API. They load information into the LLM's context without performing computation or causing side effects. +Resources can be static (fixed URI) or use URI templates for dynamic content: + ```python +import json + +from mcp.server.mcpserver import MCPServer + +mcp = MCPServer("Demo") + + @mcp.resource("config://app") def get_config() -> str: """Expose application configuration.""" return json.dumps({"theme": "dark", "version": "2.0"}) -``` -Resources can be static (fixed URI) or use URI templates for dynamic content: -```python @mcp.resource("users://{user_id}/profile") def get_profile(user_id: str) -> str: """Get a user profile by ID.""" - return json.dumps(load_profile(user_id)) + return json.dumps({"user_id": user_id, "name": "Alice"}) ``` @@ -49,13 +55,17 @@ def get_profile(user_id: str) -> str: ### Tools Tools let LLMs take actions — similar to POST endpoints. They perform computation, call external APIs, -or produce side effects. +or produce side effects: ```python +from mcp.server.mcpserver import MCPServer + +mcp = MCPServer("Demo") + + @mcp.tool() def send_email(to: str, subject: str, body: str) -> str: """Send an email to the given recipient.""" - # ... send email logic ... return f"Email sent to {to}" ``` @@ -67,6 +77,11 @@ Tools support structured output, progress reporting, and more. Prompts are reusable templates for LLM interactions. They help standardize common workflows: ```python +from mcp.server.mcpserver import MCPServer + +mcp = MCPServer("Demo") + + @mcp.prompt() def review_code(code: str, language: str = "python") -> str: """Generate a code review prompt.""" @@ -93,7 +108,10 @@ When handling requests, your functions can access a **context object** that prov like logging, progress reporting, and access to the current session: ```python -from mcp.server.mcpserver import Context +from mcp.server.mcpserver import Context, MCPServer + +mcp = MCPServer("Demo") + @mcp.tool() async def long_task(ctx: Context) -> str: @@ -113,15 +131,28 @@ Servers support a **lifespan** pattern for managing startup and shutdown logic initializing a database connection pool on startup and closing it on shutdown: ```python +from collections.abc import AsyncIterator from contextlib import asynccontextmanager +from dataclasses import dataclass + +from mcp.server.mcpserver import MCPServer + + +@dataclass +class AppContext: + db_url: str + @asynccontextmanager -async def app_lifespan(server): - db = await Database.connect() +async def app_lifespan(server: MCPServer) -> AsyncIterator[AppContext]: + # Initialize on startup + ctx = AppContext(db_url="postgresql://localhost/mydb") try: - yield {"db": db} + yield ctx finally: - await db.disconnect() + # Cleanup on shutdown + pass + mcp = MCPServer("My App", lifespan=app_lifespan) ``` diff --git a/docs/quickstart.md b/docs/quickstart.md index 6b2e99a2d..88b6dcd27 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -10,14 +10,7 @@ You'll need Python 3.10+ and [uv](https://docs.astral.sh/uv/) (recommended) or p Create a file called `server.py` with a tool, a resource, and a prompt: - ```python -"""MCPServer quickstart example. - -Run from the repository root: - uv run examples/snippets/servers/mcpserver_quickstart.py -""" - from mcp.server.mcpserver import MCPServer # Create an MCP server @@ -27,21 +20,21 @@ mcp = MCPServer("Demo") # Add an addition tool @mcp.tool() def add(a: int, b: int) -> int: - """Add two numbers""" + """Add two numbers.""" return a + b # Add a dynamic greeting resource @mcp.resource("greeting://{name}") def get_greeting(name: str) -> str: - """Get a personalized greeting""" + """Get a personalized greeting.""" return f"Hello, {name}!" # Add a prompt @mcp.prompt() def greet_user(name: str, style: str = "friendly") -> str: - """Generate a greeting prompt""" + """Generate a greeting prompt.""" styles = { "friendly": "Please write a warm, friendly greeting", "formal": "Please write a formal, professional greeting", @@ -51,14 +44,10 @@ def greet_user(name: str, style: str = "friendly") -> str: return f"{styles.get(style, styles['friendly'])} for someone named {name}." -# Run with streamable HTTP transport if __name__ == "__main__": - mcp.run(transport="streamable-http", json_response=True) + mcp.run(transport="streamable-http") ``` -_Full example: [examples/snippets/servers/mcpserver_quickstart.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/mcpserver_quickstart.py)_ - - ## Run the server ```bash diff --git a/tests/test_examples.py b/tests/test_examples.py index d92539e3d..e750fe697 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -7,6 +7,7 @@ import sys from pathlib import Path +from typing import Any import pytest from inline_snapshot import snapshot @@ -96,20 +97,52 @@ async def test_desktop(monkeypatch: pytest.MonkeyPatch): assert "/fake/path/file2.txt" in content.text +SKIP_RUN_TAGS = ["skip", "skip-run"] +SKIP_LINT_TAGS = ["skip", "skip-lint"] + +# Files with code examples that are both linted and run +DOCS_FILES = ["docs/quickstart.md", "docs/concepts.md"] + + +def _set_eval_config(eval_example: EvalExample) -> None: + eval_example.set_config( + ruff_ignore=["F841", "I001", "F821"], + target_version="py310", + line_length=120, + ) + + # TODO(v2): Change back to README.md when v2 is released @pytest.mark.parametrize( "example", - find_examples("README.v2.md", "docs/quickstart.md", "docs/concepts.md"), + find_examples("README.v2.md", *DOCS_FILES), ids=str, ) def test_docs_examples(example: CodeExample, eval_example: EvalExample): - ruff_ignore: list[str] = ["F841", "I001", "F821"] # F821: undefined names (snippets lack imports) + if any(example.prefix_settings().get(key) == "true" for key in SKIP_LINT_TAGS): + pytest.skip("skip-lint") - # Use project's actual line length of 120 - eval_example.set_config(ruff_ignore=ruff_ignore, target_version="py310", line_length=120) + _set_eval_config(eval_example) - # Use Ruff for both formatting and linting (skip Black) if eval_example.update_examples: # pragma: no cover eval_example.format_ruff(example) else: eval_example.lint_ruff(example) + + +def _get_runnable_docs_examples() -> list[CodeExample]: + examples = find_examples(*DOCS_FILES) + return [ex for ex in examples if not any(ex.prefix_settings().get(key) == "true" for key in SKIP_RUN_TAGS)] + + +@pytest.mark.parametrize("example", _get_runnable_docs_examples(), ids=str) +def test_docs_examples_run(example: CodeExample, eval_example: EvalExample): + _set_eval_config(eval_example) + + # Prevent `if __name__ == "__main__"` blocks from starting servers + globals: dict[str, Any] = {"__name__": "__docs_test__"} + + if eval_example.update_examples: # pragma: no cover + eval_example.run_print_update(example, module_globals=globals) + else: + eval_example.run_print_check(example, module_globals=globals) From 29322362521dc74b2873baac73ff2637dce64b47 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sat, 7 Feb 2026 13:21:50 +0100 Subject: [PATCH 5/8] docs: return dicts from resources instead of json.dumps MCPServer handles serialization, so resources can return dicts directly instead of calling json.dumps manually. --- docs/concepts.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/concepts.md b/docs/concepts.md index bcbe6b8b9..f724767c0 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -31,23 +31,21 @@ LLM's context without performing computation or causing side effects. Resources can be static (fixed URI) or use URI templates for dynamic content: ```python -import json - from mcp.server.mcpserver import MCPServer mcp = MCPServer("Demo") @mcp.resource("config://app") -def get_config() -> str: +def get_config() -> dict: """Expose application configuration.""" - return json.dumps({"theme": "dark", "version": "2.0"}) + return {"theme": "dark", "version": "2.0"} @mcp.resource("users://{user_id}/profile") -def get_profile(user_id: str) -> str: +def get_profile(user_id: str) -> dict: """Get a user profile by ID.""" - return json.dumps({"user_id": user_id, "name": "Alice"}) + return {"user_id": user_id, "name": "Alice"} ``` From d18a2fd007847d759d27da47e0441efa0a840980 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sat, 7 Feb 2026 13:24:09 +0100 Subject: [PATCH 6/8] docs: use properly typed dict[str, str] in resource return types --- docs/concepts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/concepts.md b/docs/concepts.md index f724767c0..64853bddb 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -37,13 +37,13 @@ mcp = MCPServer("Demo") @mcp.resource("config://app") -def get_config() -> dict: +def get_config() -> dict[str, str]: """Expose application configuration.""" return {"theme": "dark", "version": "2.0"} @mcp.resource("users://{user_id}/profile") -def get_profile(user_id: str) -> dict: +def get_profile(user_id: str) -> dict[str, str]: """Get a user profile by ID.""" return {"user_id": user_id, "name": "Alice"} ``` From 392bb35d351a37fe54f9040796885409ea10c01b Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sat, 7 Feb 2026 14:27:32 +0100 Subject: [PATCH 7/8] docs: consolidate pytest-examples into single test and add skip tags Consolidate the separate README.v2.md and docs/ pytest-examples tests into a single unified test using `find_examples("README.v2.md", "docs/")`. Add skip tags to pre-existing code blocks that aren't self-contained: - migration.md: all blocks (before/after migration patterns) - experimental/*.md: all blocks (experimental API patterns) - testing.md: test blocks (reference local server module) - README.v2.md: 4 context-dependent snippets --- README.v2.md | 8 ++--- docs/experimental/index.md | 2 +- docs/experimental/tasks-client.md | 24 ++++++------- docs/experimental/tasks-server.md | 40 ++++++++++----------- docs/experimental/tasks.md | 8 ++--- docs/migration.md | 58 +++++++++++++++---------------- docs/testing.md | 4 +-- tests/test_examples.py | 27 ++++++-------- 8 files changed, 83 insertions(+), 88 deletions(-) diff --git a/README.v2.md b/README.v2.md index 67f181811..a9d9082a3 100644 --- a/README.v2.md +++ b/README.v2.md @@ -595,7 +595,7 @@ _Full example: [examples/snippets/servers/basic_prompt.py](https://github.com/mo MCP servers can provide icons for UI display. Icons can be added to the server implementation, tools, resources, and prompts: -```python +```python skip-run="true" from mcp.server.mcpserver import MCPServer, Icon # Create an icon from a file path or URL @@ -1079,7 +1079,7 @@ The MCPServer server instance accessible via `ctx.mcp_server` provides access to - `stateless_http` - Whether the server operates in stateless mode - And other configuration options -```python +```python skip-run="true" @mcp.tool() def server_info(ctx: Context) -> dict: """Get information about the current server.""" @@ -1106,7 +1106,7 @@ The session object accessible via `ctx.session` provides advanced control over c - `await ctx.session.send_tool_list_changed()` - Notify clients that the tool list changed - `await ctx.session.send_prompt_list_changed()` - Notify clients that the prompt list changed -```python +```python skip-run="true" @mcp.tool() async def notify_data_update(resource_uri: str, ctx: Context) -> str: """Update data and notify clients of the change.""" @@ -1134,7 +1134,7 @@ The request context accessible via `ctx.request_context` contains request-specif - `ctx.request_context.request` - The original MCP request object for advanced processing - `ctx.request_context.request_id` - Unique identifier for this request -```python +```python skip-run="true" # Example with typed lifespan context @dataclass class AppContext: diff --git a/docs/experimental/index.md b/docs/experimental/index.md index 1d496b3f1..cab64f7de 100644 --- a/docs/experimental/index.md +++ b/docs/experimental/index.md @@ -26,7 +26,7 @@ Tasks are useful for: Experimental features are accessed via the `.experimental` property: -```python +```python skip="true" # Server-side @server.experimental.get_task() async def handle_get_task(request: GetTaskRequest) -> GetTaskResult: diff --git a/docs/experimental/tasks-client.md b/docs/experimental/tasks-client.md index 0374ed86b..225079562 100644 --- a/docs/experimental/tasks-client.md +++ b/docs/experimental/tasks-client.md @@ -10,7 +10,7 @@ This guide covers calling task-augmented tools from clients, handling the `input Call a tool as a task and poll for the result: -```python +```python skip="true" from mcp.client.session import ClientSession from mcp.types import CallToolResult @@ -38,7 +38,7 @@ async with ClientSession(read, write) as session: Use `call_tool_as_task()` to invoke a tool with task augmentation: -```python +```python skip="true" result = await session.experimental.call_tool_as_task( "my_tool", # Tool name {"arg": "value"}, # Arguments @@ -62,7 +62,7 @@ The response is a `CreateTaskResult` containing: The `poll_task()` async iterator polls until the task reaches a terminal state: -```python +```python skip="true" async for status in session.experimental.poll_task(task_id): print(f"Status: {status.status}") if status.statusMessage: @@ -79,7 +79,7 @@ It automatically: When a task needs user input (elicitation), it transitions to `input_required`. You must call `get_task_result()` to receive and respond to the elicitation: -```python +```python skip="true" async for status in session.experimental.poll_task(task_id): print(f"Status: {status.status}") @@ -95,7 +95,7 @@ The elicitation callback (set during session creation) handles the actual user i To handle elicitation requests from the server, provide a callback when creating the session: -```python +```python skip="true" from mcp.types import ElicitRequestParams, ElicitResult async def handle_elicitation(context, params: ElicitRequestParams) -> ElicitResult: @@ -124,7 +124,7 @@ async with ClientSession( Similarly, handle sampling requests with a callback: -```python +```python skip="true" from mcp.types import CreateMessageRequestParams, CreateMessageResult, TextContent async def handle_sampling(context, params: CreateMessageRequestParams) -> CreateMessageResult: @@ -150,7 +150,7 @@ async with ClientSession( Once a task completes, retrieve the result: -```python +```python skip="true" if status.status == "completed": result = await session.experimental.get_task_result(task_id, CallToolResult) for content in result.content: @@ -174,7 +174,7 @@ The result type matches the original request: Cancel a running task: -```python +```python skip="true" cancel_result = await session.experimental.cancel_task(task_id) print(f"Cancelled, status: {cancel_result.status}") ``` @@ -185,7 +185,7 @@ Note: Cancellation is cooperative—the server must check for and handle cancell View all tasks on the server: -```python +```python skip="true" result = await session.experimental.list_tasks() for task in result.tasks: print(f"{task.taskId}: {task.status}") @@ -205,7 +205,7 @@ Servers can send task-augmented requests to clients. This is useful when the ser Register task handlers to declare what task-augmented requests your client accepts: -```python +```python skip="true" from mcp.client.experimental.task_handlers import ExperimentalTaskHandlers from mcp.types import ( CreateTaskResult, GetTaskResult, GetTaskPayloadResult, @@ -279,7 +279,7 @@ This enables flows where: A client that handles all task scenarios: -```python +```python skip="true" import anyio from mcp.client.session import ClientSession from mcp.client.stdio import stdio_client @@ -336,7 +336,7 @@ if __name__ == "__main__": Handle task errors gracefully: -```python +```python skip="true" from mcp.shared.exceptions import MCPError try: diff --git a/docs/experimental/tasks-server.md b/docs/experimental/tasks-server.md index 761dc5de5..46759ae71 100644 --- a/docs/experimental/tasks-server.md +++ b/docs/experimental/tasks-server.md @@ -10,7 +10,7 @@ This guide covers implementing task support in MCP servers, from basic setup to The simplest way to add task support: -```python +```python skip="true" from mcp.server import Server from mcp.server.experimental.task_context import ServerTaskContext from mcp.types import CallToolResult, CreateTaskResult, TextContent, Tool, ToolExecution, TASK_REQUIRED @@ -57,7 +57,7 @@ That's it. `enable_tasks()` automatically: Tools declare task support via the `execution.taskSupport` field: -```python +```python skip="true" from mcp.types import Tool, ToolExecution, TASK_REQUIRED, TASK_OPTIONAL, TASK_FORBIDDEN Tool( @@ -75,7 +75,7 @@ Tool( Validate the request matches your tool's requirements: -```python +```python skip="true" @server.call_tool() async def handle_tool(name: str, arguments: dict): ctx = server.request_context @@ -95,7 +95,7 @@ async def handle_tool(name: str, arguments: dict): `run_task()` is the recommended way to execute task work: -```python +```python skip="true" async def handle_my_tool(arguments: dict) -> CreateTaskResult: ctx = server.request_context ctx.experimental.validate_task_mode(TASK_REQUIRED) @@ -127,7 +127,7 @@ async def handle_my_tool(arguments: dict) -> CreateTaskResult: Keep clients informed of progress: -```python +```python skip="true" async def work(task: ServerTaskContext) -> CallToolResult: await task.update_status("Starting...") @@ -149,7 +149,7 @@ Tasks can request user input via elicitation. This transitions the task to `inpu Collect structured data from the user: -```python +```python skip="true" async def work(task: ServerTaskContext) -> CallToolResult: await task.update_status("Waiting for confirmation...") @@ -177,7 +177,7 @@ async def work(task: ServerTaskContext) -> CallToolResult: Direct users to external URLs for OAuth, payments, or other out-of-band flows: -```python +```python skip="true" async def work(task: ServerTaskContext) -> CallToolResult: await task.update_status("Waiting for OAuth...") @@ -198,7 +198,7 @@ async def work(task: ServerTaskContext) -> CallToolResult: Tasks can request LLM completions from the client: -```python +```python skip="true" from mcp.types import SamplingMessage, TextContent async def work(task: ServerTaskContext) -> CallToolResult: @@ -220,7 +220,7 @@ async def work(task: ServerTaskContext) -> CallToolResult: Sampling supports additional parameters: -```python +```python skip="true" result = await task.create_message( messages=[...], max_tokens=500, @@ -235,7 +235,7 @@ result = await task.create_message( Check for cancellation in long-running work: -```python +```python skip="true" async def work(task: ServerTaskContext) -> CallToolResult: for i in range(1000): if task.is_cancelled: @@ -254,7 +254,7 @@ The SDK's default cancel handler updates the task status. Your work function sho For production, implement `TaskStore` with persistent storage: -```python +```python skip="true" from mcp.shared.experimental.tasks.store import TaskStore from mcp.types import Task, TaskMetadata, Result @@ -287,7 +287,7 @@ class RedisTaskStore(TaskStore): Use your custom store: -```python +```python skip="true" store = RedisTaskStore(redis_client) server.experimental.enable_tasks(store=store) ``` @@ -296,7 +296,7 @@ server.experimental.enable_tasks(store=store) A server with multiple task-supporting tools: -```python +```python skip="true" from mcp.server import Server from mcp.server.experimental.task_context import ServerTaskContext from mcp.types import ( @@ -382,7 +382,7 @@ async def handle_tool(name: str, arguments: dict) -> CallToolResult | CreateTask Tasks handle errors automatically, but you can also fail explicitly: -```python +```python skip="true" async def work(task: ServerTaskContext) -> CallToolResult: try: result = await risky_operation() @@ -407,7 +407,7 @@ For custom error messages, call `task.fail()` before raising. For web applications, use the Streamable HTTP transport: -```python +```python skip="true" from collections.abc import AsyncIterator from contextlib import asynccontextmanager @@ -484,7 +484,7 @@ if __name__ == "__main__": Test task functionality with the SDK's testing utilities: -```python +```python skip="true" import pytest import anyio from mcp.client.session import ClientSession @@ -530,7 +530,7 @@ async def test_task_tool(): ### Keep Work Functions Focused -```python +```python skip="true" # Good: focused work function async def work(task: ServerTaskContext) -> CallToolResult: await task.update_status("Validating...") @@ -544,7 +544,7 @@ async def work(task: ServerTaskContext) -> CallToolResult: ### Check Cancellation in Loops -```python +```python skip="true" async def work(task: ServerTaskContext) -> CallToolResult: results = [] for item in large_dataset: @@ -558,7 +558,7 @@ async def work(task: ServerTaskContext) -> CallToolResult: ### Use Meaningful Status Messages -```python +```python skip="true" async def work(task: ServerTaskContext) -> CallToolResult: await task.update_status("Connecting to database...") db = await connect() @@ -575,7 +575,7 @@ async def work(task: ServerTaskContext) -> CallToolResult: ### Handle Elicitation Responses -```python +```python skip="true" async def work(task: ServerTaskContext) -> CallToolResult: result = await task.elicit(message="Continue?", requestedSchema={...}) diff --git a/docs/experimental/tasks.md b/docs/experimental/tasks.md index 2d4d06a02..9fdda3bed 100644 --- a/docs/experimental/tasks.md +++ b/docs/experimental/tasks.md @@ -101,7 +101,7 @@ Server Client When augmenting a request with task execution, include `TaskMetadata`: -```python +```python skip="true" from mcp.types import TaskMetadata task = TaskMetadata(ttl=60000) # TTL in milliseconds @@ -113,7 +113,7 @@ The `ttl` (time-to-live) specifies how long the task and result are retained aft Servers persist task state in a `TaskStore`. The SDK provides `InMemoryTaskStore` for development: -```python +```python skip="true" from mcp.shared.experimental.tasks import InMemoryTaskStore store = InMemoryTaskStore() @@ -140,7 +140,7 @@ The SDK manages these automatically when you enable task support. **Server** (simplified API): -```python +```python skip="true" from mcp.server import Server from mcp.server.experimental.task_context import ServerTaskContext from mcp.types import CallToolResult, TextContent, TASK_REQUIRED @@ -163,7 +163,7 @@ async def handle_tool(name: str, arguments: dict): **Client:** -```python +```python skip="true" from mcp.client.session import ClientSession from mcp.types import CallToolResult diff --git a/docs/migration.md b/docs/migration.md index 7d30f0ac9..91bbc8bec 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -14,7 +14,7 @@ The deprecated `streamablehttp_client` function has been removed. Use `streamabl **Before (v1):** -```python +```python skip="true" from mcp.client.streamable_http import streamablehttp_client async with streamablehttp_client( @@ -29,7 +29,7 @@ async with streamablehttp_client( **After (v2):** -```python +```python skip="true" import httpx from mcp.client.streamable_http import streamable_http_client @@ -56,7 +56,7 @@ If you need to capture the session ID (e.g., for session resumption testing), yo **Before (v1):** -```python +```python skip="true" from mcp.client.streamable_http import streamable_http_client async with streamable_http_client(url) as (read_stream, write_stream, get_session_id): @@ -67,7 +67,7 @@ async with streamable_http_client(url) as (read_stream, write_stream, get_sessio **After (v2):** -```python +```python skip="true" import httpx from mcp.client.streamable_http import streamable_http_client @@ -115,13 +115,13 @@ The following deprecated type aliases and classes have been removed from `mcp.ty **Before (v1):** -```python +```python skip="true" from mcp.types import Content, ResourceReference, Cursor ``` **After (v2):** -```python +```python skip="true" from mcp.types import ContentBlock, ResourceTemplateReference # Use `str` instead of `Cursor` for pagination cursors ``` @@ -132,13 +132,13 @@ The deprecated `args` parameter has been removed from `ClientSessionGroup.call_t **Before (v1):** -```python +```python skip="true" result = await session_group.call_tool("my_tool", args={"key": "value"}) ``` **After (v2):** -```python +```python skip="true" result = await session_group.call_tool("my_tool", arguments={"key": "value"}) ``` @@ -155,14 +155,14 @@ Use `params=PaginatedRequestParams(cursor=...)` instead. **Before (v1):** -```python +```python skip="true" result = await session.list_resources(cursor="next_page_token") result = await session.list_tools(cursor="next_page_token") ``` **After (v2):** -```python +```python skip="true" from mcp.types import PaginatedRequestParams result = await session.list_resources(params=PaginatedRequestParams(cursor="next_page_token")) @@ -175,7 +175,7 @@ The `McpError` exception class has been renamed to `MCPError` for consistent nam **Before (v1):** -```python +```python skip="true" from mcp.shared.exceptions import McpError try: @@ -186,7 +186,7 @@ except McpError as e: **After (v2):** -```python +```python skip="true" from mcp.shared.exceptions import MCPError try: @@ -197,7 +197,7 @@ except MCPError as e: `MCPError` is also exported from the top-level `mcp` package: -```python +```python skip="true" from mcp import MCPError ``` @@ -207,7 +207,7 @@ The `FastMCP` class has been renamed to `MCPServer` to better reflect its role a **Before (v1):** -```python +```python skip="true" from mcp.server.fastmcp import FastMCP mcp = FastMCP("Demo") @@ -215,7 +215,7 @@ mcp = FastMCP("Demo") **After (v2):** -```python +```python skip="true" from mcp.server.mcpserver import MCPServer mcp = MCPServer("Demo") @@ -242,7 +242,7 @@ Transport-specific parameters have been moved from the `MCPServer` constructor t **Before (v1):** -```python +```python skip="true" from mcp.server.fastmcp import FastMCP # Transport params in constructor @@ -256,7 +256,7 @@ mcp.run(transport="sse") **After (v2):** -```python +```python skip="true" from mcp.server.mcpserver import MCPServer # Transport params passed to run() @@ -272,7 +272,7 @@ mcp.run(transport="sse", host="0.0.0.0", port=9000, sse_path="/events") When mounting in a Starlette app, pass transport params to the app methods: -```python +```python skip="true" # Before (v1) from mcp.server.fastmcp import FastMCP @@ -304,7 +304,7 @@ This means you can no longer access `.root` on these types or use `model_validat **Before (v1):** -```python +```python skip="true" from mcp.types import ClientRequest, ServerNotification # Using RootModel.model_validate() @@ -317,7 +317,7 @@ actual_notification = notification.root **After (v2):** -```python +```python skip="true" from mcp.types import client_request_adapter, server_notification_adapter # Using TypeAdapter.validate_python() @@ -355,7 +355,7 @@ The nested `RequestParams.Meta` Pydantic model class has been replaced with a to **In request context handlers:** -```python +```python skip="true" # Before (v1) @server.call_tool() async def handle_tool(name: str, arguments: dict) -> list[TextContent]: @@ -386,7 +386,7 @@ The `RequestContext` class has been split to separate shared fields from server- **Before (v1):** -```python +```python skip="true" from mcp.client.session import ClientSession from mcp.shared.context import RequestContext, LifespanContextT, RequestT from mcp.shared.progress import ProgressContext @@ -400,7 +400,7 @@ progress_ctx: ProgressContext[SendRequestT, SendNotificationT, SendResultT, Rece **After (v2):** -```python +```python skip="true" from mcp.client.context import ClientRequestContext from mcp.client.session import ClientSession from mcp.server.context import ServerRequestContext, LifespanContextT, RequestT @@ -422,7 +422,7 @@ The `uri` field on resource-related types now uses `str` instead of Pydantic's ` **Before (v1):** -```python +```python skip="true" from pydantic import AnyUrl from mcp.types import Resource @@ -432,7 +432,7 @@ resource = Resource(name="test", uri=AnyUrl("users/me")) # Would fail validatio **After (v2):** -```python +```python skip="true" from mcp.types import Resource # Plain strings accepted @@ -443,7 +443,7 @@ resource = Resource(name="test", uri="https://example.com") # Works If your code passes `AnyUrl` objects to URI fields, convert them to strings: -```python +```python skip="true" # If you have an AnyUrl from elsewhere uri = str(my_any_url) # Convert to string ``` @@ -459,7 +459,7 @@ Affected types: The `Client` and `ClientSession` methods `read_resource()`, `subscribe_resource()`, and `unsubscribe_resource()` now only accept `str` for the `uri` parameter. If you were passing `AnyUrl` objects, convert them to strings: -```python +```python skip="true" # Before (v1) from pydantic import AnyUrl @@ -481,7 +481,7 @@ await client.read_resource(str(my_any_url)) MCP protocol types no longer accept arbitrary extra fields at the top level. This matches the MCP specification which only allows extra fields within `_meta` objects, not on the types themselves. -```python +```python skip="true" # This will now raise a validation error from mcp.types import CallToolRequestParams @@ -505,7 +505,7 @@ params = CallToolRequestParams( The `streamable_http_app()` method is now available directly on the lowlevel `Server` class, not just `MCPServer`. This allows using the streamable HTTP transport without the MCPServer wrapper. -```python +```python skip="true" from mcp.server.lowlevel.server import Server server = Server("my-server") diff --git a/docs/testing.md b/docs/testing.md index 9a222c906..1489908c2 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -7,7 +7,7 @@ This makes it easy to write tests without network overhead. Let's assume you have a simple server with a single tool: -```python title="server.py" +```python title="server.py" skip-run="true" from mcp.server import MCPServer app = MCPServer("Calculator") @@ -40,7 +40,7 @@ To run the below test, you'll need to install the following dependencies: you to take snapshots of the output of your tests. Which makes it easier to create tests for your server - you don't need to use it, but we are spreading the word for best practices. -```python title="test_server.py" +```python title="test_server.py" skip-run="true" import pytest from inline_snapshot import snapshot from mcp import Client diff --git a/tests/test_examples.py b/tests/test_examples.py index e750fe697..e58ddc822 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -100,8 +100,8 @@ async def test_desktop(monkeypatch: pytest.MonkeyPatch): SKIP_RUN_TAGS = ["skip", "skip-run"] SKIP_LINT_TAGS = ["skip", "skip-lint"] -# Files with code examples that are both linted and run -DOCS_FILES = ["docs/quickstart.md", "docs/concepts.md"] +# TODO(v2): Change "README.v2.md" back to "README.md" when v2 is released +_ALL_EXAMPLES = list(find_examples("README.v2.md", "docs/")) def _set_eval_config(eval_example: EvalExample) -> None: @@ -112,16 +112,12 @@ def _set_eval_config(eval_example: EvalExample) -> None: ) -# TODO(v2): Change back to README.md when v2 is released @pytest.mark.parametrize( "example", - find_examples("README.v2.md", *DOCS_FILES), + [ex for ex in _ALL_EXAMPLES if not any(ex.prefix_settings().get(key) == "true" for key in SKIP_LINT_TAGS)], ids=str, ) def test_docs_examples(example: CodeExample, eval_example: EvalExample): - if any(example.prefix_settings().get(key) == "true" for key in SKIP_LINT_TAGS): - pytest.skip("skip-lint") - _set_eval_config(eval_example) if eval_example.update_examples: # pragma: no cover @@ -130,19 +126,18 @@ def test_docs_examples(example: CodeExample, eval_example: EvalExample): eval_example.lint_ruff(example) -def _get_runnable_docs_examples() -> list[CodeExample]: - examples = find_examples(*DOCS_FILES) - return [ex for ex in examples if not any(ex.prefix_settings().get(key) == "true" for key in SKIP_RUN_TAGS)] - - -@pytest.mark.parametrize("example", _get_runnable_docs_examples(), ids=str) +@pytest.mark.parametrize( + "example", + [ex for ex in _ALL_EXAMPLES if not any(ex.prefix_settings().get(key) == "true" for key in SKIP_RUN_TAGS)], + ids=str, +) def test_docs_examples_run(example: CodeExample, eval_example: EvalExample): _set_eval_config(eval_example) # Prevent `if __name__ == "__main__"` blocks from starting servers - globals: dict[str, Any] = {"__name__": "__docs_test__"} + module_globals: dict[str, Any] = {"__name__": "__docs_test__"} if eval_example.update_examples: # pragma: no cover - eval_example.run_print_update(example, module_globals=globals) + eval_example.run_print_update(example, module_globals=module_globals) else: - eval_example.run_print_check(example, module_globals=globals) + eval_example.run_print_check(example, module_globals=module_globals) From da7e7346132a516781504be8c8190a7fdcb636c2 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sat, 7 Feb 2026 14:44:53 +0100 Subject: [PATCH 8/8] docs: add introductory paragraphs after all headings Ensure every heading is followed by a paragraph before any code block, list, table, or other content. Also add this as a documentation guideline in CLAUDE.md. --- CLAUDE.md | 5 +++++ docs/concepts.md | 2 ++ docs/index.md | 6 ++++++ docs/quickstart.md | 6 ++++++ 4 files changed, 19 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index a4ef16e42..ffba37173 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -66,6 +66,11 @@ When making breaking changes, document them in `docs/migration.md`. Include: Search for related sections in the migration guide and group related changes together rather than adding new standalone sections. +## Documentation + +- After a heading, always add an introductory paragraph before any code block, list, table, or + other content. Never go directly from a heading to non-paragraph content. + ## Python Tools ## Code Formatting diff --git a/docs/concepts.md b/docs/concepts.md index 64853bddb..a7d4fffd1 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -159,6 +159,8 @@ mcp = MCPServer("My App", lifespan=app_lifespan) ## Next steps +Continue learning about MCP: + - **[Quickstart](quickstart.md)** — build your first server - **[Testing](testing.md)** — test your server with the `Client` class - **[Authorization](authorization.md)** — securing your servers with OAuth 2.1 diff --git a/docs/index.md b/docs/index.md index 7f25a6af4..48689222d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,6 +11,8 @@ This Python SDK implements the full MCP specification, making it easy to: ## Quick example +A minimal MCP server with a single tool: + ```python from mcp.server.mcpserver import MCPServer @@ -31,12 +33,16 @@ uv run --with mcp server.py ## Getting started +Follow these steps to start building with MCP: + 1. **[Install](installation.md)** the SDK 2. **[Quickstart](quickstart.md)** — build your first MCP server 3. **[Concepts](concepts.md)** — understand the protocol architecture and primitives ## Links +Useful references for working with MCP: + - [MCP specification](https://modelcontextprotocol.io) - [API Reference](api.md) - [Migration guide](migration.md) (v1 → v2) diff --git a/docs/quickstart.md b/docs/quickstart.md index 88b6dcd27..98d1a8049 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -50,6 +50,8 @@ if __name__ == "__main__": ## Run the server +Start the server with uv: + ```bash uv run --with mcp server.py ``` @@ -58,6 +60,8 @@ The server starts on `http://localhost:8000/mcp` using Streamable HTTP transport ## Connect a client +You can connect to your running server using any MCP client: + === "Claude Code" Add the server to [Claude Code](https://docs.claude.com/en/docs/claude-code/mcp): @@ -78,6 +82,8 @@ The server starts on `http://localhost:8000/mcp` using Streamable HTTP transport ## Next steps +Now that you have a running server, explore these topics: + - **[Concepts](concepts.md)** — understand the protocol architecture and primitives - **[Testing](testing.md)** — test your server with the `Client` class - **[API Reference](api.md)** — full API documentation