Skip to content

Commit 224fc22

Browse files
committed
Simplify sampling types to match draft spec
- Replace UserMessage/AssistantMessage with unified SamplingMessage - Add SamplingMessageContentBlock type alias for content blocks - Update CreateMessageResult to use SamplingMessageContentBlock - Remove disable_parallel_tool_use from ToolChoice (not in spec) - Add ResourceLink to ToolResultContent.content for consistency This aligns with the draft spec where SamplingMessage uses a single content type union rather than separate UserMessage/AssistantMessage classes. The CreateMessageResult now extends SamplingMessage's pattern and supports both single content blocks and arrays.
1 parent b74bff2 commit 224fc22

File tree

3 files changed

+31
-86
lines changed

3 files changed

+31
-86
lines changed

src/mcp/__init__.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from .server.stdio import stdio_server
66
from .shared.exceptions import McpError
77
from .types import (
8-
AssistantMessage,
98
CallToolRequest,
109
ClientCapabilities,
1110
ClientNotification,
@@ -45,6 +44,7 @@
4544
SamplingCapability,
4645
SamplingContextCapability,
4746
SamplingMessage,
47+
SamplingMessageContentBlock,
4848
SamplingToolsCapability,
4949
ServerCapabilities,
5050
ServerNotification,
@@ -59,14 +59,12 @@
5959
ToolsCapability,
6060
ToolUseContent,
6161
UnsubscribeRequest,
62-
UserMessage,
6362
)
6463
from .types import (
6564
Role as SamplingRole,
6665
)
6766

6867
__all__ = [
69-
"AssistantMessage",
7068
"CallToolRequest",
7169
"ClientCapabilities",
7270
"ClientNotification",
@@ -109,6 +107,7 @@
109107
"SamplingCapability",
110108
"SamplingContextCapability",
111109
"SamplingMessage",
110+
"SamplingMessageContentBlock",
112111
"SamplingRole",
113112
"SamplingToolsCapability",
114113
"ServerCapabilities",
@@ -126,7 +125,6 @@
126125
"ToolsCapability",
127126
"ToolUseContent",
128127
"UnsubscribeRequest",
129-
"UserMessage",
130128
"stdio_client",
131129
"stdio_server",
132130
]

src/mcp/types.py

Lines changed: 13 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,7 @@ class ToolResultContent(BaseModel):
822822
toolUseId: str
823823
"""The unique identifier that corresponds to the tool call's id field."""
824824

825-
content: list[Union[TextContent, ImageContent, AudioContent, "EmbeddedResource"]] = []
825+
content: list[Union[TextContent, ImageContent, AudioContent, "ResourceLink", "EmbeddedResource"]] = []
826826
"""
827827
A list of content objects representing the tool result.
828828
Defaults to empty list if not provided.
@@ -844,23 +844,17 @@ class ToolResultContent(BaseModel):
844844
model_config = ConfigDict(extra="allow")
845845

846846

847-
class SamplingMessage(BaseModel):
848-
"""
849-
Describes a message issued to or received from an LLM API.
847+
SamplingMessageContentBlock: TypeAlias = (
848+
TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent
849+
)
850+
"""Content block types allowed in sampling messages."""
850851

851-
For backward compatibility, this class accepts any role and any content type.
852-
For type-safe usage with tool calling, use UserMessage or AssistantMessage instead.
853-
"""
852+
853+
class SamplingMessage(BaseModel):
854+
"""Describes a message issued to or received from an LLM API."""
854855

855856
role: Role
856-
content: (
857-
TextContent
858-
| ImageContent
859-
| AudioContent
860-
| ToolUseContent
861-
| ToolResultContent
862-
| list[TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent]
863-
)
857+
content: SamplingMessageContentBlock | list[SamplingMessageContentBlock]
864858
"""
865859
Message content. Can be a single content block or an array of content blocks
866860
for multi-modal messages and tool interactions.
@@ -873,50 +867,6 @@ class SamplingMessage(BaseModel):
873867
model_config = ConfigDict(extra="allow")
874868

875869

876-
# Type aliases for role-specific messages
877-
UserMessageContent: TypeAlias = TextContent | ImageContent | AudioContent | ToolResultContent
878-
"""Content types allowed in user messages during sampling."""
879-
880-
AssistantMessageContent: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent
881-
"""Content types allowed in assistant messages during sampling."""
882-
883-
884-
class UserMessage(BaseModel):
885-
"""
886-
A message from the user (server) in a sampling conversation.
887-
888-
User messages can include tool results in response to assistant tool use requests.
889-
"""
890-
891-
role: Literal["user"]
892-
content: UserMessageContent | list[UserMessageContent]
893-
"""Message content. Can be a single content block or an array for multi-modal messages."""
894-
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
895-
"""
896-
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
897-
for notes on _meta usage.
898-
"""
899-
model_config = ConfigDict(extra="allow")
900-
901-
902-
class AssistantMessage(BaseModel):
903-
"""
904-
A message from the assistant (LLM) in a sampling conversation.
905-
906-
Assistant messages can include tool use requests when the LLM wants to call tools.
907-
"""
908-
909-
role: Literal["assistant"]
910-
content: AssistantMessageContent | list[AssistantMessageContent]
911-
"""Message content. Can be a single content block or an array for multi-modal messages."""
912-
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
913-
"""
914-
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
915-
for notes on _meta usage.
916-
"""
917-
model_config = ConfigDict(extra="allow")
918-
919-
920870
class EmbeddedResource(BaseModel):
921871
"""
922872
The contents of a resource, embedded into a prompt or tool call result.
@@ -1218,12 +1168,6 @@ class ToolChoice(BaseModel):
12181168
- "none": Model should not use tools
12191169
"""
12201170

1221-
disable_parallel_tool_use: bool | None = None
1222-
"""
1223-
If true, the model should not use multiple tools in parallel.
1224-
Some models may ignore this hint. Default: false (parallel use enabled).
1225-
"""
1226-
12271171
model_config = ConfigDict(extra="allow")
12281172

12291173

@@ -1275,11 +1219,11 @@ class CreateMessageRequest(Request[CreateMessageRequestParams, Literal["sampling
12751219
class CreateMessageResult(Result):
12761220
"""The client's response to a sampling/create_message request from the server."""
12771221

1278-
role: Literal["assistant"]
1279-
"""The role is always 'assistant' in responses from the LLM."""
1280-
content: AssistantMessageContent | list[AssistantMessageContent]
1222+
role: Role
1223+
"""The role of the message sender (typically 'assistant' for LLM responses)."""
1224+
content: SamplingMessageContentBlock | list[SamplingMessageContentBlock]
12811225
"""
1282-
Response content from the assistant. May be a single content block or an array.
1226+
Response content. May be a single content block or an array.
12831227
May include ToolUseContent if stopReason is 'toolUse'.
12841228
"""
12851229
model: str

tests/test_types.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from mcp.types import (
44
LATEST_PROTOCOL_VERSION,
5-
AssistantMessage,
65
ClientCapabilities,
76
ClientRequest,
87
CreateMessageRequestParams,
@@ -19,7 +18,6 @@
1918
ToolChoice,
2019
ToolResultContent,
2120
ToolUseContent,
22-
UserMessage,
2321
)
2422

2523

@@ -117,23 +115,28 @@ async def test_tool_result_content():
117115
async def test_tool_choice():
118116
"""Test ToolChoice type for SEP-1577."""
119117
# Test with mode
120-
tool_choice_data = {"mode": "required", "disable_parallel_tool_use": True}
118+
tool_choice_data = {"mode": "required"}
121119
tool_choice = ToolChoice.model_validate(tool_choice_data)
122120
assert tool_choice.mode == "required"
123-
assert tool_choice.disable_parallel_tool_use is True
124121

125122
# Test with minimal data (all fields optional)
126123
minimal_choice = ToolChoice.model_validate({})
127124
assert minimal_choice.mode is None
128-
assert minimal_choice.disable_parallel_tool_use is None
125+
126+
# Test different modes
127+
auto_choice = ToolChoice.model_validate({"mode": "auto"})
128+
assert auto_choice.mode == "auto"
129+
130+
none_choice = ToolChoice.model_validate({"mode": "none"})
131+
assert none_choice.mode == "none"
129132

130133

131134
@pytest.mark.anyio
132-
async def test_user_message():
133-
"""Test UserMessage type for SEP-1577."""
135+
async def test_sampling_message_with_user_role():
136+
"""Test SamplingMessage with user role for SEP-1577."""
134137
# Test with single content
135138
user_msg_data = {"role": "user", "content": {"type": "text", "text": "Hello"}}
136-
user_msg = UserMessage.model_validate(user_msg_data)
139+
user_msg = SamplingMessage.model_validate(user_msg_data)
137140
assert user_msg.role == "user"
138141
assert isinstance(user_msg.content, TextContent)
139142

@@ -145,15 +148,15 @@ async def test_user_message():
145148
{"type": "tool_result", "toolUseId": "call_123", "content": []},
146149
],
147150
}
148-
multi_msg = UserMessage.model_validate(multi_content_data)
151+
multi_msg = SamplingMessage.model_validate(multi_content_data)
149152
assert multi_msg.role == "user"
150153
assert isinstance(multi_msg.content, list)
151154
assert len(multi_msg.content) == 2
152155

153156

154157
@pytest.mark.anyio
155-
async def test_assistant_message():
156-
"""Test AssistantMessage type for SEP-1577."""
158+
async def test_sampling_message_with_assistant_role():
159+
"""Test SamplingMessage with assistant role for SEP-1577."""
157160
# Test with tool use content
158161
assistant_msg_data = {
159162
"role": "assistant",
@@ -164,7 +167,7 @@ async def test_assistant_message():
164167
"input": {"query": "MCP protocol"},
165168
},
166169
}
167-
assistant_msg = AssistantMessage.model_validate(assistant_msg_data)
170+
assistant_msg = SamplingMessage.model_validate(assistant_msg_data)
168171
assert assistant_msg.role == "assistant"
169172
assert isinstance(assistant_msg.content, ToolUseContent)
170173

@@ -176,7 +179,7 @@ async def test_assistant_message():
176179
{"type": "tool_use", "name": "search", "id": "call_789", "input": {}},
177180
],
178181
}
179-
multi_msg = AssistantMessage.model_validate(multi_content_data)
182+
multi_msg = SamplingMessage.model_validate(multi_content_data)
180183
assert isinstance(multi_msg.content, list)
181184
assert len(multi_msg.content) == 2
182185

0 commit comments

Comments
 (0)