ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

从零搭建MCP Server:让AI变身你的Excel自动化助手

从零搭建MCP Server:让AI变身你的Excel自动化助手

让AI帮我整理Excel,这事我琢磨了很久。最开始是让ChatGPT把格式乱七八糟的表格转成能看的报表,来回粘贴复制,数据一多就断;后来改成写Python脚本,脚本能跑,但需求一改就得改代码,改到后面我自己都烦。直到把MCP(Model Context Protocol,模型上下文协议)接到本地的Excel处理服务上,才算找到一种更像“人机协作”的玩法——AI负责理解需求和拆解步骤,我的工具负责真正操刀文件,中间的通信协议就是MCP。

这篇文章不是纯概念科普,是把我从零开发第一个MCP Server、把它变成“Excel助手”的完整过程记录下来。你如果会一点Python,平时又被Excel的重复劳动折磨,想试着把AI接进自己的数据处理工作流,这篇应该能直接帮你跑通第一个闭环。

1. 为什么是MCP:Excel自动化这次真的换玩法了

1.1 我过去踩过的自动化方案:脚本能写,但改需求太痛

在聊MCP之前,先说说我为什么对“脚本自动化Excel”这件事越来越不满意。

最早我用的是VBA宏。录个宏、改改逻辑,一套操作下来确实能省几分钟。可一旦数据源结构变了、新的列插进来了、合并单元格多起来了,宏就瘫在那里,调试VBA的过程堪比考古。

后来换Python,用openpyxl和pandas写数据处理脚本。写脚本解决“一次性需求”很舒服,比如把100个Excel文件里的指定列汇总到一张总表,这种任务半小时就能跑完。但问题出在需求变更频率上——业务同事今天要按大区汇总,明天要按品类汇总,后天要加同比环比,每次我都得打开脚本改参数。到了后面,脚本比Excel本身还复杂,维护成本越来越高。

这其实不是工具的问题,是“自动化”和“智能化”的区别。脚本是把流程固定死,AI则应该根据当前的文件内容和用户需求,动态决定先做什么、再做什么。但问题是,AI本身不会操作Excel,它没有手,也没权限碰你磁盘上的文件。

1.2 MCP改变的不是“能不能自动化”,而是“和AI协作的方式”

MCP解决的就是这个“最后一公里”问题:让AI能调用你本地的工具。它不是魔法,而是一个标准化的通信协议。AI在对话里判断“我需要读取这个Excel文件”,然后通过MCP把请求发给你的本地Server,你的Server用openpyxl把文件读出来,返回成JSON,AI再根据结果决定下一步动作。

我打个比方。传统脚本相当于你雇了一个临时工,只会照着固定清单干活,清单一变他就懵。MCP相当于你把库房钥匙交给一个会思考的助理,你只需要说“把上个月华东区的销售数据整理成日报,要按门店排名”,他自己就会去库房找文件、翻数据、算统计、做格式,中间遇到问题还会回来问你。

所以MCP真正改变的不是“能不能自动化Excel”,而是把“写死流程”变成“AI编排流程”。你不再需要提前把所有分支都想到,AI会看着实际表格内容临时决定怎么处理。这就是“用AI智能重构Excel处理工作流”最核心的价值。

1.3 这篇实战的适用范围

我先说清楚这篇适合谁:你会Python基础语法,用过openpyxl或pandas更好,但没用过也能跟上;你在日常工作中确实有Excel处理的重复劳动,而不是只想围观一个新概念。这篇的最终交付物是一个你能自己跑起来的MCP Server,它能读Excel、写Excel、保存文件,并且能接进支持MCP的AI客户端,让AI直接指挥它干活。

2. MCP协议到底在解决什么问题:从JSON-RPC到三端角色

2.1 Host、Client、Server,一次搞清楚

MCP的架构经常把人绕晕,其实就是三个角色。

Host是“宿主程序”,就是那个承载AI聊天的应用,比如Claude Desktop、Cherry Studio,或者IDE插件。你在Host里输入自然语言,Host负责把对话返回给大模型。

Client是Host内部的一个协议客户端,它负责把模型那边发起的“工具调用请求”翻译成MCP标准的JSON-RPC消息,再通过管道发给Server。

Server是真正干活的进程。它启动后暴露一组工具,比如read_range、write_cells、save_workbook,每个工具背后是你用Python写的函数。Client调用工具时,Server执行函数,把结果返回给Client,最后回到模型那里。

你可以把大模型想象成老板,Client是秘书,Server是外包执行团队。老板不会自己动手写文件,他说“把这个表弄一下”,秘书翻译成工单发给外包团队,外包团队干完活把结果交回秘书,秘书再汇报给老板。

