尧图精选

从零手写MCP服务端:让AI直接操作Excel的实战指南

🕒 发布时间:2026/10/1 7:27:32 📁 来源:尧图网络
1. 为什么我要自己动手写一个 MCP1.1 从一次崩溃的 Excel 处理说起上个月帮朋友的公司处理一批销售数据二十多个 Excel 文件每个文件里七八个 Sheet需要按区域拆分、按品类汇总、再生成一份带图表的月报。我一开始的想法很朴素写个 Python 脚本pandas 读进来groupby 一下to_excel 出去半小时的事。结果现实给了我一巴掌。表头不在第一行有的在第三行有的在第五行合并单元格满天飞日期列一半是文本格式一半是日期格式金额列里混着约 12001200元1,200这种写法。我那个脚本改了十几版每改一版就要重新跑一遍全量数据跑一次三分钟一天下来光等脚本跑完就耗掉了大半天。更难受的是每次遇到新问题我都得停下来想这个逻辑该怎么写而不是直接告诉工具帮我把这一列里带元字的数字提出来。那一刻我突然意识到我缺的不是一个更复杂的脚本而是一个能让 AI 直接理解我的意图、并且能真正操作 Excel 文件的手。这个手就是 MCP。1.2 MCP 到底是个什么东西MCP 全称 Model Context Protocol翻译过来叫模型上下文协议。很多人第一次听到协议两个字就头大觉得又是那种要啃几百页文档的东西。其实你可以把它理解成一个标准插座。想象一下你家里有台电脑、一个台灯、一个充电器如果每个电器的插头形状都不一样你就得给每个电器配一个专门的插座墙上挂满各种奇形怪状的接口。MCP 干的事就是把这些接口统一成一种标准形状——只要你的工具按照这个标准做了一个插头任何支持 MCP 的 AI 客户端都能直接插上去用。具体到技术层面MCP 定义了一套 AI 模型和外部工具之间通信的规范。AI 这边是客户端你的工具这边是服务端。客户端告诉服务端我有哪些能力服务端告诉客户端我能做什么事、需要什么参数然后双方通过标准化的消息格式来回沟通。这套机制最大的价值在于解耦你写的 Excel 处理工具不需要关心对面是哪个 AIAI 也不需要为每个工具单独写适配代码。我选择自己写一个 MCP 服务端而不是用现成的方案原因有三个。第一Excel 处理的场景太碎了通用工具很难覆盖我遇到的那些奇葩表格结构第二我想把一些自己积累的处理经验固化进去比如遇到合并单元格先展开再处理这种默认行为第三自己写一遍才能真正理解 MCP 的工作机制以后遇到别的场景能快速迁移。1.3 这个项目适合谁来参考如果你符合下面任意一条这篇内容应该能帮到你经常和 Excel 打交道被重复性的数据整理、格式转换、报表生成折磨过会一点 Python但不想每次都从零写脚本听说过 MCP 但不知道从哪下手想找一个完整的、能跑起来的例子想把 AI 真正接入自己的工作流而不是只在聊天框里问问题不需要你是 Python 高手基础的函数、列表、字典操作会就行。MCP 的 SDK 已经把大部分复杂的东西封装好了我们要做的是把业务逻辑写清楚。2. 动手之前环境准备与核心概念对齐2.1 Python 环境怎么装才不踩坑Python 安装这件事看起来简单但我见过太多人在这里翻车。最常见的坑是电脑上装了 Python但命令行里敲python提示找不到命令。这通常是因为安装时没勾选Add Python to PATH。我的建议是直接去 Python 官网下载最新稳定版写这篇的时候是 3.12.x安装时务必勾选那两个选项Add python.exe to PATH和Install launcher for all users。装完之后打开命令行敲python --version能正常输出版本号就说明成功了。如果你电脑上已经有多个 Python 版本或者之前装过 Anaconda我强烈建议用虚拟环境来隔离这个项目。虚拟环境的好处是这个项目需要的库不会污染你系统里的其他 Python 环境删掉的时候也干净。# 创建虚拟环境 python -m venv mcp-excel-env # Windows 激活 mcp-excel-env\Scripts\activate # macOS / Linux 激活 source mcp-excel-env/bin/activate激活之后命令行前面会出现(mcp-excel-env)的标识这时候装的库都只在这个环境里生效。2.2 需要装哪些库这个项目的依赖其实不多核心就几个pip install mcp openpyxl pandas逐个说一下它们的作用。mcp是官方提供的 Python SDK封装了协议通信的底层细节我们只需要关注业务逻辑。openpyxl是操作 Excel 文件的主力库读写 xlsx 格式都靠它而且它能处理单元格样式、合并单元格这些 pandas 搞不定的东西。pandas用来做数据分析和转换处理表格数据比纯 Python 循环高效得多。注意openpyxl 只能处理 .xlsx 格式如果你手上有 .xls 的老文件需要先用 Excel 另存为 xlsx或者额外装一个xlrd库来读取。2.3 MCP 的三个核心概念在写代码之前必须把三个概念搞清楚否则后面看代码会一头雾水。Tool工具这是 MCP 服务端对外暴露的能力。一个 Tool 就是一个函数有名字、有描述、有参数定义。AI 客户端看到这些信息后就知道哦这个服务端能帮我读 Excel、能帮我写 Excel。AI 决定调用某个 Tool 时会按照参数定义传进来一个 JSON我们的函数收到后执行再把结果返回去。Resource资源资源是只读的数据比如一个文件的内容、一个数据库的查询结果。和 Tool 的区别在于Resource 是被动读取的Tool 是主动执行的。Excel 场景里我一般把读取某个 Sheet 的内容做成 Resource把修改某个单元格做成 Tool。Transport传输方式客户端和服务端之间怎么通信。最常见的是 stdio标准输入输出适合本地运行的工具还有 SSEServer-Sent Events适合远程服务。我们做本地 Excel 处理用 stdio 就够了配置简单不需要开端口。理解了这三个概念整个开发过程就清晰了定义 Tool 和 Resource选好 Transport把业务逻辑填进去。3. 核心设计我的 Excel MCP 长什么样3.1 功能边界的划定一开始我想把所有能想到的 Excel 操作都做成 Tool列了个清单读取、写入、合并、拆分、排序、筛选、透视、画图、格式转换……列到二十多个的时候我停下来了。Tool 太多会带来两个问题一是 AI 面对几十个工具容易选错二是每个 Tool 都要写描述和参数定义维护成本高。最后我砍到了六个核心 Tool覆盖 90% 的日常场景Tool 名称功能典型场景read_sheet读取指定 Sheet 的数据查看表格内容、获取数据做分析write_cells向指定区域写入数据填充计算结果、更新状态列list_sheets列出文件里所有 Sheet了解文件结构get_sheet_info获取 Sheet 的维度、表头位置处理前先摸清结构transform_column对某一列做批量转换清洗数据、格式统一create_report按模板生成汇总报表生成月报、周报这个设计的关键思路是把高频操作做成原子 Tool把复杂流程留给 AI 组合。比如按区域拆分文件这个需求AI 可以先用 list_sheets 看结构再用 read_sheet 读数据然后用 write_cells 写到新文件里。我不需要为每个组合场景单独写 Tool。3.2 为什么用 openpyxl 而不是 pandas 做主力这是个值得展开说的选择。pandas 处理数据确实快一行pd.read_excel()就能把表格读成 DataFrame各种 groupby、pivot 用起来很爽。但 pandas 有个致命问题它不保留格式。你用 pandas 读一个带合并单元格、带颜色标记、带公式的表格读进来就是一堆纯数据原来的格式全丢了。写回去的时候所有单元格都是默认样式。对于我要生成一份给老板看的报表这种需求格式丢了等于白干。openpyxl 则相反它把 Excel 文件当成一个对象树来操作每个单元格、每个样式、每个合并区域都是独立的对象。你可以精确控制把 A1 到 C1 合并背景色设成浅蓝字体加粗。代价是操作起来比 pandas 啰嗦遍历几万行数据会慢一些。我的方案是两者结合用 openpyxl 打开文件、处理格式、定位数据区域把需要计算的数据转成 pandas DataFrame 做分析算完再写回 openpyxl 的对象里。这样既保留了格式控制能力又享受了 pandas 的计算效率。3.3 表头识别的处理策略前面提到我遇到的表格表头位置五花八门。这个问题不解决后面所有操作都是空中楼阁。我的处理策略是写一个detect_header函数逻辑是这样的从第一行开始往下扫对每一行做评分。评分规则包括这一行非空单元格的比例、是否包含常见表头关键词如日期金额数量名称、下一行的数据类型是否与这一行不同表头通常是文本数据行会有数字或日期。综合得分最高的那一行就认定为表头行。这个逻辑不是百分百准确但能覆盖大部分情况。对于识别错的我在 Tool 的参数里留了一个header_row参数允许手动指定。这种自动为主、手动兜底的设计在实际使用中比纯自动或纯手动都好用。4. 代码实现从零到能跑起来4.1 项目结构我习惯把项目结构保持简单一眼能看清哪个文件干什么mcp-excel/ ├── server.py # MCP 服务端入口定义 Tool 和 Resource ├── excel_ops.py # Excel 操作的核心逻辑 ├── utils.py # 工具函数如表头识别、数据清洗 └── requirements.txt # 依赖清单server.py 只负责对接 MCP 协议excel_ops.py 负责真正干活。这样分开的好处是如果以后要换一个 MCP SDK 或者加别的传输方式业务逻辑不用动。4.2 服务端骨架先看 server.py 的核心结构from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent import excel_ops app Server(excel-mcp) app.list_tools() async def list_tools(): return [ Tool( nameread_sheet, description读取 Excel 文件中指定 Sheet 的数据返回二维数组, inputSchema{ type: object, properties: { file_path: {type: string, description: Excel 文件路径}, sheet_name: {type: string, description: Sheet 名称}, header_row: {type: integer, description: 表头所在行号从1开始不填则自动识别} }, required: [file_path, sheet_name] } ), # ... 其他 Tool 定义 ] app.call_tool() async def call_tool(name: str, arguments: dict): if name read_sheet: result excel_ops.read_sheet( arguments[file_path], arguments[sheet_name], arguments.get(header_row) ) return [TextContent(typetext, textresult)] # ... 其他 Tool 的分发 async def main(): async with stdio_server() as (read, write): await app.run(read, write, app.create_initialization_options()) if __name__ __main__: import asyncio asyncio.run(main())这段代码里有两个关键点。第一inputSchema用的是 JSON Schema 格式它告诉 AI这个工具需要什么参数、每个参数是什么类型、哪些是必填的。描述写得越清楚AI 调用时越不容易出错。第二call_tool是一个分发器根据工具名路由到对应的业务函数。这种模式在 MCP 开发里很常见因为 Tool 数量多了之后把所有逻辑堆在一个函数里会很难维护。4.3 表头自动识别的实现utils.py 里的detect_header是整个项目里我觉得最有价值的一个函数值得详细说说import re HEADER_KEYWORDS [日期, 时间, 金额, 数量, 名称, 编号, 类型, 状态, 备注, 部门, 客户, 产品, 单价, 总计] def detect_header(ws, max_scan_rows10): best_row 1 best_score -1 for row_idx in range(1, min(max_scan_rows, ws.max_row) 1): score 0 non_empty 0 keyword_hits 0 for cell in ws[row_idx]: if cell.value is not None: non_empty 1 text str(cell.value) if any(kw in text for kw in HEADER_KEYWORDS): keyword_hits 1 # 非空单元格比例得分 if ws.max_column 0: score (non_empty / ws.max_column) * 40 # 关键词命中得分 score keyword_hits * 15 # 下一行数据类型差异得分 if row_idx ws.max_row: next_row ws[row_idx 1] type_diff 0 for c1, c2 in zip(ws[row_idx], next_row): if c1.value is not None and c2.value is not None: if type(c1.value) ! type(c2.value): type_diff 1 score type_diff * 5 if score best_score: best_score score best_row row_idx return best_row这个函数的评分逻辑分三块。非空比例占 40 分因为表头行通常是填得比较满的。关键词命中每个加 15 分这是最强的信号。类型差异每个加 5 分因为表头是文本、数据行是数字的情况很常见。我实测下来对于结构规整的表格这个函数基本能 100% 识别正确。对于那种表头里全是自定义字段名的比如Q1销售额区域负责人关键词命中会少一些但非空比例和类型差异通常能把分数拉上来。如果三个信号都不明显那就只能靠手动指定了。4.4 列数据批量转换transform_column这个 Tool 是我用得最频繁的因为数据清洗的需求太多了。它的设计思路是接收一个转换规则对指定列的所有单元格应用这个规则。def transform_column(file_path, sheet_name, column, rule, header_rowNone): wb openpyxl.load_workbook(file_path) ws wb[sheet_name] if header_row is None: header_row detect_header(ws) col_idx openpyxl.utils.column_index_from_string(column) changes [] for row_idx in range(header_row 1, ws.max_row 1): cell ws.cell(rowrow_idx, columncol_idx) old_value cell.value if old_value is None: continue new_value apply_rule(old_value, rule) if new_value ! old_value: cell.value new_value changes.append({ row: row_idx, old: str(old_value), new: str(new_value) }) wb.save(file_path) return changesapply_rule支持几种常见的规则类型。strip_currency去掉金额里的元等符号并转成数字normalize_date把各种日期格式统一成 YYYY-MM-DDextract_number从混合文本里提取数字trim_space去掉首尾空格。这些规则覆盖了我遇到的大部分清洗需求。实操心得批量修改前一定要先备份原文件。我吃过一次亏规则写错了把一整列数据改成了 None又没有备份只能从回收站里翻。现在我的习惯是任何写操作之前先shutil.copy一份带时间戳的备份。4.5 报表生成的设计create_report这个 Tool 稍微复杂一点它接收一个配置字典描述要生成什么样的报表。配置里包括数据源文件、分组字段、汇总字段、汇总方式求和/计数/平均、输出文件路径。def create_report(config): df pd.read_excel(config[source], sheet_nameconfig[sheet]) grouped df.groupby(config[group_by])[config[agg_column]].agg(config[agg_func]) grouped grouped.reset_index() grouped.to_excel(config[output], indexFalse) # 用 openpyxl 做格式美化 wb openpyxl.load_workbook(config[output]) ws wb.active for cell in ws[1]: cell.font openpyxl.styles.Font(boldTrue) cell.fill openpyxl.styles.PatternFill( start_colorD9E1F2, fill_typesolid ) wb.save(config[output]) return f报表已生成{config[output]}这里体现了前面说的pandas 算数据、openpyxl 做格式的组合思路。pandas 负责 groupby 和聚合算完写出去openpyxl 再把表头加粗、加背景色。两步分开各司其职。5. 接入 AI 客户端与实战验证5.1 客户端配置MCP 服务端写好了怎么让 AI 用上它需要在支持 MCP 的客户端里加一段配置。以常见的桌面客户端为例配置文件里加这么一段{ mcpServers: { excel-mcp: { command: python, args: [/path/to/mcp-excel/server.py], env: {} } } }command是启动命令args是参数。如果你用的是虚拟环境command要指向虚拟环境里的 python 可执行文件否则会找不到依赖。Windows 上是mcp-excel-env\Scripts\python.exemacOS 和 Linux 上是mcp-excel-env/bin/python。配置好之后重启客户端如果一切正常AI 就能看到我们定义的六个 Tool 了。你可以直接问它帮我看看这个 Excel 文件里有哪些 Sheet它会自动调用list_sheets。5.2 一个完整的实战案例我拿一个真实的销售数据文件来演示。文件叫sales_2024.xlsx里面有 12 个 Sheet每个 Sheet 是一个月的销售记录。表头在第 3 行列包括订单号、日期、区域、产品、数量、单价、金额。金额列里混着12001,200元约 800这几种写法。第一步我告诉 AI帮我看看 sales_2024.xlsx 里有哪些 Sheet每个 Sheet 的结构是什么样的。AI 调用list_sheets拿到 12 个 Sheet 名然后对第一个 Sheet 调用get_sheet_info返回了维度信息和自动识别的表头行号识别出是第 3 行。AI 把结果整理给我看确认结构一致。第二步我说金额列的数据格式很乱帮我统一成纯数字。AI 调用transform_column参数是columnG、ruleextract_number。函数执行后返回了修改记录告诉我哪些单元格被改了、改成了什么。我抽查了几条确认1,200元变成了 1200约 800变成了 800。第三步我说把所有月份的数据合并按区域和产品汇总金额生成一份汇总报表。AI 先对每个 Sheet 调用read_sheet读取数据在对话里做合并这一步是 AI 自己在推理不是调 Tool然后调用create_report传入分组字段和汇总配置。几十秒后一份带格式的汇总报表就生成了。整个过程我一行代码没写只是用自然语言描述需求。这就是 MCP 的价值把写脚本变成了说需求。5.3 性能实测数据我用一个 5 万行的文件做了个简单的性能测试对比纯 Python 脚本和 MCP 方式的耗时操作纯 Python 脚本MCP 方式差异原因读取单 Sheet1.2s1.4sMCP 多了一层协议通信开销列转换5万行3.5s3.8s开销可忽略生成汇总报表2.1s2.3s主要是 pandas 计算时间端到端含 AI 推理不适用15-30s大头是 AI 理解需求的时间结论很清楚MCP 本身的性能开销很小主要耗时在 AI 推理环节。对于交互式的、一次性的任务这个耗时完全可以接受。对于需要跑几千次的批处理任务还是写脚本更合适。MCP 的定位是让 AI 帮你处理那些不常做、但每次都要想半天的任务。6. 踩过的坑与排查手册6.1 常见问题速查现象可能原因解决方法客户端看不到 Tool服务端启动失败手动运行 server.py 看报错调用 Tool 报文件不存在路径用了相对路径改用绝对路径读取中文乱码文件编码问题确认文件是 xlsx 而非 csv写入后格式丢失用了 pandas 写入改用 openpyxl 写入合并单元格读取为 Noneopenpyxl 特性先展开合并单元格再读大文件内存溢出一次性加载用 read_only 模式6.2 合并单元格这个坑openpyxl 读取合并单元格时只有左上角的单元格有值其他单元格都是 None。这个特性坑了我很久。比如一个表头销售数据合并了 A1 到 D1你读 B1、C1、D1 都是 None。解决办法是先用ws.merged_cells.ranges拿到所有合并区域然后写一个展开函数把合并区域的值填充到每个单元格def unmerge_and_fill(ws): for merged_range in list(ws.merged_cells.ranges): top_left ws.cell( rowmerged_range.min_row, columnmerged_range.min_col ).value for row in range(merged_range.min_row, merged_range.max_row 1): for col in range(merged_range.min_col, merged_range.max_col 1): ws.cell(rowrow, columncol).value top_left ws.unmerge_cells(str(merged_range))这个函数要在读取数据之前调用。展开之后每个单元格都有值了后续处理就正常了。6.3 路径问题的排查思路MCP 服务端启动时的当前工作目录和你想的可能不一样。客户端启动服务端时工作目录通常是客户端自己的目录不是 server.py 所在的目录。所以代码里用相对路径data/sales.xlsx大概率会找不到文件。我的做法是所有文件路径都要求传绝对路径。在 Tool 的描述里明确写请传入绝对路径。如果 AI 传了相对路径函数里做一个转换基于 server.py 所在目录来解析import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) def resolve_path(path): if os.path.isabs(path): return path return os.path.join(BASE_DIR, path)6.4 独家避坑技巧技巧一Tool 描述要写得像给新人看的文档。AI 选 Tool 和填参数全靠描述。描述里要写清楚这个工具做什么什么时候用参数格式是什么返回什么。我见过有人描述只写读取 Excel结果 AI 经常传错参数。改成读取指定 Excel 文件中指定 Sheet 的数据返回二维数组第一行是表头之后准确率明显提升。技巧二写操作一定要有返回值。修改类 Tool 不要返回成功两个字就完事要返回具体改了什么。我让transform_column返回修改记录列表AI 拿到后可以告诉我改了 37 个单元格其中 5 个从约 800变成了 800。这样我能快速判断改得对不对。技巧三给 Tool 加一个 dry_run 参数。对于破坏性操作加一个dry_run布尔参数为 true 时只返回将会做什么而不实际执行。这样 AI 可以先跑一遍 dry_run 给我看我确认后再真正执行。这个设计在批量修改场景下特别有用。技巧四日志要写到文件里。MCP 服务端用 stdio 通信print 的内容会干扰协议消息。调试信息不要用 print用 logging 写到文件里。我配置了一个 rotating file handler每次调用 Tool 都记一条日志出问题的时候翻日志比猜快得多。7. 后续可以怎么扩展这个 MCP 服务端目前只覆盖了 Excel 处理的基础场景但框架已经搭好了扩展起来很方便。我列几个我打算做的方向。第一个是加图表生成能力。openpyxl 支持创建柱状图、折线图、饼图可以做一个create_chartTool接收数据区域和图表类型自动插入到指定位置。这样生成月报的时候连图都不用自己画了。第二个是加模板填充能力。很多报表的格式是固定的只是数据在变。可以做一个fill_templateTool接收一个模板文件和数据字典把数据填到模板的占位符里。这个在财务、人事场景下特别实用。第三个是加多文件批量处理。现在的 Tool 都是针对单个文件的可以加一个batch_processTool接收一个目录路径和一个操作列表对目录下所有 Excel 文件依次执行。配合 dry_run 参数批量操作也能很安全。第四个是加数据校验。在写入之前检查数据是否符合预期比如金额不能为负日期不能超过今天必填字段不能为空。校验不通过就返回错误信息而不是默默写进去。这个能避免很多低级错误。写 MCP 这件事最大的收获不是做出了一个工具而是理解了 AI 和外部世界交互的这套机制。一旦你掌握了这个模式任何重复性的、有明确规则的工作都可以包装成一个 MCP 服务端让 AI 帮你干。Excel 只是一个开始。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →