最近很多朋友问我:MCP到底是个什么东西?网上教程一堆,但看完还是不知道从哪下手。我的建议从来都是:别去背概念,直接做一个自己天天用得上的小工具。我选的场景就是Excel——每天都要处理表格,报表、数据清洗、把杂乱的无格式数据整理成规范格式……这些重复劳动浪费了太多时间。
这篇文章记录我开发自己的第一个MCP服务器的完整过程:用Python写一个MCP服务,让AI能直接读取、分析、写入Excel文件,把“和表格打交道”这件事交给AI去干。我会把从零到一的思路、代码、配置、踩的坑全部写出来。适合想入门MCP开发、平时又离不开Excel的读者,有一点Python基础就能跟上。
1. 为什么第一个项目选“Excel + MCP”
1.1 被Excel重复劳动逼出来的真实念头
我一开始也没想过要给AI做工具,真正促使我动手的是日常工作的“痛苦感”。你想象一下这样的日子:早上收三个同事发来的表,格式五花八门;中午老板要“把上周的数据按地区汇总”;下午又要从几百行明细里筛出某个渠道的记录。每一件都不难,但每一件都要人工打开文件、人工翻找、人工复制粘贴。一天下来,表格处理消耗的时间远超真正做判断的时间。
有人会说,这不就是写脚本能解决的吗?确实能,可问题在于:写脚本之前,我要先把需求理解透,再翻译成pandas代码,跑完之后还要把结果翻译回同事能看懂的人话。换句话说,工具一直存在,但“人肉翻译”这一层从来没省掉。传统工作流里,人始终是那个把自然语言转成代码、再把代码结果转回自然语言的中间件。
1.2 MCP给AI补上了“能动手”的接口
MCP的全称是Model Context Protocol,直译过来是“模型上下文协议”。它在2024年底被开放成标准协议,本质是给AI模型和外部工具之间定一套统一的“插拔接口”。我不喜欢背定义,打一个比方你就懂了:MCP出现之前,AI调用工具是“闭门造车”式的,各家搞各家的接口,A家的插件接到B家的模型上大概率不干活;MCP出现之后,整个关系变成了你手机上的USB-C接口——镜头、读卡器、麦克风,只要是Type-C口,插上就能用。MCP就是AI世界的Type-C。
我们平时用网页版AI聊天,AI之所以干不了实事,是因为它被锁在对话框里。你说“把这份Excel清洗一下”,它看不到文件、碰不到磁盘,只能给你写一段你自己去跑的代码。而一旦有了MCP,AI就能通过协议直接调用你注册好的工具:读文件、写文件、筛选数据,全在对话里完成。自己开发一个MCP,说白了就是给AI“造一个趁手的工具”,让它能在你的电脑上真正开始干活。
1.3 选取Excel作为突破口的四个理由
先说结论:Excel是个人开发者练手MCP最理想的第一站,不是因为它潮,恰恰因为它普通。第一,场景足够高频。任何上班族都离不开表格,开发完的成果每天都能用上,正反馈来得快。第二,反馈足够直观。AI调用完工具之后,结果对不对,打开Excel看一眼就知道,不需要复杂的验收环境。第三,技术栈成熟。Python读写Excel有pandas和openpyxl两大工具库,不需要你从零造轮子,可以把全部精力放在学习MCP机制本身。第四,可扩展性强。你把这个流程打通之后,后面想做PDF处理、本地文件管理、API数据聚合,全是同一套方法论,只是工具的“躯体”不同而已。
1.4 MCP工作流和传统工作流的差别
我实际用下来的感受,可以用“跑腿环节移交给AI”来总结。传统流程中,人要做信息抽取、格式转换、重复筛选这些脏活累活;MCP工作流把这些臭烘烘的环节全部包给了模型和工具链,人只需要说清楚目标、检查最终结果。
| 环节 | 传统工作流 | MCP工作流 |
|---|---|---|
| 理解需求 | 人脑理解自然语言 | 模型直接理解自然语言 |
| 读取文件 | 手动打开Excel,肉眼扫描 | AI调用read_excel工具 |
| 筛选/清洗 | 手工排序、筛选、删除 | AI调用filter_rows等工具 |
| 生成结果 | 手动另存为、复制粘贴 | AI调用write_excel |
| 复核 | 人工逐步对账 | 人工只看最终结果和关键中间态 |
注意,重构不等于彻底取代人。我的判断是:判断力、异常识别、最终确认依然留给人类,被替换掉的只是重复的机械操作。基于这个定位,开发目标就很清晰了:我们需要的不是一个大而全的“办公机器人”,而是一个能在数据世界里帮我们跑腿的“数字助理”。
2. 开发前必须想清楚的方案选型
2.1 MCP的三层结构:Host、Server、Protocol
理解MCP,抓住三个角色就够了。
Host,也叫客户端或宿主,就是运行大模型和界面的应用,比如Claude Desktop、Cline这类工具。它负责和用户对话,也负责帮用户调用外部工具。Server,就是你自己写的服务端程序,里面注册了一个个“工具”。每个工具本质上就是一个能完成具体任务的函数,比如“读取Excel”“写入Excel”“筛选数据”。Protocol,则是夹在Host和Server之间的通信规范,定义了工具怎么被发现、怎么被调用、结果怎么返回。
整个调用链条是这样:用户在客户端提问,模型发现当前任务需要某个工具,于是客户端把调用请求发给服务器,服务器执行完把结果返回给客户端,模型再基于结果组织语言回答用户。对你这个项目来说,核心工作就一件:写一个Server,把Excel能力注册成工具。
2.2 传输方式:本地开发优先用stdio
MCP协议目前有三种主要的传输方式。stdio通过标准输入输出通信,零网络开销、配置简单,适合本地场景,也是新手入门的首选。你只要在客户端配置文件里指定“运行哪条命令启动服务器”,剩下的交给协议本身。SSE通过HTTP端口通信,适合服务器部署在另一台机器、或者希望多个客户端共享一个服务实例的场景,代价是要暴露端口、要考虑鉴权和并发。新出的Streamable HTTP可以认为是SSE的进阶形态,但想法没有变。
第一个项目强烈建议用stdio把链路先跑通。不要一上来就追求“远程生产力形态”,你的目标是用最小的成本看到AI真的能把Excel处理掉,而不是先纠结网络拓扑。
2.3 开发框架:官方SDK的FastMCP风格
MCP开发有两条路。一条是偏底层的,直接用官方mcp库实现工具发现、初始化握手、消息循环,优点是透彻掌握协议细节,缺点是样板代码多。一个最简单的“hello world”工具,光初始化就要写五六十行,新手很容易被劝退。
另一条是官方SDK内置的FastMCP风格封装,可以把它理解成Flask之于Web开发:装饰器一标,函数一写,工具就注册好了,初始化、握手、消息循环这些细节由框架处理。我这篇文章的代码就是用这种写法。它最大的价值是让你把注意力集中在“业务逻辑”上,不被协议细节分散。
依赖安装方面,我用的是下面几项:
pip install mcp pandas openpyxl tabulate建议Python版本不低于3.10。太老的版本在类型注解和文件路径处理上会踩不少坑。
2.4 第一版功能清单:只做能用的闭环
我见过太多初学者一上来就想做一个“全功能Excel MCP”,计划表里写了十几个工具,最后没有一个能完整跑通。这个项目我坚持最小可用闭环:先把链路走通,再做增强。第一版就规划了四个工具:
| 工具名 | 功能说明 | 对应痛点 |
|---|---|---|
| read_excel | 读取指定工作表的数据行 | 每次都要人工打开文件看内容 |
| get_sheet_info | 查看工作表结构和行列概况 | 不知道文件里到底装了什么 |
| write_excel | 将AI整理好的数据写入Excel | 复制粘贴容易错位、丢格式 |
| filter_rows | 按关键词筛选数据行 | 手工Ctrl+F一条条排查 |
够用就行。这四个工具覆盖了“读、查、筛、写”四个最典型环节,AI组合调用它们就能完成很多实际任务。
3. 从零搭建第一个MCP服务器:完整代码与接入
3.1 工程结构与依赖安装
我的工程目录非常简单,没有用复杂的包结构:
excel-mcp/ server.py workspaces/workspaces是放业务Excel文件的工作目录,为什么单独建一个目录,我后面会在“安全边界”一节详细解释。先把习惯养成:所有被AI操作的文件,都放在这个白名单目录里。
安装依赖的顺序也有讲究。建议先升级pip,再装这几样:
pip install -U pip pip install mcp pandas openpyxl tabulatetabulate可能容易被忽略,但pandas的to_markdown方法依赖它。没有这个库,程序运行到表格格式化那一步会直接报错。
3.2 server.py完整实现与代码解读
下面是我测试通过的完整代码。全程用中文注释写清楚,这对工具非常重要——因为模型的“工具说明书”就是函数的签名和docstring,注释越清晰,AI调用越准确。
from pathlib import Path import io import pandas as pd from mcp.server.fastmcp import FastMCP # 白名单目录:只允许工具操作这个目录下的文件 WORKSPACE = Path(__file__).parent / "workspaces" # 初始化MCP服务器,名称会显示在客户端 mcp = FastMCP("excel-assistant") def _resolve_path(file_path: str) -> Path: """把输入的文件路径解析为白名单内的绝对路径,防止路径穿越。""" p = Path(file_path) if not p.is_absolute(): p = WORKSPACE / p p = p.resolve() # 校验路径必须位于工作区内 if not p.is_relative_to(WORKSPACE.resolve()): raise ValueError(f"无权访问该路径: {file_path}") return p @mcp.tool() def read_excel(file_path: str, sheet_name: str = "", max_rows: int = 100) -> str: """读取Excel文件中的表格数据。 参数: file_path: 文件名或相对路径,相对于工作区目录 sheet_name: 工作表名,留空则读取第一个工作表 max_rows: 最多返回多少行,默认100,最大1000 返回: Markdown格式的表格内容 """ path = _resolve_path(file_path) if not path.exists(): return f"错误:文件不存在 {path.name}" df = pd.read_excel(path, sheet_name=sheet_name if sheet_name else 0, nrows=min(max_rows, 1000)) if df.empty: return "没有读取到数据。" return df.to_markdown(index=False, numalign="left") @mcp.tool() def get_sheet_info(file_path: str) -> str: """查看Excel文件有哪些工作表,以及每个表的行列概况,用于快速了解文件结构。""" path = _resolve_path(file_path) if not path.exists(): return f"错误:文件不存在 {path.name}" xl = pd.ExcelFile(path) lines = [f"文件: {path.name}", f"工作表数量: {len(xl.sheet_names)}"] for name in xl.sheet_names: df = xl.parse(name, nrows=5) lines.append(f"- {name}: 采样{len(df)}行 x {len(df.columns)}列,列名: {list(df.columns)}") return "\n".join(lines) @mcp.tool() def write_excel(file_path: str, data: str, sheet_name: str = "Sheet1") -> str: """将CSV格式的文本数据写入Excel文件,首行作为表头。 参数: file_path: 目标文件名 data: CSV文本内容,例如 "姓名,部门\\n张三,技术部\\n李四,运营部" sheet_name: 工作表名,默认Sheet1 返回: 写入结果说明 """ path = _resolve_path(file_path) df = pd.read_csv(io.StringIO(data), encoding="utf-8") if df.empty: return "错误:没有可写入的数据。" with pd.ExcelWriter(path, engine="openpyxl", mode="w") as writer: df.to_excel(writer, sheet_name=sheet_name, index=False) return f"写入成功:{path.name},共{len(df)}行" @mcp.tool() def filter_rows(file_path: str, keyword: str, column: str = "") -> str: """在Excel表格中搜索包含指定关键词的行。 参数: file_path: 文件名 keyword: 要搜索的关键词 column: 指定在哪个列搜索;留空则在所有列中搜索 返回: 匹配到的数据行(Markdown格式) """ path = _resolve_path(file_path) if not path.exists(): return f"错误:文件不存在 {path.name}" df = pd.read_excel(path) if column: mask = df[column].astype(str).str.contains(keyword, na=False) else: mask = df.apply( lambda row: row.astype(str).str.contains(keyword, na=False).any(), axis=1 ) result = df[mask] if result.empty: return f"在{len(df)}行数据中没有找到包含「{keyword}」的记录。" return result.to_markdown(index=False, numalign="left") if __name__ == "__main__": mcp.run()代码不到100行,但覆盖了工具注册、路径安全校验、数据读写逻辑。核心要理解三点。
第一,@mcp.tool()装饰器就是注册工具的关键。函数的docstring会被AI当作“工具说明书”读取,所以每个参数都要写清楚是什么、默认值是什么。模型会根据这些描述决定怎么调用、传什么参数。
第二,_resolve_path这套白名单校验务必保留。很多教程示例把路径校验省掉了,实际上这是把“让AI操作你电脑文件”的安全底线丢掉了。后面我还会专门展开讲。
第三,工具命名要直白。read_excel就是读表,filter_rows就是筛行。别起花里胡哨的名字,模型理解起来又快又准。
3.3 本地验证:用MCP Inspector试跑工具
写好代码之后,强烈建议不要直接接客户端,先用MCP Inspector验证一遍。在项目目录下执行:
python -m mcp dev server.py命令会启动一个本地调试服务,你可以从终端给的地址打开浏览器界面,看到服务器提供了哪些工具,选择某个工具、填入参数、直接调用,立刻看到返回结果。
如果没有安装命令行工具,也可以用npx方式:
npx @modelcontextprotocol/inspector python server.py我的习惯是先用Inspector把四个工具逐个调一遍,确认read能出表、write能建文件、filter能筛对人,再去做客户端接入。这一步能把“服务器本身的问题”和“客户端连接的问题”分隔开,排查会轻松很多。
3.4 接入Claude Desktop的配置步骤
工具本身跑通之后,剩下的就是把服务器“挂”到客户端上。以Claude Desktop为例,在它的配置文件(claude_desktop_config.json)里加入下面的内容:
{ "mcpServers": { "excel-assistant": { "command": "python", "args": ["D:/projects/excel-mcp/server.py"] } } }注意command和args要写对。Windows上python命令有时候不在PATH里,这时候要把解释器的完整路径写进去,比如C:/Users/你的用户名/AppData/Local/Programs/Python/Python311/python.exe。路径分隔符建议用正斜杠/,因为JSON里反斜杠是转义符,直接用Windows的反斜杠很容易把配置搞坏。
配置完成后重启客户端,通常能在对话界面看到“已连接工具”一类的提示,说明AI已经具备了调用Excel工具的能力。这时候你可以直接说:“读取workspaces目录下的销售明细.xlsx,帮我筛选出金额大于10000的行。”如果一切正常,模型会自动决定先调用get_sheet_info了解文件结构,再调用read_excel或filter_rows执行任务,全程不需要手动点开Excel。
4. Excel工具开发中的关键细节与安全边界
4.1 大文件读取:别让一次调用吃掉全部内存
很多人的Excel看起来只有几百KB,加上公式和样式之后,变成几百MB也很常见。你的MCP工具如果每次都全量读入内存,模型还没开始分析,机器先卡住了。
我在read_excel里给max_rows设了默认100、上限1000,就是故意“限制”AI的手脚。它在初步了解时只需要看前几行,确认工作表结构后再按需扩大读取范围。这个“分步读取”的思路,让大文件场景的体验好了很多。
另外,pandas读取Excel时会对列做类型推断。混着数字和文本的列经常被猜错,比如“00123”被读成数字123。如果发现AI读出来之后数据对不上,可以提示AI在调用工具时明确指定读取方式,或者在Excel里提前把这类列的格式改成文本。这个问题不是MCP特有的,是pandas处理的固有特性,但在AI调用场景里会被放大,因为你可能不会像以前那样逐个单元格检查。
4.2 写入策略:新建文件与修改旧表要分开
write_excel默认用的mode="w",也就是目标文件已存在时直接覆盖整个工作簿。测试场景下没问题,但真实办公里,同事发来的表可能带着一堆格式、图表、透视表,一个覆盖操作全部灰飞烟灭。
更稳妥的做法是分两种场景。如果是AI生成的新数据要写新文件,直接mode="w"新建,没问题。如果要修改已有表格,老老实实走openpyxl读进来再改指定单元格,不要用pandas整体覆盖。openpyxl可以按单元格写入并保留其他所有内容:
from openpyxl import load_workbook def update_cell(file_path: str, sheet_name: str, row: int, col: int, value) -> str: wb = load_workbook(file_path) ws = wb[sheet_name] ws.cell(row=row, column=col, value=value) wb.save(file_path) return "单元格已更新"“覆盖原表”这种操作一旦由AI自动执行,代价比人手工失误大得多。开发时宁可在工具设计阶段多暴露一些选项,也不要让AI默认就对正式文件动手。这属于一开始就要想清楚的边界问题。
4.3 并发调用:给工具说明书加上约束
你不妨试一次同时让AI“统计各部门人数并各自写一个result文件”,你会在工作目录看到服务器同时收到多次写入调用。FastMCP本身支持并发执行工具,但pandas写Excel时对同一个文件的并发写会直接报错。
解决思路有两个。一是在工具设计上约定:每次写入用时间戳或随机后缀生成文件名,避免冲突。二是更简单的做法,在write_excel的docstring里加一句“执行写入操作时不要并行,一次只写一个文件”,模型通常会遵循工具说明书上的约定。
临时文件也是个隐藏细节。AI执行中间过程可能会写出中间结果,这些文件容易堆积。可以在工作区里建一个temp目录,在工具文档中写明“中间文件默认放temp目录”,定期清理即可。
4.4 安全底线:用白名单目录限定AI的活动范围
这可能是整个项目里我最想强调的一节。当你把“能操作你电脑文件的AI”装到客户端时,你实际上把外部模型的能力延伸到了本地文件系统。模型可能因提示词注入被操纵,也可能因理解偏差去改动不该动的文件。
我的原则是:MCP工具默认只给白名单目录里的文件操作权限。服务器启动时创建workspaces目录,所有工具操作的文件路径必须resolve之后仍在这个目录内,否则直接拒绝。这样即便AI“乱来”,伤害也被限制在一个沙箱里。桌面、系统目录、项目源码文件夹都碰不到。
具体到代码层面,就是那个_resolve_path函数。别看它只有几行,作用非常关键。新手做第一个MCP时,务必把安全边界一并做上,这比多写一个工具重要得多。有了这层防护,你才敢放心地对AI说“帮我把这些表合并了”。
5. 常见问题排查实录与速查表
5.1 客户端找不到MCP服务器的排查顺序
一半以上的配置问题都出在两个原因上。第一个是command路径不对。特别是Windows下,python解释器并不都在PATH里,客户端按“python”找不到命令。排查方法是在终端执行where python,把完整路径填进配置。第二个是启动报错被吞掉。客户端经常只显示一句“连接失败”,不显示Python的报错信息。这时候不要盯着配置反复看,直接把启动命令在终端手动执行一遍:
python D:/projects/excel-mcp/server.py把报错修完,再回客户端重试。这个思路适用于所有MCP排障:先证明服务器能自己跑起来,再检查连接配置。
5.2 中文乱码与路径坑
pandas读CSV遇到中文乱码,几乎都是编码问题。Excel的xlsx格式不算有这个问题,但如果你让AI顺手处理CSV,记得在工具实现里明确编码参数:
pd.read_csv(path, encoding="utf-8-sig")utf-8-sig比utf-8更适合Windows环境,因为它会正确跳过BOM头,Excel打开也不乱码。
中文路径方面,Path的resolve方法能正确处理中文,真正容易出问题的是客户端配置JSON里的中文和反斜杠。JSON里反斜杠是转义符,Windows路径要用正斜杠“/”或者把反斜杠写成双反斜杠“\\”。很多新手在这里折腾半天,最后发现就是少写了一个斜杠。
5.3 工具超时与任务拆分策略
MCP工具默认有执行时间限制。如果你的工具要处理大文件,一个跑30秒的任务很容易被客户端判定超时。两种处理思路:一是对工具本身加限制,超大文件直接拒绝并提示“文件过大,请分批处理”;二是拆分任务粒度,让一次工具调用只做一件小事,比如“读取前100行”“筛选某一列等于某值”,由模型在对话里多次调用组合来完成大任务。
我实测下来,“小工具、多调用”的体验远好于“大工具、孤注一掷”。因为模型每一步都能看到中间结果,发现异常可以即时调整策略,而不是等最后一步崩了才发现前面的数据不对。这也是为什么我把默认max_rows设成100而不是设成一次性全读。
5.4 问题速查表
为了方便调试,我把这个项目踩过的坑整理成一个速查表。
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 客户端找不到服务器 | python命令不在PATH | 配置里写python完整路径 |
| JSON配置加载失败 | 反斜杠被转义 | 路径改用正斜杠“/” |
| 启动没有报错但连不上 | 服务器初始化异常被吞 | 终端手动跑server.py看输出 |
| 中文CSV乱码 | 编码不一致 | read_csv指定utf-8-sig |
| 大文件调用超时 | 单次任务过重 | 拆分任务,限制默认nrows |
| 写入覆盖原表格式 | mode="w"整表覆盖 | 旧表修改用openpyxl按单元格写入 |
| 同一文件并发写报错 | pandas不支持同一文件并发写 | docstring要求串行操作 |
| AI读不到文件 | 路径不在白名单内 | 文件放workspaces目录 |
6. 下一步:把第一个MCP变成日常工具
6.1 实测感受:这些场景真的变快了
项目跑通之后,我连续用了一周,总结了真正省时间的场景。最典型的是“清洗不规范表格”:同事发来一个列名乱七八糟、带合并单元格的明细表,以前要手工调整半天,现在直接让AI“把列名改成规范格式,删除空行,把金额转成数字”,三步内搞定。其次是“按条件提取数据”,几百行的客户名单,按地区、按金额区间、按时间范围筛,AI调用filter_rows工具几秒出结果。
但也有不适合的场景。比如需要精细排版、带特定企业模板的报表,AI生成的格式还是需要人工调;涉及复杂公式链和透视表联动的表格,AI目前也只能“看懂”数据,未必能维持公式逻辑。我的判断是:不要期待MCP解决所有表格问题,把重复、琐碎、判断简单的事情交给它,人去做真正需要业务理解的事情。
6.2 可扩展的方向与生态参考
第一个MCP跑通之后,可以扩展的方向其实很多。你可以给服务器增加工具:合并多个Excel、按列拆分工作表、批量转换为CSV,这些都是高频需求。也可以把服务器从stdio升级到SSE,部署到公司内网服务器上,让多个同事共享同一个MCP服务。
再往外看,MCP生态里已经有浏览器自动化、数据库操作、设计软件脚本插件等大量服务器。任何一个场景都可以按“定义工具、实现函数、注册服务”这套模式去套。当你自己开发过一个之后,再看那些成熟的MCP服务器,不会再觉得神秘,因为它们骨子里就是一堆注册好的工具函数。
我个人在这几次迭代里最大的体会是:做第一个MCP最大的收获不是代码本身,而是真正理解了“AI Agent是怎么干活的”。当你看着模型自己决定先读哪个工作表、再调什么参数筛选数据、最后把结果写成新文件,那种“工具链在AI手里被自主编排”的感觉,比看一百篇文章都直观。
最后说个实在的建议:别等“彻底学会了再写”。花五分钟搭一个最小服务器,注册一个“读Excel”的工具,让AI帮你干一件平时重复的小事。从那个时刻起,你就不再是看客了。MCP这东西,名字唬人,拆开就是你给AI写的一个个函数。动手之后你会发现,门槛比想象中低得多,上限却高得多。