news 2026/11/2 9:10:51

从零手写 MCP Server:用 Python 让 AI 自动操作 Excel

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零手写 MCP Server:用 Python 让 AI 自动操作 Excel

1. 为什么我要自己动手写一个 MCP

1.1 从一次崩溃的 Excel 处理经历说起

上个月帮朋友处理一批销售数据,二十多个 Excel 文件,每个文件里都有七八张表,需要按区域拆分、按品类汇总、再生成一份带图表的月报。我一开始想的是老办法:写个 Python 脚本,用 pandas 读进来,处理完再写回去。脚本写到一半我就烦了——每换一个需求,比如“把华东区的数据单独拎出来”“按月份做个透视”,我就得改代码、重跑、再检查。更麻烦的是,朋友自己不会 Python,我每次改完还得截图告诉他结果对不对。

那天晚上我盯着屏幕想,如果有一个东西能让我用自然语言直接指挥它去操作 Excel,比如我说“把 Sheet2 里销售额大于 5000 的行标黄”,它就能自己写代码、自己执行、自己把结果给我看,那该多省事。这个念头一冒出来,我就想到了 MCP。

MCP 全称是 Model Context Protocol,你可以把它理解成一套“让 AI 模型和外部工具对话的通用插头”。以前我们想让 AI 操作 Excel,要么把数据贴进对话框让它分析,要么自己写一堆胶水代码把 AI 的输出转成可执行的操作。MCP 出现之后,这件事变得标准化了:你写一个 MCP Server,把“读 Excel”“写 Excel”“筛选数据”这些能力暴露成工具,AI 就能像调用函数一样调用它们。

我决定拿 Excel 处理这个场景练手,原因有三个。第一,Excel 是几乎所有人都绕不开的工具,需求足够普遍;第二,Python 生态里有 openpyxl、pandas 这些成熟的库,底层能力不用从零造;第三,这个场景足够“痛”,痛到我愿意花一个周末把它做出来。

1.2 MCP 到底解决了什么问题

在没有 MCP 之前,AI 和 Excel 之间的交互大概有三种方式。第一种是“人肉中转”:你把 Excel 内容复制到聊天窗口,AI 给你一段分析或代码,你再手动执行。这种方式的问题是数据量一大就崩,而且 AI 看不到执行结果,没法自我纠错。第二种是“写死脚本”:你提前把可能用到的操作都写成函数,AI 只能在这些函数里选。这种方式的问题是灵活性差,遇到没预设的需求就抓瞎。第三种是“让 AI 直接生成代码然后执行”:听起来很美,但安全性和可控性都是问题,万一它生成一句删库的代码呢。

MCP 的思路不太一样。它把“工具的定义”和“工具的调用”分开了。你作为开发者,负责定义工具的名字、参数、返回值;AI 负责根据用户的自然语言,决定调用哪个工具、传什么参数。执行过程在你的 MCP Server 里完成,AI 只拿到结果。这样一来,既保留了灵活性,又保证了可控性——因为所有能执行的操作都是你提前定义好的。

拿 Excel 场景来说,我可以定义这么几个工具:read_sheet读取指定工作表的数据,write_cell写入某个单元格,filter_rows按条件筛选行,create_chart创建图表。AI 收到“把销售额超过 5000 的行标黄”这个指令后,会先调用filter_rows拿到符合条件的行号,再调用write_cell或者一个专门的highlight_rows工具去标黄。整个过程我都能在日志里看到,出了问题也能追溯。

1.3 这个项目适合谁来参考

如果你满足下面任意一条,这个项目就值得你花时间看看。第一,你日常跟 Excel 打交道很多,厌倦了重复的复制粘贴和手动公式;第二,你有一点点 Python 基础,能看懂函数和类,但不算精通;第三,你对 AI Agent 感兴趣,想找个具体的场景练手,而不是停留在概念层面;第四,你想了解 MCP 协议到底怎么落地,而不是只看官方文档里的抽象描述。

