Skip to content

README - replace code snippets with examples - streamable http #1155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 77 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -532,9 +532,7 @@ Client usage:

<!-- snippet-source examples/snippets/clients/completion_client.py -->
```python
"""MCP client example showing completion usage.

This example demonstrates how to use the completion feature in MCP clients.
"""
cd to the `examples/snippets` directory and run:
uv run completion-client
"""
Expand Down Expand Up @@ -863,72 +861,99 @@ Note that `uv run mcp run` or `uv run mcp dev` only supports server using FastMC

> **Note**: Streamable HTTP transport is superseding SSE transport for production deployments.

<!-- snippet-source examples/snippets/servers/streamable_config.py -->
```python
"""
Run from the repository root:
uv run examples/snippets/servers/streamable_config.py
"""

from mcp.server.fastmcp import FastMCP

# Stateful server (maintains session state)
mcp = FastMCP("StatefulServer")

# Other configuration options:
# Stateless server (no session persistence)
mcp = FastMCP("StatelessServer", stateless_http=True)
# mcp = FastMCP("StatelessServer", stateless_http=True)

# Stateless server (no session persistence, no sse stream with supported client)
mcp = FastMCP("StatelessServer", stateless_http=True, json_response=True)
# mcp = FastMCP("StatelessServer", stateless_http=True, json_response=True)


# Add a simple tool to demonstrate the server
@mcp.tool()
def greet(name: str = "World") -> str:
"""Greet someone by name."""
return f"Hello, {name}!"


# Run server with streamable_http transport
mcp.run(transport="streamable-http")
if __name__ == "__main__":
mcp.run(transport="streamable-http")
```

You can mount multiple FastMCP servers in a FastAPI application:
_Full example: [examples/snippets/servers/streamable_config.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_config.py)_
<!-- /snippet-source -->

You can mount multiple FastMCP servers in a Starlette application:

<!-- snippet-source examples/snippets/servers/streamable_starlette_mount.py -->
```python
# echo.py
"""
Run from the repository root:
uvicorn examples.snippets.servers.streamable_starlette_mount:app --reload
"""

import contextlib

from starlette.applications import Starlette
from starlette.routing import Mount

from mcp.server.fastmcp import FastMCP

mcp = FastMCP(name="EchoServer", stateless_http=True)
# Create the Echo server
echo_mcp = FastMCP(name="EchoServer", stateless_http=True)


@mcp.tool()
@echo_mcp.tool()
def echo(message: str) -> str:
"""A simple echo tool"""
return f"Echo: {message}"
```

```python
# math.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP(name="MathServer", stateless_http=True)
# Create the Math server
math_mcp = FastMCP(name="MathServer", stateless_http=True)


@mcp.tool()
@math_mcp.tool()
def add_two(n: int) -> int:
"""Tool to add two to the input"""
return n + 2
```

```python
# main.py
import contextlib
from fastapi import FastAPI
from mcp.echo import echo
from mcp.math import math


# Create a combined lifespan to manage both session managers
@contextlib.asynccontextmanager
async def lifespan(app: FastAPI):
async def lifespan(app: Starlette):
async with contextlib.AsyncExitStack() as stack:
await stack.enter_async_context(echo.mcp.session_manager.run())
await stack.enter_async_context(math.mcp.session_manager.run())
await stack.enter_async_context(echo_mcp.session_manager.run())
await stack.enter_async_context(math_mcp.session_manager.run())
yield


app = FastAPI(lifespan=lifespan)
app.mount("/echo", echo.mcp.streamable_http_app())
app.mount("/math", math.mcp.streamable_http_app())
# Create the Starlette app and mount the MCP servers
app = Starlette(
routes=[
Mount("/echo", echo_mcp.streamable_http_app()),
Mount("/math", math_mcp.streamable_http_app()),
],
lifespan=lifespan,
)
```

_Full example: [examples/snippets/servers/streamable_starlette_mount.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_starlette_mount.py)_
<!-- /snippet-source -->

For low level server with Streamable HTTP implementations, see:

- Stateful server: [`examples/servers/simple-streamablehttp/`](examples/servers/simple-streamablehttp/)
Expand All @@ -945,26 +970,6 @@ The streamable HTTP transport supports:

By default, SSE servers are mounted at `/sse` and Streamable HTTP servers are mounted at `/mcp`. You can customize these paths using the methods described below.

#### Streamable HTTP servers

The following example shows how to use `streamable_http_app()`, a method that returns a `Starlette` application object.
You can then append additional routes to that application as needed.

```python
mcp = FastMCP("My App")

