1. 为什么我要自己动手写一个 MCP
1.1 从一次崩溃的 Excel 处理经历说起
上个月帮朋友处理一批销售数据,大概三百多个 Excel 文件,每个文件结构类似但列名有细微差异,需要统一格式后合并汇总。我一开始想的是写个 Python 脚本批量跑一遍就完事了,结果打开第一个文件就发现,列名有中文有英文,有的表头在第一行,有的在第三行,还有几个文件里混着合并单元格。纯脚本处理不是不行,但每遇到一种新情况就得改代码、重跑、再检查,来回折腾了整整一个下午。
后来我换了个思路:能不能让 AI 来帮我判断这些乱七八糟的表格结构,然后自动生成处理逻辑?试了几个现成的方案,要么是上传文件到网页端手动操作,要么是写死的规则引擎,灵活性都不够。直到我认真研究了一下 MCP 这个东西,才发现它正好能解决我的问题——把 Excel 处理能力封装成一个 AI 可以调用的工具,让 AI 根据实际数据情况动态决定怎么处理。
这就是我开发第一个 MCP 项目的起点。说白了,MCP 就是一套让 AI 模型能够调用外部工具的协议标准,你可以把它理解成 AI 和你的代码之间的一座桥。桥这边是 AI 的对话界面,桥那边是你写的 Python 函数,AI 说“帮我读一下这个 Excel 的列名”,你的函数就去读,然后把结果返回给 AI,AI 再决定下一步做什么。
1.2 这个项目适合谁来参考
如果你符合下面任意一条,这篇内容应该对你有用:
- 经常需要处理 Excel 数据,但每次遇到格式不统一的文件就头疼
- 有 Python 基础,想试试怎么把自己的代码接入 AI 工作流
- 听说过 MCP 但还没动手写过,想找一个完整的入门项目练手
- 已经在用一些 AI 编程工具,想进一步扩展 AI 能做的事情
不需要你是什么 AI 专家,也不需要你懂什么大模型原理。只要你会写基本的 Python 函数,知道 Excel 大概是怎么回事,就能跟着走下来。我尽量把每一步都拆开讲清楚,包括我踩过的坑和后来想明白的道理。
1.3 整体方案是怎么设计的
我的核心思路很简单:把 Excel 处理拆成若干个原子操作,每个操作封装成一个 MCP 工具,让 AI 根据任务需要自己组合调用。
具体来说,我定义了这么几个工具:
| 工具名称 | 功能 | 输入 | 输出 |
|---|---|---|---|
| read_excel_info | 读取表格基本信息 | 文件路径 | 列名、行数、数据类型 |
| read_excel_data | 读取指定范围数据 | 文件路径、列名、行范围 | 二维数组 |
| write_excel_data | 写入数据到表格 | 文件路径、数据、目标位置 | 操作结果 |
| transform_column | 对某列做转换 | 文件路径、列名、转换规则 | 新列数据 |
| merge_excel_files | 合并多个表格 | 文件路径列表、对齐规则 | 合并后文件路径 |
这样设计的好处是,AI 不需要一次性理解整个复杂任务,它可以先读信息、再判断、再操作、再验证,像人一样一步步来。比如遇到列名不统一的情况,AI 可以先读所有文件的列名,然后自己判断哪些列是同一个意思,再决定怎么对齐。
为什么不用一个大而全的函数搞定所有事情?因为 AI 调用工具的次数是有限的,每次调用都有开销。如果工具粒度太粗,AI 很难精确控制;如果粒度太细,调用次数又会爆炸。我试了几轮之后发现,按“读、写、转换、合并”这四个维度来拆分,颗粒度刚刚好。
2. 动手之前先把这些概念理清楚
2.1 MCP 到底是个什么东西
MCP 全称是 Model Context Protocol,翻译过来叫“模型上下文协议”。这个名字听起来很唬人,但其实概念特别简单。
你可以把它想象成 USB 接口。以前每个设备都有自己的充电口,诺基亚的、摩托罗拉的、索尼的都不一样,出门得带一堆线。后来有了 USB-C,一个口就能搞定所有设备。MCP 做的事情类似:以前每个 AI 平台接入外部工具的方式都不一样,OpenAI 有一套、Claude 有一套、其他平台又有一套,开发者要适配很多遍。MCP 就是想让大家都用同一个标准,你按这个标准写一次工具,所有支持 MCP 的 AI 平台都能用。
具体到技术层面,MCP 定义了一套通信规则:AI 端叫 Client,工具端叫 Server,两者之间通过标准化的消息格式来交互。Server 端需要声明自己提供哪些工具、每个工具需要什么参数、返回什么结果。Client 端负责把 AI 的意图翻译成对工具的调用请求,再把结果传回给 AI。
我一开始以为 MCP 是什么高深的技术,实际写下来发现,核心就是定义一个工具列表,然后实现每个工具的处理函数。协议本身帮你处理了通信、序列化、错误传递这些事情,你只需要关注业务逻辑。
2.2 为什么选 Python 来写
选 Python 的理由很直接:Excel 处理生态最成熟。openpyxl、pandas、xlrd 这些库经过多年打磨,各种边界情况都处理得差不多了。你要是用 JavaScript 或者 Go 来写,光是处理 Excel 的各种格式兼容性就够喝一壶的。
另外 Python 写 MCP Server 的入门门槛确实低。官方提供了 SDK,你装好包之后,定义一个函数、加个装饰器、声明一下参数类型,基本上就能跑了。不需要你懂什么异步编程、网络协议,SDK 都帮你封装好了。
还有一个实际考虑:我平时做数据分析、自动化脚本都是用 Python,Excel 处理这块的代码积累比较多,直接复用起来方便。如果你主力语言是别的,也不用担心,MCP 协议本身是语言无关的,用你熟悉的语言写 Server 完全没问题,只是 Python 的现成轮子最多。
2.3 环境准备:装什么、怎么装
先把基础环境搭好。我假设你已经装了 Python,如果还没装,去官网下载最新稳定版就行,安装的时候记得勾选“Add Python to PATH”,不然后面命令行里调不到 python 命令。
装好 Python 之后,打开终端或命令行,依次执行下面几个命令:
pip install mcp pip install openpyxl pip install pandas这三个包分别是 MCP 的 Python SDK、Excel 读写库、数据处理库。版本方面,mcp 建议用 1.0 以上的,openpyxl 用 3.1 以上,pandas 用 2.0 以上。如果你之前装过旧版本,可以先升级一下:
pip install --upgrade mcp openpyxl pandas装完之后可以验证一下:
import mcp import openpyxl import pandas print(mcp.__version__) print(openpyxl.__version__) print(pandas.__version__)能正常打印出版本号就说明环境没问题了。
注意:如果你用的是 Windows 系统,建议在 WSL 或者 Git Bash 里操作,原生 CMD 和 PowerShell 有时候会有路径和编码的坑。我一开始在 CMD 里跑,中文路径死活读不出来,换成 Git Bash 就正常了。
2.4 项目目录结构怎么安排
我习惯把 MCP 项目按下面这个结构来组织:
excel-mcp-server/ ├── server.py # MCP Server 主入口 ├── tools/ │ ├── __init__.py │ ├── read_tools.py # 读取相关工具 │ ├── write_tools.py # 写入相关工具 │ └── transform_tools.py # 转换相关工具 ├── utils/ │ ├── __init__.py │ └── excel_helper.py # Excel 操作辅助函数 ├── requirements.txt └── README.md这样分的好处是,工具多了之后不会全挤在一个文件里。每个工具模块只负责一类操作,改起来方便,也容易测试。utils 里放一些公共的辅助函数,比如路径处理、数据类型判断、异常封装这些。
requirements.txt 里把依赖列清楚:
mcp>=1.0.0 openpyxl>=3.1.0 pandas>=2.0.03. 核心工具的实现细节
3.1 读取表格信息:让 AI 先“看一眼”数据
这是最基础也最重要的一个工具。AI 在处理任何 Excel 之前,都需要先了解这个文件长什么样:有多少行、多少列、列名叫什么、每列大概是什么类型的数据。
实现思路很直接,用 openpyxl 打开文件,读第一行作为列名,然后统计行数,再对每列做一下类型推断。
from openpyxl import load_workbook from mcp.server import Server from mcp.types import Tool, TextContent server = Server("excel-mcp") @server.tool() async def read_excel_info(file_path: str, sheet_name: str = None) -> str: """读取 Excel 文件的基本信息,包括列名、行数、数据类型""" try: wb = load_workbook(file_path, read_only=True, data_only=True) if sheet_name: ws = wb[sheet_name] else: ws = wb.active # 读第一行作为列名 headers = [] for cell in ws[1]: headers.append(cell.value if cell.value else f"列{cell.column}") # 统计行数 row_count = ws.max_row - 1 # 对每列做类型推断 column_types = {} for idx, header in enumerate(headers): sample_values = [] for row in ws.iter_rows(min_row=2, max_row=min(12, ws.max_row), min_col=idx+1, max_col=idx+1): for cell in row: if cell.value is not None: sample_values.append(cell.value) if not sample_values: column_types[header] = "空列" elif all(isinstance(v, (int, float)) for v in sample_values): column_types[header] = "数值" elif all(isinstance(v, str) for v in sample_values): column_types[header] = "文本" else: column_types[header] = "混合" wb.close() result = f"文件: {file_path}\n" result += f"工作表: {ws.title}\n" result += f"数据行数: {row_count}\n" result += f"列数: {len(headers)}\n\n" result += "列信息:\n" for header in headers: result += f" - {header} ({column_types[header]})\n" return result except Exception as e: return f"读取失败: {str(e)}"这里有几个细节值得说一下。
为什么用 read_only=True:只读模式打开文件速度快很多,尤其是大文件。我们只是读信息,不需要写入,用只读模式最合适。data_only=True 是为了拿到公式计算后的值,而不是公式本身。
为什么只采样前 10 行做类型推断:全量扫描太慢,而且实际数据里经常有异常值。采样前 10 行基本能判断出这一列的主要类型。如果前 10 行都是数字,第 11 行突然来个文本,那属于数据质量问题,不是类型推断要解决的。
列名为空怎么处理:实际 Excel 里经常有列没有表头,我用“列+序号”来兜底,保证每个列都有个标识符,AI 后续引用的时候不会搞混。
3.2 读取数据:按需取数,不要一次全读
AI 有时候只需要看几行数据来确认格式,有时候需要读整列来做分析。所以读取工具要支持灵活的范围指定。
@server.tool() async def read_excel_data( file_path: str, sheet_name: str = None, columns: list = None, start_row: int = 2, max_rows: int = 100 ) -> str: """读取 Excel 指定范围的数据""" try: wb = load_workbook(file_path, read_only=True, data_only=True) ws = wb[sheet_name] if sheet_name else wb.active # 获取所有列名 all_headers = [cell.value for cell in ws[1]] # 确定要读的列索引 if columns: col_indices = [] for col in columns: if col in all_headers: col_indices.append(all_headers.index(col)) else: return f"列 '{col}' 不存在,可用列: {all_headers}" else: col_indices = list(range(len(all_headers))) # 读取数据 data_rows = [] for row in ws.iter_rows(min_row=start_row, max_row=start_row + max_rows - 1, values_only=True): selected = [row[i] if i < len(row) else None for i in col_indices] data_rows.append(selected) wb.close() # 格式化为表格文本 selected_headers = [all_headers[i] for i in col_indices] result = " | ".join(str(h) for h in selected_headers) + "\n" result += "-" * 40 + "\n" for row in data_rows: result += " | ".join(str(v) if v is not None else "" for v in row) + "\n" return result except Exception as e: return f"读取失败: {str(e)}"这个工具的关键在于参数设计要符合 AI 的使用习惯。AI 不会一次性告诉你“我要第 3 到第 15 行、第 2 和第 5 列”,它更可能说“帮我看看‘销售额’这一列的前 20 个值”。所以我把参数设计成列名列表 + 起始行 + 最大行数,AI 理解起来更自然。
max_rows 默认给 100,是防止 AI 不小心把整个大表都读出来,导致返回内容过长。如果确实需要更多数据,AI 可以自己调大这个参数,但至少有个默认的安全边界。
3.3 写入数据:小心再小心
写入操作比读取危险得多,因为一旦写错就可能覆盖原始数据。我的做法是默认不覆盖原文件,而是写入新文件,除非调用方明确指定要覆盖。
import shutil from datetime import datetime @server.tool() async def write_excel_data( file_path: str, data: list, sheet_name: str = None, start_cell: str = "A1", create_backup: bool = True ) -> str: """向 Excel 写入数据,默认创建备份""" try: # 创建备份 if create_backup: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") backup_path = f"{file_path}.backup_{timestamp}" shutil.copy2(file_path, backup_path) wb = load_workbook(file_path) ws = wb[sheet_name] if sheet_name else wb.active # 解析起始单元格 from openpyxl.utils import coordinate_to_tuple start_row, start_col = coordinate_to_tuple(start_cell) # 写入数据 for i, row_data in enumerate(data): for j, value in enumerate(row_data): ws.cell(row=start_row + i, column=start_col + j, value=value) wb.save(file_path) wb.close() return f"写入成功,共 {len(data)} 行,起始位置 {start_cell}" except Exception as e: return f"写入失败: {str(e)}"备份机制是必须的。我在这上面吃过亏,有一次测试的时候不小心把客户给的原始数据覆盖了,还好之前手动复制过一份。从那以后,所有写入操作我都强制加备份,哪怕多占点磁盘空间也值得。
start_cell 参数用 Excel 的坐标格式(如“A1”、“C5”),而不是行号列号分开传,是因为 AI 对 Excel 坐标更熟悉,它生成坐标比生成两个数字更不容易出错。
3.4 列转换:最常用的数据处理操作
实际工作中,大部分 Excel 处理需求都可以归结为“对某一列做某种转换”。比如把文本转成数字、把日期格式化、把分类值映射成另一组值、对数值做计算等等。
@server.tool() async def transform_column( file_path: str, column_name: str, operation: str, params: dict = None, output_column: str = None ) -> str: """对指定列执行转换操作""" try: wb = load_workbook(file_path) ws = wb.active headers = [cell.value for cell in ws[1]] if column_name not in headers: return f"列 '{column_name}' 不存在" col_idx = headers.index(column_name) + 1 output_idx = headers.index(output_column) + 1 if output_column in headers else len(headers) + 1 if output_column and output_column not in headers: ws.cell(row=1, column=output_idx, value=output_column) params = params or {} for row in range(2, ws.max_row + 1): cell = ws.cell(row=row, column=col_idx) value = cell.value if operation == "to_number": try: new_value = float(value) if value else 0 except (ValueError, TypeError): new_value = params.get("default", 0) elif operation == "to_text": new_value = str(value) if value is not None else "" elif operation == "multiply": factor = params.get("factor", 1) new_value = (value or 0) * factor elif operation == "replace": old = params.get("old", "") new = params.get("new", "") new_value = str(value).replace(old, new) if value else "" elif operation == "extract": import re pattern = params.get("pattern", "") match = re.search(pattern, str(value)) if value else None new_value = match.group(0) if match else "" else: return f"不支持的操作: {operation}" ws.cell(row=row, column=output_idx, value=new_value) wb.save(file_path) wb.close() return f"转换完成: {column_name} -> {operation}" except Exception as e: return f"转换失败: {str(e)}"operation 参数我设计成了字符串枚举,而不是让 AI 直接传 Python 代码。这样做更安全,也更容易控制。AI 只需要说“把这一列转成数字”或者“把这一列里的‘元’字去掉”,我来负责翻译成具体的操作。
params 用字典传额外参数,这样不同操作可以有自己的参数集,扩展起来方便。比如 multiply 需要 factor,replace 需要 old 和 new,extract 需要 pattern。
3.5 合并多个表格:处理列名不一致的情况
这是我最常用的功能,也是最能体现 AI 价值的地方。传统脚本合并表格,要求所有表格列名完全一致,但实际数据里列名经常有差异。有了 AI 参与,它可以先读所有表格的列名,判断哪些列是同一个意思,然后决定怎么对齐。
@server.tool() async def merge_excel_files( file_paths: list, output_path: str, column_mapping: dict = None ) -> str: """合并多个 Excel 文件,支持列名映射""" try: all_data = [] all_headers = set() # 第一遍:收集所有列名 for fp in file_paths: wb = load_workbook(fp, read_only=True, data_only=True) ws = wb.active headers = [cell.value for cell in ws[1] if cell.value] all_headers.update(headers) wb.close() all_headers = sorted(all_headers) # 第二遍:读取数据并按统一列名对齐 for fp in file_paths: wb = load_workbook(fp, read_only=True, data_only=True) ws = wb.active headers = [cell.value for cell in ws[1]] # 应用列名映射 if column_mapping: headers = [column_mapping.get(h, h) for h in headers] for row in ws.iter_rows(min_row=2, values_only=True): row_dict = {} for h, v in zip(headers, row): if h: row_dict[h] = v all_data.append(row_dict) wb.close() # 写入合并结果 wb_out = Workbook() ws_out = wb_out.active ws_out.append(all_headers) for row_dict in all_data: ws_out.append([row_dict.get(h) for h in all_headers]) wb_out.save(output_path) wb_out.close() return f"合并完成: {len(file_paths)} 个文件 -> {output_path},共 {len(all_data)} 行" except Exception as e: return f"合并失败: {str(e)}"column_mapping 参数是关键。AI 可以先调用 read_excel_info 读取所有文件的列名,然后自己判断“销售额”和“销售金额”是同一个意思、“产品名称”和“商品名”是同一个意思,生成一个映射字典传进来。
这个设计把“判断列名是否等价”这个最需要智能的环节交给了 AI,而把“按映射对齐数据”这个纯机械操作留给了代码。各司其职,效率最高。
4. 把 Server 跑起来并接入 AI
4.1 启动脚本怎么写
所有工具定义好之后,需要一个入口来启动 Server。MCP 的 Python SDK 提供了 stdio 和 SSE 两种传输方式,本地开发用 stdio 最简单。
# server.py import asyncio from mcp.server import Server from mcp.server.stdio import stdio_server # 导入所有工具模块,触发装饰器注册 from tools import read_tools, write_tools, transform_tools async def main(): server = Server("excel-mcp") async with stdio_server() as (read_stream, write_stream): await server.run(read_stream, write_stream, server.create_initialization_options()) if __name__ == "__main__": asyncio.run(main())这里有个容易踩的坑:工具模块必须被导入,装饰器才会执行注册。如果你只写了 tools/read_tools.py 但没在 server.py 里 import,那些工具就不会出现在工具列表里。我一开始就犯了这个错误,调试了半天才发现是导入的问题。
4.2 怎么测试工具能不能用
在接入 AI 之前,先单独测试每个工具函数,确保逻辑正确。可以写一个简单的测试脚本:
import asyncio from tools.read_tools import read_excel_info async def test(): result = await read_excel_info("test.xlsx") print(result) asyncio.run(test())准备一个测试用的 Excel 文件,包含各种情况:有中文列名、有英文列名、有数字列、有文本列、有空值。跑一遍看看输出是否符合预期。
实操心得:测试文件一定要包含“脏数据”。我专门做了一个测试文件,里面有合并单元格、有空行、有公式、有日期格式、有超长文本。每次改完代码都拿这个文件跑一遍,能提前发现很多边界问题。
4.3 接入 AI 客户端的配置方法
不同 AI 客户端的配置方式略有差异,但核心都是告诉客户端“去哪里启动这个 MCP Server”。以配置文件为例,通常需要指定命令、参数和环境变量:
{ "mcpServers": { "excel-tools": { "command": "python", "args": ["/path/to/excel-mcp-server/server.py"], "env": { "PYTHONPATH": "/path/to/excel-mcp-server" } } } }配置好之后重启客户端,AI 就能看到你定义的那些工具了。你可以直接对 AI 说“帮我看看 test.xlsx 里有哪些列”,它就会自动调用 read_excel_info 工具。
如果工具没有出现,按下面这个顺序排查:
- 确认 server.py 能独立运行不报错
- 确认所有工具模块都被导入了
- 确认配置文件路径写对了
- 查看客户端日志,看有没有连接错误信息
4.4 一个完整的处理流程演示
假设我有三个销售数据文件,列名分别是:
- 文件A:日期、产品、销售额、数量
- 文件B:日期、商品名、销售金额、数量
- 文件C:日期、产品名称、销售额、销售数量
我想把它们合并成一个表。整个交互过程是这样的:
第一步,我对 AI 说:“帮我看看这三个文件分别有哪些列。”
AI 依次调用 read_excel_info,返回三个文件的列信息。
第二步,AI 自己判断:“产品”、“商品名”、“产品名称”是同一个意思,“销售额”、“销售金额”是同一个意思,“数量”、“销售数量”是同一个意思。然后生成映射:
{ "商品名": "产品", "产品名称": "产品", "销售金额": "销售额", "销售数量": "数量" }第三步,AI 调用 merge_excel_files,传入三个文件路径和映射字典,生成合并后的文件。
整个过程我只需要说一句话,AI 自己完成了信息收集、判断、执行。这就是 MCP 的价值所在——把 AI 的理解能力和代码的执行能力结合起来。
5. 踩过的坑和对应的解决方案
5.1 中文编码问题
Windows 系统下用 openpyxl 读中文 Excel,有时候会遇到编码错误。表现是读出来的中文变成乱码,或者直接抛 UnicodeDecodeError。
根本原因是 Windows 默认编码是 GBK,而 Excel 文件内部用的是 UTF-8。解决方案是在打开文件时显式指定编码,或者用二进制模式读取:
wb = load_workbook(file_path, read_only=True, data_only=True)openpyxl 本身处理编码没问题,问题往往出在文件路径上。如果路径里有中文,在某些终端环境下会出问题。我的做法是统一用绝对路径,并且在代码里做一次路径规范化:
import os file_path = os.path.abspath(os.path.normpath(file_path))5.2 大文件处理超时
有一次处理一个 50MB 的 Excel 文件,read_excel_info 跑了快两分钟才返回,AI 客户端直接超时了。
后来我做了两个优化:一是 read_only 模式必须开,这个前面说过了;二是类型推断的采样行数从 10 行降到 5 行,并且加了缓存机制,同一个文件第二次读取时直接返回缓存结果。
_info_cache = {} async def read_excel_info(file_path: str, sheet_name: str = None) -> str: cache_key = f"{file_path}:{sheet_name}" if cache_key in _info_cache: return _info_cache[cache_key] # ... 读取逻辑 ... _info_cache[cache_key] = result return result缓存的有效期我设的是当前会话,不持久化。因为文件可能在两次调用之间被修改,持久化缓存会导致读到旧数据。
5.3 工具调用参数格式错误
AI 有时候会传一些意料之外的参数格式。比如 columns 参数,我期望的是["列A", "列B"],但 AI 可能传成"列A,列B"或者[["列A"], ["列B"]]。
解决办法是在工具函数入口做参数规范化:
def normalize_columns(columns): if columns is None: return None if isinstance(columns, str): return [c.strip() for c in columns.split(",")] if isinstance(columns, list): result = [] for c in columns: if isinstance(c, list): result.extend(c) else: result.append(c) return result return columns这个函数看起来简单,但能避免很多莫名其妙的报错。永远不要相信 AI 传过来的参数格式完全符合你的预期,做好防御性编程。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 工具列表为空 | 工具模块未导入 | 在 server.py 中 import 所有工具模块 |
| 读取返回乱码 | 路径编码问题 | 使用绝对路径,规范化路径 |
| 大文件超时 | 全量读取 | 开启 read_only,减少采样行数,加缓存 |
| 写入后文件损坏 | 未正确关闭工作簿 | 确保 wb.close() 在 save 之后调用 |
| AI 传参格式错误 | 参数类型不匹配 | 在函数入口做参数规范化 |
| 合并后列顺序混乱 | 未排序 | 对 all_headers 做 sorted 排序 |
| 日期变成数字 | Excel 日期序列号 | 用 openpyxl 的日期格式化处理 |
6. 后续可以怎么扩展
6.1 增加更多实用工具
目前只实现了最基础的读写和转换,实际用下来还有几个工具很值得加:
- 数据校验工具:检查某列是否有空值、重复值、格式异常
- 透视表生成工具:按指定维度做分组汇总
- 图表生成工具:根据数据自动生成柱状图、折线图
- 条件格式工具:对满足条件的单元格设置格式
每加一个工具,AI 能做的事情就多一分。但也不要贪多,工具太多会让 AI 选择困难,反而降低效率。我的经验是保持在 10 到 15 个工具左右比较合适。
6.2 支持更多文件格式
Excel 只是起点。同样的思路可以扩展到 CSV、JSON、甚至数据库。把“读数据”和“写数据”这两个底层操作抽象出来,上层工具就可以复用。
比如加一个 read_csv_info 工具,内部实现和 read_excel_info 几乎一样,只是换了个读取库。AI 不需要知道底层是 Excel 还是 CSV,它只需要知道“读信息”和“读数据”这两个操作。
6.3 加入错误恢复机制
现在的工具遇到错误就直接返回错误信息,AI 收到后需要自己判断怎么处理。可以进一步优化,让工具在遇到可恢复错误时自动重试。
比如读取文件时遇到临时锁定,可以等一秒后重试;写入时遇到权限问题,可以尝试用只读方式打开再另存。这些逻辑封装在工具内部,AI 就不用操心这些细节了。
6.4 性能优化方向
如果经常处理大文件,可以考虑这几个优化:
- 用 pandas 替代 openpyxl 做批量数据处理,速度更快
- 对频繁读取的文件做内存缓存
- 把耗时的操作改成异步执行,避免阻塞
- 支持分块读取,不要一次性加载整个文件
我在实际使用中的体会是,大部分 Excel 处理场景的数据量都在几千行以内,性能不是瓶颈。真正花时间的是 AI 的理解和判断过程,而不是代码执行。所以优化重点应该放在减少 AI 调用次数上,而不是单纯提升代码速度。
最后分享一个小技巧:如果你也在开发 MCP 工具,建议从最简单的“读信息”工具开始,先跑通整个链路,确认 AI 能正常调用,然后再逐步增加复杂工具。我一开始想一口气把所有工具都写完再测试,结果调试的时候根本不知道是哪个环节出了问题。后来改成写一个测一个,效率反而高了很多。