我整个项目做下来,代码量不算大,核心逻辑大概三百多行,加上测试和调试大概花了一个周末。你如果有 Python 基础,跟着走一遍应该能在一到两天内跑通。下面我把整个思路、关键细节和踩过的坑都拆开讲。

2. 整体设计与技术选型

2.1 为什么选 Python 而不是其他语言

MCP 官方提供了多种语言的 SDK,包括 Python、TypeScript、Java 等。我选 Python 的原因很直接:Excel 处理的生态在 Python 里最成熟。openpyxl 能精确控制单元格样式、公式、图表;pandas 擅长数据分析和转换;xlwings 还能跟 Excel 应用本身交互。如果用 TypeScript 写 MCP Server,底层还是得调 Python 脚本或者找 Node 版的 Excel 库,多一层转发就多一层麻烦。

另外,Python 的 MCP SDK 用起来很顺手。它基于 asyncio,定义工具就是写一个带装饰器的函数,参数用类型注解标清楚,SDK 会自动生成 JSON Schema 给 AI 看。我试过用 TypeScript 写一个类似的,类型定义要写两遍,一遍给 TypeScript 编译器,一遍给 MCP 的 schema,比较啰嗦。

还有一个实际考虑:调试方便。Python 可以随时在 REPL 里试一段代码,看看 openpyxl 的某个 API 到底返回什么。TypeScript 虽然也能调试,但编译步骤和类型检查有时候会拖慢探索节奏。对于这种“边写边试”的项目,Python 的反馈循环更短。

2.2 MCP Server 的两种通信方式怎么选

MCP Server 和 Client 之间的通信主要有两种方式:stdio 和 SSE。stdio 就是标准输入输出,Client 启动 Server 进程,通过管道发消息;SSE 是 Server-Sent Events,走 HTTP 长连接,Server 可以独立运行,多个 Client 都能连。

我一开始想用 SSE,因为听起来更“现代”,而且可以部署到远程服务器上。但实际试下来,stdio 更适合我这个场景。原因有几个:第一,Excel 文件通常在本机,Server 跑在本机直接读写文件最方便,走 SSE 还得考虑文件路径映射;第二,stdio 的调试更简单,我可以在终端里直接跑 Server,看它打印的日志;第三,安全性更好,stdio 不暴露网络端口,不用担心别人连上来乱调工具。

当然,如果你想让多个 AI 助手共享同一个 Excel 处理服务,或者想把 Server 部署到一台专门的机器上,那 SSE 更合适。我后来也试了一下 SSE 模式,把 Server 跑在本地的一个端口上,用 curl 发请求测试,也能跑通。但日常使用我还是切回了 stdio,因为启动更快,不用管端口占用。

2.3 工具粒度怎么把握

这是设计 MCP Server 时最纠结的地方:工具应该切多细?切得太细,AI 要调用很多次才能完成一个任务,效率低;切得太粗,一个工具做太多事,AI 传参数容易出错,而且不好复用。

我一开始设计了一个“万能工具”叫process_excel,参数包括操作类型、目标工作表、条件、动作等等。结果发现 AI 经常传错参数,比如把“筛选”和“排序”的参数搞混。后来我把它拆成了几个独立的工具:read_sheet、filter_rows、sort_rows、write_data、highlight_rows、create_chart。每个工具只做一件事,参数也简单明了。

拆完之后,AI 的调用准确率明显上升。比如“把销售额超过 5000 的行标黄”这个指令,AI 会先调filter_rows拿到行号列表,再调highlight_rows传入行号和颜色。虽然多了一次调用,但每一步都可验证,出错也容易定位。

我的经验是:工具粒度应该跟“用户可能提出的最小操作单元”对齐。如果用户经常说“筛选并排序”,那可以考虑提供一个组合工具;但如果用户有时候只筛选、有时候只排序,那就拆开。另外,工具的返回值要尽量结构化,比如返回行号列表而不是一段文字描述,这样 AI 更容易理解。

2.4 数据在工具之间怎么传递

MCP 工具之间不能直接共享内存,每次调用都是独立的。这意味着如果filter_rows返回了行号列表,highlight_rows需要重新读取 Excel 文件来定位这些行。这听起来有点低效,但实际影响不大,因为 Excel 文件通常不大,openpyxl 读一个几万行的表也就几百毫秒。