2.2 三个核心原语:Tools、Resources、Prompts

MCP主要操作三类东西,搞懂这三个概念,工具设计就有了方向。

Tools是“可以被AI调用的函数”。每个工具包含名字、描述、参数定义。AI会根据这些信息决定何时用什么工具。Tools可以是有副作用的操作,比如写文件、发请求,这也是MCP里最常用的原语。

Resources是“给AI读取的只读数据源”。比如你有一份数据字典,说明每列的含义,可以让AI在动手前先读一遍。Resources适合存放上下文、文档、配置,特点是只读、不执行动作。

Prompts是“预置的提示词模板”。比如你写一个clean_report(file_path)的Prompt模板,AI被触发后会自动带上固定的处理指令,省去每次重复输入。

我实际开发时,工具是主力,数据字典用Resource,固定汇报格式用Prompt,各司其职。

2.3 一次工具调用的完整链路

从你输入一句话到Excel真的被改动了,中间大概经过这六步:

  1. 用户在Host里输入“帮我读取orders.xlsx的Sheet1中前10行数据”。
  2. 大模型判断需要调用read_range工具。
  3. Host里的Client构造一个JSON-RPC请求,调用tools/call方法,附上参数。
  4. 请求通过stdio管道或远程网络发送给MCP Server进程。
  5. Server执行read_range函数,用openpyxl打开文件,读取数据。
  6. Server把结果序列化成JSON返回,大模型看到数据后继续生成回复。

整个过程都是标准化的,意味着你不用为每个AI客户端写不同的集成代码。这就是MCP作为“协议”的价值——一次开发,多处使用。

2.4 stdio还是远程服务:开发阶段怎么选

MCP支持多种传输方式,开发阶段我建议只关心stdio。

stdio模式就是客户端启动时直接拉起你的Python进程,两者通过标准输入输出流通信。这种方式配置简单、调试直观,没有网络认证的烦恼,适合本地开发。

如果你要把Server部署到服务器上,让多个客户端远程访问,那就要用HTTP/SSE或WebSocket方式。远程的好处是多个用户共享同一个服务,比如把Excel处理Server挂在公司服务器上,同事的AI都能连。但本地开发时没必要上来就搞远程,先跑通stdio再说。

我自己的经验是:第一个MCP Server老老实实走stdio,等逻辑稳定了再考虑包一层WebSocket服务。

3. 开始动手:第一个能读Excel的MCP Server

3.1 环境准备:Python、SDK和项目目录

我用的是Python 3.10,装好之后需要两个库:MCP官方的Python SDK,以及openpyxl。

安装命令很简单:

pip install mcp openpyxl

我建议再装一个filelock,后面处理并发写文件时会用到:

pip install filelock

项目目录结构不需要复杂:

mcp_excel_server/ ├── main.py └── data/ └── orders.xlsx # 你用来测试的Excel文件

为什么选openpyxl而不用pandas?因为pandas处理数据虽然强,但返回的是DataFrame,序列化成JSON给AI时很别扭,而且pandas写回Excel会破坏原有样式。openpyxl更贴近Excel原生结构,能读能写,适合作为MCP工具的底层库。等需要做复杂统计分析时,再让工具内部调用pandas,对外还是返回JSON。

3.2 核心代码:先让AI能“看见”表里有什么

先把Server骨架搭起来。官方SDK现在提供了FastMCP类,写起来非常简洁:

# main.py from mcp.server.fastmcp import FastMCP import openpyxl from pathlib import Path MCP = FastMCP("excel-server") @MCP.tool() def list_sheets(file_path: str) -> list[str]: """读取Excel文件的所有工作表名称。 Args: file_path: Excel文件路径。 """ wb = openpyxl.load_workbook(file_path, read_only=True, data_only=True) return wb.sheetnames @MCP.tool() def read_range(file_path: str, sheet_name: str, max_rows: int = 50) -> dict: """读取指定工作表的数据,返回二维数组。 Args: file_path: Excel文件路径。 sheet_name: 工作表名称。 max_rows: 最多读取的行数,防止返回数据量过大。 """ wb = openpyxl.load_workbook(file_path, data_only=True) ws = wb[sheet_name] rows = [] for i, row in enumerate(ws.iter_rows(values_only=True)): if i >= max_rows: break rows.append(list(row)) return { "sheet": sheet_name, "row_count": len(rows), "rows": rows, "note": f"仅返回前{max_rows}行,如需更多数据请增大max_rows。" } if __name__ == "__main__": MCP.run()

