Skip to content

Upgrade types.py to 2025-03-26 protocol version #456

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

Closed
Closed
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
9 changes: 8 additions & 1 deletion src/mcp/shared/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,9 @@ async def _receive_loop(self) -> None:
f"Failed to validate notification: {e}. "
f"Message was: {message.root}"
)
else: # Response or error
elif isinstance(message.root, JSONRPCError) or isinstance(
message.root, JSONRPCResponse
):
stream = self._response_streams.pop(message.root.id, None)
if stream:
await stream.send(message.root)
Expand All @@ -358,6 +360,11 @@ async def _receive_loop(self) -> None:
f"request ID: {message}"
)
)
else:
# Couldn't be anything else.
raise NotImplementedError(
"Unhandled JSONRPCMessage type, message: " f"{message}"
)

async def _received_request(
self, responder: RequestResponder[ReceiveRequestT, SendResultT]
Expand Down
117 changes: 111 additions & 6 deletions src/mcp/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
not separate types in the schema.
"""

LATEST_PROTOCOL_VERSION = "2024-11-05"
LATEST_PROTOCOL_VERSION = "2025-03-26"

ProgressToken = str | int
Cursor = str
Expand Down Expand Up @@ -131,6 +131,12 @@ class JSONRPCNotification(Notification[dict[str, Any] | None, str]):
params: dict[str, Any] | None = None


class JSONRPCBatchRequest(RootModel[list[JSONRPCRequest | JSONRPCNotification]]):
"""A JSON-RPC batch request"""

pass


class JSONRPCResponse(BaseModel):
"""A successful (non-error) response to a request."""

Expand Down Expand Up @@ -178,8 +184,21 @@ class JSONRPCError(BaseModel):
model_config = ConfigDict(extra="allow")


class JSONRPCBatchResponse(RootModel[list[JSONRPCResponse | JSONRPCError]]):
"""A JSON-RPC batch response"""

pass


class JSONRPCMessage(
RootModel[JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError]
RootModel[
JSONRPCRequest
| JSONRPCNotification
| JSONRPCBatchRequest
| JSONRPCResponse
| JSONRPCError
| JSONRPCBatchResponse
]
):
pass

Expand Down Expand Up @@ -254,13 +273,21 @@ class LoggingCapability(BaseModel):
model_config = ConfigDict(extra="allow")


class CompletionCapability(BaseModel):
"""Capability for completion operations."""

model_config = ConfigDict(extra="allow")


class ServerCapabilities(BaseModel):
"""Capabilities that a server may support."""

experimental: dict[str, dict[str, Any]] | None = None
"""Experimental, non-standard capabilities that the server supports."""
logging: LoggingCapability | None = None
"""Present if the server supports sending log messages to the client."""
completions: CompletionCapability | None = None
"""Present if the server supports argument autocompletion suggestions."""
prompts: PromptsCapability | None = None
"""Present if the server offers any prompt templates."""
resources: ResourcesCapability | None = None
Expand Down Expand Up @@ -338,6 +365,8 @@ class ProgressNotificationParams(NotificationParams):
"""
total: float | None = None
"""Total number of items to process (or total progress required), if known."""
message: str | None = None
"""An optional message describing the current progress."""
model_config = ConfigDict(extra="allow")


Expand Down Expand Up @@ -646,11 +675,25 @@ class ImageContent(BaseModel):
model_config = ConfigDict(extra="allow")


class AudioContent(BaseModel):
"""Audio provided to or from an LLM."""

type: Literal["audio"]
data: str
"""The base64-encoded audio data."""
mimeType: str
"""
The MIME type of the audio. Different providers may support different audio
types.
"""
annotations: Annotations | None = None


class SamplingMessage(BaseModel):
"""Describes a message issued to or received from an LLM API."""

role: Role
content: TextContent | ImageContent
content: TextContent | ImageContent | AudioContent
model_config = ConfigDict(extra="allow")


Expand All @@ -672,7 +715,7 @@ class PromptMessage(BaseModel):
"""Describes a message returned as part of a prompt."""

role: Role
content: TextContent | ImageContent | EmbeddedResource
content: TextContent | ImageContent | AudioContent | EmbeddedResource
model_config = ConfigDict(extra="allow")


Expand Down Expand Up @@ -705,15 +748,77 @@ class ListToolsRequest(PaginatedRequest[RequestParams | None, Literal["tools/lis
params: RequestParams | None = None


class ToolAnnotations(BaseModel):
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To set the optional bool default values, I think you would do:

    readOnlyHint: bool = False
    destructiveHint: bool = True
    idempotentHint: bool = False
    openWorldHint: bool = True

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking through the rest of the code, the convention seems to be to set the default to None instead of False/True? I think the default convention makes sense because we want to tell if the other side specified True/False or left it absent.

Note that this PR probably conflicts with a later PR #482, in which the default is left as None as well.

Not sure if you've permission to merge, but if you do, it looks like PR #482 is likely to be merged, so maybe I'll leave the ToolAnnotations out so we can merge the rest?

Additional properties describing a Tool to clients.
NOTE: all properties in ToolAnnotations are **hints**.
They are not guaranteed to provide a faithful description of
tool behavior (including descriptive properties like `title`).
Clients should never make tool use decisions based on ToolAnnotations
received from untrusted servers.
"""

title: str | None = None
"""A human-readable title for the tool."""

readOnlyHint: bool | None = None
"""
If true, the tool does not modify its environment.
Default: false
"""

destructiveHint: bool | None = None
"""
If true, the tool may perform destructive updates to its environment.
If false, the tool performs only additive updates.
(This property is meaningful only when `readOnlyHint == false`)
Default: true
"""

idempotentHint: bool | None = None
"""
If true, calling the tool repeatedly with the same arguments
will have no additional effect on the its environment.
(This property is meaningful only when `readOnlyHint == false`)
Default: false
"""

openWorldHint: bool | None = None
"""
If true, this tool may interact with an "open world" of external
entities. If false, the tool's domain of interaction is closed.
For example, the world of a web search tool is open, whereas that
of a memory tool is not.
Default: true
"""

model_config = ConfigDict(extra="allow")


class Tool(BaseModel):
"""Definition for a tool the client can call."""

name: str
"""The name of the tool."""
description: str | None = None
"""A human-readable description of the tool."""
"""
A human-readable description of the tool.
This can be used by clients to improve the LLM's understanding of available
tools. It can be thought of like a "hint" to the model.
"""
inputSchema: dict[str, Any]
"""A JSON Schema object defining the expected parameters for the tool."""
annotations: ToolAnnotations | None = None
"""Optional additional tool information."""
model_config = ConfigDict(extra="allow")


Expand Down Expand Up @@ -741,7 +846,7 @@ class CallToolRequest(Request[CallToolRequestParams, Literal["tools/call"]]):
class CallToolResult(Result):
"""The server's response to a tool call."""

content: list[TextContent | ImageContent | EmbeddedResource]
content: list[TextContent | ImageContent | AudioContent | EmbeddedResource]
isError: bool = False


Expand Down
Loading