不过有一个坑要注意:如果 AI 连续调用多个工具,而中间 Excel 文件被修改了,后面的工具可能读到不一致的数据。我的做法是让每个工具都接受一个file_path参数,并且在工具内部重新加载文件。这样虽然多了一些 IO,但保证了每次操作都是基于最新状态。如果你追求性能,可以加一个缓存层,但记得在写操作后失效缓存。

另一个问题是行号的定义。openpyxl 的行号是从 1 开始的,而 pandas 的索引是从 0 开始的。我在工具内部统一用 1-based 行号,并且在文档字符串里写清楚。AI 看到文档后一般不会搞错,但偶尔还是会传 0-based 的行号,所以我在代码里加了一个校验:如果行号小于 1,就自动加 1 并打一条警告日志。

3. 核心细节与实操要点

3.1 环境准备与依赖安装

先把环境搭起来。我假设你已经装了 Python 3.10 或更高版本,因为 MCP SDK 用了一些较新的 asyncio 特性。如果你还没装 Python,去官网下载安装包,安装时记得勾选“Add Python to PATH”。装完之后在终端里跑python --version确认一下。

接下来创建一个虚拟环境,这是好习惯,避免污染全局包。命令如下:

python -m venv mcp-excel-env source mcp-excel-env/bin/activate # Windows 用 mcp-excel-env\Scripts\activate

然后安装依赖。核心是三个包:mcp是 MCP SDK,openpyxl用来读写 Excel,pandas用来做数据处理。如果你还想生成图表,可以加一个matplotlib,但 openpyxl 自带的图表功能也够用。

pip install mcp openpyxl pandas

安装完成后,可以跑一个简单的测试确认 openpyxl 能用:

import openpyxl wb = openpyxl.Workbook() ws = wb.active ws['A1'] = 'hello' wb.save('test.xlsx') print('ok')

如果终端输出ok,说明环境没问题。这里有个小坑:有些系统上 openpyxl 的默认字体设置会导致中文显示异常,如果你处理的是中文 Excel,建议在保存时指定字体,或者用 pandas 的to_excel方法,它对中文的支持更好。

3.2 定义第一个 MCP 工具:读取工作表

MCP SDK 的核心是Server类和@server.tool()装饰器。下面是我写的第一个工具,用来读取指定工作表的数据:

from mcp.server import Server from mcp.server.stdio import stdio_server import openpyxl server = Server("excel-mcp") @server.tool() async def read_sheet(file_path: str, sheet_name: str, max_rows: int = 100) -> str: """读取 Excel 指定工作表的数据,返回 CSV 格式的文本。 Args: file_path: Excel 文件的绝对路径 sheet_name: 工作表名称 max_rows: 最多返回多少行,默认 100 """ wb = openpyxl.load_workbook(file_path, data_only=True) if sheet_name not in wb.sheetnames: return f"错误:工作表 {sheet_name} 不存在,可用的有 {wb.sheetnames}" ws = wb[sheet_name] rows = [] for i, row in enumerate(ws.iter_rows(values_only=True)): if i >= max_rows: break rows.append(','.join(str(cell) if cell is not None else '' for cell in row)) return '\n'.join(rows)

这个工具看起来简单,但有几个细节值得说。第一,data_only=True让 openpyxl 返回公式的计算结果而不是公式本身,这对 AI 理解数据很重要。第二,我加了max_rows参数,防止 AI 一次性拉取太多数据导致上下文爆炸。第三,返回值用 CSV 格式而不是 JSON,因为 CSV 更紧凑,AI 解析起来也更容易。

文档字符串(docstring)非常关键。MCP SDK 会把 docstring 里的描述和参数说明转成 JSON Schema 给 AI 看。我试过不写 docstring,结果 AI 完全不知道这个工具是干嘛的,调用时经常传错参数。所以每个工具都要写清楚:这个工具做什么、每个参数是什么意思、返回值是什么格式。

3.3 筛选与排序工具的实现

