
上个月我把手里的Excel处理工作流整个重写了一遍。以前处理销售明细、清洗重复行、按部门汇总、给特定条件加背景色这些活我基本靠VBA宏加Python脚本轮着来改一个字段就要改半天代码换台电脑还得重新配环境。现在我把MCP接进了Excel工作流AI直接在对话里读表、算数、写结果那些重复操作从几十分钟压缩到几十秒。这篇就是我自己开发第一个MCP的完整记录包括协议原理、代码实现、接入AI客户端的方法以及我在实际调试中踩过的坑。适合每天被Excel重复操作消耗时间的人也适合想上手MCP但不知道从哪切入的开发者。你不需要一次性理解所有协议细节跟着步骤抄作业就能先跑起来。1. 为什么拿MCP改造Excel工作流不只是“让AI帮忙”1.1 传统Excel自动化方案的三个痛点先说痛点不然你不知道这个改造到底值不值。很多人想到自动化Excel第一反应是VBA宏。VBA写起来确实能干活但它绑死在Excel环境里你没法用自然语言描述“把这个表按日期升序排一下再把金额大于1000的行标红”你得自己写Range.Sort、Range.Interior.Color。一旦业务规则变化改宏的成本比重新做一遍还高。第二种常见做法是Python脚本。openpyxl、pandas确实强大但问题在于“翻译需求”这个环节。业务同事跟我说要统计各区域平均客单价我得先确认口径再把“区域”“销售额”“订单数”映射成列名然后写脚本、跑结果、反馈。中间沟通一次就浪费一次时间。更麻烦的是脚本之间经常互相覆盖你今天写了个清洗脚本明天又要写个合并脚本逻辑碎片散落各处。第三种是RPA。RPA适合模拟鼠标键盘操作但处理Excel时它还是“录屏式”的表格结构一变就失灵。而且RPA产品通常很重授权贵、运行慢为了一个简单读取任务启动整个机器人有点大炮打蚊子。这三个方案的共同问题是自动化逻辑绑定在具体实现上需求变化就要动代码。MCP给我的解法是把“语义层”抽出来AI负责理解需求工具负责执行动作我只需要维护一组小而独立的Excel工具函数。用户说的是“找出每个产品销售额最高的日期”AI会自动拆解成“读取表格–按产品分组–比较销售额–挑出最大值–写回结果”中间不需要我再翻译成API调用。1.2 MCP到底在解决什么问题MCP全称是Model Context Protocol模型上下文协议。它定义了一套通用的通信方式让AI应用可以调用外部的工具和数据源就像U盘必须符合USB接口标准才能插进电脑一样。MCP就是AI生态里的“USB-C接口”不管你的MCP客户端是Claude Desktop、Cursor还是自己写的Agent只要服务端遵循MCP规范就能被统一调用。从技术实现上看MCP基于JSON-RPC 2.0通信客户端和服务端之间主要交互三类能力Resources资源给AI读取数据、Tools工具让AI执行动作、Prompts提示词模板可复用特定指令。在Excel这个场景里我们最核心的就是Tools。AI收到用户请求后根据工具描述决定调用哪个函数、传什么参数函数执行完返回结构化结果AI再把结果转成自然语言回给用户。有人会问直接给AI扔一个Excel文件让它“看一眼”不就行了实际问题是大模型上下文窗口有限一个稍大的sheet就是成千上万个Token直接塞给AI既贵又慢。MCP的价值就在于把文件操作变成工具调用AI只接收工具返回的摘要、统计结果或特定区域数据“大海捞针”这种脏活累活交给代码去处理。1.3 自己开发而不是直接用现成Excel MCP我在动手之前也搜过现成的Excel MCP Server社区里确实有能直接用的一些方案大体上能完成基础读写。但我最后还是决定自己写原因有三点。第一现成方案的工具粒度常常不对要么太大读写都封装成一个工具AI不好编排要么太小一个单元格操作都要单独调用效率很低。第二每个公司的Excel模板不一样我手里的报表有固定的表头、合并单元格和特殊命名规则这些定制逻辑只有自己写才顺手。第三安全边界很重要我不希望随便一个现成Server暴露太多系统能力我只允许它操作一个指定目录下的文件这个限制在开源代码里很容易写清楚。技术选型上我用Python而不是Node。原因很实际Python的openpyxl和pandas是Excel处理的事实标准文档全、踩坑资料多而且MCP官方Python SDK的FastMCP封装非常简洁几十行代码就能起一个服务。Node那边也有不错的SDK但如果你是数据处理出身Python学习成本更低。2. 准备工作设计你的Excel处理工作流2.1 先画清楚你要自动化哪些操作不要一上来就写代码先把你日常的Excel任务列出来分个类。我自己的高频操作大概有这么几类读取与查看查看有哪些工作表、某个区域的数据长什么样。清洗与转换去重、缺失值处理、Markdown表格转Excel、列类型纠正。统计与汇总分组求和、求平均值、按列取最大值、透视表效果。格式化条件标色、设置列宽、填充序号。写入与更新新建sheet、追加数据、修改指定单元格、把计算结果写回。分类之后你会发现自己真正需要的工具函数不超过十来个。不要试图一个函数里装所有功能AI工具调用讲究“单一职责”一个工具做一件事描述清楚输入输出AI才能像搭积木一样组合出复杂工作流。举个例子我之前接到过一个需求在一个员工名单里同一姓名可能出现多次需要给每个姓名保留“工资”列的最大值那一行。这个用日常操作很烦但如果我提供一个aggregate_max工具AI只需要调一次传入关键列名和取值列名就能返回结果。这个工具的设计源自真实需求不是凭空想象。另外要圈定边界哪些操作交给AI哪些必须人工确认我的原则是“读可以放开写要谨慎”。读操作随便AI折腾但涉及覆盖原文件、删除sheet、批量修改格式这类破坏性操作我要求工具必须支持dry_run参数或输出预览确认无误后再真正写入。后面会详细讲实现。2.2 环境准备与依赖安装我用的是Python 3.11Windows和macOS都跑过。先创建虚拟环境再装依赖python -m venv venv source venv/bin/activate # Windows下用 venv\Scripts\activate pip install mcp openpyxl pandas如果Python环境里有uv也可以用uv add mcp openpyxl pandas速度会快不少。这里mcp就是官方Python SDKopenpyxl负责Excel读写pandas不是必须的但做分组统计时确实方便我建议一起装上。目录结构上我建议单独建一个项目目录比如excel-mcp-server/下面放主程序和服务配置。因为MCP客户端会通过进程启动这个Server保持路径干净能少很多排查麻烦。excel-mcp-server/ ├── excel_mcp_server.py ├── requirements.txt ├── data/ # 只允许操作这个目录下的文件 └── output/ # 生成的结果都放这里2.3 工具函数清单我把自己最终设计的工具列成一张表供你参考。工具名就是AI看到的名称描述是AI判断是否调用该工具的依据。工具名用途关键参数返回结果list_sheets列出所有工作表名称pathlist[str]read_excel读取指定区域数据path, sheet, max_rows, max_cols文本表格write_excel把二维数据写入新表或追加path, sheet, data, mode写入状态aggregate_max按某列分组统计另一列最大值path, key_col, value_col分组结果conditional_fill按条件给单元格标色path, col, condition, color操作说明markdown_to_excel把Markdown表格转成Excelmarkdown_text, output_path保存路径create_chart基于数据区域生成图表path, sheet, chart_type图表位置这张表不是固定的你可以按自己业务增删。我的建议是宁可工具多一点也不要让AI在一个工具里绕来绕去。工具描述要写清楚“什么时候用”“参数代表什么”这两点对AI调用的准确率影响巨大后头我专门再说。3. 从零实现一个Excel MCP Server3.1 搭起FastMCP服务端骨架FastMCP的封装非常友好我们不需要手动处理JSON-RPC的请求分发只需要注册工具然后启动服务。主程序骨架长这样from mcp.server.fastmcp import FastMCP mcp FastMCP(excel-mcp-server) mcp.tool() def list_sheets(path: str) - list[str]: 列出Excel文件中所有工作表的名称。 Args: path: Excel文件的完整路径。 from openpyxl import load_workbook wb load_workbook(path, read_onlyTrue) try: return wb.sheetnames finally: wb.close() mcp.tool() def read_excel(path: str, sheet: str None, max_rows: int 200, max_cols: int 50) - str: 读取Excel指定区域的数据返回为文本表格。 Args: path: Excel文件路径。 sheet: 工作表名称默认读取第一个工作表。 max_rows: 最多读取行数防止一次性读太多数据。 max_cols: 最多读取列数。 from openpyxl import load_workbook from openpyxl.utils import get_column_letter wb load_workbook(path, read_onlyTrue, data_onlyTrue) try: ws wb[sheet] if sheet else wb.active rows [] for i, row in enumerate(ws.iter_rows(min_row1, max_rowmax_rows, max_colmax_cols, values_onlyTrue)): if i max_rows: break rows.append(row) # 转成类似CSV的字符串便于大模型阅读 return \n.join(,.join(str(c) if c is not None else for c in row) for row in rows) finally: wb.close()注意我在read_excel里默认最多读200行50列这个限制很重要。如果不加限制一个几万行的工作表全量读出来既占内存也会把大模型上下文塞爆。AI想要更多数据时可以通过max_rows参数继续分段读也能让它根据前200行推断整体结构。3.2 核心工具二写入与更新Excel读取搞定后写入工具自然少不了。我的write_excel支持覆盖模式和追加模式mcp.tool() def write_excel(path: str, sheet: str, data: list[list[str]], mode: str overwrite) - str: 把二维数据写入Excel工作表。 Args: path: Excel文件路径。 sheet: 工作表名称。 data: 二维数组第一行为表头。 mode: overwrite表示覆盖原表append表示追加到原表末尾。 from openpyxl import load_workbook, Workbook import os if os.path.exists(path): wb load_workbook(path) else: wb Workbook() if sheet in wb.sheetnames: ws wb[sheet] else: ws wb.create_sheet(sheet) if mode overwrite: ws.delete_rows(1, ws.max_row) for row in data: ws.append(row) wb.save(path) return f已写入 {len(data)} 行到 {path} 的 {sheet} 工作表这里有个隐藏细节直接ws.delete_rows(1, ws.max_row)会把原表数据清空但样式不一定能完全清干净。如果原表有合并单元格或特殊背景色覆盖后样式可能残留。为了稳妥我在实际项目里用的是ws.delete_rows后再ws.delete_cols或者干脆重建一个同名的sheet。重建sheet最简单但也会丢失全部样式看你的需求取舍。写入之前我一直建议工具先干检查目标文件是否在允许目录下、工作表是否存在、数据格式是否合法。这些检查在Server端做一次比每次都在提示词里约束AI靠谱得多。3.3 核心工具三数据清洗与分组统计这个工具是我用得最多的因为它解决的问题非常具体同一列里有重复名称要按名称分组选出另一列最大值。比如员工名单按姓名分组取工资最大值那一行。mcp.tool() def aggregate_max(path: str, key_col: str, value_col: str, sheet: str None) - str: 按key_col分组返回每组value_col的最大值。 Args: path: Excel文件路径。 key_col: 分组依据的列标题。 value_col: 需要求最大值的列标题。 sheet: 工作表名称默认第一个。 from openpyxl import load_workbook wb load_workbook(path, read_onlyTrue, data_onlyTrue) try: ws wb[sheet] if sheet else wb.active rows list(ws.iter_rows(values_onlyTrue)) if not rows: return 表格为空 header rows[0] key_idx header.index(key_col) val_idx header.index(value_col) result {} for row in rows[1:]: if row[key_idx] is None: continue key str(row[key_idx]).strip() val row[val_idx] if key not in result or val result[key]: result[key] val lines [f{key},{value} for key, value in result.items()] return \n.join(lines) finally: wb.close()这个函数本身不难但要注意几个易错点。第一read_onlyTrue模式下ws.iter_rows返回的行是只读的不能原地修改。第二表头里如果存在空格AI传参时可能对不上我建议在函数内部先用strip()做匹配。第三Excel里的数字在读出来时可能是字符串直接比大小会出错所以实际代码里我会加一个_to_number的尝试转换。这个小问题等会在坑点里详细说。3.4 再补一个Markdown表格转Excel这个工具最初是配合AI写作场景做的。很多AI会把结构化数据输出成Markdown表格但我们最终要交付Excel。那我干脆给Server加一个markdown_to_excel让整个流程闭环。mcp.tool() def markdown_to_excel(markdown_text: str, output_path: str output/md_table.xlsx) - str: 把Markdown格式的表格文本转换为Excel文件。 Args: markdown_text: 包含Markdown表格的原始文本。 output_path: 生成的Excel文件路径。 from openpyxl import Workbook lines [line.strip() for line in markdown_text.strip().splitlines() if line.strip().startswith(|)] rows [] for line in lines: cols [c.strip() for c in line.strip().strip(|).split(|)] # 跳过分隔行例如 |---|---| if all(set(c.replace(:, ).replace(-, ).strip()) set() for c in cols): continue rows.append(cols) if not rows: return 未找到有效的Markdown表格 wb Workbook() ws wb.active for row in rows: ws.append(row) wb.save(output_path) return f已生成 {output_path}注意我这个解析逻辑很“朴素”它要求每行都以|开头并且用|分割列。实际使用中AI输出的表格一般比较规整这个解析器能覆盖大部分场景。如果你遇到单元格内容里本身包含竖线解析就会出错那更建议用pandas的read_html或pd.read_clipboard做兜底。3.5 运行服务端与调试代码写完直接在命令行运行python excel_mcp_server.py不过mcp.run()默认使用stdio传输服务启动后不会在终端打印“listening on port”之类的东西它会等待客户端通过标准输入输出发送JSON-RPC请求。如果你在终端启动后发现“好像卡住了”不用慌这是正常的说明它在等客户端。如果你想单独测试工具函数可以在本地写个if __name__ __main__:分支直接调用函数。MCP协议调试时我更推荐用官方提供的mcp dev命令它会启动一个简易调试面板能把工具列表、参数、调用结果可视化。没有这个面板调试速度至少要慢一半。4. 把MCP Server接入AI客户端让工作流真正跑起来4.1 在客户端里添加MCP ServerServer写完了需要把它注册到AI客户端里。以支持MCP的桌面客户端为例配置一般写在claude_desktop_config.json或类似位置的配置文件里{ mcpServers: { excel-server: { command: python, args: [/path/to/excel_mcp_server.py], cwd: /path/to/excel-mcp-server } } }如果你用了虚拟环境command必须写虚拟环境里的Python绝对路径不能简单写python否则客户端可能找不到解释器。macOS下可能是/path/to/venv/bin/pythonWindows下是C:\\path\\to\\venv\\Scripts\\python.exe。我一开始在Windows上就是没注意这一点客户端始终报连接失败折腾了很久。配置完成后重启AI客户端再打开MCP相关面板你就能看到工具列表比如list_sheets、read_excel、write_excel。如果列表里看不到工具说明Server可能启动失败或配置路径有问题去终端手工跑一下server程序看有没有报错。4.2 用对话完成一次完整Excel处理接入成功后AI自然就能调用这些工具了。给你看一个典型流程假设我有一个销售明细.xlsx里面包含日期、产品、金额、区域四个字段。我只需要对AI说“读取销售明细.xlsx查看一下有哪些工作表和数据概况。”AI会先后调用list_sheets和read_excel拿到数据后它会格式化地告诉我表格结构、前几行数据、大概有多少列。接下来我再说“按产品分组统计销售额总和并找出每个产品销售额最高的日期把结果写入新文件命名为产品统计.xlsx。”这句话并没有指定具体代码AI会自动规划先read_excel读取数据然后可能在工具里做聚合也可能让模型自己通过返回的文本计算最后调用write_excel把结果写入目标路径。整个过程我没有写一行代码这就是工作流重构后的体验。不过也别抱有不切实际的幻想。AI对Excel的理解依赖工具返回的文字如果你的原始表里有大量合并单元格、隐藏行、非法字符AI仍然会遇到困难。所以工具函数的质量远比提示词重要函数说明要写清楚用途、参数、返回值格式这决定了AI能不能正确编排。4.3 多工具组合出复杂工作流单个工具调用是基础真正高效的是让AI连续调用多个工具形成“感知–决策–执行–反馈”的循环。举个例子我经常要做的周报工作流是这样的list_sheets和read_excel读取原始业务数据让AI建议清洗方案再由aggregate_max实现去重或取最大值用write_excel把清洗结果写到临时文件对临时文件做一轮统计用conditional_fill给异常值标红最后输出一份简短的统计结论说明哪些产品增长异常。这套流程完全由自然语言驱动AI会自己决定工具的先后顺序。如果中途某个工具返回值不符合预期AI还能调整参数学着重试。这就是MCP工作流和传统脚本的最大区别传统脚本是固定流水线MCP工作流更像是给了一个乐高工具箱AI根据目标自由拼装。5. 实际运行中的常见问题与避坑手册5.1 Server启动成功但客户端提示连接失败这个坑我至少踩过三次基本都是路径问题。检查三件事第一command是否用了绝对路径第二args里的脚本路径是否正确第三客户端是否加载了新的配置文件需要完全退出重启。还有一个很容易被忽略的点是环境变量如果你在配置文件里设置了cwd要确保工作目录下能访问到相关依赖否则子进程启动时会报“ModuleNotFoundError”。调试建议先手动在终端执行命令比如python /path/to/excel_mcp_server.py看是否报错。如果命令本身能跑起来再检查客户端的日志。很多客户端会把子进程的stdout输出显示在日志里报错原因一目了然。5.2 工具调用成功文件却没有任何变化这是另一个高发问题。原因通常有三类第一openpyxl的wb.save(path)没有执行成功代码抛了异常但AI只看到了异常信息没看到具体错误第二文件被另一个Excel进程锁定Windows常见保存时权限错误第三你把结果写到了工作目录的临时文件而不是你指定的目标文件。检查方法很简单让AI打印工具的返回值一般返回里包含“已写入多少行”或者明确的异常信息。另外建议在工具内捕获异常把异常转换为字符串返回这样AI能把错误原因反馈给你。不要在工具里静默pass异常那会让整个排错过程变得非常痛苦。5.3 大Excel处理超时或内存爆掉表格超过几万行后一次性读取很容易把内存吃满。我的应对策略是给read_excel工具加上max_rows限制并在工具描述里注明“默认只读取前200行需要更多数据请调整参数”。统计类工具尽量把聚合放在函数内部完成让AI只拿到聚合结果而不是原始数据。如果你确实需要读大文件做分析推荐用polars或pandas先做压缩再交给AI。不要试图把所有明细都塞进对话上下文你花的是Token等的是时间得到的往往还是幻觉。5.4 openpyxl的公式和数据缓存问题Excel公式在openpyxl里有个经典问题load_workbook时如果不设置data_onlyTrue读到的单元格是公式字符串比如SUM(A1:A10)设置data_onlyTrue后读到的则是公式的缓存结果。这个缓存结果只有在Excel软件打开并保存过文件后才会存在如果你用Python直接生成的公式单元格缓存值可能是None。因此我在read_excel里默认用data_onlyTrue但遇到某些由脚本生成的动态报表时还是会读到None。这时候我会提醒AI读到空值不代表单元格真的为空可能是有公式但没缓存。写公式时只要你以开头openpyxl就会把它当成公式写入这个倒是很直接。5.5 权限控制和防误操作设计这是我最想强调的一点。MCP工具和普通API不一样AI会根据你的描述自动执行工具如果没有边界它可能把整个硬盘都给扫一遍。我的Server里做了三个限制路径白名单所有文件操作必须位于data/和output/目录下工具函数内强制校验resolve()后的路径前缀。不允许删除操作我的Server没有提供删除文件、删除sheet的工具。写入前预览write_excel支持dry_runTrue只返回即将写入的行和位置不真正保存。这些限制不用写得多复杂但能防止AI一次误操作毁掉重要表格。日志同样重要每次工具调用都应该记录时间、参数、结果方便事后追溯。6. 一些可以少走弯路的实操习惯6.1 工具描述写得越细AI调用越准很多人以为MCP工具只要函数名和docstring写一下就完事了实际远不够。我发现AI调用工具的准确率和描述质量强相关。描述里不仅要写“这个工具做什么”还要写清楚“什么时候用”甚至可以给一两个参数示例。举例来说同样一个read_excel如果描述是“读取Excel文件”AI很可能在不该用的时候乱用如果描述改成“读取Excel指定工作表区域并返回文本格式适用于查看表结构和获取数据样本默认最多200行”AI就能更智能地决定是否调用、传什么参数。别觉得这是在写文档这是在给AI写使用说明书。6.2 先在小范围验证再处理整表我每次新增工具或改写流程都会先拿一个只有十几行的小表格做测试确认工具返回结果符合预期后再多模块组合跑。这个习惯帮我省了不少时间。MCP工作流里最讨厌的问题是一个工具在单独调用时正常组合起来就报错因为AI可能在中间步骤传了错误的参数。小范围验证能让你快速定位到底是哪一步出了问题。另外当你发现AI连续两次调同一个工具都报错时别让它继续死磕及时停下检查工具代码。AI不是万能的工具函数有bug它再怎么聪明也绕不过去。把日志打开看到真实异常比反复重试有效得多。我自己在跑这个Excel MCP Server的过程中最大的体会是工具服务的本质是把“AI的理解力”和“代码的执行力”拼接起来。真正值钱的不只是那几个Excel函数而是你给AI画出的那条安全、清晰、可组合的工具边界。现在这个Server已经成了我日常工作台的固定成员配合模板文件可以处理周报、销售统计、数据清洗甚至还能做Markdown转Excel的格式转换。接下来我打算再给它加一个模板校验工具让AI在处理文件前先检查表头是否符合公司规范这样就能进一步减少交付前的返工。