feat: backwards-compatible create_message overloads for SEP-1577 (#1713)

This commit is contained in:
Felix Weinberger 2025-12-02 13:17:45 +00:00 • committed by GitHub
commit fa851d93a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 209 additions and 32 deletions

View file

@ -13,6 +13,7 @@ from .types import (
CompleteRequest,
CreateMessageRequest,
CreateMessageResult,
CreateMessageResultWithTools,
ErrorData,
GetPromptRequest,
GetPromptResult,
@ -42,6 +43,7 @@ from .types import (
ResourceUpdatedNotification,
RootsCapability,
SamplingCapability,
SamplingContent,
SamplingContextCapability,
SamplingMessage,
SamplingMessageContentBlock,
@ -75,6 +77,7 @@ __all__ = [
"CompleteRequest",
"CreateMessageRequest",
"CreateMessageResult",
"CreateMessageResultWithTools",
"ErrorData",
"GetPromptRequest",
"GetPromptResult",
@ -105,6 +108,7 @@ __all__ = [
"ResourceUpdatedNotification",
"RootsCapability",
"SamplingCapability",
"SamplingContent",
"SamplingContextCapability",
"SamplingMessage",
"SamplingMessageContentBlock",

View file

@ -38,7 +38,7 @@ be instantiated directly by users of the MCP framework.
"""
from enum import Enum
from typing import Any, TypeVar
from typing import Any, TypeVar, overload
import anyio
import anyio.lowlevel
@ -233,6 +233,44 @@ class ServerSession(
)
)
@overload
async def create_message(
self,
messages: list[types.SamplingMessage],
*,
max_tokens: int,
system_prompt: str | None = None,
include_context: types.IncludeContext | None = None,
temperature: float | None = None,
stop_sequences: list[str] | None = None,
metadata: dict[str, Any] | None = None,
model_preferences: types.ModelPreferences | None = None,
tools: None = None,
tool_choice: types.ToolChoice | None = None,
related_request_id: types.RequestId | None = None,
) -> types.CreateMessageResult:
"""Overload: Without tools, returns single content."""
...
@overload
async def create_message(
self,
messages: list[types.SamplingMessage],
*,
max_tokens: int,
system_prompt: str | None = None,
include_context: types.IncludeContext | None = None,
temperature: float | None = None,
stop_sequences: list[str] | None = None,
metadata: dict[str, Any] | None = None,
model_preferences: types.ModelPreferences | None = None,
tools: list[types.Tool],
tool_choice: types.ToolChoice | None = None,
related_request_id: types.RequestId | None = None,
) -> types.CreateMessageResultWithTools:
"""Overload: With tools, returns array-capable content."""
...
async def create_message(
self,
messages: list[types.SamplingMessage],
@ -247,7 +285,7 @@ class ServerSession(
tools: list[types.Tool] | None = None,
tool_choice: types.ToolChoice | None = None,
related_request_id: types.RequestId | None = None,
) -> types.CreateMessageResult:
) -> types.CreateMessageResult | types.CreateMessageResultWithTools:
"""Send a sampling/create_message request.
Args:
@ -278,27 +316,35 @@ class ServerSession(
validate_sampling_tools(client_caps, tools, tool_choice)
validate_tool_use_result_messages(messages)
request = types.ServerRequest(
types.CreateMessageRequest(
params=types.CreateMessageRequestParams(
messages=messages,
systemPrompt=system_prompt,
includeContext=include_context,
temperature=temperature,
maxTokens=max_tokens,
stopSequences=stop_sequences,
metadata=metadata,
modelPreferences=model_preferences,
tools=tools,
toolChoice=tool_choice,
),
)
)
metadata_obj = ServerMessageMetadata(related_request_id=related_request_id)
# Use different result types based on whether tools are provided
if tools is not None:
return await self.send_request(
request=request,
result_type=types.CreateMessageResultWithTools,
metadata=metadata_obj,
)
return await self.send_request(
request=types.ServerRequest(
types.CreateMessageRequest(
params=types.CreateMessageRequestParams(
messages=messages,
systemPrompt=system_prompt,
includeContext=include_context,
temperature=temperature,
maxTokens=max_tokens,
stopSequences=stop_sequences,
metadata=metadata,
modelPreferences=model_preferences,
tools=tools,
toolChoice=tool_choice,
),
)
),
request=request,
result_type=types.CreateMessageResult,
metadata=ServerMessageMetadata(
related_request_id=related_request_id,
),
metadata=metadata_obj,
)
async def list_roots(self) -> types.ListRootsResult:

View file

@ -1146,6 +1146,10 @@ class ToolResultContent(BaseModel):
SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent
"""Content block types allowed in sampling messages."""
SamplingContent: TypeAlias = TextContent | ImageContent | AudioContent
"""Basic content types for sampling responses (without tool use).
Used for backwards-compatible CreateMessageResult when tools are not used."""
class SamplingMessage(BaseModel):
"""Describes a message issued to or received from an LLM API."""
@ -1543,7 +1547,27 @@ StopReason = Literal["endTurn", "stopSequence", "maxTokens", "toolUse"] | str
class CreateMessageResult(Result):
"""The client's response to a sampling/create_message request from the server."""
"""The client's response to a sampling/create_message request from the server.
This is the backwards-compatible version that returns single content (no arrays).
Used when the request does not include tools.
"""
role: Role
"""The role of the message sender (typically 'assistant' for LLM responses)."""
content: SamplingContent
"""Response content. Single content block (text, image, or audio)."""
model: str
"""The name of the model that generated the message."""
stopReason: StopReason | None = None
"""The reason why sampling stopped, if known."""
class CreateMessageResultWithTools(Result):
"""The client's response to a sampling/create_message request when tools were provided.
This version supports array content for tool use flows.
"""
role: Role
"""The role of the message sender (typically 'assistant' for LLM responses)."""