"""MCP 结构化模型与转换工具。 负责在 MCP SDK 原始对象与主程序内部数据模型之间进行转换, 避免连接层和管理器层直接操作大量弱类型字段。 """ from __future__ import annotations from dataclasses import dataclass, field from typing import Any, Optional from src.core.tooling import ToolAnnotation, ToolContentItem, ToolIcon def _dump_model_metadata(raw_value: Any) -> dict[str, Any]: """提取任意 MCP 模型对象中的元数据字典。 Args: raw_value: MCP SDK 返回的原始对象。 Returns: dict[str, Any]: 归一化后的元数据字典。 """ metadata = getattr(raw_value, "meta", None) if isinstance(metadata, dict): return dict(metadata) return {} def build_tool_icon(raw_icon: Any) -> ToolIcon: """将 MCP 图标对象转换为统一图标模型。 Args: raw_icon: MCP SDK 返回的图标对象。 Returns: ToolIcon: 统一图标模型。 """ sizes_value = getattr(raw_icon, "sizes", None) sizes = [str(item) for item in sizes_value] if isinstance(sizes_value, list) else [] return ToolIcon( src=str(getattr(raw_icon, "src", "") or ""), mime_type=str(getattr(raw_icon, "mimeType", "") or ""), sizes=sizes, ) def build_tool_annotation(raw_annotation: Any) -> Optional[ToolAnnotation]: """将 MCP 注解对象转换为统一注解模型。 Args: raw_annotation: MCP SDK 返回的注解对象。 Returns: Optional[ToolAnnotation]: 统一注解模型;无有效内容时返回 ``None``。 """ if raw_annotation is None: return None audience_value = getattr(raw_annotation, "audience", None) audience = [str(item) for item in audience_value] if isinstance(audience_value, list) else [] priority_value = getattr(raw_annotation, "priority", None) priority = float(priority_value) if isinstance(priority_value, int | float) else None metadata = _dump_model_metadata(raw_annotation) if not audience and priority is None and not metadata: return None return ToolAnnotation( audience=audience, priority=priority, metadata=metadata, ) def build_tool_content_item(raw_content: Any) -> ToolContentItem: """将 MCP 内容块转换为统一工具内容项。 Args: raw_content: MCP SDK 返回的内容块对象。 Returns: ToolContentItem: 统一工具内容项。 """ content_type = str(getattr(raw_content, "type", "") or "").strip().lower() annotation = build_tool_annotation(getattr(raw_content, "annotations", None)) metadata = _dump_model_metadata(raw_content) if content_type == "text" or hasattr(raw_content, "text"): return ToolContentItem( content_type="text", text=str(getattr(raw_content, "text", "") or ""), annotation=annotation, metadata=metadata, ) if content_type == "image": return ToolContentItem( content_type="image", data=str(getattr(raw_content, "data", "") or ""), mime_type=str(getattr(raw_content, "mimeType", "") or ""), annotation=annotation, metadata=metadata, ) if content_type == "audio": return ToolContentItem( content_type="audio", data=str(getattr(raw_content, "data", "") or ""), mime_type=str(getattr(raw_content, "mimeType", "") or ""), annotation=annotation, metadata=metadata, ) if content_type == "resource_link": return ToolContentItem( content_type="resource_link", uri=str(getattr(raw_content, "uri", "") or ""), name=str(getattr(raw_content, "name", "") or ""), description=str(getattr(raw_content, "description", "") or ""), mime_type=str(getattr(raw_content, "mimeType", "") or ""), annotation=annotation, metadata=metadata, ) if content_type == "resource" or hasattr(raw_content, "resource"): resource = getattr(raw_content, "resource", None) resource_metadata = metadata | _dump_model_metadata(resource) return ToolContentItem( content_type="resource", text=str(getattr(resource, "text", "") or ""), data=str(getattr(resource, "blob", "") or ""), mime_type=str(getattr(resource, "mimeType", "") or ""), uri=str(getattr(resource, "uri", "") or ""), name=str(getattr(resource, "name", "") or ""), annotation=annotation, metadata=resource_metadata, ) if hasattr(raw_content, "data"): return ToolContentItem( content_type="binary", data=str(getattr(raw_content, "data", "") or ""), mime_type=str(getattr(raw_content, "mimeType", "") or ""), annotation=annotation, metadata=metadata, ) return ToolContentItem( content_type="unknown", text=str(raw_content), annotation=annotation, metadata=metadata, ) def build_tool_content_items(raw_contents: list[Any] | None) -> list[ToolContentItem]: """批量转换 MCP 内容块列表。 Args: raw_contents: MCP SDK 返回的内容块列表。 Returns: list[ToolContentItem]: 转换后的统一内容项列表。 """ if not raw_contents: return [] return [build_tool_content_item(item) for item in raw_contents] @dataclass(slots=True) class MCPPromptArgumentSpec: """MCP Prompt 参数声明。""" name: str description: str = "" required: bool = False @dataclass(slots=True) class MCPPromptSpec: """MCP Prompt 声明。""" name: str server_name: str title: str = "" description: str = "" arguments: list[MCPPromptArgumentSpec] = field(default_factory=list) icons: list[ToolIcon] = field(default_factory=list) metadata: dict[str, Any] = field(default_factory=dict) @dataclass(slots=True) class MCPPromptMessage: """MCP Prompt 消息。""" role: str content: ToolContentItem @dataclass(slots=True) class MCPPromptResult: """MCP Prompt 获取结果。""" prompt_name: str server_name: str description: str = "" messages: list[MCPPromptMessage] = field(default_factory=list) metadata: dict[str, Any] = field(default_factory=dict) @dataclass(slots=True) class MCPResourceSpec: """MCP Resource 声明。""" uri: str server_name: str name: str title: str = "" description: str = "" mime_type: str = "" size: int | None = None icons: list[ToolIcon] = field(default_factory=list) annotation: ToolAnnotation | None = None metadata: dict[str, Any] = field(default_factory=dict) @dataclass(slots=True) class MCPResourceTemplateSpec: """MCP Resource Template 声明。""" uri_template: str server_name: str name: str title: str = "" description: str = "" mime_type: str = "" icons: list[ToolIcon] = field(default_factory=list) annotation: ToolAnnotation | None = None metadata: dict[str, Any] = field(default_factory=dict) @dataclass(slots=True) class MCPResourceReadResult: """MCP Resource 读取结果。""" uri: str server_name: str contents: list[ToolContentItem] = field(default_factory=list) metadata: dict[str, Any] = field(default_factory=dict) def build_prompt_argument_spec(raw_argument: Any) -> MCPPromptArgumentSpec: """将 MCP Prompt 参数对象转换为统一结构。 Args: raw_argument: MCP SDK 返回的 Prompt 参数对象。 Returns: MCPPromptArgumentSpec: 统一 Prompt 参数结构。 """ return MCPPromptArgumentSpec( name=str(getattr(raw_argument, "name", "") or ""), description=str(getattr(raw_argument, "description", "") or ""), required=bool(getattr(raw_argument, "required", False)), ) def build_prompt_spec(raw_prompt: Any, server_name: str) -> MCPPromptSpec: """将 MCP Prompt 定义转换为统一结构。 Args: raw_prompt: MCP SDK 返回的 Prompt 对象。 server_name: Prompt 所属的服务器名称。 Returns: MCPPromptSpec: 统一 Prompt 定义。 """ raw_arguments = getattr(raw_prompt, "arguments", None) raw_icons = getattr(raw_prompt, "icons", None) return MCPPromptSpec( name=str(getattr(raw_prompt, "name", "") or ""), server_name=server_name, title=str(getattr(raw_prompt, "title", "") or ""), description=str(getattr(raw_prompt, "description", "") or ""), arguments=[build_prompt_argument_spec(item) for item in raw_arguments] if isinstance(raw_arguments, list) else [], icons=[build_tool_icon(item) for item in raw_icons] if isinstance(raw_icons, list) else [], metadata=_dump_model_metadata(raw_prompt), ) def build_prompt_result(raw_result: Any, prompt_name: str, server_name: str) -> MCPPromptResult: """将 MCP Prompt 获取结果转换为统一结构。 Args: raw_result: MCP SDK 返回的 Prompt 结果对象。 prompt_name: Prompt 名称。 server_name: Prompt 所属服务器名称。 Returns: MCPPromptResult: 统一 Prompt 获取结果。 """ messages: list[MCPPromptMessage] = [] raw_messages = getattr(raw_result, "messages", None) if isinstance(raw_messages, list): for raw_message in raw_messages: messages.append( MCPPromptMessage( role=str(getattr(raw_message, "role", "") or ""), content=build_tool_content_item(getattr(raw_message, "content", None)), ) ) return MCPPromptResult( prompt_name=prompt_name, server_name=server_name, description=str(getattr(raw_result, "description", "") or ""), messages=messages, metadata=_dump_model_metadata(raw_result), ) def build_resource_spec(raw_resource: Any, server_name: str) -> MCPResourceSpec: """将 MCP Resource 定义转换为统一结构。 Args: raw_resource: MCP SDK 返回的 Resource 对象。 server_name: Resource 所属服务器名称。 Returns: MCPResourceSpec: 统一 Resource 定义。 """ raw_icons = getattr(raw_resource, "icons", None) size_value = getattr(raw_resource, "size", None) size = int(size_value) if isinstance(size_value, int | float) else None return MCPResourceSpec( uri=str(getattr(raw_resource, "uri", "") or ""), server_name=server_name, name=str(getattr(raw_resource, "name", "") or ""), title=str(getattr(raw_resource, "title", "") or ""), description=str(getattr(raw_resource, "description", "") or ""), mime_type=str(getattr(raw_resource, "mimeType", "") or ""), size=size, icons=[build_tool_icon(item) for item in raw_icons] if isinstance(raw_icons, list) else [], annotation=build_tool_annotation(getattr(raw_resource, "annotations", None)), metadata=_dump_model_metadata(raw_resource), ) def build_resource_template_spec(raw_template: Any, server_name: str) -> MCPResourceTemplateSpec: """将 MCP Resource Template 定义转换为统一结构。 Args: raw_template: MCP SDK 返回的 ResourceTemplate 对象。 server_name: 模板所属服务器名称。 Returns: MCPResourceTemplateSpec: 统一模板定义。 """ raw_icons = getattr(raw_template, "icons", None) return MCPResourceTemplateSpec( uri_template=str(getattr(raw_template, "uriTemplate", "") or ""), server_name=server_name, name=str(getattr(raw_template, "name", "") or ""), title=str(getattr(raw_template, "title", "") or ""), description=str(getattr(raw_template, "description", "") or ""), mime_type=str(getattr(raw_template, "mimeType", "") or ""), icons=[build_tool_icon(item) for item in raw_icons] if isinstance(raw_icons, list) else [], annotation=build_tool_annotation(getattr(raw_template, "annotations", None)), metadata=_dump_model_metadata(raw_template), ) def build_resource_read_result(raw_result: Any, uri: str, server_name: str) -> MCPResourceReadResult: """将 MCP Resource 读取结果转换为统一结构。 Args: raw_result: MCP SDK 返回的读取结果对象。 uri: 被读取的资源 URI。 server_name: 资源所属服务器名称。 Returns: MCPResourceReadResult: 统一资源读取结果。 """ contents: list[ToolContentItem] = [] raw_contents = getattr(raw_result, "contents", None) if isinstance(raw_contents, list): for raw_content in raw_contents: metadata = _dump_model_metadata(raw_content) contents.append( ToolContentItem( content_type="resource", text=str(getattr(raw_content, "text", "") or ""), data=str(getattr(raw_content, "blob", "") or ""), mime_type=str(getattr(raw_content, "mimeType", "") or ""), uri=str(getattr(raw_content, "uri", "") or uri), annotation=None, metadata=metadata, ) ) return MCPResourceReadResult( uri=uri, server_name=server_name, contents=contents, metadata=_dump_model_metadata(raw_result), )