Skip to content

Commit 3459880

Browse files
Fix pyright type errors for CreateMessageResult.content
The PR changed CreateMessageResult.content to be either a single content block or a list. Code accessing .type and .text directly now needs to handle both cases by checking isinstance(content, list) first. Also add explicit dict[str, Any] type annotations to test data containing nested empty dicts to satisfy pyright's type inference.
1 parent fd3e999 commit 3459880

File tree

4 files changed

+25
-13
lines changed

4 files changed

+25
-13
lines changed

examples/servers/everything-server/mcp_everything_server/server.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,9 @@ async def test_sampling(prompt: str, ctx: Context[ServerSession, None]) -> str:
134134
max_tokens=100,
135135
)
136136

137-
if result.content.type == "text":
138-
model_response = result.content.text
137+
content = result.content[0] if isinstance(result.content, list) else result.content
138+
if content.type == "text":
139+
model_response = content.text
139140
else:
140141
model_response = "No response"
141142

examples/snippets/servers/sampling.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ async def generate_poem(topic: str, ctx: Context[ServerSession, None]) -> str:
2020
max_tokens=100,
2121
)
2222

23-
if result.content.type == "text":
24-
return result.content.text
25-
return str(result.content)
23+
content = result.content[0] if isinstance(result.content, list) else result.content
24+
if content.type == "text":
25+
return content.text
26+
return str(content)

tests/shared/test_streamable_http.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,10 @@ async def handle_call_tool(name: str, args: dict[str, Any]) -> list[TextContent]
210210
)
211211

212212
# Return the sampling result in the tool response
213-
response = sampling_result.content.text if sampling_result.content.type == "text" else None
213+
content = (
214+
sampling_result.content[0] if isinstance(sampling_result.content, list) else sampling_result.content
215+
)
216+
response = content.text if content.type == "text" else None
214217
return [
215218
TextContent(
216219
type="text",
@@ -1239,7 +1242,12 @@ async def sampling_callback(
12391242
nonlocal sampling_callback_invoked, captured_message_params
12401243
sampling_callback_invoked = True
12411244
captured_message_params = params
1242-
message_received = params.messages[0].content.text if params.messages[0].content.type == "text" else None
1245+
msg_content = (
1246+
params.messages[0].content[0]
1247+
if isinstance(params.messages[0].content, list)
1248+
else params.messages[0].content
1249+
)
1250+
message_received = msg_content.text if msg_content.type == "text" else None
12431251

12441252
return types.CreateMessageResult(
12451253
role="assistant",

tests/test_types.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Any
2+
13
import pytest
24

35
from mcp.types import (
@@ -142,7 +144,7 @@ async def test_sampling_message_with_user_role():
142144
assert isinstance(user_msg.content, TextContent)
143145

144146
# Test with array of content including tool result
145-
multi_content_data = {
147+
multi_content_data: dict[str, Any] = {
146148
"role": "user",
147149
"content": [
148150
{"type": "text", "text": "Here's the result:"},
@@ -173,7 +175,7 @@ async def test_sampling_message_with_assistant_role():
173175
assert isinstance(assistant_msg.content, ToolUseContent)
174176

175177
# Test with array of mixed content
176-
multi_content_data = {
178+
multi_content_data: dict[str, Any] = {
177179
"role": "assistant",
178180
"content": [
179181
{"type": "text", "text": "Let me search for that..."},
@@ -195,7 +197,7 @@ async def test_sampling_message_backward_compatibility():
195197
assert isinstance(old_msg.content, TextContent)
196198

197199
# New-style message with tool content
198-
new_style_data = {
200+
new_style_data: dict[str, Any] = {
199201
"role": "assistant",
200202
"content": {"type": "tool_use", "name": "test", "id": "call_1", "input": {}},
201203
}
@@ -204,7 +206,7 @@ async def test_sampling_message_backward_compatibility():
204206
assert isinstance(new_msg.content, ToolUseContent)
205207

206208
# Array content
207-
array_style_data = {
209+
array_style_data: dict[str, Any] = {
208210
"role": "user",
209211
"content": [{"type": "text", "text": "Result:"}, {"type": "tool_result", "toolUseId": "call_1", "content": []}],
210212
}
@@ -256,7 +258,7 @@ async def test_create_message_result_with_tool_use():
256258
async def test_client_capabilities_with_sampling_tools():
257259
"""Test ClientCapabilities with nested sampling capabilities for SEP-1577."""
258260
# New structured format
259-
capabilities_data = {
261+
capabilities_data: dict[str, Any] = {
260262
"sampling": {"tools": {}},
261263
}
262264
capabilities = ClientCapabilities.model_validate(capabilities_data)
@@ -265,7 +267,7 @@ async def test_client_capabilities_with_sampling_tools():
265267
assert capabilities.sampling.tools is not None
266268

267269
# With both context and tools
268-
full_capabilities_data = {"sampling": {"context": {}, "tools": {}}}
270+
full_capabilities_data: dict[str, Any] = {"sampling": {"context": {}, "tools": {}}}
269271
full_caps = ClientCapabilities.model_validate(full_capabilities_data)
270272
assert isinstance(full_caps.sampling, SamplingCapability)
271273
assert full_caps.sampling.context is not None

0 commit comments

Comments
 (0)