refactor: use snake case instead of camel case in types (#1894)

This commit is contained in:
Marcelo Trylesinski
2026-01-16 15:51:27 +01:00
committed by GitHub
parent 7080fcf1e6
commit e94b386a13
127 changed files with 1063 additions and 1057 deletions
+20 -20
View File
@@ -442,7 +442,7 @@ def validated_tool() -> Annotated[CallToolResult, ValidationModel]:
"""Return CallToolResult with structured output validation."""
return CallToolResult(
content=[TextContent(type="text", text="Validated response")],
structuredContent={"status": "success", "data": {"result": 42}},
structured_content={"status": "success", "data": {"result": 42}},
_meta={"internal": "metadata"},
)
@@ -757,8 +757,8 @@ async def run():
# List available resource templates
templates = await session.list_resource_templates()
print("Available resource templates:")
for template in templates.resourceTemplates:
print(f" - {template.uriTemplate}")
for template in templates.resource_templates:
print(f" - {template.uri_template}")
# List available prompts
prompts = await session.list_prompts()
@@ -767,20 +767,20 @@ async def run():
print(f" - {prompt.name}")
# Complete resource template arguments
if templates.resourceTemplates:
template = templates.resourceTemplates[0]
print(f"\nCompleting arguments for resource template: {template.uriTemplate}")
if templates.resource_templates:
template = templates.resource_templates[0]
print(f"\nCompleting arguments for resource template: {template.uri_template}")
# Complete without context
result = await session.complete(
ref=ResourceTemplateReference(type="ref/resource", uri=template.uriTemplate),
ref=ResourceTemplateReference(type="ref/resource", uri=template.uri_template),
argument={"name": "owner", "value": "model"},
)
print(f"Completions for 'owner' starting with 'model': {result.completion.values}")
# Complete with context - repo suggestions based on owner
result = await session.complete(
ref=ResourceTemplateReference(type="ref/resource", uri=template.uriTemplate),
ref=ResourceTemplateReference(type="ref/resource", uri=template.uri_template),
argument={"name": "repo", "value": ""},
context_arguments={"owner": "modelcontextprotocol"},
)
@@ -910,7 +910,7 @@ async def connect_service(service_name: str, ctx: Context[ServerSession, None])
mode="url",
message=f"Authorization required to connect to {service_name}",
url=f"https://{service_name}.example.com/oauth/authorize?elicit={elicitation_id}",
elicitationId=elicitation_id,
elicitation_id=elicitation_id,
)
]
)
@@ -1706,7 +1706,7 @@ async def handle_list_tools() -> list[types.Tool]:
types.Tool(
name="query_db",
description="Query the database",
inputSchema={
input_schema={
"type": "object",
"properties": {"query": {"type": "string", "description": "SQL query to execute"}},
"required": ["query"],
@@ -1867,12 +1867,12 @@ async def list_tools() -> list[types.Tool]:
types.Tool(
name="get_weather",
description="Get current weather for a city",
inputSchema={
input_schema={
"type": "object",
"properties": {"city": {"type": "string", "description": "City name"}},
"required": ["city"],
},
outputSchema={
output_schema={
"type": "object",
"properties": {
"temperature": {"type": "number", "description": "Temperature in Celsius"},
@@ -1970,7 +1970,7 @@ async def list_tools() -> list[types.Tool]:
types.Tool(
name="advanced_tool",
description="Tool with full control including _meta field",
inputSchema={
input_schema={
"type": "object",
"properties": {"message": {"type": "string"}},
"required": ["message"],
@@ -1986,7 +1986,7 @@ async def handle_call_tool(name: str, arguments: dict[str, Any]) -> types.CallTo
message = str(arguments.get("message", ""))
return types.CallToolResult(
content=[types.TextContent(type="text", text=f"Processed: {message}")],
structuredContent={"result": "success", "message": message},
structured_content={"result": "success", "message": message},
_meta={"hidden": "data for client applications only"},
)
@@ -2062,7 +2062,7 @@ async def list_resources_paginated(request: types.ListResourcesRequest) -> types
# Determine next cursor
next_cursor = str(end) if end < len(ITEMS) else None
return types.ListResourcesResult(resources=page_items, nextCursor=next_cursor)
return types.ListResourcesResult(resources=page_items, next_cursor=next_cursor)
```
_Full example: [examples/snippets/servers/pagination_example.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/pagination_example.py)_
@@ -2103,8 +2103,8 @@ async def list_all_resources() -> None:
print(f"Fetched {len(result.resources)} resources")
# Check if there are more pages
if result.nextCursor:
cursor = result.nextCursor
if result.next_cursor:
cursor = result.next_cursor
else:
break
@@ -2167,7 +2167,7 @@ async def handle_sampling_message(
text="Hello, world! from model",
),
model="gpt-3.5-turbo",
stopReason="endTurn",
stop_reason="endTurn",
)
@@ -2205,7 +2205,7 @@ async def run():
result_unstructured = result.content[0]
if isinstance(result_unstructured, types.TextContent):
print(f"Tool result: {result_unstructured.text}")
result_structured = result.structuredContent
result_structured = result.structured_content
print(f"Structured tool result: {result_structured}")
@@ -2306,7 +2306,7 @@ async def display_resources(session: ClientSession):
print(f"Resource: {display_name} ({resource.uri})")
templates_response = await session.list_resource_templates()
for template in templates_response.resourceTemplates:
for template in templates_response.resource_templates:
display_name = get_display_name(template)
print(f"Resource Template: {display_name}")
@@ -112,7 +112,7 @@ class Server:
for item in tools_response:
if item[0] == "tools":
tools.extend(Tool(tool.name, tool.description, tool.inputSchema, tool.title) for tool in item[1])
tools.extend(Tool(tool.name, tool.description, tool.input_schema, tool.title) for tool in item[1])
return tools
@@ -25,13 +25,13 @@ async def run(url: str) -> None:
arguments={},
ttl=60000,
)
task_id = result.task.taskId
task_id = result.task.task_id
print(f"Task created: {task_id}")
status = None
# Poll until done (respects server's pollInterval hint)
async for status in session.experimental.poll_task(task_id):
print(f" Status: {status.status} - {status.statusMessage or ''}")
print(f" Status: {status.status} - {status.status_message or ''}")
# Check final status
if status and status.status != "completed":
@@ -49,7 +49,7 @@ async def sampling_callback(context, params) -> CreateMessageResult:
```python
# Call a tool as a task (returns immediately with task reference)
result = await session.experimental.call_tool_as_task("tool_name", {"arg": "value"})
task_id = result.task.taskId
task_id = result.task.task_id
# Get result - this delivers elicitation/sampling requests and blocks until complete
final = await session.experimental.get_task_result(task_id, CallToolResult)
@@ -91,7 +91,7 @@ async def run(url: str) -> None:
print("Calling confirm_delete tool...")
elicit_task = await session.experimental.call_tool_as_task("confirm_delete", {"filename": "important.txt"})
elicit_task_id = elicit_task.task.taskId
elicit_task_id = elicit_task.task.task_id
print(f"Task created: {elicit_task_id}")
# Poll until terminal, calling tasks/result on input_required
@@ -112,7 +112,7 @@ async def run(url: str) -> None:
print("Calling write_haiku tool...")
sampling_task = await session.experimental.call_tool_as_task("write_haiku", {"topic": "autumn leaves"})
sampling_task_id = sampling_task.task.taskId
sampling_task_id = sampling_task.task.task_id
print(f"Task created: {sampling_task_id}")
# Poll until terminal, calling tasks/result on input_required
@@ -20,5 +20,5 @@ class EchoResponse(BaseModel):
def echo(text: str) -> Annotated[CallToolResult, EchoResponse]:
"""Echo the input text with structure and metadata"""
return CallToolResult(
content=[TextContent(type="text", text=text)], structuredContent={"text": text}, _meta={"some": "metadata"}
content=[TextContent(type="text", text=text)], structured_content={"text": text}, _meta={"some": "metadata"}
)
+4 -4
View File
@@ -14,7 +14,7 @@ icon_path = Path(__file__).parent / "mcp.png"
icon_data = base64.standard_b64encode(icon_path.read_bytes()).decode()
icon_data_uri = f"data:image/png;base64,{icon_data}"
icon_data = Icon(src=icon_data_uri, mimeType="image/png", sizes=["64x64"])
icon_data = Icon(src=icon_data_uri, mime_type="image/png", sizes=["64x64"])
# Create server with icons in implementation
mcp = FastMCP("Icons Demo Server", website_url="https://github.com/modelcontextprotocol/python-sdk", icons=[icon_data])
@@ -40,9 +40,9 @@ def prompt_with_icon(text: str) -> str:
@mcp.tool(
icons=[
Icon(src=icon_data_uri, mimeType="image/png", sizes=["16x16"]),
Icon(src=icon_data_uri, mimeType="image/png", sizes=["32x32"]),
Icon(src=icon_data_uri, mimeType="image/png", sizes=["64x64"]),
Icon(src=icon_data_uri, mime_type="image/png", sizes=["16x16"]),
Icon(src=icon_data_uri, mime_type="image/png", sizes=["32x32"]),
Icon(src=icon_data_uri, mime_type="image/png", sizes=["64x64"]),
]
)
def multi_icon_tool(action: str) -> str:
+9 -9
View File
@@ -161,32 +161,32 @@ if __name__ == "__main__":
# Test get_weather
result = await client.call_tool("get_weather", {"city": "London"})
print("\nWeather in London:")
print(json.dumps(result.structuredContent, indent=2))
print(json.dumps(result.structured_content, indent=2))
# Test get_weather_summary
result = await client.call_tool("get_weather_summary", {"city": "Paris"})
print("\nWeather summary for Paris:")
print(json.dumps(result.structuredContent, indent=2))
print(json.dumps(result.structured_content, indent=2))
# Test get_weather_metrics
result = await client.call_tool("get_weather_metrics", {"cities": ["Tokyo", "Sydney", "Mumbai"]})
print("\nWeather metrics:")
print(json.dumps(result.structuredContent, indent=2))
print(json.dumps(result.structured_content, indent=2))
# Test get_weather_alerts
result = await client.call_tool("get_weather_alerts", {"region": "California"})
print("\nWeather alerts for California:")
print(json.dumps(result.structuredContent, indent=2))
print(json.dumps(result.structured_content, indent=2))
# Test get_temperature
result = await client.call_tool("get_temperature", {"city": "Berlin", "unit": "fahrenheit"})
print("\nTemperature in Berlin:")
print(json.dumps(result.structuredContent, indent=2))
print(json.dumps(result.structured_content, indent=2))
# Test get_weather_stats
result = await client.call_tool("get_weather_stats", {"city": "Seattle", "days": 30})
print("\nWeather stats for Seattle (30 days):")
print(json.dumps(result.structuredContent, indent=2))
print(json.dumps(result.structured_content, indent=2))
# Also show the text content for comparison
print("\nText content for last result:")
@@ -204,11 +204,11 @@ if __name__ == "__main__":
print(f"\nTool: {tool.name}")
print(f"Description: {tool.description}")
print("Input Schema:")
print(json.dumps(tool.inputSchema, indent=2))
print(json.dumps(tool.input_schema, indent=2))
if tool.outputSchema:
if tool.output_schema:
print("Output Schema:")
print(json.dumps(tool.outputSchema, indent=2))
print(json.dumps(tool.output_schema, indent=2))
else:
print("Output Schema: None (returns unstructured content)")
@@ -98,13 +98,13 @@ def test_simple_text() -> str:
@mcp.tool()
def test_image_content() -> list[ImageContent]:
"""Tests image content response"""
return [ImageContent(type="image", data=TEST_IMAGE_BASE64, mimeType="image/png")]
return [ImageContent(type="image", data=TEST_IMAGE_BASE64, mime_type="image/png")]
@mcp.tool()
def test_audio_content() -> list[AudioContent]:
"""Tests audio content response"""
return [AudioContent(type="audio", data=TEST_AUDIO_BASE64, mimeType="audio/wav")]
return [AudioContent(type="audio", data=TEST_AUDIO_BASE64, mime_type="audio/wav")]
@mcp.tool()
@@ -115,7 +115,7 @@ def test_embedded_resource() -> list[EmbeddedResource]:
type="resource",
resource=TextResourceContents(
uri="test://embedded-resource",
mimeType="text/plain",
mime_type="text/plain",
text="This is an embedded resource content.",
),
)
@@ -127,12 +127,12 @@ def test_multiple_content_types() -> list[TextContent | ImageContent | EmbeddedR
"""Tests response with multiple content types (text, image, resource)"""
return [
TextContent(type="text", text="Multiple content types test:"),
ImageContent(type="image", data=TEST_IMAGE_BASE64, mimeType="image/png"),
ImageContent(type="image", data=TEST_IMAGE_BASE64, mime_type="image/png"),
EmbeddedResource(
type="resource",
resource=TextResourceContents(
uri="test://mixed-content-resource",
mimeType="application/json",
mime_type="application/json",
text='{"test": "data", "value": 123}',
),
),
@@ -164,7 +164,7 @@ async def test_tool_with_progress(ctx: Context[ServerSession, None]) -> str:
await ctx.report_progress(progress=100, total=100, message="Completed step 100 of 100")
# Return progress token as string
progress_token = ctx.request_context.meta.progressToken if ctx.request_context and ctx.request_context.meta else 0
progress_token = ctx.request_context.meta.progress_token if ctx.request_context and ctx.request_context.meta else 0
return str(progress_token)
@@ -373,7 +373,7 @@ def test_prompt_with_embedded_resource(resourceUri: str) -> list[UserMessage]:
type="resource",
resource=TextResourceContents(
uri=resourceUri,
mimeType="text/plain",
mime_type="text/plain",
text="Embedded resource content for testing.",
),
),
@@ -386,7 +386,7 @@ def test_prompt_with_embedded_resource(resourceUri: str) -> list[UserMessage]:
def test_prompt_with_image() -> list[UserMessage]:
"""A prompt that includes image content"""
return [
UserMessage(role="user", content=ImageContent(type="image", data=TEST_IMAGE_BASE64, mimeType="image/png")),
UserMessage(role="user", content=ImageContent(type="image", data=TEST_IMAGE_BASE64, mime_type="image/png")),
UserMessage(role="user", content=TextContent(type="text", text="Please analyze the image above.")),
]
@@ -427,7 +427,7 @@ async def _handle_completion(
"""Handle completion requests"""
# Basic completion support - returns empty array for conformance
# Real implementations would provide contextual suggestions
return Completion(values=[], total=0, hasMore=False)
return Completion(values=[], total=0, has_more=False)
# CLI
@@ -19,7 +19,7 @@ SAMPLE_TOOLS = [
name=f"tool_{i}",
title=f"Tool {i}",
description=f"This is sample tool number {i}",
inputSchema={"type": "object", "properties": {"input": {"type": "string"}}},
input_schema={"type": "object", "properties": {"input": {"type": "string"}}},
)
for i in range(1, 26) # 25 tools total
]
@@ -71,7 +71,7 @@ def main(port: int, transport: str) -> int:
start_idx = int(cursor)
except (ValueError, TypeError):
# Invalid cursor, return empty
return types.ListToolsResult(tools=[], nextCursor=None)
return types.ListToolsResult(tools=[], next_cursor=None)
# Get the page of tools
page_tools = SAMPLE_TOOLS[start_idx : start_idx + page_size]
@@ -81,7 +81,7 @@ def main(port: int, transport: str) -> int:
if start_idx + page_size < len(SAMPLE_TOOLS):
next_cursor = str(start_idx + page_size)
return types.ListToolsResult(tools=page_tools, nextCursor=next_cursor)
return types.ListToolsResult(tools=page_tools, next_cursor=next_cursor)
# Paginated list_resources - returns 10 resources per page
@app.list_resources()
@@ -100,7 +100,7 @@ def main(port: int, transport: str) -> int:
start_idx = int(cursor)
except (ValueError, TypeError):
# Invalid cursor, return empty
return types.ListResourcesResult(resources=[], nextCursor=None)
return types.ListResourcesResult(resources=[], next_cursor=None)
# Get the page of resources
page_resources = SAMPLE_RESOURCES[start_idx : start_idx + page_size]
@@ -110,7 +110,7 @@ def main(port: int, transport: str) -> int:
if start_idx + page_size < len(SAMPLE_RESOURCES):
next_cursor = str(start_idx + page_size)
return types.ListResourcesResult(resources=page_resources, nextCursor=next_cursor)
return types.ListResourcesResult(resources=page_resources, next_cursor=next_cursor)
# Paginated list_prompts - returns 7 prompts per page
@app.list_prompts()
@@ -129,7 +129,7 @@ def main(port: int, transport: str) -> int:
start_idx = int(cursor)
except (ValueError, TypeError):
# Invalid cursor, return empty
return types.ListPromptsResult(prompts=[], nextCursor=None)
return types.ListPromptsResult(prompts=[], next_cursor=None)
# Get the page of prompts
page_prompts = SAMPLE_PROMPTS[start_idx : start_idx + page_size]
@@ -139,7 +139,7 @@ def main(port: int, transport: str) -> int:
if start_idx + page_size < len(SAMPLE_PROMPTS):
next_cursor = str(start_idx + page_size)
return types.ListPromptsResult(prompts=page_prompts, nextCursor=next_cursor)
return types.ListPromptsResult(prompts=page_prompts, next_cursor=next_cursor)
# Implement call_tool handler
@app.call_tool()
@@ -40,7 +40,7 @@ def main(port: int, transport: str) -> int:
name=name,
title=SAMPLE_RESOURCES[name]["title"],
description=f"A sample text resource named {name}",
mimeType="text/plain",
mime_type="text/plain",
)
for name in SAMPLE_RESOURCES.keys()
]
@@ -73,7 +73,7 @@ def main(
types.Tool(
name="start-notification-stream",
description=("Sends a stream of notifications with configurable count and interval"),
inputSchema={
input_schema={
"type": "object",
"required": ["interval", "count", "caller"],
"properties": {
@@ -87,7 +87,7 @@ def main(
types.Tool(
name="start-notification-stream",
description=("Sends a stream of notifications with configurable count and interval"),
inputSchema={
input_schema={
"type": "object",
"required": ["interval", "count", "caller"],
"properties": {
@@ -31,17 +31,17 @@ async def list_tools() -> list[types.Tool]:
types.Tool(
name="confirm_delete",
description="Asks for confirmation before deleting (demonstrates elicitation)",
inputSchema={
input_schema={
"type": "object",
"properties": {"filename": {"type": "string"}},
},
execution=types.ToolExecution(taskSupport=types.TASK_REQUIRED),
execution=types.ToolExecution(task_support=types.TASK_REQUIRED),
),
types.Tool(
name="write_haiku",
description="Asks LLM to write a haiku (demonstrates sampling)",
inputSchema={"type": "object", "properties": {"topic": {"type": "string"}}},
execution=types.ToolExecution(taskSupport=types.TASK_REQUIRED),
input_schema={"type": "object", "properties": {"topic": {"type": "string"}}},
execution=types.ToolExecution(task_support=types.TASK_REQUIRED),
),
]
@@ -59,7 +59,7 @@ async def handle_confirm_delete(arguments: dict[str, Any]) -> types.CreateTaskRe
result = await task.elicit(
message=f"Are you sure you want to delete '{filename}'?",
requestedSchema={
requested_schema={
"type": "object",
"properties": {"confirm": {"type": "boolean"}},
"required": ["confirm"],
@@ -121,7 +121,7 @@ async def handle_call_tool(name: str, arguments: dict[str, Any]) -> types.CallTo
else:
return types.CallToolResult(
content=[types.TextContent(type="text", text=f"Unknown tool: {name}")],
isError=True,
is_error=True,
)
@@ -26,8 +26,8 @@ async def list_tools() -> list[types.Tool]:
types.Tool(
name="long_running_task",
description="A task that takes a few seconds to complete with status updates",
inputSchema={"type": "object", "properties": {}},
execution=types.ToolExecution(taskSupport=types.TASK_REQUIRED),
input_schema={"type": "object", "properties": {}},
execution=types.ToolExecution(task_support=types.TASK_REQUIRED),
)
]
@@ -60,7 +60,7 @@ async def handle_call_tool(name: str, arguments: dict[str, Any]) -> types.CallTo
else:
return types.CallToolResult(
content=[types.TextContent(type="text", text=f"Unknown tool: {name}")],
isError=True,
is_error=True,
)
@@ -44,7 +44,7 @@ def main(port: int, transport: str) -> int:
name="fetch",
title="Website Fetcher",
description="Fetches a website and returns its content",
inputSchema={
input_schema={
"type": "object",
"required": ["url"],
"properties": {
@@ -120,7 +120,7 @@ def main(port: int, log_level: str, retry_interval: int) -> int:
"Process a batch of items with periodic checkpoints. "
"Demonstrates SSE polling where server closes stream periodically."
),
inputSchema={
input_schema={
"type": "object",
"properties": {
"items": {
@@ -27,12 +27,12 @@ async def list_tools() -> list[types.Tool]:
types.Tool(
name="get_weather",
description="Get weather information (simulated)",
inputSchema={
input_schema={
"type": "object",
"properties": {"city": {"type": "string", "description": "City name"}},
"required": ["city"],
},
outputSchema={
output_schema={
"type": "object",
"properties": {
"temperature": {"type": "number"},
@@ -28,8 +28,8 @@ async def run():
# List available resource templates
templates = await session.list_resource_templates()
print("Available resource templates:")
for template in templates.resourceTemplates:
print(f" - {template.uriTemplate}")
for template in templates.resource_templates:
print(f" - {template.uri_template}")
# List available prompts
prompts = await session.list_prompts()
@@ -38,20 +38,20 @@ async def run():
print(f" - {prompt.name}")
# Complete resource template arguments
if templates.resourceTemplates:
template = templates.resourceTemplates[0]
print(f"\nCompleting arguments for resource template: {template.uriTemplate}")
if templates.resource_templates:
template = templates.resource_templates[0]
print(f"\nCompleting arguments for resource template: {template.uri_template}")
# Complete without context
result = await session.complete(
ref=ResourceTemplateReference(type="ref/resource", uri=template.uriTemplate),
ref=ResourceTemplateReference(type="ref/resource", uri=template.uri_template),
argument={"name": "owner", "value": "model"},
)
print(f"Completions for 'owner' starting with 'model': {result.completion.values}")
# Complete with context - repo suggestions based on owner
result = await session.complete(
ref=ResourceTemplateReference(type="ref/resource", uri=template.uriTemplate),
ref=ResourceTemplateReference(type="ref/resource", uri=template.uri_template),
argument={"name": "repo", "value": ""},
context_arguments={"owner": "modelcontextprotocol"},
)
@@ -39,7 +39,7 @@ async def display_resources(session: ClientSession):
print(f"Resource: {display_name} ({resource.uri})")
templates_response = await session.list_resource_templates()
for template in templates_response.resourceTemplates:
for template in templates_response.resource_templates:
display_name = get_display_name(template)
print(f"Resource Template: {display_name}")
@@ -29,8 +29,8 @@ async def list_all_resources() -> None:
print(f"Fetched {len(result.resources)} resources")
# Check if there are more pages
if result.nextCursor:
cursor = result.nextCursor
if result.next_cursor:
cursor = result.next_cursor
else:
break
@@ -22,9 +22,9 @@ async def parse_tool_results():
# Example 2: Parsing structured content from JSON tools
result = await session.call_tool("get_user", {"id": "123"})
if hasattr(result, "structuredContent") and result.structuredContent:
if hasattr(result, "structured_content") and result.structured_content:
# Access structured data directly
user_data = result.structuredContent
user_data = result.structured_content
print(f"User: {user_data.get('name')}, Age: {user_data.get('age')}")
# Example 3: Parsing embedded resources
@@ -41,11 +41,11 @@ async def parse_tool_results():
result = await session.call_tool("generate_chart", {"data": [1, 2, 3]})
for content in result.content:
if isinstance(content, types.ImageContent):
print(f"Image ({content.mimeType}): {len(content.data)} bytes")
print(f"Image ({content.mime_type}): {len(content.data)} bytes")
# Example 5: Handling errors
result = await session.call_tool("failing_tool", {})
if result.isError:
if result.is_error:
print("Tool execution failed!")
for content in result.content:
if isinstance(content, types.TextContent):
+2 -2
View File
@@ -32,7 +32,7 @@ async def handle_sampling_message(
text="Hello, world! from model",
),
model="gpt-3.5-turbo",
stopReason="endTurn",
stop_reason="endTurn",
)
@@ -70,7 +70,7 @@ async def run():
result_unstructured = result.content[0]
if isinstance(result_unstructured, types.TextContent):
print(f"Tool result: {result_unstructured.text}")
result_structured = result.structuredContent
result_structured = result.structured_content
print(f"Structured tool result: {result_structured}")
@@ -154,7 +154,7 @@ async def call_tool_with_error_handling(
result = await session.call_tool(tool_name, arguments)
# Check if the tool returned an error in the result
if result.isError:
if result.is_error:
print(f"Tool returned error: {result.content}")
return None
+2 -2
View File
@@ -36,7 +36,7 @@ async def handle_completion(
languages = ["python", "javascript", "typescript", "go", "rust"]
return Completion(
values=[lang for lang in languages if lang.startswith(argument.value)],
hasMore=False,
has_more=False,
)
# Complete repository names for GitHub resources
@@ -44,6 +44,6 @@ async def handle_completion(
if ref.uri == "github://repos/{owner}/{repo}" and argument.name == "repo":
if context and context.arguments and context.arguments.get("owner") == "modelcontextprotocol":
repos = ["python-sdk", "typescript-sdk", "specification"]
return Completion(values=repos, hasMore=False)
return Completion(values=repos, has_more=False)
return None
@@ -31,7 +31,7 @@ def validated_tool() -> Annotated[CallToolResult, ValidationModel]:
"""Return CallToolResult with structured output validation."""
return CallToolResult(
content=[TextContent(type="text", text="Validated response")],
structuredContent={"status": "success", "data": {"result": 42}},
structured_content={"status": "success", "data": {"result": 42}},
_meta={"internal": "metadata"},
)
+1 -1
View File
@@ -93,7 +93,7 @@ async def connect_service(service_name: str, ctx: Context[ServerSession, None])
mode="url",
message=f"Authorization required to connect to {service_name}",
url=f"https://{service_name}.example.com/oauth/authorize?elicit={elicitation_id}",
elicitationId=elicitation_id,
elicitation_id=elicitation_id,
)
]
)
@@ -21,7 +21,7 @@ async def list_tools() -> list[types.Tool]:
types.Tool(
name="advanced_tool",
description="Tool with full control including _meta field",
inputSchema={
input_schema={
"type": "object",
"properties": {"message": {"type": "string"}},
"required": ["message"],
@@ -37,7 +37,7 @@ async def handle_call_tool(name: str, arguments: dict[str, Any]) -> types.CallTo
message = str(arguments.get("message", ""))
return types.CallToolResult(
content=[types.TextContent(type="text", text=f"Processed: {message}")],
structuredContent={"result": "success", "message": message},
structured_content={"result": "success", "message": message},
_meta={"hidden": "data for client applications only"},
)
@@ -56,7 +56,7 @@ async def handle_list_tools() -> list[types.Tool]:
types.Tool(
name="query_db",
description="Query the database",
inputSchema={
input_schema={
"type": "object",
"properties": {"query": {"type": "string", "description": "SQL query to execute"}},
"required": ["query"],
@@ -21,12 +21,12 @@ async def list_tools() -> list[types.Tool]:
types.Tool(
name="get_weather",
description="Get current weather for a city",
inputSchema={
input_schema={
"type": "object",
"properties": {"city": {"type": "string", "description": "City name"}},
"required": ["city"],
},
outputSchema={
output_schema={
"type": "object",
"properties": {
"temperature": {"type": "number", "description": "Temperature in Celsius"},
@@ -33,4 +33,4 @@ async def list_resources_paginated(request: types.ListResourcesRequest) -> types
# Determine next cursor
next_cursor = str(end) if end < len(ITEMS) else None
return types.ListResourcesResult(resources=page_items, nextCursor=next_cursor)
return types.ListResourcesResult(resources=page_items, next_cursor=next_cursor)
+1 -1
View File
@@ -225,7 +225,7 @@ class ExperimentalTaskHandlers:
requests_capability: types.ClientTasksRequestsCapability | None = None
if has_sampling or has_elicitation:
requests_capability = types.ClientTasksRequestsCapability(
sampling=types.TasksSamplingCapability(createMessage=types.TasksCreateMessageCapability())
sampling=types.TasksSamplingCapability(create_message=types.TasksCreateMessageCapability())
if has_sampling
else None,
elicitation=types.TasksElicitationCapability(create=types.TasksCreateElicitationCapability())
+5 -5
View File
@@ -8,7 +8,7 @@ WARNING: These APIs are experimental and may change without notice.
Example:
# Call a tool as a task
result = await session.experimental.call_tool_as_task("tool_name", {"arg": "value"})
task_id = result.task.taskId
task_id = result.task.task_id
# Get task status
status = await session.experimental.get_task(task_id)
@@ -77,7 +77,7 @@ class ExperimentalClientFeatures:
result = await session.experimental.call_tool_as_task(
"long_running_tool", {"input": "data"}
)
task_id = result.task.taskId
task_id = result.task.task_id
# Poll for completion
while True:
@@ -120,7 +120,7 @@ class ExperimentalClientFeatures:
return await self._session.send_request(
types.ClientRequest(
types.GetTaskRequest(
params=types.GetTaskRequestParams(taskId=task_id),
params=types.GetTaskRequestParams(task_id=task_id),
)
),
types.GetTaskResult,
@@ -148,7 +148,7 @@ class ExperimentalClientFeatures:
return await self._session.send_request(
types.ClientRequest(
types.GetTaskPayloadRequest(
params=types.GetTaskPayloadRequestParams(taskId=task_id),
params=types.GetTaskPayloadRequestParams(task_id=task_id),
)
),
result_type,
@@ -188,7 +188,7 @@ class ExperimentalClientFeatures:
return await self._session.send_request(
types.ClientRequest(
types.CancelTaskRequest(
params=types.CancelTaskRequestParams(taskId=task_id),
params=types.CancelTaskRequestParams(task_id=task_id),
)
),
types.CancelTaskResult,
+10 -10
View File
@@ -161,7 +161,7 @@ class ClientSession(
# TODO: Should this be based on whether we
# _will_ send notifications, or only whether
# they're supported?
types.RootsCapability(listChanged=True)
types.RootsCapability(list_changed=True)
if self._list_roots_callback is not _default_list_roots_callback
else None
)
@@ -170,7 +170,7 @@ class ClientSession(
types.ClientRequest(
types.InitializeRequest(
params=types.InitializeRequestParams(
protocolVersion=types.LATEST_PROTOCOL_VERSION,
protocol_version=types.LATEST_PROTOCOL_VERSION,
capabilities=types.ClientCapabilities(
sampling=sampling,
elicitation=elicitation,
@@ -178,15 +178,15 @@ class ClientSession(
roots=roots,
tasks=self._task_handlers.build_capability(),
),
clientInfo=self._client_info,
client_info=self._client_info,
),
)
),
types.InitializeResult,
)
if result.protocolVersion not in SUPPORTED_PROTOCOL_VERSIONS:
raise RuntimeError(f"Unsupported protocol version from the server: {result.protocolVersion}")
if result.protocol_version not in SUPPORTED_PROTOCOL_VERSIONS:
raise RuntimeError(f"Unsupported protocol version from the server: {result.protocol_version}")
self._server_capabilities = result.capabilities
@@ -235,7 +235,7 @@ class ClientSession(
types.ClientNotification(
types.ProgressNotification(
params=types.ProgressNotificationParams(
progressToken=progress_token,
progress_token=progress_token,
progress=progress,
total=total,
message=message,
@@ -326,7 +326,7 @@ class ClientSession(
progress_callback=progress_callback,
)
if not result.isError:
if not result.is_error:
await self._validate_tool_result(name, result)
return result
@@ -346,12 +346,12 @@ class ClientSession(
if output_schema is not None:
from jsonschema import SchemaError, ValidationError, validate
if result.structuredContent is None:
if result.structured_content is None:
raise RuntimeError(
f"Tool {name} has an output schema but did not return structured content"
) # pragma: no cover
try:
validate(result.structuredContent, output_schema)
validate(result.structured_content, output_schema)
except ValidationError as e:
raise RuntimeError(f"Invalid structured content returned by tool {name}: {e}") # pragma: no cover
except SchemaError as e: # pragma: no cover
@@ -418,7 +418,7 @@ class ClientSession(
# Cache tool output schemas for future validation
# Note: don't clear the cache, as we may be using a cursor
for tool in result.tools:
self._tool_output_schemas[tool.name] = tool.outputSchema
self._tool_output_schemas[tool.name] = tool.output_schema
return result
+3 -3
View File
@@ -119,9 +119,9 @@ class ClientSessionGroup:
_exit_stack: contextlib.AsyncExitStack
_session_exit_stacks: dict[mcp.ClientSession, contextlib.AsyncExitStack]
# Optional fn consuming (component_name, serverInfo) for custom names.
# Optional fn consuming (component_name, server_info) for custom names.
# This is provide a means to mitigate naming conflicts across servers.
# Example: (tool_name, serverInfo) => "{result.serverInfo.name}.{tool_name}"
# Example: (tool_name, server_info) => "{result.server_info.name}.{tool_name}"
_ComponentNameHook: TypeAlias = Callable[[str, types.Implementation], str]
_component_name_hook: _ComponentNameHook | None
@@ -324,7 +324,7 @@ class ClientSessionGroup:
# main _exit_stack.
await self._exit_stack.enter_async_context(session_stack)
return result.serverInfo, session
return result.server_info, session
except Exception: # pragma: no cover
# If anything during this setup fails, ensure the session-specific
# stack is closed.
+1 -1
View File
@@ -110,7 +110,7 @@ async def sse_client(
continue
try:
message = types.JSONRPCMessage.model_validate_json( # noqa: E501
sse.data
sse.data, by_name=False
)
logger.debug(f"Received server message: {message}")
except Exception as exc: # pragma: no cover
+1 -1
View File
@@ -152,7 +152,7 @@ async def stdio_client(server: StdioServerParameters, errlog: TextIO = sys.stder
for line in lines:
try:
message = types.JSONRPCMessage.model_validate_json(line)
message = types.JSONRPCMessage.model_validate_json(line, by_name=False)
except Exception as exc: # pragma: no cover
logger.exception("Failed to parse JSONRPC message from server")
await read_stream_writer.send(exc)
+4 -4
View File
@@ -113,8 +113,8 @@ class StreamableHTTPTransport:
if isinstance(message.root, JSONRPCResponse) and message.root.result: # pragma: no branch
try:
# Parse the result as InitializeResult for type safety
init_result = InitializeResult.model_validate(message.root.result)
self.protocol_version = str(init_result.protocolVersion)
init_result = InitializeResult.model_validate(message.root.result, by_name=False)
self.protocol_version = str(init_result.protocol_version)
logger.info(f"Negotiated protocol version: {self.protocol_version}")
except Exception: # pragma: no cover
logger.warning("Failed to parse initialization response as InitializeResult", exc_info=True)
@@ -137,7 +137,7 @@ class StreamableHTTPTransport:
await resumption_callback(sse.id)
return False
try:
message = JSONRPCMessage.model_validate_json(sse.data)
message = JSONRPCMessage.model_validate_json(sse.data, by_name=False)
logger.debug(f"SSE message: {message}")
# Extract protocol version from initialization response
@@ -291,7 +291,7 @@ class StreamableHTTPTransport:
"""Handle JSON response from the server."""
try:
content = await response.aread()
message = JSONRPCMessage.model_validate_json(content)
message = JSONRPCMessage.model_validate_json(content, by_name=False)
# Extract protocol version from initialization response
if is_initialization:
+1 -1
View File
@@ -53,7 +53,7 @@ async def websocket_client(
async with read_stream_writer:
async for raw_text in ws:
try:
message = types.JSONRPCMessage.model_validate_json(raw_text)
message = types.JSONRPCMessage.model_validate_json(raw_text, by_name=False)
session_message = SessionMessage(message)
await read_stream_writer.send(session_message)
except ValidationError as exc: # pragma: no cover
+1 -1
View File
@@ -125,7 +125,7 @@ async def elicit_with_validation(
result = await session.elicit_form(
message=message,
requestedSchema=json_schema,
requested_schema=json_schema,
related_request_id=related_request_id,
)
@@ -122,7 +122,7 @@ class Experimental:
Returns:
None if valid, ErrorData if invalid and raise_error=False
"""
mode = tool.execution.taskSupport if tool.execution else None
mode = tool.execution.task_support if tool.execution else None
return self.validate_task_mode(mode, raise_error=raise_error)
def can_use_tool(self, tool_task_mode: TaskExecutionMode | None) -> bool:
+13 -13
View File
@@ -52,7 +52,7 @@ class ExperimentalServerSessionFeatures:
GetTaskResult containing the task status
"""
return await self._session.send_request(
types.ServerRequest(types.GetTaskRequest(params=types.GetTaskRequestParams(taskId=task_id))),
types.ServerRequest(types.GetTaskRequest(params=types.GetTaskRequestParams(task_id=task_id))),
types.GetTaskResult,
)
@@ -72,7 +72,7 @@ class ExperimentalServerSessionFeatures:
The task result, validated against result_type
"""
return await self._session.send_request(
types.ServerRequest(types.GetTaskPayloadRequest(params=types.GetTaskPayloadRequestParams(taskId=task_id))),
types.ServerRequest(types.GetTaskPayloadRequest(params=types.GetTaskPayloadRequestParams(task_id=task_id))),
result_type,
)
@@ -97,7 +97,7 @@ class ExperimentalServerSessionFeatures:
async def elicit_as_task(
self,
message: str,
requestedSchema: types.ElicitRequestedSchema,
requested_schema: types.ElicitRequestedSchema,
*,
ttl: int = 60000,
) -> types.ElicitResult:
@@ -113,7 +113,7 @@ class ExperimentalServerSessionFeatures:
Args:
message: The message to present to the user
requestedSchema: Schema defining the expected response
requested_schema: Schema defining the expected response
ttl: Task time-to-live in milliseconds
Returns:
@@ -130,7 +130,7 @@ class ExperimentalServerSessionFeatures:
types.ElicitRequest(
params=types.ElicitRequestFormParams(
message=message,
requestedSchema=requestedSchema,
requested_schema=requested_schema,
task=types.TaskMetadata(ttl=ttl),
)
)
@@ -138,7 +138,7 @@ class ExperimentalServerSessionFeatures:
types.CreateTaskResult,
)
task_id = create_result.task.taskId
task_id = create_result.task.task_id
async for _ in self.poll_task(task_id):
pass
@@ -196,15 +196,15 @@ class ExperimentalServerSessionFeatures:
types.CreateMessageRequest(
params=types.CreateMessageRequestParams(
messages=messages,
maxTokens=max_tokens,
systemPrompt=system_prompt,
includeContext=include_context,
max_tokens=max_tokens,
system_prompt=system_prompt,
include_context=include_context,
temperature=temperature,
stopSequences=stop_sequences,
stop_sequences=stop_sequences,
metadata=metadata,
modelPreferences=model_preferences,
model_preferences=model_preferences,
tools=tools,
toolChoice=tool_choice,
tool_choice=tool_choice,
task=types.TaskMetadata(ttl=ttl),
)
)
@@ -212,7 +212,7 @@ class ExperimentalServerSessionFeatures:
types.CreateTaskResult,
)
task_id = create_result.task.taskId
task_id = create_result.task.task_id
async for _ in self.poll_task(task_id):
pass
+14 -14
View File
@@ -65,7 +65,7 @@ class ServerTaskContext:
result = await task.elicit(
message="Continue?",
requestedSchema={"type": "object", "properties": {"ok": {"type": "boolean"}}}
requested_schema={"type": "object", "properties": {"ok": {"type": "boolean"}}}
)
if result.content.get("ok"):
@@ -165,13 +165,13 @@ class ServerTaskContext:
ServerNotification(
TaskStatusNotification(
params=TaskStatusNotificationParams(
taskId=task.taskId,
task_id=task.task_id,
status=task.status,
statusMessage=task.statusMessage,
createdAt=task.createdAt,
lastUpdatedAt=task.lastUpdatedAt,
status_message=task.status_message,
created_at=task.created_at,
last_updated_at=task.last_updated_at,
ttl=task.ttl,
pollInterval=task.pollInterval,
poll_interval=task.poll_interval,
)
)
)
@@ -202,7 +202,7 @@ class ServerTaskContext:
async def elicit(
self,
message: str,
requestedSchema: ElicitRequestedSchema,
requested_schema: ElicitRequestedSchema,
) -> ElicitResult:
"""
Send an elicitation request via the task message queue.
@@ -217,7 +217,7 @@ class ServerTaskContext:
Args:
message: The message to present to the user
requestedSchema: Schema defining the expected response structure
requested_schema: Schema defining the expected response structure
Returns:
The client's response
@@ -236,7 +236,7 @@ class ServerTaskContext:
# Build the request using session's helper
request = self._session._build_elicit_form_request( # pyright: ignore[reportPrivateUsage]
message=message,
requestedSchema=requestedSchema,
requested_schema=requested_schema,
related_task_id=self.task_id,
)
request_id: RequestId = request.id
@@ -430,7 +430,7 @@ class ServerTaskContext:
async def elicit_as_task(
self,
message: str,
requestedSchema: ElicitRequestedSchema,
requested_schema: ElicitRequestedSchema,
*,
ttl: int = 60000,
) -> ElicitResult:
@@ -444,7 +444,7 @@ class ServerTaskContext:
Args:
message: The message to present to the user
requestedSchema: Schema defining the expected response structure
requested_schema: Schema defining the expected response structure
ttl: Task time-to-live in milliseconds for the client's task
Returns:
@@ -465,7 +465,7 @@ class ServerTaskContext:
request = self._session._build_elicit_form_request( # pyright: ignore[reportPrivateUsage]
message=message,
requestedSchema=requestedSchema,
requested_schema=requested_schema,
related_task_id=self.task_id,
task=TaskMetadata(ttl=ttl),
)
@@ -486,7 +486,7 @@ class ServerTaskContext:
# Wait for initial response (CreateTaskResult from client)
response_data = await resolver.wait()
create_result = CreateTaskResult.model_validate(response_data)
client_task_id = create_result.task.taskId
client_task_id = create_result.task.task_id
# Poll the client's task using session.experimental
async for _ in self._session.experimental.poll_task(client_task_id):
@@ -592,7 +592,7 @@ class ServerTaskContext:
# Wait for initial response (CreateTaskResult from client)
response_data = await resolver.wait()
create_result = CreateTaskResult.model_validate(response_data)
client_task_id = create_result.task.taskId
client_task_id = create_result.task.task_id
# Poll the client's task using session.experimental
async for _ in self._session.experimental.poll_task(client_task_id):
@@ -106,7 +106,7 @@ class TaskResultHandler:
Returns:
GetTaskPayloadResult with the task's final payload
"""
task_id = request.params.taskId
task_id = request.params.task_id
while True:
task = await self._store.get_task(task_id)
@@ -126,7 +126,7 @@ class TaskResultHandler:
# GetTaskPayloadResult is a Result with extra="allow"
# The stored result contains the actual payload data
# Per spec: tasks/result MUST include _meta with related-task metadata
related_task = RelatedTaskMetadata(taskId=task_id)
related_task = RelatedTaskMetadata(task_id=task_id)
related_task_meta: dict[str, Any] = {RELATED_TASK_METADATA_KEY: related_task.model_dump(by_alias=True)}
if result is not None:
result_data = result.model_dump(by_alias=True)
+6 -6
View File
@@ -305,8 +305,8 @@ class FastMCP(Generic[LifespanResultT]):
name=info.name,
title=info.title,
description=info.description,
inputSchema=info.parameters,
outputSchema=info.output_schema,
input_schema=info.parameters,
output_schema=info.output_schema,
annotations=info.annotations,
icons=info.icons,
_meta=info.meta,
@@ -340,7 +340,7 @@ class FastMCP(Generic[LifespanResultT]):
name=resource.name or "",
title=resource.title,
description=resource.description,
mimeType=resource.mime_type,
mime_type=resource.mime_type,
icons=resource.icons,
annotations=resource.annotations,
_meta=resource.meta,
@@ -352,11 +352,11 @@ class FastMCP(Generic[LifespanResultT]):
templates = self._resource_manager.list_templates()
return [
MCPResourceTemplate(
uriTemplate=template.uri_template,
uri_template=template.uri_template,
name=template.name,
title=template.title,
description=template.description,
mimeType=template.mime_type,
mime_type=template.mime_type,
icons=template.icons,
annotations=template.annotations,
_meta=template.meta,
@@ -1104,7 +1104,7 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT, RequestT]):
total: Optional total value e.g. 100
message: Optional message e.g. Starting render...
"""
progress_token = self.request_context.meta.progressToken if self.request_context.meta else None
progress_token = self.request_context.meta.progress_token if self.request_context.meta else None
if progress_token is None: # pragma: no cover
return
@@ -113,7 +113,7 @@ class FuncMetadata(BaseModel):
if isinstance(result, CallToolResult):
if self.output_schema is not None:
assert self.output_model is not None, "Output model must be set if output schema is defined"
self.output_model.model_validate(result.structuredContent)
self.output_model.model_validate(result.structured_content)
return result
unstructured_content = _convert_to_content(result)
+2 -2
View File
@@ -51,7 +51,7 @@ class Image:
else: # pragma: no cover
raise ValueError("No image data available")
return ImageContent(type="image", data=data, mimeType=self._mime_type)
return ImageContent(type="image", data=data, mime_type=self._mime_type)
class Audio:
@@ -98,4 +98,4 @@ class Audio:
else: # pragma: no cover
raise ValueError("No audio data available")
return AudioContent(type="audio", data=data, mimeType=self._mime_type)
return AudioContent(type="audio", data=data, mime_type=self._mime_type)
+9 -9
View File
@@ -134,23 +134,23 @@ class ExperimentalHandlers:
if GetTaskRequest not in self._request_handlers:
async def _default_get_task(req: GetTaskRequest) -> ServerResult:
task = await support.store.get_task(req.params.taskId)
task = await support.store.get_task(req.params.task_id)
if task is None:
raise McpError(
ErrorData(
code=INVALID_PARAMS,
message=f"Task not found: {req.params.taskId}",
message=f"Task not found: {req.params.task_id}",
)
)
return ServerResult(
GetTaskResult(
taskId=task.taskId,
task_id=task.task_id,
status=task.status,
statusMessage=task.statusMessage,
createdAt=task.createdAt,
lastUpdatedAt=task.lastUpdatedAt,
status_message=task.status_message,
created_at=task.created_at,
last_updated_at=task.last_updated_at,
ttl=task.ttl,
pollInterval=task.pollInterval,
poll_interval=task.poll_interval,
)
)
@@ -172,7 +172,7 @@ class ExperimentalHandlers:
async def _default_list_tasks(req: ListTasksRequest) -> ServerResult:
cursor = req.params.cursor if req.params else None
tasks, next_cursor = await support.store.list_tasks(cursor)
return ServerResult(ListTasksResult(tasks=tasks, nextCursor=next_cursor))
return ServerResult(ListTasksResult(tasks=tasks, next_cursor=next_cursor))
self._request_handlers[ListTasksRequest] = _default_list_tasks
@@ -180,7 +180,7 @@ class ExperimentalHandlers:
if CancelTaskRequest not in self._request_handlers:
async def _default_cancel_task(req: CancelTaskRequest) -> ServerResult:
result = await cancel_task(support.store, req.params.taskId)
result = await cancel_task(support.store, req.params.task_id)
return ServerResult(result)
self._request_handlers[CancelTaskRequest] = _default_cancel_task
+14 -14
View File
@@ -209,17 +209,17 @@ class Server(Generic[LifespanResultT, RequestT]):
# Set prompt capabilities if handler exists
if types.ListPromptsRequest in self.request_handlers:
prompts_capability = types.PromptsCapability(listChanged=notification_options.prompts_changed)
prompts_capability = types.PromptsCapability(list_changed=notification_options.prompts_changed)
# Set resource capabilities if handler exists
if types.ListResourcesRequest in self.request_handlers:
resources_capability = types.ResourcesCapability(
subscribe=False, listChanged=notification_options.resources_changed
subscribe=False, list_changed=notification_options.resources_changed
)
# Set tool capabilities if handler exists
if types.ListToolsRequest in self.request_handlers:
tools_capability = types.ToolsCapability(listChanged=notification_options.tools_changed)
tools_capability = types.ToolsCapability(list_changed=notification_options.tools_changed)
# Set logging capabilities if handler exists
if types.SetLevelRequest in self.request_handlers: # pragma: no cover
@@ -327,7 +327,7 @@ class Server(Generic[LifespanResultT, RequestT]):
async def handler(_: Any):
templates = await func()
return types.ServerResult(types.ListResourceTemplatesResult(resourceTemplates=templates))
return types.ServerResult(types.ListResourceTemplatesResult(resource_templates=templates))
self.request_handlers[types.ListResourceTemplatesRequest] = handler
return func
@@ -351,14 +351,14 @@ class Server(Generic[LifespanResultT, RequestT]):
return types.TextResourceContents(
uri=req.params.uri,
text=data,
mimeType=mime_type or "text/plain",
mime_type=mime_type or "text/plain",
**meta_kwargs,
)
case bytes() as data: # pragma: no cover
return types.BlobResourceContents(
uri=req.params.uri,
blob=base64.b64encode(data).decode(),
mimeType=mime_type or "application/octet-stream",
mime_type=mime_type or "application/octet-stream",
**meta_kwargs,
)
@@ -474,7 +474,7 @@ class Server(Generic[LifespanResultT, RequestT]):
return types.ServerResult(
types.CallToolResult(
content=[types.TextContent(type="text", text=error_message)],
isError=True,
is_error=True,
)
)
@@ -532,7 +532,7 @@ class Server(Generic[LifespanResultT, RequestT]):
# input validation
if validate_input and tool:
try:
jsonschema.validate(instance=arguments, schema=tool.inputSchema)
jsonschema.validate(instance=arguments, schema=tool.input_schema)
except jsonschema.ValidationError as e:
return self._make_error_result(f"Input validation error: {e.message}")
@@ -562,14 +562,14 @@ class Server(Generic[LifespanResultT, RequestT]):
return self._make_error_result(f"Unexpected return type from tool: {type(results).__name__}")
# output validation
if tool and tool.outputSchema is not None:
if tool and tool.output_schema is not None:
if maybe_structured_content is None:
return self._make_error_result(
"Output validation error: outputSchema defined but no structured output returned"
)
else:
try:
jsonschema.validate(instance=maybe_structured_content, schema=tool.outputSchema)
jsonschema.validate(instance=maybe_structured_content, schema=tool.output_schema)
except jsonschema.ValidationError as e:
return self._make_error_result(f"Output validation error: {e.message}")
@@ -577,8 +577,8 @@ class Server(Generic[LifespanResultT, RequestT]):
return types.ServerResult(
types.CallToolResult(
content=list(unstructured_content),
structuredContent=maybe_structured_content,
isError=False,
structured_content=maybe_structured_content,
is_error=False,
)
)
except UrlElicitationRequiredError:
@@ -601,7 +601,7 @@ class Server(Generic[LifespanResultT, RequestT]):
async def handler(req: types.ProgressNotification):
await func(
req.params.progressToken,
req.params.progress_token,
req.params.progress,
req.params.total,
req.params.message,
@@ -633,7 +633,7 @@ class Server(Generic[LifespanResultT, RequestT]):
types.CompleteResult(
completion=completion
if completion is not None
else types.Completion(values=[], total=None, hasMore=None),
else types.Completion(values=[], total=None, has_more=None),
)
)
+33 -33
View File
@@ -129,7 +129,7 @@ class ServerSession(
if capability.roots is not None:
if client_caps.roots is None:
return False
if capability.roots.listChanged and not client_caps.roots.listChanged:
if capability.roots.list_changed and not client_caps.roots.list_changed:
return False
if capability.sampling is not None:
@@ -165,23 +165,23 @@ class ServerSession(
async def _received_request(self, responder: RequestResponder[types.ClientRequest, types.ServerResult]):
match responder.request.root:
case types.InitializeRequest(params=params):
requested_version = params.protocolVersion
requested_version = params.protocol_version
self._initialization_state = InitializationState.Initializing
self._client_params = params
with responder:
await responder.respond(
types.ServerResult(
types.InitializeResult(
protocolVersion=requested_version
protocol_version=requested_version
if requested_version in SUPPORTED_PROTOCOL_VERSIONS
else types.LATEST_PROTOCOL_VERSION,
capabilities=self._init_options.capabilities,
serverInfo=types.Implementation(
server_info=types.Implementation(
name=self._init_options.server_name,
title=self._init_options.title,
description=self._init_options.description,
version=self._init_options.server_version,
websiteUrl=self._init_options.website_url,
website_url=self._init_options.website_url,
icons=self._init_options.icons,
),
instructions=self._init_options.instructions,
@@ -327,15 +327,15 @@ class ServerSession(
types.CreateMessageRequest(
params=types.CreateMessageRequestParams(
messages=messages,
systemPrompt=system_prompt,
includeContext=include_context,
system_prompt=system_prompt,
include_context=include_context,
temperature=temperature,
maxTokens=max_tokens,
stopSequences=stop_sequences,
max_tokens=max_tokens,
stop_sequences=stop_sequences,
metadata=metadata,
modelPreferences=model_preferences,
model_preferences=model_preferences,
tools=tools,
toolChoice=tool_choice,
tool_choice=tool_choice,
),
)
)
@@ -366,14 +366,14 @@ class ServerSession(
async def elicit(
self,
message: str,
requestedSchema: types.ElicitRequestedSchema,
requested_schema: types.ElicitRequestedSchema,
related_request_id: types.RequestId | None = None,
) -> types.ElicitResult:
"""Send a form mode elicitation/create request.
Args:
message: The message to present to the user
requestedSchema: Schema defining the expected response structure
requested_schema: Schema defining the expected response structure
related_request_id: Optional ID of the request that triggered this elicitation
Returns:
@@ -383,19 +383,19 @@ class ServerSession(
This method is deprecated in favor of elicit_form(). It remains for
backward compatibility but new code should use elicit_form().
"""
return await self.elicit_form(message, requestedSchema, related_request_id)
return await self.elicit_form(message, requested_schema, related_request_id)
async def elicit_form(
self,
message: str,
requestedSchema: types.ElicitRequestedSchema,
requested_schema: types.ElicitRequestedSchema,
related_request_id: types.RequestId | None = None,
) -> types.ElicitResult:
"""Send a form mode elicitation/create request.
Args:
message: The message to present to the user
requestedSchema: Schema defining the expected response structure
requested_schema: Schema defining the expected response structure
related_request_id: Optional ID of the request that triggered this elicitation
Returns:
@@ -411,7 +411,7 @@ class ServerSession(
types.ElicitRequest(
params=types.ElicitRequestFormParams(
message=message,
requestedSchema=requestedSchema,
requested_schema=requested_schema,
),
)
),
@@ -451,7 +451,7 @@ class ServerSession(
params=types.ElicitRequestURLParams(
message=message,
url=url,
elicitationId=elicitation_id,
elicitation_id=elicitation_id,
),
)
),
@@ -479,7 +479,7 @@ class ServerSession(
types.ServerNotification(
types.ProgressNotification(
params=types.ProgressNotificationParams(
progressToken=progress_token,
progress_token=progress_token,
progress=progress,
total=total,
message=message,
@@ -519,7 +519,7 @@ class ServerSession(
await self.send_notification(
types.ServerNotification(
types.ElicitCompleteNotification(
params=types.ElicitCompleteNotificationParams(elicitationId=elicitation_id)
params=types.ElicitCompleteNotificationParams(elicitation_id=elicitation_id)
)
),
related_request_id,
@@ -528,7 +528,7 @@ class ServerSession(
def _build_elicit_form_request(
self,
message: str,
requestedSchema: types.ElicitRequestedSchema,
requested_schema: types.ElicitRequestedSchema,
related_task_id: str | None = None,
task: types.TaskMetadata | None = None,
) -> types.JSONRPCRequest:
@@ -536,7 +536,7 @@ class ServerSession(
Args:
message: The message to present to the user
requestedSchema: Schema defining the expected response structure
requested_schema: Schema defining the expected response structure
related_task_id: If provided, adds io.modelcontextprotocol/related-task metadata
task: If provided, makes this a task-augmented request
@@ -545,7 +545,7 @@ class ServerSession(
"""
params = types.ElicitRequestFormParams(
message=message,
requestedSchema=requestedSchema,
requested_schema=requested_schema,
task=task,
)
params_data = params.model_dump(by_alias=True, mode="json", exclude_none=True)
@@ -556,7 +556,7 @@ class ServerSession(
if "_meta" not in params_data: # pragma: no cover
params_data["_meta"] = {}
params_data["_meta"][RELATED_TASK_METADATA_KEY] = types.RelatedTaskMetadata(
taskId=related_task_id
task_id=related_task_id
).model_dump(by_alias=True)
request_id = f"task-{related_task_id}-{id(params)}" if related_task_id else self._request_id
@@ -591,7 +591,7 @@ class ServerSession(
params = types.ElicitRequestURLParams(
message=message,
url=url,
elicitationId=elicitation_id,
elicitation_id=elicitation_id,
)
params_data = params.model_dump(by_alias=True, mode="json", exclude_none=True)
@@ -601,7 +601,7 @@ class ServerSession(
if "_meta" not in params_data: # pragma: no cover
params_data["_meta"] = {}
params_data["_meta"][RELATED_TASK_METADATA_KEY] = types.RelatedTaskMetadata(
taskId=related_task_id
task_id=related_task_id
).model_dump(by_alias=True)
request_id = f"task-{related_task_id}-{id(params)}" if related_task_id else self._request_id
@@ -652,15 +652,15 @@ class ServerSession(
"""
params = types.CreateMessageRequestParams(
messages=messages,
systemPrompt=system_prompt,
includeContext=include_context,
system_prompt=system_prompt,
include_context=include_context,
temperature=temperature,
maxTokens=max_tokens,
stopSequences=stop_sequences,
max_tokens=max_tokens,
stop_sequences=stop_sequences,
metadata=metadata,
modelPreferences=model_preferences,
model_preferences=model_preferences,
tools=tools,
toolChoice=tool_choice,
tool_choice=tool_choice,
task=task,
)
params_data = params.model_dump(by_alias=True, mode="json", exclude_none=True)
@@ -671,7 +671,7 @@ class ServerSession(
if "_meta" not in params_data: # pragma: no cover
params_data["_meta"] = {}
params_data["_meta"][RELATED_TASK_METADATA_KEY] = types.RelatedTaskMetadata(
taskId=related_task_id
task_id=related_task_id
).model_dump(by_alias=True)
request_id = f"task-{related_task_id}-{id(params)}" if related_task_id else self._request_id
+1 -1
View File
@@ -231,7 +231,7 @@ class SseServerTransport:
logger.debug(f"Received JSON: {body}")
try:
message = types.JSONRPCMessage.model_validate_json(body)
message = types.JSONRPCMessage.model_validate_json(body, by_name=False)
logger.debug(f"Validated client message: {message}")
except ValidationError as err:
logger.exception("Failed to parse message")
+1 -1
View File
@@ -62,7 +62,7 @@ async def stdio_server(
async with read_stream_writer:
async for line in stdin:
try:
message = types.JSONRPCMessage.model_validate_json(line)
message = types.JSONRPCMessage.model_validate_json(line, by_name=False)
except Exception as exc: # pragma: no cover
await read_stream_writer.send(exc)
continue
+1 -1
View File
@@ -471,7 +471,7 @@ class StreamableHTTPServerTransport:
return
try: # pragma: no cover
message = JSONRPCMessage.model_validate(raw_message)
message = JSONRPCMessage.model_validate(raw_message, by_name=False)
except ValidationError as e: # pragma: no cover
response = self._create_error_response(
f"Validation error: {str(e)}",
+1 -1
View File
@@ -99,6 +99,6 @@ def validate_tool_use_result_messages(messages: list[SamplingMessage]) -> None:
if has_previous_tool_use and previous_content:
tool_use_ids = {c.id for c in previous_content if c.type == "tool_use"}
tool_result_ids = {c.toolUseId for c in last_content if c.type == "tool_result"}
tool_result_ids = {c.tool_use_id for c in last_content if c.type == "tool_result"}
if tool_use_ids != tool_result_ids:
raise ValueError("ids of tool_result blocks and tool_use blocks from previous message do not match")
+1 -1
View File
@@ -37,7 +37,7 @@ async def websocket_server(scope: Scope, receive: Receive, send: Send):
async with read_stream_writer:
async for msg in websocket.iter_text():
try:
client_message = types.JSONRPCMessage.model_validate_json(msg)
client_message = types.JSONRPCMessage.model_validate_json(msg, by_name=False)
except ValidationError as exc:
await read_stream_writer.send(exc)
continue
+1 -1
View File
@@ -49,7 +49,7 @@ class UrlElicitationRequiredError(McpError):
mode="url",
message="Authorization required for your files",
url="https://example.com/oauth/authorize",
elicitationId="auth-001"
elicitation_id="auth-001"
)
])
"""
@@ -48,8 +48,8 @@ def check_tasks_capability(
if required.requests.sampling is not None:
if client.requests.sampling is None:
return False
if required.requests.sampling.createMessage is not None:
if client.requests.sampling.createMessage is None:
if required.requests.sampling.create_message is not None:
if client.requests.sampling.create_message is None:
return False
return True
@@ -74,7 +74,7 @@ def has_task_augmented_sampling(caps: ClientCapabilities) -> bool:
return False
if caps.tasks.requests.sampling is None:
return False
return caps.tasks.requests.sampling.createMessage is not None
return caps.tasks.requests.sampling.create_message is not None
def require_task_augmented_elicitation(client_caps: ClientCapabilities | None) -> None:
+1 -1
View File
@@ -41,7 +41,7 @@ class TaskContext:
@property
def task_id(self) -> str:
"""The task identifier."""
return self._task.taskId
return self._task.task_id
@property
def task(self) -> Task:
+4 -4
View File
@@ -125,12 +125,12 @@ def create_task_state(
"""
now = datetime.now(timezone.utc)
return Task(
taskId=task_id or generate_task_id(),
task_id=task_id or generate_task_id(),
status=TASK_STATUS_WORKING,
createdAt=now,
lastUpdatedAt=now,
created_at=now,
last_updated_at=now,
ttl=metadata.ttl,
pollInterval=500, # Default 500ms poll interval
poll_interval=500, # Default 500ms poll interval
)
@@ -79,14 +79,14 @@ class InMemoryTaskStore(TaskStore):
task = create_task_state(metadata, task_id)
if task.taskId in self._tasks:
raise ValueError(f"Task with ID {task.taskId} already exists")
if task.task_id in self._tasks:
raise ValueError(f"Task with ID {task.task_id} already exists")
stored = StoredTask(
task=task,
expires_at=self._calculate_expiry(metadata.ttl),
)
self._tasks[task.taskId] = stored
self._tasks[task.task_id] = stored
# Return a copy to prevent external modification
return Task(**task.model_dump())
@@ -124,10 +124,10 @@ class InMemoryTaskStore(TaskStore):
status_changed = True
if status_message is not None:
stored.task.statusMessage = status_message
stored.task.status_message = status_message
# Update lastUpdatedAt on any change
stored.task.lastUpdatedAt = datetime.now(timezone.utc)
# Update last_updated_at on any change
stored.task.last_updated_at = datetime.now(timezone.utc)
# If task is now terminal and has TTL, reset expiry timer
if status is not None and is_terminal(status) and stored.task.ttl is not None:
+1 -1
View File
@@ -41,5 +41,5 @@ async def poll_until_terminal(
if is_terminal(status.status):
break
interval_ms = status.pollInterval if status.pollInterval is not None else default_interval_ms
interval_ms = status.poll_interval if status.poll_interval is not None else default_interval_ms
await anyio.sleep(interval_ms / 1000)
+2 -2
View File
@@ -48,10 +48,10 @@ def progress(
ProgressContext[SendRequestT, SendNotificationT, SendResultT, ReceiveRequestT, ReceiveNotificationT],
None,
]:
if ctx.meta is None or ctx.meta.progressToken is None: # pragma: no cover
if ctx.meta is None or ctx.meta.progress_token is None: # pragma: no cover
raise ValueError("No progress token provided")
progress_ctx = ProgressContext(ctx.session, ctx.meta.progressToken, total)
progress_ctx = ProgressContext(ctx.session, ctx.meta.progress_token, total)
try:
yield progress_ctx
finally:
+7 -5
View File
@@ -301,7 +301,7 @@ class BaseSession(
if isinstance(response_or_error, JSONRPCError):
raise McpError(response_or_error.error)
else:
return result_type.model_validate(response_or_error.result)
return result_type.model_validate(response_or_error.result, by_name=False)
finally:
self._response_streams.pop(request_id, None)
@@ -356,7 +356,8 @@ class BaseSession(
elif isinstance(message.message.root, JSONRPCRequest):
try:
validated_request = self._receive_request_type.model_validate(
message.message.root.model_dump(by_alias=True, mode="json", exclude_none=True)
message.message.root.model_dump(by_alias=True, mode="json", exclude_none=True),
by_name=False,
)
responder = RequestResponder(
request_id=message.message.root.id,
@@ -393,17 +394,18 @@ class BaseSession(
elif isinstance(message.message.root, JSONRPCNotification):
try:
notification = self._receive_notification_type.model_validate(
message.message.root.model_dump(by_alias=True, mode="json", exclude_none=True)
message.message.root.model_dump(by_alias=True, mode="json", exclude_none=True),
by_name=False,
)
# Handle cancellation notifications
if isinstance(notification.root, CancelledNotification):
cancelled_id = notification.root.params.requestId
cancelled_id = notification.root.params.request_id
if cancelled_id in self._in_flight: # pragma: no branch
await self._in_flight[cancelled_id].cancel()
else:
# Handle progress notifications callback
if isinstance(notification.root, ProgressNotification): # pragma: no cover
progress_token = notification.root.params.progressToken
progress_token = notification.root.params.progress_token
# If there is a progress callback for this token,
# call it with the progress information
if progress_token in self._progress_callbacks:
+64 -63
View File
@@ -5,6 +5,7 @@ from datetime import datetime
from typing import Annotated, Any, Final, Generic, Literal, TypeAlias, TypeVar
from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel
from pydantic.alias_generators import to_camel
LATEST_PROTOCOL_VERSION = "2025-11-25"
@@ -31,7 +32,7 @@ TASK_REQUIRED: Final[Literal["required"]] = "required"
class MCPModel(BaseModel):
"""Base class for all MCP protocol types. Allows extra fields for forward compatibility."""
model_config = ConfigDict(extra="allow")
model_config = ConfigDict(extra="allow", alias_generator=to_camel, populate_by_name=True)
class TaskMetadata(MCPModel):
@@ -46,7 +47,7 @@ class TaskMetadata(MCPModel):
class RequestParams(MCPModel):
class Meta(MCPModel):
progressToken: ProgressToken | None = None
progress_token: ProgressToken | None = None
"""
If specified, the caller requests out-of-band progress notifications for
this request (as represented by notifications/progress). The value of this
@@ -123,7 +124,7 @@ class Result(MCPModel):
class PaginatedResult(Result):
nextCursor: Cursor | None = None
next_cursor: Cursor | None = None
"""
An opaque token representing the pagination position after the last returned result.
If present, there may be more results available.
@@ -228,7 +229,7 @@ class Icon(MCPModel):
src: str
"""URL or data URI for the icon."""
mimeType: str | None = None
mime_type: str | None = None
"""Optional MIME type for the icon."""
sizes: list[str] | None = None
@@ -246,7 +247,7 @@ class Implementation(BaseMetadata):
description: str | None = None
"""An optional human-readable description of what this implementation does."""
websiteUrl: str | None = None
website_url: str | None = None
"""An optional URL of the website for this implementation."""
icons: list[Icon] | None = None
@@ -256,7 +257,7 @@ class Implementation(BaseMetadata):
class RootsCapability(MCPModel):
"""Capability for root operations."""
listChanged: bool | None = None
list_changed: bool | None = None
"""Whether the client supports notifications for changes to the roots list."""
@@ -331,7 +332,7 @@ class TasksCreateMessageCapability(MCPModel):
class TasksSamplingCapability(MCPModel):
"""Capability for tasks sampling operations."""
createMessage: TasksCreateMessageCapability | None = None
create_message: TasksCreateMessageCapability | None = None
class TasksCreateElicitationCapability(MCPModel):
@@ -386,7 +387,7 @@ class ClientCapabilities(MCPModel):
class PromptsCapability(MCPModel):
"""Capability for prompts operations."""
listChanged: bool | None = None
list_changed: bool | None = None
"""Whether this server supports notifications for changes to the prompt list."""
@@ -395,14 +396,14 @@ class ResourcesCapability(MCPModel):
subscribe: bool | None = None
"""Whether this server supports subscribing to resource updates."""
listChanged: bool | None = None
list_changed: bool | None = None
"""Whether this server supports notifications for changes to the resource list."""
class ToolsCapability(MCPModel):
"""Capability for tools operations."""
listChanged: bool | None = None
list_changed: bool | None = None
"""Whether this server supports notifications for changes to the tool list."""
@@ -474,20 +475,20 @@ class RelatedTaskMetadata(MCPModel):
Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`.
"""
taskId: str
task_id: str
"""The task identifier this message is associated with."""
class Task(MCPModel):
"""Data associated with a task."""
taskId: str
task_id: str
"""The task identifier."""
status: TaskStatus
"""Current task state."""
statusMessage: str | None = None
status_message: str | None = None
"""
Optional human-readable message describing the current task state.
This can provide context for any status, including:
@@ -496,16 +497,16 @@ class Task(MCPModel):
- Diagnostic information for "failed" status (e.g., error details, what went wrong)
"""
createdAt: datetime # Pydantic will enforce ISO 8601 and re-serialize as a string later
created_at: datetime # Pydantic will enforce ISO 8601 and re-serialize as a string later
"""ISO 8601 timestamp when the task was created."""
lastUpdatedAt: datetime
last_updated_at: datetime
"""ISO 8601 timestamp when the task was last updated."""
ttl: Annotated[int, Field(strict=True)] | None
"""Actual retention duration from creation in milliseconds, null for unlimited."""
pollInterval: Annotated[int, Field(strict=True)] | None = None
poll_interval: Annotated[int, Field(strict=True)] | None = None
"""Suggested polling interval in milliseconds."""
@@ -516,7 +517,7 @@ class CreateTaskResult(Result):
class GetTaskRequestParams(RequestParams):
taskId: str
task_id: str
"""The task identifier to query."""
@@ -533,7 +534,7 @@ class GetTaskResult(Result, Task):
class GetTaskPayloadRequestParams(RequestParams):
taskId: str
task_id: str
"""The task identifier to retrieve results for."""
@@ -553,7 +554,7 @@ class GetTaskPayloadResult(Result):
class CancelTaskRequestParams(RequestParams):
taskId: str
task_id: str
"""The task identifier to cancel."""
@@ -597,10 +598,10 @@ class TaskStatusNotification(Notification[TaskStatusNotificationParams, Literal[
class InitializeRequestParams(RequestParams):
"""Parameters for the initialize request."""
protocolVersion: str | int
protocol_version: str | int
"""The latest version of the Model Context Protocol that the client supports."""
capabilities: ClientCapabilities
clientInfo: Implementation
client_info: Implementation
class InitializeRequest(Request[InitializeRequestParams, Literal["initialize"]]):
@@ -616,10 +617,10 @@ class InitializeRequest(Request[InitializeRequestParams, Literal["initialize"]])
class InitializeResult(Result):
"""After receiving an initialize request from the client, the server sends this."""
protocolVersion: str | int
protocol_version: str | int
"""The version of the Model Context Protocol that the server wants to use."""
capabilities: ServerCapabilities
serverInfo: Implementation
server_info: Implementation
instructions: str | None = None
"""Instructions describing how to use the server and its features."""
@@ -647,7 +648,7 @@ class PingRequest(Request[RequestParams | None, Literal["ping"]]):
class ProgressNotificationParams(NotificationParams):
"""Parameters for progress notifications."""
progressToken: ProgressToken
progress_token: ProgressToken
"""
The progress token which was given in the initial request, used to associate this
notification with the request that is proceeding.
@@ -694,7 +695,7 @@ class Resource(BaseMetadata):
"""The URI of this resource."""
description: str | None = None
"""A description of what this resource represents."""
mimeType: str | None = None
mime_type: str | None = None
"""The MIME type of this resource, if known."""
size: int | None = None
"""
@@ -716,14 +717,14 @@ class Resource(BaseMetadata):
class ResourceTemplate(BaseMetadata):
"""A template description for resources available on the server."""
uriTemplate: str
uri_template: str
"""
A URI template (according to RFC 6570) that can be used to construct resource
URIs.
"""
description: str | None = None
"""A human-readable description of what this template is for."""
mimeType: str | None = None
mime_type: str | None = None
"""
The MIME type for all resources that match this template. This should only be
included if all resources matching this template have the same type.
@@ -753,7 +754,7 @@ class ListResourceTemplatesRequest(PaginatedRequest[Literal["resources/templates
class ListResourceTemplatesResult(PaginatedResult):
"""The server's response to a resources/templates/list request from the client."""
resourceTemplates: list[ResourceTemplate]
resource_templates: list[ResourceTemplate]
class ReadResourceRequestParams(RequestParams):
@@ -778,7 +779,7 @@ class ResourceContents(MCPModel):
uri: str
"""The URI of this resource."""
mimeType: str | None = None
mime_type: str | None = None
"""The MIME type of this resource, if known."""
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
"""
@@ -956,7 +957,7 @@ class ImageContent(MCPModel):
type: Literal["image"] = "image"
data: str
"""The base64-encoded image data."""
mimeType: str
mime_type: str
"""
The MIME type of the image. Different providers may support different
image types.
@@ -975,7 +976,7 @@ class AudioContent(MCPModel):
type: Literal["audio"] = "audio"
data: str
"""The base64-encoded audio data."""
mimeType: str
mime_type: str
"""
The MIME type of the audio. Different providers may support different
audio types.
@@ -1027,7 +1028,7 @@ class ToolResultContent(MCPModel):
type: Literal["tool_result"] = "tool_result"
"""Discriminator for tool result content."""
toolUseId: str
tool_use_id: str
"""The unique identifier that corresponds to the tool call's id field."""
content: list[ContentBlock] = []
@@ -1036,12 +1037,12 @@ class ToolResultContent(MCPModel):
Defaults to empty list if not provided.
"""
structuredContent: dict[str, Any] | None = None
structured_content: dict[str, Any] | None = None
"""
Optional structured tool output that matches the tool's outputSchema (if defined).
"""
isError: bool | None = None
is_error: bool | None = None
"""Whether the tool execution resulted in an error."""
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
@@ -1161,29 +1162,29 @@ class ToolAnnotations(MCPModel):
title: str | None = None
"""A human-readable title for the tool."""
readOnlyHint: bool | None = None
read_only_hint: bool | None = None
"""
If true, the tool does not modify its environment.
Default: false
"""
destructiveHint: bool | None = None
destructive_hint: 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`)
(This property is meaningful only when `read_only_hint == false`)
Default: true
"""
idempotentHint: bool | None = None
idempotent_hint: 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`)
(This property is meaningful only when `read_only_hint == false`)
Default: false
"""
openWorldHint: bool | None = None
open_world_hint: 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.
@@ -1196,7 +1197,7 @@ class ToolAnnotations(MCPModel):
class ToolExecution(MCPModel):
"""Execution-related properties for a tool."""
taskSupport: TaskExecutionMode | None = None
task_support: TaskExecutionMode | None = None
"""
Indicates whether this tool supports task-augmented execution.
This allows clients to handle long-running operations through polling
@@ -1215,12 +1216,12 @@ class Tool(BaseMetadata):
description: str | None = None
"""A human-readable description of the tool."""
inputSchema: dict[str, Any]
input_schema: dict[str, Any]
"""A JSON Schema object defining the expected parameters for the tool."""
outputSchema: dict[str, Any] | None = None
output_schema: dict[str, Any] | None = None
"""
An optional JSON Schema object defining the structure of the tool's output
returned in the structuredContent field of a CallToolResult.
returned in the structured_content field of a CallToolResult.
"""
icons: list[Icon] | None = None
"""An optional list of icons for this tool."""
@@ -1259,9 +1260,9 @@ class CallToolResult(Result):
"""The server's response to a tool call."""
content: list[ContentBlock]
structuredContent: dict[str, Any] | None = None
structured_content: dict[str, Any] | None = None
"""An optional JSON object that represents the structured result of the tool call."""
isError: bool = False
is_error: bool = False
class ToolListChangedNotification(Notification[NotificationParams | None, Literal["notifications/tools/list_changed"]]):
@@ -1349,21 +1350,21 @@ class ModelPreferences(MCPModel):
MAY still use the priorities to select from ambiguous matches.
"""
costPriority: float | None = None
cost_priority: float | None = None
"""
How much to prioritize cost when selecting a model. A value of 0 means cost
is not important, while a value of 1 means cost is the most important
factor.
"""
speedPriority: float | None = None
speed_priority: float | None = None
"""
How much to prioritize sampling speed (latency) when selecting a model. A
value of 0 means speed is not important, while a value of 1 means speed is
the most important factor.
"""
intelligencePriority: float | None = None
intelligence_priority: float | None = None
"""
How much to prioritize intelligence and capabilities when selecting a
model. A value of 0 means intelligence is not important, while a value of 1
@@ -1392,22 +1393,22 @@ class CreateMessageRequestParams(RequestParams):
"""Parameters for creating a message."""
messages: list[SamplingMessage]
modelPreferences: ModelPreferences | None = None
model_preferences: ModelPreferences | None = None
"""
The server's preferences for which model to select. The client MAY ignore
these preferences.
"""
systemPrompt: str | None = None
system_prompt: str | None = None
"""An optional system prompt the server wants to use for sampling."""
includeContext: IncludeContext | None = None
include_context: IncludeContext | None = None
"""
A request to include context from one or more MCP servers (including the caller), to
be attached to the prompt.
"""
temperature: float | None = None
maxTokens: int
max_tokens: int
"""The maximum number of tokens to sample, as requested by the server."""
stopSequences: list[str] | None = None
stop_sequences: list[str] | None = None
metadata: dict[str, Any] | None = None
"""Optional metadata to pass through to the LLM provider."""
tools: list[Tool] | None = None
@@ -1415,7 +1416,7 @@ class CreateMessageRequestParams(RequestParams):
Tool definitions for the LLM to use during sampling.
Requires clientCapabilities.sampling.tools to be present.
"""
toolChoice: ToolChoice | None = None
tool_choice: ToolChoice | None = None
"""
Controls tool usage behavior.
Requires clientCapabilities.sampling.tools and the tools parameter to be present.
@@ -1445,7 +1446,7 @@ class CreateMessageResult(Result):
"""Response content. Single content block (text, image, or audio)."""
model: str
"""The name of the model that generated the message."""
stopReason: StopReason | None = None
stop_reason: StopReason | None = None
"""The reason why sampling stopped, if known."""
@@ -1460,11 +1461,11 @@ class CreateMessageResultWithTools(Result):
content: SamplingMessageContentBlock | list[SamplingMessageContentBlock]
"""
Response content. May be a single content block or an array.
May include ToolUseContent if stopReason is 'toolUse'.
May include ToolUseContent if stop_reason is 'toolUse'.
"""
model: str
"""The name of the model that generated the message."""
stopReason: StopReason | None = None
stop_reason: StopReason | None = None
"""
The reason why sampling stopped, if known.
'toolUse' indicates the model wants to use a tool.
@@ -1535,7 +1536,7 @@ class Completion(MCPModel):
The total number of completion options available. This can exceed the number of
values actually sent in the response.
"""
hasMore: bool | None = None
has_more: bool | None = None
"""
Indicates whether there are additional completion options beyond those provided in
the current response, even if the exact total is unknown.
@@ -1614,7 +1615,7 @@ class RootsListChangedNotification(
class CancelledNotificationParams(NotificationParams):
"""Parameters for cancellation notifications."""
requestId: RequestId | None = None
request_id: RequestId | None = None
"""
The ID of the request to cancel.
@@ -1639,7 +1640,7 @@ class CancelledNotification(Notification[CancelledNotificationParams, Literal["n
class ElicitCompleteNotificationParams(NotificationParams):
"""Parameters for elicitation completion notifications."""
elicitationId: str
elicitation_id: str
"""The unique identifier of the elicitation that was completed."""
@@ -1716,7 +1717,7 @@ class ElicitRequestFormParams(RequestParams):
message: str
"""The message to present to the user describing what information is being requested."""
requestedSchema: ElicitRequestedSchema
requested_schema: ElicitRequestedSchema
"""
A restricted subset of JSON Schema defining the structure of expected response.
Only top-level properties are allowed, without nesting.
@@ -1739,7 +1740,7 @@ class ElicitRequestURLParams(RequestParams):
url: str
"""The URL that the user should navigate to."""
elicitationId: str
elicitation_id: str
"""
The ID of the elicitation, which must be unique within the context of the server.
The client MUST treat this ID as an opaque value.
+1 -1
View File
@@ -61,7 +61,7 @@ def run_unicode_server(port: int) -> None: # pragma: no cover
Tool(
name="echo_unicode",
description="🔤 Echo Unicode text - Hello 👋 World 🌍 - Testing 🧪 Unicode ✨",
inputSchema={
input_schema={
"type": "object",
"properties": {
"text": {"type": "string", "description": "Text to echo back"},
+2 -2
View File
@@ -45,7 +45,7 @@ async def test_list_roots_callback():
async with create_session(server._mcp_server, list_roots_callback=list_roots_callback) as client_session:
# Make a request to trigger sampling callback
result = await client_session.call_tool("test_list_roots", {"message": "test message"})
assert result.isError is False
assert result.is_error is False
assert isinstance(result.content[0], TextContent)
assert result.content[0].text == "true"
@@ -53,6 +53,6 @@ async def test_list_roots_callback():
async with create_session(server._mcp_server) as client_session:
# Make a request to trigger sampling callback
result = await client_session.call_tool("test_list_roots", {"message": "test message"})
assert result.isError is True
assert result.is_error is True
assert isinstance(result.content[0], TextContent)
assert result.content[0].text == "Error executing tool test_list_roots: List roots not supported"
+3 -3
View File
@@ -78,7 +78,7 @@ async def test_logging_callback():
) as client_session:
# First verify our test tool works
result = await client_session.call_tool("test_tool", {})
assert result.isError is False
assert result.is_error is False
assert isinstance(result.content[0], TextContent)
assert result.content[0].text == "true"
@@ -101,8 +101,8 @@ async def test_logging_callback():
"extra_dict": {"a": 1, "b": 2, "c": 3},
},
)
assert log_result.isError is False
assert log_result_with_extra.isError is False
assert log_result.is_error is False
assert log_result_with_extra.is_error is False
assert len(logging_collector.log_messages) == 2
# Create meta object with related_request_id added dynamically
log = logging_collector.log_messages[0]
+11 -11
View File
@@ -66,8 +66,8 @@ class TestClientOutputSchemaValidation:
Tool(
name="get_user",
description="Get user data",
inputSchema={"type": "object"},
outputSchema=output_schema,
input_schema={"type": "object"},
output_schema=output_schema,
)
]
@@ -105,8 +105,8 @@ class TestClientOutputSchemaValidation:
Tool(
name="calculate",
description="Calculate something",
inputSchema={"type": "object"},
outputSchema=output_schema,
input_schema={"type": "object"},
output_schema=output_schema,
)
]
@@ -136,8 +136,8 @@ class TestClientOutputSchemaValidation:
Tool(
name="get_scores",
description="Get scores",
inputSchema={"type": "object"},
outputSchema=output_schema,
input_schema={"type": "object"},
output_schema=output_schema,
)
]
@@ -171,8 +171,8 @@ class TestClientOutputSchemaValidation:
Tool(
name="get_person",
description="Get person data",
inputSchema={"type": "object"},
outputSchema=output_schema,
input_schema={"type": "object"},
output_schema=output_schema,
)
]
@@ -190,7 +190,7 @@ class TestClientOutputSchemaValidation:
@pytest.mark.anyio
async def test_tool_not_listed_warning(self, caplog: pytest.LogCaptureFixture):
"""Test that client logs warning when tool is not in list_tools but has outputSchema"""
"""Test that client logs warning when tool is not in list_tools but has output_schema"""
server = Server("test-server")
@server.list_tools()
@@ -210,8 +210,8 @@ class TestClientOutputSchemaValidation:
async with client_session(server) as client:
# Call a tool that wasn't listed
result = await client.call_tool("mystery_tool", {})
assert result.structuredContent == {"result": 42}
assert result.isError is False
assert result.structured_content == {"result": 42}
assert result.is_error is False
# Check that warning was logged
assert "Tool mystery_tool not listed" in caplog.text
+7 -7
View File
@@ -25,7 +25,7 @@ async def test_sampling_callback():
role="assistant",
content=TextContent(type="text", text="This is a response from the sampling callback"),
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
async def sampling_callback(
@@ -47,7 +47,7 @@ async def test_sampling_callback():
async with create_session(server._mcp_server, sampling_callback=sampling_callback) as client_session:
# Make a request to trigger sampling callback
result = await client_session.call_tool("test_sampling", {"message": "Test message for sampling"})
assert result.isError is False
assert result.is_error is False
assert isinstance(result.content[0], TextContent)
assert result.content[0].text == "true"
@@ -55,7 +55,7 @@ async def test_sampling_callback():
async with create_session(server._mcp_server) as client_session:
# Make a request to trigger sampling callback
result = await client_session.call_tool("test_sampling", {"message": "Test message for sampling"})
assert result.isError is True
assert result.is_error is True
assert isinstance(result.content[0], TextContent)
assert result.content[0].text == "Error executing tool test_sampling: Sampling not supported"
@@ -72,7 +72,7 @@ async def test_create_message_backwards_compat_single_content():
role="assistant",
content=TextContent(type="text", text="Hello from LLM"),
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
async def sampling_callback(
@@ -99,7 +99,7 @@ async def test_create_message_backwards_compat_single_content():
async with create_session(server._mcp_server, sampling_callback=sampling_callback) as client_session:
result = await client_session.call_tool("test_backwards_compat", {"message": "Test"})
assert result.isError is False
assert result.is_error is False
assert isinstance(result.content[0], TextContent)
assert result.content[0].text == "true"
@@ -112,7 +112,7 @@ async def test_create_message_result_with_tools_type():
role="assistant",
content=ToolUseContent(type="tool_use", id="call_123", name="get_weather", input={"city": "SF"}),
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
# CreateMessageResultWithTools should have content_as_list
@@ -128,7 +128,7 @@ async def test_create_message_result_with_tools_type():
ToolUseContent(type="tool_use", id="call_456", name="get_weather", input={"city": "NYC"}),
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
content_list_array = result_array.content_as_list
assert len(content_list_array) == 2
+35 -35
View File
@@ -49,7 +49,7 @@ async def test_client_session_initialize():
result = ServerResult(
InitializeResult(
protocolVersion=LATEST_PROTOCOL_VERSION,
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(
logging=None,
resources=None,
@@ -57,7 +57,7 @@ async def test_client_session_initialize():
experimental=None,
prompts=None,
),
serverInfo=Implementation(name="mock-server", version="0.1.0"),
server_info=Implementation(name="mock-server", version="0.1.0"),
instructions="The server instructions.",
)
)
@@ -105,9 +105,9 @@ async def test_client_session_initialize():
# Assert the result
assert isinstance(result, InitializeResult)
assert result.protocolVersion == LATEST_PROTOCOL_VERSION
assert result.protocol_version == LATEST_PROTOCOL_VERSION
assert isinstance(result.capabilities, ServerCapabilities)
assert result.serverInfo == Implementation(name="mock-server", version="0.1.0")
assert result.server_info == Implementation(name="mock-server", version="0.1.0")
assert result.instructions == "The server instructions."
# Check that the client sent the initialized notification
@@ -133,13 +133,13 @@ async def test_client_session_custom_client_info():
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
received_client_info = request.root.params.clientInfo
received_client_info = request.root.params.client_info
result = ServerResult(
InitializeResult(
protocolVersion=LATEST_PROTOCOL_VERSION,
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
serverInfo=Implementation(name="mock-server", version="0.1.0"),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
)
@@ -194,13 +194,13 @@ async def test_client_session_default_client_info():
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
received_client_info = request.root.params.clientInfo
received_client_info = request.root.params.client_info
result = ServerResult(
InitializeResult(
protocolVersion=LATEST_PROTOCOL_VERSION,
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
serverInfo=Implementation(name="mock-server", version="0.1.0"),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
)
@@ -254,14 +254,14 @@ async def test_client_session_version_negotiation_success():
assert isinstance(request.root, InitializeRequest)
# Verify client sent the latest protocol version
assert request.root.params.protocolVersion == LATEST_PROTOCOL_VERSION
assert request.root.params.protocol_version == LATEST_PROTOCOL_VERSION
# Server responds with a supported older version
result = ServerResult(
InitializeResult(
protocolVersion="2024-11-05",
protocol_version="2024-11-05",
capabilities=ServerCapabilities(),
serverInfo=Implementation(name="mock-server", version="0.1.0"),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
)
@@ -296,8 +296,8 @@ async def test_client_session_version_negotiation_success():
# Assert the result with negotiated version
assert isinstance(result, InitializeResult)
assert result.protocolVersion == "2024-11-05"
assert result.protocolVersion in SUPPORTED_PROTOCOL_VERSIONS
assert result.protocol_version == "2024-11-05"
assert result.protocol_version in SUPPORTED_PROTOCOL_VERSIONS
@pytest.mark.anyio
@@ -318,9 +318,9 @@ async def test_client_session_version_negotiation_failure():
# Server responds with an unsupported version
result = ServerResult(
InitializeResult(
protocolVersion="2020-01-01", # Unsupported old version
protocol_version="2020-01-01", # Unsupported old version
capabilities=ServerCapabilities(),
serverInfo=Implementation(name="mock-server", version="0.1.0"),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
)
@@ -377,9 +377,9 @@ async def test_client_capabilities_default():
result = ServerResult(
InitializeResult(
protocolVersion=LATEST_PROTOCOL_VERSION,
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
serverInfo=Implementation(name="mock-server", version="0.1.0"),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
)
@@ -455,9 +455,9 @@ async def test_client_capabilities_with_custom_callbacks():
result = ServerResult(
InitializeResult(
protocolVersion=LATEST_PROTOCOL_VERSION,
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
serverInfo=Implementation(name="mock-server", version="0.1.0"),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
)
@@ -503,7 +503,7 @@ async def test_client_capabilities_with_custom_callbacks():
assert received_capabilities.roots is not None
assert isinstance(received_capabilities.roots, types.RootsCapability)
# Should be True for custom callback
assert received_capabilities.roots.listChanged is True
assert received_capabilities.roots.list_changed is True
@pytest.mark.anyio
@@ -538,9 +538,9 @@ async def test_client_capabilities_with_sampling_tools():
result = ServerResult(
InitializeResult(
protocolVersion=LATEST_PROTOCOL_VERSION,
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
serverInfo=Implementation(name="mock-server", version="0.1.0"),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
)
@@ -592,9 +592,9 @@ async def test_get_server_capabilities():
expected_capabilities = ServerCapabilities(
logging=types.LoggingCapability(),
prompts=types.PromptsCapability(listChanged=True),
resources=types.ResourcesCapability(subscribe=True, listChanged=True),
tools=types.ToolsCapability(listChanged=False),
prompts=types.PromptsCapability(list_changed=True),
resources=types.ResourcesCapability(subscribe=True, list_changed=True),
tools=types.ToolsCapability(list_changed=False),
)
async def mock_server():
@@ -608,9 +608,9 @@ async def test_get_server_capabilities():
result = ServerResult(
InitializeResult(
protocolVersion=LATEST_PROTOCOL_VERSION,
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=expected_capabilities,
serverInfo=Implementation(name="mock-server", version="0.1.0"),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
)
@@ -649,11 +649,11 @@ async def test_get_server_capabilities():
assert capabilities == expected_capabilities
assert capabilities.logging is not None
assert capabilities.prompts is not None
assert capabilities.prompts.listChanged is True
assert capabilities.prompts.list_changed is True
assert capabilities.resources is not None
assert capabilities.resources.subscribe is True
assert capabilities.tools is not None
assert capabilities.tools.listChanged is False
assert capabilities.tools.list_changed is False
@pytest.mark.anyio
@@ -663,7 +663,7 @@ async def test_client_tool_call_with_meta(meta: dict[str, Any] | None):
client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[SessionMessage](1)
server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[SessionMessage](1)
mocked_tool = types.Tool(name="sample_tool", inputSchema={})
mocked_tool = types.Tool(name="sample_tool", input_schema={})
async def mock_server():
# Receive initialization request from client
@@ -677,9 +677,9 @@ async def test_client_tool_call_with_meta(meta: dict[str, Any] | None):
result = ServerResult(
InitializeResult(
protocolVersion=LATEST_PROTOCOL_VERSION,
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
serverInfo=Implementation(name="mock-server", version="0.1.0"),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
)
@@ -712,7 +712,7 @@ async def test_client_tool_call_with_meta(meta: dict[str, Any] | None):
assert jsonrpc_request.root.params["_meta"] == meta
result = ServerResult(
CallToolResult(content=[TextContent(type="text", text="Called successfully")], isError=False)
CallToolResult(content=[TextContent(type="text", text="Called successfully")], is_error=False)
)
# Send the tools/call result
+3 -3
View File
@@ -59,7 +59,7 @@ class TestClientSessionGroup:
return f"{(server_info.name)}-{name}"
mcp_session_group = ClientSessionGroup(component_name_hook=hook)
mcp_session_group._tools = {"server1-my_tool": types.Tool(name="my_tool", inputSchema={})}
mcp_session_group._tools = {"server1-my_tool": types.Tool(name="my_tool", input_schema={})}
mcp_session_group._tool_to_session = {"server1-my_tool": mock_session}
text_content = types.TextContent(type="text", text="OK")
mock_session.call_tool.return_value = types.CallToolResult(content=[text_content])
@@ -324,7 +324,7 @@ class TestClientSessionGroup:
# Mock session.initialize()
mock_initialize_result = mock.AsyncMock(name="InitializeResult")
mock_initialize_result.serverInfo = types.Implementation(name="foo", version="1")
mock_initialize_result.server_info = types.Implementation(name="foo", version="1")
mock_entered_session.initialize.return_value = mock_initialize_result
# --- Test Execution ---
@@ -381,5 +381,5 @@ class TestClientSessionGroup:
mock_entered_session.initialize.assert_awaited_once()
# 3. Assert returned values
assert returned_server_info is mock_initialize_result.serverInfo
assert returned_server_info is mock_initialize_result.server_info
assert returned_session is mock_entered_session
@@ -45,9 +45,9 @@ async def test_client_capabilities_without_tasks():
result = ServerResult(
InitializeResult(
protocolVersion=LATEST_PROTOCOL_VERSION,
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
serverInfo=Implementation(name="mock-server", version="0.1.0"),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
)
@@ -119,9 +119,9 @@ async def test_client_capabilities_with_tasks():
result = ServerResult(
InitializeResult(
protocolVersion=LATEST_PROTOCOL_VERSION,
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
serverInfo=Implementation(name="mock-server", version="0.1.0"),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
)
@@ -203,9 +203,9 @@ async def test_client_capabilities_auto_built_from_handlers():
result = ServerResult(
InitializeResult(
protocolVersion=LATEST_PROTOCOL_VERSION,
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
serverInfo=Implementation(name="mock-server", version="0.1.0"),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
)
@@ -283,9 +283,9 @@ async def test_client_capabilities_with_task_augmented_handlers():
result = ServerResult(
InitializeResult(
protocolVersion=LATEST_PROTOCOL_VERSION,
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
serverInfo=Implementation(name="mock-server", version="0.1.0"),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
)
@@ -117,17 +117,17 @@ async def test_client_handles_get_task_request(client_streams: ClientTestStreams
params: GetTaskRequestParams,
) -> GetTaskResult | ErrorData:
nonlocal received_task_id
received_task_id = params.taskId
task = await store.get_task(params.taskId)
assert task is not None, f"Test setup error: task {params.taskId} should exist"
received_task_id = params.task_id
task = await store.get_task(params.task_id)
assert task is not None, f"Test setup error: task {params.task_id} should exist"
return GetTaskResult(
taskId=task.taskId,
task_id=task.task_id,
status=task.status,
statusMessage=task.statusMessage,
createdAt=task.createdAt,
lastUpdatedAt=task.lastUpdatedAt,
status_message=task.status_message,
created_at=task.created_at,
last_updated_at=task.last_updated_at,
ttl=task.ttl,
pollInterval=task.pollInterval,
poll_interval=task.poll_interval,
)
await store.create_task(TaskMetadata(ttl=60000), task_id="test-task-123")
@@ -150,7 +150,7 @@ async def test_client_handles_get_task_request(client_streams: ClientTestStreams
tg.start_soon(run_client)
await client_ready.wait()
typed_request = GetTaskRequest(params=GetTaskRequestParams(taskId="test-task-123"))
typed_request = GetTaskRequest(params=GetTaskRequestParams(task_id="test-task-123"))
request = types.JSONRPCRequest(
jsonrpc="2.0",
id="req-1",
@@ -164,7 +164,7 @@ async def test_client_handles_get_task_request(client_streams: ClientTestStreams
assert response.id == "req-1"
result = GetTaskResult.model_validate(response.result)
assert result.taskId == "test-task-123"
assert result.task_id == "test-task-123"
assert result.status == "working"
assert received_task_id == "test-task-123"
@@ -183,8 +183,8 @@ async def test_client_handles_get_task_result_request(client_streams: ClientTest
context: RequestContext[ClientSession, None],
params: GetTaskPayloadRequestParams,
) -> GetTaskPayloadResult | ErrorData:
result = await store.get_result(params.taskId)
assert result is not None, f"Test setup error: result for {params.taskId} should exist"
result = await store.get_result(params.task_id)
assert result is not None, f"Test setup error: result for {params.task_id} should exist"
assert isinstance(result, types.CallToolResult)
return GetTaskPayloadResult(**result.model_dump())
@@ -213,7 +213,7 @@ async def test_client_handles_get_task_result_request(client_streams: ClientTest
tg.start_soon(run_client)
await client_ready.wait()
typed_request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(taskId="test-task-456"))
typed_request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id="test-task-456"))
request = types.JSONRPCRequest(
jsonrpc="2.0",
id="req-2",
@@ -248,7 +248,7 @@ async def test_client_handles_list_tasks_request(client_streams: ClientTestStrea
) -> ListTasksResult | ErrorData:
cursor = params.cursor if params else None
tasks_list, next_cursor = await store.list_tasks(cursor=cursor)
return ListTasksResult(tasks=tasks_list, nextCursor=next_cursor)
return ListTasksResult(tasks=tasks_list, next_cursor=next_cursor)
await store.create_task(TaskMetadata(ttl=60000), task_id="task-1")
await store.create_task(TaskMetadata(ttl=60000), task_id="task-2")
@@ -301,16 +301,16 @@ async def test_client_handles_cancel_task_request(client_streams: ClientTestStre
context: RequestContext[ClientSession, None],
params: CancelTaskRequestParams,
) -> CancelTaskResult | ErrorData:
task = await store.get_task(params.taskId)
assert task is not None, f"Test setup error: task {params.taskId} should exist"
await store.update_task(params.taskId, status="cancelled")
updated = await store.get_task(params.taskId)
task = await store.get_task(params.task_id)
assert task is not None, f"Test setup error: task {params.task_id} should exist"
await store.update_task(params.task_id, status="cancelled")
updated = await store.get_task(params.task_id)
assert updated is not None
return CancelTaskResult(
taskId=updated.taskId,
task_id=updated.task_id,
status=updated.status,
createdAt=updated.createdAt,
lastUpdatedAt=updated.lastUpdatedAt,
created_at=updated.created_at,
last_updated_at=updated.last_updated_at,
ttl=updated.ttl,
)
@@ -334,7 +334,7 @@ async def test_client_handles_cancel_task_request(client_streams: ClientTestStre
tg.start_soon(run_client)
await client_ready.wait()
typed_request = CancelTaskRequest(params=CancelTaskRequestParams(taskId="task-to-cancel"))
typed_request = CancelTaskRequest(params=CancelTaskRequestParams(task_id="task-to-cancel"))
request = types.JSONRPCRequest(
jsonrpc="2.0",
id="req-4",
@@ -347,7 +347,7 @@ async def test_client_handles_cancel_task_request(client_streams: ClientTestStre
assert isinstance(response, types.JSONRPCResponse)
result = CancelTaskResult.model_validate(response.result)
assert result.taskId == "task-to-cancel"
assert result.task_id == "task-to-cancel"
assert result.status == "cancelled"
tg.cancel_scope.cancel()
@@ -370,17 +370,17 @@ async def test_client_task_augmented_sampling(client_streams: ClientTestStreams)
task_metadata: TaskMetadata,
) -> CreateTaskResult:
task = await store.create_task(task_metadata)
created_task_id[0] = task.taskId
created_task_id[0] = task.task_id
async def do_sampling() -> None:
result = CreateMessageResult(
role="assistant",
content=TextContent(type="text", text="Sampled response"),
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
await store.store_result(task.taskId, result)
await store.update_task(task.taskId, status="completed")
await store.store_result(task.task_id, result)
await store.update_task(task.task_id, status="completed")
sampling_completed.set()
assert background_tg[0] is not None
@@ -391,24 +391,24 @@ async def test_client_task_augmented_sampling(client_streams: ClientTestStreams)
context: RequestContext[ClientSession, None],
params: GetTaskRequestParams,
) -> GetTaskResult | ErrorData:
task = await store.get_task(params.taskId)
assert task is not None, f"Test setup error: task {params.taskId} should exist"
task = await store.get_task(params.task_id)
assert task is not None, f"Test setup error: task {params.task_id} should exist"
return GetTaskResult(
taskId=task.taskId,
task_id=task.task_id,
status=task.status,
statusMessage=task.statusMessage,
createdAt=task.createdAt,
lastUpdatedAt=task.lastUpdatedAt,
status_message=task.status_message,
created_at=task.created_at,
last_updated_at=task.last_updated_at,
ttl=task.ttl,
pollInterval=task.pollInterval,
poll_interval=task.poll_interval,
)
async def get_task_result_handler(
context: RequestContext[ClientSession, None],
params: GetTaskPayloadRequestParams,
) -> GetTaskPayloadResult | ErrorData:
result = await store.get_result(params.taskId)
assert result is not None, f"Test setup error: result for {params.taskId} should exist"
result = await store.get_result(params.task_id)
assert result is not None, f"Test setup error: result for {params.task_id} should exist"
assert isinstance(result, CreateMessageResult)
return GetTaskPayloadResult(**result.model_dump())
@@ -439,7 +439,7 @@ async def test_client_task_augmented_sampling(client_streams: ClientTestStreams)
typed_request = CreateMessageRequest(
params=CreateMessageRequestParams(
messages=[SamplingMessage(role="user", content=TextContent(type="text", text="Hello"))],
maxTokens=100,
max_tokens=100,
task=TaskMetadata(ttl=60000),
)
)
@@ -456,14 +456,14 @@ async def test_client_task_augmented_sampling(client_streams: ClientTestStreams)
assert isinstance(response, types.JSONRPCResponse)
task_result = CreateTaskResult.model_validate(response.result)
task_id = task_result.task.taskId
task_id = task_result.task.task_id
assert task_id == created_task_id[0]
# Step 3: Wait for background sampling
await sampling_completed.wait()
# Step 4: Server polls task status
typed_poll = GetTaskRequest(params=GetTaskRequestParams(taskId=task_id))
typed_poll = GetTaskRequest(params=GetTaskRequestParams(task_id=task_id))
poll_request = types.JSONRPCRequest(
jsonrpc="2.0",
id="req-poll",
@@ -479,7 +479,7 @@ async def test_client_task_augmented_sampling(client_streams: ClientTestStreams)
assert status.status == "completed"
# Step 5: Server gets result
typed_result_req = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(taskId=task_id))
typed_result_req = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id=task_id))
result_request = types.JSONRPCRequest(
jsonrpc="2.0",
id="req-result",
@@ -514,13 +514,13 @@ async def test_client_task_augmented_elicitation(client_streams: ClientTestStrea
task_metadata: TaskMetadata,
) -> CreateTaskResult | ErrorData:
task = await store.create_task(task_metadata)
created_task_id[0] = task.taskId
created_task_id[0] = task.task_id
async def do_elicitation() -> None:
# Simulate user providing elicitation response
result = ElicitResult(action="accept", content={"name": "Test User"})
await store.store_result(task.taskId, result)
await store.update_task(task.taskId, status="completed")
await store.store_result(task.task_id, result)
await store.update_task(task.task_id, status="completed")
elicitation_completed.set()
assert background_tg[0] is not None
@@ -531,24 +531,24 @@ async def test_client_task_augmented_elicitation(client_streams: ClientTestStrea
context: RequestContext[ClientSession, None],
params: GetTaskRequestParams,
) -> GetTaskResult | ErrorData:
task = await store.get_task(params.taskId)
assert task is not None, f"Test setup error: task {params.taskId} should exist"
task = await store.get_task(params.task_id)
assert task is not None, f"Test setup error: task {params.task_id} should exist"
return GetTaskResult(
taskId=task.taskId,
task_id=task.task_id,
status=task.status,
statusMessage=task.statusMessage,
createdAt=task.createdAt,
lastUpdatedAt=task.lastUpdatedAt,
status_message=task.status_message,
created_at=task.created_at,
last_updated_at=task.last_updated_at,
ttl=task.ttl,
pollInterval=task.pollInterval,
poll_interval=task.poll_interval,
)
async def get_task_result_handler(
context: RequestContext[ClientSession, None],
params: GetTaskPayloadRequestParams,
) -> GetTaskPayloadResult | ErrorData:
result = await store.get_result(params.taskId)
assert result is not None, f"Test setup error: result for {params.taskId} should exist"
result = await store.get_result(params.task_id)
assert result is not None, f"Test setup error: result for {params.task_id} should exist"
assert isinstance(result, ElicitResult)
return GetTaskPayloadResult(**result.model_dump())
@@ -579,7 +579,7 @@ async def test_client_task_augmented_elicitation(client_streams: ClientTestStrea
typed_request = ElicitRequest(
params=ElicitRequestFormParams(
message="What is your name?",
requestedSchema={"type": "object", "properties": {"name": {"type": "string"}}},
requested_schema={"type": "object", "properties": {"name": {"type": "string"}}},
task=TaskMetadata(ttl=60000),
)
)
@@ -596,14 +596,14 @@ async def test_client_task_augmented_elicitation(client_streams: ClientTestStrea
assert isinstance(response, types.JSONRPCResponse)
task_result = CreateTaskResult.model_validate(response.result)
task_id = task_result.task.taskId
task_id = task_result.task.task_id
assert task_id == created_task_id[0]
# Step 3: Wait for background elicitation
await elicitation_completed.wait()
# Step 4: Server polls task status
typed_poll = GetTaskRequest(params=GetTaskRequestParams(taskId=task_id))
typed_poll = GetTaskRequest(params=GetTaskRequestParams(task_id=task_id))
poll_request = types.JSONRPCRequest(
jsonrpc="2.0",
id="req-poll",
@@ -619,7 +619,7 @@ async def test_client_task_augmented_elicitation(client_streams: ClientTestStrea
assert status.status == "completed"
# Step 5: Server gets result
typed_result_req = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(taskId=task_id))
typed_result_req = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id=task_id))
result_request = types.JSONRPCRequest(
jsonrpc="2.0",
id="req-result",
@@ -661,7 +661,7 @@ async def test_client_returns_error_for_unhandled_task_request(client_streams: C
tg.start_soon(run_client)
await client_ready.wait()
typed_request = GetTaskRequest(params=GetTaskRequestParams(taskId="nonexistent"))
typed_request = GetTaskRequest(params=GetTaskRequestParams(task_id="nonexistent"))
request = types.JSONRPCRequest(
jsonrpc="2.0",
id="req-unhandled",
@@ -700,7 +700,7 @@ async def test_client_returns_error_for_unhandled_task_result_request(client_str
tg.start_soon(run_client)
await client_ready.wait()
typed_request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(taskId="nonexistent"))
typed_request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id="nonexistent"))
request = types.JSONRPCRequest(
jsonrpc="2.0",
id="req-result",
@@ -772,7 +772,7 @@ async def test_client_returns_error_for_unhandled_cancel_task_request(client_str
tg.start_soon(run_client)
await client_ready.wait()
typed_request = CancelTaskRequest(params=CancelTaskRequestParams(taskId="nonexistent"))
typed_request = CancelTaskRequest(params=CancelTaskRequestParams(task_id="nonexistent"))
request = types.JSONRPCRequest(
jsonrpc="2.0",
id="req-cancel",
@@ -813,7 +813,7 @@ async def test_client_returns_error_for_unhandled_task_augmented_sampling(client
typed_request = CreateMessageRequest(
params=CreateMessageRequestParams(
messages=[SamplingMessage(role="user", content=TextContent(type="text", text="Hello"))],
maxTokens=100,
max_tokens=100,
task=TaskMetadata(ttl=60000),
)
)
@@ -859,7 +859,7 @@ async def test_client_returns_error_for_unhandled_task_augmented_elicitation(
typed_request = ElicitRequest(
params=ElicitRequestFormParams(
message="What is your name?",
requestedSchema={"type": "object", "properties": {"name": {"type": "string"}}},
requested_schema={"type": "object", "properties": {"name": {"type": "string"}}},
task=TaskMetadata(ttl=60000),
)
)
@@ -20,13 +20,13 @@ def make_task_result(
"""Create GetTaskResult with sensible defaults."""
now = datetime.now(timezone.utc)
return GetTaskResult(
taskId=task_id,
task_id=task_id,
status=status,
statusMessage=status_message,
createdAt=now,
lastUpdatedAt=now,
status_message=status_message,
created_at=now,
last_updated_at=now,
ttl=60000,
pollInterval=poll_interval,
poll_interval=poll_interval,
)
@@ -117,5 +117,5 @@ async def test_poll_task_yields_full_result(features: ExperimentalClientFeatures
assert len(results) == 1
assert results[0].status == "completed"
assert results[0].statusMessage == "All done!"
assert results[0].taskId == "test-task"
assert results[0].status_message == "All done!"
assert results[0].task_id == "test-task"
+39 -39
View File
@@ -58,7 +58,7 @@ async def test_session_experimental_get_task() -> None:
@server.list_tools()
async def list_tools():
return [Tool(name="test_tool", description="Test", inputSchema={"type": "object"})]
return [Tool(name="test_tool", description="Test", input_schema={"type": "object"})]
@server.call_tool()
async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent] | CreateTaskResult:
@@ -70,10 +70,10 @@ async def test_session_experimental_get_task() -> None:
task = await app.store.create_task(task_metadata)
done_event = Event()
app.task_done_events[task.taskId] = done_event
app.task_done_events[task.task_id] = done_event
async def do_work():
async with task_execution(task.taskId, app.store) as task_ctx:
async with task_execution(task.task_id, app.store) as task_ctx:
await task_ctx.complete(CallToolResult(content=[TextContent(type="text", text="Done")]))
done_event.set()
@@ -85,16 +85,16 @@ async def test_session_experimental_get_task() -> None:
@server.experimental.get_task()
async def handle_get_task(request: GetTaskRequest) -> GetTaskResult:
app = server.request_context.lifespan_context
task = await app.store.get_task(request.params.taskId)
assert task is not None, f"Test setup error: task {request.params.taskId} should exist"
task = await app.store.get_task(request.params.task_id)
assert task is not None, f"Test setup error: task {request.params.task_id} should exist"
return GetTaskResult(
taskId=task.taskId,
task_id=task.task_id,
status=task.status,
statusMessage=task.statusMessage,
createdAt=task.createdAt,
lastUpdatedAt=task.lastUpdatedAt,
status_message=task.status_message,
created_at=task.created_at,
last_updated_at=task.last_updated_at,
ttl=task.ttl,
pollInterval=task.pollInterval,
poll_interval=task.poll_interval,
)
# Set up streams
@@ -145,7 +145,7 @@ async def test_session_experimental_get_task() -> None:
),
CreateTaskResult,
)
task_id = create_result.task.taskId
task_id = create_result.task.task_id
# Wait for task to complete
await app_context.task_done_events[task_id].wait()
@@ -153,7 +153,7 @@ async def test_session_experimental_get_task() -> None:
# Use session.experimental to get task status
task_status = await client_session.experimental.get_task(task_id)
assert task_status.taskId == task_id
assert task_status.task_id == task_id
assert task_status.status == "completed"
tg.cancel_scope.cancel()
@@ -167,7 +167,7 @@ async def test_session_experimental_get_task_result() -> None:
@server.list_tools()
async def list_tools():
return [Tool(name="test_tool", description="Test", inputSchema={"type": "object"})]
return [Tool(name="test_tool", description="Test", input_schema={"type": "object"})]
@server.call_tool()
async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent] | CreateTaskResult:
@@ -179,10 +179,10 @@ async def test_session_experimental_get_task_result() -> None:
task = await app.store.create_task(task_metadata)
done_event = Event()
app.task_done_events[task.taskId] = done_event
app.task_done_events[task.task_id] = done_event
async def do_work():
async with task_execution(task.taskId, app.store) as task_ctx:
async with task_execution(task.task_id, app.store) as task_ctx:
await task_ctx.complete(
CallToolResult(content=[TextContent(type="text", text="Task result content")])
)
@@ -198,8 +198,8 @@ async def test_session_experimental_get_task_result() -> None:
request: GetTaskPayloadRequest,
) -> GetTaskPayloadResult:
app = server.request_context.lifespan_context
result = await app.store.get_result(request.params.taskId)
assert result is not None, f"Test setup error: result for {request.params.taskId} should exist"
result = await app.store.get_result(request.params.task_id)
assert result is not None, f"Test setup error: result for {request.params.task_id} should exist"
assert isinstance(result, CallToolResult)
return GetTaskPayloadResult(**result.model_dump())
@@ -251,7 +251,7 @@ async def test_session_experimental_get_task_result() -> None:
),
CreateTaskResult,
)
task_id = create_result.task.taskId
task_id = create_result.task.task_id
# Wait for task to complete
await app_context.task_done_events[task_id].wait()
@@ -275,7 +275,7 @@ async def test_session_experimental_list_tasks() -> None:
@server.list_tools()
async def list_tools():
return [Tool(name="test_tool", description="Test", inputSchema={"type": "object"})]
return [Tool(name="test_tool", description="Test", input_schema={"type": "object"})]
@server.call_tool()
async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent] | CreateTaskResult:
@@ -287,10 +287,10 @@ async def test_session_experimental_list_tasks() -> None:
task = await app.store.create_task(task_metadata)
done_event = Event()
app.task_done_events[task.taskId] = done_event
app.task_done_events[task.task_id] = done_event
async def do_work():
async with task_execution(task.taskId, app.store) as task_ctx:
async with task_execution(task.task_id, app.store) as task_ctx:
await task_ctx.complete(CallToolResult(content=[TextContent(type="text", text="Done")]))
done_event.set()
@@ -303,7 +303,7 @@ async def test_session_experimental_list_tasks() -> None:
async def handle_list_tasks(request: ListTasksRequest) -> ListTasksResult:
app = server.request_context.lifespan_context
tasks_list, next_cursor = await app.store.list_tasks(cursor=request.params.cursor if request.params else None)
return ListTasksResult(tasks=tasks_list, nextCursor=next_cursor)
return ListTasksResult(tasks=tasks_list, next_cursor=next_cursor)
# Set up streams
server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[SessionMessage](10)
@@ -354,7 +354,7 @@ async def test_session_experimental_list_tasks() -> None:
),
CreateTaskResult,
)
await app_context.task_done_events[create_result.task.taskId].wait()
await app_context.task_done_events[create_result.task.task_id].wait()
# Use TaskClient to list tasks
list_result = await client_session.experimental.list_tasks()
@@ -372,7 +372,7 @@ async def test_session_experimental_cancel_task() -> None:
@server.list_tools()
async def list_tools():
return [Tool(name="test_tool", description="Test", inputSchema={"type": "object"})]
return [Tool(name="test_tool", description="Test", input_schema={"type": "object"})]
@server.call_tool()
async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent] | CreateTaskResult:
@@ -390,32 +390,32 @@ async def test_session_experimental_cancel_task() -> None:
@server.experimental.get_task()
async def handle_get_task(request: GetTaskRequest) -> GetTaskResult:
app = server.request_context.lifespan_context
task = await app.store.get_task(request.params.taskId)
assert task is not None, f"Test setup error: task {request.params.taskId} should exist"
task = await app.store.get_task(request.params.task_id)
assert task is not None, f"Test setup error: task {request.params.task_id} should exist"
return GetTaskResult(
taskId=task.taskId,
task_id=task.task_id,
status=task.status,
statusMessage=task.statusMessage,
createdAt=task.createdAt,
lastUpdatedAt=task.lastUpdatedAt,
status_message=task.status_message,
created_at=task.created_at,
last_updated_at=task.last_updated_at,
ttl=task.ttl,
pollInterval=task.pollInterval,
poll_interval=task.poll_interval,
)
@server.experimental.cancel_task()
async def handle_cancel_task(request: CancelTaskRequest) -> CancelTaskResult:
app = server.request_context.lifespan_context
task = await app.store.get_task(request.params.taskId)
assert task is not None, f"Test setup error: task {request.params.taskId} should exist"
await app.store.update_task(request.params.taskId, status="cancelled")
task = await app.store.get_task(request.params.task_id)
assert task is not None, f"Test setup error: task {request.params.task_id} should exist"
await app.store.update_task(request.params.task_id, status="cancelled")
# CancelTaskResult extends Task, so we need to return the updated task info
updated_task = await app.store.get_task(request.params.taskId)
updated_task = await app.store.get_task(request.params.task_id)
assert updated_task is not None
return CancelTaskResult(
taskId=updated_task.taskId,
task_id=updated_task.task_id,
status=updated_task.status,
createdAt=updated_task.createdAt,
lastUpdatedAt=updated_task.lastUpdatedAt,
created_at=updated_task.created_at,
last_updated_at=updated_task.last_updated_at,
ttl=updated_task.ttl,
)
@@ -467,7 +467,7 @@ async def test_session_experimental_cancel_task() -> None:
),
CreateTaskResult,
)
task_id = create_result.task.taskId
task_id = create_result.task.task_id
# Verify task is working
status_before = await client_session.experimental.get_task(task_id)
+12 -12
View File
@@ -15,8 +15,8 @@ async def test_task_context_properties() -> None:
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
ctx = TaskContext(task, store)
assert ctx.task_id == task.taskId
assert ctx.task.taskId == task.taskId
assert ctx.task_id == task.task_id
assert ctx.task.task_id == task.task_id
assert ctx.task.status == "working"
assert ctx.is_cancelled is False
@@ -33,9 +33,9 @@ async def test_task_context_update_status() -> None:
await ctx.update_status("Processing step 1...")
# Check status message was updated
updated = await store.get_task(task.taskId)
updated = await store.get_task(task.task_id)
assert updated is not None
assert updated.statusMessage == "Processing step 1..."
assert updated.status_message == "Processing step 1..."
store.cleanup()
@@ -51,12 +51,12 @@ async def test_task_context_complete() -> None:
await ctx.complete(result)
# Check task status
updated = await store.get_task(task.taskId)
updated = await store.get_task(task.task_id)
assert updated is not None
assert updated.status == "completed"
# Check result is stored
stored_result = await store.get_result(task.taskId)
stored_result = await store.get_result(task.task_id)
assert stored_result is not None
store.cleanup()
@@ -72,10 +72,10 @@ async def test_task_context_fail() -> None:
await ctx.fail("Something went wrong!")
# Check task status
updated = await store.get_task(task.taskId)
updated = await store.get_task(task.task_id)
assert updated is not None
assert updated.status == "failed"
assert updated.statusMessage == "Something went wrong!"
assert updated.status_message == "Something went wrong!"
store.cleanup()
@@ -101,13 +101,13 @@ def test_create_task_state_generates_id() -> None:
task1 = create_task_state(TaskMetadata(ttl=60000))
task2 = create_task_state(TaskMetadata(ttl=60000))
assert task1.taskId != task2.taskId
assert task1.task_id != task2.task_id
def test_create_task_state_uses_provided_id() -> None:
"""create_task_state uses the provided task ID."""
task = create_task_state(TaskMetadata(ttl=60000), task_id="my-task-123")
assert task.taskId == "my-task-123"
assert task.task_id == "my-task-123"
def test_create_task_state_null_ttl() -> None:
@@ -119,7 +119,7 @@ def test_create_task_state_null_ttl() -> None:
def test_create_task_state_has_created_at() -> None:
"""create_task_state sets createdAt timestamp."""
task = create_task_state(TaskMetadata(ttl=60000))
assert task.createdAt is not None
assert task.created_at is not None
@pytest.mark.anyio
@@ -148,7 +148,7 @@ async def test_task_execution_auto_fails_on_exception() -> None:
failed_task = await store.get_task("exec-fail-1")
assert failed_task is not None
assert failed_task.status == "failed"
assert "Oops!" in (failed_task.statusMessage or "")
assert "Oops!" in (failed_task.status_message or "")
store.cleanup()
@@ -81,11 +81,11 @@ async def test_task_lifecycle_with_task_execution() -> None:
Tool(
name="process_data",
description="Process data asynchronously",
inputSchema={
input_schema={
"type": "object",
"properties": {"input": {"type": "string"}},
},
execution=ToolExecution(taskSupport=TASK_REQUIRED),
execution=ToolExecution(task_support=TASK_REQUIRED),
)
]
@@ -101,11 +101,11 @@ async def test_task_lifecycle_with_task_execution() -> None:
# 2. Create event to signal completion (for testing)
done_event = Event()
app.task_done_events[task.taskId] = done_event
app.task_done_events[task.task_id] = done_event
# 3. Define work function using task_execution for safety
async def do_work():
async with task_execution(task.taskId, app.store) as task_ctx:
async with task_execution(task.task_id, app.store) as task_ctx:
await task_ctx.update_status("Processing input...")
# Simulate work
input_value = arguments.get("input", "")
@@ -126,16 +126,16 @@ async def test_task_lifecycle_with_task_execution() -> None:
@server.experimental.get_task()
async def handle_get_task(request: GetTaskRequest) -> GetTaskResult:
app = server.request_context.lifespan_context
task = await app.store.get_task(request.params.taskId)
assert task is not None, f"Test setup error: task {request.params.taskId} should exist"
task = await app.store.get_task(request.params.task_id)
assert task is not None, f"Test setup error: task {request.params.task_id} should exist"
return GetTaskResult(
taskId=task.taskId,
task_id=task.task_id,
status=task.status,
statusMessage=task.statusMessage,
createdAt=task.createdAt,
lastUpdatedAt=task.lastUpdatedAt,
status_message=task.status_message,
created_at=task.created_at,
last_updated_at=task.last_updated_at,
ttl=task.ttl,
pollInterval=task.pollInterval,
poll_interval=task.poll_interval,
)
@server.experimental.get_task_result()
@@ -143,8 +143,8 @@ async def test_task_lifecycle_with_task_execution() -> None:
request: GetTaskPayloadRequest,
) -> GetTaskPayloadResult:
app = server.request_context.lifespan_context
result = await app.store.get_result(request.params.taskId)
assert result is not None, f"Test setup error: result for {request.params.taskId} should exist"
result = await app.store.get_result(request.params.task_id)
assert result is not None, f"Test setup error: result for {request.params.task_id} should exist"
assert isinstance(result, CallToolResult)
# Return as GetTaskPayloadResult (which accepts extra fields)
return GetTaskPayloadResult(**result.model_dump())
@@ -205,22 +205,22 @@ async def test_task_lifecycle_with_task_execution() -> None:
assert isinstance(create_result, CreateTaskResult)
assert create_result.task.status == "working"
task_id = create_result.task.taskId
task_id = create_result.task.task_id
# === Step 2: Wait for task to complete ===
await app_context.task_done_events[task_id].wait()
task_status = await client_session.send_request(
ClientRequest(GetTaskRequest(params=GetTaskRequestParams(taskId=task_id))),
ClientRequest(GetTaskRequest(params=GetTaskRequestParams(task_id=task_id))),
GetTaskResult,
)
assert task_status.taskId == task_id
assert task_status.task_id == task_id
assert task_status.status == "completed"
# === Step 3: Retrieve the actual result ===
task_result = await client_session.send_request(
ClientRequest(GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(taskId=task_id))),
ClientRequest(GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id=task_id))),
CallToolResult,
)
@@ -245,7 +245,7 @@ async def test_task_auto_fails_on_exception() -> None:
Tool(
name="failing_task",
description="A task that fails",
inputSchema={"type": "object", "properties": {}},
input_schema={"type": "object", "properties": {}},
)
]
@@ -260,10 +260,10 @@ async def test_task_auto_fails_on_exception() -> None:
# Create event to signal completion (for testing)
done_event = Event()
app.task_done_events[task.taskId] = done_event
app.task_done_events[task.task_id] = done_event
async def do_failing_work():
async with task_execution(task.taskId, app.store) as task_ctx:
async with task_execution(task.task_id, app.store) as task_ctx:
await task_ctx.update_status("About to fail...")
raise RuntimeError("Something went wrong!")
# Note: complete() is never called, but task_execution
@@ -279,16 +279,16 @@ async def test_task_auto_fails_on_exception() -> None:
@server.experimental.get_task()
async def handle_get_task(request: GetTaskRequest) -> GetTaskResult:
app = server.request_context.lifespan_context
task = await app.store.get_task(request.params.taskId)
assert task is not None, f"Test setup error: task {request.params.taskId} should exist"
task = await app.store.get_task(request.params.task_id)
assert task is not None, f"Test setup error: task {request.params.task_id} should exist"
return GetTaskResult(
taskId=task.taskId,
task_id=task.task_id,
status=task.status,
statusMessage=task.statusMessage,
createdAt=task.createdAt,
lastUpdatedAt=task.lastUpdatedAt,
status_message=task.status_message,
created_at=task.created_at,
last_updated_at=task.last_updated_at,
ttl=task.ttl,
pollInterval=task.pollInterval,
poll_interval=task.poll_interval,
)
# Set up streams
@@ -340,18 +340,18 @@ async def test_task_auto_fails_on_exception() -> None:
CreateTaskResult,
)
task_id = create_result.task.taskId
task_id = create_result.task.task_id
# Wait for task to complete (even though it fails)
await app_context.task_done_events[task_id].wait()
# Check that task was auto-failed
task_status = await client_session.send_request(
ClientRequest(GetTaskRequest(params=GetTaskRequestParams(taskId=task_id))),
ClientRequest(GetTaskRequest(params=GetTaskRequestParams(task_id=task_id))),
GetTaskResult,
)
assert task_status.status == "failed"
assert task_status.statusMessage == "Something went wrong!"
assert task_status.status_message == "Something went wrong!"
tg.cancel_scope.cancel()
@@ -69,8 +69,8 @@ async def test_run_task_basic_flow() -> None:
Tool(
name="simple_task",
description="A simple task",
inputSchema={"type": "object", "properties": {"input": {"type": "string"}}},
execution=ToolExecution(taskSupport=TASK_REQUIRED),
input_schema={"type": "object", "properties": {"input": {"type": "string"}}},
execution=ToolExecution(task_support=TASK_REQUIRED),
)
]
@@ -119,7 +119,7 @@ async def test_run_task_basic_flow() -> None:
)
# Should get CreateTaskResult
task_id = result.task.taskId
task_id = result.task.task_id
assert result.task.status == "working"
# Wait for work to complete
@@ -157,8 +157,8 @@ async def test_run_task_auto_fails_on_exception() -> None:
Tool(
name="failing_task",
description="A task that fails",
inputSchema={"type": "object"},
execution=ToolExecution(taskSupport=TASK_REQUIRED),
input_schema={"type": "object"},
execution=ToolExecution(task_support=TASK_REQUIRED),
)
]
@@ -188,7 +188,7 @@ async def test_run_task_auto_fails_on_exception() -> None:
await client_session.initialize()
result = await client_session.experimental.call_tool_as_task("failing_task", {})
task_id = result.task.taskId
task_id = result.task.task_id
# Wait for work to fail
with anyio.fail_after(5):
@@ -201,7 +201,7 @@ async def test_run_task_auto_fails_on_exception() -> None:
if task_status.status == "failed": # pragma: no branch
break
assert "Something went wrong" in (task_status.statusMessage or "")
assert "Something went wrong" in (task_status.status_message or "")
async with anyio.create_task_group() as tg:
tg.start_soon(run_server)
@@ -363,8 +363,8 @@ async def test_run_task_with_model_immediate_response() -> None:
Tool(
name="task_with_immediate",
description="A task with immediate response",
inputSchema={"type": "object"},
execution=ToolExecution(taskSupport=TASK_REQUIRED),
input_schema={"type": "object"},
execution=ToolExecution(task_support=TASK_REQUIRED),
)
]
@@ -422,8 +422,8 @@ async def test_run_task_doesnt_complete_if_already_terminal() -> None:
Tool(
name="manual_complete_task",
description="A task that manually completes",
inputSchema={"type": "object"},
execution=ToolExecution(taskSupport=TASK_REQUIRED),
input_schema={"type": "object"},
execution=ToolExecution(task_support=TASK_REQUIRED),
)
]
@@ -457,7 +457,7 @@ async def test_run_task_doesnt_complete_if_already_terminal() -> None:
await client_session.initialize()
result = await client_session.experimental.call_tool_as_task("manual_complete_task", {})
task_id = result.task.taskId
task_id = result.task.task_id
with anyio.fail_after(5):
await work_completed.wait()
@@ -488,8 +488,8 @@ async def test_run_task_doesnt_fail_if_already_terminal() -> None:
Tool(
name="manual_cancel_task",
description="A task that manually cancels then raises",
inputSchema={"type": "object"},
execution=ToolExecution(taskSupport=TASK_REQUIRED),
input_schema={"type": "object"},
execution=ToolExecution(task_support=TASK_REQUIRED),
)
]
@@ -522,7 +522,7 @@ async def test_run_task_doesnt_fail_if_already_terminal() -> None:
await client_session.initialize()
result = await client_session.experimental.call_tool_as_task("manual_cancel_task", {})
task_id = result.task.taskId
task_id = result.task.task_id
with anyio.fail_after(5):
await work_completed.wait()
@@ -535,7 +535,7 @@ async def test_run_task_doesnt_fail_if_already_terminal() -> None:
break
# Task should still be failed (from manual fail, not auto-fail from exception)
assert status.statusMessage == "Manually failed" # Not "This error should not change status"
assert status.status_message == "Manually failed" # Not "This error should not change status"
async with anyio.create_task_group() as tg:
tg.start_soon(run_server)
+47 -45
View File
@@ -64,20 +64,20 @@ async def test_list_tasks_handler() -> None:
now = datetime.now(timezone.utc)
test_tasks = [
Task(
taskId="task-1",
task_id="task-1",
status="working",
createdAt=now,
lastUpdatedAt=now,
created_at=now,
last_updated_at=now,
ttl=60000,
pollInterval=1000,
poll_interval=1000,
),
Task(
taskId="task-2",
task_id="task-2",
status="completed",
createdAt=now,
lastUpdatedAt=now,
created_at=now,
last_updated_at=now,
ttl=60000,
pollInterval=1000,
poll_interval=1000,
),
]
@@ -92,8 +92,8 @@ async def test_list_tasks_handler() -> None:
assert isinstance(result, ServerResult)
assert isinstance(result.root, ListTasksResult)
assert len(result.root.tasks) == 2
assert result.root.tasks[0].taskId == "task-1"
assert result.root.tasks[1].taskId == "task-2"
assert result.root.tasks[0].task_id == "task-1"
assert result.root.tasks[1].task_id == "task-2"
@pytest.mark.anyio
@@ -105,24 +105,24 @@ async def test_get_task_handler() -> None:
async def handle_get_task(request: GetTaskRequest) -> GetTaskResult:
now = datetime.now(timezone.utc)
return GetTaskResult(
taskId=request.params.taskId,
task_id=request.params.task_id,
status="working",
createdAt=now,
lastUpdatedAt=now,
created_at=now,
last_updated_at=now,
ttl=60000,
pollInterval=1000,
poll_interval=1000,
)
handler = server.request_handlers[GetTaskRequest]
request = GetTaskRequest(
method="tasks/get",
params=GetTaskRequestParams(taskId="test-task-123"),
params=GetTaskRequestParams(task_id="test-task-123"),
)
result = await handler(request)
assert isinstance(result, ServerResult)
assert isinstance(result.root, GetTaskResult)
assert result.root.taskId == "test-task-123"
assert result.root.task_id == "test-task-123"
assert result.root.status == "working"
@@ -138,7 +138,7 @@ async def test_get_task_result_handler() -> None:
handler = server.request_handlers[GetTaskPayloadRequest]
request = GetTaskPayloadRequest(
method="tasks/result",
params=GetTaskPayloadRequestParams(taskId="test-task-123"),
params=GetTaskPayloadRequestParams(task_id="test-task-123"),
)
result = await handler(request)
@@ -155,23 +155,23 @@ async def test_cancel_task_handler() -> None:
async def handle_cancel_task(request: CancelTaskRequest) -> CancelTaskResult:
now = datetime.now(timezone.utc)
return CancelTaskResult(
taskId=request.params.taskId,
task_id=request.params.task_id,
status="cancelled",
createdAt=now,
lastUpdatedAt=now,
created_at=now,
last_updated_at=now,
ttl=60000,
)
handler = server.request_handlers[CancelTaskRequest]
request = CancelTaskRequest(
method="tasks/cancel",
params=CancelTaskRequestParams(taskId="test-task-123"),
params=CancelTaskRequestParams(task_id="test-task-123"),
)
result = await handler(request)
assert isinstance(result, ServerResult)
assert isinstance(result.root, CancelTaskResult)
assert result.root.taskId == "test-task-123"
assert result.root.task_id == "test-task-123"
assert result.root.status == "cancelled"
@@ -232,20 +232,20 @@ async def test_tool_with_task_execution_metadata() -> None:
Tool(
name="quick_tool",
description="Fast tool",
inputSchema={"type": "object", "properties": {}},
execution=ToolExecution(taskSupport=TASK_FORBIDDEN),
input_schema={"type": "object", "properties": {}},
execution=ToolExecution(task_support=TASK_FORBIDDEN),
),
Tool(
name="long_tool",
description="Long running tool",
inputSchema={"type": "object", "properties": {}},
execution=ToolExecution(taskSupport=TASK_REQUIRED),
input_schema={"type": "object", "properties": {}},
execution=ToolExecution(task_support=TASK_REQUIRED),
),
Tool(
name="flexible_tool",
description="Can be either",
inputSchema={"type": "object", "properties": {}},
execution=ToolExecution(taskSupport=TASK_OPTIONAL),
input_schema={"type": "object", "properties": {}},
execution=ToolExecution(task_support=TASK_OPTIONAL),
),
]
@@ -258,11 +258,11 @@ async def test_tool_with_task_execution_metadata() -> None:
tools = result.root.tools
assert tools[0].execution is not None
assert tools[0].execution.taskSupport == TASK_FORBIDDEN
assert tools[0].execution.task_support == TASK_FORBIDDEN
assert tools[1].execution is not None
assert tools[1].execution.taskSupport == TASK_REQUIRED
assert tools[1].execution.task_support == TASK_REQUIRED
assert tools[2].execution is not None
assert tools[2].execution.taskSupport == TASK_OPTIONAL
assert tools[2].execution.task_support == TASK_OPTIONAL
@pytest.mark.anyio
@@ -277,8 +277,8 @@ async def test_task_metadata_in_call_tool_request() -> None:
Tool(
name="long_task",
description="A long running task",
inputSchema={"type": "object", "properties": {}},
execution=ToolExecution(taskSupport="optional"),
input_schema={"type": "object", "properties": {}},
execution=ToolExecution(task_support="optional"),
)
]
@@ -361,7 +361,7 @@ async def test_task_metadata_is_task_property() -> None:
Tool(
name="test_tool",
description="Test tool",
inputSchema={"type": "object", "properties": {}},
input_schema={"type": "object", "properties": {}},
)
]
@@ -513,33 +513,35 @@ async def test_default_task_handlers_via_enable_tasks() -> None:
ListTasksResult,
)
assert len(list_result.tasks) == 1
assert list_result.tasks[0].taskId == task.taskId
assert list_result.tasks[0].task_id == task.task_id
# Test get_task (default handler - found)
get_result = await client_session.send_request(
ClientRequest(GetTaskRequest(params=GetTaskRequestParams(taskId=task.taskId))),
ClientRequest(GetTaskRequest(params=GetTaskRequestParams(task_id=task.task_id))),
GetTaskResult,
)
assert get_result.taskId == task.taskId
assert get_result.task_id == task.task_id
assert get_result.status == "working"
# Test get_task (default handler - not found path)
with pytest.raises(McpError, match="not found"):
await client_session.send_request(
ClientRequest(GetTaskRequest(params=GetTaskRequestParams(taskId="nonexistent-task"))),
ClientRequest(GetTaskRequest(params=GetTaskRequestParams(task_id="nonexistent-task"))),
GetTaskResult,
)
# Create a completed task to test get_task_result
completed_task = await store.create_task(TaskMetadata(ttl=60000))
await store.store_result(
completed_task.taskId, CallToolResult(content=[TextContent(type="text", text="Test result")])
completed_task.task_id, CallToolResult(content=[TextContent(type="text", text="Test result")])
)
await store.update_task(completed_task.taskId, status="completed")
await store.update_task(completed_task.task_id, status="completed")
# Test get_task_result (default handler)
payload_result = await client_session.send_request(
ClientRequest(GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(taskId=completed_task.taskId))),
ClientRequest(
GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id=completed_task.task_id))
),
GetTaskPayloadResult,
)
# The result should have the related-task metadata
@@ -548,10 +550,10 @@ async def test_default_task_handlers_via_enable_tasks() -> None:
# Test cancel_task (default handler)
cancel_result = await client_session.send_request(
ClientRequest(CancelTaskRequest(params=CancelTaskRequestParams(taskId=task.taskId))),
ClientRequest(CancelTaskRequest(params=CancelTaskRequestParams(task_id=task.task_id))),
CancelTaskResult,
)
assert cancel_result.taskId == task.taskId
assert cancel_result.task_id == task.task_id
assert cancel_result.status == "cancelled"
tg.cancel_scope.cancel()
@@ -576,7 +578,7 @@ async def test_build_elicit_form_request() -> None:
# Test without task_id
request = server_session._build_elicit_form_request(
message="Test message",
requestedSchema={"type": "object", "properties": {"answer": {"type": "string"}}},
requested_schema={"type": "object", "properties": {"answer": {"type": "string"}}},
)
assert request.method == "elicitation/create"
assert request.params is not None
@@ -585,7 +587,7 @@ async def test_build_elicit_form_request() -> None:
# Test with related_task_id (adds related-task metadata)
request_with_task = server_session._build_elicit_form_request(
message="Task message",
requestedSchema={"type": "object"},
requested_schema={"type": "object"},
related_task_id="test-task-123",
)
assert request_with_task.method == "elicitation/create"
@@ -45,7 +45,7 @@ async def test_server_task_context_properties() -> None:
)
assert ctx.task_id == "test-123"
assert ctx.task.taskId == "test-123"
assert ctx.task.task_id == "test-123"
assert ctx.is_cancelled is False
store.cleanup()
@@ -181,7 +181,7 @@ async def test_elicit_raises_when_client_lacks_capability() -> None:
)
with pytest.raises(McpError) as exc_info:
await ctx.elicit(message="Test?", requestedSchema={"type": "object"})
await ctx.elicit(message="Test?", requested_schema={"type": "object"})
assert "elicitation capability" in exc_info.value.error.message
mock_session.check_client_capability.assert_called_once()
@@ -232,7 +232,7 @@ async def test_elicit_raises_without_handler() -> None:
)
with pytest.raises(RuntimeError, match="handler is required"):
await ctx.elicit(message="Test?", requestedSchema={"type": "object"})
await ctx.elicit(message="Test?", requested_schema={"type": "object"})
store.cleanup()
@@ -320,22 +320,22 @@ async def test_elicit_queues_request_and_waits_for_response() -> None:
nonlocal elicit_result
elicit_result = await ctx.elicit(
message="Test?",
requestedSchema={"type": "object"},
requested_schema={"type": "object"},
)
async with anyio.create_task_group() as tg:
tg.start_soon(run_elicit)
# Wait for request to be queued
await queue.wait_for_message(task.taskId)
await queue.wait_for_message(task.task_id)
# Verify task is in input_required status
updated_task = await store.get_task(task.taskId)
updated_task = await store.get_task(task.task_id)
assert updated_task is not None
assert updated_task.status == "input_required"
# Dequeue and simulate response
msg = await queue.dequeue(task.taskId)
msg = await queue.dequeue(task.task_id)
assert msg is not None
assert msg.resolver is not None
@@ -348,7 +348,7 @@ async def test_elicit_queues_request_and_waits_for_response() -> None:
assert elicit_result.content == {"name": "Alice"}
# Verify task is back to working
final_task = await store.get_task(task.taskId)
final_task = await store.get_task(task.task_id)
assert final_task is not None
assert final_task.status == "working"
@@ -396,15 +396,15 @@ async def test_elicit_url_queues_request_and_waits_for_response() -> None:
tg.start_soon(run_elicit_url)
# Wait for request to be queued
await queue.wait_for_message(task.taskId)
await queue.wait_for_message(task.task_id)
# Verify task is in input_required status
updated_task = await store.get_task(task.taskId)
updated_task = await store.get_task(task.task_id)
assert updated_task is not None
assert updated_task.status == "input_required"
# Dequeue and simulate response
msg = await queue.dequeue(task.taskId)
msg = await queue.dequeue(task.task_id)
assert msg is not None
assert msg.resolver is not None
@@ -416,7 +416,7 @@ async def test_elicit_url_queues_request_and_waits_for_response() -> None:
assert elicit_result.action == "accept"
# Verify task is back to working
final_task = await store.get_task(task.taskId)
final_task = await store.get_task(task.task_id)
assert final_task is not None
assert final_task.status == "working"
@@ -463,15 +463,15 @@ async def test_create_message_queues_request_and_waits_for_response() -> None:
tg.start_soon(run_sampling)
# Wait for request to be queued
await queue.wait_for_message(task.taskId)
await queue.wait_for_message(task.task_id)
# Verify task is in input_required status
updated_task = await store.get_task(task.taskId)
updated_task = await store.get_task(task.task_id)
assert updated_task is not None
assert updated_task.status == "input_required"
# Dequeue and simulate response
msg = await queue.dequeue(task.taskId)
msg = await queue.dequeue(task.task_id)
assert msg is not None
assert msg.resolver is not None
@@ -491,7 +491,7 @@ async def test_create_message_queues_request_and_waits_for_response() -> None:
assert sampling_result.model == "test-model"
# Verify task is back to working
final_task = await store.get_task(task.taskId)
final_task = await store.get_task(task.task_id)
assert final_task is not None
assert final_task.status == "working"
@@ -534,7 +534,7 @@ async def test_elicit_restores_status_on_cancellation() -> None:
try:
await ctx.elicit(
message="Test?",
requestedSchema={"type": "object"},
requested_schema={"type": "object"},
)
except anyio.get_cancelled_exc_class():
cancelled_error_raised = True
@@ -543,15 +543,15 @@ async def test_elicit_restores_status_on_cancellation() -> None:
tg.start_soon(do_elicit)
# Wait for request to be queued
await queue.wait_for_message(task.taskId)
await queue.wait_for_message(task.task_id)
# Verify task is in input_required status
updated_task = await store.get_task(task.taskId)
updated_task = await store.get_task(task.task_id)
assert updated_task is not None
assert updated_task.status == "input_required"
# Get the queued message and set cancellation exception on its resolver
msg = await queue.dequeue(task.taskId)
msg = await queue.dequeue(task.task_id)
assert msg is not None
assert msg.resolver is not None
@@ -559,7 +559,7 @@ async def test_elicit_restores_status_on_cancellation() -> None:
msg.resolver.set_exception(asyncio.CancelledError())
# Verify task is back to working after cancellation
final_task = await store.get_task(task.taskId)
final_task = await store.get_task(task.task_id)
assert final_task is not None
assert final_task.status == "working"
assert cancelled_error_raised
@@ -612,15 +612,15 @@ async def test_create_message_restores_status_on_cancellation() -> None:
tg.start_soon(do_sampling)
# Wait for request to be queued
await queue.wait_for_message(task.taskId)
await queue.wait_for_message(task.task_id)
# Verify task is in input_required status
updated_task = await store.get_task(task.taskId)
updated_task = await store.get_task(task.task_id)
assert updated_task is not None
assert updated_task.status == "input_required"
# Get the queued message and set cancellation exception on its resolver
msg = await queue.dequeue(task.taskId)
msg = await queue.dequeue(task.task_id)
assert msg is not None
assert msg.resolver is not None
@@ -628,7 +628,7 @@ async def test_create_message_restores_status_on_cancellation() -> None:
msg.resolver.set_exception(asyncio.CancelledError())
# Verify task is back to working after cancellation
final_task = await store.get_task(task.taskId)
final_task = await store.get_task(task.task_id)
assert final_task is not None
assert final_task.status == "working"
assert cancelled_error_raised
@@ -646,7 +646,7 @@ async def test_elicit_as_task_raises_without_handler() -> None:
# Create mock session with proper client capabilities
mock_session = Mock()
mock_session.client_params = InitializeRequestParams(
protocolVersion="2025-01-01",
protocol_version="2025-01-01",
capabilities=ClientCapabilities(
tasks=ClientTasksCapability(
requests=ClientTasksRequestsCapability(
@@ -654,7 +654,7 @@ async def test_elicit_as_task_raises_without_handler() -> None:
)
)
),
clientInfo=Implementation(name="test", version="1.0"),
client_info=Implementation(name="test", version="1.0"),
)
ctx = ServerTaskContext(
@@ -666,7 +666,7 @@ async def test_elicit_as_task_raises_without_handler() -> None:
)
with pytest.raises(RuntimeError, match="handler is required for elicit_as_task"):
await ctx.elicit_as_task(message="Test?", requestedSchema={"type": "object"})
await ctx.elicit_as_task(message="Test?", requested_schema={"type": "object"})
store.cleanup()
@@ -681,15 +681,15 @@ async def test_create_message_as_task_raises_without_handler() -> None:
# Create mock session with proper client capabilities
mock_session = Mock()
mock_session.client_params = InitializeRequestParams(
protocolVersion="2025-01-01",
protocol_version="2025-01-01",
capabilities=ClientCapabilities(
tasks=ClientTasksCapability(
requests=ClientTasksRequestsCapability(
sampling=TasksSamplingCapability(createMessage=TasksCreateMessageCapability())
sampling=TasksSamplingCapability(create_message=TasksCreateMessageCapability())
)
)
),
clientInfo=Implementation(name="test", version="1.0"),
client_info=Implementation(name="test", version="1.0"),
)
ctx = ServerTaskContext(
+44 -44
View File
@@ -24,13 +24,13 @@ async def test_create_and_get(store: InMemoryTaskStore) -> None:
"""Test InMemoryTaskStore create and get operations."""
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
assert task.taskId is not None
assert task.task_id is not None
assert task.status == "working"
assert task.ttl == 60000
retrieved = await store.get_task(task.taskId)
retrieved = await store.get_task(task.task_id)
assert retrieved is not None
assert retrieved.taskId == task.taskId
assert retrieved.task_id == task.task_id
assert retrieved.status == "working"
@@ -42,12 +42,12 @@ async def test_create_with_custom_id(store: InMemoryTaskStore) -> None:
task_id="my-custom-id",
)
assert task.taskId == "my-custom-id"
assert task.task_id == "my-custom-id"
assert task.status == "working"
retrieved = await store.get_task("my-custom-id")
assert retrieved is not None
assert retrieved.taskId == "my-custom-id"
assert retrieved.task_id == "my-custom-id"
@pytest.mark.anyio
@@ -71,15 +71,15 @@ async def test_update_status(store: InMemoryTaskStore) -> None:
"""Test InMemoryTaskStore status updates."""
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
updated = await store.update_task(task.taskId, status="completed", status_message="All done!")
updated = await store.update_task(task.task_id, status="completed", status_message="All done!")
assert updated.status == "completed"
assert updated.statusMessage == "All done!"
assert updated.status_message == "All done!"
retrieved = await store.get_task(task.taskId)
retrieved = await store.get_task(task.task_id)
assert retrieved is not None
assert retrieved.status == "completed"
assert retrieved.statusMessage == "All done!"
assert retrieved.status_message == "All done!"
@pytest.mark.anyio
@@ -96,10 +96,10 @@ async def test_store_and_get_result(store: InMemoryTaskStore) -> None:
# Store result
result = CallToolResult(content=[TextContent(type="text", text="Result data")])
await store.store_result(task.taskId, result)
await store.store_result(task.task_id, result)
# Retrieve result
retrieved_result = await store.get_result(task.taskId)
retrieved_result = await store.get_result(task.task_id)
assert retrieved_result == result
@@ -114,7 +114,7 @@ async def test_get_result_nonexistent_returns_none(store: InMemoryTaskStore) ->
async def test_get_result_no_result_returns_none(store: InMemoryTaskStore) -> None:
"""Test that getting result when none stored returns None."""
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
result = await store.get_result(task.taskId)
result = await store.get_result(task.task_id)
assert result is None
@@ -172,14 +172,14 @@ async def test_delete_task(store: InMemoryTaskStore) -> None:
"""Test InMemoryTaskStore delete operation."""
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
deleted = await store.delete_task(task.taskId)
deleted = await store.delete_task(task.task_id)
assert deleted is True
retrieved = await store.get_task(task.taskId)
retrieved = await store.get_task(task.task_id)
assert retrieved is None
# Delete non-existent
deleted = await store.delete_task(task.taskId)
deleted = await store.delete_task(task.task_id)
assert deleted is False
@@ -210,7 +210,7 @@ async def test_create_task_with_null_ttl(store: InMemoryTaskStore) -> None:
assert task.ttl is None
# Task should persist (not expire)
retrieved = await store.get_task(task.taskId)
retrieved = await store.get_task(task.task_id)
assert retrieved is not None
@@ -221,19 +221,19 @@ async def test_task_expiration_cleanup(store: InMemoryTaskStore) -> None:
task = await store.create_task(metadata=TaskMetadata(ttl=1)) # 1ms TTL
# Manually force the expiry to be in the past
stored = store._tasks.get(task.taskId)
stored = store._tasks.get(task.task_id)
assert stored is not None
stored.expires_at = datetime.now(timezone.utc) - timedelta(seconds=10)
# Task should still exist in internal dict but be expired
assert task.taskId in store._tasks
assert task.task_id in store._tasks
# Any access operation should clean up expired tasks
# list_tasks triggers cleanup
tasks, _ = await store.list_tasks()
# Expired task should be cleaned up
assert task.taskId not in store._tasks
assert task.task_id not in store._tasks
assert len(tasks) == 0
@@ -244,17 +244,17 @@ async def test_task_with_null_ttl_never_expires(store: InMemoryTaskStore) -> Non
task = await store.create_task(metadata=TaskMetadata(ttl=None))
# Verify internal storage has no expiry
stored = store._tasks.get(task.taskId)
stored = store._tasks.get(task.task_id)
assert stored is not None
assert stored.expires_at is None
# Access operations should NOT remove this task
await store.list_tasks()
await store.get_task(task.taskId)
await store.get_task(task.task_id)
# Task should still exist
assert task.taskId in store._tasks
retrieved = await store.get_task(task.taskId)
assert task.task_id in store._tasks
retrieved = await store.get_task(task.task_id)
assert retrieved is not None
@@ -265,13 +265,13 @@ async def test_terminal_task_ttl_reset(store: InMemoryTaskStore) -> None:
task = await store.create_task(metadata=TaskMetadata(ttl=60000)) # 60s
# Get the initial expiry
stored = store._tasks.get(task.taskId)
stored = store._tasks.get(task.task_id)
assert stored is not None
initial_expiry = stored.expires_at
assert initial_expiry is not None
# Update to terminal state (completed)
await store.update_task(task.taskId, status="completed")
await store.update_task(task.task_id, status="completed")
# Expiry should be reset to a new time (from now + TTL)
new_expiry = stored.expires_at
@@ -291,16 +291,16 @@ async def test_terminal_status_transition_rejected(store: InMemoryTaskStore) ->
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
# Move to terminal state
await store.update_task(task.taskId, status=terminal_status)
await store.update_task(task.task_id, status=terminal_status)
# Attempting to transition to any other status should raise
with pytest.raises(ValueError, match="Cannot transition from terminal status"):
await store.update_task(task.taskId, status="working")
await store.update_task(task.task_id, status="working")
# Also test transitioning to another terminal state
other_terminal = "failed" if terminal_status != "failed" else "completed"
with pytest.raises(ValueError, match="Cannot transition from terminal status"):
await store.update_task(task.taskId, status=other_terminal)
await store.update_task(task.task_id, status=other_terminal)
@pytest.mark.anyio
@@ -310,15 +310,15 @@ async def test_terminal_status_allows_same_status(store: InMemoryTaskStore) -> N
This is not a transition, so it should be allowed (no-op).
"""
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
await store.update_task(task.taskId, status="completed")
await store.update_task(task.task_id, status="completed")
# Setting the same status should not raise
updated = await store.update_task(task.taskId, status="completed")
updated = await store.update_task(task.task_id, status="completed")
assert updated.status == "completed"
# Updating just the message should also work
updated = await store.update_task(task.taskId, status_message="Updated message")
assert updated.statusMessage == "Updated message"
updated = await store.update_task(task.task_id, status_message="Updated message")
assert updated.status_message == "Updated message"
@pytest.mark.anyio
@@ -334,13 +334,13 @@ async def test_cancel_task_succeeds_for_working_task(store: InMemoryTaskStore) -
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
assert task.status == "working"
result = await cancel_task(store, task.taskId)
result = await cancel_task(store, task.task_id)
assert result.taskId == task.taskId
assert result.task_id == task.task_id
assert result.status == "cancelled"
# Verify store is updated
retrieved = await store.get_task(task.taskId)
retrieved = await store.get_task(task.task_id)
assert retrieved is not None
assert retrieved.status == "cancelled"
@@ -359,10 +359,10 @@ async def test_cancel_task_rejects_nonexistent_task(store: InMemoryTaskStore) ->
async def test_cancel_task_rejects_completed_task(store: InMemoryTaskStore) -> None:
"""Test cancel_task raises McpError with INVALID_PARAMS for completed task."""
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
await store.update_task(task.taskId, status="completed")
await store.update_task(task.task_id, status="completed")
with pytest.raises(McpError) as exc_info:
await cancel_task(store, task.taskId)
await cancel_task(store, task.task_id)
assert exc_info.value.error.code == INVALID_PARAMS
assert "terminal state 'completed'" in exc_info.value.error.message
@@ -372,10 +372,10 @@ async def test_cancel_task_rejects_completed_task(store: InMemoryTaskStore) -> N
async def test_cancel_task_rejects_failed_task(store: InMemoryTaskStore) -> None:
"""Test cancel_task raises McpError with INVALID_PARAMS for failed task."""
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
await store.update_task(task.taskId, status="failed")
await store.update_task(task.task_id, status="failed")
with pytest.raises(McpError) as exc_info:
await cancel_task(store, task.taskId)
await cancel_task(store, task.task_id)
assert exc_info.value.error.code == INVALID_PARAMS
assert "terminal state 'failed'" in exc_info.value.error.message
@@ -385,10 +385,10 @@ async def test_cancel_task_rejects_failed_task(store: InMemoryTaskStore) -> None
async def test_cancel_task_rejects_already_cancelled_task(store: InMemoryTaskStore) -> None:
"""Test cancel_task raises McpError with INVALID_PARAMS for already cancelled task."""
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
await store.update_task(task.taskId, status="cancelled")
await store.update_task(task.task_id, status="cancelled")
with pytest.raises(McpError) as exc_info:
await cancel_task(store, task.taskId)
await cancel_task(store, task.task_id)
assert exc_info.value.error.code == INVALID_PARAMS
assert "terminal state 'cancelled'" in exc_info.value.error.message
@@ -398,9 +398,9 @@ async def test_cancel_task_rejects_already_cancelled_task(store: InMemoryTaskSto
async def test_cancel_task_succeeds_for_input_required_task(store: InMemoryTaskStore) -> None:
"""Test cancel_task helper succeeds for a task in input_required status."""
task = await store.create_task(metadata=TaskMetadata(ttl=60000))
await store.update_task(task.taskId, status="input_required")
await store.update_task(task.task_id, status="input_required")
result = await cancel_task(store, task.taskId)
result = await cancel_task(store, task.task_id)
assert result.taskId == task.taskId
assert result.task_id == task.task_id
assert result.status == "cancelled"
@@ -53,13 +53,13 @@ async def test_handle_returns_result_for_completed_task(
"""Test that handle() returns the stored result for a completed task."""
task = await store.create_task(TaskMetadata(ttl=60000), task_id="test-task")
result = CallToolResult(content=[TextContent(type="text", text="Done!")])
await store.store_result(task.taskId, result)
await store.update_task(task.taskId, status="completed")
await store.store_result(task.task_id, result)
await store.update_task(task.task_id, status="completed")
mock_session = Mock()
mock_session.send_message = AsyncMock()
request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(taskId=task.taskId))
request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id=task.task_id))
response = await handler.handle(request, mock_session, "req-1")
assert response is not None
@@ -73,7 +73,7 @@ async def test_handle_raises_for_nonexistent_task(
) -> None:
"""Test that handle() raises McpError for nonexistent task."""
mock_session = Mock()
request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(taskId="nonexistent"))
request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id="nonexistent"))
with pytest.raises(McpError) as exc_info:
await handler.handle(request, mock_session, "req-1")
@@ -87,12 +87,12 @@ async def test_handle_returns_empty_result_when_no_result_stored(
) -> None:
"""Test that handle() returns minimal result when task completed without stored result."""
task = await store.create_task(TaskMetadata(ttl=60000), task_id="test-task")
await store.update_task(task.taskId, status="completed")
await store.update_task(task.task_id, status="completed")
mock_session = Mock()
mock_session.send_message = AsyncMock()
request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(taskId=task.taskId))
request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id=task.task_id))
response = await handler.handle(request, mock_session, "req-1")
assert response is not None
@@ -116,8 +116,8 @@ async def test_handle_delivers_queued_messages(
params={},
),
)
await queue.enqueue(task.taskId, queued_msg)
await store.update_task(task.taskId, status="completed")
await queue.enqueue(task.task_id, queued_msg)
await store.update_task(task.task_id, status="completed")
sent_messages: list[SessionMessage] = []
@@ -127,7 +127,7 @@ async def test_handle_delivers_queued_messages(
mock_session = Mock()
mock_session.send_message = track_send
request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(taskId=task.taskId))
request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id=task.task_id))
await handler.handle(request, mock_session, "req-1")
assert len(sent_messages) == 1
@@ -143,7 +143,7 @@ async def test_handle_waits_for_task_completion(
mock_session = Mock()
mock_session.send_message = AsyncMock()
request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(taskId=task.taskId))
request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id=task.task_id))
result_holder: list[GetTaskPayloadResult | None] = [None]
async def run_handle() -> None:
@@ -153,11 +153,11 @@ async def test_handle_waits_for_task_completion(
tg.start_soon(run_handle)
# Wait for handler to start waiting (event gets created when wait starts)
while task.taskId not in store._update_events:
while task.task_id not in store._update_events:
await anyio.sleep(0)
await store.store_result(task.taskId, CallToolResult(content=[TextContent(type="text", text="Done")]))
await store.update_task(task.taskId, status="completed")
await store.store_result(task.task_id, CallToolResult(content=[TextContent(type="text", text="Done")]))
await store.update_task(task.task_id, status="completed")
assert result_holder[0] is not None
@@ -248,12 +248,12 @@ async def test_deliver_registers_resolver_for_request_messages(
resolver=resolver,
original_request_id="inner-req-1",
)
await queue.enqueue(task.taskId, queued_msg)
await queue.enqueue(task.task_id, queued_msg)
mock_session = Mock()
mock_session.send_message = AsyncMock()
await handler._deliver_queued_messages(task.taskId, mock_session, "outer-req-1")
await handler._deliver_queued_messages(task.task_id, mock_session, "outer-req-1")
assert "inner-req-1" in handler._pending_requests
assert handler._pending_requests["inner-req-1"] is resolver
@@ -278,12 +278,12 @@ async def test_deliver_skips_resolver_registration_when_no_original_id(
resolver=resolver,
original_request_id=None, # No original request ID
)
await queue.enqueue(task.taskId, queued_msg)
await queue.enqueue(task.task_id, queued_msg)
mock_session = Mock()
mock_session.send_message = AsyncMock()
await handler._deliver_queued_messages(task.taskId, mock_session, "outer-req-1")
await handler._deliver_queued_messages(task.task_id, mock_session, "outer-req-1")
# Resolver should NOT be registered since original_request_id is None
assert len(handler._pending_requests) == 0
@@ -307,10 +307,10 @@ async def test_wait_for_task_update_handles_store_exception(
# Queue a message to unblock the race via the queue path
async def enqueue_later() -> None:
# Wait for queue to start waiting (event gets created when wait starts)
while task.taskId not in queue._events:
while task.task_id not in queue._events:
await anyio.sleep(0)
await queue.enqueue(
task.taskId,
task.task_id,
QueuedMessage(
type="notification",
message=JSONRPCRequest(
@@ -325,7 +325,7 @@ async def test_wait_for_task_update_handles_store_exception(
async with anyio.create_task_group() as tg:
tg.start_soon(enqueue_later)
# This should complete via the queue path even though store raises
await handler._wait_for_task_update(task.taskId)
await handler._wait_for_task_update(task.task_id)
@pytest.mark.anyio
@@ -344,11 +344,11 @@ async def test_wait_for_task_update_handles_queue_exception(
# Update the store to unblock the race via the store path
async def update_later() -> None:
# Wait for store to start waiting (event gets created when wait starts)
while task.taskId not in store._update_events:
while task.task_id not in store._update_events:
await anyio.sleep(0)
await store.update_task(task.taskId, status="completed")
await store.update_task(task.task_id, status="completed")
async with anyio.create_task_group() as tg:
tg.start_soon(update_later)
# This should complete via the store path even though queue raises
await handler._wait_for_task_update(task.taskId)
await handler._wait_for_task_update(task.task_id)
@@ -82,7 +82,7 @@ class TestCheckTasksCapability:
"""When sampling.createMessage is required but client doesn't have it."""
required = ClientTasksCapability(
requests=ClientTasksRequestsCapability(
sampling=TasksSamplingCapability(createMessage=TasksCreateMessageCapability())
sampling=TasksSamplingCapability(create_message=TasksCreateMessageCapability())
)
)
client = ClientTasksCapability(
@@ -96,12 +96,12 @@ class TestCheckTasksCapability:
"""When sampling.createMessage is required and client has it."""
required = ClientTasksCapability(
requests=ClientTasksRequestsCapability(
sampling=TasksSamplingCapability(createMessage=TasksCreateMessageCapability())
sampling=TasksSamplingCapability(create_message=TasksCreateMessageCapability())
)
)
client = ClientTasksCapability(
requests=ClientTasksRequestsCapability(
sampling=TasksSamplingCapability(createMessage=TasksCreateMessageCapability())
sampling=TasksSamplingCapability(create_message=TasksCreateMessageCapability())
)
)
assert check_tasks_capability(required, client) is True
@@ -111,13 +111,13 @@ class TestCheckTasksCapability:
required = ClientTasksCapability(
requests=ClientTasksRequestsCapability(
elicitation=TasksElicitationCapability(create=TasksCreateElicitationCapability()),
sampling=TasksSamplingCapability(createMessage=TasksCreateMessageCapability()),
sampling=TasksSamplingCapability(create_message=TasksCreateMessageCapability()),
)
)
client = ClientTasksCapability(
requests=ClientTasksRequestsCapability(
elicitation=TasksElicitationCapability(create=TasksCreateElicitationCapability()),
sampling=TasksSamplingCapability(createMessage=TasksCreateMessageCapability()),
sampling=TasksSamplingCapability(create_message=TasksCreateMessageCapability()),
)
)
assert check_tasks_capability(required, client) is True
@@ -145,7 +145,7 @@ class TestCheckTasksCapability:
)
client = ClientTasksCapability(
requests=ClientTasksRequestsCapability(
sampling=TasksSamplingCapability(createMessage=TasksCreateMessageCapability())
sampling=TasksSamplingCapability(create_message=TasksCreateMessageCapability())
)
)
assert check_tasks_capability(required, client) is True
@@ -220,7 +220,7 @@ class TestHasTaskAugmentedSampling:
caps = ClientCapabilities(
tasks=ClientTasksCapability(
requests=ClientTasksRequestsCapability(
sampling=TasksSamplingCapability(createMessage=TasksCreateMessageCapability())
sampling=TasksSamplingCapability(create_message=TasksCreateMessageCapability())
)
)
)
@@ -276,7 +276,7 @@ class TestRequireTaskAugmentedSampling:
caps = ClientCapabilities(
tasks=ClientTasksCapability(
requests=ClientTasksRequestsCapability(
sampling=TasksSamplingCapability(createMessage=TasksCreateMessageCapability())
sampling=TasksSamplingCapability(create_message=TasksCreateMessageCapability())
)
)
)
@@ -61,13 +61,13 @@ def create_client_task_handlers(
"""Handle task-augmented elicitation by creating a client-side task."""
elicit_received.set()
task = await client_task_store.create_task(task_metadata)
task_complete_events[task.taskId] = Event()
task_complete_events[task.task_id] = Event()
async def complete_task() -> None:
# Store result before updating status to avoid race condition
await client_task_store.store_result(task.taskId, elicit_response)
await client_task_store.update_task(task.taskId, status="completed")
task_complete_events[task.taskId].set()
await client_task_store.store_result(task.task_id, elicit_response)
await client_task_store.update_task(task.task_id, status="completed")
task_complete_events[task.task_id].set()
context.session._task_group.start_soon(complete_task) # pyright: ignore[reportPrivateUsage]
return CreateTaskResult(task=task)
@@ -77,16 +77,16 @@ def create_client_task_handlers(
params: Any,
) -> GetTaskResult:
"""Handle tasks/get from server."""
task = await client_task_store.get_task(params.taskId)
assert task is not None, f"Task not found: {params.taskId}"
task = await client_task_store.get_task(params.task_id)
assert task is not None, f"Task not found: {params.task_id}"
return GetTaskResult(
taskId=task.taskId,
task_id=task.task_id,
status=task.status,
statusMessage=task.statusMessage,
createdAt=task.createdAt,
lastUpdatedAt=task.lastUpdatedAt,
status_message=task.status_message,
created_at=task.created_at,
last_updated_at=task.last_updated_at,
ttl=task.ttl,
pollInterval=100,
poll_interval=100,
)
async def handle_get_task_result(
@@ -94,11 +94,11 @@ def create_client_task_handlers(
params: Any,
) -> GetTaskPayloadResult | ErrorData:
"""Handle tasks/result from server."""
event = task_complete_events.get(params.taskId)
assert event is not None, f"No completion event for task: {params.taskId}"
event = task_complete_events.get(params.task_id)
assert event is not None, f"No completion event for task: {params.task_id}"
await event.wait()
result = await client_task_store.get_result(params.taskId)
assert result is not None, f"Result not found for task: {params.taskId}"
result = await client_task_store.get_result(params.task_id)
assert result is not None, f"Result not found for task: {params.task_id}"
return GetTaskPayloadResult.model_validate(result.model_dump(by_alias=True))
return ExperimentalTaskHandlers(
@@ -129,13 +129,13 @@ def create_sampling_task_handlers(
"""Handle task-augmented sampling by creating a client-side task."""
sampling_received.set()
task = await client_task_store.create_task(task_metadata)
task_complete_events[task.taskId] = Event()
task_complete_events[task.task_id] = Event()
async def complete_task() -> None:
# Store result before updating status to avoid race condition
await client_task_store.store_result(task.taskId, sampling_response)
await client_task_store.update_task(task.taskId, status="completed")
task_complete_events[task.taskId].set()
await client_task_store.store_result(task.task_id, sampling_response)
await client_task_store.update_task(task.task_id, status="completed")
task_complete_events[task.task_id].set()
context.session._task_group.start_soon(complete_task) # pyright: ignore[reportPrivateUsage]
return CreateTaskResult(task=task)
@@ -145,16 +145,16 @@ def create_sampling_task_handlers(
params: Any,
) -> GetTaskResult:
"""Handle tasks/get from server."""
task = await client_task_store.get_task(params.taskId)
assert task is not None, f"Task not found: {params.taskId}"
task = await client_task_store.get_task(params.task_id)
assert task is not None, f"Task not found: {params.task_id}"
return GetTaskResult(
taskId=task.taskId,
task_id=task.task_id,
status=task.status,
statusMessage=task.statusMessage,
createdAt=task.createdAt,
lastUpdatedAt=task.lastUpdatedAt,
status_message=task.status_message,
created_at=task.created_at,
last_updated_at=task.last_updated_at,
ttl=task.ttl,
pollInterval=100,
poll_interval=100,
)
async def handle_get_task_result(
@@ -162,11 +162,11 @@ def create_sampling_task_handlers(
params: Any,
) -> GetTaskPayloadResult | ErrorData:
"""Handle tasks/result from server."""
event = task_complete_events.get(params.taskId)
assert event is not None, f"No completion event for task: {params.taskId}"
event = task_complete_events.get(params.task_id)
assert event is not None, f"No completion event for task: {params.task_id}"
await event.wait()
result = await client_task_store.get_result(params.taskId)
assert result is not None, f"Result not found for task: {params.taskId}"
result = await client_task_store.get_result(params.task_id)
assert result is not None, f"Result not found for task: {params.task_id}"
return GetTaskPayloadResult.model_validate(result.model_dump(by_alias=True))
return ExperimentalTaskHandlers(
@@ -193,7 +193,7 @@ async def test_scenario1_normal_tool_normal_elicitation() -> None:
Tool(
name="confirm_action",
description="Confirm an action",
inputSchema={"type": "object"},
input_schema={"type": "object"},
)
]
@@ -204,7 +204,7 @@ async def test_scenario1_normal_tool_normal_elicitation() -> None:
# Normal elicitation - expects immediate response
result = await ctx.session.elicit(
message="Please confirm the action",
requestedSchema={"type": "object", "properties": {"confirm": {"type": "boolean"}}},
requested_schema={"type": "object", "properties": {"confirm": {"type": "boolean"}}},
)
confirmed = result.content.get("confirm", False) if result.content else False
@@ -278,7 +278,7 @@ async def test_scenario2_normal_tool_task_augmented_elicitation() -> None:
Tool(
name="confirm_action",
description="Confirm an action",
inputSchema={"type": "object"},
input_schema={"type": "object"},
)
]
@@ -289,7 +289,7 @@ async def test_scenario2_normal_tool_task_augmented_elicitation() -> None:
# Task-augmented elicitation - server polls client
result = await ctx.session.experimental.elicit_as_task(
message="Please confirm the action",
requestedSchema={"type": "object", "properties": {"confirm": {"type": "boolean"}}},
requested_schema={"type": "object", "properties": {"confirm": {"type": "boolean"}}},
ttl=60000,
)
@@ -358,8 +358,8 @@ async def test_scenario3_task_augmented_tool_normal_elicitation() -> None:
Tool(
name="confirm_action",
description="Confirm an action",
inputSchema={"type": "object"},
execution=ToolExecution(taskSupport=TASK_REQUIRED),
input_schema={"type": "object"},
execution=ToolExecution(task_support=TASK_REQUIRED),
)
]
@@ -372,7 +372,7 @@ async def test_scenario3_task_augmented_tool_normal_elicitation() -> None:
# Normal elicitation within task - queued and delivered via tasks/result
result = await task.elicit(
message="Please confirm the action",
requestedSchema={"type": "object", "properties": {"confirm": {"type": "boolean"}}},
requested_schema={"type": "object", "properties": {"confirm": {"type": "boolean"}}},
)
confirmed = result.content.get("confirm", False) if result.content else False
@@ -413,7 +413,7 @@ async def test_scenario3_task_augmented_tool_normal_elicitation() -> None:
# Call tool as task
create_result = await client_session.experimental.call_tool_as_task("confirm_action", {})
task_id = create_result.task.taskId
task_id = create_result.task.task_id
assert create_result.task.status == "working"
# Poll until input_required, then call tasks/result
@@ -472,8 +472,8 @@ async def test_scenario4_task_augmented_tool_task_augmented_elicitation() -> Non
Tool(
name="confirm_action",
description="Confirm an action",
inputSchema={"type": "object"},
execution=ToolExecution(taskSupport=TASK_REQUIRED),
input_schema={"type": "object"},
execution=ToolExecution(task_support=TASK_REQUIRED),
)
]
@@ -486,7 +486,7 @@ async def test_scenario4_task_augmented_tool_task_augmented_elicitation() -> Non
# Task-augmented elicitation within task - server polls client
result = await task.elicit_as_task(
message="Please confirm the action",
requestedSchema={"type": "object", "properties": {"confirm": {"type": "boolean"}}},
requested_schema={"type": "object", "properties": {"confirm": {"type": "boolean"}}},
ttl=60000,
)
@@ -522,7 +522,7 @@ async def test_scenario4_task_augmented_tool_task_augmented_elicitation() -> Non
# Call tool as task
create_result = await client_session.experimental.call_tool_as_task("confirm_action", {})
task_id = create_result.task.taskId
task_id = create_result.task.task_id
assert create_result.task.status == "working"
# Poll until input_required or terminal, then call tasks/result
@@ -572,7 +572,7 @@ async def test_scenario2_sampling_normal_tool_task_augmented_sampling() -> None:
Tool(
name="generate_text",
description="Generate text using sampling",
inputSchema={"type": "object"},
input_schema={"type": "object"},
)
]
@@ -658,8 +658,8 @@ async def test_scenario4_sampling_task_augmented_tool_task_augmented_sampling()
Tool(
name="generate_text",
description="Generate text using sampling",
inputSchema={"type": "object"},
execution=ToolExecution(taskSupport=TASK_REQUIRED),
input_schema={"type": "object"},
execution=ToolExecution(task_support=TASK_REQUIRED),
)
]
@@ -710,7 +710,7 @@ async def test_scenario4_sampling_task_augmented_tool_task_augmented_sampling()
# Call tool as task
create_result = await client_session.experimental.call_tool_as_task("generate_text", {})
task_id = create_result.task.taskId
task_id = create_result.task.task_id
assert create_result.task.status == "working"
# Poll until input_required or terminal
@@ -108,8 +108,8 @@ def test_validate_for_tool_with_execution_required() -> None:
tool = Tool(
name="test",
description="test",
inputSchema={"type": "object"},
execution=ToolExecution(taskSupport=TASK_REQUIRED),
input_schema={"type": "object"},
execution=ToolExecution(task_support=TASK_REQUIRED),
)
error = exp.validate_for_tool(tool, raise_error=False)
assert error is not None
@@ -121,7 +121,7 @@ def test_validate_for_tool_without_execution() -> None:
tool = Tool(
name="test",
description="test",
inputSchema={"type": "object"},
input_schema={"type": "object"},
execution=None,
)
error = exp.validate_for_tool(tool, raise_error=False)
@@ -134,8 +134,8 @@ def test_validate_for_tool_optional_with_task() -> None:
tool = Tool(
name="test",
description="test",
inputSchema={"type": "object"},
execution=ToolExecution(taskSupport=TASK_OPTIONAL),
input_schema={"type": "object"},
execution=ToolExecution(task_support=TASK_OPTIONAL),
)
error = exp.validate_for_tool(tool, raise_error=False)
assert error is None
@@ -346,10 +346,10 @@ class TestCreatingTask:
# CreateTaskResult can include model-immediate-response in _meta
task = Task(
taskId="test-123",
task_id="test-123",
status="working",
createdAt=TEST_DATETIME,
lastUpdatedAt=TEST_DATETIME,
created_at=TEST_DATETIME,
last_updated_at=TEST_DATETIME,
ttl=60000,
)
immediate_msg = "Task started, processing your request..."
@@ -89,7 +89,7 @@ async def test_lifespan_cleanup_executed():
async with ClientSession(read, write) as session:
# Initialize the session
result = await session.initialize()
assert result.protocolVersion in ["2024-11-05", "2025-06-18", "2025-11-25"]
assert result.protocol_version in ["2024-11-05", "2025-06-18", "2025-11-25"]
# Verify startup marker was created
assert Path(startup_marker).exists(), "Server startup marker not created"
+3 -3
View File
@@ -27,16 +27,16 @@ async def test_resource_templates():
types.ListResourceTemplatesRequest(params=None)
)
assert isinstance(result.root, types.ListResourceTemplatesResult)
templates = result.root.resourceTemplates
templates = result.root.resource_templates
# Verify we get both templates back
assert len(templates) == 2
# Verify template details
greeting_template = next(t for t in templates if t.name == "get_greeting") # pragma: no cover
assert greeting_template.uriTemplate == "greeting://{name}"
assert greeting_template.uri_template == "greeting://{name}"
assert greeting_template.description == "Get a personalized greeting"
profile_template = next(t for t in templates if t.name == "get_user_profile") # pragma: no cover
assert profile_template.uriTemplate == "users://{user_id}/profile"
assert profile_template.uri_template == "users://{user_id}/profile"
assert profile_template.description == "Dynamic user data"
+6 -6
View File
@@ -14,7 +14,7 @@ async def test_icons_and_website_url():
# Create test icon
test_icon = Icon(
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==",
mimeType="image/png",
mime_type="image/png",
sizes=["1x1"],
)
@@ -51,7 +51,7 @@ async def test_icons_and_website_url():
assert mcp.icons is not None
assert len(mcp.icons) == 1
assert mcp.icons[0].src == test_icon.src
assert mcp.icons[0].mimeType == test_icon.mimeType
assert mcp.icons[0].mime_type == test_icon.mime_type
assert mcp.icons[0].sizes == test_icon.sizes
# Test tool includes icon
@@ -86,7 +86,7 @@ async def test_icons_and_website_url():
assert len(templates) == 1
template = templates[0]
assert template.name == "test_resource_template"
assert template.uriTemplate == "test://weather/{city}"
assert template.uri_template == "test://weather/{city}"
assert template.icons is not None
assert len(template.icons) == 1
assert template.icons[0].src == test_icon.src
@@ -96,9 +96,9 @@ async def test_multiple_icons():
"""Test that multiple icons can be added to tools, resources, and prompts."""
# Create multiple test icons
icon1 = Icon(src="data:image/png;base64,icon1", mimeType="image/png", sizes=["16x16"])
icon2 = Icon(src="data:image/png;base64,icon2", mimeType="image/png", sizes=["32x32"])
icon3 = Icon(src="data:image/png;base64,icon3", mimeType="image/png", sizes=["64x64"])
icon1 = Icon(src="data:image/png;base64,icon1", mime_type="image/png", sizes=["16x16"])
icon2 = Icon(src="data:image/png;base64,icon2", mime_type="image/png", sizes=["32x32"])
icon3 = Icon(src="data:image/png;base64,icon3", mime_type="image/png", sizes=["64x64"])
mcp = FastMCP("MultiIconServer")
+4 -4
View File
@@ -85,10 +85,10 @@ async def test_resource_template_client_interaction():
# List available resources
resources = await session.list_resource_templates()
assert isinstance(resources, ListResourceTemplatesResult)
assert len(resources.resourceTemplates) == 2
assert len(resources.resource_templates) == 2
# Verify resource templates are listed correctly
templates = [r.uriTemplate for r in resources.resourceTemplates]
templates = [r.uri_template for r in resources.resource_templates]
assert "resource://users/{user_id}/posts/{post_id}" in templates
assert "resource://users/{user_id}/profile" in templates
@@ -97,14 +97,14 @@ async def test_resource_template_client_interaction():
contents = result.contents[0]
assert isinstance(contents, TextResourceContents)
assert contents.text == "Post 456 by user 123"
assert contents.mimeType == "text/plain"
assert contents.mime_type == "text/plain"
# Read another resource with valid parameters
result = await session.read_resource(AnyUrl("resource://users/789/profile"))
contents = result.contents[0]
assert isinstance(contents, TextResourceContents)
assert contents.text == "Profile for user 789"
assert contents.mimeType == "text/plain"
assert contents.mime_type == "text/plain"
# Verify invalid resource URIs raise appropriate errors
with pytest.raises(Exception): # Specific exception type may vary
+10 -10
View File
@@ -45,19 +45,19 @@ async def test_fastmcp_resource_mime_type():
bytes_resource = mapping["test://image_bytes"]
# Verify mime types
assert string_resource.mimeType == "image/png", "String resource mime type not respected"
assert bytes_resource.mimeType == "image/png", "Bytes resource mime type not respected"
assert string_resource.mime_type == "image/png", "String resource mime type not respected"
assert bytes_resource.mime_type == "image/png", "Bytes resource mime type not respected"
# Also verify the content can be read correctly
string_result = await client.read_resource(AnyUrl("test://image"))
assert len(string_result.contents) == 1
assert getattr(string_result.contents[0], "text") == base64_string, "Base64 string mismatch"
assert string_result.contents[0].mimeType == "image/png", "String content mime type not preserved"
assert string_result.contents[0].mime_type == "image/png", "String content mime type not preserved"
bytes_result = await client.read_resource(AnyUrl("test://image_bytes"))
assert len(bytes_result.contents) == 1
assert base64.b64decode(getattr(bytes_result.contents[0], "blob")) == image_bytes, "Bytes mismatch"
assert bytes_result.contents[0].mimeType == "image/png", "Bytes content mime type not preserved"
assert bytes_result.contents[0].mime_type == "image/png", "Bytes content mime type not preserved"
async def test_lowlevel_resource_mime_type():
@@ -70,11 +70,11 @@ async def test_lowlevel_resource_mime_type():
# Create test resources with specific mime types
test_resources = [
types.Resource(uri="test://image", name="test image", mimeType="image/png"),
types.Resource(uri="test://image", name="test image", mime_type="image/png"),
types.Resource(
uri="test://image_bytes",
name="test image bytes",
mimeType="image/png",
mime_type="image/png",
),
]
@@ -103,16 +103,16 @@ async def test_lowlevel_resource_mime_type():
bytes_resource = mapping["test://image_bytes"]
# Verify mime types
assert string_resource.mimeType == "image/png", "String resource mime type not respected"
assert bytes_resource.mimeType == "image/png", "Bytes resource mime type not respected"
assert string_resource.mime_type == "image/png", "String resource mime type not respected"
assert bytes_resource.mime_type == "image/png", "Bytes resource mime type not respected"
# Also verify the content can be read correctly
string_result = await client.read_resource(AnyUrl("test://image"))
assert len(string_result.contents) == 1
assert getattr(string_result.contents[0], "text") == base64_string, "Base64 string mismatch"
assert string_result.contents[0].mimeType == "image/png", "String content mime type not preserved"
assert string_result.contents[0].mime_type == "image/png", "String content mime type not preserved"
bytes_result = await client.read_resource(AnyUrl("test://image_bytes"))
assert len(bytes_result.contents) == 1
assert base64.b64decode(getattr(bytes_result.contents[0], "blob")) == image_bytes, "Bytes mismatch"
assert bytes_result.contents[0].mimeType == "image/png", "Bytes content mime type not preserved"
assert bytes_result.contents[0].mime_type == "image/png", "Bytes content mime type not preserved"
@@ -125,7 +125,7 @@ def test_resource_contents_uri_json_roundtrip():
contents = types.TextResourceContents(
uri=uri_str,
text="data",
mimeType="text/plain",
mime_type="text/plain",
)
json_data = contents.model_dump(mode="json")
restored = types.TextResourceContents.model_validate(json_data)
@@ -26,7 +26,7 @@ async def test_mime_type_with_parameters():
resources = await mcp.list_resources()
assert len(resources) == 1
assert resources[0].mimeType == "text/html;profile=mcp-app"
assert resources[0].mime_type == "text/html;profile=mcp-app"
async def test_mime_type_with_parameters_and_space():
@@ -39,7 +39,7 @@ async def test_mime_type_with_parameters_and_space():
resources = await mcp.list_resources()
assert len(resources) == 1
assert resources[0].mimeType == "application/json; charset=utf-8"
assert resources[0].mime_type == "application/json; charset=utf-8"
async def test_mime_type_with_multiple_parameters():
@@ -52,7 +52,7 @@ async def test_mime_type_with_multiple_parameters():
resources = await mcp.list_resources()
assert len(resources) == 1
assert resources[0].mimeType == "text/plain; charset=utf-8; format=fixed"
assert resources[0].mime_type == "text/plain; charset=utf-8; format=fixed"
async def test_mime_type_preserved_in_read_resource():
@@ -67,4 +67,4 @@ async def test_mime_type_preserved_in_read_resource():
# Read the resource
result = await client.read_resource(AnyUrl("ui://my-widget"))
assert len(result.contents) == 1
assert result.contents[0].mimeType == "text/html;profile=mcp-app"
assert result.contents[0].mime_type == "text/html;profile=mcp-app"
+1 -1
View File
@@ -17,7 +17,7 @@ async def test_progress_token_zero_first_call():
# Create request context with progress token 0
mock_meta = MagicMock()
mock_meta.progressToken = 0 # This is the key test case - token is 0
mock_meta.progress_token = 0 # This is the key test case - token is 0
request_context = RequestContext(
request_id="test-request",
+2 -2
View File
@@ -59,9 +59,9 @@ async def test_request_id_match() -> None:
id="init-1",
method="initialize",
params=InitializeRequestParams(
protocolVersion=LATEST_PROTOCOL_VERSION,
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ClientCapabilities(),
clientInfo=Implementation(name="test-client", version="1.0.0"),
client_info=Implementation(name="test-client", version="1.0.0"),
).model_dump(by_alias=True, exclude_none=True),
jsonrpc="2.0",
)
+2 -2
View File
@@ -42,12 +42,12 @@ async def test_notification_validation_error(tmp_path: Path):
types.Tool(
name="slow",
description="A slow tool",
inputSchema={"type": "object"},
input_schema={"type": "object"},
),
types.Tool(
name="fast",
description="A fast tool",
inputSchema={"type": "object"},
input_schema={"type": "object"},
),
]
+5 -5
View File
@@ -96,7 +96,7 @@ class TestRenderPrompt:
resource=TextResourceContents(
uri="file://file.txt",
text="File contents",
mimeType="text/plain",
mime_type="text/plain",
),
)
)
@@ -109,7 +109,7 @@ class TestRenderPrompt:
resource=TextResourceContents(
uri="file://file.txt",
text="File contents",
mimeType="text/plain",
mime_type="text/plain",
),
)
)
@@ -128,7 +128,7 @@ class TestRenderPrompt:
resource=TextResourceContents(
uri="file://file.txt",
text="File contents",
mimeType="text/plain",
mime_type="text/plain",
),
)
),
@@ -144,7 +144,7 @@ class TestRenderPrompt:
resource=TextResourceContents(
uri="file://file.txt",
text="File contents",
mimeType="text/plain",
mime_type="text/plain",
),
)
),
@@ -176,7 +176,7 @@ class TestRenderPrompt:
resource=TextResourceContents(
uri="file://file.txt",
text="File contents",
mimeType="text/plain",
mime_type="text/plain",
),
)
)
+1 -1
View File
@@ -290,7 +290,7 @@ async def test_elicitation_with_default_values():
async def callback_schema_verify(context: RequestContext[ClientSession, None], params: ElicitRequestParams):
# Verify the schema includes defaults
assert isinstance(params, types.ElicitRequestFormParams), "Expected form mode elicitation"
schema = params.requestedSchema
schema = params.requested_schema
props = schema["properties"]
assert props["name"]["default"] == "Guest"
+2 -2
View File
@@ -850,7 +850,7 @@ def test_tool_call_result_annotated_is_structured_and_converted():
name: str
def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]: # pragma: no cover
return CallToolResult(content=[], structuredContent={"name": "Brandon"})
return CallToolResult(content=[], structured_content={"name": "Brandon"})
meta = func_metadata(func_returning_annotated_tool_call_result)
@@ -870,7 +870,7 @@ def test_tool_call_result_annotated_is_structured_and_invalid():
name: str
def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, PersonClass]: # pragma: no cover
return CallToolResult(content=[], structuredContent={"person": "Brandon"})
return CallToolResult(content=[], structured_content={"person": "Brandon"})
meta = func_metadata(func_returning_annotated_tool_call_result)
+10 -10
View File
@@ -258,7 +258,7 @@ async def test_basic_tools(server_transport: str, server_url: str) -> None:
# Test initialization
result = await session.initialize()
assert isinstance(result, InitializeResult)
assert result.serverInfo.name == "Tool Example"
assert result.server_info.name == "Tool Example"
assert result.capabilities.tools is not None
# Test sum tool
@@ -295,7 +295,7 @@ async def test_basic_resources(server_transport: str, server_url: str) -> None:
# Test initialization
result = await session.initialize()
assert isinstance(result, InitializeResult)
assert result.serverInfo.name == "Resource Example"
assert result.server_info.name == "Resource Example"
assert result.capabilities.resources is not None
# Test document resource
@@ -336,7 +336,7 @@ async def test_basic_prompts(server_transport: str, server_url: str) -> None:
# Test initialization
result = await session.initialize()
assert isinstance(result, InitializeResult)
assert result.serverInfo.name == "Prompt Example"
assert result.server_info.name == "Prompt Example"
assert result.capabilities.prompts is not None
# Test review_code prompt
@@ -396,7 +396,7 @@ async def test_tool_progress(server_transport: str, server_url: str) -> None:
# Test initialization
result = await session.initialize()
assert isinstance(result, InitializeResult)
assert result.serverInfo.name == "Progress Example"
assert result.server_info.name == "Progress Example"
# Test progress callback
progress_updates = []
@@ -449,7 +449,7 @@ async def test_sampling(server_transport: str, server_url: str) -> None:
# Test initialization
result = await session.initialize()
assert isinstance(result, InitializeResult)
assert result.serverInfo.name == "Sampling Example"
assert result.server_info.name == "Sampling Example"
assert result.capabilities.tools is not None
# Test sampling tool
@@ -480,7 +480,7 @@ async def test_elicitation(server_transport: str, server_url: str) -> None:
# Test initialization
result = await session.initialize()
assert isinstance(result, InitializeResult)
assert result.serverInfo.name == "Elicitation Example"
assert result.server_info.name == "Elicitation Example"
# Test booking with unavailable date (triggers elicitation)
booking_result = await session.call_tool(
@@ -537,7 +537,7 @@ async def test_notifications(server_transport: str, server_url: str) -> None:
# Test initialization
result = await session.initialize()
assert isinstance(result, InitializeResult)
assert result.serverInfo.name == "Notifications Example"
assert result.server_info.name == "Notifications Example"
# Call tool that generates notifications
tool_result = await session.call_tool("process_data", {"data": "test_data"})
@@ -578,7 +578,7 @@ async def test_completion(server_transport: str, server_url: str) -> None:
# Test initialization
result = await session.initialize()
assert isinstance(result, InitializeResult)
assert result.serverInfo.name == "Example"
assert result.server_info.name == "Example"
assert result.capabilities.resources is not None
assert result.capabilities.prompts is not None
@@ -635,7 +635,7 @@ async def test_fastmcp_quickstart(server_transport: str, server_url: str) -> Non
# Test initialization
result = await session.initialize()
assert isinstance(result, InitializeResult)
assert result.serverInfo.name == "Demo"
assert result.server_info.name == "Demo"
# Test add tool
tool_result = await session.call_tool("add", {"a": 10, "b": 20})
@@ -673,7 +673,7 @@ async def test_structured_output(server_transport: str, server_url: str) -> None
# Test initialization
result = await session.initialize()
assert isinstance(result, InitializeResult)
assert result.serverInfo.name == "Structured Output Example"
assert result.server_info.name == "Structured Output Example"
# Test get_weather tool
weather_result = await session.call_tool("get_weather", {"city": "New York"})

Some files were not shown because too many files have changed in this diff Show More