为什么你的 MCP 服务器需要自己的日志记录——而不仅仅是 Claude Desktop 的
Source: Dev.to
(请提供需要翻译的正文内容,我才能为您完成简体中文的翻译。)
AI 代理的隐形问题
在我之前的文章中,我构建了一个 Hybrid MCP Agent,它能够控制云 API(Gmail、Salesforce、Google Calendar)以及本地文件系统操作(扫描文件夹、移动文件、生成报告)。架构能够运行,但我对代理实际在做什么 毫无可见性。
当出现故障时,我根本不知道该去哪里查找。
这就是 AI 代理系统中的可观测性缺口。传统的应用监控工具,如 Datadog 或 New Relic,并未针对 MCP 工具调用链进行设计。当你的代理跨越两个完全不同的环境——一个 GCP 虚拟机和一台 Windows 桌面——挑战会成倍增加。
下面是我的解决方案。
架构:两个数据流,一个数据库
远程 MCP 服务器(GCP 虚拟机)
通过云 API 处理 Gmail、Salesforce、Calendar。
流程
第 1 部分:使用 FastMCP 中间件进行远程日志记录
在服务器端记录日志的五个理由:
-
桌面端并不总是客户端。
MCP 服务器可以被任何客户端调用——API 集成、其他代理、计划任务,或多个用户同时访问同一端点。桌面日志只能捕获 你 的 Claude Desktop 会话所做的操作。中间件能够捕获所有访问服务器的请求,不论调用者是谁。 -
多用户可见性需要集中日志。
在生产环境中,单个 MCP 服务器为多个拥有不同角色的用户提供服务——管理员、销售、财务——每个用户通过/mcp?user_id=role进行路由。每个用户的 Claude Desktop 只记录自己的会话。若没有服务器端日志,回答 “哪个用户在下午 2 点触发了 Salesforce API 速率限制?” 就必须从每台笔记本收集日志。中间件在同一位置记录所有用户的活动,并标记user_id,使跨用户分析变得轻而易举。 -
企业运营需要统一视图。
将此扩展到团队或部门:10、50 甚至更多用户通过同一 MCP 服务器发送邮件、更新 CRM 记录、生成文档。运营经理需要回答:- “我们今天的总 API 使用量是多少?”
- “哪种工具最常出错?”
- “是否有用户的调用量异常高?”
从每台机器收集桌面日志根本不可扩展。服务器端中间件向统一仪表盘提供数据,从第一天起就满足此需求。
-
延迟精度。
桌面日志记录的是客户端 发送 请求和收到响应的时间戳,因此持续时间包含网络往返时间。服务器端中间件测量的是 实际工具执行时间——在诊断慢调用是网络问题还是工具性能问题时,这一点至关重要。 -
运营独立性。
在生产环境中,你的监控不应依赖开发者的笔记本在线并运行上传脚本。中间件实时直接写入数据库,零依赖任何外部组件。如果本地上传器崩溃、离线或错过一次周期——远程日志仍然完整。
简而言之: 桌面日志解析是对你无法控制的环境(例如 npm 包 Desktop Commander)的变通方案。服务器端中间件才是对你 能够 控制的环境进行正确仪表化的方式。
对于云端 MCP 服务器,我使用 FastMCP 的中间件系统 自动拦截每一次工具调用。业务逻辑无需任何修改。
中间件实现
log_data = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"source": "remote",
"tool_name": tool_name,
"parameters": context.message.arguments or {},
"success": True,
}
try:
result = await call_next(context)
log_data["duration_ms"] = (time.time() - start_time) * 1000
log_data["result_summary"] = summarize_result(result)
return result
except Exception as e:
log_data["success"] = False
log_data["error_message"] = str(e)
raise
finally:
log_db.insert_log(log_data)
关键设计决定: 中间件包装 call_next(),在单一的 finally 块中捕获成功和失败两种情况。这保证 每一次 工具调用都会被记录,即使抛出异常。
SQLite Schema
(省略了模式定义以简洁——该表包括 timestamp、source、tool_name、parameters、success、duration_ms、result_summary、error_message 等列。)
第2部分:本地日志收集管道
Log Parser
示例日志行:
2026-02-18T08:46:22Z [local-commander] Message from client: …
2026-02-18T08:46:22Z [local-commander] Message from server: …
def parse_tool_call_request(line: str) -> Optional[Dict]:
timestamp = extract_timestamp(line)
json_match = re.search(
r'Message from client: (\{.*?"id":\d+\})', line
)
msg = json.loads(json_match.group(1))
params = msg.get('params', {})
return {
'timestamp': timestamp,
'tool_name': params.get('name'),
'arguments': params.get('arguments', {}),
'request_id': msg.get('id'),
}
增量上传与书签
{
"mcp-server-local-commander.log": 1724902
}
with open(filepath, 'r', encoding='utf-8') as f:
# read new lines since last bookmark ...
@router.post("/logs/upload")
def upload_logs(logs_data: List[Dict]):
count = log_db.insert_logs_bulk(logs_data)
return {"status": "success", "uploaded_count": count}
source 字段是统一标识——它使仪表盘能够并排过滤和比较远程与本地活动。
第3部分:仪表盘
使用 Streamlit 实现。
- 摘要卡片: 总调用次数、成功率、平均响应时间、错误计数。
- 过滤器: 按
source(remote/local)、tool_name、user_id、时间范围。 - 下钻表格: 原始日志、错误详情、按用户的 API 使用情况。
(已省略截图。)
第 4 部分:悄然删除我的数据的 Bug
症状
部署后数据开始消失。
初始假设
保留任务(cron job)太激进。它只会清除 30 天 以前的数据,所以这不是原因。
调查
检查了部署流水线。
根本原因
cd ~
部署脚本把项目目录中的 所有 文件都当作可以从 Git 复现的内容。SQLite 数据库位于项目文件夹内部,因此每次部署都会 覆盖 数据库文件,导致所有日志被擦除。
这是一个经典的基础设施反模式:把有状态的数据当作无状态的代码来处理。
修复:将数据与代码分离
# docker‑compose (or similar) volume mounts
volumes:
- /home/user/ai_mcp_fastmcp_remote/logs:/app/logs
- /home/user/mcp_data/db:/app/data/db
更新代码中的路径解析:
DB_DIR = (Path(__file__).parent.parent / "data" / "db")
现在数据库位于代码库之外,能够在重新部署后保持。
可观测性对 AI 代理
仅将日志写入文件是不够的。你需要 可查询、可过滤、跨环境的可视化,并且能够在亚秒级别进行细粒度追踪。当一个代理串联了八个工具调用且第六个失败时,你必须能够立即看到完整的调用序列。
中间件 – MCP 日志的正确抽象
FastMCP 的中间件模式让你在不触及业务逻辑的情况下捕获 每一次工具调用。
- 一个类,注册一次,覆盖所有当前和未来的工具。
将有状态数据与无状态代码分离
这是 DevOps 101,但当你的 “数据库” 只是一 个放在项目目录下的 SQLite 文件时,容易被忽视。如果 rm -rf 能把它删掉,那它放错了位置。
书签模式 – 防止重复上传
对于任何日志转发管道,跟踪文件读取位置既简单又有效。它能够处理:
- 增量新数据(常见情况)
- 文件轮转(边缘情况)
从 SQLite 起步
对于单节点 MCP 部署,SQLite 是合适的选择:
- 无需额外基础设施
- 符合 ACID 标准
- 如有需要,后期可轻松迁移到 PostgreSQL
在我的案例中,数月的全部工具调用历史 comfortably fits in a single file under 10 MB.
接下来
在下一篇文章中,我将分享从分析这些日志中学到的关于 AI 代理自主性的真实限制——以及为什么“人类在回路中”不仅是安全特性,更是性能优化。
- 完整实现已在 SunnyLab TV 的视频中演示。
- 本文引用的所有代码均来自运行在 GCP 上的生产 MCP 系统。
- 在 Medium 上关注我,阅读本系列的前几篇文章。
标签: 人工智能, 软件工程, DevOps, Python, 云计算