mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-23 05:54:19 +02:00
Adopt snake_case SDK type constructor kwargs
This commit is contained in:
parent
ed85d477f5
commit
2ea012ead4
50 changed files with 283 additions and 281 deletions
|
|
@ -791,7 +791,7 @@ _LOG_PANEL_HTML = """\
|
|||
|
||||
function renderEntry(entry) {
|
||||
var div = document.createElement("div");
|
||||
var isError = entry.direction === "response" && entry.body
|
||||
var is_error = entry.direction === "response" && entry.body
|
||||
&& (entry.body.error || (entry.body.result && entry.body.result.is_error));
|
||||
div.className = "log-entry" + (isError ? " error" : "");
|
||||
var dirClass = isError ? "error" : entry.direction;
|
||||
|
|
|
|||
|
|
@ -816,7 +816,7 @@ class Client(
|
|||
root=mcp_types.CancelledNotification(
|
||||
method="notifications/cancelled",
|
||||
params=mcp_types.CancelledNotificationParams(
|
||||
requestId=request_id,
|
||||
request_id=request_id,
|
||||
reason=reason,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class ClientTaskManagementMixin:
|
|||
RuntimeError: If client not connected
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
request = GetTaskRequest(params=GetTaskRequestParams(taskId=task_id))
|
||||
request = GetTaskRequest(params=GetTaskRequestParams(task_id=task_id))
|
||||
return await self._await_with_session_monitoring(
|
||||
self.session.send_request(
|
||||
request=request, # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
|
||||
|
|
@ -70,7 +70,7 @@ class ClientTaskManagementMixin:
|
|||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
request = GetTaskPayloadRequest(
|
||||
params=GetTaskPayloadRequestParams(taskId=task_id)
|
||||
params=GetTaskPayloadRequestParams(task_id=task_id)
|
||||
)
|
||||
# Return raw result - Task classes handle type-specific parsing
|
||||
result = await self._await_with_session_monitoring(
|
||||
|
|
@ -148,7 +148,7 @@ class ClientTaskManagementMixin:
|
|||
RuntimeError: If task doesn't exist
|
||||
MCPError: If the request results in a TimeoutError | JSONRPCError
|
||||
"""
|
||||
request = CancelTaskRequest(params=CancelTaskRequestParams(taskId=task_id))
|
||||
request = CancelTaskRequest(params=CancelTaskRequestParams(task_id=task_id))
|
||||
return await self._await_with_session_monitoring(
|
||||
self.session.send_request(
|
||||
request=request, # type: ignore[invalid-argument-type] # ty:ignore[invalid-argument-type]
|
||||
|
|
|
|||
|
|
@ -449,5 +449,5 @@ class AnthropicSamplingHandler:
|
|||
content=content,
|
||||
role="assistant",
|
||||
model=message.model,
|
||||
stopReason=stop_reason,
|
||||
stop_reason=stop_reason,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -399,5 +399,5 @@ def _response_to_result_with_tools(
|
|||
content=content,
|
||||
role="assistant",
|
||||
model=model,
|
||||
stopReason=stop_reason,
|
||||
stop_reason=stop_reason,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -509,5 +509,5 @@ class OpenAISamplingHandler:
|
|||
content=content, # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
|
||||
role="assistant",
|
||||
model=chat_completion.model,
|
||||
stopReason=stop_reason,
|
||||
stop_reason=stop_reason,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -181,12 +181,12 @@ class Task(abc.ABC, Generic[TaskResultT]):
|
|||
# Return synthetic completed status
|
||||
now = datetime.now(timezone.utc)
|
||||
return GetTaskResult(
|
||||
taskId=self._task_id,
|
||||
task_id=self._task_id,
|
||||
status="completed",
|
||||
createdAt=now,
|
||||
lastUpdatedAt=now,
|
||||
created_at=now,
|
||||
last_updated_at=now,
|
||||
ttl=None,
|
||||
pollInterval=1000,
|
||||
poll_interval=1000,
|
||||
)
|
||||
|
||||
# Return cached status if available (from notification)
|
||||
|
|
@ -409,7 +409,7 @@ class ToolTask(Task["CallToolResult"]):
|
|||
):
|
||||
mcp_result = mcp_types.CallToolResult(
|
||||
content=raw_result.content,
|
||||
structuredContent=raw_result.structured_content,
|
||||
structured_content=raw_result.structured_content,
|
||||
_meta=raw_result.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
)
|
||||
result = await self._client._parse_call_tool_result(
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class CallToolRequestResult(CallToolResult):
|
|||
return cls(
|
||||
tool=tool,
|
||||
arguments=arguments,
|
||||
isError=result.is_error,
|
||||
is_error=result.is_error,
|
||||
content=result.content,
|
||||
)
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ class BulkToolCaller(MCPMixin):
|
|||
return CallToolRequestResult(
|
||||
tool=tool,
|
||||
arguments=arguments,
|
||||
isError=True,
|
||||
is_error=True,
|
||||
content=[
|
||||
TextContent(
|
||||
type="text",
|
||||
|
|
@ -146,6 +146,6 @@ class BulkToolCaller(MCPMixin):
|
|||
return CallToolRequestResult(
|
||||
tool=tool,
|
||||
arguments=arguments,
|
||||
isError=result.is_error,
|
||||
is_error=result.is_error,
|
||||
content=result.content,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -106,14 +106,14 @@ class ResourceContent(pydantic.BaseModel):
|
|||
return mcp_types.TextResourceContents(
|
||||
uri=AnyUrl(uri) if isinstance(uri, str) else uri,
|
||||
text=self.content,
|
||||
mimeType=self.mime_type or "text/plain",
|
||||
mime_type=self.mime_type or "text/plain",
|
||||
_meta=self.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
)
|
||||
else:
|
||||
return mcp_types.BlobResourceContents(
|
||||
uri=AnyUrl(uri) if isinstance(uri, str) else uri,
|
||||
blob=base64.b64encode(self.content).decode(),
|
||||
mimeType=self.mime_type or "application/octet-stream",
|
||||
mime_type=self.mime_type or "application/octet-stream",
|
||||
_meta=self.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
)
|
||||
|
||||
|
|
@ -412,7 +412,7 @@ class Resource(FastMCPComponent):
|
|||
name=overrides.get("name", self.name),
|
||||
uri=overrides.get("uri", self.uri),
|
||||
description=overrides.get("description", self.description),
|
||||
mimeType=overrides.get("mimeType", self.mime_type),
|
||||
mime_type=overrides.get("mimeType", self.mime_type),
|
||||
title=overrides.get("title", self.title),
|
||||
icons=overrides.get("icons", self.icons),
|
||||
annotations=overrides.get("annotations", self.annotations),
|
||||
|
|
|
|||
|
|
@ -323,9 +323,9 @@ class ResourceTemplate(FastMCPComponent):
|
|||
|
||||
return SDKResourceTemplate(
|
||||
name=overrides.get("name", self.name),
|
||||
uriTemplate=overrides.get("uriTemplate", self.uri_template),
|
||||
uri_template=overrides.get("uriTemplate", self.uri_template),
|
||||
description=overrides.get("description", self.description),
|
||||
mimeType=overrides.get("mimeType", self.mime_type),
|
||||
mime_type=overrides.get("mimeType", self.mime_type),
|
||||
title=overrides.get("title", self.title),
|
||||
icons=overrides.get("icons", self.icons),
|
||||
annotations=overrides.get("annotations", self.annotations),
|
||||
|
|
|
|||
|
|
@ -1189,7 +1189,7 @@ class Context:
|
|||
# Standard request mode: use session.elicit directly
|
||||
result = await self.session.elicit(
|
||||
message=message,
|
||||
requestedSchema=config.schema,
|
||||
requested_schema=config.schema,
|
||||
related_request_id=self.request_id,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ class MCPOperationsMixin:
|
|||
request.params.cursor if request is not None and request.params else None
|
||||
)
|
||||
page, next_cursor = _apply_pagination(sdk_tools, cursor, server._list_page_size)
|
||||
return mcp_types.ListToolsResult(tools=page, nextCursor=next_cursor)
|
||||
return mcp_types.ListToolsResult(tools=page, next_cursor=next_cursor)
|
||||
|
||||
async def _list_resources_mcp(
|
||||
self, request: mcp_types.ListResourcesRequest
|
||||
|
|
@ -138,7 +138,7 @@ class MCPOperationsMixin:
|
|||
page, next_cursor = _apply_pagination(
|
||||
sdk_resources, cursor, server._list_page_size
|
||||
)
|
||||
return mcp_types.ListResourcesResult(resources=page, nextCursor=next_cursor)
|
||||
return mcp_types.ListResourcesResult(resources=page, next_cursor=next_cursor)
|
||||
|
||||
async def _list_resource_templates_mcp(
|
||||
self, request: mcp_types.ListResourceTemplatesRequest
|
||||
|
|
@ -154,7 +154,7 @@ class MCPOperationsMixin:
|
|||
list(await server.list_resource_templates()), lambda t: t.uri_template
|
||||
)
|
||||
sdk_templates = [
|
||||
template.to_mcp_template(uriTemplate=template.uri_template)
|
||||
template.to_mcp_template(uri_template=template.uri_template)
|
||||
for template in templates
|
||||
]
|
||||
cursor = request.params.cursor if request.params else None
|
||||
|
|
@ -162,7 +162,7 @@ class MCPOperationsMixin:
|
|||
sdk_templates, cursor, server._list_page_size
|
||||
)
|
||||
return mcp_types.ListResourceTemplatesResult(
|
||||
resourceTemplates=page, nextCursor=next_cursor
|
||||
resource_templates=page, next_cursor=next_cursor
|
||||
)
|
||||
|
||||
async def _list_prompts_mcp(
|
||||
|
|
@ -183,7 +183,7 @@ class MCPOperationsMixin:
|
|||
page, next_cursor = _apply_pagination(
|
||||
sdk_prompts, cursor, server._list_page_size
|
||||
)
|
||||
return mcp_types.ListPromptsResult(prompts=page, nextCursor=next_cursor)
|
||||
return mcp_types.ListPromptsResult(prompts=page, next_cursor=next_cursor)
|
||||
|
||||
async def _call_tool_mcp(
|
||||
self, key: str, arguments: dict[str, Any]
|
||||
|
|
|
|||
|
|
@ -979,7 +979,7 @@ async def default_proxy_elicitation_handler(
|
|||
)
|
||||
result = await ctx.session.elicit(
|
||||
message=message,
|
||||
requestedSchema=requested_schema,
|
||||
requested_schema=requested_schema,
|
||||
related_request_id=ctx.request_id,
|
||||
)
|
||||
return ElicitResult(action=result.action, content=result.content)
|
||||
|
|
|
|||
|
|
@ -222,13 +222,13 @@ async def call_sampling_handler(
|
|||
result = context.fastmcp.sampling_handler(
|
||||
messages,
|
||||
SamplingParams(
|
||||
systemPrompt=system_prompt,
|
||||
system_prompt=system_prompt,
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
maxTokens=max_tokens,
|
||||
modelPreferences=_parse_model_preferences(model_preferences),
|
||||
max_tokens=max_tokens,
|
||||
model_preferences=_parse_model_preferences(model_preferences),
|
||||
tools=sdk_tools,
|
||||
toolChoice=tool_choice,
|
||||
tool_choice=tool_choice,
|
||||
),
|
||||
context.request_context,
|
||||
)
|
||||
|
|
@ -244,7 +244,7 @@ async def call_sampling_handler(
|
|||
role="assistant",
|
||||
content=TextContent(type="text", text=result),
|
||||
model="unknown",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
return result
|
||||
|
|
@ -287,14 +287,14 @@ async def execute_tools(
|
|||
if tool is None:
|
||||
return ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId=tool_use.id,
|
||||
tool_use_id=tool_use.id,
|
||||
content=[
|
||||
TextContent(
|
||||
type="text",
|
||||
text=f"Error: Unknown tool '{tool_use.name}'",
|
||||
)
|
||||
],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
)
|
||||
|
||||
tracer = get_tracer()
|
||||
|
|
@ -309,7 +309,7 @@ async def execute_tools(
|
|||
result_value = await tool.run(tool_use.input)
|
||||
return ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId=tool_use.id,
|
||||
tool_use_id=tool_use.id,
|
||||
content=[TextContent(type="text", text=str(result_value))],
|
||||
)
|
||||
except ToolError as e:
|
||||
|
|
@ -324,9 +324,9 @@ async def execute_tools(
|
|||
)
|
||||
return ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId=tool_use.id,
|
||||
tool_use_id=tool_use.id,
|
||||
content=[TextContent(type="text", text=str(e))],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
)
|
||||
except Exception as e:
|
||||
if span.is_recording():
|
||||
|
|
@ -340,9 +340,9 @@ async def execute_tools(
|
|||
error_text = f"Error executing tool '{tool_use.name}': {e}"
|
||||
return ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId=tool_use.id,
|
||||
tool_use_id=tool_use.id,
|
||||
content=[TextContent(type="text", text=error_text)],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
)
|
||||
|
||||
# Check if any tool requires sequential execution
|
||||
|
|
@ -718,7 +718,7 @@ async def sample_impl(
|
|||
content=[
|
||||
ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId=tool_call.id,
|
||||
tool_use_id=tool_call.id,
|
||||
content=[
|
||||
TextContent(
|
||||
type="text",
|
||||
|
|
@ -728,7 +728,7 @@ async def sample_impl(
|
|||
),
|
||||
)
|
||||
],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class SamplingTool(FastMCPBaseModel):
|
|||
return SDKTool(
|
||||
name=self.name,
|
||||
description=self.description,
|
||||
inputSchema=self.parameters,
|
||||
input_schema=self.parameters,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ async def relay_elicitation(
|
|||
try:
|
||||
result = await session.elicit(
|
||||
message=elicitation["message"],
|
||||
requestedSchema=elicitation["requestedSchema"],
|
||||
requested_schema=elicitation["requestedSchema"],
|
||||
)
|
||||
await handle_task_input(
|
||||
task_id=task_id,
|
||||
|
|
|
|||
|
|
@ -226,11 +226,11 @@ async def submit_to_docket(
|
|||
# Tasks MUST begin in "working" status per SEP-1686 final spec (line 381)
|
||||
return mcp_types.CreateTaskResult(
|
||||
task=mcp_types.Task(
|
||||
taskId=server_task_id,
|
||||
task_id=server_task_id,
|
||||
status="working",
|
||||
createdAt=created_at,
|
||||
lastUpdatedAt=created_at,
|
||||
created_at=created_at,
|
||||
last_updated_at=created_at,
|
||||
ttl=ttl_ms,
|
||||
pollInterval=poll_interval_ms,
|
||||
poll_interval=poll_interval_ms,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -198,13 +198,13 @@ async def tasks_get_handler(server: FastMCP, params: dict[str, Any]) -> GetTaskR
|
|||
created_at_dt = datetime.now(timezone.utc)
|
||||
|
||||
return GetTaskResult(
|
||||
taskId=client_task_id,
|
||||
task_id=client_task_id,
|
||||
status=mcp_state,
|
||||
createdAt=created_at_dt,
|
||||
lastUpdatedAt=datetime.now(timezone.utc),
|
||||
created_at=created_at_dt,
|
||||
last_updated_at=datetime.now(timezone.utc),
|
||||
ttl=DEFAULT_TTL_MS,
|
||||
pollInterval=poll_interval_ms,
|
||||
statusMessage=status_message,
|
||||
poll_interval=poll_interval_ms,
|
||||
status_message=status_message,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -277,7 +277,7 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
|
|||
# Task failed - return error result
|
||||
return mcp_types.CallToolResult(
|
||||
content=[mcp_types.TextContent(type="text", text=str(error))],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
_meta={ # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
"io.modelcontextprotocol/related-task": {
|
||||
"taskId": client_task_id,
|
||||
|
|
@ -337,7 +337,7 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
|
|||
content, structured_content = mcp_result
|
||||
mcp_result = mcp_types.CallToolResult(
|
||||
content=content,
|
||||
structuredContent=structured_content,
|
||||
structured_content=structured_content,
|
||||
_meta=related_task_meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
)
|
||||
else:
|
||||
|
|
@ -390,7 +390,7 @@ async def tasks_list_handler(
|
|||
ListTasksResult: Response with tasks list and pagination
|
||||
"""
|
||||
# Return empty list - client tracks tasks locally
|
||||
return ListTasksResult(tasks=[], nextCursor=None)
|
||||
return ListTasksResult(tasks=[], next_cursor=None)
|
||||
|
||||
|
||||
async def tasks_cancel_handler(
|
||||
|
|
@ -438,13 +438,13 @@ async def tasks_cancel_handler(
|
|||
# createdAt is REQUIRED per SEP-1686 final spec (line 430)
|
||||
# Per spec lines 447-448: SHOULD NOT include related-task metadata in tasks/cancel
|
||||
return CancelTaskResult(
|
||||
taskId=client_task_id,
|
||||
task_id=client_task_id,
|
||||
status="cancelled",
|
||||
createdAt=datetime.fromisoformat(created_at)
|
||||
created_at=datetime.fromisoformat(created_at)
|
||||
if created_at
|
||||
else datetime.now(timezone.utc),
|
||||
lastUpdatedAt=datetime.now(timezone.utc),
|
||||
last_updated_at=datetime.now(timezone.utc),
|
||||
ttl=DEFAULT_TTL_MS,
|
||||
pollInterval=poll_interval_ms,
|
||||
statusMessage="Task cancelled",
|
||||
poll_interval=poll_interval_ms,
|
||||
status_message="Task cancelled",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ from fastmcp.server.transforms import GetToolNext, Transform
|
|||
from fastmcp.tools.base import Tool
|
||||
from fastmcp.utilities.versions import VersionSpec
|
||||
|
||||
_DEFAULT_ANNOTATIONS = ToolAnnotations(readOnlyHint=True)
|
||||
_DEFAULT_ANNOTATIONS = ToolAnnotations(read_only_hint=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fastmcp.server.providers.base import Provider
|
||||
|
|
|
|||
|
|
@ -167,9 +167,9 @@ class ToolResult(BaseModel):
|
|||
# reaches the client; the plain content/tuple returns can't carry it.
|
||||
if self.meta is not None or self.is_error:
|
||||
return CallToolResult(
|
||||
structuredContent=self.structured_content,
|
||||
structured_content=self.structured_content,
|
||||
content=self.content,
|
||||
isError=self.is_error,
|
||||
is_error=self.is_error,
|
||||
_meta=self.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
|
||||
)
|
||||
if self.structured_content is None:
|
||||
|
|
@ -235,8 +235,8 @@ class Tool(FastMCPComponent):
|
|||
name=overrides.get("name", self.name),
|
||||
title=overrides.get("title", title),
|
||||
description=overrides.get("description", self.description),
|
||||
inputSchema=overrides.get("inputSchema", self.parameters),
|
||||
outputSchema=overrides.get("outputSchema", self.output_schema),
|
||||
input_schema=overrides.get("inputSchema", self.parameters),
|
||||
output_schema=overrides.get("outputSchema", self.output_schema),
|
||||
icons=overrides.get("icons", self.icons),
|
||||
annotations=overrides.get("annotations", self.annotations),
|
||||
execution=overrides.get("execution", self.execution),
|
||||
|
|
@ -250,7 +250,7 @@ class Tool(FastMCPComponent):
|
|||
and "execution" not in overrides
|
||||
and not self.execution
|
||||
):
|
||||
mcp_tool.execution = ToolExecution(taskSupport=self.task_config.mode)
|
||||
mcp_tool.execution = ToolExecution(task_support=self.task_config.mode)
|
||||
|
||||
return mcp_tool
|
||||
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ class Image:
|
|||
return mcp_types.ImageContent(
|
||||
type="image",
|
||||
data=data,
|
||||
mimeType=mime_type or self._mime_type,
|
||||
mime_type=mime_type or self._mime_type,
|
||||
annotations=annotations or self.annotations,
|
||||
)
|
||||
|
||||
|
|
@ -360,7 +360,7 @@ class Audio:
|
|||
return mcp_types.AudioContent(
|
||||
type="audio",
|
||||
data=data,
|
||||
mimeType=mime_type or self._mime_type,
|
||||
mime_type=mime_type or self._mime_type,
|
||||
annotations=annotations or self.annotations,
|
||||
)
|
||||
|
||||
|
|
@ -433,14 +433,14 @@ class File:
|
|||
text = raw_data.decode("latin-1")
|
||||
resource = mcp_types.TextResourceContents(
|
||||
text=text,
|
||||
mimeType=mime,
|
||||
mime_type=mime,
|
||||
uri=uri,
|
||||
)
|
||||
else:
|
||||
data = base64.b64encode(raw_data).decode()
|
||||
resource = mcp_types.BlobResourceContents(
|
||||
blob=data,
|
||||
mimeType=mime,
|
||||
mime_type=mime,
|
||||
uri=uri,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -170,8 +170,8 @@ class TestFormatToolSignature:
|
|||
return mcp_types.Tool(
|
||||
name=name,
|
||||
description=description,
|
||||
inputSchema=input_schema,
|
||||
outputSchema=output_schema,
|
||||
input_schema=input_schema,
|
||||
output_schema=output_schema,
|
||||
)
|
||||
|
||||
def test_no_params(self):
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ class TestToolFunctionSource:
|
|||
def test_required_param(self):
|
||||
tool = mcp_types.Tool(
|
||||
name="greet",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {"name": {"type": "string", "description": "Who"}},
|
||||
"required": ["name"],
|
||||
},
|
||||
|
|
@ -164,7 +164,7 @@ class TestToolFunctionSource:
|
|||
def test_optional_param(self):
|
||||
tool = mcp_types.Tool(
|
||||
name="search",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Search query"},
|
||||
"limit": {"type": "integer", "description": "Max results"},
|
||||
|
|
@ -180,7 +180,7 @@ class TestToolFunctionSource:
|
|||
def test_param_with_default(self):
|
||||
tool = mcp_types.Tool(
|
||||
name="fetch",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {
|
||||
"url": {"type": "string", "description": "URL"},
|
||||
"timeout": {
|
||||
|
|
@ -199,7 +199,7 @@ class TestToolFunctionSource:
|
|||
def test_no_params(self):
|
||||
tool = mcp_types.Tool(
|
||||
name="ping",
|
||||
inputSchema={"properties": {}},
|
||||
input_schema={"properties": {}},
|
||||
)
|
||||
source = _tool_function_source(tool)
|
||||
assert "async def ping(" in source
|
||||
|
|
@ -208,7 +208,7 @@ class TestToolFunctionSource:
|
|||
def test_preserves_underscores(self):
|
||||
tool = mcp_types.Tool(
|
||||
name="get_forecast",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {"city": {"type": "string"}},
|
||||
"required": ["city"],
|
||||
},
|
||||
|
|
@ -219,7 +219,7 @@ class TestToolFunctionSource:
|
|||
def test_sanitizes_tool_name(self):
|
||||
tool = mcp_types.Tool(
|
||||
name="my.tool/v2",
|
||||
inputSchema={"properties": {}},
|
||||
input_schema={"properties": {}},
|
||||
)
|
||||
source = _tool_function_source(tool)
|
||||
assert "async def my_tool_v2(" in source
|
||||
|
|
@ -228,7 +228,7 @@ class TestToolFunctionSource:
|
|||
def test_sanitizes_param_name(self):
|
||||
tool = mcp_types.Tool(
|
||||
name="fetch",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {"content-type": {"type": "string", "description": "CT"}},
|
||||
"required": ["content-type"],
|
||||
},
|
||||
|
|
@ -241,7 +241,7 @@ class TestToolFunctionSource:
|
|||
tool = mcp_types.Tool(
|
||||
name="greet",
|
||||
description="Say hello to someone.",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {"name": {"type": "string"}},
|
||||
"required": ["name"],
|
||||
},
|
||||
|
|
@ -253,7 +253,7 @@ class TestToolFunctionSource:
|
|||
tool = mcp_types.Tool(
|
||||
name="fetch",
|
||||
description="Fetch data from 'source' API.",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {"url": {"type": "string"}},
|
||||
"required": ["url"],
|
||||
},
|
||||
|
|
@ -268,7 +268,7 @@ class TestToolFunctionSource:
|
|||
tool = mcp_types.Tool(
|
||||
name="tag_items",
|
||||
description="Tag multiple items.",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {
|
||||
"item_id": {"type": "string"},
|
||||
"tags": {"type": "array", "items": {"type": "string"}},
|
||||
|
|
@ -288,7 +288,7 @@ class TestToolFunctionSource:
|
|||
tool = mcp_types.Tool(
|
||||
name="create_user",
|
||||
description="Create a user.",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"metadata": {
|
||||
|
|
@ -321,7 +321,7 @@ class TestToolFunctionSource:
|
|||
tool = mcp_types.Tool(
|
||||
name="batch_process",
|
||||
description="Process batches.",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {
|
||||
"batches": {
|
||||
"type": "array",
|
||||
|
|
@ -348,7 +348,7 @@ class TestToolFunctionSource:
|
|||
"""Test that complex types with defaults are JSON-serialized."""
|
||||
tool = mcp_types.Tool(
|
||||
name="configure",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {
|
||||
"options": {
|
||||
"type": "object",
|
||||
|
|
@ -369,7 +369,7 @@ class TestToolFunctionSource:
|
|||
"""Test that parameter name collisions are detected."""
|
||||
tool = mcp_types.Tool(
|
||||
name="test",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {
|
||||
"content-type": {"type": "string"},
|
||||
"content_type": {"type": "string"},
|
||||
|
|
@ -414,7 +414,7 @@ class TestGenerateCliScript:
|
|||
mcp_types.Tool(
|
||||
name="greet",
|
||||
description="Say hello",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {
|
||||
"name": {"type": "string", "description": "Who to greet"},
|
||||
},
|
||||
|
|
@ -424,7 +424,7 @@ class TestGenerateCliScript:
|
|||
mcp_types.Tool(
|
||||
name="add_numbers",
|
||||
description="Add two numbers",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {
|
||||
"a": {"type": "integer", "description": "First number"},
|
||||
"b": {"type": "integer", "description": "Second number"},
|
||||
|
|
@ -510,7 +510,7 @@ class TestGenerateCliScript:
|
|||
mcp_types.Tool(
|
||||
name="my.tool/v2",
|
||||
description="A tool with dots and slashes",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"properties": {
|
||||
"content-type": {"type": "string", "description": "CT"},
|
||||
},
|
||||
|
|
@ -774,7 +774,7 @@ class TestGenerateSkillContent:
|
|||
mcp_types.Tool(
|
||||
name="greet",
|
||||
description="Say hello",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string", "description": "Who to greet"}
|
||||
|
|
@ -796,7 +796,7 @@ class TestGenerateSkillContent:
|
|||
tools = [
|
||||
mcp_types.Tool(
|
||||
name="greet",
|
||||
inputSchema={"type": "object", "properties": {}},
|
||||
input_schema={"type": "object", "properties": {}},
|
||||
),
|
||||
]
|
||||
content = generate_skill_content("weather", "cli.py", tools)
|
||||
|
|
@ -807,7 +807,7 @@ class TestGenerateSkillContent:
|
|||
mcp_types.Tool(
|
||||
name="search",
|
||||
description="Search things",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string"},
|
||||
|
|
@ -827,7 +827,7 @@ class TestGenerateSkillContent:
|
|||
mcp_types.Tool(
|
||||
name="create",
|
||||
description="Create item",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
|
|
@ -847,7 +847,7 @@ class TestGenerateSkillContent:
|
|||
mcp_types.Tool(
|
||||
name="ping",
|
||||
description="Ping the server",
|
||||
inputSchema={"type": "object", "properties": {}},
|
||||
input_schema={"type": "object", "properties": {}},
|
||||
),
|
||||
]
|
||||
content = generate_skill_content("test", "cli.py", tools)
|
||||
|
|
@ -866,7 +866,7 @@ class TestGenerateSkillContent:
|
|||
mcp_types.Tool(
|
||||
name="test",
|
||||
description="Test",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mode": {"type": "string", "description": "a|b|c"},
|
||||
|
|
@ -882,7 +882,7 @@ class TestGenerateSkillContent:
|
|||
mcp_types.Tool(
|
||||
name="test",
|
||||
description="Test",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"val": {"type": ["string", "null"]},
|
||||
|
|
@ -899,7 +899,7 @@ class TestGenerateSkillContent:
|
|||
mcp_types.Tool(
|
||||
name="run",
|
||||
description="Run something",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"verbose": {"type": "boolean", "description": "Verbose output"},
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ class TestParseToolResultEdgeCases:
|
|||
"""
|
||||
|
||||
async def test_error_with_empty_content_raises_with_fallback_message(self):
|
||||
result = mcp_types.CallToolResult(content=[], isError=True)
|
||||
result = mcp_types.CallToolResult(content=[], is_error=True)
|
||||
|
||||
with pytest.raises(ToolError, match="Tool 'my_tool' returned an error"):
|
||||
await _parse_call_tool_result(
|
||||
|
|
@ -221,9 +221,9 @@ class TestParseToolResultEdgeCases:
|
|||
async def test_error_with_non_text_content_raises_with_fallback_message(self):
|
||||
result = mcp_types.CallToolResult(
|
||||
content=[
|
||||
mcp_types.ImageContent(type="image", data="abc", mimeType="image/png")
|
||||
mcp_types.ImageContent(type="image", data="abc", mime_type="image/png")
|
||||
],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
)
|
||||
|
||||
with pytest.raises(ToolError, match="Tool 'my_tool' returned an error"):
|
||||
|
|
@ -238,7 +238,7 @@ class TestParseToolResultEdgeCases:
|
|||
async def test_error_with_text_content_raises_with_message(self):
|
||||
result = mcp_types.CallToolResult(
|
||||
content=[mcp_types.TextContent(type="text", text="custom error msg")],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
)
|
||||
|
||||
with pytest.raises(ToolError, match="custom error msg"):
|
||||
|
|
@ -253,8 +253,8 @@ class TestParseToolResultEdgeCases:
|
|||
async def test_error_with_structured_content_does_not_parse_data(self):
|
||||
result = mcp_types.CallToolResult(
|
||||
content=[mcp_types.TextContent(type="text", text="error happened")],
|
||||
isError=True,
|
||||
structuredContent={"key": "value"},
|
||||
is_error=True,
|
||||
structured_content={"key": "value"},
|
||||
)
|
||||
|
||||
parsed = await _parse_call_tool_result(
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ def test_convert_sampling_messages_to_anthropic_messages():
|
|||
|
||||
def test_image_content_to_anthropic_block():
|
||||
block = _image_content_to_anthropic_block(
|
||||
ImageContent(type="image", data="YWJj", mimeType="image/png")
|
||||
ImageContent(type="image", data="YWJj", mime_type="image/png")
|
||||
)
|
||||
|
||||
assert block == {
|
||||
|
|
@ -62,7 +62,7 @@ def test_image_content_to_anthropic_block():
|
|||
def test_image_content_unsupported_mime_type_raises():
|
||||
with pytest.raises(ValueError, match="Unsupported image MIME type"):
|
||||
_image_content_to_anthropic_block(
|
||||
ImageContent(type="image", data="YWJj", mimeType="image/bmp")
|
||||
ImageContent(type="image", data="YWJj", mime_type="image/bmp")
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ def test_convert_single_image_content_to_anthropic_message():
|
|||
messages=[
|
||||
SamplingMessage(
|
||||
role="user",
|
||||
content=ImageContent(type="image", data="YWJj", mimeType="image/png"),
|
||||
content=ImageContent(type="image", data="YWJj", mime_type="image/png"),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
|
@ -99,7 +99,7 @@ def test_convert_single_audio_content_raises():
|
|||
SamplingMessage(
|
||||
role="user",
|
||||
content=AudioContent(
|
||||
type="audio", data="YWJj", mimeType="audio/wav"
|
||||
type="audio", data="YWJj", mime_type="audio/wav"
|
||||
),
|
||||
)
|
||||
],
|
||||
|
|
@ -113,7 +113,7 @@ def test_convert_list_content_with_image_and_text():
|
|||
role="user",
|
||||
content=[
|
||||
TextContent(type="text", text="Describe this image"),
|
||||
ImageContent(type="image", data="YWJj", mimeType="image/jpeg"),
|
||||
ImageContent(type="image", data="YWJj", mime_type="image/jpeg"),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
|
@ -144,7 +144,7 @@ def test_convert_list_content_with_audio_raises():
|
|||
role="user",
|
||||
content=[
|
||||
TextContent(type="text", text="Listen to this"),
|
||||
AudioContent(type="audio", data="YWJj", mimeType="audio/wav"),
|
||||
AudioContent(type="audio", data="YWJj", mime_type="audio/wav"),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
|
@ -158,7 +158,7 @@ def test_convert_image_in_assistant_message_raises():
|
|||
SamplingMessage(
|
||||
role="assistant",
|
||||
content=ImageContent(
|
||||
type="image", data="YWJj", mimeType="image/png"
|
||||
type="image", data="YWJj", mime_type="image/png"
|
||||
),
|
||||
)
|
||||
],
|
||||
|
|
@ -173,7 +173,7 @@ def test_convert_list_image_in_assistant_message_raises():
|
|||
role="assistant",
|
||||
content=[
|
||||
TextContent(type="text", text="Here's the image"),
|
||||
ImageContent(type="image", data="YWJj", mimeType="image/png"),
|
||||
ImageContent(type="image", data="YWJj", mime_type="image/png"),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
|
@ -302,7 +302,7 @@ def test_convert_tools_to_anthropic():
|
|||
Tool(
|
||||
name="get_weather",
|
||||
description="Get the current weather",
|
||||
inputSchema={
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"properties": {"location": {"type": "string"}},
|
||||
"required": ["location"],
|
||||
|
|
@ -358,7 +358,7 @@ def test_convert_messages_with_tool_result_content():
|
|||
role="user",
|
||||
content=ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId="toolu_123",
|
||||
tool_use_id="toolu_123",
|
||||
content=[TextContent(type="text", text="72F and sunny")],
|
||||
),
|
||||
),
|
||||
|
|
@ -387,7 +387,7 @@ def test_convert_messages_raises_on_unsupported_content_type():
|
|||
embedded = EmbeddedResource(
|
||||
type="resource",
|
||||
resource=TextResourceContents(
|
||||
uri=AnyUrl("file:///test.txt"), text="hello", mimeType="text/plain"
|
||||
uri=AnyUrl("file:///test.txt"), text="hello", mime_type="text/plain"
|
||||
),
|
||||
)
|
||||
# Must be inside a list content — single-content messages hit a
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ def test_convert_sampling_messages_to_google_genai_content():
|
|||
|
||||
def test_convert_single_image_content_to_google_genai():
|
||||
part = _sampling_content_to_google_genai_part(
|
||||
ImageContent(type="image", data="YWJj", mimeType="image/png")
|
||||
ImageContent(type="image", data="YWJj", mime_type="image/png")
|
||||
)
|
||||
|
||||
assert part.inline_data is not None
|
||||
|
|
@ -76,7 +76,7 @@ def test_convert_single_image_content_to_google_genai():
|
|||
|
||||
def test_convert_single_audio_content_to_google_genai():
|
||||
part = _sampling_content_to_google_genai_part(
|
||||
AudioContent(type="audio", data="YWJj", mimeType="audio/wav")
|
||||
AudioContent(type="audio", data="YWJj", mime_type="audio/wav")
|
||||
)
|
||||
|
||||
assert part.inline_data is not None
|
||||
|
|
@ -89,7 +89,7 @@ def test_convert_image_message_to_google_genai_content():
|
|||
messages=[
|
||||
SamplingMessage(
|
||||
role="user",
|
||||
content=ImageContent(type="image", data="YWJj", mimeType="image/jpeg"),
|
||||
content=ImageContent(type="image", data="YWJj", mime_type="image/jpeg"),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
|
@ -105,7 +105,7 @@ def test_convert_audio_message_to_google_genai_content():
|
|||
messages=[
|
||||
SamplingMessage(
|
||||
role="user",
|
||||
content=AudioContent(type="audio", data="YWJj", mimeType="audio/mp3"),
|
||||
content=AudioContent(type="audio", data="YWJj", mime_type="audio/mp3"),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
|
@ -123,7 +123,7 @@ def test_convert_list_content_with_image_and_text():
|
|||
role="user",
|
||||
content=[
|
||||
TextContent(type="text", text="What is in this image?"),
|
||||
ImageContent(type="image", data="YWJj", mimeType="image/png"),
|
||||
ImageContent(type="image", data="YWJj", mime_type="image/png"),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
|
@ -144,7 +144,7 @@ def test_convert_list_content_with_audio_and_text():
|
|||
role="user",
|
||||
content=[
|
||||
TextContent(type="text", text="Transcribe this audio"),
|
||||
AudioContent(type="audio", data="YWJj", mimeType="audio/wav"),
|
||||
AudioContent(type="audio", data="YWJj", mime_type="audio/wav"),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
|
@ -243,7 +243,7 @@ def test_sampling_content_to_google_genai_part_tool_result():
|
|||
"""Test converting ToolResultContent to Google GenAI Part with FunctionResponse."""
|
||||
content = ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId="get_weather_abc123",
|
||||
tool_use_id="get_weather_abc123",
|
||||
content=[TextContent(type="text", text="Weather is sunny")],
|
||||
)
|
||||
|
||||
|
|
@ -259,7 +259,7 @@ def test_sampling_content_to_google_genai_part_tool_result_empty():
|
|||
"""Test converting empty ToolResultContent to Google GenAI Part."""
|
||||
content = ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId="my_tool_xyz789",
|
||||
tool_use_id="my_tool_xyz789",
|
||||
content=[],
|
||||
)
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ def test_sampling_content_to_google_genai_part_tool_result_no_underscore():
|
|||
"""Test ToolResultContent when toolUseId has no underscore (fallback)."""
|
||||
content = ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId="simplefunction",
|
||||
tool_use_id="simplefunction",
|
||||
content=[TextContent(type="text", text="Result")],
|
||||
)
|
||||
|
||||
|
|
@ -320,7 +320,7 @@ def test_convert_messages_with_tool_result():
|
|||
role="user",
|
||||
content=ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId="get_weather_123",
|
||||
tool_use_id="get_weather_123",
|
||||
content=[TextContent(type="text", text="Sunny, 72F")],
|
||||
),
|
||||
),
|
||||
|
|
@ -343,7 +343,7 @@ def test_convert_messages_with_multiple_content_blocks():
|
|||
TextContent(type="text", text="I need weather info."),
|
||||
ToolResultContent(
|
||||
type="tool_result",
|
||||
toolUseId="get_weather_xyz",
|
||||
tool_use_id="get_weather_xyz",
|
||||
content=[TextContent(type="text", text="Cloudy")],
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ def test_convert_sampling_messages_to_openai_messages():
|
|||
|
||||
def test_image_content_to_openai_part():
|
||||
part = _image_content_to_openai_part(
|
||||
ImageContent(type="image", data="YWJj", mimeType="image/png")
|
||||
ImageContent(type="image", data="YWJj", mime_type="image/png")
|
||||
)
|
||||
|
||||
assert part == ChatCompletionContentPartImageParam(
|
||||
|
|
@ -69,7 +69,7 @@ def test_image_content_to_openai_part():
|
|||
|
||||
def test_audio_content_to_openai_part_wav():
|
||||
part = _audio_content_to_openai_part(
|
||||
AudioContent(type="audio", data="YWJj", mimeType="audio/wav")
|
||||
AudioContent(type="audio", data="YWJj", mime_type="audio/wav")
|
||||
)
|
||||
|
||||
assert part == ChatCompletionContentPartInputAudioParam(
|
||||
|
|
@ -80,7 +80,7 @@ def test_audio_content_to_openai_part_wav():
|
|||
|
||||
def test_audio_content_to_openai_part_mp3():
|
||||
part = _audio_content_to_openai_part(
|
||||
AudioContent(type="audio", data="YWJj", mimeType="audio/mpeg")
|
||||
AudioContent(type="audio", data="YWJj", mime_type="audio/mpeg")
|
||||
)
|
||||
|
||||
assert part["input_audio"]["format"] == "mp3"
|
||||
|
|
@ -89,14 +89,14 @@ def test_audio_content_to_openai_part_mp3():
|
|||
def test_audio_content_to_openai_part_unsupported_raises():
|
||||
with pytest.raises(ValueError, match="Unsupported audio MIME type"):
|
||||
_audio_content_to_openai_part(
|
||||
AudioContent(type="audio", data="YWJj", mimeType="audio/ogg")
|
||||
AudioContent(type="audio", data="YWJj", mime_type="audio/ogg")
|
||||
)
|
||||
|
||||
|
||||
def test_image_content_to_openai_part_unsupported_raises():
|
||||
with pytest.raises(ValueError, match="Unsupported image MIME type"):
|
||||
_image_content_to_openai_part(
|
||||
ImageContent(type="image", data="YWJj", mimeType="image/bmp")
|
||||
ImageContent(type="image", data="YWJj", mime_type="image/bmp")
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ def test_convert_single_image_content_to_openai_message():
|
|||
messages=[
|
||||
SamplingMessage(
|
||||
role="user",
|
||||
content=ImageContent(type="image", data="YWJj", mimeType="image/png"),
|
||||
content=ImageContent(type="image", data="YWJj", mime_type="image/png"),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
|
@ -129,7 +129,7 @@ def test_convert_single_audio_content_to_openai_message():
|
|||
messages=[
|
||||
SamplingMessage(
|
||||
role="user",
|
||||
content=AudioContent(type="audio", data="YWJj", mimeType="audio/wav"),
|
||||
content=AudioContent(type="audio", data="YWJj", mime_type="audio/wav"),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
|
@ -154,7 +154,7 @@ def test_convert_list_content_with_image_and_text():
|
|||
role="user",
|
||||
content=[
|
||||
TextContent(type="text", text="What is in this image?"),
|
||||
ImageContent(type="image", data="YWJj", mimeType="image/jpeg"),
|
||||
ImageContent(type="image", data="YWJj", mime_type="image/jpeg"),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
|
@ -183,7 +183,7 @@ def test_convert_image_in_assistant_message_raises():
|
|||
SamplingMessage(
|
||||
role="assistant",
|
||||
content=ImageContent(
|
||||
type="image", data="YWJj", mimeType="image/png"
|
||||
type="image", data="YWJj", mime_type="image/png"
|
||||
),
|
||||
)
|
||||
],
|
||||
|
|
@ -198,7 +198,7 @@ def test_convert_audio_in_assistant_message_raises():
|
|||
SamplingMessage(
|
||||
role="assistant",
|
||||
content=AudioContent(
|
||||
type="audio", data="YWJj", mimeType="audio/wav"
|
||||
type="audio", data="YWJj", mime_type="audio/wav"
|
||||
),
|
||||
)
|
||||
],
|
||||
|
|
@ -215,7 +215,7 @@ def test_convert_list_image_in_assistant_message_raises():
|
|||
role="assistant",
|
||||
content=[
|
||||
TextContent(type="text", text="Here's the image"),
|
||||
ImageContent(type="image", data="YWJj", mimeType="image/png"),
|
||||
ImageContent(type="image", data="YWJj", mime_type="image/png"),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
|
@ -237,7 +237,7 @@ def test_convert_list_tool_calls_with_image_raises():
|
|||
name="my_tool",
|
||||
input={"arg": "val"},
|
||||
),
|
||||
ImageContent(type="image", data="YWJj", mimeType="image/png"),
|
||||
ImageContent(type="image", data="YWJj", mime_type="image/png"),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
|
@ -284,7 +284,7 @@ async def test_handler_passes_max_completion_tokens():
|
|||
messages = [
|
||||
SamplingMessage(role="user", content=TextContent(type="text", text="hello"))
|
||||
]
|
||||
params = CreateMessageRequestParams(messages=messages, maxTokens=300)
|
||||
params = CreateMessageRequestParams(messages=messages, max_tokens=300)
|
||||
await handler(messages, params, context=None) # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
|
||||
|
||||
call_kwargs = mock_client.chat.completions.create.call_args
|
||||
|
|
@ -331,7 +331,7 @@ def test_convert_messages_raises_on_unsupported_content_type():
|
|||
embedded = EmbeddedResource(
|
||||
type="resource",
|
||||
resource=TextResourceContents(
|
||||
uri=AnyUrl("file:///test.txt"), text="hello", mimeType="text/plain"
|
||||
uri=AnyUrl("file:///test.txt"), text="hello", mime_type="text/plain"
|
||||
),
|
||||
)
|
||||
msg = SamplingMessage.model_construct(
|
||||
|
|
|
|||
|
|
@ -218,11 +218,11 @@ async def test_wait_returns_on_input_required(task_notification_server):
|
|||
# Directly inject an input_required status into the cache and signal the event
|
||||
now = datetime.now(timezone.utc)
|
||||
input_required_status = GetTaskResult(
|
||||
taskId=task._task_id,
|
||||
task_id=task._task_id,
|
||||
status="input_required",
|
||||
statusMessage="Waiting for user input",
|
||||
createdAt=now,
|
||||
lastUpdatedAt=now,
|
||||
status_message="Waiting for user input",
|
||||
created_at=now,
|
||||
last_updated_at=now,
|
||||
ttl=None,
|
||||
)
|
||||
task._status_cache = input_required_status
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class TestSamplingResultType:
|
|||
)
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -96,7 +96,7 @@ class TestSamplingResultType:
|
|||
)
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
else:
|
||||
# Second call: call final_response
|
||||
|
|
@ -115,7 +115,7 @@ class TestSamplingResultType:
|
|||
)
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -168,7 +168,7 @@ class TestSamplingResultType:
|
|||
)
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
else:
|
||||
# Second call: valid type after seeing error
|
||||
|
|
@ -183,7 +183,7 @@ class TestSamplingResultType:
|
|||
)
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -237,7 +237,7 @@ class TestSamplingResultType:
|
|||
role="assistant",
|
||||
content=[TextContent(type="text", text="Hello world")],
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -271,7 +271,7 @@ class TestSampleStep:
|
|||
role="assistant",
|
||||
content=[TextContent(type="text", text="Hello from step")],
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -316,14 +316,14 @@ class TestSampleStep:
|
|||
)
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
else:
|
||||
return CreateMessageResultWithTools(
|
||||
role="assistant",
|
||||
content=[TextContent(type="text", text="Done")],
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -373,7 +373,7 @@ class TestSampleStep:
|
|||
)
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -416,7 +416,7 @@ class TestSampleStep:
|
|||
)
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -454,7 +454,7 @@ class TestTextResponseRetry:
|
|||
role="assistant",
|
||||
content=[TextContent(type="text", text=text)],
|
||||
model="m",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -472,7 +472,7 @@ class TestTextResponseRetry:
|
|||
)
|
||||
],
|
||||
model="m",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
|
||||
async def test_text_response_then_success(self):
|
||||
|
|
@ -562,7 +562,7 @@ def _final_response(call_id: str, input_data: dict) -> CreateMessageResultWithTo
|
|||
)
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -576,7 +576,7 @@ def _tool_call(
|
|||
ToolUseContent(type="tool_use", id=call_id, name=name, input=input_data)
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class TestAutomaticToolLoop:
|
|||
)
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
else:
|
||||
# Second call: return final response
|
||||
|
|
@ -50,7 +50,7 @@ class TestAutomaticToolLoop:
|
|||
role="assistant",
|
||||
content=[TextContent(type="text", text="The weather is sunny!")],
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -110,14 +110,14 @@ class TestAutomaticToolLoop:
|
|||
),
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
else:
|
||||
return CreateMessageResultWithTools(
|
||||
role="assistant",
|
||||
content=[TextContent(type="text", text="Done!")],
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -165,14 +165,14 @@ class TestAutomaticToolLoop:
|
|||
)
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
else:
|
||||
return CreateMessageResultWithTools(
|
||||
role="assistant",
|
||||
content=[TextContent(type="text", text="Handled error")],
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -239,14 +239,14 @@ class TestAutomaticToolLoop:
|
|||
)
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
else:
|
||||
return CreateMessageResultWithTools(
|
||||
role="assistant",
|
||||
content=[TextContent(type="text", text="Handled error")],
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -333,14 +333,14 @@ class TestAutomaticToolLoop:
|
|||
),
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
else:
|
||||
return CreateMessageResultWithTools(
|
||||
role="assistant",
|
||||
content=[TextContent(type="text", text="Done!")],
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -411,14 +411,14 @@ class TestAutomaticToolLoop:
|
|||
),
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
else:
|
||||
return CreateMessageResultWithTools(
|
||||
role="assistant",
|
||||
content=[TextContent(type="text", text="Done!")],
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -491,14 +491,14 @@ class TestAutomaticToolLoop:
|
|||
),
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
else:
|
||||
return CreateMessageResultWithTools(
|
||||
role="assistant",
|
||||
content=[TextContent(type="text", text="Done!")],
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -577,14 +577,14 @@ class TestAutomaticToolLoop:
|
|||
),
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
else:
|
||||
return CreateMessageResultWithTools(
|
||||
role="assistant",
|
||||
content=[TextContent(type="text", text="Done!")],
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -651,14 +651,14 @@ class TestAutomaticToolLoop:
|
|||
),
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
else:
|
||||
return CreateMessageResultWithTools(
|
||||
role="assistant",
|
||||
content=[TextContent(type="text", text="Handled errors")],
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
@ -732,14 +732,14 @@ class TestAutomaticToolLoop:
|
|||
),
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
else:
|
||||
return CreateMessageResultWithTools(
|
||||
role="assistant",
|
||||
content=[TextContent(type="text", text="Done!")],
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ async def test_embedded_resource() -> list:
|
|||
type="resource",
|
||||
resource=mcp_types.TextResourceContents(
|
||||
uri=AnyUrl("test://embedded-resource"),
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
text="This is an embedded resource content.",
|
||||
),
|
||||
)
|
||||
|
|
@ -93,13 +93,13 @@ async def test_multiple_content_types() -> list:
|
|||
ImageContent(
|
||||
type="image",
|
||||
data=base64.b64encode(_1X1_PNG).decode(),
|
||||
mimeType="image/png",
|
||||
mime_type="image/png",
|
||||
),
|
||||
EmbeddedResource(
|
||||
type="resource",
|
||||
resource=mcp_types.TextResourceContents(
|
||||
uri=AnyUrl("test://mixed-content-resource"),
|
||||
mimeType="application/json",
|
||||
mime_type="application/json",
|
||||
text='{"test":"data","value":123}',
|
||||
),
|
||||
),
|
||||
|
|
@ -185,7 +185,7 @@ async def test_elicitation_sep1330_enums(ctx: Context) -> str:
|
|||
"""Tests elicitation with enum schema improvements per SEP-1330."""
|
||||
result = await ctx.session.elicit(
|
||||
message="Please select options from the enum fields",
|
||||
requestedSchema={
|
||||
requested_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"untitledSingle": {
|
||||
|
|
@ -349,7 +349,7 @@ async def test_prompt_with_embedded_resource(resourceUri: str) -> list:
|
|||
type="resource",
|
||||
resource=mcp_types.TextResourceContents(
|
||||
uri=AnyUrl(resourceUri),
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
text=f"Content of resource {resourceUri}",
|
||||
),
|
||||
)
|
||||
|
|
@ -365,7 +365,7 @@ async def test_prompt_with_image() -> list:
|
|||
ImageContent(
|
||||
type="image",
|
||||
data=base64.b64encode(_1X1_PNG).decode(),
|
||||
mimeType="image/png",
|
||||
mime_type="image/png",
|
||||
)
|
||||
),
|
||||
Message("Please analyze the image above."),
|
||||
|
|
|
|||
|
|
@ -239,12 +239,14 @@ def tool_server():
|
|||
def mixed_content_tool() -> list[TextContent | ImageContent | EmbeddedResource]:
|
||||
return [
|
||||
TextContent(type="text", text="Hello"),
|
||||
ImageContent(type="image", data="abc", mimeType="application/octet-stream"),
|
||||
ImageContent(
|
||||
type="image", data="abc", mime_type="application/octet-stream"
|
||||
),
|
||||
EmbeddedResource(
|
||||
type="resource",
|
||||
resource=BlobResourceContents(
|
||||
blob=base64.b64encode(b"abc").decode(),
|
||||
mimeType="application/octet-stream",
|
||||
mime_type="application/octet-stream",
|
||||
uri=AnyUrl("file:///test.bin"),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ def error_tool_result_factory(arg1: str) -> CallToolRequestResult:
|
|||
"Error calling tool 'error_tool': Error in tool with arg1: " + arg1
|
||||
)
|
||||
return CallToolRequestResult(
|
||||
isError=True,
|
||||
is_error=True,
|
||||
content=[TextContent(text=formatted_error_text, type="text")],
|
||||
tool="error_tool",
|
||||
arguments={"arg1": arg1},
|
||||
|
|
@ -46,7 +46,7 @@ async def echo_tool(arg1: str) -> str:
|
|||
def echo_tool_result_factory(arg1: str) -> CallToolRequestResult:
|
||||
"""A tool that returns a result based on the input arguments."""
|
||||
return CallToolRequestResult(
|
||||
isError=False,
|
||||
is_error=False,
|
||||
content=[TextContent(text=f"{arg1}", type="text")],
|
||||
tool="echo_tool",
|
||||
arguments={"arg1": arg1},
|
||||
|
|
@ -60,7 +60,7 @@ async def no_return_tool(arg1: str) -> None:
|
|||
def no_return_tool_result_factory(arg1: str) -> CallToolRequestResult:
|
||||
"""A tool that returns a result based on the input arguments."""
|
||||
return CallToolRequestResult(
|
||||
isError=False,
|
||||
is_error=False,
|
||||
content=[],
|
||||
tool="no_return_tool",
|
||||
arguments={"arg1": arg1},
|
||||
|
|
@ -147,7 +147,7 @@ async def test_call_tool_bulk_error_stops(bulk_caller_live: BulkToolCaller):
|
|||
text="Error calling tool 'error_tool': Error in tool with arg1: error_value",
|
||||
)
|
||||
],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
tool="error_tool",
|
||||
arguments={"arg1": "error_value"},
|
||||
)
|
||||
|
|
@ -174,7 +174,7 @@ async def test_call_tool_bulk_error_continues(bulk_caller_live: BulkToolCaller):
|
|||
text="Error calling tool 'error_tool': Error in tool with arg1: error_value",
|
||||
)
|
||||
],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
tool="error_tool",
|
||||
arguments={"arg1": "error_value"},
|
||||
),
|
||||
|
|
@ -249,7 +249,7 @@ async def test_call_tools_bulk_error_stops(bulk_caller_live: BulkToolCaller):
|
|||
text="Error calling tool 'error_tool': Error in tool with arg1: error_value",
|
||||
)
|
||||
],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
tool="error_tool",
|
||||
arguments={"arg1": "error_value"},
|
||||
)
|
||||
|
|
@ -275,7 +275,7 @@ async def test_call_tools_bulk_error_continues(bulk_caller_live: BulkToolCaller)
|
|||
text="Error calling tool 'error_tool': Error in tool with arg1: error_value",
|
||||
)
|
||||
],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
tool="error_tool",
|
||||
arguments={"arg1": "error_value"},
|
||||
),
|
||||
|
|
@ -309,7 +309,7 @@ async def test_call_tools_bulk_blocks_self_invocation(bulk_caller_live: BulkTool
|
|||
),
|
||||
)
|
||||
],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
tool="call_tools_bulk",
|
||||
arguments={"tool_calls": []},
|
||||
),
|
||||
|
|
@ -341,7 +341,7 @@ async def test_call_tool_bulk_blocks_self_invocation(bulk_caller_live: BulkToolC
|
|||
),
|
||||
)
|
||||
],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
tool="call_tool_bulk",
|
||||
arguments={"arg1": "value1"},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -656,7 +656,7 @@ async def test_code_mode_execute_non_text_content_stringified() -> None:
|
|||
|
||||
@mcp.tool
|
||||
def image_tool() -> ImageContent:
|
||||
return ImageContent(type="image", data="base64data", mimeType="image/png")
|
||||
return ImageContent(type="image", data="base64data", mime_type="image/png")
|
||||
|
||||
mcp.add_transform(CodeMode(sandbox_provider=_UnsafeTestSandboxProvider()))
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ class TestRenderPrompt:
|
|||
resource=TextResourceContents(
|
||||
uri=FileUrl("file://file.txt"),
|
||||
text="File contents",
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
),
|
||||
),
|
||||
role="user",
|
||||
|
|
@ -130,7 +130,7 @@ class TestRenderPrompt:
|
|||
resource=TextResourceContents(
|
||||
uri=FileUrl("file://file.txt"),
|
||||
text="File contents",
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
),
|
||||
),
|
||||
role="user",
|
||||
|
|
@ -149,7 +149,7 @@ class TestRenderPrompt:
|
|||
resource=TextResourceContents(
|
||||
uri=FileUrl("file://file.txt"),
|
||||
text="File contents",
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
),
|
||||
),
|
||||
role="user",
|
||||
|
|
@ -167,7 +167,7 @@ class TestRenderPrompt:
|
|||
resource=TextResourceContents(
|
||||
uri=FileUrl("file://file.txt"),
|
||||
text="File contents",
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
),
|
||||
),
|
||||
role="user",
|
||||
|
|
@ -186,7 +186,7 @@ class TestRenderPrompt:
|
|||
resource=TextResourceContents(
|
||||
uri=FileUrl("file://file.txt"),
|
||||
text="File contents",
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
),
|
||||
),
|
||||
role="user",
|
||||
|
|
@ -202,7 +202,7 @@ class TestRenderPrompt:
|
|||
resource=TextResourceContents(
|
||||
uri=FileUrl("file://file.txt"),
|
||||
text="File contents",
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
),
|
||||
),
|
||||
role="user",
|
||||
|
|
@ -628,7 +628,7 @@ class TestMessage:
|
|||
"""Test Message passes through ImageContent without JSON serialization."""
|
||||
from mcp_types import ImageContent
|
||||
|
||||
img = ImageContent(type="image", data="base64data", mimeType="image/png")
|
||||
img = ImageContent(type="image", data="base64data", mime_type="image/png")
|
||||
msg = Message(img, role="user")
|
||||
assert isinstance(msg.content, ImageContent)
|
||||
assert msg.content.data == "base64data"
|
||||
|
|
@ -638,7 +638,7 @@ class TestMessage:
|
|||
"""Test Message passes through AudioContent without JSON serialization."""
|
||||
from mcp_types import AudioContent
|
||||
|
||||
audio = AudioContent(type="audio", data="base64audio", mimeType="audio/wav")
|
||||
audio = AudioContent(type="audio", data="base64audio", mime_type="audio/wav")
|
||||
msg = Message(audio, role="user")
|
||||
assert isinstance(msg.content, AudioContent)
|
||||
assert msg.content.data == "base64audio"
|
||||
|
|
@ -648,7 +648,7 @@ class TestMessage:
|
|||
"""Test that ImageContent round-trips through to_mcp_prompt_message."""
|
||||
from mcp_types import ImageContent
|
||||
|
||||
img = ImageContent(type="image", data="base64data", mimeType="image/png")
|
||||
img = ImageContent(type="image", data="base64data", mime_type="image/png")
|
||||
msg = Message(img, role="user")
|
||||
mcp_msg = msg.to_mcp_prompt_message()
|
||||
assert isinstance(mcp_msg.content, ImageContent)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class TestResourceTemplateFieldPreservation:
|
|||
name="t",
|
||||
title="Human Title",
|
||||
meta={"owner": "team-a"},
|
||||
icons=[Icon(src="https://example.com/icon.png", mimeType="image/png")],
|
||||
icons=[Icon(src="https://example.com/icon.png", mime_type="image/png")],
|
||||
annotations=Annotations(priority=0.5, audience=["user"]),
|
||||
)
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ class TestResourceTemplateFieldPreservation:
|
|||
"data://{param}",
|
||||
title="Sub Title",
|
||||
meta={"owner": "team-b"},
|
||||
icons=[Icon(src="https://example.com/s.png", mimeType="image/png")],
|
||||
icons=[Icon(src="https://example.com/s.png", mime_type="image/png")],
|
||||
annotations=Annotations(priority=0.9),
|
||||
tags={"alpha"},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ class TestEnhancedAuthorizationHandler:
|
|||
# Create FastMCP server with custom branding
|
||||
mcp = FastMCP(
|
||||
"My Custom Server",
|
||||
icons=[Icon(src="https://example.com/icon.png", mimeType="image/png")],
|
||||
icons=[Icon(src="https://example.com/icon.png", mime_type="image/png")],
|
||||
)
|
||||
|
||||
# Create app with OAuth routes
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ class TestResponseLimitingMiddleware:
|
|||
def binary_tool() -> ToolResult:
|
||||
return ToolResult(
|
||||
content=[
|
||||
ImageContent(type="image", data="x" * 10_000, mimeType="image/png")
|
||||
ImageContent(type="image", data="x" * 10_000, mime_type="image/png")
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ def fastmcp_server():
|
|||
content=mcp_types.ImageContent(
|
||||
type="image",
|
||||
data="iVBORw0KGgoAAAANSUhEUg==",
|
||||
mimeType="image/png",
|
||||
mime_type="image/png",
|
||||
),
|
||||
role="user",
|
||||
),
|
||||
|
|
@ -389,10 +389,10 @@ class TestTools:
|
|||
error_result = mcp_types.CallToolResult(
|
||||
content=[
|
||||
mcp_types.ImageContent(
|
||||
type="image", data="abc123", mimeType="image/png"
|
||||
type="image", data="abc123", mime_type="image/png"
|
||||
)
|
||||
],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
)
|
||||
with patch.object(
|
||||
Client, "call_tool_mcp", new_callable=AsyncMock, return_value=error_result
|
||||
|
|
@ -405,7 +405,7 @@ class TestTools:
|
|||
"""Error responses with empty content should not crash."""
|
||||
error_result = mcp_types.CallToolResult(
|
||||
content=[],
|
||||
isError=True,
|
||||
is_error=True,
|
||||
)
|
||||
with patch.object(
|
||||
Client, "call_tool_mcp", new_callable=AsyncMock, return_value=error_result
|
||||
|
|
@ -419,11 +419,11 @@ class TestTools:
|
|||
error_result = mcp_types.CallToolResult(
|
||||
content=[
|
||||
mcp_types.ImageContent(
|
||||
type="image", data="abc123", mimeType="image/png"
|
||||
type="image", data="abc123", mime_type="image/png"
|
||||
)
|
||||
],
|
||||
structuredContent={"detail": "boom"},
|
||||
isError=True,
|
||||
structured_content={"detail": "boom"},
|
||||
is_error=True,
|
||||
)
|
||||
with patch.object(
|
||||
Client, "call_tool_mcp", new_callable=AsyncMock, return_value=error_result
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ class TestBackgroundTaskIntegration:
|
|||
role="assistant",
|
||||
content=TextContent(type="text", text="hello from background"),
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
async with Client(mcp, sampling_handler=sampling_handler) as client:
|
||||
|
|
|
|||
|
|
@ -283,7 +283,7 @@ class TestToolExecutionMetadata:
|
|||
assert tool.execution.task_support == "optional"
|
||||
|
||||
async def test_required_tool_exposes_task_support(self):
|
||||
"""Tools with mode=required should expose taskSupport='required'."""
|
||||
"""Tools with mode=required should expose task_support='required'."""
|
||||
mcp = FastMCP("test", tasks=False)
|
||||
|
||||
@mcp.tool(task=TaskConfig(mode="required"))
|
||||
|
|
|
|||
|
|
@ -582,8 +582,8 @@ class TestMountedTaskMetadata:
|
|||
mcp_tool = MCPTool(
|
||||
name="remote_task_tool",
|
||||
description="A remote tool that supports tasks",
|
||||
inputSchema={"type": "object", "properties": {}},
|
||||
execution=ToolExecution(taskSupport="optional"),
|
||||
input_schema={"type": "object", "properties": {}},
|
||||
execution=ToolExecution(task_support="optional"),
|
||||
)
|
||||
|
||||
proxy = ProxyTool.from_mcp_tool(lambda: None, mcp_tool) # ty: ignore[invalid-argument-type]
|
||||
|
|
|
|||
|
|
@ -581,7 +581,7 @@ async def mcp_content_server(tmp_path):
|
|||
return ImageContent(
|
||||
type="image",
|
||||
data=base64.b64encode(test_image.read_bytes()).decode(),
|
||||
mimeType="image/png",
|
||||
mime_type="image/png",
|
||||
)
|
||||
|
||||
@mcp.tool(task=True)
|
||||
|
|
@ -606,7 +606,7 @@ async def mcp_content_server(tmp_path):
|
|||
ImageContent(
|
||||
type="image",
|
||||
data=base64.b64encode(test_image.read_bytes()).decode(),
|
||||
mimeType="image/png",
|
||||
mime_type="image/png",
|
||||
),
|
||||
TextContent(type="text", text="Third block"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -121,13 +121,13 @@ class TestSamplingToolSpan:
|
|||
)
|
||||
],
|
||||
model="test-model",
|
||||
stopReason="toolUse",
|
||||
stop_reason="toolUse",
|
||||
)
|
||||
return CreateMessageResultWithTools(
|
||||
role="assistant",
|
||||
content=[TextContent(type="text", text="done")],
|
||||
model="test-model",
|
||||
stopReason="endTurn",
|
||||
stop_reason="endTurn",
|
||||
)
|
||||
|
||||
mcp = FastMCP(sampling_handler=sampling_handler)
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@ class TestServerIcons:
|
|||
icons = [
|
||||
Icon(
|
||||
src="https://example.com/icon.png",
|
||||
mimeType="image/png",
|
||||
mime_type="image/png",
|
||||
sizes=["48x48"],
|
||||
),
|
||||
Icon(
|
||||
src="data:image/svg+xml;base64,PHN2Zz48L3N2Zz4=",
|
||||
mimeType="image/svg+xml",
|
||||
mime_type="image/svg+xml",
|
||||
sizes=["any"],
|
||||
),
|
||||
]
|
||||
|
|
@ -58,7 +58,7 @@ class TestToolIcons:
|
|||
mcp = FastMCP("TestServer")
|
||||
|
||||
icons = [
|
||||
Icon(src="https://example.com/tool-icon.png", mimeType="image/png"),
|
||||
Icon(src="https://example.com/tool-icon.png", mime_type="image/png"),
|
||||
]
|
||||
|
||||
@mcp.tool(icons=icons)
|
||||
|
|
@ -272,17 +272,17 @@ class TestIconTypes:
|
|||
icons = [
|
||||
Icon(
|
||||
src="https://example.com/icon-48.png",
|
||||
mimeType="image/png",
|
||||
mime_type="image/png",
|
||||
sizes=["48x48"],
|
||||
),
|
||||
Icon(
|
||||
src="https://example.com/icon-96.png",
|
||||
mimeType="image/png",
|
||||
mime_type="image/png",
|
||||
sizes=["96x96"],
|
||||
),
|
||||
Icon(
|
||||
src="https://example.com/icon.svg",
|
||||
mimeType="image/svg+xml",
|
||||
mime_type="image/svg+xml",
|
||||
sizes=["any"],
|
||||
),
|
||||
]
|
||||
|
|
@ -299,7 +299,7 @@ class TestIconTypes:
|
|||
# Simple SVG data URI
|
||||
data_uri = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6Ii8+PC9zdmc+"
|
||||
|
||||
icons = [Icon(src=data_uri, mimeType="image/svg+xml")]
|
||||
icons = [Icon(src=data_uri, mime_type="image/svg+xml")]
|
||||
|
||||
mcp = FastMCP("TestServer")
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ async def test_tool_annotations_in_tool_manager():
|
|||
@mcp.tool(
|
||||
annotations=ToolAnnotations(
|
||||
title="Echo Tool",
|
||||
readOnlyHint=True,
|
||||
openWorldHint=False,
|
||||
read_only_hint=True,
|
||||
open_world_hint=False,
|
||||
)
|
||||
)
|
||||
def echo(message: str) -> str:
|
||||
|
|
@ -39,8 +39,8 @@ async def test_tool_annotations_in_mcp_protocol():
|
|||
@mcp.tool(
|
||||
annotations=ToolAnnotations(
|
||||
title="Echo Tool",
|
||||
readOnlyHint=True,
|
||||
openWorldHint=False,
|
||||
read_only_hint=True,
|
||||
open_world_hint=False,
|
||||
)
|
||||
)
|
||||
def echo(message: str) -> str:
|
||||
|
|
@ -63,8 +63,8 @@ async def test_tool_annotations_in_client_api():
|
|||
@mcp.tool(
|
||||
annotations=ToolAnnotations(
|
||||
title="Echo Tool",
|
||||
readOnlyHint=True,
|
||||
openWorldHint=False,
|
||||
read_only_hint=True,
|
||||
open_world_hint=False,
|
||||
)
|
||||
)
|
||||
def echo(message: str) -> str:
|
||||
|
|
@ -114,10 +114,10 @@ async def test_direct_tool_annotations_in_tool_manager():
|
|||
|
||||
annotations = ToolAnnotations(
|
||||
title="Direct Tool",
|
||||
readOnlyHint=False,
|
||||
destructiveHint=True,
|
||||
idempotentHint=False,
|
||||
openWorldHint=True,
|
||||
read_only_hint=False,
|
||||
destructive_hint=True,
|
||||
idempotent_hint=False,
|
||||
open_world_hint=True,
|
||||
)
|
||||
|
||||
@mcp.tool(annotations=annotations)
|
||||
|
|
@ -142,10 +142,10 @@ async def test_direct_tool_annotations_in_client_api():
|
|||
|
||||
annotations = ToolAnnotations(
|
||||
title="Direct Tool",
|
||||
readOnlyHint=False,
|
||||
destructiveHint=True,
|
||||
idempotentHint=False,
|
||||
openWorldHint=True,
|
||||
read_only_hint=False,
|
||||
destructive_hint=True,
|
||||
idempotent_hint=False,
|
||||
open_world_hint=True,
|
||||
)
|
||||
|
||||
@mcp.tool(annotations=annotations)
|
||||
|
|
@ -177,8 +177,8 @@ async def test_add_tool_method_annotations():
|
|||
name="create_item",
|
||||
annotations=ToolAnnotations(
|
||||
title="Create Item",
|
||||
readOnlyHint=False,
|
||||
destructiveHint=False,
|
||||
read_only_hint=False,
|
||||
destructive_hint=False,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -206,8 +206,8 @@ async def test_tool_functionality_with_annotations():
|
|||
name="create_item",
|
||||
annotations=ToolAnnotations(
|
||||
title="Create Item",
|
||||
readOnlyHint=False,
|
||||
destructiveHint=False,
|
||||
read_only_hint=False,
|
||||
destructive_hint=False,
|
||||
),
|
||||
)
|
||||
mcp.add_tool(tool)
|
||||
|
|
|
|||
|
|
@ -104,8 +104,8 @@ class TestConvertResultToContent:
|
|||
argnames="content_block",
|
||||
argvalues=[
|
||||
(TextContent(type="text", text="hello")),
|
||||
(ImageContent(type="image", data="fakeimagedata", mimeType="image/png")),
|
||||
(AudioContent(type="audio", data="fakeaudiodata", mimeType="audio/mpeg")),
|
||||
(ImageContent(type="image", data="fakeimagedata", mime_type="image/png")),
|
||||
(AudioContent(type="audio", data="fakeaudiodata", mime_type="audio/mpeg")),
|
||||
(
|
||||
ResourceLink(
|
||||
type="resource_link",
|
||||
|
|
@ -118,7 +118,7 @@ class TestConvertResultToContent:
|
|||
type="resource",
|
||||
resource=TextResourceContents(
|
||||
uri=AnyUrl("resource://test"),
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
text="resource content",
|
||||
),
|
||||
)
|
||||
|
|
@ -140,7 +140,7 @@ class TestConvertResultToContent:
|
|||
Image(data=b"fakeimagedata"),
|
||||
[
|
||||
ImageContent(
|
||||
type="image", data="ZmFrZWltYWdlZGF0YQ==", mimeType="image/png"
|
||||
type="image", data="ZmFrZWltYWdlZGF0YQ==", mime_type="image/png"
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
@ -148,7 +148,7 @@ class TestConvertResultToContent:
|
|||
Audio(data=b"fakeaudiodata"),
|
||||
[
|
||||
AudioContent(
|
||||
type="audio", data="ZmFrZWF1ZGlvZGF0YQ==", mimeType="audio/wav"
|
||||
type="audio", data="ZmFrZWF1ZGlvZGF0YQ==", mime_type="audio/wav"
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
@ -160,7 +160,7 @@ class TestConvertResultToContent:
|
|||
resource=BlobResourceContents(
|
||||
uri=AnyUrl("file:///resource.octet-stream"),
|
||||
blob="ZmlsZWRhdGE=",
|
||||
mimeType="application/octet-stream",
|
||||
mime_type="application/octet-stream",
|
||||
),
|
||||
)
|
||||
],
|
||||
|
|
@ -193,7 +193,7 @@ class TestConvertResultToContent:
|
|||
type="resource",
|
||||
resource=TextResourceContents(
|
||||
uri=AnyUrl("resource://test"),
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
text="resource content",
|
||||
),
|
||||
),
|
||||
|
|
@ -209,10 +209,10 @@ class TestConvertResultToContent:
|
|||
TextContent(type="text", text='{"key":"value"}'),
|
||||
TextContent(type="text", text='{"x":1,"y":"hello"}'),
|
||||
ImageContent(
|
||||
type="image", data="ZmFrZWltYWdlZGF0YQ==", mimeType="image/png"
|
||||
type="image", data="ZmFrZWltYWdlZGF0YQ==", mime_type="image/png"
|
||||
),
|
||||
AudioContent(
|
||||
type="audio", data="ZmFrZWF1ZGlvZGF0YQ==", mimeType="audio/wav"
|
||||
type="audio", data="ZmFrZWF1ZGlvZGF0YQ==", mime_type="audio/wav"
|
||||
),
|
||||
ResourceLink(
|
||||
name="test resource",
|
||||
|
|
@ -223,7 +223,7 @@ class TestConvertResultToContent:
|
|||
type="resource",
|
||||
resource=TextResourceContents(
|
||||
uri=AnyUrl("resource://test"),
|
||||
mimeType="text/plain",
|
||||
mime_type="text/plain",
|
||||
text="resource content",
|
||||
),
|
||||
),
|
||||
|
|
@ -475,7 +475,7 @@ class TestAutomaticStructuredContent:
|
|||
assert result.content == snapshot(
|
||||
[
|
||||
AudioContent(
|
||||
type="audio", data="ZmFrZWF1ZGlvZGF0YQ==", mimeType="audio/wav"
|
||||
type="audio", data="ZmFrZWF1ZGlvZGF0YQ==", mime_type="audio/wav"
|
||||
)
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -542,7 +542,7 @@ class TestToolExecutionField:
|
|||
name="my_tool",
|
||||
description="A tool with execution",
|
||||
parameters={"type": "object", "properties": {}},
|
||||
execution=ToolExecution(taskSupport="optional"),
|
||||
execution=ToolExecution(task_support="optional"),
|
||||
)
|
||||
|
||||
mcp_tool = tool.to_mcp_tool()
|
||||
|
|
@ -566,10 +566,10 @@ class TestToolExecutionField:
|
|||
name="my_tool",
|
||||
description="A tool",
|
||||
parameters={"type": "object", "properties": {}},
|
||||
execution=ToolExecution(taskSupport="optional"),
|
||||
execution=ToolExecution(task_support="optional"),
|
||||
)
|
||||
|
||||
override_execution = ToolExecution(taskSupport="required")
|
||||
override_execution = ToolExecution(task_support="required")
|
||||
mcp_tool = tool.to_mcp_tool(execution=override_execution)
|
||||
assert mcp_tool.execution is not None
|
||||
assert mcp_tool.execution.task_support == "required"
|
||||
|
|
@ -593,7 +593,7 @@ class TestToolExecutionField:
|
|||
name="my_tool",
|
||||
description="A tool with required execution",
|
||||
parameters={"type": "object", "properties": {}},
|
||||
execution=ToolExecution(taskSupport="required"),
|
||||
execution=ToolExecution(task_support="required"),
|
||||
)
|
||||
|
||||
mcp_tool = tool.to_mcp_tool()
|
||||
|
|
@ -606,7 +606,7 @@ class TestToolExecutionField:
|
|||
name="my_tool",
|
||||
description="A tool with forbidden execution",
|
||||
parameters={"type": "object", "properties": {}},
|
||||
execution=ToolExecution(taskSupport="forbidden"),
|
||||
execution=ToolExecution(task_support="forbidden"),
|
||||
)
|
||||
|
||||
mcp_tool = tool.to_mcp_tool()
|
||||
|
|
|
|||
|
|
@ -107,8 +107,8 @@ class TestCircularReferencesSerialization:
|
|||
tool = MCPTool(
|
||||
name="get_node",
|
||||
description="Get a node",
|
||||
inputSchema={"type": "object", "properties": {}},
|
||||
outputSchema=output_schema,
|
||||
input_schema={"type": "object", "properties": {}},
|
||||
output_schema=output_schema,
|
||||
)
|
||||
# This must not raise ValueError: Circular reference detected
|
||||
tool.model_dump(by_alias=True, mode="json", exclude_none=True)
|
||||
|
|
@ -153,8 +153,8 @@ class TestCircularReferencesSerialization:
|
|||
tool = MCPTool(
|
||||
name="get_pr",
|
||||
description="Get a pull request",
|
||||
inputSchema={"type": "object", "properties": {}},
|
||||
outputSchema=output_schema,
|
||||
input_schema={"type": "object", "properties": {}},
|
||||
output_schema=output_schema,
|
||||
)
|
||||
tool.model_dump(by_alias=True, mode="json", exclude_none=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class TestIconExtraction:
|
|||
icons=[
|
||||
Icon(
|
||||
src="https://example.com/icon.png",
|
||||
mimeType="image/png",
|
||||
mime_type="image/png",
|
||||
sizes=["48x48"],
|
||||
)
|
||||
],
|
||||
|
|
@ -63,7 +63,7 @@ class TestIconExtraction:
|
|||
icons=[
|
||||
Icon(
|
||||
src="https://example.com/calculator.png",
|
||||
mimeType="image/png",
|
||||
mime_type="image/png",
|
||||
)
|
||||
]
|
||||
)
|
||||
|
|
@ -98,7 +98,7 @@ class TestIconExtraction:
|
|||
|
||||
@mcp.resource(
|
||||
"resource://data",
|
||||
icons=[Icon(src="https://example.com/data.png", mimeType="image/png")],
|
||||
icons=[Icon(src="https://example.com/data.png", mime_type="image/png")],
|
||||
)
|
||||
def get_data() -> str:
|
||||
"""Get data."""
|
||||
|
|
@ -131,7 +131,7 @@ class TestIconExtraction:
|
|||
|
||||
@mcp.resource(
|
||||
"resource://user/{id}",
|
||||
icons=[Icon(src="https://example.com/user.png", mimeType="image/png")],
|
||||
icons=[Icon(src="https://example.com/user.png", mime_type="image/png")],
|
||||
)
|
||||
def get_user(id: str) -> str:
|
||||
"""Get user by ID."""
|
||||
|
|
@ -167,7 +167,7 @@ class TestIconExtraction:
|
|||
mcp = FastMCP("PromptIconServer")
|
||||
|
||||
@mcp.prompt(
|
||||
icons=[Icon(src="https://example.com/analyze.png", mimeType="image/png")]
|
||||
icons=[Icon(src="https://example.com/analyze.png", mime_type="image/png")]
|
||||
)
|
||||
def analyze(data: str) -> list:
|
||||
"""Analyze data."""
|
||||
|
|
@ -201,12 +201,12 @@ class TestIconExtraction:
|
|||
icons=[
|
||||
Icon(
|
||||
src="https://example.com/icon-48.png",
|
||||
mimeType="image/png",
|
||||
mime_type="image/png",
|
||||
sizes=["48x48"],
|
||||
),
|
||||
Icon(
|
||||
src="https://example.com/icon-96.png",
|
||||
mimeType="image/png",
|
||||
mime_type="image/png",
|
||||
sizes=["96x96"],
|
||||
),
|
||||
],
|
||||
|
|
@ -245,7 +245,7 @@ class TestIconExtraction:
|
|||
|
||||
mcp = FastMCP("DataURIServer")
|
||||
|
||||
@mcp.tool(icons=[Icon(src=data_uri, mimeType="image/png")])
|
||||
@mcp.tool(icons=[Icon(src=data_uri, mime_type="image/png")])
|
||||
def data_uri_tool() -> str:
|
||||
"""Tool with data URI icon."""
|
||||
return "data"
|
||||
|
|
@ -264,7 +264,7 @@ class TestIconExtraction:
|
|||
mcp = FastMCP1x("Icon1xServer")
|
||||
|
||||
@mcp.tool(
|
||||
icons=[Icon(src="https://example.com/v1-tool.png", mimeType="image/png")]
|
||||
icons=[Icon(src="https://example.com/v1-tool.png", mime_type="image/png")]
|
||||
)
|
||||
def v1_tool() -> str:
|
||||
"""Tool in v1 server."""
|
||||
|
|
@ -284,11 +284,11 @@ class TestIconExtraction:
|
|||
mcp = FastMCP(
|
||||
"FormattedIconServer",
|
||||
website_url="https://example.com",
|
||||
icons=[Icon(src="https://example.com/server.png", mimeType="image/png")],
|
||||
icons=[Icon(src="https://example.com/server.png", mime_type="image/png")],
|
||||
)
|
||||
|
||||
@mcp.tool(
|
||||
icons=[Icon(src="https://example.com/tool.png", mimeType="image/png")]
|
||||
icons=[Icon(src="https://example.com/tool.png", mime_type="image/png")]
|
||||
)
|
||||
def icon_tool() -> str:
|
||||
"""Tool with icon."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue