1. 为什么 Agent 直连数仓总在“最后一公里”翻车
Apache Doris 在实时数据分析圈子里已经不算新面孔,MPP 架构、列式存储、Merge-on-Write 这些特性让它在宽表聚合和秒级写入场景里表现稳定。但真正把 Agent 接进来的时候,很多人会发现一个尴尬的现实:模型能写 SQL,却不知道怎么安全地拿到表结构;能生成查询,却没法把结果稳定地回传给上层应用。MCP(Model Context Protocol)要解决的正是这段“最后一公里”——它把数据库能力包装成模型可发现、可调用的工具,让 Agent 不用硬编码就能查询 Doris。
这篇文章面向的是想让智能体直接查询数仓的开发者。我会从实际接入的角度出发,给出 MCP Server 连接 Doris 的可复制配置、Agent 调用链路的完整示例,以及查询延迟和结果正确性的验证动作。你不需要先成为 MCP 协议专家,只要手里有一个能跑的 Doris 实例和基本的 Python 环境,就能跟着把这条链路搭起来。
核心检索词先明确:Apache Doris 提供实时分析底座,MCP 提供标准化工具接口,Agent 负责编排调用。三者组合起来,才是可复现的智能化分析底座。适合谁?做数据平台的后端、做 AI 应用的工程师、以及想把 BI 查询能力开放给智能体的团队。不适合谁?只想跑单条 SQL 看看结果、不打算做工具封装的场景,直接用 MySQL 客户端更省事。
我试过把 Doris 的查询接口直接暴露给模型,结果模型经常编造字段名,或者在聚合函数上出错。后来换成 MCP 封装,把表结构和常用查询模板作为资源暴露出去,错误率明显下降。这不是模型变聪明了,而是约束变清晰了。
2. TaoToken 前置:把模型调用和工具调用拆开看
在搭 Doris + MCP 之前,需要先把模型侧的调用通道准备好。很多教程会把模型接入和工具接入混在一起讲,导致排障时分不清是模型没响应还是 MCP Server 没起来。我的建议是拆成两层:模型层用 TaoToken 提供的统一接口,工具层用本地或远程的 MCP Server。
TaoToken 的 API 地址是 https://taotoken.net/api,它兼容常见的模型调用格式,你可以在模型对话页面先验证模型是否能正常返回。这一步的意义在于:当 Agent 调用 Doris 失败时,你能快速判断是模型侧的问题还是工具侧的问题。如果模型对话都返回不了,那后面 MCP 的调试就是白费力气。
具体操作上,先到官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 了解接入方式,然后在控制台创建 API Key。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite,API Keys 管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite。拿到 Key 之后,先别急着写 Agent,用模型对话页面发一条简单消息,确认通道是通的。
这里有个容易踩的坑:有人把 MCP Server 的地址填成模型 API 的地址,结果两边都报错。记住,TaoToken 的 API 是给模型用的,MCP Server 是你自己部署的、用来连接 Doris 的服务,两者是独立的。Agent 的工作是把模型输出转换成 MCP 工具调用,再把工具返回的结果喂回模型。
如果你打算长期跑编码类或 Agent 类任务,可以关注 Coding Plan 页面 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite,它更适合持续性的开发场景。但本文的重点是 Doris 查询链路,模型侧只要保证能稳定调用即可。
接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,遇到请求格式问题可以先查这里。Claude Code 相关的接入在 https://taotoken.net/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=ClaudeCodeAnthropic&utm_campaign=rewrite,如果你用 Claude Code 作为 Agent 宿主,可以参考这个页面配置。
3. 可复制配置:Doris MCP Server 的 settings 与工具定义
这一节给出可以直接复制修改的配置片段。先说明目录结构,避免路径对不上。假设你的工作目录是~/doris-mcp,里面放一个server.py和一个settings.json。Doris 的连接信息通过环境变量注入,不要硬编码在代码里。
先看settings.json,这是 MCP Server 的配置文件,路径与原文一致:
{ "mcpServers": { "doris-analytics": { "command": "python", "args": ["/home/yourname/doris-mcp/server.py"], "env": { "DORIS_HOST": "127.0.0.1", "DORIS_PORT": "9030", "DORIS_USER": "root", "DORIS_PASSWORD": "", "DORIS_DATABASE": "analytics", "MCP_SERVER_PORT": "8321" } } } }这个文件的作用是告诉 Agent 宿主:有一个叫doris-analytics的 MCP Server,用 Python 启动,环境变量里带着 Doris 的连接信息。注意args里的路径要换成你机器上的真实路径,Windows 下用反斜杠或双引号转义。
接下来是server.py的核心部分。这里用官方的 MCP Python SDK 来定义工具,工具名是doris_query,参数是一个 SQL 字符串。为了让模型知道有哪些表可用,再定义一个资源doris://schema,返回表结构摘要。
import os import pymysql from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent, Resource app = Server("doris-analytics") def get_conn(): return pymysql.connect( host=os.environ["DORIS_HOST"], port=int(os.environ["DORIS_PORT"]), user=os.environ["DORIS_USER"], password=os.environ["DORIS_PASSWORD"], database=os.environ["DORIS_DATABASE"], charset="utf8mb4", cursorclass=pymysql.cursors.DictCursor, ) @app.list_tools() async def list_tools(): return [ Tool( name="doris_query", description="在 Apache Doris 上执行只读 SQL 查询,返回 JSON 结果", inputSchema={ "type": "object", "properties": { "sql": {"type": "string", "description": "SELECT 语句"} }, "required": ["sql"], }, ) ] @app.call_tool() async def call_tool(name, arguments): if name != "doris_query": raise ValueError(f"unknown tool: {name}") sql = arguments["sql"].strip() if not sql.lower().startswith("select"): return [TextContent(type="text", text="仅允许 SELECT 查询")] conn = get_conn() try: with conn.cursor() as cur: cur.execute(sql) rows = cur.fetchall() return [TextContent(type="text", text=str(rows))] finally: conn.close() @app.list_resources() async def list_resources(): return [Resource(uri="doris://schema", name="Doris 表结构", mimeType="text/plain")] @app.read_resource() async def read_resource(uri): if str(uri) != "doris://schema": raise ValueError("unknown resource") conn = get_conn() try: with conn.cursor() as cur: cur.execute("SHOW TABLES") tables = [list(r.values())[0] for r in cur.fetchall()] lines = [f"数据库 {os.environ['DORIS_DATABASE']} 下的表:"] lines.extend(tables) return "\n".join(lines) finally: conn.close() async def main(): async with stdio_server() as (read, write): await app.run(read, write, app.create_initialization_options()) if __name__ == "__main__": import asyncio asyncio.run(main())这段代码里有两个关键点。第一,工具只允许 SELECT,避免 Agent 误删数据。第二,资源doris://schema让模型在生成 SQL 前能先拉取表名,减少编造字段的概率。实际部署时,你可以把SHOW TABLES换成查询information_schema.columns,返回字段名和类型,效果更好。
依赖安装命令:
pip install mcp pymysql如果你用的是 Cline 或 CC Switch 这类支持 MCP 的客户端,把settings.json放到对应的配置目录即可。Cline 的 MCP 配置通常在设置界面里粘贴 JSON,CC Switch 则读取本地配置文件。无论哪种,Base URL、Key、Model ID 三件套要写全:Base URL 用https://taotoken.net/api,Key 用控制台生成的,Model ID 按你选的模型填。这三项缺一个,Agent 就调不起来。
4. 验证请求:从 Agent 发起到 Doris 返回的完整链路
配置写完之后,不要直接上复杂查询,先用一条最简单的 SQL 验证链路。启动 MCP Server 后,在 Agent 宿主里发一条消息:“帮我查一下 analytics 库里的表”。Agent 应该先调用doris://schema资源,拿到表名列表,再决定是否执行查询。
如果你想手动验证 MCP Server 是否正常,可以用 stdio 方式直接调用。下面是一个最小客户端示例:
import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): params = StdioServerParameters( command="python", args=["/home/yourname/doris-mcp/server.py"], env={ "DORIS_HOST": "127.0.0.1", "DORIS_PORT": "9030", "DORIS_USER": "root", "DORIS_PASSWORD": "", "DORIS_DATABASE": "analytics", }, ) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() print("可用工具:", [t.name for t in tools.tools]) result = await session.call_tool( "doris_query", {"sql": "SELECT COUNT(*) AS cnt FROM orders"} ) print("查询结果:", result.content[0].text) asyncio.run(main())运行后如果看到类似查询结果: [{'cnt': 12843}]的输出,说明 Doris 连接和 MCP 工具调用都正常。这一步的延迟主要来自 Doris 查询本身,MCP 层的开销通常在毫秒级。你可以用time命令包一下,记录从调用到返回的耗时。
在 Agent 侧,调用链路是这样的:用户提问 → 模型判断需要查数 → 模型输出工具调用请求 → Agent 宿主转发给 MCP Server → MCP Server 执行 SQL → 结果回传模型 → 模型生成自然语言回答。任何一环断了,表现都是“Agent 没反应”或“回答里没有数据”。所以验证时要分段确认:先确认模型能回消息,再确认 MCP Server 能列工具,最后确认 SQL 能返回结果。
结果正确性方面,建议拿一条你已知答案的 SQL 做对照。比如SELECT COUNT(*) FROM orders WHERE dt = '2024-01-01',先在 MySQL 客户端里跑一遍,记下数字,再让 Agent 跑同样的查询。如果数字一致,说明链路没有丢数据或改条件。如果 Agent 返回的数字不对,优先检查模型是否改写了 SQL,可以在 MCP Server 里加日志,把实际执行的 SQL 打印出来。
5. 常见错排查:401、local proxy failed 与 reading choices
这一节对照真实报错,给出排查路径。第一个高频错误是401 Unauthorized。如果你在 Agent 侧看到这个,通常是 TaoToken 的 API Key 没填对或过期了。检查settings.json或宿主配置里的 Key 是否和控制台一致,注意不要有多余空格。如果 MCP Server 侧报 401,那是 Doris 的账号密码问题,检查DORIS_USER和DORIS_PASSWORD。
第二个错误是local proxy failed。这个报错通常出现在 Agent 宿主尝试连接 MCP Server 时,原因可能是server.py路径写错、Python 环境不对、或者端口被占用。排查步骤:先在终端手动运行python /home/yourname/doris-mcp/server.py,看是否报错;再用ps aux | grep server.py确认进程是否活着;最后检查settings.json里的args路径是否和实际一致。Windows 下路径要用双反斜杠或正斜杠。
第三个错误是reading choices相关的解析失败。这通常发生在模型返回格式不符合预期时,比如模型把工具调用写成了普通文本,或者返回了多个 choice 但 Agent 只解析了第一个。解决方法是检查模型输出日志,确认工具调用的 JSON 结构是否完整。如果用的是 TaoToken 的模型对话接口,可以先在模型对话页面测试同样的提示词,看返回是否稳定。
第四个错误是 OAuth 相关的认证失败。MCP 协议支持 OAuth 2.0,但本地开发时通常用 stdio 或简单 token 就够了。如果你在远程 MCP Server 上启用了 OAuth,确保客户端配置里的 token 端点、client_id、client_secret 都正确。本地场景下,建议先用 stdio 跑通,再考虑远程部署。
还有一个隐蔽的坑:Doris 的SHOW TABLES返回的字段名在不同版本里可能不一样,有的返回Tables_in_analytics,有的返回Table_name。如果你的资源读取代码写死了字段名,换版本就会报 KeyError。用list(r.values())[0]取第一个值更稳妥。
排查时记住一个原则:先隔离,再定位。把模型调用和 MCP 调用分开测,把 Doris 查询和 MCP 封装分开测。哪一层单独跑不通,问题就在哪一层。不要一上来就怀疑协议不兼容,大多数问题都是配置路径或认证信息写错了。
6. 把这条链路用起来:从验证到日常查询
链路跑通之后,你可以把常用的查询封装成更具体的工具,而不是只暴露一个通用doris_query。比如定义doris_daily_gmv,参数是日期,内部固定 SQL 模板。这样模型不需要生成复杂 SQL,只需要填参数,出错概率更低。工具描述里写清楚每个参数的格式和含义,模型就能更准确地调用。
对于长期运行的 Agent 场景,建议把 MCP Server 做成常驻进程,而不是每次调用都启动。可以用 systemd 或 supervisor 管理,日志输出到文件,方便排查。Doris 侧则要注意连接池和查询超时,避免 Agent 发起大查询把连接占满。在pymysql.connect里加上read_timeout和connect_timeout,给查询设一个上限。
如果你需要更完整的接入示例和配置模板,可以到接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 查看。模型对话验证在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite,API Keys 管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite。长期编码和 Agent 任务可以看 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite。
最后说一个实用技巧:在 MCP Server 里加一个查询耗时统计,每次调用后把 SQL 和执行时间写到日志。跑一段时间后,你会清楚哪些查询是 Agent 高频发起的,哪些表被扫得最多。这些数据反过来可以指导你优化 Doris 的索引和分桶,让整条链路越跑越快。