做Excel自动化十多年,我自己用的工具换了好几轮——从VBA宏,到Python脚本,再到现在直接用MCP把AI接进表格工作流。MCP的全称是Model Context Protocol,模型上下文协议,它要解决的事情很朴素:让AI大模型不再只会聊天,而是能安全、标准地调用外部工具,替人干活。这篇文章是我近期把MCP用于Excel处理工作流的完整开发记录,包括设计思路、核心代码、客户端接入方式和一路踩过的坑,适合有Python基础、想把AI落地到日常办公场景的开发者参考。
如果你还没接触过MCP,不用慌。它没那么玄,我们边写代码边说,等你自己写完第一个Server,你会觉得这玩意儿的潜力比眼下大多数人吹的大得多。
1. 先把话说明白:MCP在我这个Excel场景里到底解决什么问题
1.1 MCP就是AI世界的USB-C接口
我习惯用一个类比解释MCP:把它当成AI时代的USB-C接口。USB-C出现之前,手机、耳机、硬盘各用各的接口,换设备就要换线。MCP做了同样的事情——它定义一个统一协议,把"AI客户端"和"工具服务端"连接起来。任何支持MCP的模型客户端(比如Claude Desktop、Cursor、各种聊天应用),都能像插入USB-C那样接入你的MCP Server,进而调用你写的工具。
这里把角色拆开看。
- MCP Client:AI模型所在的客户端,负责理解用户意图、规划调用哪个工具,并把用户的问题和上下文发给模型。
- MCP Server:你写的程序,对外暴露一个个"工具"(Tool)。每个工具被定义成函数,有名称、描述和输入参数说明。
- 协议层:客户端和Server之间用标准JSON-RPC格式通信,传输方式可以是本地stdio,也可以是网络SSE/WebSocket。
在我这个Excel项目里,AI就是客户端大脑,我的Server是一双手,手能拿Excel、能筛选数据、能写文件。用户不需要记住任何Excel函数,只需把需求说清楚,剩下的由AI自己决定怎么调用工具。
1.2 传统Excel自动化的真实痛点
做Excel自动化的人都知道,VBA看起来很对口,但维护起来太痛苦。一个带十几个宏的Excel文件,过三个月再看,连自己都未必记得某个过程是干什么的。如果需求有变化,比如汇总口径从按月改成按周,你得钻进VBA编辑器里逐行找逻辑,改完还要担心是不是把别的功能弄坏了。
用Python写脚本是进阶方案,比VBA好维护一些,但它依然有个绕不开的问题:脚本是按需求一个个写死的。你今天要"合并三个表",就写个合并脚本;明天要"把销量大于100的筛选出来导出",又写一个新脚本。到最后,脚本积累了几十个,同事找你处理数据还是靠口头描述,你也得花时间回忆哪个脚本能覆盖当前需求。真正的时间不是浪费在写代码上,而是浪费在"需求翻译成代码"这件事上。
有了MCP之后,同样的事不一样了。你不再需要为单个需求写具名脚本,而是提供少数几个通用的、能力原子化的工具。剩下的由AI实时组合。这等于把"编程解决问题"变成了"向AI描述问题,AI自己编排工具链"。
1.3 为什么第一个MCP项目选Excel
我总跟身边人讲,要做MCP练手,首选Excel,别一上来就折腾数据库或浏览器自动化。原因有三点。
第一,Excel数据规整。它天然是二维表格,一行一条记录,一列一个字段。这种结构转换成工具参数、JSON输出都非常直接,几乎不需要复杂的状态管理。
第二,反馈链路短。AI调工具后,结果立刻以表格或文本形式返回,我能马上判断对错。调试期最怕黑盒,Excel场景每个工具几乎都能直观看结果。
第三,数据风险可控。我的Server只会读写指定目录里的表格文件,即使AI哪一步调错了,损失的也就是一个测试文件。不像直接给AI数据库权限,一失手可能删半张表。
基于这三点,我搭了完整项目骨架,然后开始处理一个真实问题:把"读表-筛选-汇总-写表"这条高频链路,全部交给AI调度。
2. 动手前的准备:语言选型、SDK安装与项目骨架
2.1 选型:Python、FastMCP和pandas是稳的
MCP Server的开发语言其实不限,官方SDK有Python和TypeScript。我选了Python,原因很现实:pandas和openpyxl在表格处理领域太成熟,读写Excel几乎不需要写底层代码。
SDK层面,当前主流是官方MCP包里的FastMCP模块,或者社区独立的FastMCP库。它们的API风格一致,都用装饰器把函数暴露成工具,一个Server代码量能压缩到几十行。我下面统一用官方mcp包自带的mcp.server.fastmcp。
这里顺便说个选型心得:当你准备做MCP Server,不要一上来就想着用框架把几十个功能全封装完。好的Server是"小而专",五六个工具足够解决一类场景。工具越多,AI在选择时越容易犯迷糊,参数冲突概率也会上升。
2.2 项目结构与依赖安装
我把项目放在一个独立目录excel-mcp下,里面强制分工:data目录放原始Excel,output目录放AI生成的结果,server.py放所有工具逻辑。目录分离不是形式主义,是为了后续加路径权限时干净利落。
excel-mcp/ ├── server.py ├── requirements.txt ├── data/ │ └── orders.xlsx └── output/依赖安装,直接用pip:
pip install "mcp[cli]" pandas openpyxl tabulate三个包的用途:mcp[cli]提供FastMCP开发环境和调试工具;pandas处理表格数据;openpyxl是读写xlsx的引擎;tabulate是为了让pandas能输出Markdown格式的表格,AI读起来更顺。
2.3 开发期调试:MCP Inspector是你的显微镜
第一次写MCP Server,很多人直接连客户端,结果遇到问题根本分不清是Server报错还是客户端配置错误。我的建议是在开发初期先启动MCP Inspector,它是官方提供的可视化调试工具,可以单独启动Server,手动调用每个工具,还能看到输入输出JSON帧。
启动方式很简单:
npx @modelcontextprotocol/inspector python server.py这句命令会自动拉起一个浏览器界面,左边列出所有工具,右边是对话窗口,你可以在里面模拟AI调用。跑出预期结果,再接客户端,效率高得多。
在项目骨架搭建完毕、工具能正常调用后,就可以进入真正的核心开发环节了。
3. 核心开发实录:三个Excel工具的完整代码与设计思路
3.1 先给工具画边界:不是所有功能都塞进MCP Server
动手写代码之前,我先画了工具边界。一个MCP Server不需要覆盖Excel的所有功能,我只保留高频、边界清晰、AI容易判断的几个操作。
我最终定了五个工具:list_sheets、read_excel、filter_excel、summarize_excel、write_excel。这些足够跑通"读表-筛选-聚合-写表"的闭环。更复杂的格式调整、图表生成,后面可以再扩展。
为什么这个边界要提前画?因为MCP工具一旦提供给AI,AI就会依赖描述去判断什么时候用。如果工具描述写得太宽泛,或者功能互相重叠(比如有个"读取并筛选"工具,又有一个"读取"工具),AI就很容易拿不定主意。宁可工具原子化、职责单一,让AI自己去组合。
3.2 读取工具:list_sheets和read_excel
list_sheets解决的是"先看看文件里有几张表"的问题。很多Excel文件不止一个Sheet,AI如果连表名都不知道,直接read_excel很容易扑空。
import json import logging import os from typing import Optional, List, Dict, Any import pandas as pd from mcp.server.fastmcp import FastMCP logging.basicConfig(filename="mcp.log", level=logging.INFO, force=True) logger = logging.getLogger("excel_mcp") mcp = FastMCP("excel-mcp") ALLOWED_DIRS = [ os.path.abspath("data"), os.path.abspath("output"), ] def _safe_path(file_path: str) -> str: abs_path = os.path.abspath(file_path) for d in ALLOWED_DIRS: if abs_path.startswith(d): return abs_path raise ValueError(f"路径 {file_path} 不在允许范围内,只允许访问 data/ 和 output/ 目录") @mcp.tool() def list_sheets(file_path: str) -> str: """列出Excel文件中的所有工作表名称,返回JSON数组。调用其他工具前可以先调用它确认表名。""" path = _safe_path(file_path) xl = pd.ExcelFile(path) return json.dumps(xl.sheet_names, ensure_ascii=False)read_excel是主读取工具。我让它返回Markdown表格而不是JSON或CSV,原因是Markdown在AI上下文里占用token少,而且模型对表格结构的理解更好,后续筛选、聚合判断列名也更方便。
@mcp.tool() def read_excel(file_path: str, sheet_name: Optional[str] = None, max_rows: int = 50) -> str: """读取Excel指定工作表的前若干行,转换为Markdown表格文本返回。""" path = _safe_path(file_path) df = pd.read_excel(path, sheet_name=sheet_name) return df.head(max_rows).to_markdown(index=False)max_rows默认50防止一次性把几万行全塞进上下文。需要大量分析时,AI会自己在描述里要求先筛选再统计。
3.3 分析工具:filter_excel和summarize_excel
筛选工具我做得尽量简单,只支持数值条件的比较操作。有人问为什么不支持文本模糊匹配?因为一旦支持文本匹配,描述就要解释模糊度、大小写、通配符,AI容易在你的语义和他的经验之间摇摆。数值比较是最容易校验、也最不容易被误用的。
@mcp.tool() def filter_excel(file_path: str, column: str, operator: str, value: float) -> str: """按数值类型条件筛选Excel数据。operator支持 >, >=, <, <=, ==,value为比较阈值。""" path = _safe_path(file_path) df = pd.read_excel(path) ops = { ">": df[column] > value, ">=": df[column] >= value, "<": df[column] < value, "<=": df[column] <= value, "==": df[column] == value, } if operator not in ops: raise ValueError("operator 只支持 >, >=, <, <=, ==") return df[ops[operator]].to_markdown(index=False)聚合工具则是Excel高频功能的替身,取代了手动写透视表。它按指定列分组,对另一列做聚合,支持求和、平均、最大、最小。
@mcp.tool() def summarize_excel(file_path: str, group_by: str, value_col: str, agg: str = "sum") -> str: """按指定列分组,对另一列进行聚合统计。agg支持 sum、mean、max、min。""" path = _safe_path(file_path) df = pd.read_excel(path) result = df.groupby(group_by)[value_col].agg(agg).reset_index() return result.to_markdown(index=False)3.4 写入工具:write_excel
系数处理完,最后要把结果写回去。write_excel接收一个字典列表,每个字典代表一行,key是列名。AI在跑完筛选或聚合后,会把返回的Markdown表格重新组织成字典列表传进来。
@mcp.tool() def write_excel(file_path: str, rows: List[Dict[str, Any]], sheet_name: str = "Sheet1") -> str: """把字典列表写入Excel文件。每个字典代表一行,字典key为列名。若文件存在会覆盖原Sheet。""" path = _safe_path(file_path) df = pd.DataFrame(rows) df.to_excel(path, sheet_name=sheet_name, index=False) return f"已写入 {len(rows)} 行到 {path}"这个工具描述里我故意写了"若文件存在会覆盖原Sheet",这是给AI的一个隐式提醒。它知道覆盖有破坏性,在要求模糊时更有可能停下来向用户确认,而不是直接打出一份可能覆盖原始数据的结果。
3.5 工具描述词是AI的"操作手册"
MCP工具的docstring不是摆设,AI是靠描述判断是否调用工具的。描述写得好不好,直接决定准确率。
我自己的经验是描述要包含三个信息:工具做什么、典型触发场景、参数要求。举一个反例:
筛选Excel数据。这个描述太白,AI只知道"能筛选",但不一定清楚你能筛选什么类型。比较下面这个:
按数值类型条件筛选Excel数据。operator支持 >, >=, <, <=, ==,value为比较阈值。适用于用户想筛选销售额大于10000的记录、找出库存低于50的商品等场景。后者给定了具体参数范围,AI一看就知道该传什么。开发时多花30秒写描述,能减少后面大量的传参错误。
4. 把Server接进客户端:两种运行方式与一次真实AI任务
4.1 stdio和SSE:本地开发选stdio,团队共享选SSE
MCP Server有两种主流传输模式:stdio和SSE。
stdio模式下,Server由客户端作为子进程启动,输入输出走标准输入输出流。优点是不占网络端口,配置简单,适合本地个人使用。mcp.run()默认就是stdio模式,直接运行python server.py即可。
SSE模式下,Server作为独立HTTP服务运行,客户端通过URL访问。适合多人共用一个Server,比如团队里共享同一套Excel处理服务。启动方式:
if __name__ == "__main__": mcp.run(transport="sse")端口默认8000,客户端连接时填http://localhost:8000/sse。
需要提醒的是:SSE模式跑在网络上,一定要控制访问范围。个人开发就本机或用内网域名,别顺手开公网端口。MCP Server默认拥有本机文件系统的读写能力,裸奔在公网等于把家钥匙挂门口。
4.2 客户端配置:以Claude Desktop和通用客户端为例
现在主流客户端都支持MCP配置。以Claude Desktop为例,在配置文件里添加一段mcpServers配置:
{ "mcpServers": { "excel-mcp": { "command": "python", "args": ["D:/projects/excel-mcp/server.py"] } } }command是启动命令,args是Server脚本的绝对路径。配置逻辑是通用的:客户端启动子进程跑Server,然后通过stdio互相通信。
如果你用的是Cherry Studio、Cursor这一类工具,配置位置不同但原理一致,核心就是填command加args,或者选SSE模式填URL。第一次配置完如果列表里没刷新,重启客户端即可。
4.3 真实任务:从读表、筛选、汇总到写表全流程跑通
我手上的测试文件data/orders.xlsx有3000多行销售明细,字段包括日期、产品、类别、销量、单价、销售额。现在我在支持MCP的客户端对话框里说:
"看看orders.xlsx里有几张表,然后读取前几行。帮我把销量大于100的记录筛选出来,按类别汇总销售额,最后把汇总结果写到output/summary.xlsx。"
这句话放到传统方式下,我得写一个不少于30行、包含好几个pandas函数的脚本。而在MCP模式下,AI自己编排了整个工具链,我看日志看到的调用顺序是:
list_sheets获取Sheet列表;read_excel读取数据前50行,确认列名和字段含义;filter_excel按销量 > 100做筛选;summarize_excel按类别聚合并计算销售额总和;write_excel把结果写到输出文件。
全程我一行代码没写。中间我故意少说一句"操作符用大于",AI自己在数值比较的语境下默认选择了>。这五个工具组合起来,覆盖了我过去需要反复写好几遍脚本的完整链路。
这里要特别说明:MCP的最大价值不是消灭代码,而是消灭"需求到代码的翻译成本"。工具还得写,但写一次,供AI反复组合使用。
5. 踩坑实录:开发MCP时最容易翻车的几个问题
5.1 stdio模式下所有print都是"事故现场"
我第一次写完Server,兴冲冲地在代码里加了几个print用来调试,结果客户端连接时报ProtocolError,日志里全是奇奇怪怪的乱码。排查了半天才明白:stdio模式下,stdout这个通道被协议占用了,你print出来的内容会被客户端当成协议帧去解析,必然出错。
解决方案很简单:所有日志一律写入文件。
logging.basicConfig(filename="mcp.log", level=logging.INFO, force=True)这样既能看到运行日志,又不污染协议通道。这是一上来最容易被忽视、又最影响调试效率的坑。
5.2 中文路径、中文表头和编码的三个坑
Windows下的中文路径是个老大难。我在自己的Windows机器上测试时,data/订单表.xlsx这种路径,pandas读起来没问题,但如果你做路径拼接时用了旧式的gbk编码,偶尔会在某些依赖库里抛UnicodeDecodeError。
另一个坑是表头里的中文列名。pandas读取后列名默认是Unicode,传给AI完全没问题;但如果中间某一步做了编码转换,比如顺手encode('utf-8')之后又decode,很容易乱码。我的经验是:在读取阶段只做一次openpyxl引擎解析,全程保持Unicode,不手动转码。
最后,返回JSON时记得用ensure_ascii=False。默认的JSON序列化会把中文转成\uXXXX转义序列,AI读到一堆转义符,可读性大打折扣。
5.3 AI传参不按规格来?让schema回到预期
有一次测试,我在工具里定义了value: float,结果AI传入的是字符串"一百",导致pydantic参数校验失败。工具执行的报错信息会整个抛回给AI,AI看到后又重新修正参数再试。
这个机制本身是好的,但你不能一味依赖它。更有效的做法是在描述里明确示例:
value为数值类型的比较阈值,例如100、50.5这种数字,不要传中文数字或百分比字符串。参数校验失败不是灾难,MCP会把异常信息传回客户端,AI会自动尝试修正。但描述越清晰,这类自动修正的次数就越少,整个任务跑得更流畅。
5.4 路径越权:给MCP工具加个"安全锁"
MCP Server跑在本地,如果工具能力是"读写任意文件路径",AI一旦被恶意指令利用,就能翻遍整台机器的文件。我在项目里专门写了个_safe_path函数,把路径限定在data和output两个目录里。
安全锁的思路很简单:所有工具函数在干活之前先调用_safe_path做一次绝对路径校验,不通过就直接抛异常。这样即使AI被诱导请求任意路径,也只能读写白名单目录。
对于更严格的环境,还可以加上文件扩展名校验、按用户级别控制可访问目录。这些细节在个人项目里可有可无,但放到企业内网环境就是必须项。
5.5 SSE端口占用和多客户端连接问题
SSE模式用久了会碰到端口占用。Windows下Address already in use,一般是上一次Server进程没退出干净。解决方式很简单:关掉所有相关Python进程,或者换一个端口启动。
多客户端共用一个SSE Server时,还要注意临时文件冲突。比如客户端A和B同时让Server写output/summary.xlsx,后写入的会覆盖先写入的。我的处理是在描述里要求AI把输出文件写成带标识的名字,比如summary_20250217.xlsx,从源头避开冲突。
6. 后面还能怎么玩:从Excel到一整套办公自动化
6.1 工具矩阵可以扩展到PPT、Word、PDF甚至数据库
Excel只是第一个场景,同样的Server结构稍加修改,就可以扩展成一套办公自动化MCP。比如增加python-docx读写Word合同,用pymupdf提取PDF文本,再或者直接对接SQLite做数据查询。工具描述的写法、参数校验的思路、权限控制的架构全部可以复用。
我下一步的计划是加一个generate_report工具,让AI基于汇总结果自动生成一份带标题、表格和结论的Markdown日报。这本质上是把"分析"和"输出"再往上推一层,形成从原始数据到最终文档的一条龙链路。
6.2 MCP生态里已经有人在做的"工作流"方向
最近常看到有人在讨论n8n工作流、Coze工作流,很多人问这和MCP什么关系。其实它们是可以配合的:MCP让AI获得工具调用能力,工作流平台把这些能力编排成固定节奏的自动化流程。比如你可以在n8n里定时触发一个AI Agent,用MCP读取当日Excel,汇总后通过邮件或IM发出去。
另外,MCP生态本身也在快速外扩——有人做网页自动化的Playwright MCP,有人做3D建模软件Blender MCP。这些案例都说明:MCP不是某个厂商私有协议,而是新一代AI工具连接的通用标准。你在这里学的Server开发经验,换一个场景就是另一套工具的Agent能力。
6.3 把它变成团队可用的东西,注意三件事
如果想让这套Excel MCP真正给团队用,我认为要补三件事。
第一,加鉴权。SSE服务不允许匿名访问,至少套一层Token或内网白名单。
第二,加审计日志。每个工具调用时间、谁调用的、操作了什么文件,全记录。AI操作越方便,日志越要齐全,否则出了问题没法回溯。
第三,模型的选型。MCP并不绑定某一家大模型,控制器逻辑在Server端,客户端用哪个模型可以随时切换。这意味着你可以先用云端模型验证效果,再逐步换成私有化部署的模型,平稳落地。
我自己在这套流程跑通之后的真实感受是:MCP改变的不是某一个工具,而是人和软件打交道的方式。过去我为了一个临时需求要写一个完整的脚本,脚本用两三个月就被扔进角落;现在我把通用能力沉淀成工具,需求来了只需要把话说明白,AI自己会组合这些工具,我再也不用维护一堆"一次性脚本"了。
如果你也想入门MCP,别去死记规范文档,拿手头最常用的那个Excel文件起手,写你的第一个Server。从1个工具开始,跑通闭环,再加功能。等你回头再看,那些接近20年前VBA时代的旧习惯,已经可以正式退休了。