筛选和排序是 Excel 处理里最高频的操作。我一开始想用一个工具搞定,后来还是拆开了。先看筛选:

@server.tool() async def filter_rows(file_path: str, sheet_name: str, column: str, operator: str, value: str) -> str: """按条件筛选行,返回符合条件的行号列表。 Args: file_path: Excel 文件路径 sheet_name: 工作表名称 column: 列名(第一行的值) operator: 比较运算符,支持 gt, lt, eq, contains value: 比较的值 """ wb = openpyxl.load_workbook(file_path, data_only=True) ws = wb[sheet_name] headers = [cell.value for cell in ws[1]] if column not in headers: return f"错误:列 {column} 不存在,可用的有 {headers}" col_idx = headers.index(column) + 1 matched = [] for row_idx in range(2, ws.max_row + 1): cell_val = ws.cell(row=row_idx, column=col_idx).value if cell_val is None: continue if operator == 'gt' and float(cell_val) > float(value): matched.append(row_idx) elif operator == 'lt' and float(cell_val) < float(value): matched.append(row_idx) elif operator == 'eq' and str(cell_val) == value: matched.append(row_idx) elif operator == 'contains' and value in str(cell_val): matched.append(row_idx) return f"匹配到 {len(matched)} 行:{matched}"

这里有几个实操心得。第一,列名匹配我用的是精确匹配,但实际 Excel 里经常有空格或大小写差异,后来我改成了strip().lower()后再比较。第二,float(cell_val)在遇到非数字时会抛异常,我加了一个 try-except 包起来,遇到非数字就跳过。第三,返回行号列表而不是数据本身,这样 AI 可以决定下一步是标黄、删除还是提取。

排序工具类似,但要注意排序会改变行号,所以如果 AI 先筛选再排序,筛选得到的行号就失效了。我的做法是在排序工具的文档里明确写“排序后行号会变化,请重新筛选”,并且在代码里返回排序后的前几行数据,让 AI 知道新顺序。

3.4 写入与样式工具的实现

写入工具负责修改单元格的值,样式工具负责改颜色、字体、边框。这两个工具我分开写,因为写入是数据操作,样式是展示操作,混在一起容易乱。

@server.tool() async def write_cell(file_path: str, sheet_name: str, row: int, column: int, value: str) -> str: """写入单个单元格。 Args: file_path: Excel 文件路径 sheet_name: 工作表名称 row: 行号,从 1 开始 column: 列号,从 1 开始 value: 要写入的值 """ wb = openpyxl.load_workbook(file_path) ws = wb[sheet_name] ws.cell(row=row, column=column, value=value) wb.save(file_path) return f"已写入 {sheet_name} 的 ({row}, {column})"

样式工具稍微复杂一点,因为 openpyxl 的样式 API 比较绕:

from openpyxl.styles import PatternFill @server.tool() async def highlight_rows(file_path: str, sheet_name: str, rows: list[int], color: str = "FFFF00") -> str: """将指定行标黄。 Args: file_path: Excel 文件路径 sheet_name: 工作表名称 rows: 行号列表 color: 十六进制颜色,默认黄色 """ wb = openpyxl.load_workbook(file_path) ws = wb[sheet_name] fill = PatternFill(start_color=color, end_color=color, fill_type="solid") for row_idx in rows: for col_idx in range(1, ws.max_column + 1): ws.cell(row=row_idx, column=col_idx).fill = fill wb.save(file_path) return f"已标黄 {len(rows)} 行"

这里有个性能坑:如果行数很多,逐单元格设置样式会很慢。我试过标黄 1000 行,花了将近 10 秒。后来改成先收集所有单元格,再用ws.iter_rows批量设置,速度快了不少。另外,PatternFill的颜色参数是 ARGB 格式,前两位是透明度,FFFF00其实是“不透明的黄色”,如果你写FF0000会得到红色。

3.5 把 Server 跑起来

工具定义完之后,需要写一个入口函数来启动 Server:

async def main(): async with stdio_server() as (read_stream, write_stream): await server.run(read_stream, write_stream, server.create_initialization_options()) if __name__ == "__main__": import asyncio asyncio.run(main())

