Rename FastMCP to MCPServer (#1951)

This commit is contained in:
Marcelo Trylesinski 2026-01-25 14:45:52 +01:00 • committed by GitHub
commit 65c614e48e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
123 changed files with 713 additions and 693 deletions

View file

@ -39,7 +39,7 @@ body:
demonstrating the bug.
placeholder: |
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
...
render: Python

184
README.md
View file

@ -45,7 +45,7 @@
- [Sampling](#sampling)
- [Logging and Notifications](#logging-and-notifications)
- [Authentication](#authentication)
- [FastMCP Properties](#fastmcp-properties)
- [MCPServer Properties](#mcpserver-properties)
- [Session Properties and Methods](#session-properties-and-methods)
- [Request Context Properties](#request-context-properties)
- [Running Your Server](#running-your-server)
@ -134,18 +134,18 @@ uv run mcp
Let's create a simple MCP server that exposes a calculator tool and some data:
<!-- snippet-source examples/snippets/servers/fastmcp_quickstart.py -->
<!-- snippet-source examples/snippets/servers/mcpserver_quickstart.py -->
```python
"""FastMCP quickstart example.
"""MCPServer quickstart example.
Run from the repository root:
uv run examples/snippets/servers/fastmcp_quickstart.py
uv run examples/snippets/servers/mcpserver_quickstart.py
"""
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
# Create an MCP server
mcp = FastMCP("Demo")
mcp = MCPServer("Demo")
# Add an addition tool
@ -180,13 +180,13 @@ if __name__ == "__main__":
mcp.run(transport="streamable-http", json_response=True)
```
_Full example: [examples/snippets/servers/fastmcp_quickstart.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/fastmcp_quickstart.py)_
_Full example: [examples/snippets/servers/mcpserver_quickstart.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/mcpserver_quickstart.py)_
<!-- /snippet-source -->
You can install this server in [Claude Code](https://docs.claude.com/en/docs/claude-code/mcp) and interact with it right away. First, run the server:
```bash
uv run --with mcp examples/snippets/servers/fastmcp_quickstart.py
uv run --with mcp examples/snippets/servers/mcpserver_quickstart.py
```
Then add it to Claude Code:
@ -216,7 +216,7 @@ The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you bui
### Server
The FastMCP server is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
The MCPServer server is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
<!-- snippet-source examples/snippets/servers/lifespan_example.py -->
```python
@ -226,7 +226,7 @@ from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from dataclasses import dataclass
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.mcpserver import Context, MCPServer
from mcp.server.session import ServerSession
@ -256,7 +256,7 @@ class AppContext:
@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
async def app_lifespan(server: MCPServer) -> AsyncIterator[AppContext]:
"""Manage application lifecycle with type-safe context."""
# Initialize on startup
db = await Database.connect()
@ -268,7 +268,7 @@ async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
# Pass lifespan to server
mcp = FastMCP("My App", lifespan=app_lifespan)
mcp = MCPServer("My App", lifespan=app_lifespan)
# Access type-safe lifespan context in tools
@ -288,9 +288,9 @@ Resources are how you expose data to LLMs. They're similar to GET endpoints in a
<!-- snippet-source examples/snippets/servers/basic_resource.py -->
```python
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
mcp = FastMCP(name="Resource Example")
mcp = MCPServer(name="Resource Example")
@mcp.resource("file://documents/{name}")
@ -319,9 +319,9 @@ Tools let LLMs take actions through your server. Unlike resources, tools are exp
<!-- snippet-source examples/snippets/servers/basic_tool.py -->
```python
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
mcp = FastMCP(name="Tool Example")
mcp = MCPServer(name="Tool Example")
@mcp.tool()
@ -340,14 +340,14 @@ def get_weather(city: str, unit: str = "celsius") -> str:
_Full example: [examples/snippets/servers/basic_tool.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/basic_tool.py)_
<!-- /snippet-source -->
Tools can optionally receive a Context object by including a parameter with the `Context` type annotation. This context is automatically injected by the FastMCP framework and provides access to MCP capabilities:
Tools can optionally receive a Context object by including a parameter with the `Context` type annotation. This context is automatically injected by the MCPServer framework and provides access to MCP capabilities:
<!-- snippet-source examples/snippets/servers/tool_progress.py -->
```python
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.mcpserver import Context, MCPServer
from mcp.server.session import ServerSession
mcp = FastMCP(name="Progress Example")
mcp = MCPServer(name="Progress Example")
@mcp.tool()
@ -395,7 +395,7 @@ validated data that clients can easily process.
**Note:** For backward compatibility, unstructured results are also
returned. Unstructured results are provided for backward compatibility
with previous versions of the MCP specification, and are quirks-compatible
with previous versions of FastMCP in the current version of the SDK.
with previous versions of MCPServer in the current version of the SDK.
**Note:** In cases where a tool function's return type annotation
causes the tool to be classified as structured _and this is undesirable_,
@ -414,10 +414,10 @@ from typing import Annotated
from pydantic import BaseModel
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
from mcp.types import CallToolResult, TextContent
mcp = FastMCP("CallToolResult Example")
mcp = MCPServer("CallToolResult Example")
class ValidationModel(BaseModel):
@ -465,9 +465,9 @@ from typing import TypedDict
from pydantic import BaseModel, Field
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
mcp = FastMCP("Structured Output Example")
mcp = MCPServer("Structured Output Example")
# Using Pydantic models for rich structured data
@ -567,10 +567,10 @@ Prompts are reusable templates that help LLMs interact with your server effectiv
<!-- snippet-source examples/snippets/servers/basic_prompt.py -->
```python
from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp.prompts import base
from mcp.server.mcpserver import MCPServer
from mcp.server.mcpserver.prompts import base
mcp = FastMCP(name="Prompt Example")
mcp = MCPServer(name="Prompt Example")
@mcp.prompt(title="Code Review")
@ -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
from mcp.server.fastmcp import FastMCP, Icon
from mcp.server.mcpserver import MCPServer, Icon
# Create an icon from a file path or URL
icon = Icon(
@ -605,7 +605,7 @@ icon = Icon(
)
# Add icons to server
mcp = FastMCP(
mcp = MCPServer(
"My Server",
website_url="https://example.com",
icons=[icon]
@ -623,21 +623,21 @@ def my_resource():
return "content"
```
_Full example: [examples/fastmcp/icons_demo.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/fastmcp/icons_demo.py)_
_Full example: [examples/mcpserver/icons_demo.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/mcpserver/icons_demo.py)_
### Images
FastMCP provides an `Image` class that automatically handles image data:
MCPServer provides an `Image` class that automatically handles image data:
<!-- snippet-source examples/snippets/servers/images.py -->
```python
"""Example showing image handling with FastMCP."""
"""Example showing image handling with MCPServer."""
from PIL import Image as PILImage
from mcp.server.fastmcp import FastMCP, Image
from mcp.server.mcpserver import Image, MCPServer
mcp = FastMCP("Image Example")
mcp = MCPServer("Image Example")
@mcp.tool()
@ -660,9 +660,9 @@ The Context object is automatically injected into tool and resource functions th
To use context in a tool or resource function, add a parameter with the `Context` type annotation:
```python
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.mcpserver import Context, MCPServer
mcp = FastMCP(name="Context Example")
mcp = MCPServer(name="Context Example")
@mcp.tool()
@ -678,7 +678,7 @@ The Context object provides the following capabilities:
- `ctx.request_id` - Unique ID for the current request
- `ctx.client_id` - Client ID if available
- `ctx.fastmcp` - Access to the FastMCP server instance (see [FastMCP Properties](#fastmcp-properties))
- `ctx.mcp_server` - Access to the MCPServer server instance (see [MCPServer Properties](#mcpserver-properties))
- `ctx.session` - Access to the underlying session for advanced communication (see [Session Properties and Methods](#session-properties-and-methods))
- `ctx.request_context` - Access to request-specific data and lifespan resources (see [Request Context Properties](#request-context-properties))
- `await ctx.debug(message)` - Send debug log message
@ -692,10 +692,10 @@ The Context object provides the following capabilities:
<!-- snippet-source examples/snippets/servers/tool_progress.py -->
```python
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.mcpserver import Context, MCPServer
from mcp.server.session import ServerSession
mcp = FastMCP(name="Progress Example")
mcp = MCPServer(name="Progress Example")
@mcp.tool()
@ -824,12 +824,12 @@ import uuid
from pydantic import BaseModel, Field
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.mcpserver import Context, MCPServer
from mcp.server.session import ServerSession
from mcp.shared.exceptions import UrlElicitationRequiredError
from mcp.types import ElicitRequestURLParams
mcp = FastMCP(name="Elicitation Example")
mcp = MCPServer(name="Elicitation Example")
class BookingPreferences(BaseModel):
@ -931,11 +931,11 @@ Tools can interact with LLMs through sampling (generating text):
<!-- snippet-source examples/snippets/servers/sampling.py -->
```python
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.mcpserver import Context, MCPServer
from mcp.server.session import ServerSession
from mcp.types import SamplingMessage, TextContent
mcp = FastMCP(name="Sampling Example")
mcp = MCPServer(name="Sampling Example")
@mcp.tool()
@ -968,10 +968,10 @@ Tools can send logs and notifications through the context:
<!-- snippet-source examples/snippets/servers/notifications.py -->
```python
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.mcpserver import Context, MCPServer
from mcp.server.session import ServerSession
mcp = FastMCP(name="Notifications Example")
mcp = MCPServer(name="Notifications Example")
@mcp.tool()
@ -1010,7 +1010,7 @@ from pydantic import AnyHttpUrl
from mcp.server.auth.provider import AccessToken, TokenVerifier
from mcp.server.auth.settings import AuthSettings
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
class SimpleTokenVerifier(TokenVerifier):
@ -1020,8 +1020,8 @@ class SimpleTokenVerifier(TokenVerifier):
pass # This is where you would implement actual token validation
# Create FastMCP instance as a Resource Server
mcp = FastMCP(
# Create MCPServer instance as a Resource Server
mcp = MCPServer(
"Weather Service",
# Token verifier for authentication
token_verifier=SimpleTokenVerifier(),
@ -1062,15 +1062,15 @@ For a complete example with separate Authorization Server and Resource Server im
See [TokenVerifier](src/mcp/server/auth/provider.py) for more details on implementing token validation.
### FastMCP Properties
### MCPServer Properties
The FastMCP server instance accessible via `ctx.fastmcp` provides access to server configuration and metadata:
The MCPServer server instance accessible via `ctx.mcp_server` provides access to server configuration and metadata:
- `ctx.fastmcp.name` - The server's name as defined during initialization
- `ctx.fastmcp.instructions` - Server instructions/description provided to clients
- `ctx.fastmcp.website_url` - Optional website URL for the server
- `ctx.fastmcp.icons` - Optional list of icons for UI display
- `ctx.fastmcp.settings` - Complete server configuration object containing:
- `ctx.mcp_server.name` - The server's name as defined during initialization
- `ctx.mcp_server.instructions` - Server instructions/description provided to clients
- `ctx.mcp_server.website_url` - Optional website URL for the server
- `ctx.mcp_server.icons` - Optional list of icons for UI display
- `ctx.mcp_server.settings` - Complete server configuration object containing:
- `debug` - Debug mode flag
- `log_level` - Current logging level
- `host` and `port` - Server network configuration
@ -1083,12 +1083,12 @@ The FastMCP server instance accessible via `ctx.fastmcp` provides access to serv
def server_info(ctx: Context) -> dict:
"""Get information about the current server."""
return {
"name": ctx.fastmcp.name,
"instructions": ctx.fastmcp.instructions,
"debug_mode": ctx.fastmcp.settings.debug,
"log_level": ctx.fastmcp.settings.log_level,
"host": ctx.fastmcp.settings.host,
"port": ctx.fastmcp.settings.port,
"name": ctx.mcp_server.name,
"instructions": ctx.mcp_server.instructions,
"debug_mode": ctx.mcp_server.settings.debug,
"log_level": ctx.mcp_server.settings.log_level,
"host": ctx.mcp_server.settings.host,
"port": ctx.mcp_server.settings.port,
}
```
@ -1203,9 +1203,9 @@ cd to the `examples/snippets` directory and run:
python servers/direct_execution.py
"""
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
mcp = FastMCP("My App")
mcp = MCPServer("My App")
@mcp.tool()
@ -1234,7 +1234,7 @@ python servers/direct_execution.py
uv run mcp run servers/direct_execution.py
```
Note that `uv run mcp run` or `uv run mcp dev` only supports server using FastMCP and not the low-level server variant.
Note that `uv run mcp run` or `uv run mcp dev` only supports server using MCPServer and not the low-level server variant.
### Streamable HTTP Transport
@ -1246,9 +1246,9 @@ Note that `uv run mcp run` or `uv run mcp dev` only supports server using FastMC
uv run examples/snippets/servers/streamable_config.py
"""
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
mcp = FastMCP("StatelessServer")
mcp = MCPServer("StatelessServer")
# Add a simple tool to demonstrate the server
@ -1275,7 +1275,7 @@ if __name__ == "__main__":
_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:
You can mount multiple MCPServer servers in a Starlette application:
<!-- snippet-source examples/snippets/servers/streamable_starlette_mount.py -->
```python
@ -1288,10 +1288,10 @@ import contextlib
from starlette.applications import Starlette
from starlette.routing import Mount
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
# Create the Echo server
echo_mcp = FastMCP(name="EchoServer")
echo_mcp = MCPServer(name="EchoServer")
@echo_mcp.tool()
@ -1301,7 +1301,7 @@ def echo(message: str) -> str:
# Create the Math server
math_mcp = FastMCP(name="MathServer")
math_mcp = MCPServer(name="MathServer")
@math_mcp.tool()
@ -1400,10 +1400,10 @@ import contextlib
from starlette.applications import Starlette
from starlette.routing import Mount
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
# Create MCP server
mcp = FastMCP("My App")
mcp = MCPServer("My App")
@mcp.tool()
@ -1447,10 +1447,10 @@ import contextlib
from starlette.applications import Starlette
from starlette.routing import Host
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
# Create MCP server
mcp = FastMCP("MCP Host App")
mcp = MCPServer("MCP Host App")
@mcp.tool()
@ -1494,11 +1494,11 @@ import contextlib
from starlette.applications import Starlette
from starlette.routing import Mount
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
# Create multiple MCP servers
api_mcp = FastMCP("API Server")
chat_mcp = FastMCP("Chat Server")
api_mcp = MCPServer("API Server")
chat_mcp = MCPServer("Chat Server")
@api_mcp.tool()
@ -1540,7 +1540,7 @@ _Full example: [examples/snippets/servers/streamable_http_multiple_servers.py](h
<!-- snippet-source examples/snippets/servers/streamable_http_path_config.py -->
```python
"""Example showing path configuration when mounting FastMCP.
"""Example showing path configuration when mounting MCPServer.
Run from the repository root:
uvicorn examples.snippets.servers.streamable_http_path_config:app --reload
@ -1549,10 +1549,10 @@ Run from the repository root:
from starlette.applications import Starlette
from starlette.routing import Mount
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
# Create a simple FastMCP server
mcp_at_root = FastMCP("My Server")
# Create a simple MCPServer server
mcp_at_root = MCPServer("My Server")
@mcp_at_root.tool()
@ -1585,10 +1585,10 @@ You can mount the SSE server to an existing ASGI server using the `sse_app` meth
```python
from starlette.applications import Starlette
from starlette.routing import Mount, Host
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
mcp = FastMCP("My App")
mcp = MCPServer("My App")
# Mount the SSE server to the existing ASGI server
app = Starlette(
@ -1606,12 +1606,12 @@ You can also mount multiple MCP servers at different sub-paths. The SSE transpor
```python
from starlette.applications import Starlette
from starlette.routing import Mount
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
# Create multiple MCP servers
github_mcp = FastMCP("GitHub API")
browser_mcp = FastMCP("Browser")
search_mcp = FastMCP("Search")
github_mcp = MCPServer("GitHub API")
browser_mcp = MCPServer("Browser")
search_mcp = MCPServer("Search")
# Mount each server at its own sub-path
# The SSE transport automatically uses ASGI's root_path to construct
@ -2126,7 +2126,7 @@ from mcp.shared.context import RequestContext
# Create server parameters for stdio connection
server_params = StdioServerParameters(
command="uv", # Using uv to run the server
args=["run", "server", "fastmcp_quickstart", "stdio"], # We're already in snippets dir
args=["run", "server", "mcpserver_quickstart", "stdio"], # We're already in snippets dir
env={"UV_INDEX": os.environ.get("UV_INDEX", "")},
)
@ -2157,7 +2157,7 @@ async def run():
prompts = await session.list_prompts()
print(f"Available prompts: {[p.name for p in prompts.prompts]}")
# Get a prompt (greet_user prompt from fastmcp_quickstart)
# Get a prompt (greet_user prompt from mcpserver_quickstart)
if prompts.prompts:
prompt = await session.get_prompt("greet_user", arguments={"name": "Alice", "style": "friendly"})
print(f"Prompt result: {prompt.messages[0].content}")
@ -2170,13 +2170,13 @@ async def run():
tools = await session.list_tools()
print(f"Available tools: {[t.name for t in tools.tools]}")
# Read a resource (greeting resource from fastmcp_quickstart)
# Read a resource (greeting resource from mcpserver_quickstart)
resource_content = await session.read_resource("greeting://World")
content_block = resource_content.contents[0]
if isinstance(content_block, types.TextContent):
print(f"Resource content: {content_block.text}")
# Call a tool (add tool from fastmcp_quickstart)
# Call a tool (add tool from mcpserver_quickstart)
result = await session.call_tool("add", arguments={"a": 5, "b": 3})
result_unstructured = result.content[0]
if isinstance(result_unstructured, types.TextContent):
@ -2254,7 +2254,7 @@ from mcp.shared.metadata_utils import get_display_name
# Create server parameters for stdio connection
server_params = StdioServerParameters(
command="uv", # Using uv to run the server
args=["run", "server", "fastmcp_quickstart", "stdio"],
args=["run", "server", "mcpserver_quickstart", "stdio"],
env={"UV_INDEX": os.environ.get("UV_INDEX", "")},
)

View file

@ -15,9 +15,9 @@ If you want to read more about the specification, please visit the [MCP document
Here's a simple MCP server that exposes a tool, resource, and prompt:
```python title="server.py"
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
mcp = FastMCP("Test Server", json_response=True)
mcp = MCPServer("Test Server", json_response=True)
@mcp.tool()

View file

@ -21,7 +21,7 @@ The following dependencies are automatically installed:
- [`starlette`](https://pypi.org/project/starlette/): Web framework used to build the HTTP transport endpoints.
- [`python-multipart`](https://pypi.org/project/python-multipart/): Handle HTTP body parsing.
- [`sse-starlette`](https://pypi.org/project/sse-starlette/): Server-Sent Events for Starlette, used to build the SSE transport endpoint.
- [`pydantic-settings`](https://pypi.org/project/pydantic-settings/): Settings management used in FastMCP.
- [`pydantic-settings`](https://pypi.org/project/pydantic-settings/): Settings management used in MCPServer.
- [`uvicorn`](https://pypi.org/project/uvicorn/): ASGI server used to run the HTTP transport endpoints.
- [`jsonschema`](https://pypi.org/project/jsonschema/): JSON schema validation.
- [`pywin32`](https://pypi.org/project/pywin32/): Windows specific dependencies for the CLI tools.

View file

@ -121,15 +121,35 @@ result = await session.list_resources(params=PaginatedRequestParams(cursor="next
result = await session.list_tools(params=PaginatedRequestParams(cursor="next_page_token"))
```
### `mount_path` parameter removed from FastMCP
### `FastMCP` renamed to `MCPServer`
The `mount_path` parameter has been removed from `FastMCP.__init__()`, `FastMCP.run()`, `FastMCP.run_sse_async()`, and `FastMCP.sse_app()`. It was also removed from the `Settings` class.
The `FastMCP` class has been renamed to `MCPServer` to better reflect its role as the main server class in the SDK. This is a simple rename with no functional changes to the class itself.
**Before (v1):**
```python
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Demo")
```
**After (v2):**
```python
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("Demo")
```
### `mount_path` parameter removed from MCPServer
The `mount_path` parameter has been removed from `MCPServer.__init__()`, `MCPServer.run()`, `MCPServer.run_sse_async()`, and `MCPServer.sse_app()`. It was also removed from the `Settings` class.
This parameter was redundant because the SSE transport already handles sub-path mounting via ASGI's standard `root_path` mechanism. When using Starlette's `Mount("/path", app=mcp.sse_app())`, Starlette automatically sets `root_path` in the ASGI scope, and the `SseServerTransport` uses this to construct the correct message endpoint path.
### Transport-specific parameters moved from FastMCP constructor to run()/app methods
### Transport-specific parameters moved from MCPServer constructor to run()/app methods
Transport-specific parameters have been moved from the `FastMCP` constructor to the `run()`, `sse_app()`, and `streamable_http_app()` methods. This provides better separation of concerns - the constructor now only handles server identity and authentication, while transport configuration is passed when starting the server.
Transport-specific parameters have been moved from the `MCPServer` constructor to the `run()`, `sse_app()`, and `streamable_http_app()` methods. This provides better separation of concerns - the constructor now only handles server identity and authentication, while transport configuration is passed when starting the server.
**Parameters moved:**
@ -157,28 +177,32 @@ mcp.run(transport="sse")
**After (v2):**
```python
from mcp.server.fastmcp import FastMCP
from mcp.server.mcpserver import MCPServer
# Transport params passed to run()
mcp = FastMCP("Demo")
mcp = MCPServer("Demo")
mcp.run(transport="streamable-http", json_response=True, stateless_http=True)
# Or for SSE
mcp = FastMCP("Server")
mcp = MCPServer("Server")
mcp.run(transport="sse", host="0.0.0.0", port=9000, sse_path="/events")
```
**For mounted apps:**
When mounting FastMCP in a Starlette app, pass transport params to the app methods:
When mounting in a Starlette app, pass transport params to the app methods:
```python
# Before (v1)
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("App", json_response=True)
app = Starlette(routes=[Mount("/", app=mcp.streamable_http_app())])
# After (v2)
mcp = FastMCP("App")
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("App")
app = Starlette(routes=[Mount("/", app=mcp.streamable_http_app(json_response=True))])
```
@ -354,7 +378,7 @@ params = CallToolRequestParams(
### `streamable_http_app()` available on lowlevel Server
The `streamable_http_app()` method is now available directly on the lowlevel `Server` class, not just `FastMCP`. This allows using the streamable HTTP transport without the FastMCP wrapper.
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
from mcp.server.lowlevel.server import Server

View file

@ -8,9 +8,9 @@ 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"
from mcp.server import FastMCP
from mcp.server import MCPServer
app = FastMCP("Calculator")
app = MCPServer("Calculator")
@app.tool()
def add(a: int, b: int) -> int:

View file

@ -0,0 +1,29 @@
"""MCPServer Complex inputs Example
Demonstrates validation via pydantic with complex models.
"""
from typing import Annotated
from pydantic import BaseModel, Field
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("Shrimp Tank")
class ShrimpTank(BaseModel):
class Shrimp(BaseModel):
name: Annotated[str, Field(max_length=10)]
shrimp: list[Shrimp]
@mcp.tool()
def name_shrimp(
tank: ShrimpTank,
# You can use pydantic Field in function signatures for validation.
extra_names: Annotated[list[str], Field(max_length=10)],
) -> list[str]:
"""List all shrimp names in the tank"""
return [shrimp.name for shrimp in tank.shrimp] + extra_names

View file

@ -0,0 +1,24 @@
"""MCPServer Desktop Example
A simple example that exposes the desktop directory as a resource.
"""
from pathlib import Path
from mcp.server.mcpserver import MCPServer
# Create server
mcp = MCPServer("Demo")
@mcp.resource("dir://desktop")
def desktop() -> list[str]:
"""List the files in the user's desktop"""
desktop = Path.home() / "Desktop"
return [str(f) for f in desktop.iterdir()]
@mcp.tool()
def sum(a: int, b: int) -> int:
"""Add two numbers"""
return a + b

View file

@ -0,0 +1,22 @@
"""MCPServer Echo Server with direct CallToolResult return"""
from typing import Annotated
from pydantic import BaseModel
from mcp.server.mcpserver import MCPServer
from mcp.types import CallToolResult, TextContent
mcp = MCPServer("Echo Server")
class EchoResponse(BaseModel):
text: str
@mcp.tool()
def echo(text: str) -> Annotated[CallToolResult, EchoResponse]:
"""Echo the input text with structure and metadata"""
return CallToolResult(
content=[TextContent(type="text", text=text)], structured_content={"text": text}, _meta={"some": "metadata"}
)

View file

@ -0,0 +1,28 @@
"""MCPServer Echo Server"""
from mcp.server.mcpserver import MCPServer
# Create server
mcp = MCPServer("Echo Server")
@mcp.tool()
def echo_tool(text: str) -> str:
"""Echo the input text"""
return text
@mcp.resource("echo://static")
def echo_resource() -> str:
return "Echo!"
@mcp.resource("echo://{text}")
def echo_template(text: str) -> str:
"""Echo the input text"""
return f"Echo: {text}"
@mcp.prompt("echo")
def echo_prompt(text: str) -> str:
return text

View file

@ -0,0 +1,56 @@
"""MCPServer Icons Demo Server
Demonstrates using icons with tools, resources, prompts, and implementation.
"""
import base64
from pathlib import Path
from mcp.server.mcpserver import Icon, MCPServer
# Load the icon file and convert to data URI
icon_path = Path(__file__).parent / "mcp.png"
icon_data = base64.standard_b64encode(icon_path.read_bytes()).decode()
icon_data_uri = f"data:image/png;base64,{icon_data}"
icon_data = Icon(src=icon_data_uri, mime_type="image/png", sizes=["64x64"])
# Create server with icons in implementation
mcp = MCPServer(
"Icons Demo Server", website_url="https://github.com/modelcontextprotocol/python-sdk", icons=[icon_data]
)
@mcp.tool(icons=[icon_data])
def demo_tool(message: str) -> str:
"""A demo tool with an icon."""
return message
@mcp.resource("demo://readme", icons=[icon_data])
def readme_resource() -> str:
"""A demo resource with an icon"""
return "This resource has an icon"
@mcp.prompt("prompt_with_icon", icons=[icon_data])
def prompt_with_icon(text: str) -> str:
"""A demo prompt with an icon"""
return text
@mcp.tool(
icons=[
Icon(src=icon_data_uri, mime_type="image/png", sizes=["16x16"]),
Icon(src=icon_data_uri, mime_type="image/png", sizes=["32x32"]),
Icon(src=icon_data_uri, mime_type="image/png", sizes=["64x64"]),
]
)
def multi_icon_tool(action: str) -> str:
"""A tool demonstrating multiple icons."""
return "multi_icon_tool"
if __name__ == "__main__":
# Run the server
mcp.run()

View file

@ -0,0 +1,31 @@
"""MCPServer Echo Server that sends log messages and progress updates to the client"""
import asyncio
from mcp.server.mcpserver import Context, MCPServer
# Create server
mcp = MCPServer("Echo Server with logging and progress updates")
@mcp.tool()
async def echo(text: str, ctx: Context) -> str:
"""Echo the input text sending log messages and progress updates during processing."""
await ctx.report_progress(progress=0, total=100)
await ctx.info("Starting to process echo for input: " + text)
await asyncio.sleep(2)
await ctx.info("Halfway through processing echo for input: " + text)
await ctx.report_progress(progress=50, total=100)
await asyncio.sleep(2)
await ctx.info("Finished processing echo for input: " + text)
await ctx.report_progress(progress=100, total=100)
# Progress notifications are process asynchronously by the client.
# A small delay here helps ensure the last notification is processed by the client.
await asyncio.sleep(0.1)
return text

BIN
examples/mcpserver/mcp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1,324 @@
# /// script
# dependencies = ["pydantic-ai-slim[openai]", "asyncpg", "numpy", "pgvector"]
# ///
# uv pip install 'pydantic-ai-slim[openai]' asyncpg numpy pgvector
"""Recursive memory system inspired by the human brain's clustering of memories.
Uses OpenAI's 'text-embedding-3-small' model and pgvector for efficient
similarity search.
"""
import asyncio
import math
import os
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Annotated, Self, TypeVar
import asyncpg
import numpy as np
from openai import AsyncOpenAI
from pgvector.asyncpg import register_vector # Import register_vector
from pydantic import BaseModel, Field
from pydantic_ai import Agent
from mcp.server.mcpserver import MCPServer
MAX_DEPTH = 5
SIMILARITY_THRESHOLD = 0.7
DECAY_FACTOR = 0.99
REINFORCEMENT_FACTOR = 1.1
DEFAULT_LLM_MODEL = "openai:gpt-4o"
DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small"
T = TypeVar("T")
mcp = MCPServer("memory")
DB_DSN = "postgresql://postgres:postgres@localhost:54320/memory_db"
# reset memory with rm ~/.mcp/{USER}/memory/*
PROFILE_DIR = (Path.home() / ".mcp" / os.environ.get("USER", "anon") / "memory").resolve()
PROFILE_DIR.mkdir(parents=True, exist_ok=True)
def cosine_similarity(a: list[float], b: list[float]) -> float:
a_array = np.array(a, dtype=np.float64)
b_array = np.array(b, dtype=np.float64)
return np.dot(a_array, b_array) / (np.linalg.norm(a_array) * np.linalg.norm(b_array))
async def do_ai(
user_prompt: str,
system_prompt: str,
result_type: type[T] | Annotated,
deps=None,
) -> T:
agent = Agent(
DEFAULT_LLM_MODEL,
system_prompt=system_prompt,
result_type=result_type,
)
result = await agent.run(user_prompt, deps=deps)
return result.data
@dataclass
class Deps:
openai: AsyncOpenAI
pool: asyncpg.Pool
async def get_db_pool() -> asyncpg.Pool:
async def init(conn):
await conn.execute("CREATE EXTENSION IF NOT EXISTS vector;")
await register_vector(conn)
pool = await asyncpg.create_pool(DB_DSN, init=init)
return pool
class MemoryNode(BaseModel):
id: int | None = None
content: str
summary: str = ""
importance: float = 1.0
access_count: int = 0
timestamp: float = Field(default_factory=lambda: datetime.now(timezone.utc).timestamp())
embedding: list[float]
@classmethod
async def from_content(cls, content: str, deps: Deps):
embedding = await get_embedding(content, deps)
return cls(content=content, embedding=embedding)
async def save(self, deps: Deps):
async with deps.pool.acquire() as conn:
if self.id is None:
result = await conn.fetchrow(
"""
INSERT INTO memories (content, summary, importance, access_count,
timestamp, embedding)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id
""",
self.content,
self.summary,
self.importance,
self.access_count,
self.timestamp,
self.embedding,
)
self.id = result["id"]
else:
await conn.execute(
"""
UPDATE memories
SET content = $1, summary = $2, importance = $3,
access_count = $4, timestamp = $5, embedding = $6
WHERE id = $7
""",
self.content,
self.summary,
self.importance,
self.access_count,
self.timestamp,
self.embedding,
self.id,
)
async def merge_with(self, other: Self, deps: Deps):
self.content = await do_ai(
f"{self.content}\n\n{other.content}",
"Combine the following two texts into a single, coherent text.",
str,
deps,
)
self.importance += other.importance
self.access_count += other.access_count
self.embedding = [(a + b) / 2 for a, b in zip(self.embedding, other.embedding)]
self.summary = await do_ai(self.content, "Summarize the following text concisely.", str, deps)
await self.save(deps)
# Delete the merged node from the database
if other.id is not None:
await delete_memory(other.id, deps)
def get_effective_importance(self):
return self.importance * (1 + math.log(self.access_count + 1))
async def get_embedding(text: str, deps: Deps) -> list[float]:
embedding_response = await deps.openai.embeddings.create(
input=text,
model=DEFAULT_EMBEDDING_MODEL,
)
return embedding_response.data[0].embedding
async def delete_memory(memory_id: int, deps: Deps):
async with deps.pool.acquire() as conn:
await conn.execute("DELETE FROM memories WHERE id = $1", memory_id)
async def add_memory(content: str, deps: Deps):
new_memory = await MemoryNode.from_content(content, deps)
await new_memory.save(deps)
similar_memories = await find_similar_memories(new_memory.embedding, deps)
for memory in similar_memories:
if memory.id != new_memory.id:
await new_memory.merge_with(memory, deps)
await update_importance(new_memory.embedding, deps)
await prune_memories(deps)
return f"Remembered: {content}"
async def find_similar_memories(embedding: list[float], deps: Deps) -> list[MemoryNode]:
async with deps.pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT id, content, summary, importance, access_count, timestamp, embedding
FROM memories
ORDER BY embedding <-> $1
LIMIT 5
""",
embedding,
)
memories = [
MemoryNode(
id=row["id"],
content=row["content"],
summary=row["summary"],
importance=row["importance"],
access_count=row["access_count"],
timestamp=row["timestamp"],
embedding=row["embedding"],
)
for row in rows
]
return memories
async def update_importance(user_embedding: list[float], deps: Deps):
async with deps.pool.acquire() as conn:
rows = await conn.fetch("SELECT id, importance, access_count, embedding FROM memories")
for row in rows:
memory_embedding = row["embedding"]
similarity = cosine_similarity(user_embedding, memory_embedding)
if similarity > SIMILARITY_THRESHOLD:
new_importance = row["importance"] * REINFORCEMENT_FACTOR
new_access_count = row["access_count"] + 1
else:
new_importance = row["importance"] * DECAY_FACTOR
new_access_count = row["access_count"]
await conn.execute(
"""
UPDATE memories
SET importance = $1, access_count = $2
WHERE id = $3
""",
new_importance,
new_access_count,
row["id"],
)
async def prune_memories(deps: Deps):
async with deps.pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT id, importance, access_count
FROM memories
ORDER BY importance DESC
OFFSET $1
""",
MAX_DEPTH,
)
for row in rows:
await conn.execute("DELETE FROM memories WHERE id = $1", row["id"])
async def display_memory_tree(deps: Deps) -> str:
async with deps.pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT content, summary, importance, access_count
FROM memories
ORDER BY importance DESC
LIMIT $1
""",
MAX_DEPTH,
)
result = ""
for row in rows:
effective_importance = row["importance"] * (1 + math.log(row["access_count"] + 1))
summary = row["summary"] or row["content"]
result += f"- {summary} (Importance: {effective_importance:.2f})\n"
return result
@mcp.tool()
async def remember(
contents: list[str] = Field(description="List of observations or memories to store"),
):
deps = Deps(openai=AsyncOpenAI(), pool=await get_db_pool())
try:
return "\n".join(await asyncio.gather(*[add_memory(content, deps) for content in contents]))
finally:
await deps.pool.close()
@mcp.tool()
async def read_profile() -> str:
deps = Deps(openai=AsyncOpenAI(), pool=await get_db_pool())
profile = await display_memory_tree(deps)
await deps.pool.close()
return profile
async def initialize_database():
pool = await asyncpg.create_pool("postgresql://postgres:postgres@localhost:54320/postgres")
try:
async with pool.acquire() as conn:
await conn.execute("""
SELECT pg_terminate_backend(pg_stat_activity.pid)
FROM pg_stat_activity
WHERE pg_stat_activity.datname = 'memory_db'
AND pid <> pg_backend_pid();
""")
await conn.execute("DROP DATABASE IF EXISTS memory_db;")
await conn.execute("CREATE DATABASE memory_db;")
finally:
await pool.close()
pool = await asyncpg.create_pool(DB_DSN)
try:
async with pool.acquire() as conn:
await conn.execute("CREATE EXTENSION IF NOT EXISTS vector;")
await register_vector(conn)
await conn.execute("""
CREATE TABLE IF NOT EXISTS memories (
id SERIAL PRIMARY KEY,
content TEXT NOT NULL,
summary TEXT,
importance REAL NOT NULL,
access_count INT NOT NULL,
timestamp DOUBLE PRECISION NOT NULL,
embedding vector(1536) NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_memories_embedding ON memories
USING hnsw (embedding vector_l2_ops);
""")
finally:
await pool.close()
if __name__ == "__main__":
asyncio.run(initialize_database())

View file

@ -0,0 +1,19 @@
"""MCPServer Example showing parameter descriptions"""
from pydantic import Field
from mcp.server.mcpserver import MCPServer
# Create server
mcp = MCPServer("Parameter Descriptions Server")
@mcp.tool()
def greet_user(
name: str = Field(description="The name of the person to greet"),
title: str = Field(description="Optional title like Mr/Ms/Dr", default=""),
times: int = Field(description="Number of times to repeat the greeting", default=1),
) -> str:
"""Greet a user with optional title and repetition"""
greeting = f"Hello {title + ' ' if title else ''}{name}!"
return "\n".join([greeting] * times)

View file

@ -0,0 +1,18 @@
from mcp.server.mcpserver import MCPServer
# Create an MCP server
mcp = MCPServer("Demo")
# Add an addition tool
@mcp.tool()
def sum(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}!"

View file

@ -0,0 +1,27 @@
"""MCPServer Screenshot Example
Give Claude a tool to capture and view screenshots.
"""
import io
from mcp.server.mcpserver import MCPServer
from mcp.server.mcpserver.utilities.types import Image
# Create server
mcp = MCPServer("Screenshot Demo")
@mcp.tool()
def take_screenshot() -> Image:
"""Take a screenshot of the user's screen and return it as an image. Use
this tool anytime the user wants you to look at something they're doing.
"""
import pyautogui
buffer = io.BytesIO()
# if the file exceeds ~1MB, it will be rejected by Claude
screenshot = pyautogui.screenshot()
screenshot.convert("RGB").save(buffer, format="JPEG", quality=60, optimize=True)
return Image(data=buffer.getvalue(), format="jpeg")

View file

@ -0,0 +1,12 @@
"""MCPServer Echo Server"""
from mcp.server.mcpserver import MCPServer
# Create server
mcp = MCPServer("Echo Server")
@mcp.tool()
def echo(text: str) -> str:
"""Echo the input text"""
return text

View file

@ -0,0 +1,67 @@
# /// script
# dependencies = []
# ///
"""MCPServer Text Me Server
--------------------------------
This defines a simple MCPServer server that sends a text message to a phone number via https://surgemsg.com/.
To run this example, create a `.env` file with the following values:
SURGE_API_KEY=...
SURGE_ACCOUNT_ID=...
SURGE_MY_PHONE_NUMBER=...
SURGE_MY_FIRST_NAME=...
SURGE_MY_LAST_NAME=...
Visit https://surgemsg.com/ and click "Get Started" to obtain these values.
"""
from typing import Annotated
import httpx
from pydantic import BeforeValidator
from pydantic_settings import BaseSettings, SettingsConfigDict
from mcp.server.mcpserver import MCPServer
class SurgeSettings(BaseSettings):
model_config: SettingsConfigDict = SettingsConfigDict(env_prefix="SURGE_", env_file=".env")
api_key: str
account_id: str
my_phone_number: Annotated[str, BeforeValidator(lambda v: "+" + v if not v.startswith("+") else v)]
my_first_name: str
my_last_name: str
# Create server
mcp = MCPServer("Text me")
surge_settings = SurgeSettings() # type: ignore
@mcp.tool(name="textme", description="Send a text message to me")
def text_me(text_content: str) -> str:
"""Send a text message to a phone number via https://surgemsg.com/"""
with httpx.Client() as client:
response = client.post(
"https://api.surgemsg.com/messages",
headers={
"Authorization": f"Bearer {surge_settings.api_key}",
"Surge-Account": surge_settings.account_id,
"Content-Type": "application/json",
},
json={
"body": text_content,
"conversation": {
"contact": {
"first_name": surge_settings.my_first_name,
"last_name": surge_settings.my_last_name,
"phone_number": surge_settings.my_phone_number,
}
},
},
)
response.raise_for_status()
return f"Message sent: {text_content}"

View file

@ -0,0 +1,59 @@
"""Example MCPServer server that uses Unicode characters in various places to help test
Unicode handling in tools and inspectors.
"""
from mcp.server.mcpserver import MCPServer
mcp = MCPServer()
@mcp.tool(description="🌟 A tool that uses various Unicode characters in its description: á é í ó ú ñ 漢字 🎉")
def hello_unicode(name: str = "世界", greeting: str = "¡Hola") -> str:
"""A simple tool that demonstrates Unicode handling in:
- Tool description (emojis, accents, CJK characters)
- Parameter defaults (CJK characters)
- Return values (Spanish punctuation, emojis)
"""
return f"{greeting}, {name}! 👋"
@mcp.tool(description="🎨 Tool that returns a list of emoji categories")
def list_emoji_categories() -> list[str]:
"""Returns a list of emoji categories with emoji examples."""
return [
"😀 Smileys & Emotion",
"👋 People & Body",
"🐶 Animals & Nature",
"🍎 Food & Drink",
"⚽ Activities",
"🌍 Travel & Places",
"💡 Objects",
"❤️ Symbols",
"🚩 Flags",
]
@mcp.tool(description="🔤 Tool that returns text in different scripts")
def multilingual_hello() -> str:
"""Returns hello in different scripts and writing systems."""
return "\n".join(
[
"English: Hello!",
"Spanish: ¡Hola!",
"French: Bonjour!",
"German: Grüß Gott!",
"Russian: Привет!",
"Greek: Γεια σας!",
"Hebrew: !שָׁלוֹם",
"Arabic: !مرحبا",
"Hindi: नमस्ते!",
"Chinese: 你好!",
"Japanese: こんにちは!",
"Korean: 안녕하세요!",
"Thai: สวัสดี!",
]
)
if __name__ == "__main__":
mcp.run()

Some files were not shown because too many files have changed in this diff Show more