函数签名里的类型注解和docstring,会被自动转成JSON Schema给AI看。所以函数描述一定要写人话,比如read_range的说明里我特意加了一句“防止返回数据量过大”,AI看到之后就知道调用时要控制行数,而不是傻乎乎地读20万行。

3.3 核心代码:让AI能写和保存

光能读还不够,得让AI能改文件。我加了三个工具:写单元格、新建工作簿、强制保存。

@MCP.tool() def write_cells(file_path: str, sheet_name: str, start_row: int, start_column: int, values: list) -> str: """将二维数组写入工作表指定位置并保存文件。 Args: file_path: Excel文件路径。 sheet_name: 工作表名称。 start_row: 起始行号,从1开始。 start_column: 起始列号,从1开始。 values: 二维数组,例如[[1, "A"], [2, "B"]]。 """ wb = openpyxl.load_workbook(file_path) ws = wb[sheet_name] for r, row in enumerate(values): for c, value in enumerate(row): ws.cell(row=start_row + r, column=start_column + c, value=value) wb.save(file_path) return f"成功写入{len(values)}行到{sheet_name},文件已保存。" @MCP.tool() def create_workbook(file_path: str, sheet_name: str = "Sheet1") -> str: """新建一个Excel文件,并创建指定名称的工作表。 Args: file_path: 要创建的文件路径。 sheet_name: 第一个工作表的名称,默认Sheet1。 """ wb = openpyxl.Workbook() wb.active.title = sheet_name wb.save(file_path) return f"文件已创建:{file_path},包含工作表:{sheet_name}"

这里有个细节:openpyxl的load_workbook()在读取时如果用了read_only=True,写操作会受限,所以我只在只读工具里用read_only,写操作的工具都用普通模式加载。读取性能很重要,写操作的正确性更重要。

3.4 启动方式:stdio模式下的进程逻辑

启动很简单,直接运行脚本:

python main.py

你会看到进程起来之后什么也不输出——它在等标准输入。MCP Client会通过管道来调它。如果你现在直接按回车,可能还会因为收到非法JSON报错,正常,这说明它正在监听stdio。

我第一次跑通的时候,最大的感受是“原来所谓MCP Server,就是一个跑在本机的Python进程”。它不依赖GPU、不依赖云服务,数据也在本地流转,这对办公场景来说是个很安心的特性。

4. 工具封装的质量决定了AI能不能用对:参数、错误和边界

4.1 工具名和描述,就是给AI看的说明书

这是我自己踩过最大的坑。MCP工具的参数定义会被大模型拿来当“API文档”分析,你写得含糊,AI就会用错。

比如我之前把工具命名成process_data,描述写成“处理数据”,AI根本不知道这个工具是干什么的,调用频率极低。后来改成merge_excel_files,描述写明“合并多个Excel文件到第一个文件,要求所有文件结构一致”,AI看到名字和描述,基本不会再理解错。

工具描述里应该包含:这个工具做什么、输入参数的类型和含义、什么情况下适合用这个工具、什么情况下不适合。你可以把它当成写给另一个程序员看的函数注释,但这个“程序员”是AI。

4.2 小工具成链,别做万能函数

第二个容易犯的错,是想做一个“万能Excel处理工具”,参数里塞上operation字段,让AI自己填“read”还是“write”。我试过之后发现效果很差,一是参数太多模型容易乱,二是调试困难——出错了你不知道是哪个分支的问题。

我的原则变成:一个工具只做一件事。读是读,写是写,统计是统计,合并是合并。工具多了没关系,AI会自己选择合适的那一个。每个工具的参数控制在三到五个,太多就该拆分了。比如read_range关注的是“读哪个文件、哪个表、最多读多少行”,而“读取后做什么处理”是AI在后台自己推理的,不需要放进工具逻辑里。

4.3 错误处理:让AI能从报错里自我修复

AI Agent有个特性:它收到错误信息后会尝试自己修正。所以你的工具返回错误时,不要只返回"error",要把错误原因和修复建议都写清楚。

比如文件被Excel占用时,openpyxl的save()会抛PermissionError。我捕获之后返回:

from openpyxl.utils.exceptions import InvalidFileException @MCP.tool() def save_workbook_safe(file_path: str) -> str: try: wb = openpyxl.load_workbook(file_path) wb.save(file_path) return f"文件已保存:{file_path}" except PermissionError: return "保存失败:文件可能正被Excel或其他程序占用,请先关闭打开该文件的程序,再重新调用。" except InvalidFileException: return "保存失败:文件路径不存在或文件不是有效的Excel文件,请检查file_path参数。"

