:MCP 使用教程之 MCP 工具动态注册到 LangGraph 主管智能体)
AI智能体项目实战记录22MCP 使用教程之 MCP 工具动态注册到 LangGraph 主管智能体0. 写在前面上一篇已经完成 MCP 客户端和两个调试接口stdio本地文件系统 MCP ServerStreamable HTTP远程 MCP ServerGET /mcp/v1/tools、POST /mcp/v1/call可以先验证工具发现和调用。但主管智能体还不知道这些工具的存在。本文解决最后一公里把 MCP 工具动态注册到主管的候选工具列表把 MCP 调用接入现有工具执行器用“读取工作目录文档”和“查询 GitHub 文档”两个例子跑通完整链路。决策stdio / HTTP工具结果总结用户提问LangGraph 主管MCP 工具 mcp_*MCP Client外部 MCP Server前置条件已完成第 21 篇的 MCP 客户端.env中MCP_ENABLEDtrue至少一种 transport 可用stdio 或 streamable_http。1. 整体思路主管图现有的工具来自app/graph/agent_descriptions.py的ALL_TOOLS/TOOL_MAP给提示词用app/graph/supervisor_graph.py的_TOOL_INSTANCES给执行器用。MCP 工具动态注册时同时写这三处MCP tools/list → 工具名加前缀 mcp_ → _TOOL_INSTANCES 注册执行包装 → ALL_TOOLS 追加 ToolDescription → TOOL_MAP 建立名称映射首次进入主管节点时懒加载失败不影响原有工具。2. MCP 工具包装app/mcp/tool.pyclassMCPTool:通用 MCP 工具包装参数原样透传给 MCP Serverdef__init__(self,server_tool_name:str):self.server_tool_nameserver_tool_namedef_run(self,arguments:Dict[str,Any])-str:resultMCPClient.from_settings().call_tool(self.server_tool_name,argumentsor{})returnjson.dumps(result,ensure_asciiFalse)文件读取便捷工具classMCPWorkspaceFileTool:读取 MCP 工作目录下文件的便捷工具底层调用 read_text_filedef_run(self,arguments:Dict[str,Any])-str:file_namestr(arguments.get(file_name)orarguments.get(path)or).strip()ifnotfile_name:return错误缺少参数 file_nameworkspaceworkspace_dir()pathfile_nameifos.path.isabs(file_name)elseos.path.join(workspace,file_name)pathos.path.abspath(path)ifpath!workspaceandnotpath.startswith(workspaceos.sep):return错误文件必须位于 MCP 工作目录内resultMCPClient.from_settings().call_tool(read_text_file,{path:path})returnjson.dumps(result,ensure_asciiFalse)3. 动态注册_mcp_tools_loadedFalse_mcp_tools_lockthreading.Lock()def_ensure_mcp_tools()-None:global_mcp_tools_loadedif_mcp_tools_loadedornotsetting.mcp_enabled:returnwith_mcp_tools_lock:if_mcp_tools_loaded:returntry:clientMCPClient.from_settings()toolsclient.list_tools()exceptExceptionase:logger.warning(f[mcp] 加载 MCP 工具失败本次对话不注册 MCP 工具:{e})returnregistered0tool_namesset()foritemintools:raw_nameitem.get(name)ifnotraw_name:continuetool_names.add(raw_name)namefmcp_{raw_name}ifnamein_TOOL_INSTANCES:continuedescriptionitem.get(description)orfMCP 工具{raw_name}ifraw_nameread_text_file:descriptionf\n文件系统工作目录{workspace_dir()}descToolDescription(namename,display_namefMCP ·{raw_name},descriptiondescription,applicable_scenarios[description],input_descriptionjson.dumps(item.get(inputSchema)or{},ensure_asciiFalse),)_TOOL_INSTANCES[name]MCPTool(raw_name)ALL_TOOLS.append(desc)TOOL_MAP[name]desc registered1ifread_text_fileintool_namesandmcp_read_workspace_filenotin_TOOL_INSTANCES:_TOOL_INSTANCES[mcp_read_workspace_file]MCPWorkspaceFileTool()descToolDescription(namemcp_read_workspace_file,display_nameMCP 读取工作目录文档,descriptionf读取 MCP 工作目录{workspace_dir()}下的文档内容。,applicable_scenarios[用户要求读取 mcp_workspace 目录下的文档,用户要求总结工作目录中的某个文件,],input_description文件名参数名 file_name,)ALL_TOOLS.append(desc)TOOL_MAP[mcp_read_workspace_file]desc registered1_mcp_tools_loadedTruelogger.info(f[mcp] 已注册{registered}个 MCP 工具 (transport{client.transport}))4. 执行分支_execute_single_tool增加iftool_name.startswith(mcp_):resulttool._run(tool_args)eliftool_namesearch_calendar:...MCP 工具的参数直接按inputSchema透传不再走各工具自己的参数名适配。5. 主管节点调用在_node_supervisor构建提示词之前调用_ensure_mcp_tools()selection_promptget_tool_selection_prompt(tool_results,skill)这样第一次对话会自动拉取 MCP 工具并追加到提示词后续对话走缓存。6. 示例一读取工作目录文档stdio步骤 1切到 stdioMCP_TRANSPORTstdio MCP_ENABLEDtrue步骤 2把上一篇文档复制到工作目录PowerShellCopy-Iteme:\myProject\my-langchain\agent\docs\AI智能体项目实战记录20项目鉴权优化依赖注入 Redis 登录态缓存.mde:\myProject\my-langchain\agent\chat_py_server\mcp_workspace\CMDcopy e:\myProject\my-langchain\agent\docs\AI智能体项目实战记录20项目鉴权优化依赖注入 Redis 登录态缓存.md e:\myProject\my-langchain\agent\chat_py_server\mcp_workspace\步骤 3重启后端进入聊天页面提问请读取 mcp_workspace 下的《AI智能体项目实战记录20项目鉴权优化依赖注入 Redis 登录态缓存.md》 总结鉴权优化的核心思路和代码改动。主管预期输出{tool_calls:[{name:mcp_read_workspace_file,args:{file_name:AI智能体项目实战记录20项目鉴权优化依赖注入 Redis 登录态缓存.md}}],reasoning:需要读取工作目录中的文档内容}执行后read_text_file返回文档内容主管再统一总结。实测tools/list 返回 14 个工具 已注册 15 个 MCP 工具含 mcp_read_workspace_file mcp_read_workspace_file({file_name:hello.txt}) → {isError: false, content: [{type:text,text:hello mcp}]}7. 示例二查询 GitHub 文档Streamable HTTPMCP_TRANSPORTstreamable_http MCP_HTTP_URLhttps://mcp.deepwiki.com/mcp提问查一下 langchain-ai/langgraph 的文档目录结构。主管预期调用{tool_calls:[{name:mcp_read_wiki_structure,args:{repoName:langchain-ai/langgraph}}]}实测已注册 3 个 MCP 工具 (transportstreamable_http) mcp_read_wiki_structure 执行成功返回 LangGraph 文档目录8. 日志与调用记录MCP 工具调用会进入现有的日志切面[mcp] 已注册 15 个 MCP 工具 (transportstdio) 工具 [mcp_read_workspace_file] 执行成功ai_tool_invoke_log会记录工具名、参数、耗时、结果ai_invoke_log仍按主管/子智能体行记录。注意MCP 工具没有对应的ai_child_agent记录时CHILD_AGENT_ID_会为空这是正常的。9. 配置提醒9.1 stdioMCP_ENABLEDtrue MCP_TRANSPORTstdio MCP_SERVER_COMMANDnpx.cmd MCP_SERVER_ARGS[-y,modelcontextprotocol/server-filesystem,{workspace}] MCP_WORKSPACE_DIRmcp_workspace9.2 Streamable HTTPMCP_ENABLEDtrue MCP_TRANSPORTstreamable_http MCP_HTTP_URLhttps://mcp.deepwiki.com/mcp MCP_HTTP_HEADERS{} MCP_HTTP_PROTOCOL_VERSION2025-03-26客户端实现细节见第 21 篇本文只关注“如何注册进主管图”。10. 常见问题Q1启动日志没有“已注册 MCP 工具”检查MCP_ENABLED是否为trueMCP Server 是否可启动/可访问日志中是否有[mcp] 加载 MCP 工具失败是否已经进入过一次主管节点工具是懒加载的。Q2MCP 工具没有被主管选中检查get_tool_selection_prompt中是否出现mcp_*工具工具描述是否清楚用户问题是否明确需要该工具工具数量是否过多导致提示词过长、模型选择困难。Q3读取文件提示“文件必须位于 MCP 工作目录内”mcp_read_workspace_file只允许读取mcp_workspace下的文件防止../逃逸。把文件复制到该目录即可。Q4远程 Server 需要登录怎么办在.env中配置MCP_HTTP_HEADERS{Authorization:Bearer token}不要把 token 写进工具参数。Q5MCP 工具太多提示词太长怎么办可以后续增加工具白名单只注册需要的工具当前实现会注册tools/list返回的全部工具。Q6MCP 工具调用会计入 token 吗MCP 工具本身不调用大模型不产生 token主管决策和最终总结仍然计入现有 token 统计。11. 小结MCP 工具注册不需要修改主管的决策逻辑只需要扩展ALL_TOOLS、TOOL_MAP和_TOOL_INSTANCES工具名统一加mcp_前缀避免和本地工具重名首次进入主管节点时懒加载失败不影响原有对话_execute_single_tool用mcp_前缀分发参数按 JSON Schema 原样透传mcp_read_workspace_file把“读取工作目录文档”变成模型容易调用的工具生产环境要限制 MCP Server、工作目录、超时和鉴权 Header避免把任意本地命令暴露给模型。