list_mcp_tools.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. # -*- coding: utf-8 -*-
  2. """
  3. MCP 远程工具列表查询 —— 格式化输出所有可用的 MCP 工具及参数签名
  4. """
  5. import asyncio
  6. import shutil
  7. from fastmcp import Client
  8. # ── 终端宽度 ──
  9. WIDTH = min(shutil.get_terminal_size().columns, 120)
  10. # ── 工具分类定义(按名称前缀归类)──
  11. CATEGORY_MAP = {
  12. "vent": "🌬️ 通风监测",
  13. "device": "🔧 设备管理",
  14. "tunnel": "🛤️ 巷道信息",
  15. "mine": "⛏️ 煤矿基础",
  16. "needq": "📊 需风量计算",
  17. }
  18. def _category(name: str) -> str:
  19. for prefix, label in CATEGORY_MAP.items():
  20. if name.startswith(prefix):
  21. return label
  22. return "📦 其他"
  23. def parse_tool_params(schema: dict) -> list[dict]:
  24. """解析 inputSchema 为参数列表"""
  25. params = []
  26. required = schema.get("required", [])
  27. props = schema.get("properties", {})
  28. for name, info in props.items():
  29. params.append({
  30. "name": name,
  31. "type": info.get("type", "—"),
  32. "desc": (info.get("description", "") or "")[:120],
  33. "required": name in required,
  34. })
  35. return params
  36. # ── 主入口 ──
  37. MCP_URL = "http://39.97.59.228:8071/mcp"
  38. client = Client(MCP_URL)
  39. async def list_all_mcp_tools_tcp():
  40. """TCP 远程 MCP 客户端查询工具列表"""
  41. async with client:
  42. tools = await client.list_tools()
  43. # ══════════════════════════════════════════════
  44. # 头部
  45. # ══════════════════════════════════════════════
  46. print()
  47. print("╔" + "═" * (WIDTH - 2) + "╗")
  48. print("║" + f" 🔌 MCP 远程工具列表 | {MCP_URL} | 共 {len(tools)} 个工具".center(WIDTH - 2) + "║")
  49. print("╚" + "═" * (WIDTH - 2) + "╝")
  50. print()
  51. # 按类别分组
  52. groups: dict[str, list] = {}
  53. for t in tools:
  54. cat = _category(t.name)
  55. groups.setdefault(cat, []).append(t)
  56. order = ["🌬️ 通风监测", "🛤️ 巷道信息", "🔧 设备管理", "📊 需风量计算", "⛏️ 煤矿基础", "📦 其他"]
  57. index = 0
  58. for cat in order:
  59. if cat not in groups:
  60. continue
  61. group_tools = groups[cat]
  62. print(f" {cat}({len(group_tools)} 个)")
  63. print(f" {'─' * (WIDTH - 6)}")
  64. for t in group_tools:
  65. index += 1
  66. # ── 工具名 + 序号 ──
  67. print(f"\n ┌─ [{index:02d}] {t.name} {'─' * max(WIDTH - len(t.name) - 15, 0)}")
  68. # ── 描述 ──
  69. desc = (t.description or "").strip()
  70. if desc:
  71. # 自动换行
  72. for line in _wrap(desc, WIDTH - 8):
  73. print(f" │ 📝 {line}")
  74. # ── 参数表 ──
  75. params = parse_tool_params(t.inputSchema)
  76. if params:
  77. print(f" │ ┌ 参数{'─' * (WIDTH - 13)}")
  78. for p in params:
  79. flag = "🔴" if p["required"] else "🟢"
  80. print(f" │ │ {flag} {p['name']}: {p['type']}")
  81. if p["desc"]:
  82. for line in _wrap(p["desc"], WIDTH - 12):
  83. print(f" │ │ {line}")
  84. print(f" │ └{'─' * (WIDTH - 9)}")
  85. else:
  86. print(f" │ (无参数)")
  87. print(f" └{'─' * (WIDTH - 6)}")
  88. print()
  89. # ══════════════════════════════════════════════
  90. # 底部统计
  91. # ══════════════════════════════════════════════
  92. total_params = sum(
  93. len(parse_tool_params(t.inputSchema)) for t in tools
  94. )
  95. print("╔" + "═" * (WIDTH - 2) + "╗")
  96. print("║" + f" ✅ 总计 {len(tools)} 个工具,{total_params} 个参数,{len(groups)} 个分类".ljust(WIDTH - 2) + "║")
  97. print("╚" + "═" * (WIDTH - 2) + "╝")
  98. print()
  99. def _wrap(text: str, width: int) -> list[str]:
  100. """按宽度自动换行,尊重中文宽度"""
  101. lines = []
  102. while len(text) > width:
  103. # 尝试在空格处断行
  104. cut = text.rfind(" ", 0, width)
  105. if cut == -1:
  106. cut = width
  107. lines.append(text[:cut].strip())
  108. text = text[cut:].strip()
  109. if text:
  110. lines.append(text)
  111. return lines
  112. asyncio.run(list_all_mcp_tools_tcp())