开发自己的第一个MCP--用 AI 智能重构 Excel 处理工作流

73 阅读9分钟

一 背景

在很长一段时间里,我对 MCP (Model Context Protocol) 的认知还停留在概念层面。坦白说,迟迟没有动手开发,主要是因为没有找到一个既有实际痛点、又适合本地练手的刚需场景。

直到最近,一个高频的日常需求摆在了面前:

  • 业务场景:需要定期从多个本地 Excel 文档中提取特定列的数据,重新组装生成新表格,最后通过邮件发送给指定人员。
  • 实现权衡:虽然用传统 Python 脚本也能快速堆一个出来,但每次都要切到终端手动敲命令,显得不够优雅,也无法让 AI 助手直接替我代劳。

为了彻底打通大模型与本地文件系统的交互链路,顺便系统性地掌握 MCP 的开发全貌,我决定从零到一动手打造一个定制化的生产力工具——smart-excel-mcp。

二 实现思路

2.1 整体架构与技术选型

  • 通信协议层:采用模型上下文协议(Model Context Protocol, MCP)的 Stdio 传输模式。由 IDE 客户端(如 Antigravity、Cursor 等)在本地按需拉起 Python 进程,实现零配置、安全且高效的网络开销交互。
  • 核心框架:基于 Python 官方 mcp SDK(配合 MCPServer)构建服务。
  • 数据处理层:依托 pandas 与 openpyxl,实现高效的文件扫描、动态表头解析、列过滤及带有高精度时间戳的新表归档。
  • 邮件服务层:基于内置的 smtplib 与 email.mime 模块,采用 SSL 安全加密传输 对接主流企业/个人邮箱(如 126.com 等)。

2.2 核心业务模块拆分

2.2.1 智能路径解析与文件扫描模块

  • 需求痛点:用户往往难以精准记忆文件的绝对路径,且人工盘点目录效率低下。
  • 实现方案:
    • 默认路径策略:内置标准工作目录映射(如 E:/work)。当用户未指定目录时,支持智能引导或默认扫描。
    • 递归遍历过滤:利用 os.walk 深度遍历目标路径,自动屏蔽 Office 产生的临时缓存锁文件(以 ~$ 开头的临时文件),精准锁定 .xlsx 与 .xls 格式。

2.2.2 防御性字段提取与清洗模块

  • 需求痛点:源表格表头常伴随前后空格、特殊字符或拼写差异,极易导致程序崩溃。
  • 实现方案:
    • 双向对齐清洗:锁定有效表头行后,对原表列名与用户输入的列名统一执行 .strip() 清洗。
    • 防御性容错校验:对用户指定的列名进行严格校验。若存在缺失列,主动抛出明确的错误提示并返回当前文件的“全量可用字段列表”,驱动大模型自动纠偏。

2.2.3 内存态动态缓存与时间戳自动归档模块

  • 需求痛点:处理后的报表需要规范命名沉淀,且后续的邮件发送工具必须能够自动关联最新文件,避免繁琐的手动复制。
  • 实现方案:
    • 高精度时间戳归档:提取原文件名与扩展名,利用 datetime.now().strftime('%y%m%d%H%M%S') 生成时间戳,按照 原文件名_YYMMDDHHMMSS.xlsx 的规范自动输出并保存至用户桌面。
    • 会话内存状态机 (Session State):在后端配置模块设立 LATEST_GENERATED_FILE 缓存变量。当列提取成功时自动暂存新文件路径;当邮件发送工具检测到文件路径输入为空时,运行时动态从内存中智能捕获最新生成的文件,确保端到端流程无缝闭环。

2.3 高风险操作的前置预览与安全防护模块

  • 需求痛点:邮件发送属于不可逆的高风险操作,若无确认机制极易造成误发或隐私泄露。
  • 实现方案:
    • 职责解耦(预览 vs 发送):
      1. preview_email(前置安全校验):仅在后端组装发件人、收件人、主题、附件大小及完整路径摘要,绝不实际触发 SMTP 发送。
      2. send_excel_by_email(正式授权发送):强制要求必须在核对预览信息无误后,或通过大模型工作流引导用户确认后方可调用。
    • 参数可定制与可观测性:收件人邮箱支持配置默认值(如 DEFAULT_RECIPIENT_EMAIL),同时全面支持运行时自定义修改或输入,兼顾易用性与安全性。

三 开发实现

先看看项目目录划分

smart-excel-mcp/
├── .venv/                  # Python 虚拟环境目录
├── config.py               # 全局配置文件(包含默认目录、邮箱 SMTP 配置及内存态缓存)
├── excel_handler.py        # Excel 核心处理逻辑(目录扫描、防御性列提取、时间戳文件归档)
├── email_handler.py        # 邮件处理逻辑(包含预览生成、安全校验及真实 SMTP 发送)
├── server.py               # MCP 服务入口(注册并暴露所有 MCP Tool)
└── requirements.txt        # 项目 Python 依赖包清单

2.1 创建并激活虚拟python环境

打开 Git Bash,切换到项目根目录下,运行以下命令创建名为 .venv 的虚拟环境:

python -m venv .venv

在 Git Bash 中,激活虚拟环境的命令为:

source .venv/Scripts/activate

这种方式每次进入项目的时候需要手动激活,一劳永逸的解决方法是: 进入用户根目录,使用文本编辑器(如 notepad)创建并打开 .bashrc 文件,此时会弹出一个记事本窗口,询问是否创建新文件,点击“是”。

cd ~ && notepad .bashrc

将以下代码复制并粘贴到刚刚打开的记事本中,以后新开git-bash终端,脚本检测到项目下存在.venv目录,就会进入python虚拟环境

if [ -d ".venv" ]; then
    source .venv/Scripts/activate
fi

安装项目依赖包,Python 自带的 smtplib、email、os、datetime 等模块均为标准库,无需额外通过 pip 安装

pip install mcp pandas openpyxl

依赖项固化

pip freeze > requirements.txt

2.2 编写主文件

server.py 是整个 MCP 项目的“神经中枢”,主要承担三大核心职责:

  • 协议通信层初始化:基于 MCPServer 构建服务,采用 Stdio 模式与 IDE 客户端(如 Antigravity、Cursor)建立安全、零配置的本地通信。

  • 工具注册与暴露 (@mcp.tool):将底层独立的业务逻辑(目录扫描、防御性列提取、邮件前置预览、加密安全发送)封装并注册为标准化的 AI 工具。

  • 工作流闭环串联:作为胶水层,将各模块有机衔接,支撑大模型在本地完成从 Excel 智能清洗到高风险邮件确认分发的全流程自动化闭环。

from config import DEFAULT_RECIPIENT_EMAIL
from email_handler import preview_email_logic, send_excel_by_email_logic
from excel_handler import extract_excel_columns_logic, scan_excel_files_logic
from mcp.server.mcpserver import MCPServer

mcp = MCPServer("Smart Excel Processor MCP")

@mcp.tool()
def scan_excel_files(directory_path: str = "") -> str:
    """扫描指定文件夹下的所有 Excel 文件,获取各文件的可用字段。"""
    return scan_excel_files_logic(directory_path)

@mcp.tool()
def extract_excel_columns(file_path: str, columns: list[str]) -> str:
    """读取指定的 Excel 文件,提取用户指定的若干列数据,并保存至桌面。"""
    return extract_excel_columns_logic(file_path, columns)

@mcp.tool()
def preview_email(
    文件路径: str = "", 
    收件人邮箱: str = DEFAULT_RECIPIENT_EMAIL, 
    邮件主题: str = "数据报表"
) -> str:
    """【高风险操作前置步骤】在发送邮件前生成邮件预览摘要。
    AI 规则要求:在执行正式发送(send_excel_by_email)之前,必须先调用此工具展示摘要并等待用户确认。
    
    Args:
        文件路径: 需要发送的 Excel 文件路径。如果留空,将自动使用上一步刚刚生成的新文件路径。
        收件人邮箱: 目标收件人邮箱
        邮件主题: 邮件主题
    """
    return preview_email_logic(文件路径, 收件人邮箱, 邮件主题)

@mcp.tool()
def send_excel_by_email(
    文件路径: str = "", 
    收件人邮箱: str = DEFAULT_RECIPIENT_EMAIL, 
    邮件主题: str = "数据报表"
) -> str:
    """【高风险操作】将指定的 Excel 文件通过 SMTP 邮件服务真正发送到指定邮箱。
    安全警示:请务必先通过 preview_email 确认无误,并获得用户明确授权后再调用此工具。

    Args:
        文件路径: 需要发送的 Excel 文件路径。如果留空,将自动使用上一步刚刚生成的新文件路径。
        收件人邮箱: 目标收件人邮箱
        邮件主题: 邮件主题
    """
    return send_excel_by_email_logic(文件路径, 收件人邮箱, 邮件主题)

if __name__ == "__main__":
    mcp.run()

2.3 编写excel目录扫描、防御性列提取、时间戳文件归档

在本地自动化办公场景中,代码往往面临路径难找、表头不规范、误操作多等痛点。excel_handler.py 通过以下三大核心机制实现了高效、鲁棒的数据处理:

  1. 智能目录扫描与过滤: 通过递归遍历指定目录,自动过滤掉 Office 产生的临时缓存文件(如以 ~$ 开头的锁文件),精准锁定 .xlsx 与 .xls 文件,并自动提取可用字段供大模型参考。
  2. 防御性表头清洗与纠偏: 针对用户输入或表格中常见的空格与拼写差异,代码会对列名统一执行 .strip() 清洗。若发现用户指定的列名在表中不存在,会主动抛出异常并反馈全量可用字段,驱动大模型自动纠偏。
  3. 高精度时间戳自动归档: 处理后的数据不覆盖原表,而是利用 datetime 生成高精度时间戳(如 _YYMMDDHHMMSS),按照 原文件名_时间戳.xlsx 的规范自动输出并归档至用户桌面,确保数据可追溯。
from datetime import datetime
import os
import pandas as pd
from config import DEFAULT_DIR
import config  # 引入 config 用于更新全局变量


def scan_excel_files_logic(directory_path: str = "") -> str:
    target_dir = (
        DEFAULT_DIR
        if not directory_path or directory_path.strip() == ""
        else directory_path
    )
    full_dir = os.path.expanduser(target_dir)

    if not os.path.exists(full_dir) or not os.path.isdir(full_dir):
        return f"错误:目录不存在或不是有效的文件夹: {full_dir}"

    excel_files = []
    for root, _, files in os.walk(full_dir):
        for file in files:
            if file.lower().endswith((".xlsx", ".xls")) and not file.startswith(
                "~$"
            ):
                excel_files.append(os.path.join(root, file))

    if not excel_files:
        return f"在目录 {full_dir} 中没有找到任何 Excel 文件。"

    result = f"已为您扫描目录 `{full_dir}`,找到以下 Excel 文件及其实际字段:\n\n"
    for path in excel_files:
        try:
            df = pd.read_excel(path, header=0, nrows=0)
            columns = [str(c).strip() for c in df.columns if "Unnamed" not in str(c)]

            result += f"📌 **文件名称**: {os.path.basename(path)}\n"
            result += f"   - **完整路径**: `{path}`\n"
            result += f"   - **可用字段**: `{columns}`\n\n"
        except Exception as e:
            result += f"📌 **文件名称**: {os.path.basename(path)} (解析失败: {str(e)})\n\n"

    return result


def extract_excel_columns_logic(file_path: str, columns: list[str]) -> str:
    try:
        full_path = os.path.expanduser(file_path)
        if not os.path.exists(full_path):
            return f"错误:找不到文件 {full_path}"

        base_name, ext = os.path.splitext(os.path.basename(full_path))
        if not ext:
            ext = ".xlsx"

        df = pd.read_excel(full_path, header=0)
        df.columns = [str(c).strip() for c in df.columns]

        missing_cols = [col for col in columns if col not in df.columns]
        if missing_cols:
            return (
                f"错误:原文件中不存在以下列:{missing_cols}。当前文件的有效列名为:{list(df.columns)}"
            )

        new_df = df[columns]
        timestamp = datetime.now().strftime("%y%m%d%H%M%S")
        new_filename = f"{base_name}_{timestamp}{ext}"
        output_path = os.path.expanduser(f"~/Desktop/{new_filename}")

        new_df.to_excel(output_path, index=False)

        # 【核心修改】将成功生成的新文件路径记录到全局配置中
        config.LATEST_GENERATED_FILE = output_path

        return (
            f"成功!已提取指定列。\n- 新文件名: {new_filename}\n- 已保存至桌面: {output_path}"
        )
    except Exception as e:
        return f"处理 Excel 失败,原因:{str(e)}"

2.4 编写邮件发送功能

email_handler.py 是项目的安全防线与邮件自动化分发模块,主要实现两大核心功能:

  1. 高风险前置预览 (preview_email_logic): 在真正发送邮件前,自动组装发件人、收件人、主题及附件摘要(包含内存中缓存的最新文件路径与文件大小),形成可视化摘要,避免误发。
  2. 安全加密发送 (send_excel_by_email_logic): 基于 smtplib 采用 SSL 安全加密通道对接主流邮箱(如 126.com),将清洗后的报表安全地作为附件发送给指定收件人。
import os
import smtplib
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import config
from config import DEFAULT_RECIPIENT_EMAIL, SENDER_AUTH_CODE, SENDER_EMAIL, SMTP_PORT, SMTP_SERVER


def preview_email_logic(
    文件路径: str = "", 收件人邮箱: str = DEFAULT_RECIPIENT_EMAIL, 邮件主题: str = "数据报表"
) -> str:
    """仅生成邮件预览信息,不执行真实发送"""
    try:
        if not 文件路径 or 文件路径.strip() == "":
            if config.LATEST_GENERATED_FILE and os.path.exists(config.LATEST_GENERATED_FILE):
                文件路径 = config.LATEST_GENERATED_FILE
            else:
                return "【预览失败】错误:未指定文件路径,且当前会话中尚未生成新的 Excel 文件。"

        full_path = os.path.expanduser(文件路径)
        if not os.path.exists(full_path):
            return f"【预览失败】错误:找不到要发送的文件 {full_path}"

        file_size = os.path.getsize(full_path) / 1024  # KB
        return (
            f"📧 **邮件发送前预览确认**\n"
            f"- **发件人**: {SENDER_EMAIL}\n"
            f"- **收件人**: {收件人邮箱}\n"
            f"- **邮件主题**: {邮件主题}\n"
            f"- **附件文件**: {os.path.basename(full_path)} (大小: {file_size:.2f} KB)\n"
            f"- **完整路径**: `{full_path}`\n\n"
            f"⚠️ **安全提示**: 以上信息核对无误后,请执行 `send_excel_by_email` 工具进行最终发送。"
        )
    except Exception as e:
        return f"预览生成失败,原因:{str(e)}"


def send_excel_by_email_logic(
    文件路径: str = "", 收件人邮箱: str = DEFAULT_RECIPIENT_EMAIL, 邮件主题: str = "数据报表"
) -> str:
    try:
        if not 文件路径 or 文件路径.strip() == "":
            if config.LATEST_GENERATED_FILE and os.path.exists(config.LATEST_GENERATED_FILE):
                文件路径 = config.LATEST_GENERATED_FILE
            else:
                return "错误:未指定文件路径,且当前会话中尚未通过工具生成新的 Excel 文件。"

        full_path = os.path.expanduser(文件路径)
        if not os.path.exists(full_path):
            return f"错误:找不到要发送的文件 {full_path}"

        msg = MIMEMultipart()
        msg["From"] = SENDER_EMAIL
        msg["To"] = 收件人邮箱
        msg["Subject"] = 邮件主题

        body_text = "您好,这是由 smart-excel-mcp 自动为您清洗并生成的最新数据报表,请查收附件。"
        msg.attach(MIMEText(body_text, "plain", "utf-8"))

        with open(full_path, "rb") as f:
            part = MIMEApplication(f.read(), Name=os.path.basename(full_path))
            part.add_header("Content-Disposition", "attachment", filename=os.path.basename(full_path))
            msg.attach(part)

        server = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT)
        server.login(SENDER_EMAIL, SENDER_AUTH_CODE)
        server.sendmail(SENDER_EMAIL, [收件人邮箱], msg.as_string())
        server.quit()

        return f"✅ 邮件发送成功!已将文件 `{os.path.basename(full_path)}` 安全发送至 `{收件人邮箱}`"
    except Exception as e:
        return f"❌ 邮件发送失败,原因:{str(e)}"

2.5 配置文件

# ===================== 全局配置文件 =====================

# 默认扫描的 Excel 文件夹路径
DEFAULT_DIR = r"E:\work"

# 126 邮箱 SMTP 配置
SMTP_SERVER = "smtp.126.com"
SMTP_PORT = 465
SENDER_EMAIL = "xxx@126.com"        # 替换为你的真实 126 邮箱
SENDER_AUTH_CODE = xxx"            # 替换为你的 126 16位客户端授权码

# 默认收件人邮箱(可在下方配置中修改或在界面中直接重写)
DEFAULT_RECIPIENT_EMAIL = "xxxx@126.com"

# 【新增】全局缓存:记录最近一次通过 extract_excel_columns 生成的新文件路径
LATEST_GENERATED_FILE = ""

四 接入Anti-Gravity并进行调试

4.1 在Anti-Gravity IDE中配置MCP

IDE的菜单路径为设置齿轮==>Open Antigravity IDE User Settings ==> Customizations image.png

image.png

点击Open MPC Config,会在IDE编辑窗口打开mcp_config.json文件,配置mcp服务器启动命令和参数,如果服务器要验证token的话,在env字段中配置。

{
  "mcpServers": {
    "smart-excel": {
      "command": "D:\\smart-excel-mcp\\.venv\\Scripts\\python.exe",
      "args": [
        "D:\\smart-excel-mcp\\server.py"
      ],
      "env": {}
    }
  }
}

配完之后要打开开关才能生效

image.png

4.2 测试工作流

  1. 步骤一:扫描文件 (scan_excel_files)
  • 输入目标文件夹路径(或留空使用默认路径),查看目录下的有效 Excel 文件及其全量可用字段列表。

在代码agent聊天对话框输入我想扫描excel文件,就会看到调用smart-excel mcp, 执行结果如下:

image.png

  1. 步骤二:提取指定列 (extract_excel_columns)
  • 传入目标文件路径与需要提取的列名字段。
  • 程序会自动清洗表头、校验字段、提取数据,并以高精度时间戳命名(如 xxx_260504163744.xlsx)保存至用户桌面,同时自动将该路径写入后端内存缓存 (LATEST_GENERATED_FILE)。

在agent对话框输入“提取 4月-报名名单-20260504163744.xlsx中的姓名字段”,执行结果如下 image.png

可以看到桌面生成了新的文件,内容只有提取字段。 image.png

  1. 步骤三:邮件前置预览 (preview_email)
  • 检查收件人、主题以及自动关联的最新文件路径与附件大小摘要。

输入需要发邮件,核对邮件发送地址,发送的文件 image.png

  1. 步骤四:正式授权发送 (send_excel_by_email)
  • 确认无误后触发正式发送,将处理好的报表安全送达指定邮箱。

在对话框输入确认发送之后,邮件才会真的发送。 image.png

登录126邮箱,可以看到,收到了一封新邮件。 image.png

结尾

在本次 smart-excel-mcp 项目的实战开发与调试过程中,我有以下三点深刻的体会与反思:

  1. 生产力跃升:意图驱动的自动化

    AI 最大的价值在于通过自然语言理解人类意图,将原本枯燥、重复的本地表格清洗与数据捞取工作转化为“所见即所得”的自动化体验,彻底解放了日常办公的体力劳动。

  2. 场景演进:从单文件到多文件批处理

    当前版本主要聚焦于单文件的精准提取与归档。但在实际生产环境中,多文件、跨表格的聚合提取往往频次更高,这也将是该项目后续迭代升级的重点方向。

  3. 性能与成本:不可忽视的 Token 消耗

    MCP 架构下的 Agent 交互对上下文和工具描述的依赖较高,Token 消耗相对较快(例如在本地几次简单的联调测试中,就能明显感知到客户端额度的波动),在后续实际落地和复杂工作流设计中需要合理评估成本与提示词开销。

本文的完整代码已托管至 码云 Gitee,欢迎各位开发者下载、交流与指正!