从Excel加班到MCP服务:用Python打造AI驱动的数据处理工作流 1. 为什么我要自己动手写一个 MCP1.1 从一次加班到凌晨的 Excel 说起上个月底财务部门丢过来一个压缩包里面是十二个分公司的销售明细表每个表的结构还不太一样——有的用“销售额”有的用“成交金额”有的干脆把日期和订单号塞在同一列里。需求听起来很简单合并成一张总表按月份和产品线做透视再生成一份带图表的汇总报告。我打开第一个文件的时候还觉得今晚十点前能收工结果光是处理合并单元格和日期格式就耗到了凌晨两点。这种场景我相信做数据处理的同行都不陌生。Excel 本身是个极好的工具但一旦涉及跨文件、跨表、结构不统一的批量处理手工操作就变成了纯粹的体力消耗。更麻烦的是这类工作往往每周、每月都要重复一遍每次都要重新踩一遍坑。后来我开始用 Python 写脚本来自动化这些流程pandas 加 openpyxl 的组合确实能解决大部分问题。但新的问题又来了脚本越写越多每个脚本都是针对特定文件结构硬编码的换个数据源就得改代码。而且这些脚本散落在各个文件夹里时间一长自己都忘了哪个是哪个。我需要一个更统一的入口让 AI 能够理解我的意图自动调用合适的处理逻辑。这就是我开始研究 MCP 的起点。MCP 全称 Model Context Protocol是一个让 AI 模型能够与外部工具、数据源进行标准化交互的协议。你可以把它理解成 AI 世界的 USB-C 接口——不管对面是数据库、文件系统还是某个具体的业务 API只要按照 MCP 的规范封装好AI 就能直接调用。对于我这种需要频繁处理 Excel 的人来说这意味着我可以把常用的数据处理能力封装成 MCP 服务然后让 AI 根据自然语言指令自动完成整个工作流。1.2 MCP 到底解决了什么实际问题在没有 MCP 之前如果我想让 AI 帮我处理 Excel通常有几种做法。第一种是把文件内容粘贴到对话框里让 AI 给出处理建议或者生成代码然后我再手动运行代码。这种方式的问题是 AI 看不到完整的文件结构只能基于我粘贴的片段做判断而且每次都要重复粘贴。第二种是写一个完整的 Python 脚本把 AI 的调用嵌进去但这样又变成了传统的程序开发失去了 AI 灵活理解意图的优势。MCP 的思路不一样。它把“能力”和“调用方式”分开我只需要把 Excel 处理的能力按照 MCP 协议封装成一个服务端AI 客户端就能自动发现这些能力并根据我的自然语言描述来决定调用哪个工具、传什么参数。整个过程我不需要写死任何调用逻辑AI 会根据上下文动态决策。举个例子我可以封装三个 MCP 工具读取 Excel 结构、按条件筛选数据、生成透视汇总。当我说“把上个月销售额超过十万的订单找出来按区域汇总”时AI 会自动先调用读取工具了解表结构再调用筛选工具过滤数据最后调用汇总工具生成结果。我只需要用日常语言描述需求剩下的编排工作交给 AI。这种模式特别适合处理那些“每次都不太一样”的任务。比如同样是合并报表这个月多了两个分公司下个月某个分公司换了列名传统脚本需要改代码而 MCP 方案只需要 AI 重新理解一次表结构就行。1.3 适合哪些人参考这套方案这篇文章主要面向有一定 Python 基础、日常需要处理 Excel 数据、并且对 AI 辅助工作流感兴趣的读者。你不需要是 MCP 协议的专家也不需要深入理解底层的通信机制只要会写基本的 Python 函数就能跟着我把第一个 MCP 服务跑起来。如果你是完全的 Python 新手建议先花半小时了解一下 pip 安装、虚拟环境创建和基本的文件读写操作这些是后续所有步骤的基础。如果你已经用过 pandas 或者 openpyxl那整个过程会非常顺畅。至于 AI 客户端的选择我后面会给出具体的配置方法你只需要有一个支持 MCP 协议的客户端就行。2. 动手之前的整体设计思路2.1 为什么选择 Python 作为 MCP 服务的实现语言MCP 协议本身是语言无关的理论上用任何语言都能实现。我选择 Python 主要有三个原因。第一Excel 处理生态在 Python 里最成熟pandas、openpyxl、xlrd 这些库经过多年迭代稳定性和功能覆盖都没得说。第二MCP 的官方 SDK 对 Python 支持得很好封装程度高不需要自己处理底层的 JSON-RPC 通信细节。第三Python 的代码可读性强后续如果要调整工具逻辑改起来很快。如果你更熟悉 JavaScript 或者 Go也完全可以用对应的 SDK 来实现核心思路是一样的。但考虑到 Excel 处理的场景Python 的库生态优势太明显了我建议还是用 Python 起步。2.2 工具拆分的粒度怎么把握这是设计 MCP 服务时最关键的一个决策。工具拆得太细AI 需要调用很多次才能完成一个任务效率低而且容易出错拆得太粗一个工具承担了太多职责参数复杂AI 理解起来也困难。我的经验是每个工具只做一件明确的事但这件事的边界要足够清晰。比如“读取 Excel 文件”和“筛选数据”应该分开因为读取是获取原始信息筛选是基于条件的变换。但“筛选数据”和“排序数据”可以合并成一个“查询数据”工具因为它们本质上都是对数据集的查询操作只是条件不同。具体到 Excel 处理场景我最终设计了四个核心工具工具名称职责关键参数read_excel_structure读取文件结构返回列名、数据类型、行数、样例数据文件路径query_data按条件筛选、排序、取前 N 行文件路径、筛选条件、排序字段、返回行数aggregate_data分组汇总支持求和、计数、平均文件路径、分组字段、聚合字段、聚合方式export_result将处理结果写入新文件数据内容、输出路径、格式这四个工具覆盖了从读取到输出的完整链路每个工具的职责都很单一AI 编排起来逻辑清晰。后续如果要扩展比如加一个“生成图表”的工具直接新增就行不影响已有的工具。2.3 通信方式的选择stdio 还是 SSEMCP 协议支持多种传输方式最常用的是 stdio标准输入输出和 SSEServer-Sent Events。stdio 方式下MCP 客户端会启动服务端进程通过标准输入输出流进行通信。SSE 方式下服务端是一个独立的 HTTP 服务客户端通过网络连接。对于个人使用场景我强烈建议用 stdio。原因很简单配置方便不需要额外管理端口和进程客户端启动时自动拉起服务端关闭时自动结束。SSE 方式更适合团队共享或者需要远程调用的场景但会引入网络配置、认证、进程守护等一系列额外问题。我一开始用的是 SSE想着以后可以给同事共享结果光是在不同机器上配置环境就折腾了半天。后来换回 stdio整个世界都清净了。所以除非你确实有远程调用的需求否则 stdio 是更务实的选择。3. 环境搭建与核心依赖安装3.1 Python 环境的准备与常见坑首先确认你的 Python 版本。MCP 的 Python SDK 要求 Python 3.10 及以上我建议直接用 3.11 或 3.12兼容性最好。在命令行里运行python --version确认版本如果低于 3.10先去官网下载新版安装。安装的时候有一个坑要注意Windows 上如果从 Microsoft Store 安装 Python有时候会出现权限问题导致后续 pip 安装库的时候报错。我建议直接从 python.org 下载安装包安装时勾选“Add Python to PATH”这样后续在命令行里调用会方便很多。虚拟环境是必须的。不同项目依赖的库版本可能冲突用虚拟环境隔离是最稳妥的做法。创建和激活虚拟环境的命令如下# 创建虚拟环境 python -m venv mcp-excel-env # Windows 激活 mcp-excel-env\Scripts\activate # macOS/Linux 激活 source mcp-excel-env/bin/activate激活后命令行前面会出现(mcp-excel-env)的标识说明已经进入虚拟环境。后续所有安装操作都在这个环境下进行。3.2 安装 MCP SDK 和 Excel 处理库核心依赖就两个MCP 的 Python SDK 和 pandas。openpyxl 是 pandas 读写 xlsx 文件的底层依赖安装 pandas 时会自动带上但有时候版本不匹配会导致读取报错所以我会显式指定一下。pip install mcp pandas openpyxl如果你需要处理老版本的 xls 文件还需要额外安装 xlrdpip install xlrd安装完成后可以跑一个简单的验证脚本确认 pandas 能正常读写 Excelimport pandas as pd # 创建一个测试数据 df pd.DataFrame({ 日期: [2024-01-01, 2024-01-02], 产品: [A, B], 销售额: [1000, 2000] }) # 写入文件 df.to_excel(test.xlsx, indexFalse) # 读取文件 df_read pd.read_excel(test.xlsx) print(df_read)如果这段代码能正常输出数据说明环境没问题。如果报错大概率是 openpyxl 版本问题可以尝试pip install --upgrade openpyxl。3.3 项目目录结构的设计一个清晰的目录结构能让后续维护轻松很多。我的习惯是这样组织mcp-excel-server/ ├── server.py # MCP 服务主入口 ├── tools/ │ ├── __init__.py │ ├── reader.py # 读取相关工具 │ ├── query.py # 查询相关工具 │ └── export.py # 导出相关工具 ├── utils/ │ ├── __init__.py │ └── helpers.py # 通用辅助函数 └── requirements.txt # 依赖清单把不同职责的工具拆到不同文件里主入口只负责注册和启动。这样当工具数量增多时代码不会变成一个几千行的巨型文件。对于刚开始的版本你也可以把所有代码放在一个文件里但一旦超过三个工具建议还是拆开。4. 核心工具的实现细节4.1 读取 Excel 结构让 AI 先“看懂”文件这个工具是整个工作流的起点。AI 在不知道文件结构的情况下无法生成正确的查询条件。所以读取工具需要返回足够的信息列名、每列的数据类型、总行数、前几行样例数据。import pandas as pd from mcp.server import Server from mcp.types import Tool, TextContent async def read_excel_structure(file_path: str) - str: 读取 Excel 文件结构返回列信息和样例数据 try: df pd.read_excel(file_path, nrows100) # 构建列信息 columns_info [] for col in df.columns: dtype str(df[col].dtype) non_null df[col].notna().sum() sample df[col].dropna().head(3).tolist() columns_info.append({ name: col, type: dtype, non_null_count: int(non_null), sample_values: sample }) # 获取总行数不加载全部数据 total_rows len(pd.read_excel(file_path, usecols[0])) result { file_path: file_path, total_rows: total_rows, columns: columns_info } return json.dumps(result, ensure_asciiFalse, indent2) except Exception as e: return f读取文件失败: {str(e)}这里有几个细节值得说明。第一我用nrows100只读取前一百行来获取结构信息避免大文件加载过慢。第二获取总行数时用usecols[0]只加载第一列这样内存占用最小。第三样例数据取每列的前三个非空值让 AI 能直观看到数据长什么样。注意如果 Excel 文件有多个 sheet需要在参数里增加 sheet_name默认读取第一个 sheet。有些文件第一个 sheet 是说明页实际数据在第二个 sheet这种情况如果不指定就会读错。4.2 查询工具把自然语言条件翻译成数据操作查询工具的核心是把 AI 传来的条件转换成 pandas 的筛选逻辑。这里的设计难点在于AI 传来的条件格式需要足够灵活能表达等于、大于、包含、区间等多种操作。我定义了一套简单的条件描述格式用 JSON 数组表示多个条件的组合[ {column: 销售额, operator: gt, value: 10000}, {column: 区域, operator: in, value: [华东, 华南]} ]支持的 operator 包括eq等于、ne不等于、gt大于、gte大于等于、lt小于、lte小于等于、in在列表中、contains包含字符串、between区间。def apply_filters(df: pd.DataFrame, filters: list) - pd.DataFrame: 根据条件列表筛选数据 result df.copy() for f in filters: col f[column] op f[operator] val f[value] if col not in result.columns: raise ValueError(f列不存在: {col}) if op eq: result result[result[col] val] elif op ne: result result[result[col] ! val] elif op gt: result result[result[col] val] elif op gte: result result[result[col] val] elif op lt: result result[result[col] val] elif op lte: result result[result[col] val] elif op in: result result[result[col].isin(val)] elif op contains: result result[result[col].astype(str).str.contains(str(val), naFalse)] elif op between: result result[(result[col] val[0]) (result[col] val[1])] else: raise ValueError(f不支持的操作符: {op}) return result这个实现看起来简单但实际使用中有一个坑数值比较时如果 Excel 里的数字被读成了字符串操作会报错。我的处理方式是在筛选前先尝试把目标列转成数值类型如果转换失败就保持原类型并给出提示。另一个坑是日期比较。Excel 里的日期格式五花八门有的是2024-01-01有的是2024/1/1还有的是 Excel 内部的序列号。pandas 的to_datetime能处理大部分情况但遇到混合格式会报错。我的做法是先用pd.to_datetime尝试转换如果失败就保持字符串比较并在返回结果里提示 AI 日期格式可能有问题。4.3 汇总工具分组聚合的灵活实现汇总工具需要支持按一个或多个字段分组对指定字段做求和、计数、平均、最大、最小等聚合操作。参数设计上我用两个列表来分别描述分组字段和聚合配置async def aggregate_data( file_path: str, group_by: list, aggregations: list, filters: list None ) - str: 分组汇总数据 group_by: 分组字段列表如 [区域, 产品线] aggregations: 聚合配置如 [ {column: 销售额, function: sum, alias: 总销售额}, {column: 订单号, function: count, alias: 订单数} ] df pd.read_excel(file_path) if filters: df apply_filters(df, filters) # 构建聚合字典 agg_dict {} for agg in aggregations: col agg[column] func agg[function] alias agg.get(alias, f{col}_{func}) agg_dict[alias] pd.NamedAgg(columncol, aggfuncfunc) result df.groupby(group_by).agg(**agg_dict).reset_index() return result.to_json(orientrecords, force_asciiFalse)这里用pd.NamedAgg是为了支持聚合结果重命名这样 AI 在后续引用结果时可以用更语义化的列名。比如“总销售额”比“销售额_sum”更容易理解。实操心得分组字段如果有空值pandas 默认会丢弃这些行。如果业务上需要保留空值分组要在 groupby 里加dropnaFalse。我一开始没注意这个参数导致汇总结果比预期少了几行排查了半天才发现是空值被丢了。4.4 导出工具把结果写回 Excel导出工具相对简单但有几个细节需要注意。首先是输出路径的处理如果目录不存在需要自动创建。其次是格式控制比如是否包含索引、日期格式、数字精度等。async def export_result( data: str, output_path: str, sheet_name: str Sheet1, include_index: bool False ) - str: 将 JSON 数据导出为 Excel 文件 import os import json # 确保输出目录存在 output_dir os.path.dirname(output_path) if output_dir and not os.path.exists(output_dir): os.makedirs(output_dir, exist_okTrue) # 解析数据 records json.loads(data) df pd.DataFrame(records) # 写入文件 df.to_excel(output_path, sheet_namesheet_name, indexinclude_index) return f已导出 {len(df)} 行数据到 {output_path}如果数据量很大写入 xlsx 格式会比较慢因为 openpyxl 需要构建完整的 XML 结构。超过十万行的时候我会建议 AI 改用 csv 格式输出速度快很多。这个判断逻辑可以放在工具内部根据数据行数自动选择格式。5. 把工具注册到 MCP 服务5.1 服务端主入口的编写所有工具函数写好后需要在主入口里注册到 MCP 服务。注册的过程就是告诉服务端有哪些工具可用、每个工具叫什么名字、接受什么参数、返回什么内容。import asyncio from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent app Server(excel-processor) app.list_tools() async def list_tools() - list[Tool]: return [ Tool( nameread_excel_structure, description读取Excel文件的结构信息包括列名、数据类型、行数和样例数据, inputSchema{ type: object, properties: { file_path: { type: string, description: Excel文件的完整路径 } }, required: [file_path] } ), Tool( namequery_data, description按条件筛选和排序Excel数据, inputSchema{ type: object, properties: { file_path: {type: string}, filters: { type: array, items: { type: object, properties: { column: {type: string}, operator: {type: string}, value: {} } } }, sort_by: {type: string}, sort_order: {type: string, enum: [asc, desc]}, limit: {type: integer} }, required: [file_path] } ), # ... 其他工具注册 ] app.call_tool() async def call_tool(name: str, arguments: dict) - list[TextContent]: if name read_excel_structure: result await read_excel_structure(arguments[file_path]) elif name query_data: result await query_data(**arguments) elif name aggregate_data: result await aggregate_data(**arguments) elif name export_result: result await export_result(**arguments) else: result f未知工具: {name} return [TextContent(typetext, textresult)] async def main(): async with stdio_server() as (read_stream, write_stream): await app.run(read_stream, write_stream, app.create_initialization_options()) if __name__ __main__: asyncio.run(main())inputSchema的定义很关键它决定了 AI 能否正确理解每个参数的含义和格式。description 要写得足够清晰让 AI 知道这个参数是干什么的、什么情况下需要传。比如 filters 参数的 description 应该说明支持哪些操作符、value 的格式是什么样的。5.2 在 AI 客户端中配置 MCP 服务不同的 AI 客户端配置方式略有差异但核心都是告诉客户端有一个 MCP 服务通过什么命令启动工作目录在哪里。以常见的配置文件为例{ mcpServers: { excel-processor: { command: python, args: [/path/to/mcp-excel-server/server.py], env: { PYTHONPATH: /path/to/mcp-excel-server } } } }如果你用的是虚拟环境command 需要指向虚拟环境里的 python 解释器比如/path/to/mcp-excel-env/bin/pythonmacOS/Linux或C:\path\to\mcp-excel-env\Scripts\python.exeWindows。配置完成后重启客户端如果一切正常客户端会显示已连接的 MCP 服务并且能看到注册的工具列表。这时候你就可以用自然语言让 AI 调用这些工具了。常见问题如果客户端启动后没有显示 MCP 服务先检查 python 路径是否正确再检查 server.py 是否能独立运行。可以在命令行里直接执行python server.py如果报错会直接显示出来比在客户端里排查方便得多。6. 完整工作流实战演示6.1 场景设定合并多个月度报表假设我有三个 Excel 文件分别是 1 月、2 月、3 月的销售明细每个文件的列名略有差异。我需要把它们合并成一张总表按产品线汇总销售额并导出结果。在没有 MCP 之前我需要写一个脚本手动处理列名映射、合并、分组、导出。有了 MCP 之后我只需要在 AI 客户端里描述需求AI 会自动编排工具调用。第一步我先让 AI 了解每个文件的结构请读取 data/2024-01.xlsx、data/2024-02.xlsx、data/2024-03.xlsx 这三个文件的结构。AI 会依次调用 read_excel_structure 工具返回每个文件的列名和样例数据。假设返回结果显示1 月文件有“产品线”“销售额”“日期”三列2 月文件有“产品”“成交额”“日期”三列3 月文件有“产品线”“销售额”“月份”三列。第二步我告诉 AI 列名映射关系2 月文件的“产品”对应“产品线”“成交额”对应“销售额”。3 月文件的“月份”对应“日期”。请分别读取这三个文件统一列名后合并。AI 会调用 query_data 工具读取每个文件的数据然后在返回结果的基础上做列名统一。这里有一个细节query_data 返回的是 JSON 格式的数据AI 需要在内存中做列名映射而不是让工具去做。这是因为列名映射的逻辑是业务层面的不适合硬编码在工具里。第三步让 AI 做汇总按产品线汇总销售额计算每个产品线的总销售额和订单数。AI 会调用 aggregate_data 工具传入 group_by[产品线]aggregations[{column: 销售额, function: sum, alias: 总销售额}, {column: 订单号, function: count, alias: 订单数}]。第四步导出结果把汇总结果导出到 output/summary.xlsx。AI 调用 export_result 工具把上一步的汇总数据写入指定路径。整个过程中我没有写一行代码只是用自然语言描述了需求。AI 根据每个工具的 description 和 inputSchema 自动决定了调用顺序和参数。这就是 MCP 的价值所在把固定的能力封装好让 AI 负责灵活的编排。6.2 处理过程中的参数计算与选择在实际操作中有几个参数需要根据数据情况动态调整。比如读取文件时如果文件很大nrows100可能不够需要增加到 500 或 1000 才能让 AI 看到足够的数据特征。我的经验是如果文件列数少于 20 列100 行样例足够如果列数很多或者数据类型复杂增加到 500 行。再比如汇总时的聚合方式选择。如果销售额列有负数退款求和结果可能比预期小这时候需要确认业务上是否应该取绝对值还是保留负数。这个判断 AI 做不了需要我在指令里明确说明。还有一个容易忽略的参数是日期范围。如果三个文件的时间范围有重叠合并后会出现重复数据。我通常会在合并前先让 AI 检查每个文件的日期范围确认没有重叠后再合并。这个检查可以通过 query_data 工具实现按日期排序取第一条和最后一条记录。6.3 实操现场记录与结果验证跑完整个流程后我会做几个验证。第一检查总行数是否等于三个文件行数之和。第二抽查几个产品线的汇总数据手工计算一遍确认无误。第三检查导出文件的格式是否正确特别是数字列有没有变成文本。有一次我发现汇总结果里某个产品线的销售额明显偏低排查后发现是 2 月文件的“成交额”列里有几个单元格是文本格式的数字pandas 读取时当成了字符串求和时被跳过了。解决办法是在读取后强制转换类型df[成交额] pd.to_numeric(df[成交额], errorscoerce)。这个转换逻辑我后来加到了 query_data 工具里作为可选参数coerce_numeric默认开启。7. 常见问题与排查技巧7.1 工具调用失败的高频原因在实际使用中工具调用失败最常见的原因有三类路径问题、参数格式问题、数据类型问题。路径问题通常表现为“文件不存在”。MCP 服务的工作目录和 AI 客户端的工作目录可能不一致所以文件路径最好用绝对路径。如果必须用相对路径要确保服务端的工作目录设置正确。参数格式问题多见于 filters 和 aggregations 这类复杂参数。AI 有时候会把 operator 写成 “greater_than” 而不是 “gt”或者把 value 写成字符串 “10000” 而不是数字 10000。解决办法是在工具的 description 里给出明确的示例让 AI 照着格式传参。数据类型问题最隐蔽。比如 Excel 里的日期列pandas 可能读成字符串也可能读成 datetime 对象取决于单元格格式。如果筛选条件里写的是字符串日期而实际列是 datetime 类型比较就会失败。我的处理方式是在 apply_filters 里做类型推断如果列是 datetime 类型而 value 是字符串尝试把 value 转成 datetime 再比较。7.2 性能优化的几个实用技巧当处理大文件时性能问题会变得明显。以下是我实测有效的几个优化手段优化手段效果适用场景指定 usecols减少内存占用 50% 以上只需要部分列时指定 dtype避免类型推断开销列类型已知时分块读取避免内存溢出文件超过 50 万行使用 csv 中间格式读写速度提升 3-5 倍数据交换频繁时关闭 openpyxl 的样式解析读取速度提升明显不需要样式信息时其中“关闭样式解析”这个技巧比较隐蔽。openpyxl 默认会解析单元格的样式信息但数据处理场景根本用不到这些。可以在 read_excel 时传入engineopenpyxl并设置read_onlyTrue这样读取速度会快很多。7.3 常见问题速查表问题现象可能原因解决方法读取文件报“File not found”路径错误或工作目录不对使用绝对路径筛选结果为空数据类型不匹配检查列类型必要时转换汇总结果行数偏少分组列有空值被丢弃groupby 加 dropnaFalse导出文件打不开数据包含非法字符清理特殊字符后导出工具调用超时文件过大分块读取或改用 csvAI 传参格式错误description 不够清晰在 description 里加示例日期筛选不生效日期格式不一致统一转成 datetime 类型数字被当成文本单元格格式问题pd.to_numeric 强制转换避坑技巧在开发阶段建议在工具函数里加详细的日志输出记录每次调用的参数和返回结果。这样当 AI 调用出错时可以通过日志快速定位是参数问题还是逻辑问题。日志可以写到文件里也可以直接输出到 stderrstdio 模式下 stderr 不会干扰 MCP 通信。8. 后续扩展与个人体会这套 MCP 服务我用了大概两个月最大的感受是把重复性的数据处理逻辑封装成工具之后我的工作重心从“怎么写代码”变成了“怎么描述需求”。以前遇到一个新的报表需求我要花时间想 pandas 的 API 怎么调、参数怎么传现在只需要把需求说清楚AI 会自动选择合适的工具和参数。后续我计划在这几个方向做扩展。第一是增加图表生成工具让 AI 能直接输出带图表的 Excel 报告。第二是增加数据校验工具在合并前自动检查列名一致性、数据类型一致性、空值比例等。第三是增加模板功能把常用的处理流程保存成模板下次直接调用。如果你也想动手写自己的 MCP 服务我的建议是从最简单的工具开始先跑通一个“读取文件”的工具确认整个链路没问题再逐步增加复杂度。不要一上来就设计一个大而全的服务那样很容易在调试环节卡住。另外工具的 description 值得花时间打磨它直接决定了 AI 能不能正确理解和使用你的工具。我一开始 description 写得很简略结果 AI 经常传错参数后来把每个参数的格式和示例都写清楚调用成功率明显提升。最后分享一个我在调试时常用的小技巧在 MCP 服务里加一个echo工具功能就是原样返回传入的参数。当 AI 调用其他工具出错时可以先让它调用 echo 工具确认 AI 理解的参数格式和我期望的是否一致。这个工具虽然简单但排查参数问题时特别有用。