Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
158 changes: 158 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Changelog

## [1.3.0] - 2025-02-20

### Breaking Changes

- **Context API Changes**: The Context logging methods (info, debug, warning, error) are now async and must be awaited. ([#172](https://github.com/modelcontextprotocol/python-sdk/pull/172))
- **Resource Response Format**: Standardized resource response format to return both content and MIME type. Method `read_resource()` now returns a tuple of `(content, mime_type)` instead of just content. ([#170](https://github.com/modelcontextprotocol/python-sdk/pull/170))

### New Features

#### Lifespan Support
Added comprehensive server lifecycle management through the lifespan API:
```python
@dataclass
class AppContext:
db: Database

@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
try:
await db.connect()
yield AppContext(db=db)
finally:
await db.disconnect()

mcp = FastMCP("My App", lifespan=app_lifespan)

@mcp.tool()
def query_db(ctx: Context) -> str:
db = ctx.request_context.lifespan_context["db"]
return db.query()
```
([#203](https://github.com/modelcontextprotocol/python-sdk/pull/203))

#### Async Resources
Added support for async resource functions in FastMCP:
```python
@mcp.resource("users://{user_id}")
async def get_user(user_id: str) -> str:
async with client.session() as session:
response = await session.get(f"/users/{user_id}")
return await response.text()
```
([#157](https://github.com/modelcontextprotocol/python-sdk/pull/157))

#### Concurrent Request Handling
Made message handling concurrent, allowing multiple requests to be processed simultaneously. ([#206](https://github.com/modelcontextprotocol/python-sdk/pull/206))

#### Request Cancellation
Added support for canceling in-flight requests and cleaning up resources. ([#167](https://github.com/modelcontextprotocol/python-sdk/pull/167))

#### Server Instructions
Added support for the `instructions` field in server initialization, allowing servers to provide usage guidance. ([#150](https://github.com/modelcontextprotocol/python-sdk/pull/150))

### Bug Fixes

- Fixed progress reporting for first tool call by correcting progress_token handling ([#176](https://github.com/modelcontextprotocol/python-sdk/pull/176))
- Fixed server crash when using debug logging ([#158](https://github.com/modelcontextprotocol/python-sdk/pull/158))
- Fixed resource template handling in FastMCP server ([#137](https://github.com/modelcontextprotocol/python-sdk/pull/137))
- Fixed MIME type preservation in resource responses ([#170](https://github.com/modelcontextprotocol/python-sdk/pull/170))
- Fixed documentation for environment variables in CLI commands ([#149](https://github.com/modelcontextprotocol/python-sdk/pull/149))
- Fixed request ID preservation in JSON-RPC responses ([#205](https://github.com/modelcontextprotocol/python-sdk/pull/205))

### Dependency Updates

- Relaxed version constraints for better compatibility:
- `pydantic`: Changed from `>=2.10.1,<3.0.0` to `>=2.7.2,<3.0.0`
- `pydantic-settings`: Changed from `>=2.6.1` to `>=2.5.2`
- `uvicorn`: Changed from `>=0.30` to `>=0.23.1`
([#180](https://github.com/modelcontextprotocol/python-sdk/pull/180))

### Examples

- Added a simple chatbot example client to demonstrate SDK usage ([#98](https://github.com/modelcontextprotocol/python-sdk/pull/98))

### Client Improvements

- Added client support for sampling, list roots, and ping requests ([#218](https://github.com/modelcontextprotocol/python-sdk/pull/218))
- Added flexible type system for tool result returns ([#222](https://github.com/modelcontextprotocol/python-sdk/pull/222))

### Compatibility and Platform Support

- Updated URL validation to allow file and other nonstandard schemas ([#68fcf92](https://github.com/modelcontextprotocol/python-sdk/commit/68fcf92947f7d02d50340053a72a969d6bb70e1b))
- Force stdin/stdout encoding to UTF-8 for cross-platform compatibility ([#d92ee8f](https://github.com/modelcontextprotocol/python-sdk/commit/d92ee8feaa5675efddd399f3e8ebe8ed976b84c2))

### Internal Improvements

- Improved type annotations for better IDE support ([#181](https://github.com/modelcontextprotocol/python-sdk/pull/181))
- Added comprehensive tests for SSE transport ([#151](https://github.com/modelcontextprotocol/python-sdk/pull/151))
- Updated types to match 2024-11-05 MCP schema ([#165](https://github.com/modelcontextprotocol/python-sdk/pull/165))
- Refactored request and notification handling for better code organization ([#166](https://github.com/modelcontextprotocol/python-sdk/pull/166))

## [1.2.1] - 2024-01-27

### Added
- Support for async resources
- Example and test for parameter descriptions in FastMCP tools

### Fixed
- MCP install command with environment variables
- Resource template handling in FastMCP server (#129)
- Package in the generated MCP run config (#128)
- FastMCP logger debug output
- Handling of strings containing numbers in FastMCP (@sd2k, #142)

### Changed
- Refactored to use built-in typing.Annotated instead of typing_extensions
- Updated uv.lock
- Added .DS_Store to gitignore

# MCP Python SDK v1.2.0rc1 Release Notes

## Major Features

### FastMCP Integration
- Integrated [FastMCP](https://github.com/jlowin/fastmcp) as the recommended high-level server framework
- Added new `mcp.server.fastmcp` module with simplified decorator-based API
- Introduced `FastMCP` class for easier server creation and management
- Added comprehensive documentation and examples for FastMCP usage

### New CLI Package
- Added new CLI package for improved developer experience
- Introduced `mcp dev` command for local development and testing
- Added `mcp install` command for Claude Desktop integration
- Added `mcp run` command for direct server execution

## Improvements

### Documentation
- Completely revamped README with new structure and examples
- Added detailed sections on core concepts (Resources, Tools, Prompts)
- Updated documentation to recommend FastMCP as primary API
- Added sections on development workflow and deployment options
- Improved example server documentation

### Developer Experience
- Added pre-commit hooks for code quality
- Updated to Pydantic 2.10.0 for improved type checking
- Added uvicorn as a dependency for better server capabilities

## Bug Fixes
- Fixed deprecation warnings in core components
- Fixed Pydantic field handling for meta fields
- Fixed type issues throughout the codebase
- Fixed example server READMEs

## Breaking Changes
- Deprecated direct usage of `mcp.server` in favor of `mcp.server.fastmcp`
- Updated import paths for FastMCP integration
- Changed recommended installation to include CLI features (`pip install "mcp[cli]"`)

## Contributors
Special thanks to all contributors who made this release possible, including:
- Jeremiah Lowin (FastMCP)
- Oskar Raszkiewicz

**Full Changelog**: https://github.com/modelcontextprotocol/python-sdk/compare/v1.1.2...v1.2.0rc1
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,21 @@ server_params = StdioServerParameters(
env=None # Optional environment variables
)

# Optional: create a sampling callback
async def handle_sampling_message(message: types.CreateMessageRequestParams) -> types.CreateMessageResult:
return types.CreateMessageResult(
role="assistant",
content=types.TextContent(
type="text",
text="Hello, world! from model",
),
model="gpt-3.5-turbo",
stopReason="endTurn",
)

async def run():
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
async with ClientSession(read, write, sampling_callback=handle_sampling_message) as session:
# Initialize the connection
await session.initialize()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import click
import mcp.types as types
from mcp.server.lowlevel import Server
from pydantic import AnyUrl
from pydantic import FileUrl

SAMPLE_RESOURCES = {
"greeting": "Hello! This is a sample text resource.",
Expand All @@ -26,7 +26,7 @@ def main(port: int, transport: str) -> int:
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri=AnyUrl(f"file:///{name}.txt"),
uri=FileUrl(f"file:///{name}.txt"),
name=name,
description=f"A sample text resource named {name}",
mimeType="text/plain",
Expand All @@ -35,8 +35,7 @@ async def list_resources() -> list[types.Resource]:
]

@app.read_resource()
async def read_resource(uri: AnyUrl) -> str | bytes:
assert uri.path is not None
async def read_resource(uri: FileUrl) -> str | bytes:
name = uri.path.replace(".txt", "").lstrip("/")

if name not in SAMPLE_RESOURCES:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "mcp"
version = "1.3.0.dev0"
version = "1.3.0"
description = "Model Context Protocol SDK"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
98 changes: 89 additions & 9 deletions src/mcp/client/session.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,51 @@
from datetime import timedelta
from typing import Any, Protocol

from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
from pydantic import AnyUrl
from pydantic import AnyUrl, TypeAdapter

import mcp.types as types
from mcp.shared.session import BaseSession
from mcp.shared.context import RequestContext
from mcp.shared.session import BaseSession, RequestResponder
from mcp.shared.version import SUPPORTED_PROTOCOL_VERSIONS


class SamplingFnT(Protocol):
async def __call__(
self,
context: RequestContext["ClientSession", Any],
params: types.CreateMessageRequestParams,
) -> types.CreateMessageResult | types.ErrorData: ...


class ListRootsFnT(Protocol):
async def __call__(
self, context: RequestContext["ClientSession", Any]
) -> types.ListRootsResult | types.ErrorData: ...


async def _default_sampling_callback(
context: RequestContext["ClientSession", Any],
params: types.CreateMessageRequestParams,
) -> types.CreateMessageResult | types.ErrorData:
return types.ErrorData(
code=types.INVALID_REQUEST,
message="Sampling not supported",
)


async def _default_list_roots_callback(
context: RequestContext["ClientSession", Any],
) -> types.ListRootsResult | types.ErrorData:
return types.ErrorData(
code=types.INVALID_REQUEST,
message="List roots not supported",
)


ClientResponse = TypeAdapter(types.ClientResult | types.ErrorData)


class ClientSession(
BaseSession[
types.ClientRequest,
Expand All @@ -22,6 +60,8 @@ def __init__(
read_stream: MemoryObjectReceiveStream[types.JSONRPCMessage | Exception],
write_stream: MemoryObjectSendStream[types.JSONRPCMessage],
read_timeout_seconds: timedelta | None = None,
sampling_callback: SamplingFnT | None = None,
list_roots_callback: ListRootsFnT | None = None,
) -> None:
super().__init__(
read_stream,
Expand All @@ -30,23 +70,34 @@ def __init__(
types.ServerNotification,
read_timeout_seconds=read_timeout_seconds,
)
self._sampling_callback = sampling_callback or _default_sampling_callback
self._list_roots_callback = list_roots_callback or _default_list_roots_callback

async def initialize(self) -> types.InitializeResult:
sampling = (
types.SamplingCapability() if self._sampling_callback is not None else None
)
roots = (
types.RootsCapability(
# TODO: Should this be based on whether we
# _will_ send notifications, or only whether
# they're supported?
listChanged=True,
)
if self._list_roots_callback is not None
else None
)

result = await self.send_request(
types.ClientRequest(
types.InitializeRequest(
method="initialize",
params=types.InitializeRequestParams(
protocolVersion=types.LATEST_PROTOCOL_VERSION,
capabilities=types.ClientCapabilities(
sampling=None,
sampling=sampling,
experimental=None,
roots=types.RootsCapability(
# TODO: Should this be based on whether we
# _will_ send notifications, or only whether
# they're supported?
listChanged=True
),
roots=roots,
),
clientInfo=types.Implementation(name="mcp", version="0.1.0"),
),
Expand Down Expand Up @@ -243,3 +294,32 @@ async def send_roots_list_changed(self) -> None:
)
)
)

async def _received_request(
self, responder: RequestResponder[types.ServerRequest, types.ClientResult]
) -> None:
ctx = RequestContext[ClientSession, Any](
request_id=responder.request_id,
meta=responder.request_meta,
session=self,
lifespan_context=None,
)

match responder.request.root:
case types.CreateMessageRequest(params=params):
with responder:
response = await self._sampling_callback(ctx, params)
client_response = ClientResponse.validate_python(response)
await responder.respond(client_response)

case types.ListRootsRequest():
with responder:
response = await self._list_roots_callback(ctx)
client_response = ClientResponse.validate_python(response)
await responder.respond(client_response)

case types.PingRequest():
with responder:
return await responder.respond(
types.ClientResult(root=types.EmptyResult())
)
Loading
Loading