app = mcp.streamable_http_app()
# Additional non-MCP routes can be added like so:
# from starlette.routing import Route
# app.router.routes.append(Route("/", endpoint=other_route_function))
```

To customize the route from the default of "/mcp", either specify the `streamable_http_path` option for the `FastMCP` constructor,
or set `FASTMCP_STREAMABLE_HTTP_PATH` environment variable.

Note that in Starlette and FastAPI (which is based on Starlette), the "/mcp" route will redirect to "/mcp/",
so you may need to use "/mcp/" when pointing MCP clients at your servers.

For more information on mounting applications in Starlette, see the [Starlette documentation](https://www.starlette.io/routing/#submounting-routes).

#### SSE servers
Expand Down Expand Up @@ -1336,9 +1341,7 @@ The SDK provides a high-level client interface for connecting to MCP servers usi

<!-- snippet-source examples/snippets/clients/stdio_client.py -->
```python
"""MCP client example using stdio transport.

This is a documentation example showing how to write an MCP client.
"""
cd to the `examples/snippets/clients` directory and run:
uv run client
"""
Expand Down Expand Up @@ -1428,14 +1431,22 @@ _Full example: [examples/snippets/clients/stdio_client.py](https://github.com/mo

Clients can also connect using [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http):

<!-- snippet-source examples/snippets/clients/streamable_basic.py -->
```python
from mcp.client.streamable_http import streamablehttp_client
"""
Run from the repository root:
uv run examples/snippets/clients/streamable_basic.py
"""

import asyncio

from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client


async def main():
# Connect to a streamable HTTP server
async with streamablehttp_client("example/mcp") as (
async with streamablehttp_client("http://localhost:8000/mcp") as (
read_stream,
write_stream,
_,
Expand All @@ -1444,21 +1455,25 @@ async def main():
async with ClientSession(read_stream, write_stream) as session:
# Initialize the connection
await session.initialize()
# Call a tool
tool_result = await session.call_tool("echo", {"message": "hello"})
# List available tools
tools = await session.list_tools()
print(f"Available tools: {[tool.name for tool in tools.tools]}")


if __name__ == "__main__":
asyncio.run(main())
```

_Full example: [examples/snippets/clients/streamable_basic.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/streamable_basic.py)_
<!-- /snippet-source -->

### Client Display Utilities

When building MCP clients, the SDK provides utilities to help display human-readable names for tools, resources, and prompts:

<!-- snippet-source examples/snippets/clients/display_utilities.py -->
```python
"""Client display utilities example.

This example shows how to use the SDK's display utilities to show
human-readable names for tools, resources, and prompts.

"""
cd to the `examples/snippets` directory and run:
uv run display-utilities-client
"""
Expand Down
4 changes: 1 addition & 3 deletions examples/snippets/clients/completion_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
"""MCP client example showing completion usage.
This example demonstrates how to use the completion feature in MCP clients.
"""
cd to the `examples/snippets` directory and run:
uv run completion-client
"""
Expand Down
6 changes: 1 addition & 5 deletions examples/snippets/clients/display_utilities.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
"""Client display utilities example.
This example shows how to use the SDK's display utilities to show
human-readable names for tools, resources, and prompts.
"""
cd to the `examples/snippets` directory and run:
uv run display-utilities-client
"""
Expand Down
4 changes: 1 addition & 3 deletions examples/snippets/clients/stdio_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
"""MCP client example using stdio transport.
This is a documentation example showing how to write an MCP client.
"""
cd to the `examples/snippets/clients` directory and run:
uv run client
"""
Expand Down
29 changes: 29 additions & 0 deletions examples/snippets/clients/streamable_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""
Run from the repository root:
uv run examples/snippets/clients/streamable_basic.py
"""

import asyncio

from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client


async def main():
# Connect to a streamable HTTP server
async with streamablehttp_client("http://localhost:8000/mcp") as (
read_stream,
write_stream,
_,
):
# Create a session using the client streams
async with ClientSession(read_stream, write_stream) as session:
# Initialize the connection
await session.initialize()
# List available tools
tools = await session.list_tools()
print(f"Available tools: {[tool.name for tool in tools.tools]}")


if __name__ == "__main__":
asyncio.run(main())
28 changes: 28 additions & 0 deletions examples/snippets/servers/streamable_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
Run from the repository root:
uv run examples/snippets/servers/streamable_config.py
"""

from mcp.server.fastmcp import FastMCP

# Stateful server (maintains session state)
mcp = FastMCP("StatefulServer")

# Other configuration options:
# Stateless server (no session persistence)
# mcp = FastMCP("StatelessServer", stateless_http=True)

# Stateless server (no session persistence, no sse stream with supported client)
# mcp = FastMCP("StatelessServer", stateless_http=True, json_response=True)


# Add a simple tool to demonstrate the server
@mcp.tool()
def greet(name: str = "World") -> str:
"""Greet someone by name."""
return f"Hello, {name}!"


# Run server with streamable_http transport
if __name__ == "__main__":
mcp.run(transport="streamable-http")
49 changes: 49 additions & 0 deletions examples/snippets/servers/streamable_starlette_mount.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""
Run from the repository root:
uvicorn examples.snippets.servers.streamable_starlette_mount:app --reload
"""

import contextlib

from starlette.applications import Starlette
from starlette.routing import Mount

from mcp.server.fastmcp import FastMCP

# Create the Echo server
echo_mcp = FastMCP(name="EchoServer", stateless_http=True)


@echo_mcp.tool()
def echo(message: str) -> str:
"""A simple echo tool"""
return f"Echo: {message}"


# Create the Math server
math_mcp = FastMCP(name="MathServer", stateless_http=True)


@math_mcp.tool()
def add_two(n: int) -> int:
"""Tool to add two to the input"""
return n + 2


# Create a combined lifespan to manage both session managers
@contextlib.asynccontextmanager
async def lifespan(app: Starlette):
async with contextlib.AsyncExitStack() as stack:
await stack.enter_async_context(echo_mcp.session_manager.run())
await stack.enter_async_context(math_mcp.session_manager.run())
yield


# Create the Starlette app and mount the MCP servers
app = Starlette(
routes=[
Mount("/echo", echo_mcp.streamable_http_app()),
Mount("/math", math_mcp.streamable_http_app()),
],
lifespan=lifespan,
)
Loading