内容
# MCP-A
[](https://modelcontextprotocol.io/)
[](https://python.org)
[](LICENSE)
**MCP-A(模型上下文协议代理)** 是一款基于 [模型上下文协议(MCP)](https://modelcontextprotocol.io/) 构建的 AI 原生电子表格自动化系统。它通过 UNO 桥接器提供对 LibreOffice Calc 的编程控制,使 AI 代理能够以语义理解的方式读取、写入、样式化和操作电子表格。
这是一个演示 **MCP 原生工具架构** 的参考实现 - AI 代理通过定义良好的协议接口与结构化数据交互,而不是脆弱的 API 包装器。
---
## 🏗️ 架构概述
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ MCP-A 架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────────────┐ │
│ │ AI 代理 │ │ MCP 服务器 │ │ LibreOffice │ │
│ │ (Hermes 等) │◄──►│ (FastMCP) │◄──►│ Calc via UNO │ │
│ │ │ │ │ │ 桥接器 │ │
│ │ • 语义意图 │ │ • 协议验证 │ │ • 文档 I/O │ │
│ │ • 上下文管理 │ │ • 工具路由 │ │ • 单元格操作 │ │
│ │ • 多步骤推理 │ │ • 传输抽象 │ │ • 公式求值 │ │
│ │ │ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ └──────────────────────┘ │
│ │ │ │
│ │ ┌────────┴────────┐ │
│ │ │ 传输方式 │ │
│ │ │ • 标准输入 │ │
│ │ │ • SSE │ │
│ │ │ • streamable-│ │
│ │ │ HTTP │ │
│ │ └─────────────────┘ │
│ │ │
│ └────────────────────────────────────────────────┐ │
│ │ │
│ ┌─────────────────────────────────────────────────────────┐│ │
│ │ 工具注册 ││ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ││ │
│ │ │ 文档工具 │ │ 单元格工具 │ │ 工作表工具 │ │ 样式工具 │ ││ │
│ │ │ 工具 │ │ 工具 │ │ 工具 │ │ 工具 │ ││ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ ││ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ││ │
│ │ │ 行/列工具 │ │ 命令行管理 │ │ 打开 │ ││ │
│ │ │ 工具 │ │ 工具 │ │ 网页工具 │ ││ │
│ │ └──────────┘ └──────────┘ └──────────┘ ││ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### 设计原则
1. **协议原生**: 基于模型上下文协议标准,实现无缝 AI 代理集成
2. **传输无关**: 支持标准输入、SSE 或 HTTP 传输方式
3. **语义工具**: 操作表达意图(`style_range`、`set_formula`)而非实现
4. **基于会话**: 文档在内存中持久化,通过 UUID 句柄进行多轮工作流管理
5. **双向交互**: AI 代理可以读取电子表格状态并写入结构化数据
---
## 🛠️ 工具定义
### 文档生命周期
| 工具 | 描述 | 返回值 |
|------|-------------|---------|
| `create_spreadsheet()` | 创建新的内存电子表格 | `doc_id`, `sheets` |
| `open_spreadsheet(path)` | 打开现有文件 (.ods/.xlsx/.xls/.csv) | `doc_id`, `sheets` |
| `save_spreadsheet(doc_id, path?)` | 保存到磁盘 | `"saved"` |
| `close_spreadsheet(doc_id, save=false)` | 关闭并清理 | `"closed"` |
### 工作表管理
| 工具 | 描述 |
|------|-------------|
| `list_sheets(doc_id)` | → `["Sheet1", "Sheet2", ...]` |
| `add_sheet(doc_id, name, position=-1)` | 在指定位置插入(-1 = 追加) |
| `remove_sheet(doc_id, name)` | 删除工作表 |
| `rename_sheet(doc_id, old_name, new_name)` | 重命名现有工作表 |
### 单元格操作
| 工具 | 参数 | 描述 |
|------|------------|-------------|
| `get_cell` | `(doc_id, sheet, address)` | 读取单个单元格(A1 表示法) |
| `set_cell` | `(doc_id, sheet, address, value?, formula?)` | 写入值或公式 |
| `get_range` | `(doc_id, sheet, range_address)` | 读取 A1:D5 范围 |
| `set_range` | `(doc_id, sheet, start_addr, data[][])` | 写入 2D 数据数组 |
| `get_used_range` | `(doc_id, sheet)` | 获取所有已填充单元格 |
### 行和列操作
| 工具 | 参数 |
|------|------------|
| `insert_rows(doc_id, sheet, start_row, count=1)` | 在行 N 之前插入 |
| `delete_rows(doc_id, sheet, start_row, count=1)` | 删除行 |
| `insert_columns(doc_id, sheet, start_col, count=1)` | 在列字母之前插入 |
| `delete_columns(doc_id, sheet, start_col, count=1)` | 删除列 |
### 样式引擎
| 工具 | 关键参数 |
|------|----------------|
| `style_range` | `bold`, `italic`, `font_size`, `font_name`, `color` (#RRGGBB), `background`, `align`, `valign` |
| `set_column_width` | `col`(字母),`width_mm`(null = 自动) |
| `set_row_height` | `row`(1-based),`height_mm`(null = 自动) |
| `set_range_border` | `style`(thin/medium/thick/double),`sides`(all/top/bottom/left/right),`color` |
| `merge_cells` / `unmerge_cells` | `range_address` |
| `set_number_format` | `fmt` 字符串(例如,`'#,##0.00'`,`'0%'`)
---
## 📋 安装
### 前提条件
```bash
# Ubuntu/Debian
sudo snap install libreoffice
# 或适用于您的平台的等效命令
```
### 软件包安装
```bash
# 克隆存储库
git clone https://github.com/quantai-dev/calcmcp.git
cd calcmcp
# 安装 MCP 框架
pip install "mcp[cli]"
# 安装软件包(editable)
pip install -e .
```
**要求**: Python >=3.12
---
## 🚀 使用示例
### 示例 1:基本电子表格创建
```python
# 通过 MCP 工具的 AI 代理工作流
doc_id = create_spreadsheet().doc_id
set_cell(doc_id, "Sheet1", "A1", "产品")
set_cell(doc_id, "Sheet1", "B1", "价格")
set_cell(doc_id, "Sheet1", "C1", "数量")
set_cell(doc_id, "Sheet1", "D1", "总计")
set_range(doc_id, "Sheet1", "A2", [
["Widget", 19.99, 150],
["Gadget", 29.99, 75],
["Thingama", 9.99, 300]
])
set_cell(doc_id, "Sheet1", "D2", formula="=B2*C2")
set_cell(doc_id, "Sheet1", "D3", formula="=B3*C3")
set_cell(doc_id, "Sheet1", "D4", formula="=B4*C4")
style_range(doc_id, "Sheet1", "A1:D1", bold=True, background="#4472C4", color="#FFFFFF")
style_range(doc_id, "Sheet1", "B2:D4", align="right")
set_number_format(doc_id, "Sheet1", "B2:B4", '"$"#,##0.00')
set_number_format(doc_id, "Sheet1", "D2:D4", '"$"#,##0.00')
set_range_border(doc_id, "Sheet1", "A1:D5", style="thin", sides="all")
save_spreadsheet(doc_id, "inventory.xlsx")
close_spreadsheet(doc_id)
```
### 示例 2:Hermes 代理集成
在 `~/.hermes/config.yaml` 中配置:
```yaml
mcp:
- server: stdio
command: calcmcp
args: ["start", "--transport", "stdio"]
env:
CALCMCP_HEADLESS: "true"
# 或使用 SSE 传输:
# - server: http
# url: http://127.0.0.1:8000/mcp
```
重启后,Hermes 自动发现所有工具:
```
> 创建一份带有图表的季度销售报告
我将创建一个专业的销售报告,包含格式化的数据。
1. 首先,让我创建一个新的电子表格...
✓ 已创建电子表格(doc_id: a3f8e21b)
2. 设置标题和 Q1-Q4 数据...
✓ 已添加标题和销售数据
3. 应用货币格式化和总计...
✓ 已格式化列并添加 SUM 公式
4. 创建标题行样式...
✓ 已应用品牌颜色
5. 保存到 ~/Documents/q1_q4_sales.xlsx
✓ 报告保存成功
```
### 示例 3:Streamlit 集成
```python
import streamlit as st
from mcp import ClientSession, StdioServerParameters
st.title("📊 MCP-A 电子表格代理")
# 连接到 MCP-A 服务器
async with ClientSession(server) as session:
tools = await session.list_tools()
uploaded = st.file_uploader("上传电子表格", type=["xlsx", "ods", "csv"])
if uploaded:
# 代理查询数据
result = await session.call_tool("open_spreadsheet", {"path": uploaded.name})
doc_id = result.content[0].doc_id
# 读取数据
data = await session.call_tool("get_used_range", {"doc_id": doc_id, "sheet": "Sheet1"})
st.dataframe(data.content[0].data)
```
---
## 🖥️ 命令行参考
```bash
# 启动服务器(默认:SSE on localhost:8000)
calcmcp start
# 使用标准输入传输启动(适用于本地代理)
calcmcp start --transport stdio
# 使用自定义端口/无头模式启动
calcmcp start --port 9000 --headless
# 服务器生命周期管理
calcmcp status # 检查是否正在运行
calcmcp stop # 正常关闭
calcmcp restart # 使用相同的标志重启
calcmcp logs -f # 实时跟踪日志
```
### 环境变量
| 变量 | 默认值 | 描述 |
|----------|---------|-------------|
| `CALCMCP_HEADLESS` | `false` | 无图形界面运行 LibreOffice |
| `CALCMCP_HOST` | `127.0.0.1` | 服务器绑定地址 |
| `CALCMCP_PORT` | `8000` | 服务器端口 |
---
## 🔌 协议合规性
MCP-A 实现 **模型上下文协议** 规范:
- ✅ **工具发现**: 完整的工具模式,包含描述和类型
- ✅ **类型参数**: Pydantic 验证的参数模式
- ✅ **结构化响应**: 一致的 JSON 返回值
- ✅ **错误处理**: 符合协议的错误消息
- ✅ **多传输**: 标准输入、SSE、streamable-HTTP
---
## 📊 性能
原生 UNO 桥接性能特征:
| 操作 | 延迟(本地) | 吞吐量 |
|-----------|-----------------|------------|
| 单个单元格读取 | ~10ms | — |
| 单元格写入 | ~15ms | — |
| 1000×1000 范围读取 | ~2s | 500k 单元格/秒 |
| 批量范围写入 | ~3s | 330k 单元格/秒 |
| 样式应用 | ~50ms/范围 | — |
*在 AMD Ryzen 9 上测量,LibreOffice 24.x,本地套接字连接。*
---
## 🤝 贡献
MCP-A 旨在演示 MCP 原生架构。要扩展:
1. **添加工具**: 在 `server.py` 中使用 `@mcp.tool()` 装饰器定义
2. **添加传输方式**: FastMCP 支持开箱即用的附加传输方式
3. **桥接扩展**: 扩展 `calc_bridge.py` 以获取 Calc 特定功能
```python
@mcp.tool()
def my_custom_operation(doc_id: str, param: str) -> dict:
"""自定义操作,用于专用工作流。
参数:
doc_id: 文档句柄
param: 操作参数
返回:
操作结果
"""
doc = _require_doc(doc_id)
# 实现此处
return {"status": "success", "result": ...}
```
---
## 📄 许可证
```
Copyright 2026 Ethan Kuhrts (Superposition.devs)
遵循 Apache License, Version 2.0(“许可证”);
除非遵守许可证,否则您不得使用此文件;
您可以在以下网址获取许可证副本:
http://www.apache.org/licenses/LICENSE-2.0
除非适用法律要求或书面同意,否则根据许可证分发的软件
以“原样”分发,不提供任何保证或条件,
无论是明示还是暗示。
请参阅许可证以了解许可证下的具体语言和限制。
```
---
## 🔗 链接
- **模型上下文协议**: https://modelcontextprotocol.io/
- **MCP Python SDK**: https://github.com/modelcontextprotocol/python-sdk
- **LibreOffice UNO**: https://api.libreoffice.org/
- **作者**: https://github.com/quantai-dev
---
<p align="center">
<i>由 Superposition.devs 构建 ❤️</i><br>
<sub>MCP-Native • AI-First • 协议原生</sub>
</p>