Adopt snake_case SDK type constructor kwargs

This commit is contained in:
Jeremiah Lowin 2026-07-05 18:11:04 -04:00
commit 2ea012ead4
No known key found for this signature in database
50 changed files with 283 additions and 281 deletions

View file

@ -791,7 +791,7 @@ _LOG_PANEL_HTML = """\
function renderEntry(entry) {
var div = document.createElement("div");
var isError = entry.direction === "response" && entry.body
var is_error = entry.direction === "response" && entry.body
&& (entry.body.error || (entry.body.result && entry.body.result.is_error));
div.className = "log-entry" + (isError ? " error" : "");
var dirClass = isError ? "error" : entry.direction;

View file

@ -816,7 +816,7 @@ class Client(
root=mcp_types.CancelledNotification(
method="notifications/cancelled",
params=mcp_types.CancelledNotificationParams(
requestId=request_id,
request_id=request_id,
reason=reason,
),
)

View file

@ -45,7 +45,7 @@ class ClientTaskManagementMixin:
RuntimeError: If client not connected
MCPError: If the request results in a TimeoutError | JSONRPCError
"""
request = GetTaskRequest(params=GetTaskRequestParams(taskId=task_id))
request = GetTaskRequest(params=GetTaskRequestParams(task_id=task_id))
return await self._await_with_session_monitoring(
self.session.send_request(
request=request, # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
@ -70,7 +70,7 @@ class ClientTaskManagementMixin:
MCPError: If the request results in a TimeoutError | JSONRPCError
"""
request = GetTaskPayloadRequest(
params=GetTaskPayloadRequestParams(taskId=task_id)
params=GetTaskPayloadRequestParams(task_id=task_id)
)
# Return raw result - Task classes handle type-specific parsing
result = await self._await_with_session_monitoring(
@ -148,7 +148,7 @@ class ClientTaskManagementMixin:
RuntimeError: If task doesn't exist
MCPError: If the request results in a TimeoutError | JSONRPCError
"""
request = CancelTaskRequest(params=CancelTaskRequestParams(taskId=task_id))
request = CancelTaskRequest(params=CancelTaskRequestParams(task_id=task_id))
return await self._await_with_session_monitoring(
self.session.send_request(
request=request, # type: ignore[invalid-argument-type] # ty:ignore[invalid-argument-type]

View file

@ -449,5 +449,5 @@ class AnthropicSamplingHandler:
content=content,
role="assistant",
model=message.model,
stopReason=stop_reason,
stop_reason=stop_reason,
)

View file

@ -399,5 +399,5 @@ def _response_to_result_with_tools(
content=content,
role="assistant",
model=model,
stopReason=stop_reason,
stop_reason=stop_reason,
)

View file

@ -509,5 +509,5 @@ class OpenAISamplingHandler:
content=content, # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
role="assistant",
model=chat_completion.model,
stopReason=stop_reason,
stop_reason=stop_reason,
)

View file

@ -181,12 +181,12 @@ class Task(abc.ABC, Generic[TaskResultT]):
# Return synthetic completed status
now = datetime.now(timezone.utc)
return GetTaskResult(
taskId=self._task_id,
task_id=self._task_id,
status="completed",
createdAt=now,
lastUpdatedAt=now,
created_at=now,
last_updated_at=now,
ttl=None,
pollInterval=1000,
poll_interval=1000,
)
# Return cached status if available (from notification)
@ -409,7 +409,7 @@ class ToolTask(Task["CallToolResult"]):
):
mcp_result = mcp_types.CallToolResult(
content=raw_result.content,
structuredContent=raw_result.structured_content,
structured_content=raw_result.structured_content,
_meta=raw_result.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
)
result = await self._client._parse_call_tool_result(

View file

@ -43,7 +43,7 @@ class CallToolRequestResult(CallToolResult):
return cls(
tool=tool,
arguments=arguments,
isError=result.is_error,
is_error=result.is_error,
content=result.content,
)
@ -128,7 +128,7 @@ class BulkToolCaller(MCPMixin):
return CallToolRequestResult(
tool=tool,
arguments=arguments,
isError=True,
is_error=True,
content=[
TextContent(
type="text",
@ -146,6 +146,6 @@ class BulkToolCaller(MCPMixin):
return CallToolRequestResult(
tool=tool,
arguments=arguments,
isError=result.is_error,
is_error=result.is_error,
content=result.content,
)

View file

@ -106,14 +106,14 @@ class ResourceContent(pydantic.BaseModel):
return mcp_types.TextResourceContents(
uri=AnyUrl(uri) if isinstance(uri, str) else uri,
text=self.content,
mimeType=self.mime_type or "text/plain",
mime_type=self.mime_type or "text/plain",
_meta=self.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
)
else:
return mcp_types.BlobResourceContents(
uri=AnyUrl(uri) if isinstance(uri, str) else uri,
blob=base64.b64encode(self.content).decode(),
mimeType=self.mime_type or "application/octet-stream",
mime_type=self.mime_type or "application/octet-stream",
_meta=self.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
)
@ -412,7 +412,7 @@ class Resource(FastMCPComponent):
name=overrides.get("name", self.name),
uri=overrides.get("uri", self.uri),
description=overrides.get("description", self.description),
mimeType=overrides.get("mimeType", self.mime_type),
mime_type=overrides.get("mimeType", self.mime_type),
title=overrides.get("title", self.title),
icons=overrides.get("icons", self.icons),
annotations=overrides.get("annotations", self.annotations),

View file

@ -323,9 +323,9 @@ class ResourceTemplate(FastMCPComponent):
return SDKResourceTemplate(
name=overrides.get("name", self.name),
uriTemplate=overrides.get("uriTemplate", self.uri_template),
uri_template=overrides.get("uriTemplate", self.uri_template),
description=overrides.get("description", self.description),
mimeType=overrides.get("mimeType", self.mime_type),
mime_type=overrides.get("mimeType", self.mime_type),
title=overrides.get("title", self.title),
icons=overrides.get("icons", self.icons),
annotations=overrides.get("annotations", self.annotations),

View file

@ -1189,7 +1189,7 @@ class Context:
# Standard request mode: use session.elicit directly
result = await self.session.elicit(
message=message,
requestedSchema=config.schema,
requested_schema=config.schema,
related_request_id=self.request_id,
)

View file

@ -115,7 +115,7 @@ class MCPOperationsMixin:
request.params.cursor if request is not None and request.params else None
)
page, next_cursor = _apply_pagination(sdk_tools, cursor, server._list_page_size)
return mcp_types.ListToolsResult(tools=page, nextCursor=next_cursor)
return mcp_types.ListToolsResult(tools=page, next_cursor=next_cursor)
async def _list_resources_mcp(
self, request: mcp_types.ListResourcesRequest
@ -138,7 +138,7 @@ class MCPOperationsMixin:
page, next_cursor = _apply_pagination(
sdk_resources, cursor, server._list_page_size
)
return mcp_types.ListResourcesResult(resources=page, nextCursor=next_cursor)
return mcp_types.ListResourcesResult(resources=page, next_cursor=next_cursor)
async def _list_resource_templates_mcp(
self, request: mcp_types.ListResourceTemplatesRequest
@ -154,7 +154,7 @@ class MCPOperationsMixin:
list(await server.list_resource_templates()), lambda t: t.uri_template
)
sdk_templates = [
template.to_mcp_template(uriTemplate=template.uri_template)
template.to_mcp_template(uri_template=template.uri_template)
for template in templates
]
cursor = request.params.cursor if request.params else None
@ -162,7 +162,7 @@ class MCPOperationsMixin:
sdk_templates, cursor, server._list_page_size
)
return mcp_types.ListResourceTemplatesResult(
resourceTemplates=page, nextCursor=next_cursor
resource_templates=page, next_cursor=next_cursor
)
async def _list_prompts_mcp(
@ -183,7 +183,7 @@ class MCPOperationsMixin:
page, next_cursor = _apply_pagination(
sdk_prompts, cursor, server._list_page_size
)
return mcp_types.ListPromptsResult(prompts=page, nextCursor=next_cursor)
return mcp_types.ListPromptsResult(prompts=page, next_cursor=next_cursor)
async def _call_tool_mcp(
self, key: str, arguments: dict[str, Any]

View file

@ -979,7 +979,7 @@ async def default_proxy_elicitation_handler(
)
result = await ctx.session.elicit(
message=message,
requestedSchema=requested_schema,
requested_schema=requested_schema,
related_request_id=ctx.request_id,
)
return ElicitResult(action=result.action, content=result.content)

View file

@ -222,13 +222,13 @@ async def call_sampling_handler(
result = context.fastmcp.sampling_handler(
messages,
SamplingParams(
systemPrompt=system_prompt,
system_prompt=system_prompt,
messages=messages,
temperature=temperature,
maxTokens=max_tokens,
modelPreferences=_parse_model_preferences(model_preferences),
max_tokens=max_tokens,
model_preferences=_parse_model_preferences(model_preferences),
tools=sdk_tools,
toolChoice=tool_choice,
tool_choice=tool_choice,
),
context.request_context,
)
@ -244,7 +244,7 @@ async def call_sampling_handler(
role="assistant",
content=TextContent(type="text", text=result),
model="unknown",
stopReason="endTurn",
stop_reason="endTurn",
)
return result
@ -287,14 +287,14 @@ async def execute_tools(
if tool is None:
return ToolResultContent(
type="tool_result",
toolUseId=tool_use.id,
tool_use_id=tool_use.id,
content=[
TextContent(
type="text",
text=f"Error: Unknown tool '{tool_use.name}'",
)
],
isError=True,
is_error=True,
)
tracer = get_tracer()
@ -309,7 +309,7 @@ async def execute_tools(
result_value = await tool.run(tool_use.input)
return ToolResultContent(
type="tool_result",
toolUseId=tool_use.id,
tool_use_id=tool_use.id,
content=[TextContent(type="text", text=str(result_value))],
)
except ToolError as e:
@ -324,9 +324,9 @@ async def execute_tools(
)
return ToolResultContent(
type="tool_result",
toolUseId=tool_use.id,
tool_use_id=tool_use.id,
content=[TextContent(type="text", text=str(e))],
isError=True,
is_error=True,
)
except Exception as e:
if span.is_recording():
@ -340,9 +340,9 @@ async def execute_tools(
error_text = f"Error executing tool '{tool_use.name}': {e}"
return ToolResultContent(
type="tool_result",
toolUseId=tool_use.id,
tool_use_id=tool_use.id,
content=[TextContent(type="text", text=error_text)],
isError=True,
is_error=True,
)
# Check if any tool requires sequential execution
@ -718,7 +718,7 @@ async def sample_impl(
content=[
ToolResultContent(
type="tool_result",
toolUseId=tool_call.id,
tool_use_id=tool_call.id,
content=[
TextContent(
type="text",
@ -728,7 +728,7 @@ async def sample_impl(
),
)
],
isError=True,
is_error=True,
)
],
)

View file

@ -77,7 +77,7 @@ class SamplingTool(FastMCPBaseModel):
return SDKTool(
name=self.name,
description=self.description,
inputSchema=self.parameters,
input_schema=self.parameters,
)
@classmethod

View file

@ -257,7 +257,7 @@ async def relay_elicitation(
try:
result = await session.elicit(
message=elicitation["message"],
requestedSchema=elicitation["requestedSchema"],
requested_schema=elicitation["requestedSchema"],
)
await handle_task_input(
task_id=task_id,

View file

@ -226,11 +226,11 @@ async def submit_to_docket(
# Tasks MUST begin in "working" status per SEP-1686 final spec (line 381)
return mcp_types.CreateTaskResult(
task=mcp_types.Task(
taskId=server_task_id,
task_id=server_task_id,
status="working",
createdAt=created_at,
lastUpdatedAt=created_at,
created_at=created_at,
last_updated_at=created_at,
ttl=ttl_ms,
pollInterval=poll_interval_ms,
poll_interval=poll_interval_ms,
)
)

View file

@ -198,13 +198,13 @@ async def tasks_get_handler(server: FastMCP, params: dict[str, Any]) -> GetTaskR
created_at_dt = datetime.now(timezone.utc)
return GetTaskResult(
taskId=client_task_id,
task_id=client_task_id,
status=mcp_state,
createdAt=created_at_dt,
lastUpdatedAt=datetime.now(timezone.utc),
created_at=created_at_dt,
last_updated_at=datetime.now(timezone.utc),
ttl=DEFAULT_TTL_MS,
pollInterval=poll_interval_ms,
statusMessage=status_message,
poll_interval=poll_interval_ms,
status_message=status_message,
)
@ -277,7 +277,7 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
# Task failed - return error result
return mcp_types.CallToolResult(
content=[mcp_types.TextContent(type="text", text=str(error))],
isError=True,
is_error=True,
_meta={ # type: ignore[call-arg] # _meta is Pydantic alias for meta field
"io.modelcontextprotocol/related-task": {
"taskId": client_task_id,
@ -337,7 +337,7 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
content, structured_content = mcp_result
mcp_result = mcp_types.CallToolResult(
content=content,
structuredContent=structured_content,
structured_content=structured_content,
_meta=related_task_meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
)
else:
@ -390,7 +390,7 @@ async def tasks_list_handler(
ListTasksResult: Response with tasks list and pagination
"""
# Return empty list - client tracks tasks locally
return ListTasksResult(tasks=[], nextCursor=None)
return ListTasksResult(tasks=[], next_cursor=None)
async def tasks_cancel_handler(
@ -438,13 +438,13 @@ async def tasks_cancel_handler(
# createdAt is REQUIRED per SEP-1686 final spec (line 430)
# Per spec lines 447-448: SHOULD NOT include related-task metadata in tasks/cancel
return CancelTaskResult(
taskId=client_task_id,
task_id=client_task_id,
status="cancelled",
createdAt=datetime.fromisoformat(created_at)
created_at=datetime.fromisoformat(created_at)
if created_at
else datetime.now(timezone.utc),
lastUpdatedAt=datetime.now(timezone.utc),
last_updated_at=datetime.now(timezone.utc),
ttl=DEFAULT_TTL_MS,
pollInterval=poll_interval_ms,
statusMessage="Task cancelled",
poll_interval=poll_interval_ms,
status_message="Task cancelled",
)

View file

@ -32,7 +32,7 @@ from fastmcp.server.transforms import GetToolNext, Transform
from fastmcp.tools.base import Tool
from fastmcp.utilities.versions import VersionSpec
_DEFAULT_ANNOTATIONS = ToolAnnotations(readOnlyHint=True)
_DEFAULT_ANNOTATIONS = ToolAnnotations(read_only_hint=True)
if TYPE_CHECKING:
from fastmcp.server.providers.base import Provider

View file

@ -167,9 +167,9 @@ class ToolResult(BaseModel):
# reaches the client; the plain content/tuple returns can't carry it.
if self.meta is not None or self.is_error:
return CallToolResult(
structuredContent=self.structured_content,
structured_content=self.structured_content,
content=self.content,
isError=self.is_error,
is_error=self.is_error,
_meta=self.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
)
if self.structured_content is None:
@ -235,8 +235,8 @@ class Tool(FastMCPComponent):
name=overrides.get("name", self.name),
title=overrides.get("title", title),
description=overrides.get("description", self.description),
inputSchema=overrides.get("inputSchema", self.parameters),
outputSchema=overrides.get("outputSchema", self.output_schema),
input_schema=overrides.get("inputSchema", self.parameters),
output_schema=overrides.get("outputSchema", self.output_schema),
icons=overrides.get("icons", self.icons),
annotations=overrides.get("annotations", self.annotations),
execution=overrides.get("execution", self.execution),
@ -250,7 +250,7 @@ class Tool(FastMCPComponent):
and "execution" not in overrides
and not self.execution
):
mcp_tool.execution = ToolExecution(taskSupport=self.task_config.mode)
mcp_tool.execution = ToolExecution(task_support=self.task_config.mode)
return mcp_tool

View file

@ -297,7 +297,7 @@ class Image:
return mcp_types.ImageContent(
type="image",
data=data,
mimeType=mime_type or self._mime_type,
mime_type=mime_type or self._mime_type,
annotations=annotations or self.annotations,
)
@ -360,7 +360,7 @@ class Audio:
return mcp_types.AudioContent(
type="audio",
data=data,
mimeType=mime_type or self._mime_type,
mime_type=mime_type or self._mime_type,
annotations=annotations or self.annotations,
)
@ -433,14 +433,14 @@ class File:
text = raw_data.decode("latin-1")
resource = mcp_types.TextResourceContents(
text=text,
mimeType=mime,
mime_type=mime,
uri=uri,
)
else:
data = base64.b64encode(raw_data).decode()
resource = mcp_types.BlobResourceContents(
blob=data,
mimeType=mime,
mime_type=mime,
uri=uri,
)

View file

@ -170,8 +170,8 @@ class TestFormatToolSignature:
return mcp_types.Tool(
name=name,
description=description,
inputSchema=input_schema,
outputSchema=output_schema,
input_schema=input_schema,
output_schema=output_schema,
)
def test_no_params(self):

View file

@ -150,7 +150,7 @@ class TestToolFunctionSource:
def test_required_param(self):
tool = mcp_types.Tool(
name="greet",
inputSchema={
input_schema={
"properties": {"name": {"type": "string", "description": "Who"}},
"required": ["name"],
},
@ -164,7 +164,7 @@ class TestToolFunctionSource:
def test_optional_param(self):
tool = mcp_types.Tool(
name="search",
inputSchema={
input_schema={
"properties": {
"query": {"type": "string", "description": "Search query"},
"limit": {"type": "integer", "description": "Max results"},
@ -180,7 +180,7 @@ class TestToolFunctionSource:
def test_param_with_default(self):
tool = mcp_types.Tool(
name="fetch",
inputSchema={
input_schema={
"properties": {
"url": {"type": "string", "description": "URL"},
"timeout": {
@ -199,7 +199,7 @@ class TestToolFunctionSource:
def test_no_params(self):
tool = mcp_types.Tool(
name="ping",
inputSchema={"properties": {}},
input_schema={"properties": {}},
)
source = _tool_function_source(tool)
assert "async def ping(" in source
@ -208,7 +208,7 @@ class TestToolFunctionSource:
def test_preserves_underscores(self):
tool = mcp_types.Tool(
name="get_forecast",
inputSchema={
input_schema={
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
@ -219,7 +219,7 @@ class TestToolFunctionSource:
def test_sanitizes_tool_name(self):
tool = mcp_types.Tool(
name="my.tool/v2",
inputSchema={"properties": {}},
input_schema={"properties": {}},
)
source = _tool_function_source(tool)
assert "async def my_tool_v2(" in source
@ -228,7 +228,7 @@ class TestToolFunctionSource:
def test_sanitizes_param_name(self):
tool = mcp_types.Tool(
name="fetch",
inputSchema={
input_schema={
"properties": {"content-type": {"type": "string", "description": "CT"}},
"required": ["content-type"],
},
@ -241,7 +241,7 @@ class TestToolFunctionSource:
tool = mcp_types.Tool(
name="greet",
description="Say hello to someone.",
inputSchema={
input_schema={
"properties": {"name": {"type": "string"}},
"required": ["name"],
},
@ -253,7 +253,7 @@ class TestToolFunctionSource:
tool = mcp_types.Tool(
name="fetch",
description="Fetch data from 'source' API.",
inputSchema={
input_schema={
"properties": {"url": {"type": "string"}},
"required": ["url"],
},
@ -268,7 +268,7 @@ class TestToolFunctionSource:
tool = mcp_types.Tool(
name="tag_items",
description="Tag multiple items.",
inputSchema={
input_schema={
"properties": {
"item_id": {"type": "string"},
"tags": {"type": "array", "items": {"type": "string"}},
@ -288,7 +288,7 @@ class TestToolFunctionSource:
tool = mcp_types.Tool(
name="create_user",
description="Create a user.",
inputSchema={
input_schema={
"properties": {
"name": {"type": "string"},
"metadata": {
@ -321,7 +321,7 @@ class TestToolFunctionSource:
tool = mcp_types.Tool(
name="batch_process",
description="Process batches.",
inputSchema={
input_schema={
"properties": {
"batches": {
"type": "array",
@ -348,7 +348,7 @@ class TestToolFunctionSource:
"""Test that complex types with defaults are JSON-serialized."""
tool = mcp_types.Tool(
name="configure",
inputSchema={
input_schema={
"properties": {
"options": {
"type": "object",
@ -369,7 +369,7 @@ class TestToolFunctionSource:
"""Test that parameter name collisions are detected."""
tool = mcp_types.Tool(
name="test",
inputSchema={
input_schema={
"properties": {
"content-type": {"type": "string"},
"content_type": {"type": "string"},
@ -414,7 +414,7 @@ class TestGenerateCliScript:
mcp_types.Tool(
name="greet",
description="Say hello",
inputSchema={
input_schema={
"properties": {
"name": {"type": "string", "description": "Who to greet"},
},
@ -424,7 +424,7 @@ class TestGenerateCliScript:
mcp_types.Tool(
name="add_numbers",
description="Add two numbers",
inputSchema={
input_schema={
"properties": {
"a": {"type": "integer", "description": "First number"},
"b": {"type": "integer", "description": "Second number"},
@ -510,7 +510,7 @@ class TestGenerateCliScript:
mcp_types.Tool(
name="my.tool/v2",
description="A tool with dots and slashes",
inputSchema={
input_schema={
"properties": {
"content-type": {"type": "string", "description": "CT"},
},
@ -774,7 +774,7 @@ class TestGenerateSkillContent:
mcp_types.Tool(
name="greet",
description="Say hello",
inputSchema={
input_schema={
"type": "object",
"properties": {
"name": {"type": "string", "description": "Who to greet"}
@ -796,7 +796,7 @@ class TestGenerateSkillContent:
tools = [
mcp_types.Tool(
name="greet",
inputSchema={"type": "object", "properties": {}},
input_schema={"type": "object", "properties": {}},
),
]
content = generate_skill_content("weather", "cli.py", tools)
@ -807,7 +807,7 @@ class TestGenerateSkillContent:
mcp_types.Tool(
name="search",
description="Search things",
inputSchema={
input_schema={
"type": "object",
"properties": {
"query": {"type": "string"},
@ -827,7 +827,7 @@ class TestGenerateSkillContent:
mcp_types.Tool(
name="create",
description="Create item",
inputSchema={
input_schema={
"type": "object",
"properties": {
"data": {
@ -847,7 +847,7 @@ class TestGenerateSkillContent:
mcp_types.Tool(
name="ping",
description="Ping the server",
inputSchema={"type": "object", "properties": {}},
input_schema={"type": "object", "properties": {}},
),
]
content = generate_skill_content("test", "cli.py", tools)
@ -866,7 +866,7 @@ class TestGenerateSkillContent:
mcp_types.Tool(
name="test",
description="Test",
inputSchema={
input_schema={
"type": "object",
"properties": {
"mode": {"type": "string", "description": "a|b|c"},
@ -882,7 +882,7 @@ class TestGenerateSkillContent:
mcp_types.Tool(
name="test",
description="Test",
inputSchema={
input_schema={
"type": "object",
"properties": {
"val": {"type": ["string", "null"]},
@ -899,7 +899,7 @@ class TestGenerateSkillContent:
mcp_types.Tool(
name="run",
description="Run something",
inputSchema={
input_schema={
"type": "object",
"properties": {
"verbose": {"type": "boolean", "description": "Verbose output"},

View file

@ -207,7 +207,7 @@ class TestParseToolResultEdgeCases:
"""
async def test_error_with_empty_content_raises_with_fallback_message(self):
result = mcp_types.CallToolResult(content=[], isError=True)
result = mcp_types.CallToolResult(content=[], is_error=True)
with pytest.raises(ToolError, match="Tool 'my_tool' returned an error"):
await _parse_call_tool_result(
@ -221,9 +221,9 @@ class TestParseToolResultEdgeCases:
async def test_error_with_non_text_content_raises_with_fallback_message(self):
result = mcp_types.CallToolResult(
content=[
mcp_types.ImageContent(type="image", data="abc", mimeType="image/png")
mcp_types.ImageContent(type="image", data="abc", mime_type="image/png")
],
isError=True,
is_error=True,
)
with pytest.raises(ToolError, match="Tool 'my_tool' returned an error"):
@ -238,7 +238,7 @@ class TestParseToolResultEdgeCases:
async def test_error_with_text_content_raises_with_message(self):
result = mcp_types.CallToolResult(
content=[mcp_types.TextContent(type="text", text="custom error msg")],
isError=True,
is_error=True,
)
with pytest.raises(ToolError, match="custom error msg"):
@ -253,8 +253,8 @@ class TestParseToolResultEdgeCases:
async def test_error_with_structured_content_does_not_parse_data(self):
result = mcp_types.CallToolResult(
content=[mcp_types.TextContent(type="text", text="error happened")],
isError=True,
structuredContent={"key": "value"},
is_error=True,
structured_content={"key": "value"},
)
parsed = await _parse_call_tool_result(

View file

@ -46,7 +46,7 @@ def test_convert_sampling_messages_to_anthropic_messages():
def test_image_content_to_anthropic_block():
block = _image_content_to_anthropic_block(
ImageContent(type="image", data="YWJj", mimeType="image/png")
ImageContent(type="image", data="YWJj", mime_type="image/png")
)
assert block == {
@ -62,7 +62,7 @@ def test_image_content_to_anthropic_block():
def test_image_content_unsupported_mime_type_raises():
with pytest.raises(ValueError, match="Unsupported image MIME type"):
_image_content_to_anthropic_block(
ImageContent(type="image", data="YWJj", mimeType="image/bmp")
ImageContent(type="image", data="YWJj", mime_type="image/bmp")
)
@ -71,7 +71,7 @@ def test_convert_single_image_content_to_anthropic_message():
messages=[
SamplingMessage(
role="user",
content=ImageContent(type="image", data="YWJj", mimeType="image/png"),
content=ImageContent(type="image", data="YWJj", mime_type="image/png"),
)
],
)
@ -99,7 +99,7 @@ def test_convert_single_audio_content_raises():
SamplingMessage(
role="user",
content=AudioContent(
type="audio", data="YWJj", mimeType="audio/wav"
type="audio", data="YWJj", mime_type="audio/wav"
),
)
],
@ -113,7 +113,7 @@ def test_convert_list_content_with_image_and_text():
role="user",
content=[
TextContent(type="text", text="Describe this image"),
ImageContent(type="image", data="YWJj", mimeType="image/jpeg"),
ImageContent(type="image", data="YWJj", mime_type="image/jpeg"),
],
)
],
@ -144,7 +144,7 @@ def test_convert_list_content_with_audio_raises():
role="user",
content=[
TextContent(type="text", text="Listen to this"),
AudioContent(type="audio", data="YWJj", mimeType="audio/wav"),
AudioContent(type="audio", data="YWJj", mime_type="audio/wav"),
],
)
],
@ -158,7 +158,7 @@ def test_convert_image_in_assistant_message_raises():
SamplingMessage(
role="assistant",
content=ImageContent(
type="image", data="YWJj", mimeType="image/png"
type="image", data="YWJj", mime_type="image/png"
),
)
],
@ -173,7 +173,7 @@ def test_convert_list_image_in_assistant_message_raises():
role="assistant",
content=[
TextContent(type="text", text="Here's the image"),
ImageContent(type="image", data="YWJj", mimeType="image/png"),
ImageContent(type="image", data="YWJj", mime_type="image/png"),
],
)
],
@ -302,7 +302,7 @@ def test_convert_tools_to_anthropic():
Tool(
name="get_weather",
description="Get the current weather",
inputSchema={
input_schema={
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
@ -358,7 +358,7 @@ def test_convert_messages_with_tool_result_content():
role="user",
content=ToolResultContent(
type="tool_result",
toolUseId="toolu_123",
tool_use_id="toolu_123",
content=[TextContent(type="text", text="72F and sunny")],
),
),
@ -387,7 +387,7 @@ def test_convert_messages_raises_on_unsupported_content_type():
embedded = EmbeddedResource(
type="resource",
resource=TextResourceContents(
uri=AnyUrl("file:///test.txt"), text="hello", mimeType="text/plain"
uri=AnyUrl("file:///test.txt"), text="hello", mime_type="text/plain"
),
)
# Must be inside a list content — single-content messages hit a

View file

@ -66,7 +66,7 @@ def test_convert_sampling_messages_to_google_genai_content():
def test_convert_single_image_content_to_google_genai():
part = _sampling_content_to_google_genai_part(
ImageContent(type="image", data="YWJj", mimeType="image/png")
ImageContent(type="image", data="YWJj", mime_type="image/png")
)
assert part.inline_data is not None
@ -76,7 +76,7 @@ def test_convert_single_image_content_to_google_genai():
def test_convert_single_audio_content_to_google_genai():
part = _sampling_content_to_google_genai_part(
AudioContent(type="audio", data="YWJj", mimeType="audio/wav")
AudioContent(type="audio", data="YWJj", mime_type="audio/wav")
)
assert part.inline_data is not None
@ -89,7 +89,7 @@ def test_convert_image_message_to_google_genai_content():
messages=[
SamplingMessage(
role="user",
content=ImageContent(type="image", data="YWJj", mimeType="image/jpeg"),
content=ImageContent(type="image", data="YWJj", mime_type="image/jpeg"),
)
],
)
@ -105,7 +105,7 @@ def test_convert_audio_message_to_google_genai_content():
messages=[
SamplingMessage(
role="user",
content=AudioContent(type="audio", data="YWJj", mimeType="audio/mp3"),
content=AudioContent(type="audio", data="YWJj", mime_type="audio/mp3"),
)
],
)
@ -123,7 +123,7 @@ def test_convert_list_content_with_image_and_text():
role="user",
content=[
TextContent(type="text", text="What is in this image?"),
ImageContent(type="image", data="YWJj", mimeType="image/png"),
ImageContent(type="image", data="YWJj", mime_type="image/png"),
],
)
],
@ -144,7 +144,7 @@ def test_convert_list_content_with_audio_and_text():
role="user",
content=[
TextContent(type="text", text="Transcribe this audio"),
AudioContent(type="audio", data="YWJj", mimeType="audio/wav"),
AudioContent(type="audio", data="YWJj", mime_type="audio/wav"),
],
)
],
@ -243,7 +243,7 @@ def test_sampling_content_to_google_genai_part_tool_result():
"""Test converting ToolResultContent to Google GenAI Part with FunctionResponse."""
content = ToolResultContent(
type="tool_result",
toolUseId="get_weather_abc123",
tool_use_id="get_weather_abc123",
content=[TextContent(type="text", text="Weather is sunny")],
)
@ -259,7 +259,7 @@ def test_sampling_content_to_google_genai_part_tool_result_empty():
"""Test converting empty ToolResultContent to Google GenAI Part."""
content = ToolResultContent(
type="tool_result",
toolUseId="my_tool_xyz789",
tool_use_id="my_tool_xyz789",
content=[],
)
@ -274,7 +274,7 @@ def test_sampling_content_to_google_genai_part_tool_result_no_underscore():
"""Test ToolResultContent when toolUseId has no underscore (fallback)."""
content = ToolResultContent(
type="tool_result",
toolUseId="simplefunction",
tool_use_id="simplefunction",
content=[TextContent(type="text", text="Result")],
)
@ -320,7 +320,7 @@ def test_convert_messages_with_tool_result():
role="user",
content=ToolResultContent(
type="tool_result",
toolUseId="get_weather_123",
tool_use_id="get_weather_123",
content=[TextContent(type="text", text="Sunny, 72F")],
),
),
@ -343,7 +343,7 @@ def test_convert_messages_with_multiple_content_blocks():
TextContent(type="text", text="I need weather info."),
ToolResultContent(
type="tool_result",
toolUseId="get_weather_xyz",
tool_use_id="get_weather_xyz",
content=[TextContent(type="text", text="Cloudy")],
),
],

View file

@ -58,7 +58,7 @@ def test_convert_sampling_messages_to_openai_messages():
def test_image_content_to_openai_part():
part = _image_content_to_openai_part(
ImageContent(type="image", data="YWJj", mimeType="image/png")
ImageContent(type="image", data="YWJj", mime_type="image/png")
)
assert part == ChatCompletionContentPartImageParam(
@ -69,7 +69,7 @@ def test_image_content_to_openai_part():
def test_audio_content_to_openai_part_wav():
part = _audio_content_to_openai_part(
AudioContent(type="audio", data="YWJj", mimeType="audio/wav")
AudioContent(type="audio", data="YWJj", mime_type="audio/wav")
)
assert part == ChatCompletionContentPartInputAudioParam(
@ -80,7 +80,7 @@ def test_audio_content_to_openai_part_wav():
def test_audio_content_to_openai_part_mp3():
part = _audio_content_to_openai_part(
AudioContent(type="audio", data="YWJj", mimeType="audio/mpeg")
AudioContent(type="audio", data="YWJj", mime_type="audio/mpeg")
)
assert part["input_audio"]["format"] == "mp3"
@ -89,14 +89,14 @@ def test_audio_content_to_openai_part_mp3():
def test_audio_content_to_openai_part_unsupported_raises():
with pytest.raises(ValueError, match="Unsupported audio MIME type"):
_audio_content_to_openai_part(
AudioContent(type="audio", data="YWJj", mimeType="audio/ogg")
AudioContent(type="audio", data="YWJj", mime_type="audio/ogg")
)
def test_image_content_to_openai_part_unsupported_raises():
with pytest.raises(ValueError, match="Unsupported image MIME type"):
_image_content_to_openai_part(
ImageContent(type="image", data="YWJj", mimeType="image/bmp")
ImageContent(type="image", data="YWJj", mime_type="image/bmp")
)
@ -106,7 +106,7 @@ def test_convert_single_image_content_to_openai_message():
messages=[
SamplingMessage(
role="user",
content=ImageContent(type="image", data="YWJj", mimeType="image/png"),
content=ImageContent(type="image", data="YWJj", mime_type="image/png"),
)
],
)
@ -129,7 +129,7 @@ def test_convert_single_audio_content_to_openai_message():
messages=[
SamplingMessage(
role="user",
content=AudioContent(type="audio", data="YWJj", mimeType="audio/wav"),
content=AudioContent(type="audio", data="YWJj", mime_type="audio/wav"),
)
],
)
@ -154,7 +154,7 @@ def test_convert_list_content_with_image_and_text():
role="user",
content=[
TextContent(type="text", text="What is in this image?"),
ImageContent(type="image", data="YWJj", mimeType="image/jpeg"),
ImageContent(type="image", data="YWJj", mime_type="image/jpeg"),
],
)
],
@ -183,7 +183,7 @@ def test_convert_image_in_assistant_message_raises():
SamplingMessage(
role="assistant",
content=ImageContent(
type="image", data="YWJj", mimeType="image/png"
type="image", data="YWJj", mime_type="image/png"
),
)
],
@ -198,7 +198,7 @@ def test_convert_audio_in_assistant_message_raises():
SamplingMessage(
role="assistant",
content=AudioContent(
type="audio", data="YWJj", mimeType="audio/wav"
type="audio", data="YWJj", mime_type="audio/wav"
),
)
],
@ -215,7 +215,7 @@ def test_convert_list_image_in_assistant_message_raises():
role="assistant",
content=[
TextContent(type="text", text="Here's the image"),
ImageContent(type="image", data="YWJj", mimeType="image/png"),
ImageContent(type="image", data="YWJj", mime_type="image/png"),
],
)
],
@ -237,7 +237,7 @@ def test_convert_list_tool_calls_with_image_raises():
name="my_tool",
input={"arg": "val"},
),
ImageContent(type="image", data="YWJj", mimeType="image/png"),
ImageContent(type="image", data="YWJj", mime_type="image/png"),
],
)
],
@ -284,7 +284,7 @@ async def test_handler_passes_max_completion_tokens():
messages = [
SamplingMessage(role="user", content=TextContent(type="text", text="hello"))
]
params = CreateMessageRequestParams(messages=messages, maxTokens=300)
params = CreateMessageRequestParams(messages=messages, max_tokens=300)
await handler(messages, params, context=None) # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
call_kwargs = mock_client.chat.completions.create.call_args
@ -331,7 +331,7 @@ def test_convert_messages_raises_on_unsupported_content_type():
embedded = EmbeddedResource(
type="resource",
resource=TextResourceContents(
uri=AnyUrl("file:///test.txt"), text="hello", mimeType="text/plain"
uri=AnyUrl("file:///test.txt"), text="hello", mime_type="text/plain"
),
)
msg = SamplingMessage.model_construct(

View file

@ -218,11 +218,11 @@ async def test_wait_returns_on_input_required(task_notification_server):
# Directly inject an input_required status into the cache and signal the event
now = datetime.now(timezone.utc)
input_required_status = GetTaskResult(
taskId=task._task_id,
task_id=task._task_id,
status="input_required",
statusMessage="Waiting for user input",
createdAt=now,
lastUpdatedAt=now,
status_message="Waiting for user input",
created_at=now,
last_updated_at=now,
ttl=None,
)
task._status_cache = input_required_status

View file

@ -36,7 +36,7 @@ class TestSamplingResultType:
)
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -96,7 +96,7 @@ class TestSamplingResultType:
)
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
else:
# Second call: call final_response
@ -115,7 +115,7 @@ class TestSamplingResultType:
)
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -168,7 +168,7 @@ class TestSamplingResultType:
)
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
else:
# Second call: valid type after seeing error
@ -183,7 +183,7 @@ class TestSamplingResultType:
)
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -237,7 +237,7 @@ class TestSamplingResultType:
role="assistant",
content=[TextContent(type="text", text="Hello world")],
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -271,7 +271,7 @@ class TestSampleStep:
role="assistant",
content=[TextContent(type="text", text="Hello from step")],
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -316,14 +316,14 @@ class TestSampleStep:
)
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
else:
return CreateMessageResultWithTools(
role="assistant",
content=[TextContent(type="text", text="Done")],
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -373,7 +373,7 @@ class TestSampleStep:
)
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -416,7 +416,7 @@ class TestSampleStep:
)
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -454,7 +454,7 @@ class TestTextResponseRetry:
role="assistant",
content=[TextContent(type="text", text=text)],
model="m",
stopReason="endTurn",
stop_reason="endTurn",
)
@staticmethod
@ -472,7 +472,7 @@ class TestTextResponseRetry:
)
],
model="m",
stopReason="toolUse",
stop_reason="toolUse",
)
async def test_text_response_then_success(self):
@ -562,7 +562,7 @@ def _final_response(call_id: str, input_data: dict) -> CreateMessageResultWithTo
)
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
@ -576,7 +576,7 @@ def _tool_call(
ToolUseContent(type="tool_use", id=call_id, name=name, input=input_data)
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)

View file

@ -42,7 +42,7 @@ class TestAutomaticToolLoop:
)
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
else:
# Second call: return final response
@ -50,7 +50,7 @@ class TestAutomaticToolLoop:
role="assistant",
content=[TextContent(type="text", text="The weather is sunny!")],
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -110,14 +110,14 @@ class TestAutomaticToolLoop:
),
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
else:
return CreateMessageResultWithTools(
role="assistant",
content=[TextContent(type="text", text="Done!")],
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -165,14 +165,14 @@ class TestAutomaticToolLoop:
)
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
else:
return CreateMessageResultWithTools(
role="assistant",
content=[TextContent(type="text", text="Handled error")],
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -239,14 +239,14 @@ class TestAutomaticToolLoop:
)
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
else:
return CreateMessageResultWithTools(
role="assistant",
content=[TextContent(type="text", text="Handled error")],
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -333,14 +333,14 @@ class TestAutomaticToolLoop:
),
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
else:
return CreateMessageResultWithTools(
role="assistant",
content=[TextContent(type="text", text="Done!")],
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -411,14 +411,14 @@ class TestAutomaticToolLoop:
),
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
else:
return CreateMessageResultWithTools(
role="assistant",
content=[TextContent(type="text", text="Done!")],
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -491,14 +491,14 @@ class TestAutomaticToolLoop:
),
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
else:
return CreateMessageResultWithTools(
role="assistant",
content=[TextContent(type="text", text="Done!")],
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -577,14 +577,14 @@ class TestAutomaticToolLoop:
),
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
else:
return CreateMessageResultWithTools(
role="assistant",
content=[TextContent(type="text", text="Done!")],
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -651,14 +651,14 @@ class TestAutomaticToolLoop:
),
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
else:
return CreateMessageResultWithTools(
role="assistant",
content=[TextContent(type="text", text="Handled errors")],
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
mcp = FastMCP(sampling_handler=sampling_handler)
@ -732,14 +732,14 @@ class TestAutomaticToolLoop:
),
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
else:
return CreateMessageResultWithTools(
role="assistant",
content=[TextContent(type="text", text="Done!")],
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
mcp = FastMCP(sampling_handler=sampling_handler)

View file

@ -78,7 +78,7 @@ async def test_embedded_resource() -> list:
type="resource",
resource=mcp_types.TextResourceContents(
uri=AnyUrl("test://embedded-resource"),
mimeType="text/plain",
mime_type="text/plain",
text="This is an embedded resource content.",
),
)
@ -93,13 +93,13 @@ async def test_multiple_content_types() -> list:
ImageContent(
type="image",
data=base64.b64encode(_1X1_PNG).decode(),
mimeType="image/png",
mime_type="image/png",
),
EmbeddedResource(
type="resource",
resource=mcp_types.TextResourceContents(
uri=AnyUrl("test://mixed-content-resource"),
mimeType="application/json",
mime_type="application/json",
text='{"test":"data","value":123}',
),
),
@ -185,7 +185,7 @@ async def test_elicitation_sep1330_enums(ctx: Context) -> str:
"""Tests elicitation with enum schema improvements per SEP-1330."""
result = await ctx.session.elicit(
message="Please select options from the enum fields",
requestedSchema={
requested_schema={
"type": "object",
"properties": {
"untitledSingle": {
@ -349,7 +349,7 @@ async def test_prompt_with_embedded_resource(resourceUri: str) -> list:
type="resource",
resource=mcp_types.TextResourceContents(
uri=AnyUrl(resourceUri),
mimeType="text/plain",
mime_type="text/plain",
text=f"Content of resource {resourceUri}",
),
)
@ -365,7 +365,7 @@ async def test_prompt_with_image() -> list:
ImageContent(
type="image",
data=base64.b64encode(_1X1_PNG).decode(),
mimeType="image/png",
mime_type="image/png",
)
),
Message("Please analyze the image above."),

View file

@ -239,12 +239,14 @@ def tool_server():
def mixed_content_tool() -> list[TextContent | ImageContent | EmbeddedResource]:
return [
TextContent(type="text", text="Hello"),
ImageContent(type="image", data="abc", mimeType="application/octet-stream"),
ImageContent(
type="image", data="abc", mime_type="application/octet-stream"
),
EmbeddedResource(
type="resource",
resource=BlobResourceContents(
blob=base64.b64encode(b"abc").decode(),
mimeType="application/octet-stream",
mime_type="application/octet-stream",
uri=AnyUrl("file:///test.bin"),
),
),

View file

@ -31,7 +31,7 @@ def error_tool_result_factory(arg1: str) -> CallToolRequestResult:
"Error calling tool 'error_tool': Error in tool with arg1: " + arg1
)
return CallToolRequestResult(
isError=True,
is_error=True,
content=[TextContent(text=formatted_error_text, type="text")],
tool="error_tool",
arguments={"arg1": arg1},
@ -46,7 +46,7 @@ async def echo_tool(arg1: str) -> str:
def echo_tool_result_factory(arg1: str) -> CallToolRequestResult:
"""A tool that returns a result based on the input arguments."""
return CallToolRequestResult(
isError=False,
is_error=False,
content=[TextContent(text=f"{arg1}", type="text")],
tool="echo_tool",
arguments={"arg1": arg1},
@ -60,7 +60,7 @@ async def no_return_tool(arg1: str) -> None:
def no_return_tool_result_factory(arg1: str) -> CallToolRequestResult:
"""A tool that returns a result based on the input arguments."""
return CallToolRequestResult(
isError=False,
is_error=False,
content=[],
tool="no_return_tool",
arguments={"arg1": arg1},
@ -147,7 +147,7 @@ async def test_call_tool_bulk_error_stops(bulk_caller_live: BulkToolCaller):
text="Error calling tool 'error_tool': Error in tool with arg1: error_value",
)
],
isError=True,
is_error=True,
tool="error_tool",
arguments={"arg1": "error_value"},
)
@ -174,7 +174,7 @@ async def test_call_tool_bulk_error_continues(bulk_caller_live: BulkToolCaller):
text="Error calling tool 'error_tool': Error in tool with arg1: error_value",
)
],
isError=True,
is_error=True,
tool="error_tool",
arguments={"arg1": "error_value"},
),
@ -249,7 +249,7 @@ async def test_call_tools_bulk_error_stops(bulk_caller_live: BulkToolCaller):
text="Error calling tool 'error_tool': Error in tool with arg1: error_value",
)
],
isError=True,
is_error=True,
tool="error_tool",
arguments={"arg1": "error_value"},
)
@ -275,7 +275,7 @@ async def test_call_tools_bulk_error_continues(bulk_caller_live: BulkToolCaller)
text="Error calling tool 'error_tool': Error in tool with arg1: error_value",
)
],
isError=True,
is_error=True,
tool="error_tool",
arguments={"arg1": "error_value"},
),
@ -309,7 +309,7 @@ async def test_call_tools_bulk_blocks_self_invocation(bulk_caller_live: BulkTool
),
)
],
isError=True,
is_error=True,
tool="call_tools_bulk",
arguments={"tool_calls": []},
),
@ -341,7 +341,7 @@ async def test_call_tool_bulk_blocks_self_invocation(bulk_caller_live: BulkToolC
),
)
],
isError=True,
is_error=True,
tool="call_tool_bulk",
arguments={"arg1": "value1"},
)

View file

@ -656,7 +656,7 @@ async def test_code_mode_execute_non_text_content_stringified() -> None:
@mcp.tool
def image_tool() -> ImageContent:
return ImageContent(type="image", data="base64data", mimeType="image/png")
return ImageContent(type="image", data="base64data", mime_type="image/png")
mcp.add_transform(CodeMode(sandbox_provider=_UnsafeTestSandboxProvider()))

View file

@ -114,7 +114,7 @@ class TestRenderPrompt:
resource=TextResourceContents(
uri=FileUrl("file://file.txt"),
text="File contents",
mimeType="text/plain",
mime_type="text/plain",
),
),
role="user",
@ -130,7 +130,7 @@ class TestRenderPrompt:
resource=TextResourceContents(
uri=FileUrl("file://file.txt"),
text="File contents",
mimeType="text/plain",
mime_type="text/plain",
),
),
role="user",
@ -149,7 +149,7 @@ class TestRenderPrompt:
resource=TextResourceContents(
uri=FileUrl("file://file.txt"),
text="File contents",
mimeType="text/plain",
mime_type="text/plain",
),
),
role="user",
@ -167,7 +167,7 @@ class TestRenderPrompt:
resource=TextResourceContents(
uri=FileUrl("file://file.txt"),
text="File contents",
mimeType="text/plain",
mime_type="text/plain",
),
),
role="user",
@ -186,7 +186,7 @@ class TestRenderPrompt:
resource=TextResourceContents(
uri=FileUrl("file://file.txt"),
text="File contents",
mimeType="text/plain",
mime_type="text/plain",
),
),
role="user",
@ -202,7 +202,7 @@ class TestRenderPrompt:
resource=TextResourceContents(
uri=FileUrl("file://file.txt"),
text="File contents",
mimeType="text/plain",
mime_type="text/plain",
),
),
role="user",
@ -628,7 +628,7 @@ class TestMessage:
"""Test Message passes through ImageContent without JSON serialization."""
from mcp_types import ImageContent
img = ImageContent(type="image", data="base64data", mimeType="image/png")
img = ImageContent(type="image", data="base64data", mime_type="image/png")
msg = Message(img, role="user")
assert isinstance(msg.content, ImageContent)
assert msg.content.data == "base64data"
@ -638,7 +638,7 @@ class TestMessage:
"""Test Message passes through AudioContent without JSON serialization."""
from mcp_types import AudioContent
audio = AudioContent(type="audio", data="base64audio", mimeType="audio/wav")
audio = AudioContent(type="audio", data="base64audio", mime_type="audio/wav")
msg = Message(audio, role="user")
assert isinstance(msg.content, AudioContent)
assert msg.content.data == "base64audio"
@ -648,7 +648,7 @@ class TestMessage:
"""Test that ImageContent round-trips through to_mcp_prompt_message."""
from mcp_types import ImageContent
img = ImageContent(type="image", data="base64data", mimeType="image/png")
img = ImageContent(type="image", data="base64data", mime_type="image/png")
msg = Message(img, role="user")
mcp_msg = msg.to_mcp_prompt_message()
assert isinstance(mcp_msg.content, ImageContent)

View file

@ -46,7 +46,7 @@ class TestResourceTemplateFieldPreservation:
name="t",
title="Human Title",
meta={"owner": "team-a"},
icons=[Icon(src="https://example.com/icon.png", mimeType="image/png")],
icons=[Icon(src="https://example.com/icon.png", mime_type="image/png")],
annotations=Annotations(priority=0.5, audience=["user"]),
)
@ -84,7 +84,7 @@ class TestResourceTemplateFieldPreservation:
"data://{param}",
title="Sub Title",
meta={"owner": "team-b"},
icons=[Icon(src="https://example.com/s.png", mimeType="image/png")],
icons=[Icon(src="https://example.com/s.png", mime_type="image/png")],
annotations=Annotations(priority=0.9),
tags={"alpha"},
)

View file

@ -157,7 +157,7 @@ class TestEnhancedAuthorizationHandler:
# Create FastMCP server with custom branding
mcp = FastMCP(
"My Custom Server",
icons=[Icon(src="https://example.com/icon.png", mimeType="image/png")],
icons=[Icon(src="https://example.com/icon.png", mime_type="image/png")],
)
# Create app with OAuth routes

View file

@ -127,7 +127,7 @@ class TestResponseLimitingMiddleware:
def binary_tool() -> ToolResult:
return ToolResult(
content=[
ImageContent(type="image", data="x" * 10_000, mimeType="image/png")
ImageContent(type="image", data="x" * 10_000, mime_type="image/png")
]
)