跑起来之后,Server 会等待 Client 发消息。如果你用的是 Claude Desktop 或者其他的 MCP Client,需要在配置文件里指定这个 Server 的启动命令。比如在 Claude Desktop 的配置里加一段:

{ "mcpServers": { "excel": { "command": "python", "args": ["/path/to/your/server.py"] } } }

配置完之后重启 Client,它就会自动启动这个 Server。你可以在对话里说“读取 test.xlsx 的 Sheet1”,AI 就会调用read_sheet工具。如果没反应,先检查 Server 能不能在终端里独立跑起来,再看 Client 的日志。

4. 实操过程与核心环节

4.1 从零跑通第一个指令

环境搭好、代码写完之后,我做的第一件事是跑一个最简单的指令:“读取 test.xlsx 的 Sheet1 的前 5 行”。为了测试,我先用 openpyxl 造了一个测试文件:

import openpyxl wb = openpyxl.Workbook() ws = wb.active ws.title = "Sheet1" ws.append(["姓名", "部门", "销售额"]) ws.append(["张三", "华东", 5200]) ws.append(["李四", "华南", 3800]) ws.append(["王五", "华东", 6100]) ws.append(["赵六", "华北", 4500]) wb.save("test.xlsx")

然后在 Client 里发指令。第一次跑的时候,AI 回复说“无法找到文件”,我检查了一下,发现是文件路径的问题——Client 的工作目录跟我终端的不一样。后来我把路径改成绝对路径,问题解决。这个坑很常见,建议在工具里加一个路径校验,如果文件不存在就返回明确的错误信息,而不是让 openpyxl 抛异常。

第二次跑,AI 成功调用了read_sheet,返回了 CSV 格式的数据。但我发现它把表头也当成了数据行,因为我的max_rows是从第一行开始数的。后来我改了一下,让read_sheet默认跳过第一行(表头),或者加一个include_header参数。这个细节虽小,但影响 AI 对数据的理解。

4.2 一个完整的处理流程:筛选、标黄、汇总

跑通读取之后,我试了一个完整的流程:“把销售额超过 5000 的行标黄,然后统计华东区的总销售额”。AI 的执行过程是这样的:

第一步,调用filter_rows,参数是column="销售额",operator="gt",value="5000"。返回[2, 4],因为张三的 5200 和王五的 6100 都超过 5000。

第二步,调用highlight_rows,参数是rows=[2, 4]。返回“已标黄 2 行”。

第三步,调用filter_rows,参数是column="部门",operator="eq",value="华东"。返回[2, 4]。

第四步,AI 需要计算这两个行的销售额之和。但我的工具里没有“求和”功能,所以 AI 只能再调用read_sheet拿到数据,然后自己算。这暴露了一个问题:工具集不够完整。后来我加了一个sum_column工具,接受行号列表和列名,直接返回求和结果。

这个流程跑下来,我最大的感受是:AI 的规划能力比我想象的强,但它依赖工具提供的“原子能力”。如果你只给了筛选和标黄,它就没法做求和;如果你给了求和但没给筛选,它就得先把所有数据拉出来自己算。所以工具集的设计要尽量覆盖常见的操作组合,但又不能太粗。

4.3 参数计算与选择:max_rows 设多少合适

max_rows这个参数我调了好几次。一开始设的是 1000,结果 AI 经常一次性拉取上千行数据,上下文直接被撑爆,后面的对话就乱了。后来我降到 100,发现大部分场景够用,因为 AI 通常只需要看前几行来理解数据结构,具体处理靠工具完成。

但有些场景确实需要看更多数据,比如“找出所有销售额为空的记录”。这时候 100 行不够,AI 会反复调用read_sheet并翻页。我的解决方案是加一个offset参数,让 AI 可以分页读取。同时,我在文档里建议 AI“先用 filter_rows 缩小范围,再读取具体行”。