AI收到“文件正被Excel占用”这种明确提示后,下一轮回复就会直接告诉用户“请先关闭Excel”,而不是傻乎乎地再试一次。

4.4 安全边界:路径白名单、大小限制、覆盖确认

MCP工具一旦接入了AI,你的文件系统就相当于暴露给了它。虽然大模型的指令通常来自对话,不会真的“乱来”,但严谨一点总没错。

我在Server里预设了一个DATA_DIR作为白名单目录,所有文件路径必须解析后位于这个目录下,否则直接拒绝。这样AI永远无法访问你桌面上与工作无关的文件。

DATA_DIR = Path("/Users/me/mcp_excel_server/data").resolve() def safe_join_path(file_path: str) -> Path: p = Path(file_path).expanduser().resolve() if not p.is_relative_to(DATA_DIR): raise ValueError(f"只允许访问{DATA_DIR}目录下的文件,拒绝:{p}") return p

另外,写操作前最好做一次文件大小检查,超过50MB的Excel文件用openpyxl硬读会非常慢,直接提示AI改用更小范围的操作。涉及覆盖已有文件时,让工具返回确认信息,而不是闷头覆盖。

5. 接入AI客户端:配置、验证和第一次对话

5.1 配置Claude Desktop/其他客户端

MCP Server写好之后,要接进AI客户端才能用。支持MCP的客户端很多,我以Claude Desktop为例,它的配置文件在claude_desktop_config.json里,加一段mcpServers即可。

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

如果你是在Windows上跑,command建议写成Python的完整路径,比如C:\\Python310\\python.exe,否则可能找不到Python。

其他客户端配置思路都一样,无非是填入命令和启动参数。本质就是让客户端帮你拉起这个Python进程,然后通过stdio通信。

5.2 怎么确认MCP真的连上了

配置完最怕的就是表面看不出问题,一用就报错。我建议先做两步验证。

第一步,在客户端里直接问AI:“你现在有哪些工具可用?”如果连接成功,AI会列出list_sheets、read_range、write_cells这些工具名字,说明MCP Server已经注册成功。

第二步,让AI做一次实际读取:“读取data/orders.xlsx里的Sheet1前5行。”如果它返回了正确的表格数据,说明工具调用链路完全打通了。

如果看不到工具列表,大概率是配置路径写错了,或者Python环境不对。把main.py改成绝对路径、把python换成绝对路径,一般能解决90%的问题。

5.3 一个典型对话背后的工具调用链

我第一次跑通的完整对话是这样的:

用户:“帮我看看orders.xlsx里面单笔金额最大的10笔订单。”

AI内心判断需要先知道有哪些表,再读数据,然后排序。它实际调用了list_sheets拿到“Sheet1”,再调用一次read_range读取整个表的前200行,然后在自己的推理过程中做筛选排序,最终返回给用户“这是金额最大的10笔订单”。

注意,排序这个动作AI没再调用工具,它是基于读到的数据自己算的。这就是AI工作流和传统脚本的本质差异:读数据用工具,处理数据靠模型推理,而不是靠代码硬算。

我自己看日志的时候,发现AI会“试错”。比如第一次读文件报路径错误,它会立刻修正路径再试。这让整个系统看起来真的像一个会自己思考的助理,而不是一个死板的API调用链。

6. 完整工作流Demo:从原始CSV到自动生成日报

6.1 需求与工具链设计

只看单独的读写工具,你可能还不觉得MCP有多大价值。用一个完整的工作流Demo来说明,需求是这样:

我每天会拿到一个orders.csv,包含订单号、门店ID、销售额、日期。我需要生成一个daily_report.xlsx,里面Sheet1是全部订单明细,Sheet2是按门店汇总的销售额排名,Sheet3是最近7天销售趋势,而且都要带基础样式。

如果写传统脚本,这个需求至少半小时。但有了MCP Server,我只在Server里加了一个读CSV的工具和调用pandas做汇总的工具,再把“生成带样式的报表”封装成工具,剩下的流程让AI自己串联。

6.2 让AI理解业务字段:用Resource喂数据字典

这里我用到了Resources原语。我给Server加了一个数据字典资源,让AI在处理前先读一遍:

@MCP.resource("data-dict://orders") def orders_dict() -> str: """订单数据字典""" return """ 字段说明: order_id: 订单号,唯一 store_id: 门店ID amount: 销售额,单位元 order_date: 下单日期,格式YYYY-MM-DD """

这样AI在跑流程前,会主动读取这个资源,就不会出现把金额字段当成字符串拼接这种低级错误。