View file

@ -147,7 +147,7 @@ def fastmcp_server():
content=mcp_types.ImageContent(
type="image",
data="iVBORw0KGgoAAAANSUhEUg==",
mimeType="image/png",
mime_type="image/png",
),
role="user",
),
@ -389,10 +389,10 @@ class TestTools:
error_result = mcp_types.CallToolResult(
content=[
mcp_types.ImageContent(
type="image", data="abc123", mimeType="image/png"
type="image", data="abc123", mime_type="image/png"
)
],
isError=True,
is_error=True,
)
with patch.object(
Client, "call_tool_mcp", new_callable=AsyncMock, return_value=error_result
@ -405,7 +405,7 @@ class TestTools:
"""Error responses with empty content should not crash."""
error_result = mcp_types.CallToolResult(
content=[],
isError=True,
is_error=True,
)
with patch.object(
Client, "call_tool_mcp", new_callable=AsyncMock, return_value=error_result
@ -419,11 +419,11 @@ class TestTools:
error_result = mcp_types.CallToolResult(
content=[
mcp_types.ImageContent(
type="image", data="abc123", mimeType="image/png"
type="image", data="abc123", mime_type="image/png"
)
],
structuredContent={"detail": "boom"},
isError=True,
structured_content={"detail": "boom"},
is_error=True,
)
with patch.object(
Client, "call_tool_mcp", new_callable=AsyncMock, return_value=error_result

View file

@ -303,7 +303,7 @@ class TestBackgroundTaskIntegration:
role="assistant",
content=TextContent(type="text", text="hello from background"),
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
async with Client(mcp, sampling_handler=sampling_handler) as client:

View file

@ -283,7 +283,7 @@ class TestToolExecutionMetadata:
assert tool.execution.task_support == "optional"
async def test_required_tool_exposes_task_support(self):
"""Tools with mode=required should expose taskSupport='required'."""
"""Tools with mode=required should expose task_support='required'."""
mcp = FastMCP("test", tasks=False)
@mcp.tool(task=TaskConfig(mode="required"))

View file

@ -582,8 +582,8 @@ class TestMountedTaskMetadata:
mcp_tool = MCPTool(
name="remote_task_tool",
description="A remote tool that supports tasks",
inputSchema={"type": "object", "properties": {}},
execution=ToolExecution(taskSupport="optional"),
input_schema={"type": "object", "properties": {}},
execution=ToolExecution(task_support="optional"),
)
proxy = ProxyTool.from_mcp_tool(lambda: None, mcp_tool) # ty: ignore[invalid-argument-type]

View file

@ -581,7 +581,7 @@ async def mcp_content_server(tmp_path):
return ImageContent(
type="image",
data=base64.b64encode(test_image.read_bytes()).decode(),
mimeType="image/png",
mime_type="image/png",
)
@mcp.tool(task=True)
@ -606,7 +606,7 @@ async def mcp_content_server(tmp_path):
ImageContent(
type="image",
data=base64.b64encode(test_image.read_bytes()).decode(),
mimeType="image/png",
mime_type="image/png",
),
TextContent(type="text", text="Third block"),
]

View file

@ -121,13 +121,13 @@ class TestSamplingToolSpan:
)
],
model="test-model",
stopReason="toolUse",
stop_reason="toolUse",
)
return CreateMessageResultWithTools(
role="assistant",
content=[TextContent(type="text", text="done")],
model="test-model",
stopReason="endTurn",
stop_reason="endTurn",
)
mcp = FastMCP(sampling_handler=sampling_handler)

