1. 多 MCP 智能助手在 SQLite 本地场景下到底解决什么问题
多 MCP 智能助手,简单说就是让一个 AI 代理同时挂载多个 MCP(Model Context Protocol)服务,把 GitHub、搜索、日历、邮件这些外部能力当成“工具”来调用,再用一个统一的大模型做编排。它适合谁?适合已经在用 Agno 这类代理框架、想让本地助手同时操作多个外部服务、又不想为每个服务单独维护一套 Key 和通道的开发者。核心检索词就三个:MCP 负责工具标准化,Agno 负责代理编排,GPT-4o 负责理解与决策,SQLite 负责把会话记忆落在本地。
我这次聚焦的是本地 SQLite 场景:会话历史、用户记忆全部写进一个.db文件,不依赖任何远程数据库。这样做的好处是调试直观——出问题时直接打开 SQLite 看表,比翻云日志快得多。难点也很集中:多 MCP 服务注册时环境变量怎么传、Agno 的MultiMCPTools怎么和SqliteDb共存、GPT-4o 的 Key 怎么统一走一条通道而不是散落在多个.env里。
这篇会给出可复制的config.toml与settings.json骨架、多 MCP 服务注册示例,并完整演示一次端到端调用和三类高频报错的排查动作。你跟着做,能跑通一个“GitHub + 搜索 + 日历”三服务协同的本地助手。
2. 前置准备:用 TaoToken 统一 Key 与 API 通道
多 MCP 助手最烦的地方不是写代码,是 Key 管理。GitHub 一个 token、搜索一个 key、模型一个 key,每个 MCP 服务还要各自读环境变量,一旦某个服务启动时读不到就整条链路挂掉。我的做法是把模型调用这一层收敛到 TaoToken:一个 Key、一个 API 通道,兼容 OpenAI 风格的接口,Agno 里的OpenAIChat只要改base_url和api_key就能接上,不用改代理逻辑。
TaoToken 在这里扮演的是“模型网关”角色,不是替代你的编辑器或 MCP 服务。MCP 服务该用 npx 起的还是 npx 起,GitHub token 该配的还是配,只是 GPT-4o 这一层的出口统一了。这样做的直接收益:换模型、调额度、排查 401 都只在一个地方看。
你需要准备的东西:
- TaoToken 的 API Key,在控制台的 API Keys 页面创建,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
- 模型对话入口可以先在网页上验证 Key 是否可用:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
- 接入文档(OpenAI 兼容写法、base_url 规范)在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
- 如果你后面要做长期编码或 Agent 常驻,可以看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
注意:TaoToken 的 API 基址是
https://taotoken.net/api,在 Agno 里配置时不要带末尾斜杠,否则部分 OpenAI 兼容客户端会拼出双斜杠导致 404。
3. 可复制配置:config.toml 与 settings.json 骨架
Agno 本身用 Python 配置居多,但多 MCP 项目里服务注册项一多,硬编码在.py里很难维护。我习惯拆成两个文件:config.toml管 MCP 服务清单和模型参数,settings.json管运行时开关和 SQLite 路径。下面是可以直接抄的骨架。
3.1 config.toml:MCP 服务与模型参数
# config.toml [model] provider = "openai" model_id = "gpt-4o" base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" temperature = 0.3 max_tokens = 4096 [memory] db_file = "tmp/multi_mcp_agent.db" enable_user_memories = true add_history_to_context = true num_history_runs = 10 [[mcp_servers]] name = "github" command = "npx -y @modelcontextprotocol/server-github" env_keys = ["GITHUB_PERSONAL_ACCESS_TOKEN"] [[mcp_servers]] name = "perplexity" command = "npx -y @chatmcp/server-perplexity-ask" env_keys = ["PERPLEXITY_API_KEY"] [[mcp_servers]] name = "calendar" command = "npx @gongrzhe/server-calendar-autoauth-mcp" env_keys = []这里的关键设计是env_keys:每个 MCP 服务声明自己需要哪些环境变量,启动时统一从os.environ注入,避免某个服务因为读不到变量而静默失败。base_url指向 TaoToken 的 API 通道,api_key_env只存变量名不存明文,Key 本身放.env。
3.2 settings.json:运行时开关
{ "runtime": { "debug_mode": true, "retries": 3, "stream": true, "markdown": true, "exit_on": ["exit", "quit", "bye", "goodbye"] }, "sqlite": { "journal_mode": "WAL", "busy_timeout_ms": 5000 }, "logging": { "level": "INFO", "mcp_trace": true } }journal_mode设成WAL是专门针对后面要讲的“数据库锁定错误”的,busy_timeout_ms给并发写留缓冲。mcp_trace打开后能看到每次工具调用的入参出参,排查 MCP 连接问题非常有用。
3.3 .env:只放密钥
TAOTOKEN_API_KEY=sk-你的TaoToken密钥 GITHUB_PERSONAL_ACCESS_TOKEN=ghp_你的GitHub令牌 PERPLEXITY_API_KEY=pplx_你的搜索密钥3.4 读取配置的主程序骨架
# multi_mcp_agent.py import asyncio import os import json import uuid import tomllib from textwrap import dedent from agno.agent import Agent from agno.models.openai import OpenAIChat from agno.tools.mcp import MultiMCPTools from agno.db.sqlite import SqliteDb from dotenv import load_dotenv load_dotenv() def load_config(): with open("config.toml", "rb") as f: cfg = tomllib.load(f) with open("settings.json", "r", encoding="utf-8") as f: settings = json.load(f) return cfg, settings def build_mcp_commands(cfg): commands = [] for server in cfg["mcp_servers"]: commands.append(server["command"]) return commands def build_mcp_env(cfg): env = dict(os.environ) for server in cfg["mcp_servers"]: for key in server.get("env_keys", []): value = os.getenv(key) if value: env[key] = value return envtomllib是 Python 3.11 起内置的,如果你用 3.10 及以下,换成tomli即可。这段代码把配置读取和 MCP 命令拼装拆开了,后面加服务只改config.toml,不动主逻辑。
4. 多 MCP 服务注册与端到端调用验证
配置就绪后,核心是把MultiMCPTools和SqliteDb接进 Agno 的Agent,然后跑一次真实调用。
4.1 组装 Agent 并连接多 MCP
async def main(): cfg, settings = load_config() model_cfg = cfg["model"] mem_cfg = cfg["memory"] api_key = os.getenv(model_cfg["api_key_env"]) if not api_key: raise RuntimeError(f"缺少环境变量 {model_cfg['api_key_env']}") user_id = f"user_{uuid.uuid4().hex[:8]}" session_id = f"session_{uuid.uuid4().hex[:8]}" db = SqliteDb(db_file=mem_cfg["db_file"]) mcp_commands = build_mcp_commands(cfg) mcp_env = build_mcp_env(cfg) async with MultiMCPTools(mcp_commands, env=mcp_env) as mcp_tools: agent = Agent( name="MultiMCPAgent", model=OpenAIChat( id=model_cfg["model_id"], api_key=api_key, base_url=model_cfg["base_url"], ), tools=[mcp_tools], description="集成 GitHub、搜索与日历的多 MCP 智能助手", instructions=dedent(f""" 你是多 MCP 智能助手,按需调用工具完成任务。 会话信息:user_id={user_id}, session_id={session_id} 规则: 1. 涉及仓库、issue、PR 时调用 GitHub 工具 2. 涉及实时信息时调用搜索工具 3. 涉及日程时调用日历工具 4. 多步任务先规划再逐步调用,最后汇总 """), markdown=settings["runtime"]["markdown"], debug_mode=settings["runtime"]["debug_mode"], retries=settings["runtime"]["retries"], db=db, enable_user_memories=mem_cfg["enable_user_memories"], add_history_to_context=mem_cfg["add_history_to_context"], num_history_runs=mem_cfg["num_history_runs"], ) await agent.acli_app( user_id=user_id, session_id=session_id, user="你", stream=settings["runtime"]["stream"], markdown=settings["runtime"]["markdown"], exit_on=settings["runtime"]["exit_on"], ) if __name__ == "__main__": asyncio.run(main())注意OpenAIChat里base_url直接读config.toml的值,指向 TaoToken 的 API 通道。这样模型出口统一,MCP 服务各自独立,职责清晰。
4.2 依赖清单
agno>=2.2.10 openai mcp python-dotenv安装:
pip install -r requirements.txt node --version && npx --versionNode.js 必须装,因为 GitHub、搜索、日历这几个 MCP 服务都是通过npx拉起的。
4.3 一次端到端调用
启动程序:
python multi_mcp_agent.py看到“成功连接到所有 MCP 服务器”后,输入一条跨服务指令:
帮我查一下我最近的 GitHub 仓库,然后搜索一下这个仓库相关技术的最新动态,最后把结果整理成一段摘要预期行为:代理先调用 GitHub MCP 拉仓库列表,再调用搜索 MCP 查动态,最后用 GPT-4o 汇总。debug_mode=true时终端会打印每次工具调用的名称和参数,你能清楚看到多 MCP 协同的链路。
4.4 验证 SQLite 记忆是否落盘
调用结束后,检查数据库:
sqlite3 tmp/multi_mcp_agent.db ".tables" sqlite3 tmp/multi_mcp_agent.db "SELECT count(*) FROM session_history;"如果表存在且行数大于 0,说明会话历史已经写进本地 SQLite。再启动一次程序,用同一个session_id提问“我上次问了什么”,代理能基于历史上下文回答,就证明记忆系统生效了。
5. 本篇常见错排查
5.1 环境变量未设置导致启动即退出
现象:程序打印缺少环境变量后直接返回。排查顺序是先确认.env在项目根目录,再确认变量名和config.toml里的env_keys完全一致。常见坑是.env里写了OPENAI_API_KEY,但配置里读的是TAOTOKEN_API_KEY,名字对不上。用一行命令快速验证:
python -c "import os; from dotenv import load_dotenv; load_dotenv(); print(bool(os.getenv('TAOTOKEN_API_KEY')))"输出True才算通过。
5.2 MCP 服务器连接失败
现象:卡在“正在初始化 MCP 服务器连接”,或某个服务单独报错。先单独跑一条 MCP 命令看它能不能起来:
npx -y @modelcontextprotocol/server-github如果这条命令本身报错,问题在 Node 环境或包名,不在 Agno。如果单跑正常但集成后失败,多半是env没传进去——检查build_mcp_env是否把GITHUB_PERSONAL_ACCESS_TOKEN注入了。另一个高频原因是base_url写错导致模型层先挂,看起来像 MCP 失败,实际是模型 401。这时去模型对话页面用同一个 Key 发一条消息,能快速区分是模型层还是 MCP 层的问题:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
5.3 SQLite 数据库锁定错误
现象:database is locked。原因是多个进程同时写同一个.db文件。解决分两步:第一,确认没有残留的 Python 进程还在占用,ps aux | grep multi_mcp_agent杀掉旧进程;第二,在settings.json里启用 WAL 模式并设置 busy timeout,让并发写有缓冲。如果还是频繁锁定,把db_file换成每个会话独立文件,例如tmp/agent_{session_id}.db,彻底隔离写入。
5.4 工具调用返回空结果
现象:代理说“已调用工具”但没内容。先看mcp_trace日志里工具的真实返回。GitHub 工具返回空,通常是 token 权限不足,需要repo、user、admin:org权限;搜索工具返回空,多半是搜索服务的 Key 额度用尽。这类问题不是代码问题,是凭证问题,按服务逐个验证即可。
6. 继续接入与长期运行的建议
跑通三服务协同后,下一步通常是加服务或让它常驻。加服务只改config.toml的[[mcp_servers]]段,主程序不用动,这是拆配置的直接好处。常驻运行的话,模型调用会变成持续消耗,建议把 Key 和额度管理放到统一通道上,接入文档里有 OpenAI 兼容写法和 base_url 规范,照着配就行:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
如果你要做的是长期编码或 Agent 常驻任务,Coding Plan 更适合这种持续调用的场景:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
新建 Key 或调整额度在控制台完成:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
最后给一个我踩过的坑:SQLite 记忆文件不要放在会被清理的临时目录,tmp/在某些系统重启后会被清空,导致历史全丢。把db_file指到一个固定数据目录,比如./data/multi_mcp_agent.db,再配合 WAL 模式,长期跑下来最稳。