6.3 全流程实测记录与结果评估

实际跑的时候,AI自己规划了这样的步骤:

  1. 读取orders.csv,看前10行了解结构。
  2. 读取数据字典,确认字段含义。
  3. 调用pandas汇总工具,按store_id分组计算销售额总和。
  4. 调用pandas汇总工具,按order_date分组计算每日总额。
  5. 调用新建工作簿工具,创建daily_report.xlsx。
  6. 把明细、门店汇总、日期趋势依次写入三个工作表。
  7. 返回文件路径给用户。

整个过程它没有向我确认任何细节,全部自动完成。最后生成的报表打开后,我确认了数据没有算错,格式也基本符合预期。虽然样式美化方面不如我手工调的模板精致,但效率提升非常明显——从30分钟的脚本开发加调参,变成了3分钟的自然语言对话。

7. 实战踩坑记录:文件占用、超时、路径和格式问题

7.1 PermissionError:Excel还开着,文件写不进去

这个坑是Excel处理特有的。如果用户正用Excel打开着某个文件,AI这边调用write_cells保存时,Windows和macOS上大概率会抛PermissionError。我一开始没处理,AI就反复重试同一个失败操作,来回折腾。

后面我把所有写文件工具的异常都捕获,返回上面说的明确提示“请先关闭占用该文件的程序”。AI看到之后就不会硬重试了,而是停下来让用户处理。你还可以在Server里把文件锁再细化,但最方便的其实是让用户养成“AI处理文件期间别打开同一个Excel”的习惯。

7.2 超时问题:表太大,模型等不起

MCP请求和HTTP请求一样有时长限制。如果某个表格很大,load_workbook加读区域要好几秒,AI那边可能已经等不到结果了。

我的解决办法是分层读取:read_range默认最多读50行,让AI先看头部结构;对需要全表的统计任务,专门写一个summarize_column工具,在Server内部算好摘要再返回。这样返回给模型的数据量小,响应时间也快,模型也不会因为一次性塞入太多行导致上下文爆炸。

7.3 路径问题:AI不知道你的工作目录在哪

AI在对话里生成的文件路径,经常是相对路径或者带~的路径,比如orders.xlsx。但MCP Server进程的工作目录未必是用户想当然的那个目录,稍不注意就读不到文件。

我在所有读文件的工具入口统一做路径解析,先expanduser()再把相对路径拼到DATA_DIR,同时让AI养成的习惯是在对话里写完整路径。你可以把Server的DATA_DIR当成AI的“专属工作区”,所有文件读写都限制在那里,路径问题就集中解决掉了。

7.4 格式问题:openpyxl不是万能的,图表和宏会丢

openpyxl读取时,对图表、图片、VBA宏这些复杂内容支持有限。如果你拿一个带图表的Excel,用openpyxl加载再保存,图表很可能就丢了。

这个问题没有完美解决,只能看场景做取舍:如果只是改数据,就接受格式上的部分损失;如果必须保留图表和样式,就得用xlsxwriter重新生成整个报表,我在Demo里生成日报用的就是xlsxwriter,专门负责画框线、调列宽、写表头样式。把“快速改数据”和“精致生成新报表”拆成两套工具,反而比一个万能工具更靠谱。

7.5 并发写同一文件:两个工具同时动手,文件互相覆盖

当AI一次对话里连续调用两次写工具时,有没有可能两个请求同时到达Server操作同一个文件?我遇到过,结果就是后保存的覆盖先保存的,数据直接丢。

解决方案是给写操作加文件锁,我用的filelock:

from filelock import FileLock def write_cells_locked(file_path: str, sheet_name: str, ...): lock = FileLock(file_path + ".lock") with lock: # 原有的写入逻辑 wb.save(file_path)

锁的作用是把写操作串行化,同时只有一个工具在改文件。另一个更稳妥的思路是写临时文件再原子替换,但直接加锁对Excel这种低频操作已经够用。

如果你也想把MCP接入自己的数据处理工作流,我给的建议很简单:第一个Server不要追求功能全,先实现“读一个区域、写一个单元格、保存文件”这三件事,接进客户端跑通一次,你就能感受到AI做Excel和传统脚本做Excel的区别。我这边现在已经把它用在月度经营报表上了,原来半小时的整理工作变成几轮对话的事,而且每次需求变了,不用改工具代码,直接在对话里说清楚就行。这一步跨过去,后面你想扩展PDF处理、数据库查询、文件批量管理,都是同一个套路——给AI几把钥匙,让它自己去开门。

返回列表