View file

@ -17,12 +17,12 @@ class TestServerIcons:
icons = [
Icon(
src="https://example.com/icon.png",
mimeType="image/png",
mime_type="image/png",
sizes=["48x48"],
),
Icon(
src="data:image/svg+xml;base64,PHN2Zz48L3N2Zz4=",
mimeType="image/svg+xml",
mime_type="image/svg+xml",
sizes=["any"],
),
]
@ -58,7 +58,7 @@ class TestToolIcons:
mcp = FastMCP("TestServer")
icons = [
Icon(src="https://example.com/tool-icon.png", mimeType="image/png"),
Icon(src="https://example.com/tool-icon.png", mime_type="image/png"),
]
@mcp.tool(icons=icons)
@ -272,17 +272,17 @@ class TestIconTypes:
icons = [
Icon(
src="https://example.com/icon-48.png",
mimeType="image/png",
mime_type="image/png",
sizes=["48x48"],
),
Icon(
src="https://example.com/icon-96.png",
mimeType="image/png",
mime_type="image/png",
sizes=["96x96"],
),
Icon(
src="https://example.com/icon.svg",
mimeType="image/svg+xml",
mime_type="image/svg+xml",
sizes=["any"],
),
]
@ -299,7 +299,7 @@ class TestIconTypes:
# Simple SVG data URI
data_uri = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6Ii8+PC9zdmc+"
icons = [Icon(src=data_uri, mimeType="image/svg+xml")]
icons = [Icon(src=data_uri, mime_type="image/svg+xml")]
mcp = FastMCP("TestServer")