另一个参数是color。我一开始只支持黄色,后来发现用户有时候想标红、标绿。改成十六进制颜色之后,AI 需要知道颜色代码,这增加了它的认知负担。折中方案是提供几个预设颜色名:yellow、red、green、blue,在代码里映射到对应的十六进制值。这样 AI 只需要传color="red",不用记颜色代码。

4.4 实操现场:一次失败的调试记录

有一次我改了一个工具的返回值格式,从“匹配到 3 行:[2, 4, 6]”改成了纯列表[2, 4, 6]。结果 AI 收到之后,不知道这个列表是什么意思,把它当成了数据本身,回复用户说“销售额是 2、4、6”。这个错误让我意识到:工具的返回值不仅要结构化,还要有自描述性。

后来我把返回值改成 JSON 格式:

{"matched_rows": [2, 4, 6], "count": 3, "message": "匹配到 3 行"}

这样 AI 能清楚地知道每个字段的含义。虽然 JSON 比纯文本占更多 token,但准确性提升明显,值得。

还有一个调试技巧:在 Server 里加日志。MCP SDK 默认不打印工具调用的详细信息,我加了一个装饰器,在每次工具调用前后打印参数和返回值:

import functools def log_tool(func): @functools.wraps(func) async def wrapper(*args, **kwargs): print(f"[TOOL] {func.__name__} called with {kwargs}") result = await func(*args, **kwargs) print(f"[TOOL] {func.__name__} returned: {result[:200]}") return result return wrapper

然后把它加到每个工具上。这样在终端里就能看到 AI 到底调了什么、传了什么参数、返回了什么。调试的时候非常有用。

5. 常见问题与排查技巧

5.1 工具调用失败排查表

现象可能原因排查方法解决方案
AI 说找不到工具Server 没启动或配置错误检查 Client 日志,看 Server 进程是否在跑确认配置文件路径正确,手动在终端跑 Server
工具调用返回空文件路径不对或工作表名错误在工具里打印实际路径和 sheetnames用绝对路径,加路径校验
AI 传错参数类型docstring 描述不清看日志里 AI 传的参数在 docstring 里写明类型和示例
写入后文件损坏多个工具同时写同一个文件检查是否有并发调用加文件锁,或者串行化写操作
中文乱码编码问题用 pandas 读取看是否正常指定 encoding='utf-8',或用 pandas 替代 openpyxl
样式不生效颜色格式错误检查 PatternFill 参数用 ARGB 格式,前两位是透明度
行号对不上0-based 和 1-based 混用在工具里打印实际行号统一用 1-based,加校验

这个表是我踩坑之后整理的,基本上覆盖了八成的问题。其中“行号对不上”是最常见的,因为 pandas 和 openpyxl 的行号规则不一样,AI 有时候会搞混。我的做法是在所有涉及行号的工具里,第一行代码就是校验:如果行号小于 1,自动加 1 并打日志。

5.2 性能优化的几个实用技巧

Excel 文件大了之后,openpyxl 的读写会变慢。我试过处理一个 5 万行的文件,读取花了 3 秒,写入花了 5 秒。对于交互式场景,这个延迟有点难受。后来我做了几个优化:

第一,用read_only=True模式读取。openpyxl 默认会把整个文件加载到内存,read_only模式是流式读取,内存占用小,速度快。但缺点是只能顺序读,不能随机访问。对于read_sheet这种顺序读取的场景,read_only很合适。

第二,写入时用write_only=True模式。这个模式创建一个新的工作簿,只能追加行,不能修改已有单元格。对于“生成新文件”的场景很快,但对于“修改现有文件”就不适用。

第三,批量操作代替逐个操作。比如标黄 1000 行,逐个设置样式很慢,改成先收集所有单元格对象,再统一设置。openpyxl 的iter_rows返回的是生成器,可以一次性拿到所有行。

第四,缓存工作簿对象。如果 AI 连续调用多个工具操作同一个文件,每次重新加载很浪费。我加了一个简单的缓存:用文件路径和修改时间做 key,缓存工作簿对象。写操作后清除缓存。这样连续读操作就快了。

5.3 安全性与边界处理

