Adopt snake_case SDK type attribute access

This commit is contained in:
Jeremiah Lowin 2026-07-05 18:09:03 -04:00
commit ed85d477f5
No known key found for this signature in database
77 changed files with 410 additions and 406 deletions

View file

@ -792,7 +792,7 @@ _LOG_PANEL_HTML = """\
function renderEntry(entry) {
var div = document.createElement("div");
var isError = entry.direction === "response" && entry.body
&& (entry.body.error || (entry.body.result && entry.body.result.isError));
&& (entry.body.error || (entry.body.result && entry.body.result.is_error));
div.className = "log-entry" + (isError ? " error" : "");
var dirClass = isError ? "error" : entry.direction;
var arrows = {request: "\u2192", response: "\u2190", bridge: "\u2191", notification: "\u2193"};

View file

@ -191,7 +191,7 @@ async def _terminal_elicitation_handler(
return ElicitResult(action="cancel")
return ElicitResult(action="accept", content={})
schema = params.requestedSchema
schema = params.requested_schema
properties = schema.get("properties", {})
required = set(schema.get("required", []))
@ -371,7 +371,7 @@ def format_tool_signature(tool: mcp_types.Tool) -> str:
"""Build ``name(param: type, ...) -> return_type`` from a tool's JSON schemas."""
params: list[str] = []
schema = tool.inputSchema
schema = tool.input_schema
properties = schema.get("properties", {})
required = set(schema.get("required", []))
@ -386,8 +386,8 @@ def format_tool_signature(tool: mcp_types.Tool) -> str:
sig = f"{tool.name}({', '.join(params)})"
if tool.outputSchema:
ret = _json_schema_type_to_str(tool.outputSchema)
if tool.output_schema:
ret = _json_schema_type_to_str(tool.output_schema)
sig += f" -> {ret}"
return sig
@ -441,10 +441,10 @@ def _format_call_result_text(result: CallToolResult) -> None:
console.print(_sanitize_untrusted_text(block.text))
elif isinstance(block, mcp_types.ImageContent):
size = len(block.data) * 3 // 4 # rough decoded size
console.print(f"[dim][Image: {block.mimeType}, ~{size} bytes][/dim]")
console.print(f"[dim][Image: {block.mime_type}, ~{size} bytes][/dim]")
elif isinstance(block, mcp_types.AudioContent):
size = len(block.data) * 3 // 4
console.print(f"[dim][Audio: {block.mimeType}, ~{size} bytes][/dim]")
console.print(f"[dim][Audio: {block.mime_type}, ~{size} bytes][/dim]")
else:
console.print(_sanitize_untrusted_text(str(block)))
@ -454,9 +454,9 @@ def _content_block_to_dict(block: mcp_types.ContentBlock) -> dict[str, Any]:
if isinstance(block, mcp_types.TextContent):
return {"type": "text", "text": block.text}
if isinstance(block, mcp_types.ImageContent):
return {"type": "image", "mimeType": block.mimeType, "data": block.data}
return {"type": "image", "mimeType": block.mime_type, "data": block.data}
if isinstance(block, mcp_types.AudioContent):
return {"type": "audio", "mimeType": block.mimeType, "data": block.data}
return {"type": "audio", "mimeType": block.mime_type, "data": block.data}
return {"type": "unknown", "value": str(block)}
@ -477,8 +477,8 @@ def _tools_to_json(tools: list[mcp_types.Tool]) -> list[dict[str, Any]]:
{
"name": t.name,
"description": t.description,
"inputSchema": t.inputSchema,
**({"outputSchema": t.outputSchema} if t.outputSchema else {}),
"inputSchema": t.input_schema,
**({"outputSchema": t.output_schema} if t.output_schema else {}),
}
for t in tools
]
@ -512,9 +512,9 @@ async def _handle_tool_call(
sys.exit(1)
tool = tool_map[tool_name]
parsed_args = parse_tool_arguments(arguments, input_json, tool.inputSchema)
parsed_args = parse_tool_arguments(arguments, input_json, tool.input_schema)
required = set(tool.inputSchema.get("required", []))
required = set(tool.input_schema.get("required", []))
provided = set(parsed_args.keys())
missing = required - provided
if missing:
@ -553,7 +553,7 @@ async def _handle_resource(
data.append(
{
"uri": str(block.uri),
"mimeType": block.mimeType,
"mimeType": block.mime_type,
"text": block.text,
}
)
@ -561,7 +561,7 @@ async def _handle_resource(
data.append(
{
"uri": str(block.uri),
"mimeType": block.mimeType,
"mimeType": block.mime_type,
"blob": block.blob,
}
)
@ -573,7 +573,7 @@ async def _handle_resource(
console.print(_sanitize_untrusted_text(block.text))
elif isinstance(block, mcp_types.BlobResourceContents):
size = len(block.blob) * 3 // 4
console.print(f"[dim][Blob: {block.mimeType}, ~{size} bytes][/dim]")
console.print(f"[dim][Blob: {block.mime_type}, ~{size} bytes][/dim]")
async def _handle_prompt(
@ -626,7 +626,7 @@ async def _handle_prompt(
elif isinstance(msg.content, mcp_types.ImageContent):
size = len(msg.content.data) * 3 // 4
console.print(
f" [dim][Image: {msg.content.mimeType}, ~{size} bytes][/dim]"
f" [dim][Image: {msg.content.mime_type}, ~{size} bytes][/dim]"
)
else:
console.print(f" {_sanitize_untrusted_text(str(msg.content))}")
@ -718,7 +718,7 @@ async def list_command(
"uri": str(r.uri),
"name": r.name,
"description": r.description,
"mimeType": r.mimeType,
"mimeType": r.mime_type,
}
for r in res
]
@ -749,9 +749,9 @@ async def list_command(
f" {_sanitize_untrusted_text(tool.description)}"
)
if input_schema:
_print_schema("Input", tool.inputSchema)
if output_schema and tool.outputSchema:
_print_schema("Output", tool.outputSchema)
_print_schema("Input", tool.input_schema)
if output_schema and tool.output_schema:
_print_schema("Output", tool.output_schema)
console.print()
if resources:

View file

@ -165,7 +165,7 @@ def _to_python_identifier(name: str) -> str:
def _tool_function_source(tool: mcp_types.Tool) -> str:
"""Generate the source for a single ``@call_tool_app.command`` function."""
schema = tool.inputSchema
schema = tool.input_schema
properties: dict[str, Any] = schema.get("properties", {})
required = set(schema.get("required", []))
@ -361,10 +361,10 @@ def generate_cli_script(
console.print(block.text)
elif isinstance(block, mcp_types.ImageContent):
size = len(block.data) * 3 // 4
console.print(f"[dim][Image: {block.mimeType}, ~{size} bytes][/dim]")
console.print(f"[dim][Image: {block.mime_type}, ~{size} bytes][/dim]")
elif isinstance(block, mcp_types.AudioContent):
size = len(block.data) * 3 // 4
console.print(f"[dim][Audio: {block.mimeType}, ~{size} bytes][/dim]")
console.print(f"[dim][Audio: {block.mime_type}, ~{size} bytes][/dim]")
async def _call_tool(tool_name: str, arguments: dict) -> None:
@ -401,8 +401,8 @@ def generate_cli_script(
return
for tool in tools:
sig_parts = []
props = tool.inputSchema.get("properties", {})
required = set(tool.inputSchema.get("required", []))
props = tool.input_schema.get("properties", {})
required = set(tool.input_schema.get("required", []))
for pname, pschema in props.items():
ptype = pschema.get("type", "string")
if pname in required:
@ -443,7 +443,7 @@ def generate_cli_script(
console.print(block.text)
elif isinstance(block, mcp_types.BlobResourceContents):
size = len(block.blob) * 3 // 4
console.print(f"[dim][Blob: {block.mimeType}, ~{size} bytes][/dim]")
console.print(f"[dim][Blob: {block.mime_type}, ~{size} bytes][/dim]")
@app.command
@ -487,7 +487,7 @@ def generate_cli_script(
console.print(f" {msg.content.text}")
elif isinstance(msg.content, mcp_types.ImageContent):
size = len(msg.content.data) * 3 // 4
console.print(f" [dim][Image: {msg.content.mimeType}, ~{size} bytes][/dim]")
console.print(f" [dim][Image: {msg.content.mime_type}, ~{size} bytes][/dim]")
else:
console.print(f" {msg.content}")
console.print()""")
@ -566,7 +566,7 @@ def _schema_type_label(prop_schema: dict[str, Any]) -> str:
def _tool_skill_section(tool: mcp_types.Tool, cli_filename: str) -> str:
"""Generate a SKILL.md section for a single tool."""
schema = tool.inputSchema
schema = tool.input_schema
properties: dict[str, Any] = schema.get("properties", {})
required = set(schema.get("required", []))

View file

@ -511,7 +511,7 @@ class Client(
client = Client(server, auto_initialize=False)
async with client:
result = await client.initialize()
print(f"Server: {result.serverInfo.name}")
print(f"Server: {result.server_info.name}")
print(f"Instructions: {result.instructions}")
```
"""
@ -782,7 +782,7 @@ class Client(
Updates Task object's cache and triggers events/callbacks.
"""
# Extract task ID from notification params
task_id = notification.params.taskId
task_id = notification.params.task_id
if not task_id:
return

View file

@ -45,10 +45,10 @@ def create_elicitation_callback(
try:
# requestedSchema only exists on ElicitRequestFormParams, not ElicitRequestURLParams
if isinstance(params, ElicitRequestFormParams):
if params.requestedSchema == {"type": "object", "properties": {}}:
if params.requested_schema == {"type": "object", "properties": {}}:
response_type = None
else:
response_type = json_schema_to_type(params.requestedSchema)
response_type = json_schema_to_type(params.requested_schema)
else:
# URL-based elicitation doesn't have a schema
response_type = None
@ -65,7 +65,7 @@ def create_elicitation_callback(
# (single "value" property). This lets handlers return T directly
# for ctx.elicit("msg", str/int/float/bool).
if isinstance(params, ElicitRequestFormParams) and set(
params.requestedSchema.get("properties", {}).keys()
params.requested_schema.get("properties", {}).keys()
) == {"value"}:
content = {"value": content}
else:

View file

@ -89,16 +89,16 @@ class ClientPromptsMixin:
for _ in range(max_pages):
result = await self.list_prompts_mcp(cursor=cursor)
all_prompts.extend(result.prompts)
if not result.nextCursor:
if not result.next_cursor:
break
if result.nextCursor in seen_cursors:
if result.next_cursor in seen_cursors:
logger.warning(
f"[{self.name}] Server returned duplicate pagination cursor"
f" {result.nextCursor!r} for list_prompts; stopping pagination"
f" {result.next_cursor!r} for list_prompts; stopping pagination"
)
break
seen_cursors.add(result.nextCursor)
cursor = result.nextCursor
seen_cursors.add(result.next_cursor)
cursor = result.next_cursor
else:
raise RuntimeError(
f"[{self.name}] Reached auto-pagination limit"
@ -311,7 +311,7 @@ class ClientPromptsMixin:
if isinstance(raw_result, mcp_types.CreateTaskResult):
# Task was accepted - extract task info from CreateTaskResult
server_task_id = raw_result.task.taskId
server_task_id = raw_result.task.task_id
self._submitted_task_ids.add(server_task_id)
task_obj = PromptTask(

View file

@ -88,16 +88,16 @@ class ClientResourcesMixin:
for _ in range(max_pages):
result = await self.list_resources_mcp(cursor=cursor)
all_resources.extend(result.resources)
if not result.nextCursor:
if not result.next_cursor:
break
if result.nextCursor in seen_cursors:
if result.next_cursor in seen_cursors:
logger.warning(
f"[{self.name}] Server returned duplicate pagination cursor"
f" {result.nextCursor!r} for list_resources; stopping pagination"
f" {result.next_cursor!r} for list_resources; stopping pagination"
)
break
seen_cursors.add(result.nextCursor)
cursor = result.nextCursor
seen_cursors.add(result.next_cursor)
cursor = result.next_cursor
else:
raise RuntimeError(
f"[{self.name}] Reached auto-pagination limit"
@ -164,18 +164,18 @@ class ClientResourcesMixin:
for _ in range(max_pages):
result = await self.list_resource_templates_mcp(cursor=cursor)
all_templates.extend(result.resourceTemplates)
if not result.nextCursor:
all_templates.extend(result.resource_templates)
if not result.next_cursor:
break
if result.nextCursor in seen_cursors:
if result.next_cursor in seen_cursors:
logger.warning(
f"[{self.name}] Server returned duplicate pagination cursor"
f" {result.nextCursor!r} for list_resource_templates;"
f" {result.next_cursor!r} for list_resource_templates;"
" stopping pagination"
)
break
seen_cursors.add(result.nextCursor)
cursor = result.nextCursor
seen_cursors.add(result.next_cursor)
cursor = result.next_cursor
else:
raise RuntimeError(
f"[{self.name}] Reached auto-pagination limit"
@ -365,7 +365,7 @@ class ClientResourcesMixin:
if isinstance(raw_result, mcp_types.CreateTaskResult):
# Task was accepted - extract task info from CreateTaskResult
server_task_id = raw_result.task.taskId
server_task_id = raw_result.task.task_id
self._submitted_task_ids.add(server_task_id)
task_obj = ResourceTask(

View file

@ -93,16 +93,16 @@ class ClientToolsMixin:
for _ in range(max_pages):
result = await self.list_tools_mcp(cursor=cursor)
all_tools.extend(result.tools)
if not result.nextCursor:
if not result.next_cursor:
break
if result.nextCursor in seen_cursors:
if result.next_cursor in seen_cursors:
logger.warning(
f"[{self.name}] Server returned duplicate pagination cursor"
f" {result.nextCursor!r} for list_tools; stopping pagination"
f" {result.next_cursor!r} for list_tools; stopping pagination"
)
break
seen_cursors.add(result.nextCursor)
cursor = result.nextCursor
seen_cursors.add(result.next_cursor)
cursor = result.next_cursor
else:
raise RuntimeError(
f"[{self.name}] Reached auto-pagination limit"
@ -170,7 +170,7 @@ class ClientToolsMixin:
# Reflect tool-level errors on the span so callers see ERROR
# status even though the MCP protocol call itself succeeded.
if result.isError and span.is_recording():
if result.is_error and span.is_recording():
span.set_attribute("error.type", "tool_error")
description = ""
if result.content and isinstance(
@ -366,7 +366,7 @@ class ClientToolsMixin:
if isinstance(raw_result, mcp_types.CreateTaskResult):
# Task was accepted - extract task info from CreateTaskResult
server_task_id = raw_result.task.taskId
server_task_id = raw_result.task.task_id
self._submitted_task_ids.add(server_task_id)
task_obj = ToolTask(
@ -418,13 +418,13 @@ async def _parse_call_tool_result(
from fastmcp.client.client import CallToolResult
data = None
if result.isError and raise_on_error:
if result.is_error and raise_on_error:
if result.content and isinstance(result.content[0], mcp_types.TextContent):
msg = result.content[0].text
else:
msg = f"Tool '{name}' returned an error"
raise ToolError(msg)
elif result.structuredContent and not result.isError:
elif result.structured_content and not result.is_error:
try:
raw_fastmcp_meta = (result.meta or {}).get("fastmcp")
fastmcp_meta = (
@ -441,15 +441,15 @@ async def _parse_call_tool_result(
if wrap_from_meta:
# Meta tells us the result is wrapped — unwrap and validate.
structured_content = result.structuredContent.get("result")
structured_content = result.structured_content.get("result")
elif name in tool_output_schemas:
output_schema = tool_output_schemas.get(name)
if output_schema and output_schema.get("x-fastmcp-wrap-result"):
structured_content = result.structuredContent.get("result")
structured_content = result.structured_content.get("result")
else:
structured_content = result.structuredContent
structured_content = result.structured_content
else:
structured_content = result.structuredContent
structured_content = result.structured_content
# Type-validate through the schema if available.
output_schema = tool_output_schemas.get(name)
@ -470,8 +470,8 @@ async def _parse_call_tool_result(
return CallToolResult(
content=result.content,
structured_content=result.structuredContent,
structured_content=result.structured_content,
meta=result.meta,
data=data,
is_error=result.isError,
is_error=result.is_error,
)

View file

@ -54,16 +54,16 @@ _ANTHROPIC_IMAGE_MEDIA_TYPES = frozenset(
def _image_content_to_anthropic_block(content: ImageContent) -> ImageBlockParam:
"""Convert MCP ImageContent to Anthropic ImageBlockParam."""
if content.mimeType not in _ANTHROPIC_IMAGE_MEDIA_TYPES:
if content.mime_type not in _ANTHROPIC_IMAGE_MEDIA_TYPES:
raise ValueError(
f"Unsupported image MIME type for Anthropic: {content.mimeType!r}. "
f"Unsupported image MIME type for Anthropic: {content.mime_type!r}. "
f"Supported types: {', '.join(sorted(_ANTHROPIC_IMAGE_MEDIA_TYPES))}"
)
return ImageBlockParam(
type="image",
source=Base64ImageSourceParam(
type="base64",
media_type=content.mimeType, # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
media_type=content.mime_type, # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
data=content.data,
),
)
@ -103,7 +103,9 @@ class AnthropicSamplingHandler:
messages=messages,
)
model: ModelParam = self._select_model_from_preferences(params.modelPreferences)
model: ModelParam = self._select_model_from_preferences(
params.model_preferences
)
# Convert MCP tools to Anthropic format
anthropic_tools: list[ToolParam] | None = None
@ -113,8 +115,8 @@ class AnthropicSamplingHandler:
# Convert tool_choice to Anthropic format
# Returns None if mode is "none", signaling tools should be omitted
anthropic_tool_choice: ToolChoiceParam | None = None
if params.toolChoice:
converted = self._convert_tool_choice_to_anthropic(params.toolChoice)
if params.tool_choice:
converted = self._convert_tool_choice_to_anthropic(params.tool_choice)
if converted is None:
# tool_choice="none" means don't use tools
anthropic_tools = None
@ -126,14 +128,14 @@ class AnthropicSamplingHandler:
kwargs: dict[str, Any] = {
"model": model,
"messages": anthropic_messages,
"max_tokens": params.maxTokens,
"max_tokens": params.max_tokens,
}
if params.systemPrompt is not None:
kwargs["system"] = params.systemPrompt
if params.system_prompt is not None:
kwargs["system"] = params.system_prompt
if params.temperature is not None:
kwargs["temperature"] = params.temperature
if params.stopSequences is not None:
kwargs["stop_sequences"] = params.stopSequences
if params.stop_sequences is not None:
kwargs["stop_sequences"] = params.stop_sequences
if anthropic_tools is not None:
kwargs["tools"] = anthropic_tools
if anthropic_tool_choice is not None:
@ -229,9 +231,9 @@ class AnthropicSamplingHandler:
content_blocks.append(
ToolResultBlockParam(
type="tool_result",
tool_use_id=item.toolUseId,
tool_use_id=item.tool_use_id,
content=result_content,
is_error=item.isError if item.isError else False,
is_error=item.is_error if item.is_error else False,
)
)
else:
@ -285,9 +287,11 @@ class AnthropicSamplingHandler:
content=[
ToolResultBlockParam(
type="tool_result",
tool_use_id=content.toolUseId,
tool_use_id=content.tool_use_id,
content=result_content_str,
is_error=content.isError if content.isError else False,
is_error=content.is_error
if content.is_error
else False,
)
],
)
@ -364,7 +368,7 @@ class AnthropicSamplingHandler:
anthropic_tools: list[ToolParam] = []
for tool in tools:
# Build input_schema dict, ensuring required fields
input_schema: dict[str, Any] = dict(tool.inputSchema)
input_schema: dict[str, Any] = dict(tool.input_schema)
if "type" not in input_schema:
input_schema["type"] = "object"

View file

@ -100,10 +100,10 @@ class GoogleGenaiSamplingHandler:
google_tools = [
_convert_tool_to_google_genai(tool) for tool in params.tools
]
tool_config = _convert_tool_choice_to_google_genai(params.toolChoice)
tool_config = _convert_tool_choice_to_google_genai(params.tool_choice)
# Select the model based on preferences
selected_model = self._get_model(model_preferences=params.modelPreferences)
selected_model = self._get_model(model_preferences=params.model_preferences)
# Configure thinking if a budget is specified
thinking_config = (
@ -117,10 +117,10 @@ class GoogleGenaiSamplingHandler:
model=selected_model,
contents=contents,
config=GenerateContentConfig(
system_instruction=params.systemPrompt,
system_instruction=params.system_prompt,
temperature=params.temperature,
max_output_tokens=params.maxTokens,
stop_sequences=params.stopSequences,
max_output_tokens=params.max_tokens,
stop_sequences=params.stop_sequences,
thinking_config=thinking_config,
tools=google_tools, # ty: ignore[invalid-argument-type]
tool_config=tool_config,
@ -150,7 +150,7 @@ def _convert_tool_to_google_genai(tool: MCPTool) -> GoogleTool:
"""
from fastmcp.utilities.json_schema import compress_schema
schema = compress_schema(tool.inputSchema, prune_titles=True)
schema = compress_schema(tool.input_schema, prune_titles=True)
return GoogleTool(
function_declarations=[
FunctionDeclaration(
@ -207,7 +207,7 @@ def _sampling_content_to_google_genai_part(
return Part(
inline_data=Blob(
data=base64.b64decode(content.data),
mime_type=content.mimeType,
mime_type=content.mime_type,
)
)
@ -215,7 +215,7 @@ def _sampling_content_to_google_genai_part(
return Part(
inline_data=Blob(
data=base64.b64decode(content.data),
mime_type=content.mimeType,
mime_type=content.mime_type,
)
)
@ -249,7 +249,7 @@ def _sampling_content_to_google_genai_part(
# Our IDs are formatted as "{function_name}_{uuid8}", so extract the name.
# Note: This is a limitation of MCP's ToolResultContent which only carries
# toolUseId, while Google's FunctionResponse requires the function name.
tool_use_id = content.toolUseId
tool_use_id = content.tool_use_id
if "_" in tool_use_id:
# Split and rejoin all but the last part (the UUID suffix)
parts = tool_use_id.rsplit("_", 1)

View file

@ -64,12 +64,12 @@ def _image_content_to_openai_part(
content: ImageContent,
) -> ChatCompletionContentPartImageParam:
"""Convert MCP ImageContent to OpenAI image_url content part."""
if content.mimeType not in _OPENAI_IMAGE_MEDIA_TYPES:
if content.mime_type not in _OPENAI_IMAGE_MEDIA_TYPES:
raise ValueError(
f"Unsupported image MIME type for OpenAI: {content.mimeType!r}. "
f"Unsupported image MIME type for OpenAI: {content.mime_type!r}. "
f"Supported types: {', '.join(sorted(_OPENAI_IMAGE_MEDIA_TYPES))}"
)
data_url = f"data:{content.mimeType};base64,{content.data}"
data_url = f"data:{content.mime_type};base64,{content.data}"
return ChatCompletionContentPartImageParam(
type="image_url",
image_url={"url": data_url},
@ -80,10 +80,10 @@ def _audio_content_to_openai_part(
content: AudioContent,
) -> ChatCompletionContentPartInputAudioParam:
"""Convert MCP AudioContent to OpenAI input_audio content part."""
audio_format = _OPENAI_AUDIO_FORMATS.get(content.mimeType)
audio_format = _OPENAI_AUDIO_FORMATS.get(content.mime_type)
if audio_format is None:
raise ValueError(
f"Unsupported audio MIME type for OpenAI: {content.mimeType!r}. "
f"Unsupported audio MIME type for OpenAI: {content.mime_type!r}. "
f"Supported types: {', '.join(sorted(_OPENAI_AUDIO_FORMATS))}"
)
return ChatCompletionContentPartInputAudioParam(
@ -112,12 +112,12 @@ class OpenAISamplingHandler:
) -> CreateMessageResult | CreateMessageResultWithTools:
openai_messages: list[ChatCompletionMessageParam] = (
self._convert_to_openai_messages(
system_prompt=params.systemPrompt,
system_prompt=params.system_prompt,
messages=messages,
)
)
model: ChatModel = self._select_model_from_preferences(params.modelPreferences)
model: ChatModel = self._select_model_from_preferences(params.model_preferences)
# Convert MCP tools to OpenAI format
openai_tools: list[ChatCompletionToolParam] | None = None
@ -126,8 +126,8 @@ class OpenAISamplingHandler:
# Convert tool_choice to OpenAI format
openai_tool_choice: ChatCompletionToolChoiceOptionParam | None = None
if params.toolChoice:
openai_tool_choice = self._convert_tool_choice_to_openai(params.toolChoice)
if params.tool_choice:
openai_tool_choice = self._convert_tool_choice_to_openai(params.tool_choice)
# Build kwargs to avoid sentinel type compatibility issues across
# openai SDK versions (NotGiven vs Omit)
@ -135,12 +135,12 @@ class OpenAISamplingHandler:
"model": model,
"messages": openai_messages,
}
if params.maxTokens is not None:
kwargs["max_completion_tokens"] = params.maxTokens
if params.max_tokens is not None:
kwargs["max_completion_tokens"] = params.max_tokens
if params.temperature is not None:
kwargs["temperature"] = params.temperature
if params.stopSequences:
kwargs["stop"] = params.stopSequences
if params.stop_sequences:
kwargs["stop"] = params.stop_sequences
if openai_tools is not None:
kwargs["tools"] = openai_tools
if openai_tool_choice is not None:
@ -240,7 +240,7 @@ class OpenAISamplingHandler:
tool_messages.append(
ChatCompletionToolMessageParam(
role="tool",
tool_call_id=item.toolUseId,
tool_call_id=item.tool_use_id,
content=content_text,
)
)
@ -327,7 +327,7 @@ class OpenAISamplingHandler:
openai_messages.append(
ChatCompletionToolMessageParam(
role="tool",
tool_call_id=content.toolUseId,
tool_call_id=content.tool_use_id,
content="\n".join(result_texts),
)
)
@ -417,7 +417,7 @@ class OpenAISamplingHandler:
openai_tools: list[ChatCompletionToolParam] = []
for tool in tools:
# Build parameters dict, ensuring required fields
parameters: dict[str, Any] = dict(tool.inputSchema)
parameters: dict[str, Any] = dict(tool.input_schema)
if "type" not in parameters:
parameters["type"] = "object"

View file

@ -162,7 +162,7 @@ class Task(abc.ABC, Generic[TaskResultT]):
>>> task = await client.call_tool("slow_operation", {}, task=True)
>>>
>>> def on_update(status: GetTaskResult):
... print(f"Task {status.taskId} is now {status.status}")
... print(f"Task {status.task_id} is now {status.status}")
>>>
>>> task.on_status_change(on_update)
>>> result = await task # Callback fires when status changes

View file

@ -43,7 +43,7 @@ class CallToolRequestResult(CallToolResult):
return cls(
tool=tool,
arguments=arguments,
isError=result.isError,
isError=result.is_error,
content=result.content,
)
@ -84,7 +84,7 @@ class BulkToolCaller(MCPMixin):
results.append(result)
if result.isError and not continue_on_error:
if result.is_error and not continue_on_error:
return results
return results
@ -112,7 +112,7 @@ class BulkToolCaller(MCPMixin):
results.append(result)
if result.isError and not continue_on_error:
if result.is_error and not continue_on_error:
return results
return results
@ -146,6 +146,6 @@ class BulkToolCaller(MCPMixin):
return CallToolRequestResult(
tool=tool,
arguments=arguments,
isError=result.isError,
isError=result.is_error,
content=result.content,
)

View file

@ -340,10 +340,10 @@ class ResourceTemplate(FastMCPComponent):
# Note: This creates a simple ResourceTemplate instance. For function-based templates,
# the original function is lost, which is expected for remote templates.
return cls(
uri_template=mcp_template.uriTemplate,
uri_template=mcp_template.uri_template,
name=mcp_template.name,
description=mcp_template.description,
mime_type=mcp_template.mimeType or "text/plain",
mime_type=mcp_template.mime_type or "text/plain",
parameters={}, # Remote templates don't have local parameters
)

View file

@ -401,7 +401,7 @@ class Context:
"""
progress_token = (
self.request_context.meta.progressToken
self.request_context.meta.progress_token
if self.request_context and self.request_context.meta
else None
)
@ -471,12 +471,12 @@ class Context:
request = request_factory(cursor)
result = await call_method(request)
all_items.extend(extract_items(result))
if not result.nextCursor:
if not result.next_cursor:
break
if result.nextCursor in seen_cursors:
if result.next_cursor in seen_cursors:
break
seen_cursors.add(result.nextCursor)
cursor = result.nextCursor
seen_cursors.add(result.next_cursor)
cursor = result.next_cursor
return all_items
async def list_resources(self) -> list[SDKResource]:

View file

@ -152,9 +152,9 @@ class ProxyTool(Tool):
name=mcp_tool.name,
title=mcp_tool.title,
description=mcp_tool.description,
parameters=mcp_tool.inputSchema,
parameters=mcp_tool.input_schema,
annotations=mcp_tool.annotations,
output_schema=mcp_tool.outputSchema,
output_schema=mcp_tool.output_schema,
icons=mcp_tool.icons,
meta=mcp_tool.meta,
tags=get_fastmcp_metadata(mcp_tool.meta).get("tags", []),
@ -217,9 +217,9 @@ class ProxyTool(Tool):
# Preserve backend's meta (includes task metadata for background tasks)
return ToolResult(
content=result.content,
structured_content=result.structuredContent,
structured_content=result.structured_content,
meta=result.meta,
is_error=result.isError,
is_error=result.is_error,
)
def get_span_attributes(self) -> dict[str, Any]:
@ -277,7 +277,7 @@ class ProxyResource(Resource):
name=mcp_resource.name,
title=mcp_resource.title,
description=mcp_resource.description,
mime_type=mcp_resource.mimeType or "text/plain",
mime_type=mcp_resource.mime_type or "text/plain",
icons=mcp_resource.icons,
meta=mcp_resource.meta,
tags=get_fastmcp_metadata(mcp_resource.meta).get("tags", []),
@ -312,7 +312,7 @@ class ProxyResource(Resource):
contents.append(
ResourceContent(
content=item.text,
mime_type=item.mimeType,
mime_type=item.mime_type,
meta=item.meta,
)
)
@ -320,7 +320,7 @@ class ProxyResource(Resource):
contents.append(
ResourceContent(
content=base64.b64decode(item.blob),
mime_type=item.mimeType,
mime_type=item.mime_type,
meta=item.meta,
)
)
@ -370,11 +370,11 @@ class ProxyTemplate(ResourceTemplate):
return cls(
client_factory=client_factory,
uri_template=mcp_template.uriTemplate,
uri_template=mcp_template.uri_template,
name=mcp_template.name,
title=mcp_template.title,
description=mcp_template.description,
mime_type=mcp_template.mimeType or "text/plain",
mime_type=mcp_template.mime_type or "text/plain",
icons=mcp_template.icons,
parameters={}, # Remote templates don't have local parameters
meta=mcp_template.meta,
@ -410,7 +410,7 @@ class ProxyTemplate(ResourceTemplate):
contents.append(
ResourceContent(
content=item.text,
mime_type=item.mimeType,
mime_type=item.mime_type,
meta=item.meta,
)
)
@ -418,7 +418,7 @@ class ProxyTemplate(ResourceTemplate):
contents.append(
ResourceContent(
content=base64.b64decode(item.blob),
mime_type=item.mimeType,
mime_type=item.mime_type,
meta=item.meta,
)
)
@ -435,7 +435,7 @@ class ProxyTemplate(ResourceTemplate):
description=self.description,
mime_type=result[
0
].mimeType, # Use first item's mimeType for backward compatibility
].mime_type, # Use first item's mimeType for backward compatibility
icons=self.icons,
meta=self.meta,
tags=get_fastmcp_metadata(self.meta).get("tags", []),
@ -949,10 +949,10 @@ async def default_proxy_sampling_handler(
ctx = get_context()
result = await ctx.sample(
list(messages),
system_prompt=params.systemPrompt,
system_prompt=params.system_prompt,
temperature=params.temperature,
max_tokens=params.maxTokens,
model_preferences=params.modelPreferences,
max_tokens=params.max_tokens,
model_preferences=params.model_preferences,
)
content = mcp_types.TextContent(type="text", text=result.text or "")
return mcp_types.CreateMessageResult(
@ -973,7 +973,7 @@ async def default_proxy_elicitation_handler(
ctx = get_context()
# requestedSchema only exists on ElicitRequestFormParams, not ElicitRequestURLParams
requested_schema = (
params.requestedSchema
params.requested_schema
if isinstance(params, ElicitRequestFormParams)
else {"type": "object", "properties": {}}
)

View file

@ -88,7 +88,7 @@ class SampleStep:
def is_tool_use(self) -> bool:
"""True if the LLM is requesting tool execution."""
if isinstance(self.response, CreateMessageResultWithTools):
return self.response.stopReason == "toolUse"
return self.response.stop_reason == "toolUse"
return False
@property
@ -575,7 +575,7 @@ async def sample_step_impl(
# Check if this is a tool use response
is_tool_use_response = (
isinstance(response, CreateMessageResultWithTools)
and response.stopReason == "toolUse"
and response.stop_reason == "toolUse"
)
# Always include the assistant response in history

View file

@ -105,7 +105,7 @@ class ToolResult(BaseModel):
is_error: bool = Field(
default=False,
description="Whether this result represents a tool execution error. "
"When True, it maps to CallToolResult.isError so the error is returned "
"When True, it maps to CallToolResult.is_error so the error is returned "
"to the client rather than raised.",
)

View file

@ -121,7 +121,7 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
key=tool.key,
name=tool.name or tool.key,
description=tool.description,
input_schema=mcp_tool.inputSchema if mcp_tool.inputSchema else {},
input_schema=mcp_tool.input_schema if mcp_tool.input_schema else {},
output_schema=tool.output_schema,
annotations=tool.annotations.model_dump() if tool.annotations else None,
tags=list(tool.tags) if tool.tags else None,
@ -263,7 +263,7 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
key=mcp_tool.name,
name=mcp_tool.name,
description=mcp_tool.description,
input_schema=mcp_tool.inputSchema if mcp_tool.inputSchema else {},
input_schema=mcp_tool.input_schema if mcp_tool.input_schema else {},
output_schema=None, # v1 doesn't have output_schema
annotations=None, # v1 doesn't have annotations
tags=None, # v1 doesn't have tags
@ -307,7 +307,7 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
uri=str(mcp_resource.uri),
name=mcp_resource.name,
description=mcp_resource.description,
mime_type=mcp_resource.mimeType,
mime_type=mcp_resource.mime_type,
annotations=None, # v1 doesn't have annotations
tags=None, # v1 doesn't have tags
title=None, # v1 doesn't have title
@ -323,11 +323,11 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
for mcp_template in mcp_templates:
template_infos.append( # noqa: PERF401
TemplateInfo(
key=str(mcp_template.uriTemplate),
uri_template=str(mcp_template.uriTemplate),
key=str(mcp_template.uri_template),
uri_template=str(mcp_template.uri_template),
name=mcp_template.name,
description=mcp_template.description,
mime_type=mcp_template.mimeType,
mime_type=mcp_template.mime_type,
parameters=None, # v1 doesn't expose template parameters
annotations=None, # v1 doesn't have annotations
tags=None, # v1 doesn't have tags
@ -348,14 +348,14 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
}
# Extract server-level icons and website_url from serverInfo
server_info = client.initialize_result.serverInfo
server_info = client.initialize_result.server_info
server_icons = (
[icon.model_dump() for icon in server_info.icons]
if hasattr(server_info, "icons") and server_info.icons
else None
)
server_website_url = (
server_info.websiteUrl if hasattr(server_info, "websiteUrl") else None
server_info.website_url if hasattr(server_info, "websiteUrl") else None
)
return FastMCPInfo(
@ -443,7 +443,7 @@ async def format_mcp_info(mcp: FastMCP[Any] | FastMCP1x) -> bytes:
templates_result = await client.list_resource_templates_mcp()
# Get server info from the initialize result
server_info = client.initialize_result.serverInfo
server_info = client.initialize_result.server_info
# Combine into MCP protocol structure with environment metadata
result = {
@ -456,7 +456,7 @@ async def format_mcp_info(mcp: FastMCP[Any] | FastMCP1x) -> bytes:
"tools": tools_result.tools,
"prompts": prompts_result.prompts,
"resources": resources_result.resources,
"resourceTemplates": templates_result.resourceTemplates,
"resourceTemplates": templates_result.resource_templates,
}
return pydantic_core.to_json(result, indent=2)

View file

@ -70,7 +70,7 @@ async def test_call_tool_mcp(fastmcp_server):
# Check that we got the raw MCP CallToolResult object
assert hasattr(result, "content")
assert hasattr(result, "isError")
assert result.isError is False
assert result.is_error is False
# The content is a list, so we'll check the first element
# by properly accessing it
content = result.content
@ -356,7 +356,7 @@ async def test_initialize_called_once(fastmcp_server):
async with client:
# Verify that initialization succeeded by checking initialize_result
assert client.initialize_result is not None
assert client.initialize_result.serverInfo is not None
assert client.initialize_result.server_info is not None
async def test_initialize_result_connected(fastmcp_server):
@ -372,8 +372,8 @@ async def test_initialize_result_connected(fastmcp_server):
# Verify the initialize result has expected properties
assert hasattr(result, "serverInfo")
assert result.serverInfo.name == "TestServer"
assert result.serverInfo.version is not None
assert result.server_info.name == "TestServer"
assert result.server_info.version is not None
async def test_initialize_result_disconnected(fastmcp_server):
@ -401,8 +401,8 @@ async def test_server_info_custom_version():
async with client:
result = client.initialize_result
assert result is not None
assert result.serverInfo.name == "CustomVersionServer"
assert result.serverInfo.version == "1.2.3"
assert result.server_info.name == "CustomVersionServer"
assert result.server_info.version == "1.2.3"
# Test without version (backward compatibility)
server_without_version = FastMCP("DefaultVersionServer")
@ -411,9 +411,9 @@ async def test_server_info_custom_version():
async with client:
result = client.initialize_result
assert result is not None
assert result.serverInfo.name == "DefaultVersionServer"
assert result.server_info.name == "DefaultVersionServer"
# Should fall back to FastMCP version
assert result.serverInfo.version == fastmcp.__version__
assert result.server_info.version == fastmcp.__version__
class _DelayedConnectTransport(ClientTransport):
@ -717,7 +717,7 @@ async def test_resource_template(fastmcp_server):
# Check that our template is available
assert len(result) == 1
assert "data://user/{user_id}" in result[0].uriTemplate
assert "data://user/{user_id}" in result[0].uri_template
# Now use the template with a specific user_id
uri = cast(AnyUrl, "data://user/123")
@ -739,8 +739,8 @@ async def test_list_resource_templates_mcp(fastmcp_server):
# Check that we got the raw MCP ListResourceTemplatesResult object
assert hasattr(result, "resourceTemplates")
assert len(result.resourceTemplates) == 1
assert "data://user/{user_id}" in result.resourceTemplates[0].uriTemplate
assert len(result.resource_templates) == 1
assert "data://user/{user_id}" in result.resource_templates[0].uri_template
async def test_mcp_resource_generation(fastmcp_server):
@ -772,7 +772,7 @@ async def test_mcp_template_generation(fastmcp_server):
assert hasattr(template, "uriTemplate")
assert hasattr(template, "name")
assert hasattr(template, "description")
assert "data://user/{user_id}" in template.uriTemplate
assert "data://user/{user_id}" in template.uri_template
async def test_template_access_via_client(fastmcp_server):
@ -811,7 +811,7 @@ async def test_tagged_template_metadata(tagged_resources_server):
template = templates[0]
# Verify template metadata is preserved
assert "template://{id}" in template.uriTemplate
assert "template://{id}" in template.uri_template
assert template.description == "A tagged template"

View file

@ -27,7 +27,7 @@ class TestErrorHandling:
async with client:
result = await client.call_tool_mcp("error_tool", {})
assert result.isError
assert result.is_error
assert isinstance(result.content[0], TextContent)
assert "test error" in result.content[0].text
assert "abc" in result.content[0].text
@ -43,7 +43,7 @@ class TestErrorHandling:
async with client:
result = await client.call_tool_mcp("error_tool", {})
assert result.isError
assert result.is_error
assert isinstance(result.content[0], TextContent)
assert "test error" not in result.content[0].text
assert "abc" not in result.content[0].text
@ -57,7 +57,7 @@ class TestErrorHandling:
async with Client(transport=FastMCPTransport(mcp)) as client:
result = await client.call_tool_mcp("validated_tool", {"x": "abc"})
assert result.isError
assert result.is_error
# Pydantic validation error message should NOT be masked
assert isinstance(result.content[0], TextContent)
assert "Input should be a valid integer" in result.content[0].text
@ -73,7 +73,7 @@ class TestErrorHandling:
async with client:
result = await client.call_tool_mcp("custom_error_tool", {})
assert result.isError
assert result.is_error
assert isinstance(result.content[0], TextContent)
assert "test error" in result.content[0].text
assert "abc" in result.content[0].text
@ -283,7 +283,7 @@ class TestLogLevel:
with caplog.at_level(logging.WARNING):
result = await client.call_tool_mcp("custom_level_tool", {})
assert result.isError
assert result.is_error
assert isinstance(result.content[0], TextContent)
assert "Missing required parameter" in result.content[0].text
assert any(
@ -307,7 +307,7 @@ class TestLogLevel:
with caplog.at_level(logging.ERROR):
result = await client.call_tool_mcp("regular_error_tool", {})
assert result.isError
assert result.is_error
assert isinstance(result.content[0], TextContent)
assert "Something went wrong" in result.content[0].text
assert any(
@ -428,7 +428,7 @@ class TestLogLevel:
)
assert len(results) == 1
assert results[0].isError
assert results[0].is_error
assert "Expected sampling error" in results[0].content[0].text # type: ignore
assert any(
"Error calling sampling tool" in record.message

View file

@ -14,7 +14,7 @@ class TestInitialize:
async with client:
# Should be automatically initialized
assert client.initialize_result is not None
assert client.initialize_result.serverInfo.name == "TestServer"
assert client.initialize_result.server_info.name == "TestServer"
assert client.initialize_result.instructions is None
async def test_auto_initialize_explicit_true(self, fastmcp_server):
@ -23,7 +23,7 @@ class TestInitialize:
async with client:
assert client.initialize_result is not None
assert client.initialize_result.serverInfo.name == "TestServer"
assert client.initialize_result.server_info.name == "TestServer"
async def test_auto_initialize_false(self, fastmcp_server):
"""Test that auto_initialize=False prevents automatic initialization."""
@ -42,7 +42,7 @@ class TestInitialize:
result = await client.initialize()
assert result is not None
assert result.serverInfo.name == "TestServer"
assert result.server_info.name == "TestServer"
assert client.initialize_result is result
async def test_initialize_idempotent(self, fastmcp_server):
@ -90,7 +90,7 @@ class TestInitialize:
# Access via property
result = client.initialize_result
assert result is not None
assert result.serverInfo.name == "TestServer"
assert result.server_info.name == "TestServer"
# Call method - should return cached
result2 = await client.initialize()

View file

@ -252,7 +252,7 @@ def test_message_to_result_with_tools():
assert result.role == "assistant"
assert result.model == "claude-3-5-sonnet-20241022"
assert result.stopReason == "toolUse"
assert result.stop_reason == "toolUse"
content = result.content_as_list
assert len(content) == 2
assert content[0] == TextContent(type="text", text="I'll help you with that.")

View file

@ -372,7 +372,7 @@ def test_response_to_result_with_tools_text_only():
assert result.role == "assistant"
assert result.model == "gemini-2.0-flash"
assert result.stopReason == "endTurn"
assert result.stop_reason == "endTurn"
assert isinstance(result.content, list)
assert len(result.content) == 1
assert result.content[0].type == "text"
@ -394,7 +394,7 @@ def test_response_to_result_with_tools_function_call():
result = _response_to_result_with_tools(mock_response, model="gemini-2.0-flash")
assert result.stopReason == "toolUse"
assert result.stop_reason == "toolUse"
assert isinstance(result.content, list)
assert len(result.content) == 1
tool_use = result.content[0]
@ -421,7 +421,7 @@ def test_response_to_result_with_tools_mixed_content():
result = _response_to_result_with_tools(mock_response, model="gemini-2.0-flash")
assert result.stopReason == "toolUse"
assert result.stop_reason == "toolUse"
assert isinstance(result.content, list)
assert len(result.content) == 2
text_content = result.content[0]
@ -576,7 +576,7 @@ def test_normal_response_text_and_function_call():
assert isinstance(result.content[1], ToolUseContent) # ty: ignore[not-subscriptable]
assert result.content[1].name == "lookup" # ty: ignore[not-subscriptable]
assert result.content[1].input == {"q": "test"} # ty: ignore[not-subscriptable]
assert result.stopReason == "toolUse"
assert result.stop_reason == "toolUse"
def test_thought_with_function_call_keeps_function_call():
@ -598,4 +598,4 @@ def test_thought_with_function_call_keeps_function_call():
assert len(result.content) == 1 # ty: ignore[invalid-argument-type]
assert isinstance(result.content[0], ToolUseContent) # ty: ignore[not-subscriptable]
assert result.content[0].name == "get_weather" # ty: ignore[not-subscriptable]
assert result.stopReason == "toolUse"
assert result.stop_reason == "toolUse"

View file

@ -206,7 +206,7 @@ async def test_notification_with_failed_task(task_notification_server):
status = await task.status()
assert status.status == "failed"
assert (
status.statusMessage is not None
status.status_message is not None
) # Error details in statusMessage per spec

View file

@ -79,7 +79,7 @@ async def test_tool_task_status_and_wait(tool_task_server):
task = await client.call_tool("echo", {"message": "test"}, task=True)
status = await task.status()
assert status.taskId == task.task_id
assert status.task_id == task.task_id
assert status.status in ["working", "completed"]
# Wait for completion

View file

@ -108,7 +108,7 @@ async def test_elicitation_handler_parameters():
assert captured_params["message"] == "Test message"
assert "ScalarElicitationType" in str(captured_params["response_type"])
assert captured_params["params"].requestedSchema == {
assert captured_params["params"].requested_schema == {
"properties": {"value": {"title": "Value", "type": "integer"}},
"required": ["value"],
"title": "ScalarElicitationType",
@ -135,7 +135,7 @@ async def test_elicitation_response_title_and_description_on_scalar():
return "no answer"
async def elicitation_handler(message, response_type, params, ctx):
captured_schema.update(params.requestedSchema)
captured_schema.update(params.requested_schema)
return ElicitResult(action="accept", content={"value": True})
async with Client(mcp, elicitation_handler=elicitation_handler) as client:
@ -164,7 +164,7 @@ async def test_elicitation_response_title_on_dict_shorthand():
return "ok" if isinstance(result, AcceptedElicitation) else "none"
async def elicitation_handler(message, response_type, params, ctx):
captured_schema.update(params.requestedSchema)
captured_schema.update(params.requested_schema)
return ElicitResult(action="accept", content={"value": "low"})
async with Client(mcp, elicitation_handler=elicitation_handler) as client:
@ -188,7 +188,7 @@ async def test_elicitation_response_title_on_list_shorthand():
return "ok" if isinstance(result, AcceptedElicitation) else "none"
async def elicitation_handler(message, response_type, params, ctx):
captured_schema.update(params.requestedSchema)
captured_schema.update(params.requested_schema)
return ElicitResult(action="accept", content={"value": "red"})
async with Client(mcp, elicitation_handler=elicitation_handler) as client:
@ -302,7 +302,7 @@ class TestScalarResponseTypes:
message, response_type, params: ElicitRequestParams, ctx
):
assert isinstance(params, ElicitRequestFormParams)
assert params.requestedSchema == {"type": "object", "properties": {}}
assert params.requested_schema == {"type": "object", "properties": {}}
assert response_type is None
return ElicitResult(action="accept")
@ -612,7 +612,7 @@ async def test_structured_response_type(
)
# Verify the schema has the dataclass fields (available in params)
schema = params.requestedSchema
schema = params.requested_schema
assert schema["type"] == "object"
assert "name" in schema["properties"]
assert "age" in schema["properties"]

View file

@ -170,7 +170,7 @@ async def test_dict_based_titled_single_select():
async def elicitation_handler(message, response_type, params, ctx):
# Verify schema follows SEP-1330 pattern with type: "string"
schema = params.requestedSchema
schema = params.requested_schema
assert schema["type"] == "object"
assert "value" in schema["properties"]
value_schema = schema["properties"]["value"]
@ -205,7 +205,7 @@ async def test_list_list_multi_select_untitled():
async def elicitation_handler(message, response_type, params, ctx):
# Verify schema has array with enum pattern
schema = params.requestedSchema
schema = params.requested_schema
assert schema["type"] == "object"
assert "value" in schema["properties"]
value_schema = schema["properties"]["value"]
@ -243,7 +243,7 @@ async def test_list_dict_multi_select_titled():
async def elicitation_handler(message, response_type, params, ctx):
# Verify schema has array with SEP-1330 compliant items (anyOf pattern)
schema = params.requestedSchema
schema = params.requested_schema
assert schema["type"] == "object"
assert "value" in schema["properties"]
value_schema = schema["properties"]["value"]
@ -310,7 +310,7 @@ async def test_list_enum_multi_select_direct():
async def elicitation_handler(message, response_type, params, ctx):
# Verify schema has array with enum pattern
schema = params.requestedSchema
schema = params.requested_schema
assert schema["type"] == "object"
assert "value" in schema["properties"]
value_schema = schema["properties"]["value"]

View file

@ -85,8 +85,8 @@ async def test_sampling_with_system_prompt(fastmcp_server: FastMCP):
def sampling_handler(
messages: list[SamplingMessage], params: SamplingParams, ctx: RequestContext
) -> str:
assert params.systemPrompt is not None
return params.systemPrompt
assert params.system_prompt is not None
return params.system_prompt
async with Client(fastmcp_server, sampling_handler=sampling_handler) as client:
result = await client.call_tool(

View file

@ -218,7 +218,7 @@ class TestSamplingResultType:
tool_result = msg.content
break
assert tool_result is not None
assert tool_result.isError is True
assert tool_result.is_error is True
assert isinstance(tool_result.content[0], TextContent)
error_text = tool_result.content[0].text
assert "Validation error" in error_text

View file

@ -201,7 +201,7 @@ class TestAutomaticToolLoop:
tool_result = msg.content
break
assert tool_result is not None
assert tool_result.isError is True
assert tool_result.is_error is True
# Content is list of TextContent objects
assert isinstance(tool_result.content[0], TextContent)
error_text = tool_result.content[0].text
@ -275,7 +275,7 @@ class TestAutomaticToolLoop:
tool_result = msg.content
break
assert tool_result is not None
assert tool_result.isError is True
assert tool_result.is_error is True
# Content is list of TextContent objects
assert isinstance(tool_result.content[0], TextContent)
error_text = tool_result.content[0].text
@ -682,8 +682,8 @@ class TestAutomaticToolLoop:
tool_results = cast(list[ToolResultContent], tool_result_message.content)
assert len(tool_results) == 2
# One should be success, one should be error
assert any(not r.isError for r in tool_results)
assert any(r.isError for r in tool_results)
assert any(not r.is_error for r in tool_results)
assert any(r.is_error for r in tool_results)
async def test_concurrent_tool_result_order_preserved(self):
"""Test that tool results maintain the same order as tool calls."""
@ -761,9 +761,9 @@ class TestAutomaticToolLoop:
tool_result_message = messages_received[1][-1]
tool_results = cast(list[ToolResultContent], tool_result_message.content)
assert len(tool_results) == 3
assert tool_results[0].toolUseId == "call_1"
assert tool_results[1].toolUseId == "call_2"
assert tool_results[2].toolUseId == "call_3"
assert tool_results[0].tool_use_id == "call_1"
assert tool_results[1].tool_use_id == "call_2"
assert tool_results[2].tool_use_id == "call_3"
# Check values are correct
result_texts = [cast(TextContent, r.content[0]).text for r in tool_results]
assert result_texts == ["1", "2", "3"]

View file

@ -555,7 +555,7 @@ async def test_import_conflict_resolution_templates():
async with Client(main_app) as client:
# The later imported server should win
templates = await client.list_resource_templates()
template_uris = [t.uriTemplate for t in templates]
template_uris = [t.uri_template for t in templates]
assert "users://{user_id}/profile" in template_uris
assert (
template_uris.count("users://{user_id}/profile") == 1

View file

@ -64,8 +64,8 @@ class TestGithubMCPRemote:
assert isinstance(tool, Tool)
assert len(tool.name) > 0
assert tool.description is not None and len(tool.description) > 0
assert isinstance(tool.inputSchema, dict)
assert len(tool.inputSchema) > 0
assert isinstance(tool.input_schema, dict)
assert len(tool.input_schema) > 0
async def test_list_resources(
self, streamable_http_client: Client[StreamableHttpTransport]

View file

@ -632,7 +632,7 @@ class TestMessage:
msg = Message(img, role="user")
assert isinstance(msg.content, ImageContent)
assert msg.content.data == "base64data"
assert msg.content.mimeType == "image/png"
assert msg.content.mime_type == "image/png"
def test_message_passthrough_audio_content(self):
"""Test Message passes through AudioContent without JSON serialization."""
@ -642,7 +642,7 @@ class TestMessage:
msg = Message(audio, role="user")
assert isinstance(msg.content, AudioContent)
assert msg.content.data == "base64audio"
assert msg.content.mimeType == "audio/wav"
assert msg.content.mime_type == "audio/wav"
def test_message_image_content_to_mcp_prompt_message(self):
"""Test that ImageContent round-trips through to_mcp_prompt_message."""

View file

@ -261,7 +261,7 @@ class TestResourceContentToMcp:
assert hasattr(mcp_content, "text")
assert mcp_content.text == "hello world"
assert mcp_content.mimeType == "text/html"
assert mcp_content.mime_type == "text/html"
assert mcp_content.meta == {"csp": "script-src 'self'"}
def test_binary_content_to_mcp(self):
@ -275,18 +275,18 @@ class TestResourceContentToMcp:
assert hasattr(mcp_content, "blob")
assert mcp_content.blob == "AAEC" # base64 of \x00\x01\x02
assert mcp_content.mimeType == "application/octet-stream"
assert mcp_content.mime_type == "application/octet-stream"
assert mcp_content.meta == {"encoding": "raw"}
def test_default_mime_types(self):
"""Test default mime types are applied correctly."""
text_rc = ResourceContent(content="text")
text_mcp = text_rc.to_mcp_resource_contents("resource://test")
assert text_mcp.mimeType == "text/plain"
assert text_mcp.mime_type == "text/plain"
binary_rc = ResourceContent(content=b"binary")
binary_mcp = binary_rc.to_mcp_resource_contents("resource://test")
assert binary_mcp.mimeType == "application/octet-stream"
assert binary_mcp.mime_type == "application/octet-stream"
def test_none_meta(self):
"""Test that None meta is handled correctly."""

View file

@ -176,7 +176,7 @@ class TestResourceContent:
mcp_content = content.to_mcp_resource_contents("resource://test")
assert isinstance(mcp_content, mcp_types.TextResourceContents)
assert mcp_content.text == "hello"
assert mcp_content.mimeType == "text/plain"
assert mcp_content.mime_type == "text/plain"
assert str(mcp_content.uri) == "resource://test"
assert mcp_content.meta == {"k": "v"}
@ -188,7 +188,7 @@ class TestResourceContent:
mcp_content = content.to_mcp_resource_contents("resource://binary")
assert isinstance(mcp_content, mcp_types.BlobResourceContents)
assert mcp_content.blob == "AAEC" # base64 of \x00\x01\x02
assert mcp_content.mimeType == "application/octet-stream"
assert mcp_content.mime_type == "application/octet-stream"
class TestResourceResult:

View file

@ -165,7 +165,7 @@ class TestResourceDecorator:
async with Client(mcp) as client:
templates = await client.list_resource_templates()
assert any(t.uriTemplate == "users://{user_id}/profile" for t in templates)
assert any(t.uri_template == "users://{user_id}/profile" for t in templates)
result = await client.read_resource("users://123/profile")
assert "123" in str(result)

View file

@ -521,7 +521,7 @@ class TestAuthMiddleware:
result = await mcp._list_resource_templates_mcp(
mcp_types.ListResourceTemplatesRequest()
)
assert [template.uriTemplate for template in result.resourceTemplates] == [
assert [template.uri_template for template in result.resource_templates] == [
"resource://allowed/{item}"
]

View file

@ -32,7 +32,7 @@ class TestDereferenceRefsMiddleware:
async with Client(mcp) as client:
tools = await client.list_tools()
schema = tools[0].inputSchema
schema = tools[0].input_schema
# $defs should be removed — everything inlined
assert "$defs" not in schema
# The Color enum should be inlined into the request property
@ -49,7 +49,7 @@ class TestDereferenceRefsMiddleware:
async with Client(mcp) as client:
tools = await client.list_tools()
schema = tools[0].inputSchema
schema = tools[0].input_schema
# $defs should still be present
assert "$defs" in schema
@ -64,7 +64,7 @@ class TestDereferenceRefsMiddleware:
async with Client(mcp) as client:
tools = await client.list_tools()
schema = tools[0].inputSchema
schema = tools[0].input_schema
assert "$defs" not in schema
async def test_does_not_mutate_original_tool(self):
@ -100,9 +100,9 @@ class TestDereferenceRefsMiddleware:
tool = tools[0]
# Both input and output schemas should be dereferenced
assert "$defs" not in tool.inputSchema
if tool.outputSchema is not None:
assert "$defs" not in tool.outputSchema
assert "$defs" not in tool.input_schema
if tool.output_schema is not None:
assert "$defs" not in tool.output_schema
async def test_resource_templates_dereferenced(self):
"""Middleware dereferences resource template schemas."""
@ -130,7 +130,7 @@ class TestDereferenceRefsMiddleware:
async with Client(mcp) as client:
tools = await client.list_tools()
schema = tools[0].inputSchema
schema = tools[0].input_schema
# Simple schema should not have $defs regardless
assert "$defs" not in schema
assert schema["properties"]["a"]["type"] == "integer"

View file

@ -39,7 +39,7 @@ class InitializationMiddleware(Middleware):
if hasattr(context.message, "params") and hasattr(
context.message.params, "clientInfo"
):
self.client_info = context.message.params.clientInfo
self.client_info = context.message.params.client_info
# Store in instance for cross-request access
# (session state is not available during on_initialize)
@ -96,7 +96,7 @@ class ClientDetectionMiddleware(Middleware):
if tool.annotations is None:
tool.annotations = mt.ToolAnnotations()
# Mark as read-only for test clients
tool.annotations.readOnlyHint = True
tool.annotations.read_only_hint = True
self.tools_modified = True
return tools
@ -173,7 +173,7 @@ async def test_client_detection_middleware():
# Check that the tool has the modified annotation
tool = tools[0]
assert tool.annotations is not None
assert tool.annotations.readOnlyHint is True
assert tool.annotations.read_only_hint is True
async def test_multiple_middleware_initialization():
@ -293,8 +293,8 @@ async def test_middleware_can_access_initialize_result():
assert isinstance(middleware.initialize_result, mt.InitializeResult)
# Verify the result contains expected server info
assert middleware.initialize_result.serverInfo.name == "TestServer"
assert middleware.initialize_result.protocolVersion is not None
assert middleware.initialize_result.server_info.name == "TestServer"
assert middleware.initialize_result.protocol_version is not None
assert middleware.initialize_result.capabilities is not None

View file

@ -391,7 +391,7 @@ class TestMiddlewareHooks:
templates = await client.list_resource_templates()
assert len(templates) == 1
assert str(templates[0].uriTemplate) == "resource://public/{x}"
assert str(templates[0].uri_template) == "resource://public/{x}"
async def test_list_prompts_filtering_middleware(self):
"""Test that middleware can filter prompts."""

View file

@ -216,8 +216,8 @@ class TestToolInjectionMiddleware:
multiply_tool = next(t for t in tools if t.name == "multiply")
assert multiply_tool.description == "Multiply two numbers."
assert "a" in multiply_tool.inputSchema["properties"]
assert "b" in multiply_tool.inputSchema["properties"]
assert "a" in multiply_tool.input_schema["properties"]
assert "b" in multiply_tool.input_schema["properties"]
async def test_injected_tool_does_not_conflict_with_base_tool(
self, base_server: FastMCP

View file

@ -123,7 +123,7 @@ class TestToolReturnTypes:
content = result.content[0]
assert isinstance(content, ImageContent)
assert content.type == "image"
assert content.mimeType == "image/png"
assert content.mime_type == "image/png"
decoded = base64.b64decode(content.data)
assert decoded == b"fake png data"
@ -142,7 +142,7 @@ class TestToolReturnTypes:
content = result.content[0]
assert isinstance(content, AudioContent)
assert content.type == "audio"
assert content.mimeType == "audio/wav"
assert content.mime_type == "audio/wav"
decoded = base64.b64decode(content.data)
assert decoded == b"fake wav data"
@ -162,7 +162,7 @@ class TestToolReturnTypes:
assert isinstance(content, EmbeddedResource)
assert content.type == "resource"
resource = content.resource
assert resource.mimeType == "application/octet-stream"
assert resource.mime_type == "application/octet-stream"
assert hasattr(resource, "blob")
blob_data = getattr(resource, "blob")
decoded = base64.b64decode(blob_data)
@ -179,12 +179,12 @@ class TestToolReturnTypes:
assert isinstance(content1, TextContent)
assert content1.text == "Hello"
assert isinstance(content2, ImageContent)
assert content2.mimeType == "application/octet-stream"
assert content2.mime_type == "application/octet-stream"
assert content2.data == "abc"
assert isinstance(content3, EmbeddedResource)
assert content3.type == "resource"
resource = content3.resource
assert resource.mimeType == "application/octet-stream"
assert resource.mime_type == "application/octet-stream"
assert hasattr(resource, "blob")
blob_data = getattr(resource, "blob")
decoded = base64.b64decode(blob_data)
@ -208,7 +208,7 @@ class TestToolReturnTypes:
assert content1.text == "text message"
content2 = result.content[1]
assert isinstance(content2, ImageContent)
assert content2.mimeType == "image/png"
assert content2.mime_type == "image/png"
assert base64.b64decode(content2.data) == b"test image data"
content3 = result.content[2]
assert isinstance(content3, TextContent)
@ -235,7 +235,7 @@ class TestToolReturnTypes:
assert content1.text == "text message"
content2 = result.content[1]
assert isinstance(content2, AudioContent)
assert content2.mimeType == "audio/wav"
assert content2.mime_type == "audio/wav"
assert base64.b64decode(content2.data) == b"test audio data"
content3 = result.content[2]
assert isinstance(content3, TextContent)
@ -264,7 +264,7 @@ class TestToolReturnTypes:
assert isinstance(content2, EmbeddedResource)
assert content2.type == "resource"
resource = content2.resource
assert resource.mimeType == "application/octet-stream"
assert resource.mime_type == "application/octet-stream"
assert hasattr(resource, "blob")
blob_data = getattr(resource, "blob")
assert base64.b64decode(blob_data) == b"test file data"

View file

@ -105,7 +105,7 @@ class TestToolParameters:
assert result.structured_content is None
assert isinstance(result.content, list)
assert isinstance(result.content[0], ImageContent)
assert result.content[0].mimeType == "image/png"
assert result.content[0].mime_type == "image/png"
assert result.content[0].data == base64.b64encode(b"fake png data").decode()
async def test_tool_with_invalid_input(self):

View file

@ -430,7 +430,7 @@ class TestOpenAPIComprehensive:
collision_tool = next(
tool for tool in tools if tool.name == "collision_test"
)
schema = collision_tool.inputSchema
schema = collision_tool.input_schema
properties = schema["properties"]
# Should have unique parameter names for colliding parameters
@ -461,7 +461,7 @@ class TestOpenAPIComprehensive:
search_tool = next(
tool for tool in tools if tool.name == "search_users"
)
schema = search_tool.inputSchema
schema = search_tool.input_schema
properties = schema["properties"]
# Should have flattened deepObject parameters
@ -681,7 +681,7 @@ class TestOpenAPIComprehensive:
# Find create_user tool which uses schema refs
create_tool = next(tool for tool in tools if tool.name == "create_user")
schema = create_tool.inputSchema
schema = create_tool.input_schema
properties = schema["properties"]
# Should have resolved User schema properties
@ -702,7 +702,7 @@ class TestOpenAPIComprehensive:
# Check list_users tool - has optional query parameters
list_tool = next(tool for tool in tools if tool.name == "list_users")
schema = list_tool.inputSchema
schema = list_tool.input_schema
# Query parameters should be optional
# (may not appear in required list)
# This test just ensures the schema is well-formed
@ -712,7 +712,7 @@ class TestOpenAPIComprehensive:
search_tool = next(
tool for tool in tools if tool.name == "search_users"
)
search_schema = search_tool.inputSchema
search_schema = search_tool.input_schema
# Should have some required parameters
assert len(search_schema["properties"]) > 0

View file

@ -206,7 +206,7 @@ class TestDeepObjectStyle:
assert surveys_tool is not None
# Check that deepObject parameters are included in schema
params = surveys_tool.inputSchema
params = surveys_tool.input_schema
properties = params["properties"]
# Should have the deepObject parameters
@ -236,7 +236,7 @@ class TestDeepObjectStyle:
)
# Check that explode=true parameters are properly structured
params = surveys_tool.inputSchema
params = surveys_tool.input_schema
properties = params["properties"]
# Target parameter with explode=true should allow individual property access
@ -261,7 +261,7 @@ class TestDeepObjectStyle:
)
# Check that explode=false parameters are handled
params = surveys_tool.inputSchema
params = surveys_tool.input_schema
properties = params["properties"]
# Compact parameter with explode=false should still be present and valid
@ -292,7 +292,7 @@ class TestDeepObjectStyle:
assert prefs_tool is not None
# Check that nested object structure is preserved
params = prefs_tool.inputSchema
params = prefs_tool.input_schema
properties = params["properties"]
# Should have path parameter
@ -338,9 +338,9 @@ class TestDeepObjectStyle:
# All tools should have valid schemas
for tool in tools:
assert tool.inputSchema is not None
assert tool.inputSchema["type"] == "object"
assert "properties" in tool.inputSchema
assert tool.input_schema is not None
assert tool.input_schema["type"] == "object"
assert "properties" in tool.input_schema
# Should have some properties
assert len(tool.inputSchema["properties"]) > 0
assert len(tool.input_schema["properties"]) > 0

View file

@ -114,7 +114,7 @@ class TestEndToEndFunctionality:
assert tool.description
# Check schema structure
schema = tool.inputSchema
schema = tool.input_schema
assert schema["type"] == "object"
properties = schema.get("properties", {})
@ -141,7 +141,7 @@ class TestEndToEndFunctionality:
assert len(tools) == 1
tool = tools[0]
schema = tool.inputSchema
schema = tool.input_schema
# Both should have collision-resolved parameters
properties = schema.get("properties", {})

View file

@ -163,7 +163,7 @@ class TestParameterHandling:
assert search_tool is not None
# Check that parameters are included in the tool's input schema
params = search_tool.inputSchema
params = search_tool.input_schema
assert params["type"] == "object"
properties = params["properties"]
@ -211,7 +211,7 @@ class TestParameterHandling:
assert user_post_tool is not None
# Check that path parameters are included
params = user_post_tool.inputSchema
params = user_post_tool.input_schema
properties = params["properties"]
# Check that path parameters are present
@ -316,7 +316,7 @@ class TestRequestBodyHandling:
assert create_tool is not None
# Check that request body properties are included
params = create_tool.inputSchema
params = create_tool.input_schema
properties = params["properties"]
# Check that request body properties are present
@ -637,7 +637,7 @@ class TestResourceTemplateMimeType:
async with Client(mcp) as mcp_client:
templates = await mcp_client.list_resource_templates()
assert len(templates) == 1
assert templates[0].mimeType == "text/plain"
assert templates[0].mime_type == "text/plain"
async def test_resource_template_html_mime_type(self, html_spec):
"""Resource template should reflect text/html from OpenAPI spec."""
@ -651,7 +651,7 @@ class TestResourceTemplateMimeType:
async with Client(mcp) as mcp_client:
templates = await mcp_client.list_resource_templates()
assert len(templates) == 1
assert templates[0].mimeType == "text/html"
assert templates[0].mime_type == "text/html"
async def test_resource_template_defaults_json_mime_type(self):
"""Resource template defaults to application/json for JSON responses."""
@ -702,7 +702,7 @@ class TestResourceTemplateMimeType:
async with Client(mcp) as mcp_client:
templates = await mcp_client.list_resource_templates()
assert len(templates) == 1
assert templates[0].mimeType == "application/json"
assert templates[0].mime_type == "application/json"
class TestResourceMimeType:
@ -741,7 +741,7 @@ class TestResourceMimeType:
async with Client(mcp) as mcp_client:
resources = await mcp_client.list_resources()
assert len(resources) == 1
assert resources[0].mimeType == "text/plain"
assert resources[0].mime_type == "text/plain"
async def test_resource_mime_type_without_schema(self):
"""Resource with media type but no schema still infers MIME type."""
@ -774,7 +774,7 @@ class TestResourceMimeType:
async with Client(mcp) as mcp_client:
resources = await mcp_client.list_resources()
assert len(resources) == 1
assert resources[0].mimeType == "text/plain"
assert resources[0].mime_type == "text/plain"
class TestValidateOutput:
@ -981,10 +981,10 @@ class TestValidateOutput:
tools = await mcp_client.list_tools()
get_user = next(t for t in tools if t.name == "get_user")
# With validate_output=False, the outputSchema should be permissive
assert get_user.outputSchema is not None
assert get_user.outputSchema.get("additionalProperties") is True
assert get_user.output_schema is not None
assert get_user.output_schema.get("additionalProperties") is True
# Should NOT have specific properties from the original schema
assert "properties" not in get_user.outputSchema
assert "properties" not in get_user.output_schema
class TestRedactHeaders:

View file

@ -146,7 +146,7 @@ class TestParameterCollisions:
assert update_tool is not None
# Check that both path and body 'id' parameters are included
params = update_tool.inputSchema
params = update_tool.input_schema
properties = params["properties"]
# Should have both path ID and body ID (with potential suffixing)
@ -188,7 +188,7 @@ class TestParameterCollisions:
assert search_tool is not None
# Check that both query and header 'query' parameters are handled
params = search_tool.inputSchema
params = search_tool.input_schema
properties = params["properties"]
# Should handle the collision somehow (suffixing or other mechanism)
@ -220,6 +220,6 @@ class TestParameterCollisions:
# Tools should have valid schemas
for tool in tools:
assert tool.inputSchema is not None
assert tool.inputSchema["type"] == "object"
assert "properties" in tool.inputSchema
assert tool.input_schema is not None
assert tool.input_schema["type"] == "object"
assert "properties" in tool.input_schema

View file

@ -348,7 +348,7 @@ class TestOpenAPIProviderBasicFunctionality:
},
"required": ["name", "active"],
}
assert tool.inputSchema == expected_input_schema
assert tool.input_schema == expected_input_schema
expected_output_schema = {
"type": "object",
@ -360,4 +360,4 @@ class TestOpenAPIProviderBasicFunctionality:
"required": ["id", "name", "active"],
"title": "User",
}
assert tool.outputSchema == expected_output_schema
assert tool.output_schema == expected_output_schema

View file

@ -152,10 +152,10 @@ class TestProxyClient:
content=TextContent(type="text", text="Hello, world!"),
)
]
assert params.systemPrompt == "You love FastMCP"
assert params.system_prompt == "You love FastMCP"
assert params.temperature == 0.5
assert params.maxTokens == 100
assert params.modelPreferences == ModelPreferences(
assert params.max_tokens == 100
assert params.model_preferences == ModelPreferences(
hints=[ModelHint(name="gpt-4o")]
)
return ""
@ -189,7 +189,7 @@ class TestProxyClient:
assert message == "What is your name?"
assert "Person" in str(response_type)
assert isinstance(params, ElicitRequestFormParams)
assert params.requestedSchema == {
assert params.requested_schema == {
"title": "Person",
"type": "object",
"properties": {"name": {"title": "Name", "type": "string"}},
@ -388,7 +388,7 @@ class TestProxyClient:
):
# Verify the schema is correct - acknowledge should have default=False, not be nullable
assert isinstance(params, ElicitRequestFormParams)
schema = params.requestedSchema
schema = params.requested_schema
assert schema["properties"]["acknowledge"]["type"] == "boolean"
assert schema["properties"]["acknowledge"]["default"] is False

View file

@ -479,7 +479,7 @@ class TestTools:
async with Client(proxy_server) as client:
tools = await client.list_tools()
greet_tool = next(t for t in tools if t.name == "greet")
assert "extra" in greet_tool.inputSchema["properties"]
assert "extra" in greet_tool.input_schema["properties"]
class TestResources:
@ -546,18 +546,18 @@ class TestResources:
assert isinstance(original, TextResourceContents)
assert isinstance(proxied, TextResourceContents)
assert original.text == proxied.text, f"Content {i} text mismatch"
assert original.mimeType == proxied.mimeType, (
assert original.mime_type == proxied.mime_type, (
f"Content {i} mimeType mismatch"
)
assert original.meta == proxied.meta, f"Content {i} meta mismatch"
# Verify the contents are what we expect
assert original_result[0].text == "First item"
assert original_result[0].mimeType == "text/plain"
assert original_result[0].mime_type == "text/plain"
assert original_result[1].text == '{"key": "value"}'
assert original_result[1].mimeType == "application/json"
assert original_result[1].mime_type == "application/json"
assert original_result[2].text == "# Markdown\nContent"
assert original_result[2].mimeType == "text/markdown"
assert original_result[2].mime_type == "text/markdown"
async def test_read_resource_returns_none_if_not_found(self, proxy_server):
with pytest.raises(
@ -661,15 +661,15 @@ class TestResourceTemplates:
assert isinstance(original, TextResourceContents)
assert isinstance(proxied, TextResourceContents)
assert original.text == proxied.text, f"Content {i} text mismatch"
assert original.mimeType == proxied.mimeType, (
assert original.mime_type == proxied.mime_type, (
f"Content {i} mimeType mismatch"
)
# Verify the contents are what we expect
assert original_result[0].text == "Item test123 - First"
assert original_result[0].mimeType == "text/plain"
assert original_result[0].mime_type == "text/plain"
assert original_result[1].text == '{"id": "test123", "status": "active"}'
assert original_result[1].mimeType == "application/json"
assert original_result[1].mime_type == "application/json"
async def test_proxy_can_overwrite_proxied_resource_template(self, proxy_server):
"""
@ -706,7 +706,7 @@ class TestResourceTemplates:
async with Client(proxy_server) as client:
templates = await client.list_resource_templates()
user_template = next(
t for t in templates if t.uriTemplate == "data://user/{user_id}"
t for t in templates if t.uri_template == "data://user/{user_id}"
)
assert user_template.name == "overwritten_get_user"
@ -888,7 +888,7 @@ class TestPrompts:
# Verify the image content is preserved as ImageContent, not JSON text
assert isinstance(proxy_result.messages[1].content, mcp_types.ImageContent)
assert proxy_result.messages[1].content.data == "iVBORw0KGgoAAAANSUhEUg=="
assert proxy_result.messages[1].content.mimeType == "image/png"
assert proxy_result.messages[1].content.mime_type == "image/png"
async def test_proxy_handles_multiple_concurrent_tasks_correctly(

View file

@ -125,7 +125,7 @@ class TestSamplingToolSDKConversion:
assert sdk_tool.name == "search"
assert sdk_tool.description == "Search the web."
assert "query" in sdk_tool.inputSchema.get("properties", {})
assert "query" in sdk_tool.input_schema.get("properties", {})
class TestSamplingToolFromCallableTool:

View file

@ -97,7 +97,7 @@ async def test_progress_status_message_in_background_task():
# Verify statusMessage field is accessible and contains progress info
# Should not raise AttributeError
msg = status.statusMessage
msg = status.status_message
assert msg is None or msg.startswith("Step")
# Wait for completion

View file

@ -185,7 +185,7 @@ class TestResourceTaskMetaDirectServerCall:
# Read inner resource as background task
result = await server.read_resource("data://inner", task_meta=TaskMeta())
# Should get CreateTaskResult since we provided task_meta
return f"Created task: {result.task.taskId}"
return f"Created task: {result.task.task_id}"
async with Client(server) as client:
result = await client.call_tool("outer_tool", {})
@ -221,7 +221,7 @@ class TestResourceTaskMetaDirectServerCall:
@server.tool
async def outer_tool() -> str:
result = await server.read_resource("item://99", task_meta=TaskMeta())
return f"Created task: {result.task.taskId}"
return f"Created task: {result.task.task_id}"
async with Client(server) as client:
result = await client.call_tool("outer_tool", {})

View file

@ -265,7 +265,7 @@ class TestPromptModeEnforcement:
class TestToolExecutionMetadata:
"""Test that ToolExecution.taskSupport is set correctly in tool metadata."""
"""Test that ToolExecution.task_support is set correctly in tool metadata."""
async def test_optional_tool_exposes_task_support(self):
"""Tools with task enabled should expose taskSupport in metadata."""
@ -280,7 +280,7 @@ class TestToolExecutionMetadata:
tool = next(t for t in tools if t.name == "my_tool")
assert isinstance(tool, MCPTool)
assert isinstance(tool.execution, ToolExecution)
assert tool.execution.taskSupport == "optional"
assert tool.execution.task_support == "optional"
async def test_required_tool_exposes_task_support(self):
"""Tools with mode=required should expose taskSupport='required'."""
@ -295,7 +295,7 @@ class TestToolExecutionMetadata:
tool = next(t for t in tools if t.name == "my_tool")
assert isinstance(tool, MCPTool)
assert isinstance(tool.execution, ToolExecution)
assert tool.execution.taskSupport == "required"
assert tool.execution.task_support == "required"
async def test_forbidden_tool_has_no_execution(self):
"""Tools with mode=forbidden should not expose execution metadata."""

View file

@ -263,7 +263,7 @@ class TestTaskMetaDirectServerCall:
"inner_tool", {"x": x}, task_meta=TaskMeta()
)
# Should get CreateTaskResult since we're in server context
return f"Created task: {result.task.taskId}"
return f"Created task: {result.task.task_id}"
async with Client(server) as client:
# Call outer_tool which internally calls inner_tool with task_meta

View file

@ -35,7 +35,7 @@ async def test_tasks_get_includes_related_task_metadata(metadata_server: FastMCP
# GetTaskResult is returned from response with metadata
# Verify the protocol included related-task metadata by checking the response worked
assert status.taskId == task_id
assert status.task_id == task_id
assert status.status in ["working", "completed"]

View file

@ -45,7 +45,7 @@ async def test_tasks_get_endpoint_returns_status(endpoint_server):
# Check status immediately - should be submitted or working
status = await task.status()
assert status.taskId == task.task_id
assert status.task_id == task.task_id
assert status.status in ["working", "completed"]
# Wait for completion
@ -62,8 +62,8 @@ async def test_tasks_get_endpoint_includes_poll_interval(endpoint_server):
task = await client.call_tool("quick_tool", {"value": 42}, task=True)
status = await task.status()
assert status.pollInterval is not None
assert isinstance(status.pollInterval, int)
assert status.poll_interval is not None
assert isinstance(status.poll_interval, int)
async def test_tasks_result_endpoint_returns_result_when_completed(endpoint_server):

View file

@ -553,7 +553,7 @@ class TestMountedTaskMetadata:
"""Test task metadata exposure for mounted tools."""
async def test_mounted_tool_list_preserves_task_support_metadata(self):
"""Mounted tools should preserve execution.taskSupport in tools/list."""
"""Mounted tools should preserve execution.task_support in tools/list."""
child = FastMCP("child")
@child.tool(task=True)
@ -574,11 +574,11 @@ class TestMountedTaskMetadata:
assert child_mcp_tool.execution is not None
assert parent_mcp_tool.execution is not None
assert child_mcp_tool.execution.taskSupport == "optional"
assert parent_mcp_tool.execution.taskSupport == "optional"
assert child_mcp_tool.execution.task_support == "optional"
assert parent_mcp_tool.execution.task_support == "optional"
async def test_proxy_tool_preserves_execution_metadata(self):
"""ProxyTool.from_mcp_tool should propagate execution.taskSupport (#3569)."""
"""ProxyTool.from_mcp_tool should propagate execution.task_support (#3569)."""
mcp_tool = MCPTool(
name="remote_task_tool",
description="A remote tool that supports tasks",
@ -590,7 +590,7 @@ class TestMountedTaskMetadata:
result = proxy.to_mcp_tool(name=proxy.name)
assert result.execution is not None
assert result.execution.taskSupport == "optional"
assert result.execution.task_support == "optional"
class TestMountedTaskConfigModes:
@ -928,7 +928,7 @@ class TestMountedTasksWithTaskMetaParameter:
result = await parent.call_tool(
"child_add", {"a": 2, "b": 3}, task_meta=TaskMeta(ttl=300)
)
return f"task:{result.task.taskId}"
return f"task:{result.task.task_id}"
async with Client(parent) as client:
result = await client.call_tool("outer", {})
@ -952,7 +952,7 @@ class TestMountedTasksWithTaskMetaParameter:
result = await parent.read_resource(
"data://child/info", task_meta=TaskMeta(ttl=300)
)
return f"task:{result.task.taskId}"
return f"task:{result.task.task_id}"
async with Client(parent) as client:
result = await client.call_tool("outer", {})
@ -976,7 +976,7 @@ class TestMountedTasksWithTaskMetaParameter:
result = await parent.read_resource(
"item://child/42", task_meta=TaskMeta(ttl=300)
)
return f"task:{result.task.taskId}"
return f"task:{result.task.task_id}"
async with Client(parent) as client:
result = await client.call_tool("outer", {})
@ -1003,7 +1003,7 @@ class TestMountedTasksWithTaskMetaParameter:
result = await parent.call_tool(
"c_gc_compute", {"n": 7}, task_meta=TaskMeta(ttl=300)
)
return f"task:{result.task.taskId}"
return f"task:{result.task.task_id}"
async with Client(parent) as client:
result = await client.call_tool("outer", {})
@ -1030,7 +1030,7 @@ class TestMountedTasksWithTaskMetaParameter:
result = await parent.read_resource(
"doc://c/gc/readme", task_meta=TaskMeta(ttl=300)
)
return f"task:{result.task.taskId}"
return f"task:{result.task.task_id}"
async with Client(parent) as client:
result = await client.call_tool("outer", {})
@ -1054,7 +1054,7 @@ class TestMountedTasksWithTaskMetaParameter:
result = await parent.render_prompt(
"child_greet", {"name": "World"}, task_meta=TaskMeta(ttl=300)
)
return f"task:{result.task.taskId}"
return f"task:{result.task.task_id}"
async with Client(parent) as client:
result = await client.call_tool("outer", {})
@ -1081,7 +1081,7 @@ class TestMountedTasksWithTaskMetaParameter:
result = await parent.render_prompt(
"c_gc_describe", {"topic": "FastMCP"}, task_meta=TaskMeta(ttl=300)
)
return f"task:{result.task.taskId}"
return f"task:{result.task.task_id}"
async with Client(parent) as client:
result = await client.call_tool("outer", {})

View file

@ -61,7 +61,7 @@ async def test_task_notification_sent_after_submission(task_enabled_server):
# Verify we can query the task
status = await task.status()
assert status.taskId == task.task_id
assert status.task_id == task.task_id
async def test_failed_task_stores_error(task_enabled_server):

View file

@ -407,7 +407,7 @@ async def media_server(tmp_path):
lambda r: (
len(r.content) == 1
and r.content[0].type == "image"
and r.content[0].mimeType == "image/png"
and r.content[0].mime_type == "image/png"
),
),
(
@ -630,7 +630,7 @@ async def mcp_content_server(tmp_path):
lambda r: (
len(r.content) == 1
and r.content[0].type == "image"
and r.content[0].mimeType == "image/png"
and r.content[0].mime_type == "image/png"
),
),
(

View file

@ -139,10 +139,10 @@ async def test_dependencies_excluded_from_schema(mcp: FastMCP):
result = await mcp._list_tools_mcp(mcp_types.ListToolsRequest())
tool = next(t for t in result.tools if t.name == "my_tool")
assert "name" in tool.inputSchema["properties"]
assert "age" in tool.inputSchema["properties"]
assert "config" not in tool.inputSchema["properties"]
assert len(tool.inputSchema["properties"]) == 2
assert "name" in tool.input_schema["properties"]
assert "age" in tool.input_schema["properties"]
assert "config" not in tool.input_schema["properties"]
assert len(tool.input_schema["properties"]) == 2
async def test_current_context_dependency(mcp: FastMCP):
@ -471,8 +471,8 @@ async def test_connection_dependency_excluded_from_tool_schema(mcp: FastMCP):
result = await mcp._list_tools_mcp(mcp_types.ListToolsRequest())
tool = next(t for t in result.tools if t.name == "with_connection")
assert "name" in tool.inputSchema["properties"]
assert "connection" not in tool.inputSchema["properties"]
assert "name" in tool.input_schema["properties"]
assert "connection" not in tool.input_schema["properties"]
async def test_sync_tool_context_manager_stays_open(mcp: FastMCP):
@ -592,7 +592,7 @@ async def test_external_user_cannot_override_dependency(mcp: FastMCP):
# Verify dependency is NOT in the schema
result = await mcp._list_tools_mcp(mcp_types.ListToolsRequest())
tool = next(t for t in result.tools if t.name == "check_permission")
assert "admin" not in tool.inputSchema["properties"]
assert "admin" not in tool.input_schema["properties"]
# Normal call - dependency is resolved
result = await mcp.call_tool("check_permission", {"action": "read"})
@ -979,8 +979,8 @@ class TestAuthDependencies:
result = await mcp._list_tools_mcp(mcp_types.ListToolsRequest())
tool = next(t for t in result.tools if t.name == "tool_with_token")
assert "name" in tool.inputSchema["properties"]
assert "token" not in tool.inputSchema["properties"]
assert "name" in tool.input_schema["properties"]
assert "token" not in tool.input_schema["properties"]
async def test_token_claim_excluded_from_tool_schema(self, mcp: FastMCP):
"""Test that TokenClaim dependency is excluded from tool schema."""
@ -998,8 +998,8 @@ class TestAuthDependencies:
result = await mcp._list_tools_mcp(mcp_types.ListToolsRequest())
tool = next(t for t in result.tools if t.name == "tool_with_claim")
assert "name" in tool.inputSchema["properties"]
assert "user_id" not in tool.inputSchema["properties"]
assert "name" in tool.input_schema["properties"]
assert "user_id" not in tool.input_schema["properties"]
def test_current_access_token_exported_from_all(self):
"""Test that CurrentAccessToken is exported from __all__."""
@ -1143,8 +1143,8 @@ class TestSharedDependencies:
result = await mcp._list_tools_mcp(mcp_types.ListToolsRequest())
tool = next(t for t in result.tools if t.name == "my_tool")
assert "name" in tool.inputSchema["properties"]
assert "db" not in tool.inputSchema["properties"]
assert "name" in tool.input_schema["properties"]
assert "db" not in tool.input_schema["properties"]
async def test_shared_in_resource(self, mcp: FastMCP):
"""Shared dependencies work in resource functions."""

View file

@ -172,9 +172,9 @@ class TestTransformContextAnnotations:
# Both ctx params should be excluded from schema
result = await mcp._list_tools_mcp(mcp_types.ListToolsRequest())
tool = next(t for t in result.tools if t.name == "tool_with_multiple_ctx")
assert "name" in tool.inputSchema["properties"]
assert "ctx1" not in tool.inputSchema["properties"]
assert "ctx2" not in tool.inputSchema["properties"]
assert "name" in tool.input_schema["properties"]
assert "ctx1" not in tool.input_schema["properties"]
assert "ctx2" not in tool.input_schema["properties"]
async def test_context_in_class_method(self, mcp: FastMCP):
"""Test Context transformation works with bound methods."""

View file

@ -36,8 +36,8 @@ class TestServerIcons:
# Verify that icons and website_url are passed to the underlying server
async with Client(mcp) as client:
server_info = client.initialize_result.serverInfo
assert server_info.websiteUrl == "https://example.com"
server_info = client.initialize_result.server_info
assert server_info.website_url == "https://example.com"
assert server_info.icons == icons
async def test_server_without_icons_and_website_url(self):
@ -45,8 +45,8 @@ class TestServerIcons:
mcp = FastMCP(name="TestServer")
async with Client(mcp) as client:
server_info = client.initialize_result.serverInfo
assert server_info.websiteUrl is None
server_info = client.initialize_result.server_info
assert server_info.website_url is None
assert server_info.icons is None
@ -290,7 +290,7 @@ class TestIconTypes:
mcp = FastMCP("TestServer", icons=icons)
async with Client(mcp) as client:
server_info = client.initialize_result.serverInfo
server_info = client.initialize_result.server_info
assert len(server_info.icons) == 3
assert server_info.icons == icons
@ -319,9 +319,9 @@ class TestIconTypes:
mcp = FastMCP("TestServer", icons=icons)
async with Client(mcp) as client:
server_info = client.initialize_result.serverInfo
server_info = client.initialize_result.server_info
assert server_info.icons[0].src == "https://example.com/icon.png"
assert server_info.icons[0].mimeType is None
assert server_info.icons[0].mime_type is None
assert server_info.icons[0].sizes is None

View file

@ -166,17 +166,17 @@ class TestServerPagination:
# First page
result = await client.list_tools_mcp()
assert len(result.tools) == 10
assert result.nextCursor is not None
assert result.next_cursor is not None
# Second page
result2 = await client.list_tools_mcp(cursor=result.nextCursor)
result2 = await client.list_tools_mcp(cursor=result.next_cursor)
assert len(result2.tools) == 10
assert result2.nextCursor is not None
assert result2.next_cursor is not None
# Third (last) page
result3 = await client.list_tools_mcp(cursor=result2.nextCursor)
result3 = await client.list_tools_mcp(cursor=result2.next_cursor)
assert len(result3.tools) == 5
assert result3.nextCursor is None
assert result3.next_cursor is None
async def test_invalid_cursor_returns_error(self) -> None:
"""Server should return MCP error for invalid cursor."""
@ -204,7 +204,7 @@ class TestServerPagination:
async with Client(server) as client:
result = await client.list_tools_mcp()
assert len(result.tools) == 25
assert result.nextCursor is None
assert result.next_cursor is None
async def test_pagination_exact_page_boundary(self) -> None:
"""Test pagination at exact page boundaries."""
@ -220,12 +220,12 @@ class TestServerPagination:
# First page
result = await client.list_tools_mcp()
assert len(result.tools) == 10
assert result.nextCursor is not None
assert result.next_cursor is not None
# Second (last) page
result2 = await client.list_tools_mcp(cursor=result.nextCursor)
result2 = await client.list_tools_mcp(cursor=result.next_cursor)
assert len(result2.tools) == 10
assert result2.nextCursor is None
assert result2.next_cursor is None
class TestPageSizeValidation:
@ -265,7 +265,7 @@ class TestPaginationCycleDetection:
cursor: str | None = None,
) -> mcp_types.ListToolsResult:
result = await original(cursor=cursor)
result.nextCursor = "stuck"
result.next_cursor = "stuck"
return result
with patch.object(
@ -294,7 +294,7 @@ class TestPaginationCycleDetection:
cursor: str | None = None,
) -> mcp_types.ListPromptsResult:
result = await original(cursor=cursor)
result.nextCursor = "stuck"
result.next_cursor = "stuck"
return result
with patch.object(
@ -321,7 +321,7 @@ class TestPaginationCycleDetection:
cursor: str | None = None,
) -> mcp_types.ListResourcesResult:
result = await original(cursor=cursor)
result.nextCursor = "stuck"
result.next_cursor = "stuck"
return result
with patch.object(
@ -348,7 +348,7 @@ class TestPaginationCycleDetection:
cursor: str | None = None,
) -> mcp_types.ListResourceTemplatesResult:
result = await original(cursor=cursor)
result.nextCursor = "stuck"
result.next_cursor = "stuck"
return result
with patch.object(
@ -380,7 +380,7 @@ class TestPaginationCycleDetection:
result = await original(cursor=cursor)
# Cycle through A -> B -> C -> A
cursors = ["A", "B", "C"]
result.nextCursor = cursors[call_count % 3]
result.next_cursor = cursors[call_count % 3]
call_count += 1
return result
@ -409,7 +409,7 @@ class TestPaginationCycleDetection:
cursor: str | None = None,
) -> mcp_types.ListToolsResult:
result = await original(cursor=cursor)
result.nextCursor = ""
result.next_cursor = ""
return result
with patch.object(
@ -439,7 +439,7 @@ class TestPaginationCycleDetection:
nonlocal call_count
result = await original(cursor=cursor)
call_count += 1
result.nextCursor = f"cursor-{call_count}"
result.next_cursor = f"cursor-{call_count}"
return result
with (
@ -469,7 +469,7 @@ class TestPaginationCycleDetection:
nonlocal call_count
result = await original(cursor=cursor)
call_count += 1
result.nextCursor = f"cursor-{call_count}"
result.next_cursor = f"cursor-{call_count}"
return result
with (
@ -499,7 +499,7 @@ class TestPaginationCycleDetection:
nonlocal call_count
result = await original(cursor=cursor)
call_count += 1
result.nextCursor = f"cursor-{call_count}"
result.next_cursor = f"cursor-{call_count}"
return result
with (

View file

@ -365,7 +365,7 @@ class TestMeta:
async with Client(mcp) as client:
templates = await client.list_resource_templates()
template = next(
t for t in templates if t.uriTemplate == "test://template/{id}"
t for t in templates if t.uri_template == "test://template/{id}"
)
assert template.meta is not None
assert set(template.meta["fastmcp"]["tags"]) == {

View file

@ -28,8 +28,8 @@ async def test_tool_annotations_in_tool_manager():
assert len(tools) == 1
assert tools[0].annotations is not None
assert tools[0].annotations.title == "Echo Tool"
assert tools[0].annotations.readOnlyHint is True
assert tools[0].annotations.openWorldHint is False
assert tools[0].annotations.read_only_hint is True
assert tools[0].annotations.open_world_hint is False
async def test_tool_annotations_in_mcp_protocol():
@ -52,8 +52,8 @@ async def test_tool_annotations_in_mcp_protocol():
assert len(result.tools) == 1
assert result.tools[0].annotations is not None
assert result.tools[0].annotations.title == "Echo Tool"
assert result.tools[0].annotations.readOnlyHint is True
assert result.tools[0].annotations.openWorldHint is False
assert result.tools[0].annotations.read_only_hint is True
assert result.tools[0].annotations.open_world_hint is False
async def test_tool_annotations_in_client_api():
@ -78,8 +78,8 @@ async def test_tool_annotations_in_client_api():
assert tools_result[0].name == "echo"
assert tools_result[0].annotations is not None
assert tools_result[0].annotations.title == "Echo Tool"
assert tools_result[0].annotations.readOnlyHint is True
assert tools_result[0].annotations.openWorldHint is False
assert tools_result[0].annotations.read_only_hint is True
assert tools_result[0].annotations.open_world_hint is False
async def test_provide_tool_annotations_as_dict_to_decorator():
@ -104,8 +104,8 @@ async def test_provide_tool_annotations_as_dict_to_decorator():
assert tools_result[0].name == "echo"
assert tools_result[0].annotations is not None
assert tools_result[0].annotations.title == "Echo Tool"
assert tools_result[0].annotations.readOnlyHint is True
assert tools_result[0].annotations.openWorldHint is False
assert tools_result[0].annotations.read_only_hint is True
assert tools_result[0].annotations.open_world_hint is False
async def test_direct_tool_annotations_in_tool_manager():
@ -130,10 +130,10 @@ async def test_direct_tool_annotations_in_tool_manager():
assert len(tools) == 1
assert tools[0].annotations is not None
assert tools[0].annotations.title == "Direct Tool"
assert tools[0].annotations.readOnlyHint is False
assert tools[0].annotations.destructiveHint is True
assert tools[0].annotations.idempotentHint is False
assert tools[0].annotations.openWorldHint is True
assert tools[0].annotations.read_only_hint is False
assert tools[0].annotations.destructive_hint is True
assert tools[0].annotations.idempotent_hint is False
assert tools[0].annotations.open_world_hint is True
async def test_direct_tool_annotations_in_client_api():
@ -160,8 +160,8 @@ async def test_direct_tool_annotations_in_client_api():
assert tools_result[0].name == "modify"
assert tools_result[0].annotations is not None
assert tools_result[0].annotations.title == "Direct Tool"
assert tools_result[0].annotations.readOnlyHint is False
assert tools_result[0].annotations.destructiveHint is True
assert tools_result[0].annotations.read_only_hint is False
assert tools_result[0].annotations.destructive_hint is True
async def test_add_tool_method_annotations():
@ -189,8 +189,8 @@ async def test_add_tool_method_annotations():
assert len(tools) == 1
assert tools[0].annotations is not None
assert tools[0].annotations.title == "Create Item"
assert tools[0].annotations.readOnlyHint is False
assert tools[0].annotations.destructiveHint is False
assert tools[0].annotations.read_only_hint is False
assert tools[0].annotations.destructive_hint is False
async def test_tool_functionality_with_annotations():
@ -221,7 +221,7 @@ async def test_tool_functionality_with_annotations():
async def test_task_execution_auto_populated_for_task_enabled_tool():
"""Test that execution.taskSupport is automatically set when tool has task=True."""
"""Test that execution.task_support is automatically set when tool has task=True."""
mcp = FastMCP("Test Server")
@mcp.tool(task=True)
@ -235,7 +235,7 @@ async def test_task_execution_auto_populated_for_task_enabled_tool():
assert tools_result[0].name == "background_tool"
assert isinstance(tools_result[0], MCPTool)
assert isinstance(tools_result[0].execution, ToolExecution)
assert tools_result[0].execution.taskSupport == "optional"
assert tools_result[0].execution.task_support == "optional"
async def test_task_execution_omitted_for_task_disabled_tool():

View file

@ -277,9 +277,9 @@ async def test_openapi_path_params_not_duplicated_in_description():
assert tool.description == "My endpoint"
# Hidden param gone from schema, visible param still present
assert "version" not in tool.inputSchema.get("properties", {})
assert "user_id" in tool.inputSchema["properties"]
assert "version" not in tool.input_schema.get("properties", {})
assert "user_id" in tool.input_schema["properties"]
assert (
tool.inputSchema["properties"]["user_id"]["description"]
tool.input_schema["properties"]["user_id"]["description"]
== "The user ID"
)

View file

@ -241,7 +241,7 @@ class TestResourcesAsToolsAnnotations:
tools = await client.list_tools()
tool = next(t for t in tools if t.name == "list_resources")
assert tool.annotations is not None
assert tool.annotations.readOnlyHint is True
assert tool.annotations.read_only_hint is True
async def test_read_resource_is_read_only(self):
"""read_resource is annotated as read-only by default."""
@ -252,7 +252,7 @@ class TestResourcesAsToolsAnnotations:
tools = await client.list_tools()
tool = next(t for t in tools if t.name == "read_resource")
assert tool.annotations is not None
assert tool.annotations.readOnlyHint is True
assert tool.annotations.read_only_hint is True
def _deny_all(ctx: AuthContext) -> bool:

View file

@ -465,7 +465,7 @@ class TestIntegration:
resources = await client.list_resources()
assert len(resources) == 1
assert str(resources[0].uri) == "ui://my-app/view.html"
assert resources[0].mimeType == UI_MIME_TYPE
assert resources[0].mime_type == UI_MIME_TYPE
async def test_ui_resource_read_preserves_mime_type(self):
"""Reading a ui:// resource returns content with the correct MIME type."""
@ -478,7 +478,7 @@ class TestIntegration:
async with Client(server) as client:
result = await client.read_resource_mcp("ui://my-app/view.html")
assert len(result.contents) == 1
assert result.contents[0].mimeType == UI_MIME_TYPE
assert result.contents[0].mime_type == UI_MIME_TYPE
async def test_app_tool_callable(self):
"""A tool registered with app= is still callable normally."""

View file

@ -882,7 +882,7 @@ class TestAppIntegration:
# Call the UI tool through the client and check structured_content
result = await client.call_tool_mcp("crm_contact_form", {})
sc = result.structuredContent
sc = result.structured_content
assert sc is not None
# Call the backend tool via its hashed address — bypasses namespace

View file

@ -107,7 +107,7 @@ class TestFutureAnnotations:
async with Client(fastmcp_server) as client:
result = await client.call_tool("returns_image", {})
assert result.content[0].type == "image"
assert result.content[0].mimeType == "image/png"
assert result.content[0].mime_type == "image/png"
async def test_async_with_context(self):
async with Client(fastmcp_server) as client:

View file

@ -83,10 +83,10 @@ class TestToolResultIsError:
)
mcp_result = result.to_mcp_result()
assert isinstance(mcp_result, CallToolResult)
assert mcp_result.isError is True
assert mcp_result.is_error is True
assert isinstance(mcp_result.content[0], TextContent)
assert mcp_result.content[0].text == "boom"
assert mcp_result.structuredContent == {"code": 42}
assert mcp_result.structured_content == {"code": 42}
def test_default_is_not_error(self):
result = ToolResult(content="ok")
@ -269,7 +269,7 @@ class TestSerializeByAlias:
"id": "123",
"filepath": "/p",
}
assert set(tools["get_biofile"].outputSchema["properties"]) == { # type: ignore[index]
assert set(tools["get_biofile"].output_schema["properties"]) == { # type: ignore[index]
"id",
"filepath",
}
@ -292,7 +292,7 @@ class TestSerializeByAlias:
result = await client.call_tool("get_biofile", {})
assert result.structured_content == {"_id": "123", "filepath": "/p"}
assert set(tools["get_biofile"].outputSchema["properties"]) == { # type: ignore[index]
assert set(tools["get_biofile"].output_schema["properties"]) == { # type: ignore[index]
"_id",
"filepath",
}
@ -315,7 +315,7 @@ class TestSerializeByAlias:
result = await client.call_tool("get_biofile", {})
assert result.structured_content == {"_id": "123"}
assert set(tools["get_biofile"].outputSchema["properties"]) == {"_id"} # type: ignore[index]
assert set(tools["get_biofile"].output_schema["properties"]) == {"_id"} # type: ignore[index]
async def test_nested_models_respect_config(self):
"""serialize_by_alias=False propagates through nested models."""
@ -364,7 +364,7 @@ class TestSerializeByAlias:
# raises if they disagree
result = await client.call_tool("get_biofile", {})
schema_props = set(tools["get_biofile"].outputSchema["properties"]) # type: ignore[index]
schema_props = set(tools["get_biofile"].output_schema["properties"]) # type: ignore[index]
assert schema_props == set(result.structured_content) # type: ignore[arg-type]
assert result.structured_content == {"result": {"id": "1"}}
@ -391,5 +391,5 @@ class TestSerializeByAlias:
tools = {t.name: t for t in await client.list_tools()}
result = await client.call_tool("get_model", {})
schema_props = set(tools["get_model"].outputSchema["properties"]) # type: ignore[index]
schema_props = set(tools["get_model"].output_schema["properties"]) # type: ignore[index]
assert schema_props == set(result.structured_content) # type: ignore[arg-type]

View file

@ -547,7 +547,7 @@ class TestToolExecutionField:
mcp_tool = tool.to_mcp_tool()
assert mcp_tool.execution is not None
assert mcp_tool.execution.taskSupport == "optional"
assert mcp_tool.execution.task_support == "optional"
def test_tool_without_execution_field(self):
"""Test that Tool without execution returns None."""
@ -572,7 +572,7 @@ class TestToolExecutionField:
override_execution = ToolExecution(taskSupport="required")
mcp_tool = tool.to_mcp_tool(execution=override_execution)
assert mcp_tool.execution is not None
assert mcp_tool.execution.taskSupport == "required"
assert mcp_tool.execution.task_support == "required"
async def test_function_tool_task_config_still_works(self):
"""FunctionTool should still derive execution from task_config."""
@ -585,7 +585,7 @@ class TestToolExecutionField:
# FunctionTool sets execution from task_config
assert mcp_tool.execution is not None
assert mcp_tool.execution.taskSupport == "optional"
assert mcp_tool.execution.task_support == "optional"
def test_tool_execution_required_mode(self):
"""Test that Tool can store required execution mode."""
@ -598,7 +598,7 @@ class TestToolExecutionField:
mcp_tool = tool.to_mcp_tool()
assert mcp_tool.execution is not None
assert mcp_tool.execution.taskSupport == "required"
assert mcp_tool.execution.task_support == "required"
def test_tool_execution_forbidden_mode(self):
"""Test that Tool can store forbidden execution mode."""
@ -611,4 +611,4 @@ class TestToolExecutionField:
mcp_tool = tool.to_mcp_tool()
assert mcp_tool.execution is not None
assert mcp_tool.execution.taskSupport == "forbidden"
assert mcp_tool.execution.task_support == "forbidden"

View file

@ -458,7 +458,7 @@ class TestNullableInputSchemaIntegration:
"""Test that nullable fields are converted in tool input schemas end-to-end.
These tests exercise the full pipeline: OpenAPI spec -> OpenAPIProvider ->
tool.inputSchema, verifying that `nullable: true` doesn't leak through.
tool.input_schema, verifying that `nullable: true` doesn't leak through.
"""
async def test_nullable_query_param_converted_in_tool_input_schema(self):
@ -496,7 +496,7 @@ class TestNullableInputSchemaIntegration:
async with Client(mcp) as mcp_client:
tools = await mcp_client.list_tools()
assert len(tools) == 1
schema = tools[0].inputSchema
schema = tools[0].input_schema
category_prop = schema["properties"]["category"]
assert "nullable" not in category_prop
assert category_prop["type"] == ["string", "null"]
@ -545,7 +545,7 @@ class TestNullableInputSchemaIntegration:
async with Client(mcp) as mcp_client:
tools = await mcp_client.list_tools()
assert len(tools) == 1
schema = tools[0].inputSchema
schema = tools[0].input_schema
# Find the bio property — it may be inline or in $defs
if "$defs" in schema:

View file

@ -204,7 +204,7 @@ class TestImage:
content = img.to_image_content()
assert content.type == "image"
assert content.mimeType == "image/png"
assert content.mime_type == "image/png"
assert content.data == base64.b64encode(test_data).decode()
# Test with data
@ -212,7 +212,7 @@ class TestImage:
content = img.to_image_content()
assert content.type == "image"
assert content.mimeType == "image/jpeg"
assert content.mime_type == "image/jpeg"
assert content.data == base64.b64encode(test_data).decode()
def test_to_image_content_error(self, monkeypatch):
@ -327,7 +327,7 @@ class TestAudio:
content = audio.to_audio_content()
assert content.type == "audio"
assert content.mimeType == "audio/wav"
assert content.mime_type == "audio/wav"
assert content.data == base64.b64encode(test_data).decode()
# Test with data
@ -335,7 +335,7 @@ class TestAudio:
content = audio.to_audio_content()
assert content.type == "audio"
assert content.mimeType == "audio/mp3"
assert content.mime_type == "audio/mp3"
assert content.data == base64.b64encode(test_data).decode()
def test_to_audio_content_error(self, monkeypatch):
@ -359,7 +359,7 @@ class TestAudio:
content = audio.to_audio_content(mime_type="audio/custom")
assert content.type == "audio"
assert content.mimeType == "audio/custom"
assert content.mime_type == "audio/custom"
assert content.data == base64.b64encode(test_data).decode()
@ -442,7 +442,7 @@ class TestFile:
resource = file.to_resource_content()
assert resource.type == "resource"
assert resource.resource.mimeType == "text/plain"
assert resource.resource.mime_type == "text/plain"
# Convert both to strings for comparison
assert str(resource.resource.uri) == file_path.resolve().as_uri()
if isinstance(resource.resource, BlobResourceContents):
@ -455,7 +455,7 @@ class TestFile:
resource = file.to_resource_content()
assert resource.type == "resource"
assert resource.resource.mimeType == "application/pdf"
assert resource.resource.mime_type == "application/pdf"
# Convert URI to string for comparison
assert str(resource.resource.uri) == "file:///resource.pdf"
if isinstance(resource.resource, BlobResourceContents):
@ -469,7 +469,7 @@ class TestFile:
assert resource.type == "resource"
# Should be TextResourceContents for text/plain
assert isinstance(resource.resource, TextResourceContents)
assert resource.resource.mimeType == "text/plain"
assert resource.resource.mime_type == "text/plain"
assert resource.resource.text == "hello world"
def test_to_resource_content_error(self, monkeypatch):
@ -490,7 +490,7 @@ class TestFile:
file = File(path=file_path)
resource = file.to_resource_content(mime_type="application/custom")
assert resource.resource.mimeType == "application/custom"
assert resource.resource.mime_type == "application/custom"
class TestReplaceType: