1. 为什么工具系统总在“最后一公里”掉链子RAGFlow 的工具系统与 MCP 集成说白了就是让大模型从“只会聊天”变成“能动手干活”。Function Calling 负责让模型表达“我想调用哪个工具、传什么参数”MCP 负责把外部工具服务器动态挂进来而工具系统本身负责注册、校验、执行、回传结果。三者串起来才是一条完整的动态工具扩展链路。但真正动手时问题往往不在架构图而在配置。RAGFlow 的 Agent 画布能识别工具可模型请求要发出去、工具调用要收回来中间必须有一条稳定的 API 通道。很多同学卡在这里本地工具能跑一接远程模型就报 401MCP 服务器连上了工具列表却拉不下来config.toml 和 settings.json 两个文件到底谁管谁改错一个就整条链路失效。这篇是 RAGFlow 系列教程第 18 课的实操版聚焦工具系统与 MCP 集成场景把 Function Calling 到动态工具扩展的链路拆开。我会给出 TaoToken 统一 Key/API 通道在 config.toml 与 settings.json 中的可复制配置骨架再演示一次工具注册与调用验证动作。适合已经在用 RAGFlow Agent、想接 MCP 做动态工具扩展、但被配置和报错卡住的开发者。读完你能跑通一条最小可用的动态工具扩展流程而不是只停留在看架构图。2. TaoToken 在工具链路里扮演什么角色RAGFlow 的工具调用链路里模型是决策者工具是执行者而模型请求必须走一个兼容 OpenAI Function Calling 格式的 API 端点。TaoToken 在这里提供的就是这条统一通道一个 Key、一个 API 地址同时覆盖模型对话、Coding Plan、控制台和 API Keys 管理。对 RAGFlow 来说它就是一个标准的 OpenAI 兼容端点工具系统生成的 function 定义能直接被模型消费。先把入口理清楚后面配置才不会乱官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基址https://taotoken.net/api 这个不加 UTM直接填进配置模型对话https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_campaignrewriteCoding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_campaignrewriteAPI Keyshttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_campaignrewriteClaudeCodeAnthropichttps://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_campaignrewrite注意RAGFlow 里模型配置和工具配置是两套东西。模型走的是 LLM API工具走的是工具自身的 API Key比如 Tavily或 MCP 服务器地址。TaoToken 解决的是前者别把两者混在一个字段里。我试过把 TaoToken 的 Key 同时用在模型对话和 Coding Plan 场景RAGFlow 侧只需要保证 base_url 指向https://taotoken.net/api模型名填控制台里可用的即可。工具系统那边Function Calling 的格式转换由 RAGFlow 的get_meta()自动完成你不需要手写 OpenAI 的 function schema。3. config.toml 与 settings.json 的可复制配置骨架RAGFlow 的配置分两层config.toml管服务级参数settings.json管运行时模型和工具相关设置。工具系统与 MCP 集成要动的主要是这两处。下面给的是骨架字段名以你本地版本为准重点是结构和位置。3.1 config.toml 里的模型与沙箱段# config.toml 片段模型通道 沙箱执行 [llm] base_url https://taotoken.net/api api_key sk-你的TaoTokenKey model 你的可用模型名 timeout 120 [sandbox] # CodeExec 工具依赖沙箱默认走自管理 Docker host 127.0.0.1 port 9385 provider self_managed exec_timeout 600 [mcp] # MCP 会话相关SSE 与 Streamable HTTP 双传输 enable true default_timeout 10 max_sessions 8这里[llm]段是工具调用能发出去的前提。base_url必须是https://taotoken.net/api不要带尾部斜杠也不要带 UTM 参数。api_key从 API Keys 页面拿。[sandbox]段对应 CodeExec 工具如果你暂时不跑代码执行可以先不启用但 MCP 工具里如果有代码类工具沙箱必须通。3.2 settings.json 里的工具与 MCP 注册{ llm: { base_url: https://taotoken.net/api, api_key: sk-你的TaoTokenKey, model: 你的可用模型名 }, tools: { enabled: [Retrieval, TavilySearch, DuckDuckGo, CodeExec], tavily: { api_key: tvly-你的TavilyKey, search_depth: basic, max_results: 6 } }, mcp_servers: [ { name: local-tools, server_type: streamable_http, url: http://127.0.0.1:8000/mcp, headers: {}, variables: {} } ] }tools.enabled决定哪些内置工具参与 Function Calling 格式转换。mcp_servers数组里每一项对应一个 MCP 服务器server_type支持sse和streamable_http两种。variables用于 URL 或 header 里的变量模板替换比如把 token 抽出来。提示config.toml和settings.json里如果都写了base_url以运行时加载的settings.json为准。改完记得重启 RAGFlow 服务热加载不一定生效。3.3 工具注册的自动发现机制RAGFlow 的工具注册靠agent/tools/__init__.py的动态导入扫描目录下所有.py文件排除__开头和base.py用inspect.getmembers()提取公开类注册进__all_classes。这意味着你新增一个工具文件只要继承ToolBase并定义好ToolParamBase放进目录就会被发现不需要手动注册。# agent/tools/my_tool.py 最小骨架 from abc import ABC from agent.tools.base import ToolMeta, ToolParamBase, ToolBase from common.connection_utils import timeout class MyToolParam(ToolParamBase): def __init__(self): self.meta: ToolMeta { name: my_tool_name, description: 工具功能描述LLM 据此决定是否调用, parameters: { param1: { type: string, description: 参数说明, required: True, }, }, } super().__init__() self.custom_config default_value def check(self): self.check_empty(self.custom_config, Custom Config) class MyTool(ToolBase, ABC): component_name MyTool timeout(60) def _invoke(self, **kwargs): query kwargs.get(param1, self._param.param1) result do_something(query) self.set_output(content, str(result)) return self.output()get_meta()会把ToolMeta转成 OpenAI Function Calling 格式MCP 工具则通过mcp_tool_metadata_to_openai_tool()做同样的转换。两条路径最终都汇到LLMToolPluginCallSession的tools_map里本地工具和 MCP 会话共存靠isinstance区分调用方式。4. 一次工具注册与调用验证动作配置写完得验证链路真的通。下面这套动作从工具列表拉取到实际调用覆盖本地工具和 MCP 工具两条路径。4.1 验证模型通道与 Function Calling 格式先确认模型能正常返回 tool_calls。用 curl 直接打 TaoToken 的 API带上一个简单的 function 定义curl -s https://taotoken.net/api/chat/completions \ -H Authorization: Bearer sk-你的TaoTokenKey \ -H Content-Type: application/json \ -d { model: 你的可用模型名, messages: [{role: user, content: 北京天气怎么样}], tools: [{ type: function, function: { name: get_weather, description: 查询指定城市天气, parameters: { type: object, properties: {city: {type: string}}, required: [city] } } }], tool_choice: auto }如果返回体里出现tool_calls字段且function.name是get_weather说明模型通道和 Function Calling 格式都没问题。这一步不通后面工具系统再对也没用。4.2 验证 MCP 工具列表拉取MCP 会话通过get_tools()同步拉取工具列表底层是asyncio.run_coroutine_threadsafe()桥接到独立事件循环。你可以在 RAGFlow 的 Python 环境里跑一段验证# 验证 MCP 工具列表 from common.mcp_tool_call_conn import MCPToolCallSession from common.mcp_tool_call_conn import mcp_tool_metadata_to_openai_tool session MCPToolCallSession( mcp_servertype(S, (), { url: http://127.0.0.1:8000/mcp, server_type: streamable_http, headers: {} })() ) tools session.get_tools(timeout10) print(MCP 工具数量:, len(tools)) for t in tools: openai_tool mcp_tool_metadata_to_openai_tool(t) print(openai_tool[function][name], -, openai_tool[function][description])正常输出会列出 MCP 服务器暴露的所有工具名和描述。如果这里报ValueError或超时先查 MCP 服务器本身是否在跑再查server_type是否和服务器实现匹配。4.3 验证一次完整工具调用工具列表拿到后走一次tool_call# 验证 MCP 工具调用 result session.tool_call( nameyour_tool_name, arguments{param1: test_value}, timeout10 ) print(调用结果:, result)本地工具的验证更直接在 Agent 画布上挂一个Retrieval或DuckDuckGo发一条会触发工具的消息看画布引用区是否出现 chunk。_retrieve_chunks()会把搜索结果统一格式化并注入画布引用这是判断工具是否真正执行成功的直观信号。4.4 成功结果的判断标准一次成功的动态工具扩展应该同时满足模型返回了正确的tool_callsLLMToolPluginCallSession.tool_call()没有抛assert name in self.tools_map工具执行结果通过callback记录到了工具名、参数、结果和耗时画布引用区或对话上下文里出现了工具返回的内容。四个都满足链路才算通。5. 本篇常见错排查5.1 401 或模型不可用最常见的是base_url写错。必须是https://taotoken.net/api不能带/v1后缀也不能带 UTM 参数。api_key确认从 API Keys 页面复制完整没有多余空格。如果config.toml和settings.json都配了检查运行时实际加载的是哪个。5.2 MCP 工具列表为空先确认 MCP 服务器进程在跑端口对得上。server_type填sse但服务器只支持streamable_http或者反过来都会导致initialize()超时。headers里如果需要鉴权确认变量模板替换后的值正确。max_sessions太小、并发拉取时也会失败。5.3 工具调用报 name does not existLLMToolPluginCallSession的tools_map里没有这个工具名。可能是工具没被自动发现文件名以__开头或叫base.py也可能是 MCP 工具列表没拉取成功。检查agent/tools/目录下的文件命名以及 MCP 会话是否在 Agent 加载时完成了get_tools()。5.4 CodeExec 沙箱超时CodeExec默认超时是COMPONENT_EXEC_TIMEOUT默认 10 分钟。如果沙箱 Provider 不可用会回退到 HTTP 请求http://SANDBOX_HOST:9385/run。确认config.toml里[sandbox]的 host 和 port 正确Docker 沙箱容器在运行。自管理 Provider 需要本地 Docker 环境。5.5 工具执行结果没进上下文工具执行了但模型没用到结果通常是callback没正确把结果附加到对话上下文。检查 Agent 组件的max_rounds默认 5 轮轮次用完就停了。另外确认工具返回格式符合预期_retrieve_chunks()之外的工具有没有正确set_output()。6. 把链路跑通之后工具系统与 MCP 集成的核心是把 Function Calling 的格式转换、MCP 的动态发现、以及统一调度这三段接起来。TaoToken 在这条链路里的位置很明确提供模型请求的统一 Key 和 API 通道让get_meta()生成的 function 定义能被模型正确消费。配置骨架给的是结构真正跑通靠的是逐段验证——先确认模型通道再确认 MCP 工具列表最后确认一次完整调用。如果你还在接入阶段建议先把 API Keys 和接入文档过一遍把 Key 和 base_url 固定下来。验证模型是否支持 Function Calling可以直接用模型对话页面发一条带 tools 的请求试。长期做编码和 Agent 扩展的话Coding Plan 那条线也值得看它和工具系统的调用场景是打通的。链路跑通一次之后后面加工具就是往目录里放文件、往mcp_servers里加一项的事。