MCP Server 跑在本机,理论上只能操作本机文件,但还是要加一些边界检查。第一,限制可访问的目录。我在工具里加了一个ALLOWED_DIR环境变量,如果文件路径不在这个目录下,直接拒绝。第二,限制文件大小。超过 50MB 的 Excel 文件直接返回错误,避免内存爆炸。第三,限制操作行数。highlight_rows最多标黄 10000 行,超过就拒绝,防止 AI 误操作把整个表标黄。

还有一个容易被忽略的点:公式注入。如果 AI 写入的值以=开头,openpyxl 会把它当成公式。这本身没问题,但如果 AI 被诱导写入恶意公式,可能会有风险。我的做法是在write_cell里加一个检查:如果值以=开头,在前面加一个单引号,强制当成文本。如果确实需要写公式,提供一个单独的write_formula工具。

5.4 独家避坑心得

第一个心得:不要相信 AI 会记住上下文。我试过在一个对话里先让 AI 读取 Sheet1,然后说“把刚才那些行标黄”。结果 AI 忘了“刚才那些行”是哪些,因为它没有保存行号。后来我让 AI 在需要跨步骤操作时,先把中间结果写到一个临时文件或者返回给用户确认。这样虽然多了一步,但避免了“AI 失忆”的问题。

第二个心得:工具的命名要一致。我一开始有的工具叫read_sheet,有的叫get_data,有的叫fetch_rows。AI 经常搞混,调用时传错工具名。后来我统一成“动词_名词”的格式:read_sheet、write_cell、filter_rows、sort_rows、highlight_rows、create_chart。命名一致之后,AI 的调用准确率明显提升。

第三个心得:给 AI 留“退路”。有些操作可能失败,比如筛选时列名不存在。如果工具直接抛异常,AI 会收到一个错误堆栈,它不知道怎么处理。我的做法是返回结构化的错误信息,比如{"error": "column_not_found", "available_columns": ["姓名", "部门", "销售额"]}。这样 AI 可以自己纠正,比如换一个列名重试。

第四个心得:测试用例要覆盖“坏输入”。我写了一个测试脚本,故意传空文件路径、不存在的 sheet 名、非数字的筛选值,看工具怎么反应。结果发现好几个工具直接崩了。后来我加了 try-except,把所有异常转成友好的错误信息。这个习惯在 MCP 开发里特别重要,因为 AI 不会像人一样“小心操作”,它可能会传各种奇怪的参数。

6. 后续可以怎么扩展

6.1 加入图表生成能力

openpyxl 支持创建柱状图、折线图、饼图。我后来加了一个create_chart工具,接受数据范围、图表类型、存放位置,生成图表并插入到指定工作表。实现的时候要注意:图表的引用范围要用Reference对象,而且数据区域和类别区域要分开指定。这个工具让 AI 可以完成“生成月报”这种端到端的任务。

6.2 支持多文件批量处理

现在的工具都是针对单个文件的。如果用户说“把这三个文件都处理一下”,AI 需要循环调用。我后来加了一个batch_process工具,接受文件列表和一个操作描述,内部循环执行。但这样又回到了“工具太粗”的问题。折中方案是提供一个list_files工具,让 AI 先拿到文件列表,再逐个调用单文件工具。这样既灵活又可控。

6.3 接入更多数据源

Excel 只是起点。同样的 MCP 架构可以扩展到 CSV、JSON、数据库。我后来把read_sheet抽象成了一个read_data工具,根据文件扩展名自动选择解析器。这样 AI 不需要知道底层是 Excel 还是 CSV,只需要说“读取这个文件的数据”。这个抽象让工具集更通用,但也增加了实现的复杂度,需要权衡。

6.4 用 AI 自动生成工具描述

工具的 docstring 是给 AI 看的,写得好不好直接影响调用准确率。我试过让 AI 自己根据函数签名生成 docstring,效果还不错。具体做法是:写一个脚本,读取所有工具的签名,调用 AI 生成描述,然后人工审核。这样比手写快,而且 AI 更了解“AI 需要什么信息”。不过要注意,生成的描述要经过人工检查,避免出现误导性的内容。

