为什么你的 MCP 服务器需要自己的日志记录——而不仅仅是 Claude Desktop 的

发布: (2026年3月4日 GMT+8 10:04)
10 分钟阅读
原文: Dev.to

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 中间件进行远程日志记录

在服务器端记录日志的五个理由:

  1. 桌面端并不总是客户端。
    MCP 服务器可以被任何客户端调用——API 集成、其他代理、计划任务,或多个用户同时访问同一端点。桌面日志只能捕获 的 Claude Desktop 会话所做的操作。中间件能够捕获所有访问服务器的请求,不论调用者是谁。

  2. 多用户可见性需要集中日志。
    在生产环境中,单个 MCP 服务器为多个拥有不同角色的用户提供服务——管理员、销售、财务——每个用户通过 /mcp?user_id=role 进行路由。每个用户的 Claude Desktop 只记录自己的会话。若没有服务器端日志,回答 “哪个用户在下午 2 点触发了 Salesforce API 速率限制?” 就必须从每台笔记本收集日志。中间件在同一位置记录所有用户的活动,并标记 user_id,使跨用户分析变得轻而易举。

  3. 企业运营需要统一视图。
    将此扩展到团队或部门:10、50 甚至更多用户通过同一 MCP 服务器发送邮件、更新 CRM 记录、生成文档。运营经理需要回答:

    • “我们今天的总 API 使用量是多少?”
    • “哪种工具最常出错?”
    • “是否有用户的调用量异常高?”

    从每台机器收集桌面日志根本不可扩展。服务器端中间件向统一仪表盘提供数据,从第一天起就满足此需求。

  4. 延迟精度。
    桌面日志记录的是客户端 发送 请求和收到响应的时间戳,因此持续时间包含网络往返时间。服务器端中间件测量的是 实际工具执行时间——在诊断慢调用是网络问题还是工具性能问题时,这一点至关重要。

  5. 运营独立性。
    在生产环境中,你的监控不应依赖开发者的笔记本在线并运行上传脚本。中间件实时直接写入数据库,零依赖任何外部组件。如果本地上传器崩溃、离线或错过一次周期——远程日志仍然完整。

简而言之: 桌面日志解析是对你无法控制的环境(例如 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_nameuser_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 能把它删掉,那它放错了位置。

书签模式 – 防止重复上传

对于任何日志转发管道,跟踪文件读取位置既简单又有效。它能够处理:

  1. 增量新数据(常见情况)
  2. 文件轮转(边缘情况)

从 SQLite 起步

对于单节点 MCP 部署,SQLite 是合适的选择:

  • 无需额外基础设施
  • 符合 ACID 标准
  • 如有需要,后期可轻松迁移到 PostgreSQL

在我的案例中,数月的全部工具调用历史 comfortably fits in a single file under 10 MB.

接下来

在下一篇文章中,我将分享从分析这些日志中学到的关于 AI 代理自主性的真实限制——以及为什么“人类在回路中”不仅是安全特性,更是性能优化。

  • 完整实现已在 SunnyLab TV 的视频中演示。
  • 本文引用的所有代码均来自运行在 GCP 上的生产 MCP 系统。
  • Medium 上关注我,阅读本系列的前几篇文章。

标签: 人工智能, 软件工程, DevOps, Python, 云计算

0 浏览
Back to Blog

相关文章

阅读更多 »

AI、人类与我们打破的循环

🌅 经验的回响 — 站在地平线 曾经有一段时间,混沌塑造了我。但当我真正选择了自己——真正选择了自己——一切都改变了。我…