View file

@ -15,8 +15,8 @@ async def test_tool_annotations_in_tool_manager():
@mcp.tool(
annotations=ToolAnnotations(
title="Echo Tool",
readOnlyHint=True,
openWorldHint=False,
read_only_hint=True,
open_world_hint=False,
)
)
def echo(message: str) -> str:
@ -39,8 +39,8 @@ async def test_tool_annotations_in_mcp_protocol():
@mcp.tool(
annotations=ToolAnnotations(
title="Echo Tool",
readOnlyHint=True,
openWorldHint=False,
read_only_hint=True,
open_world_hint=False,
)
)
def echo(message: str) -> str:
@ -63,8 +63,8 @@ async def test_tool_annotations_in_client_api():
@mcp.tool(
annotations=ToolAnnotations(
title="Echo Tool",
readOnlyHint=True,
openWorldHint=False,
read_only_hint=True,
open_world_hint=False,
)
)
def echo(message: str) -> str:
@ -114,10 +114,10 @@ async def test_direct_tool_annotations_in_tool_manager():
annotations = ToolAnnotations(
title="Direct Tool",
readOnlyHint=False,
destructiveHint=True,
idempotentHint=False,
openWorldHint=True,
read_only_hint=False,
destructive_hint=True,
idempotent_hint=False,
open_world_hint=True,
)
@mcp.tool(annotations=annotations)
@ -142,10 +142,10 @@ async def test_direct_tool_annotations_in_client_api():
annotations = ToolAnnotations(
title="Direct Tool",
readOnlyHint=False,
destructiveHint=True,
idempotentHint=False,
openWorldHint=True,
read_only_hint=False,
destructive_hint=True,
idempotent_hint=False,
open_world_hint=True,
)
@mcp.tool(annotations=annotations)
@ -177,8 +177,8 @@ async def test_add_tool_method_annotations():
name="create_item",
annotations=ToolAnnotations(
title="Create Item",
readOnlyHint=False,
destructiveHint=False,
read_only_hint=False,
destructive_hint=False,
),
)
@ -206,8 +206,8 @@ async def test_tool_functionality_with_annotations():
name="create_item",
annotations=ToolAnnotations(
title="Create Item",
readOnlyHint=False,
destructiveHint=False,
read_only_hint=False,
destructive_hint=False,
),
)
mcp.add_tool(tool)

View file