6.5 把 Server 打包成可执行文件

如果你想让不懂 Python 的人也能用,可以用 PyInstaller 把 Server 打包成单个可执行文件。这样用户不需要装 Python 和依赖,直接运行 exe 就行。打包的时候要注意把 openpyxl 的数据文件也包含进去,否则会报“找不到样式表”的错误。我试过打包,生成的 exe 大概 15MB,启动速度比 Python 脚本慢一点,但可以接受。

整个项目做下来,我最大的体会是:MCP 不是一个“高大上”的东西,它就是一个让 AI 和外部工具对话的协议。你不需要理解协议的所有细节,只需要把工具定义清楚、把参数校验做好、把错误处理完善,就能做出一个可用的 MCP Server。Excel 处理只是一个切入点,同样的思路可以用在文件管理、数据库查询、API 调用等任何场景。如果你也想动手做一个,建议从最小的工具开始,跑通之后再逐步扩展。遇到问题先看日志,再查文档,最后才改代码——这个顺序能帮你省很多时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 9:10:36

AI安全运营闭环:告警降噪、自动响应与攻击图谱实践

简介&#xff1a;这份PPT围绕人工智能在网络安全领域的落地实践展开&#xff0c;面向安全从业者、企业信息化负责人及希望了解智能安全运营的学习者&#xff0c;系统梳理传统防护在恶意软件检测、数据泄露和人才短缺等方面的痛点&#xff0c;并给出AI驱动的破局思路。内容以智能…

作者头像 李华
网站建设 2026/11/2 9:09:36

Spring Boot 构建生产级 AI 应用平台:核心技术与实战避坑

1. 为什么要在 Spring Boot 里做 AI 应用平台1.1 从“能跑通”到“能上线”之间隔着什么我见过太多团队做 AI 应用的路径是这样的&#xff1a;先用 Python 写个脚本调一下模型接口&#xff0c;跑通了&#xff0c;效果不错&#xff0c;然后老板说“给我做成一个产品”。这时候问…

作者头像 李华
网站建设 2026/11/2 9:09:32

OpenCloudOS与openEuler混合环境Ansible安装配置实战

1. 先搞清楚&#xff1a;这两套系统上的 Ansible 到底难在哪一批新机器上架&#xff0c;系统盘里装的不是同一个发行版——一部分是 OpenCloudOS&#xff0c;一部分是欧拉&#xff08;openEuler&#xff09;&#xff0c;版本还横跨 8 和 9、20.03 和 22.03。机器到位之后&#…

作者头像 李华
网站建设 2026/11/2 9:08:44

AI写3D游戏实测:三大代码模型生成Three.js寻宝迷宫对比

最近我把 Step 5 Preview、DeepSeek V4 Pro 和 GLM5.3 拉到同一条起跑线上&#xff0c;让它们分别写同一个 3D 游戏。任务很简单粗暴&#xff1a;一个基于 Three.js 的浏览器 3D 寻宝迷宫&#xff0c;第一人称视角&#xff0c;有碰撞、有胜利判定、还要求能塞进手机浏览器里跑得…

作者头像 李华
网站建设 2026/11/2 9:08:20

基于斑马优化算法的风电功率预测模型优化

1. 项目背景与核心价值风电功率预测在新能源并网调度中扮演着关键角色。传统BP神经网络因其强大的非线性拟合能力被广泛采用&#xff0c;但存在收敛速度慢、易陷入局部最优等问题。斑马优化算法(ZOA)作为新型仿生优化算法&#xff0c;通过模拟斑马群觅食行为中的社会等级机制和…

作者头像 李华
网站建设 2026/11/2 9:05:32

OpenCV任意角度图像旋转原理与工程实践指南

简介&#xff1a;本资源是一份基于OpenCV实现图像旋转的完整C工程实践包&#xff0c;面向计算机视觉初学者、图像处理学习者及C开发人员&#xff0c;解决图像方向调整、预处理与增强中的核心旋转操作问题。压缩包共14个文件&#xff0c;包含核心源码myRoteteImage.cpp、可执行程…

作者头像 李华