Files
agent-craft/m11_mcp_advanced/transports/stdio.py
T

89 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from contextlib import AsyncExitStack
from typing import Optional,Literal
from mcp import ClientSession,StdioServerParameters
from mcp.client.stdio import stdio_client
class StdioMCPTransport:
def __init__(self,command:str=None,args:list[str]=None,env:dict=None):
"""
MCP 客户端 - 支持 stdio 与 HTTP 两种传输方式
:param transport: 传输模式 "stdio" / "http"
:param command: stdio 模式的命令 (如npx
:param args: stdio 模式的参数
:param env: stdio 模式的环境变量
"""
# MCP启动方式(npx/uvx/python -m xxx)
self.params = StdioServerParameters(command=command,args=args,env=env)
# 工程核心:资源栈
self.exit_stack = AsyncExitStack()
# 连接会话(长连接)
self.session:Optional[ClientSession]=None
async def connect(self):
"""建立MCP长连接(一次连接,多次调用)"""
if self.session:
return # 已连接无需重复
# 进入transport(读/写管道)
transport = await self.exit_stack.enter_async_context(
stdio_client(self.params)
)
# 创建JSON-RPC对话
self.session = await self.exit_stack.enter_async_context(
ClientSession(transport[0],transport[1])
)
# 等待MCP服务器返回工具清单
await self.session.initialize()
async def list_tools(self):
"""查询工具列表,为LLM建立上下文用"""
if not self.session:
raise RuntimeError("未连接,请先 connect()")
result = await self.session.list_tools()
# 🔍 调试:打印工具的完整信息,确认工具是否被正确封装
# if result.tools:
# import json
# # 使用 model_dump() (Pydantic v2) 或 dict() (v1) 查看原始数据
# first_tool = result.tools[0]
# print(f"\n🔍 [DEBUG] 原始工具数据: {first_tool}\n")
# 转为纯字典,LLM能读
return[
{
"name":tool.name,
"description":tool.description,
"input_schema":tool.inputSchema
}
for tool in result.tools
]
async def call_tool(self,name:str,args:dict):
"""调用工具(工程化:加上防御性处理)"""
if not self.session:
raise RuntimeError("未连接,请先connect()")
result = await self.session.call_tool(name,args)
# 有些工具可能执行成功但无文本返回
if hasattr(result,"content") and result.content:
return result.content[0].text
return "工具执行成功,但无文本返回"
async def cleanup(self):
"""关闭MCP服务、会话和transport"""
if self.session:
await self.exit_stack.aclose()
self.session = None