@ -104,8 +104,8 @@ class TestConvertResultToContent:
argnames="content_block",
argvalues=[
(TextContent(type="text", text="hello")),
(ImageContent(type="image", data="fakeimagedata", mimeType="image/png")),
(AudioContent(type="audio", data="fakeaudiodata", mimeType="audio/mpeg")),
(ImageContent(type="image", data="fakeimagedata", mime_type="image/png")),
(AudioContent(type="audio", data="fakeaudiodata", mime_type="audio/mpeg")),
(
ResourceLink(
type="resource_link",
@ -118,7 +118,7 @@ class TestConvertResultToContent:
type="resource",
resource=TextResourceContents(
uri=AnyUrl("resource://test"),
mimeType="text/plain",
mime_type="text/plain",
text="resource content",
),
)
@ -140,7 +140,7 @@ class TestConvertResultToContent:
Image(data=b"fakeimagedata"),
[
ImageContent(
type="image", data="ZmFrZWltYWdlZGF0YQ==", mimeType="image/png"
type="image", data="ZmFrZWltYWdlZGF0YQ==", mime_type="image/png"
)
],
),
@ -148,7 +148,7 @@ class TestConvertResultToContent:
Audio(data=b"fakeaudiodata"),
[
AudioContent(
type="audio", data="ZmFrZWF1ZGlvZGF0YQ==", mimeType="audio/wav"
type="audio", data="ZmFrZWF1ZGlvZGF0YQ==", mime_type="audio/wav"
)
],
),
@ -160,7 +160,7 @@ class TestConvertResultToContent:
resource=BlobResourceContents(
uri=AnyUrl("file:///resource.octet-stream"),
blob="ZmlsZWRhdGE=",
mimeType="application/octet-stream",
mime_type="application/octet-stream",
),
)
],
@ -193,7 +193,7 @@ class TestConvertResultToContent:
type="resource",
resource=TextResourceContents(
uri=AnyUrl("resource://test"),
mimeType="text/plain",
mime_type="text/plain",
text="resource content",
),
),
@ -209,10 +209,10 @@ class TestConvertResultToContent:
TextContent(type="text", text='{"key":"value"}'),
TextContent(type="text", text='{"x":1,"y":"hello"}'),
ImageContent(
type="image", data="ZmFrZWltYWdlZGF0YQ==", mimeType="image/png"
type="image", data="ZmFrZWltYWdlZGF0YQ==", mime_type="image/png"
),
AudioContent(
type="audio", data="ZmFrZWF1ZGlvZGF0YQ==", mimeType="audio/wav"
type="audio", data="ZmFrZWF1ZGlvZGF0YQ==", mime_type="audio/wav"
),
ResourceLink(
name="test resource",
@ -223,7 +223,7 @@ class TestConvertResultToContent:
type="resource",
resource=TextResourceContents(
uri=AnyUrl("resource://test"),
mimeType="text/plain",
mime_type="text/plain",
text="resource content",
),
),
@ -475,7 +475,7 @@ class TestAutomaticStructuredContent:
assert result.content == snapshot(
[
AudioContent(
type="audio", data="ZmFrZWF1ZGlvZGF0YQ==", mimeType="audio/wav"
type="audio", data="ZmFrZWF1ZGlvZGF0YQ==", mime_type="audio/wav"
)
]
)

View file

@ -542,7 +542,7 @@ class TestToolExecutionField:
name="my_tool",
description="A tool with execution",
parameters={"type": "object", "properties": {}},
execution=ToolExecution(taskSupport="optional"),
execution=ToolExecution(task_support="optional"),
)
mcp_tool = tool.to_mcp_tool()
@ -566,10 +566,10 @@ class TestToolExecutionField:
name="my_tool",
description="A tool",
parameters={"type": "object", "properties": {}},
execution=ToolExecution(taskSupport="optional"),
execution=ToolExecution(task_support="optional"),
)
override_execution = ToolExecution(taskSupport="required")
override_execution = ToolExecution(task_support="required")
mcp_tool = tool.to_mcp_tool(execution=override_execution)
assert mcp_tool.execution is not None
assert mcp_tool.execution.task_support == "required"
@ -593,7 +593,7 @@ class TestToolExecutionField:
name="my_tool",
description="A tool with required execution",
parameters={"type": "object", "properties": {}},
execution=ToolExecution(taskSupport="required"),
execution=ToolExecution(task_support="required"),
)
mcp_tool = tool.to_mcp_tool()
@ -606,7 +606,7 @@ class TestToolExecutionField:
name="my_tool",
description="A tool with forbidden execution",
parameters={"type": "object", "properties": {}},
execution=ToolExecution(taskSupport="forbidden"),
execution=ToolExecution(task_support="forbidden"),
)
mcp_tool = tool.to_mcp_tool()

View file

@ -107,8 +107,8 @@ class TestCircularReferencesSerialization:
tool = MCPTool(
name="get_node",
description="Get a node",
inputSchema={"type": "object", "properties": {}},
outputSchema=output_schema,
input_schema={"type": "object", "properties": {}},
output_schema=output_schema,
)
# This must not raise ValueError: Circular reference detected
tool.model_dump(by_alias=True, mode="json", exclude_none=True)
@ -153,8 +153,8 @@ class TestCircularReferencesSerialization:
tool = MCPTool(
name="get_pr",
description="Get a pull request",
inputSchema={"type": "object", "properties": {}},
outputSchema=output_schema,
input_schema={"type": "object", "properties": {}},
output_schema=output_schema,
)
tool.model_dump(by_alias=True, mode="json", exclude_none=True)

View file

@ -29,7 +29,7 @@ class TestIconExtraction:
icons=[
Icon(
src="https://example.com/icon.png",
mimeType="image/png",
mime_type="image/png",
sizes=["48x48"],
)
],
@ -63,7 +63,7 @@ class TestIconExtraction:
icons=[
Icon(
src="https://example.com/calculator.png",
mimeType="image/png",
mime_type="image/png",
)
]
)
@ -98,7 +98,7 @@ class TestIconExtraction:
@mcp.resource(
"resource://data",
icons=[Icon(src="https://example.com/data.png", mimeType="image/png")],
icons=[Icon(src="https://example.com/data.png", mime_type="image/png")],
)
def get_data() -> str:
"""Get data."""
@ -131,7 +131,7 @@ class TestIconExtraction:
@mcp.resource(
"resource://user/{id}",
icons=[Icon(src="https://example.com/user.png", mimeType="image/png")],
icons=[Icon(src="https://example.com/user.png", mime_type="image/png")],
)
def get_user(id: str) -> str:
"""Get user by ID."""
@ -167,7 +167,7 @@ class TestIconExtraction:
mcp = FastMCP("PromptIconServer")
@mcp.prompt(
icons=[Icon(src="https://example.com/analyze.png", mimeType="image/png")]
icons=[Icon(src="https://example.com/analyze.png", mime_type="image/png")]
)
def analyze(data: str) -> list:
"""Analyze data."""
@ -201,12 +201,12 @@ class TestIconExtraction:
icons=[
Icon(
src="https://example.com/icon-48.png",
mimeType="image/png",
mime_type="image/png",
sizes=["48x48"],
),
Icon(
src="https://example.com/icon-96.png",
mimeType="image/png",
mime_type="image/png",
sizes=["96x96"],
),
],
@ -245,7 +245,7 @@ class TestIconExtraction:
mcp = FastMCP("DataURIServer")
@mcp.tool(icons=[Icon(src=data_uri, mimeType="image/png")])
@mcp.tool(icons=[Icon(src=data_uri, mime_type="image/png")])
def data_uri_tool() -> str:
"""Tool with data URI icon."""
return "data"
@ -264,7 +264,7 @@ class TestIconExtraction:
mcp = FastMCP1x("Icon1xServer")
@mcp.tool(
icons=[Icon(src="https://example.com/v1-tool.png", mimeType="image/png")]
icons=[Icon(src="https://example.com/v1-tool.png", mime_type="image/png")]
)
def v1_tool() -> str:
"""Tool in v1 server."""
@ -284,11 +284,11 @@ class TestIconExtraction:
mcp = FastMCP(
"FormattedIconServer",
website_url="https://example.com",
icons=[Icon(src="https://example.com/server.png", mimeType="image/png")],
icons=[Icon(src="https://example.com/server.png", mime_type="image/png")],
)
@mcp.tool(
icons=[Icon(src="https://example.com/tool.png", mimeType="image/png")]
icons=[Icon(src="https://example.com/tool.png", mime_type="image/png")]
)
def icon_tool() -> str:
"""Tool with icon."""