Skip to content
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions README.v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand All @@ -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."""
Expand Down Expand Up @@ -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:
Expand Down
172 changes: 163 additions & 9 deletions docs/concepts.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,167 @@
# 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

<!--
- Server vs Client
- Three primitives (tools, resources, prompts)
- Transports (stdio, SSE, streamable HTTP)
- Context and sessions
- Lifecycle and state
-->
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**, **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
from mcp.server.mcpserver import MCPServer

mcp = MCPServer("Demo")


@mcp.resource("config://app")
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[str, str]:
"""Get a user profile by ID."""
return {"user_id": user_id, "name": "Alice"}
```

<!-- TODO: 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
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."""
return f"Email sent to {to}"
```

Tools support structured output, progress reporting, and more.
<!-- TODO: See [Tools](server/tools.md) for full documentation. -->

### Prompts

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."""
return f"Please review the following {language} code:\n\n{code}"
```

<!-- TODO: 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) |

<!-- TODO: 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, MCPServer

mcp = MCPServer("Demo")


@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, elicitation, sampling, and more.
<!-- TODO: link to server/context.md, server/logging.md, server/elicitation.md, server/sampling.md -->

## 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 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: MCPServer) -> AsyncIterator[AppContext]:
# Initialize on startup
ctx = AppContext(db_url="postgresql://localhost/mydb")
try:
yield ctx
finally:
# Cleanup on shutdown
pass


mcp = MCPServer("My App", lifespan=app_lifespan)
```

<!-- TODO: See [Server](server/index.md) for more on lifecycle management. -->

## 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
- **[API Reference](api.md)** — full API documentation
2 changes: 1 addition & 1 deletion docs/experimental/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
24 changes: 12 additions & 12 deletions docs/experimental/tasks-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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}")

Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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}")
```
Expand All @@ -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}")
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -336,7 +336,7 @@ if __name__ == "__main__":

Handle task errors gracefully:

```python
```python skip="true"
from mcp.shared.exceptions import MCPError

try:
Expand Down
Loading