"""MCP 工具错误处理演示（无需 API key）。

架构要点：MCP 的 tool 错误是「对话的一部分」，不是崩溃——
- server 端 `ToolError` → client 端正常返回 `is_error=True`，错误文本进入 content，
  让模型读到错误后能自我修正（对应模块 04/05 的 repair retry 回路）；
- 未知工具名同样返回 is_error=True，而不是抛异常；
- host 侧务必先看 `is_error` 再信任 `structured_content`。

来源: https://py.sdk.modelcontextprotocol.io/client/ （访问于 2026-08-30）

运行：
    uv run --with mcp python 02_tool_error_handling.py
"""

import asyncio

from mcp import Client
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError


mcp = MCPServer("inventory")


@mcp.tool()
def get_stock(sku: str) -> dict:
    """Return stock info for a SKU. Raises ToolError for unknown SKUs."""
    if sku != "SKU-1":
        # 业务错误：显式抛 ToolError，把可读的修复线索写进消息
        raise ToolError(f"No SKU named {sku!r}. Known SKUs: ['SKU-1'].")
    return {"sku": sku, "qty": 42, "warehouse": "BER-1"}


def report(label: str, result) -> None:
    print(f"[{label}]")
    print("  is_error        :", result.is_error)
    print("  content         :", [getattr(b, "text", b) for b in result.content])
    print("  structured      :", result.structured_content)
    print()


async def main() -> None:
    async with Client(mcp) as client:
        # 1) 成功路径：structured_content 给下游代码用
        ok = await client.call_tool("get_stock", {"sku": "SKU-1"})
        report("成功调用", ok)

        # 2) 业务错误：ToolError → is_error=True，错误文本进 content（模型可读可修复）
        biz = await client.call_tool("get_stock", {"sku": "SKU-404"})
        report("业务错误 ToolError", biz)

        # 3) 协议级错误：调一个不存在的工具
        missing = await client.call_tool("does_not_exist", {})
        report("未知工具", missing)

        # 4) host 的修复式重试（repair retry）：把错误信息回喂给「模型」，
        #    模型据 ToolError 消息修正参数后重试 —— 这里直接模拟修正后的重试
        retry = await client.call_tool("get_stock", {"sku": "SKU-1"})
        report("修复后重试", retry)
        print("架构结论：先查 is_error，再把错误文本交回模型上下文，由其自我修正。")


if __name__ == "__main__":
    asyncio.run(main())
