Skip to main content
Glama
README.md
# Hermes WPS MCP

[![build-wps-mcp](https://github.com/DSMFRP1024/hermes-wps-mcp/actions/workflows/build.yml/badge.svg)](https://github.com/DSMFRP1024/hermes-wps-mcp/actions/workflows/build.yml)

上游 [`lc2panda/wps-skills`](https://github.com/lc2panda/wps-skills) 的**离线部署适配版**,
用于内网 aarch64 Linux(银河麒麟 V10 / 统信 UOS 20 / openEuler)上,
让 Hermes Agent 通过 MCP 直接操作正在运行的 WPS 文档。

- 上游 pin:`a82533662268b3245f93d8685bc45dffede048b6`
- 许可:MIT(与上游一致)
- **不是重写**,是 clone + 环境适配。业务逻辑(243 个工具的实现)一行没动。
- 上游原始说明保留在 [`README.upstream.md`](README.upstream.md)

关于「为什么不直接在 GitHub 上 fork」:本仓库是**独立仓库**,但保留了完整上游历史
(首个提交的父提交就是上游 pin),并且配了 `upstream` remote。这样上游后续修复仍能合并:

```bash
git remote add upstream https://github.com/lc2panda/wps-skills.git   # 已配置则跳过
git fetch upstream main
git merge upstream/main        # 或 git diff upstream/main 只看我们改了什么
```

GitHub 上的「Forked from」标识是没有的 —— 这是一个内网适配发布仓库,
需要打自己的 release 产物、写自己的 README,独立更清晰。

## 能力

243 个工具:231 个专业工具 + 12 个内置工具,覆盖 Excel 82 / Word 28 / PPT 112 / 通用 21。

能做到「实时」是因为加载项跑在 WPS 自己的 V8 引擎里,直接操作文档对象模型:
**改动立刻上屏、格式自动继承、不与用户的 Ctrl+S 冲突**。
MCP Server 不碰文件,只把命令排进队列,等加载项轮询取走。

```
Hermes Agent
   │  MCP (stdio)
   ▼
wps-office-mcp  (node dist/index.js)
   │  HTTP 轮询,仅 127.0.0.1:58891
   ▼
WPS 加载项「WPS助手」(WPS 内嵌 V8)
   │  WPS JS API
   ▼
你正在编辑的文档(内存对象)
```

## 三端

| 平台 | 通道 | 本仓库状态 |
|---|---|---|
| **Linux** | 反向轮询(与 macOS 共用一套) | **适配目标,CI 覆盖** |
| macOS | 反向轮询 | 上游支持,未验证 |
| Windows | PowerShell + COM | 上游支持,未验证 |

## 相对上游改了什么

只有下面这些,逐条列出便于对照回滚。

| # | 文件 | 改动 | 原因 |
|---|---|---|---|
| 1 | `wps-office-mcp/src/client/mac-poll-server.ts` | `switchApp()` 开头加 Linux 分支直接返回 | 原实现调 `wps-auto.sh`,其 `start_app()` 用 macOS 的 `open` 命令(Linux 没有),pptx 分支还依赖 `python-pptx`。内网场景 Hermes 常驻、WPS 用户自己开,无需拉起/杀进程 |
| 2 | `install.sh` | **新增** | 上游 `scripts/install.sh` 硬编码了特定 AI 宿主的 MCP 注册命令与 skills 软链目录,还带联网检查 |
| 3 | `build/test_poll_protocol.mjs` | **新增** | 上游**零测试**:`jest.config.js` 指向的 `src/tests/setup.ts` 根本不存在,`npm test` 必然失败 |
| 4 | `build/verify_dist.mjs` | **新增** | 拦「编译绿了但产物不可用」 |
| 5 | `build/package_offline.sh` | **新增** | 内网无 npm,必须自带 `node_modules` |
| 6 | `build/verify_package.py` | **新增** | CI 与本地同一份校验规则 |
| 7 | `.github/workflows/build.yml` | **新增** | 目标机是 aarch64,x86 上跑绿没意义 |
| 8 | `README.md` / `SKILL.md` | **替换** | 说明来源、改动、部署 |
| 9 | `build/test_install.py` | **新增** | `install.sh` 的语义测试:合并、幂等、卸载不误伤他人配置 |
| 10 | `build/test_verify_package.py` | **新增** | 校验器自身的回归测试(符号链接判定),不依赖宿主符号链接权限 |
| 11 | `wps-assistant/**`、`install.sh`、`wps-addon/**` | **改名 + 去品牌** | 界面/文档不再出现第三方品牌:功能区显示「WPS助手」、注册 ID `wps-assistant`、源码目录 `wps-assistant/`。`install.sh` 会顺手清掉旧 ID 的注册项与目录(防升级后出现两个页签) |

**完全没动**:`tools/**`、`server/**`、`client/wps-client.ts`。
加载项本体除 `manifest.xml` / `ribbon.xml` 的显示名与 id、`main.js` 里 3 处
`console.log` 文案外,业务实现零改动。

> 界面显示名是我方自定义的,与上游品牌无关;上游原文说明存档在
> `README.upstream.md`,那份文件**刻意未做替换**,以保留可核对的原始出处。


## 产出离线包

联网机或 CI:

```bash
cd wps-office-mcp && npm ci && npx tsc -p tsconfig.json && cd ..
node build/verify_dist.mjs
node build/test_poll_protocol.mjs
bash build/package_offline.sh
# -> dist-pkg/wps-mcp-offline-aarch64.tar.gz + .sha256
```

或直接取 CI artifact(workflow `build-wps-mcp`,runner `ubuntu-24.04-arm`):

```
wps-mcp-offline-aarch64.tar.gz    约 4.2 MiB / 约 5000 条目
wps-mcp-offline-aarch64.tar.gz.sha256
```

**字节数与 sha256 每次构建都会变**,所以这里不写死:README 本身也打进了产物,
写死哈希会立刻变成自指的过期值。校验一律以 artifact 里那个 `.sha256` 为准:

```bash
sha256sum -c wps-mcp-offline-aarch64.tar.gz.sha256
```

CI 会在两个 node 版本(22 / 24)上各跑一遍,只有 node 22 那路上传产物。
打包后立刻用 `build/verify_package.py` 校验(28 项),本地下载回来再跑**同一份规则**。

## 内网安装

```bash
tar -xzf wps-mcp-offline-aarch64.tar.gz -C wps-mcp
cd wps-mcp
bash install.sh --dry-run     # 先看它要做什么
bash install.sh
```

`install.sh` 依次做:探测 node/WPS → 校验包完整性 → 部署服务与加载项 →
**合并**(非覆盖)`publish.xml`(改前备份)→ 重启 WPS → 打印 MCP 配置片段。

### 加载项要不要手动启用?

**不需要。** 注册进 `publish.xml` 之后,每次 WPS 启动都会自动扫描并加载,
功能区的 `onLoad="OnAddinLoad"` 自动触发,轮询随即开始,用户不用点任何按钮。

只有一个**一次性**动作:**把所有 WPS 进程退干净再打开**(托盘/后台残留也算)。
因为 `publish.xml` 只在进程启动时读一次。`install.sh` 会自动执行
`quickstartoffice restart` 代替你完成;探测不到该脚本时才需要手动重启。

`publish.xml` 里的 `enable="enable_dev"` 是让它默认启用、并跳过 md5 校验,
省掉在「加载项管理器」里手工勾选这一步。

功能区没出现「WPS助手」时,按顺序查:

1. **装到了 root 名下** —— `sudo bash install.sh` 会把加载项写进
   `/root/.local/share/Kingsoft/wps/jsaddons/`,你用普通用户开 WPS 自然看不到。
   `install.sh` 现在会对这种情况显式告警。用 `--jaddons` 指到正确用户目录。
2. **没真正重启** —— `ps -ef | grep -i wps` 还有残留就得全杀掉再开。
3. **没有图形会话** —— ssh 无 X11/Wayland 转发时加载项不会加载,无报错。
4. 以上都对仍不出现,看 `~/.local/share/Kingsoft/wps/jsaddons/publish.xml`
   里有没有 `name="wps-assistant"`,以及目录是否叫 `wps-assistant_`(尾部下划线)。

### 在 Hermes 注册 MCP

```json
{
  "wps-office": {
    "command": "/path/to/node",
    "args": ["/home/<user>/.local/share/hermes-wps-mcp/wps-office-mcp/dist/index.js"],
    "env": { "WPS_MCP_PORT": "58891" }
  }
}
```

## 启动顺序(重要)

**先起 Hermes(含 MCP),再开 WPS。**

反之在麒麟上有已知问题:WPS 已运行后再起 MCP Server,WPS 进程可能异常退出
(上游 Issue #17)。Hermes 常驻这一点天然满足正确顺序。

## 验证

```bash
# 加载项是否注册成功
cat ~/.local/share/Kingsoft/wps/jsaddons/publish.xml
ls  ~/.local/share/Kingsoft/wps/jsaddons/wps-assistant_/

# 服务是否在跑(需 Hermes 已拉起 MCP)
curl -s http://127.0.0.1:58891/status

# 端到端:重启 WPS,功能区应出现「WPS助手」
#         然后在 Hermes 里问「看看当前表格有哪些工作表」
```

日志:`~/.wps-office-mcp/logs/combined.log` / `error.log`。

## 已知限制

- **必须有图形会话**。加载项的宿主是运行中的 WPS 进程;无头服务器上整条链不成立。
- **只能操作活动文档**。WPS JS API 是 `ActiveWorkbook` / `ActiveDocument` 语义。
- **同机**。加载项与 MCP Server 必须同机,走 `127.0.0.1`。
- **加载项注册机制脆弱**。目录名尾部 `_`、`publish.xml` 的 `url` 前缀、
  `oem.ini` 的 `JsApiPlugin`,任一不符就**静默不加载**,零报错。
- **ARM64 的 WPS 无公开下载**。需从统信/麒麟 OEM 渠道或已预装整机获取,
  注意 OEM 授权与机器绑定。
- **测试覆盖有限**。补的协议测试只覆盖轮询链路,不覆盖 243 个工具的语义。

## 卸载

```bash
bash install.sh --uninstall
```

**什么都不删,全部改名留备份**:

- 安装目录 → `~/.local/share/hermes-wps-mcp.bak.<时间戳>`
- 加载项目录 → `jsaddons/wps-assistant_.bak.<时间戳>`
- `publish.xml` 改前先 `cp` 一份 `.bak.<时间戳>`

卸载只做两件事:从 `publish.xml` 摘掉 `name="wps-assistant"` 那一条(**别人的条目一条不碰**),
以及把上面三个位置改名。想回滚:把两个 `.bak` 改回原名即可。

> 不用 `rm -rf` 是有原因的:部分环境把 `rm` 劫持成回收站 shim,
> 会阻塞或直接失败;而且删了之后回滚只能重装。

## 本地快速跑测试

Windows / 无 `node_modules` 的环境也能跑安装语义测试:

```bash
WPS_TEST_STUB=1 python3 build/test_install.py     # 桩包,秒级
python3 build/test_verify_package.py              # 校验器回归测试
```

CI 不设 `WPS_TEST_STUB`,始终用真包 —— 防止「桩包绿了真包红」。