Initial commit: book-mcp 教辅处理 MCP server + Arco Vue 管理端

后端 (book_mcp_server.py, 335 行, 15 个 MCP 工具):
- create_book / add_document / mark_html_ready / mark_json_ready / mark_failed
- update_book_status / soft_delete_book / retry_failed
- get_book / list_books / get_document / list_documents / get_document_url
- upload_to_rustfs / health
- 走 JSON-RPC + stateless_http + json_response(前端可直接调)
- launchd 守护 / 6 秒拉起(macOS)
- systemd 守护脚本(Linux/codelab)

存储:
- MongoDB (mongo:8.0 容器) - book/book_document/document_file 三表
- RustFS 对象存储(fs.code-lab.cn/scan/)- book/<id>/docs/<doc_id>/{docx,html,json}
- 幂等设计: zip_sha256 唯一 + (book_id+original_path) 唯一

前端 (Vue 3 + TS + Arco Design):
- 列表页 /book/list - 搜索 + 状态过滤 + 进度
- 详情页 /book/:id - 文档折叠 + 文件下载
- MCP 客户端 src/api/mcp.ts (JSON-RPC over fetch)

部署:
- deploy/install.sh (codelab 一键装 backend + systemd)
- deploy/install-frontend.sh (vite dev / 生产构建)
- docs/deploy.md (运维手册)

测试:
- tests/test_basic.py (sanity 7 项)
- tests/test_e2e.py (完整 e2e: docx→HTML→入库→上传→校验)
This commit is contained in:
2026-10-11 18:03:45 +08:00
parent f7fb571bce
commit 9b0910ccb1
160 changed files with 23628 additions and 3 deletions

373
docs/book-mcp-design.md Normal file
View File

@@ -0,0 +1,373 @@
# 教辅处理 MCP 服务 — 方案设计
> **作者**: 助手(Quentin 的 AI 助手)
> **日期**: 2026-10-11
> **状态**: 待评审
---
## 1. 业务目标
> Agent 上传一个教辅 zip → MCP 自动解压 + 批量转 HTML + 上传对象存储 + 入库 → 后续页面可查、可看 HTML。
### 核心工作流
```
┌─ Agent 端 ───────────────────────────────────────────┐
│ │
│ 1) Agent: upload_zip(local_zip) → rustfs 返回 key │
│ 2) Agent: mcp.create_book(name, zip_path=key) │
│ ↓ │
│ 返回 {book_id, status="pending"} │
│ │
│ 3) Agent: 本地解压 zip │
│ 4) Agent: 循环每 docx │
│ ├─ mcp.add_document(book_id, filename, path) │
│ │ ↓ 返回 doc_id │
│ ├─ wordparse.docx_to_html(docx) → 本地 html │
│ ├─ mcp.upload_to_rustfs(html, book/doc_id/html) │
│ ├─ Agent 本地解析 html → structure.json │
│ ├─ mcp.upload_to_rustfs(json, book/doc_id/json) │
│ ├─ mcp.mark_html_ready(doc_id, html_url, stats) │
│ └─ mcp.mark_json_ready(doc_id, json_url, ver) │
│ │
│ 5) Agent: mcp.update_book_status(book_id, "done") │
│ │
└─ 后续页面 ───────────────────────────────────────────┘
│ - HTTP API 查 book / doc / file(复用 mcp 的查询类) │
│ - 列表页:分页查 book + 每本完成进度条 │
│ - 详情页:doc 列表 + 跳 html / json 在线预览 │
└──────────────────────────────────────────────────────┘
```
---
## 2. 数据模型
### 2.1 ER 关系
```
┌────────────┐ 1 N ┌────────────────┐ 1 N ┌──────────────┐
│ book │─────────│ book_document │─────────│ document_file│
│ 教辅 │ │ 文档 │ │ 文件(多形态) │
└────────────┘ └────────────────┘ └──────────────┘
```
### 2.2 实体字段
#### `book` 教辅(zip / 套书)
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | BIGINT PK | 自增 |
| `name` | VARCHAR(200) | 教辅名("2027版.曲一线3•2物理.广西版.一轮复习-突破训练")|
| `subject` | VARCHAR(50) | 学科(物理/数学/语文/...)|
| `grade` | VARCHAR(50) | 年级(高三/高一/...)|
| `version` | VARCHAR(50) | 版本(人教版/广西版/2027版/...)|
| `type` | VARCHAR(50) | 类型(突破训练/同步训练/真题/...)|
| `zip_path` | VARCHAR(500) | 原始 zip 在对象存储的路径(如 `book/2027-物理-突破训练.zip`)|
| `zip_sha256` | VARCHAR(64) UNIQUE | 原始 zip 哈希(幂等去重)|
| `zip_size` | BIGINT | 原始 zip 字节数 |
| `status` | ENUM | `pending` / `processing` / `done` / `failed` / `partial` |
| `total_count` | INT | zip 内 docx 总数 |
| `success_count` | INT | 成功转 HTML 数 |
| `failed_count` | INT | 失败数 |
| `error_msg` | TEXT | 失败原因 |
| `created_at` | DATETIME | 创建时间 |
| `updated_at` | DATETIME | 处理完成时间 |
| `created_by` | VARCHAR(100) | 哪个 Agent / 哪个用户传 |
| `is_deleted` | TINYINT | 软删标记(0/1)|
**唯一索引**: `uk_name_version (name, version, is_deleted)` 防同名教辅重复创建
#### `book_document` 文档(zip 里的每个 docx 一条)
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | BIGINT PK | 自增 |
| `book_id` | BIGINT FK→book.id | 属于哪本教辅 |
| `doc_id` | VARCHAR(64) UNIQUE | **本服务的核心 ID**(算法见下)|
| `original_filename` | VARCHAR(200) | 原始文件名 |
| `original_path` | VARCHAR(500) | zip 内的相对路径 |
| `title` | VARCHAR(200) | 解析出的标题(如能从 docx 提取)|
| `doc_type` | ENUM | `课时作业` / `周末提升` / `阶段质量评估` / `综合评估` / `unknown` |
| `order_index` | INT | 在教辅中的顺序(按文件名排序)|
| `status` | ENUM | `pending` / `html_ready` / `json_ready` / `failed` |
| `error_msg` | TEXT | 失败原因 |
| `created_at` | DATETIME | |
| `updated_at` | DATETIME | |
**`doc_id` 生成算法**(保证全局唯一 + 路径稳定):
```python
doc_id = sha1(f"{book_id}|{original_path}").hexdigest()[:16]
# 例: 5b3a2f1e9c0d4b7a
```
- 同本书同路径永远同一 doc_id
- 重复传同一 zip 不会重复入库(幂等)
- 不带敏感信息(文件名脱敏)
**索引**: `uk_doc_id`, `idx_book_id (book_id, order_index)`
#### `document_file` 文件(HTML / JSON / 原 docx 多产物)
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | BIGINT PK | 自增 |
| `doc_id` | VARCHAR(64) FK→book_document.doc_id | 关联到文档 |
| `file_type` | ENUM | `docx` / `html` / `json` / `preview_png` / `error_log` |
| `object_key` | VARCHAR(500) | 对象存储 key |
| `object_bucket` | VARCHAR(50) | 桶名 |
| `size` | BIGINT | 字节数 |
| `checksum` | VARCHAR(64) | sha256 |
| `url` | VARCHAR(500) | 公开 URL(如果是公开桶)|
| `meta` | JSON | 附加信息:公式数、图片数、字符数等 |
| `created_at` | DATETIME | |
**索引**: `uk_doc_type (doc_id, file_type)`, `idx_bucket_key (object_bucket, object_key)`
**一个 doc 对应 3-5 条 file 记录**: 原 docx + html + json + (可选) 错误日志
---
## 3. 对象存储路径结构
```
<bucket>/
└── book/
└── <book_id_or_slug>/ # book-{id}-{slug}
└── docs/
└── <doc_id>/ # sha1 短 hash
├── source.docx # 原 docx(备份用)
├── output.html # 转的 HTML(含 MathJax)
├── structure.json # Agent 后续结构化输出
├── preview.png # 可选:首页截图
└── error.log # 仅失败时有
```
**优点**:
- 同 doc_id 所有产物都在一个目录
- 删教辅 → 删整个 `<book_id>/` 前缀
- 列表时一次 list `<book_id>/docs/` 拿到全部
---
## 4. MCP 工具集
### 4.1 写入类
| # | 工具 | 签名 | 返回 |
|---|---|---|---|
| 1 | `create_book` | `(name, zip_path, subject?, grade?, version?, type?, created_by?)` | `{book_id, status, total_count}` |
| 2 | `add_document` | `(book_id, original_filename, original_path, zip_size?)` | `{doc_id, order_index, status}` |
| 3 | `mark_html_ready` | `(doc_id, html_url, html_size, stats: {formulas, images, chars})` | `{ok, file_id}` |
| 4 | `mark_json_ready` | `(doc_id, json_url, json_size, schema_version)` | `{ok, file_id}` |
| 5 | `mark_failed` | `(doc_id, error_msg, stage: "html" \| "json" \| "upload")` | `{ok, status}` |
| 6 | `update_book_status` | `(book_id, status, error_msg?)` | `{ok}` |
| 15 | `upload_to_rustfs` | `(local_path, key, bucket?)` | `{url, key, size, checksum}` |
### 4.2 查询类
| # | 工具 | 签名 | 返回 |
|---|---|---|---|
| 7 | `get_book` | `(book_id)` | `{book + documents[] + files[]}` |
| 8 | `list_books` | `(status?, page?, page_size?, keyword?)` | `{books[], total, page, page_size}` |
| 9 | `get_document` | `(doc_id)` | `{document + files[]}` |
| 10 | `list_documents` | `(book_id, status?, page?, page_size?)` | `{documents[], total}` |
| 11 | `get_document_files` | `(doc_id)` | `{files[]}` |
| 12 | `get_document_url` | `(doc_id, file_type: "html"\|"json"\|"docx")` | `{url, expires_in}` |
### 4.3 辅助类
| # | 工具 | 签名 | 返回 |
|---|---|---|---|
| 13 | `delete_book` | `(book_id, hard?: bool)` | `{ok, deleted_files}` |
| 14 | `retry_failed` | `(book_id)` | `{retried_count, ok, failed_count}` |
### 4.4 工具数量
- **v1 最小可用**: 8 个(`create_book` / `add_document` / `mark_html_ready` / `mark_json_ready` / `mark_failed` / `update_book_status` / `get_book` / `list_books`)
- **v2 完整**: 加 `get_document` / `list_documents` / `get_document_files` / `get_document_url`
- **v3 兜底**: 加 `delete_book` / `retry_failed` / `upload_to_rustfs`
---
## 5. 技术栈选型
| 层 | 选型 | 理由 |
|---|---|---|
| MCP 框架 | `fastmcp` (Python 3.13) | 跟 `wordparse_server.py` 一致,**<10 行起 server** |
| HTTP 框架 | 不另起 — fastmcp 自动暴露 HTTP | 省一个服务 |
| 数据库 | MySQL / PolarDB | 跟 jby 同栈;`SQLAlchemy` 2.0 + `alembic` |
| 对象存储 | RustFS `fs.code-lab.cn`(你已有)| 同 wordparse |
| Word→HTML | 复用 `wordparse_server.py` 的 `docx_to_html` | 不重写,自动清洗已集成 |
| HTML→JSON | Agent 调 LLM 自己做 | 复杂结构化逻辑,放业务侧 |
| 配置 | YAML | 跟 wordparse 一致 |
---
## 6. 项目目录
```
book-mcp/
├── config.yaml # db / rustfs / 默认桶
├── book_mcp_server.py # MCP 入口(fastmcp)
├── db/
│ ├── models.py # SQLAlchemy ORM
│ ├── migrations/ # alembic
│ └── schema.sql # 一份纯 SQL(给 DBA / 手动建表用)
├── services/
│ ├── book_service.py # book CRUD
│ ├── document_service.py # doc CRUD
│ ├── file_service.py # file CRUD
│ └── rustfs_service.py # 上传 / 删
├── tests/
└── README.md
```
---
## 7. 关键设计决策
| 问题 | 决策 | 理由 |
|---|---|---|
| 用 `book_id` 还是 `name` 查重? | name + version 唯一索引 | 同名教辅重复传不创建新 book |
| 同一 zip 多次上传? | 幂等(按 zip sha256 去重 book)| 防呆 |
| docx 处理失败怎么办? | `book.status = "partial"`,不阻塞整本 | 容错,UI 标黄提醒 |
| html 公开还是私有? | 公开(读教辅是低频操作)| 简化 URL 生成 |
| json 谁生成? | Agent 调 LLM | 强业务相关,不放 MCP |
| MCP 一次返回多少文档? | `list_documents` 分页 50/页 | 防大书 OOM |
| 数据库有没有? | 独立新库 `book_mcp` | 避免污染 jby schema |
| 删除能级? | 软删(`is_deleted` 字段)| 防误删 |
| 后续页面怎么查? | 直接调 MCP 的查询类工具(fastmcp 自动暴露 HTTP)| 不写新 API |
---
## 8. v1 范围(最小可用)
**做这 8 个工具就够用了**:
1. `create_book` — 必做
2. `add_document` — 必做
3. `mark_html_ready` — 必做
4. `mark_json_ready` — 必做
5. `mark_failed` — 必做
6. `update_book_status` — 必做
7. `get_book` — 必做
8. `list_books` — 必做
**v2 再加**: `get_document` / `list_documents` / `get_document_files` / `get_document_url` / `delete_book` / `retry_failed`
**v1 不做**:
- 鉴权(内网/白名单)
- 全文搜索(用对象存储 + ES 后续加)
- 文件版本管理(覆盖式)
- 异步任务队列(同步够用,每本 ~3-5 分钟)
---
## 9. 风险 & 备选
| 风险 | 应对 |
|---|---|
| 上传 1000+ docx 的大教辅会卡 Agent | `add_document` 一次传 1 个,避免事务过大;Agent 用并发池 |
| docx 转 HTML 全失败(脏文件)| 用之前加的 `_clean_docx_xml` 自动清洗 |
| 对象存储 URL 变(key 改了)| 数据库记 `object_key`,URL 重新生成 |
| MCP server 自己挂了 | launchd 保活 + 自动重启(参考 paop gateway)|
---
## 10. 待确认事项
| 待定 | 选项 |
|---|---|
| **数据库** | A. 复用 jby PolarDB(`questions-bank` 库加表)<br>B. 起新库 `book_mcp`<br>C. 用 SQLite(开发期够用)|
| **MCP 名字** | `book-mcp` / `exam-book-mcp` / `teach-assist-mcp` |
| **桶** | 复用 `scan` 还是新开 `book` 桶? |
| **写入控制** | 要不要签名 URL(防外链)?还是公开? |
| **MCP 端口** | 8080 / 8100 / 复用 8089 |
---
## 附录 A: 数据库 schema 草稿
```sql
CREATE TABLE book (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL,
subject VARCHAR(50),
grade VARCHAR(50),
version VARCHAR(50),
type VARCHAR(50),
zip_path VARCHAR(500) NOT NULL,
zip_sha256 VARCHAR(64) NOT NULL,
zip_size BIGINT NOT NULL DEFAULT 0,
status ENUM('pending','processing','done','failed','partial') NOT NULL DEFAULT 'pending',
total_count INT NOT NULL DEFAULT 0,
success_count INT NOT NULL DEFAULT 0,
failed_count INT NOT NULL DEFAULT 0,
error_msg TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by VARCHAR(100),
is_deleted TINYINT NOT NULL DEFAULT 0,
UNIQUE KEY uk_zip_sha (zip_sha256),
UNIQUE KEY uk_name_version (name, version, is_deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE book_document (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
book_id BIGINT NOT NULL,
doc_id VARCHAR(64) NOT NULL,
original_filename VARCHAR(200) NOT NULL,
original_path VARCHAR(500) NOT NULL,
title VARCHAR(200),
doc_type ENUM('课时作业','周末提升','阶段质量评估','综合评估','unknown') NOT NULL DEFAULT 'unknown',
order_index INT NOT NULL DEFAULT 0,
status ENUM('pending','html_ready','json_ready','failed') NOT NULL DEFAULT 'pending',
error_msg TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_doc_id (doc_id),
INDEX idx_book_order (book_id, order_index),
CONSTRAINT fk_book_doc FOREIGN KEY (book_id) REFERENCES book(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE document_file (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
doc_id VARCHAR(64) NOT NULL,
file_type ENUM('docx','html','json','preview_png','error_log') NOT NULL,
object_key VARCHAR(500) NOT NULL,
object_bucket VARCHAR(50) NOT NULL,
size BIGINT NOT NULL DEFAULT 0,
checksum VARCHAR(64),
url VARCHAR(500),
meta JSON,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_doc_type (doc_id, file_type),
INDEX idx_bucket_key (object_bucket, object_key),
CONSTRAINT fk_file_doc FOREIGN KEY (doc_id) REFERENCES book_document(doc_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
---
## 附录 B: 状态机
```
book.status 状态机:
pending → processing → done (全部成功)
└──→ partial (有失败)
└──→ failed (完全失败,异常中断)
book_document.status 状态机:
pending → html_ready → json_ready (正常完成)
└──→ failed (任何一步失败,可重试)
```
---
**END** — 等 Quentin 确认待定项后开干。

213
docs/deploy.md Normal file
View File

@@ -0,0 +1,213 @@
# book-mcp 部署文档
## 架构
```
Agent (任意 Agent 客户端)
↓ HTTP/JSON-RPC
book-mcp (8100) ──┬─→ MongoDB (27018/book_mcp) ← docker container
└─→ RustFS (fs.code-lab.cn/scan/) ← 对象存储
└─→ wordparse (8089) ← docker container docx4j
```
## 组件
| 组件 | 位置 | 端口 | 启动方式 |
|---|---|---|---|
| **book-mcp** | `~/WorkSpace/book-mcp/book_mcp_server.py` | 8100 | launchd `ai.book-mcp.server` |
| **MongoDB** | docker container `book-mcp-mongo` | 27018 | `docker start book-mcp-mongo` |
| **RustFS** | `fs.code-lab.cn` | 443 | 远程 |
| **wordparse (docx4j)** | docker container `wordparse-test` | 8089 | `docker start wordparse-test` |
## 安装(一次性)
### 1. MongoDB 容器
```bash
docker run -d \
--name book-mcp-mongo \
-p 27018:27017 \
-e MONGO_INITDB_ROOT_USERNAME=book_admin \
-e MONGO_INITDB_ROOT_PASSWORD=book_mongo_2026 \
-e MONGO_INITDB_DATABASE=book_mcp \
-v book-mcp-mongo-data:/data/db \
--restart unless-stopped \
mongo:8.0
```
### 2. Python venv(一次性)
```bash
python3.13 -m venv /tmp/wordparse_venv
/tmp/wordparse_venv/bin/pip install -r ~/WorkSpace/book-mcp/requirements.txt
```
依赖:`fastmcp boto3 pymongo pyyaml`
### 3. MongoDB 索引
```bash
/tmp/wordparse_venv/bin/python ~/WorkSpace/book-mcp/book_mcp_server.py --init-db
```
输出:
```
✓ MongoDB 已就绪(db: book_mcp, collections: [...])
✓ 8 个索引创建完成
```
### 4. LaunchAgent
```bash
# 装(已装好:~/Library/LaunchAgents/ai.book-mcp.server.plist)
launchctl load -w ~/Library/LaunchAgents/ai.book-mcp.server.plist
```
## 日常运维
### 状态查询
```bash
# launchd 状态
launchctl print gui/501/ai.book-mcp.server | grep -E "state|pid"
# 端口
lsof -i :8100
# 健康
curl http://127.0.0.1:8100/mcp
# fastmcp 在 /mcp 走 HTTP
# 实时日志
tail -f ~/.hermes/services/book-mcp/logs/server.log
```
### 重启
```bash
# 软重启(SIGTERM,launchd 30s ThrottleInterval)
kill $(lsof -ti :8100)
# launchd 自动拉起
sleep 5
ps aux | grep book_mcp_server | grep -v grep
```
### 调试
```bash
# 1. 跑 e2e 测试
/tmp/wordparse_venv/bin/python ~/WorkSpace/book-mcp/tests/test_e2e.py
# 2. 直接调 MCP 工具(fastmcp client)
/tmp/wordparse_venv/bin/python << 'PYEOF'
import asyncio
from fastmcp.client import Client
async def main():
async with Client("http://127.0.0.1:8100/mcp") as c:
r = await c.call_tool('health', {})
print(r.data)
asyncio.run(main())
PYEOF
# 3. mongo 直连
/tmp/wordparse_venv/bin/python << 'PYEOF'
from db import get_collection, COL_BOOK
print(f"books count: {get_collection(COL_BOOK).count_documents({})}")
PYEOF
```
## 配置
`~/WorkSpace/book-mcp/config.yaml`:
```yaml
server:
port: 8100 # MCP server 端口
database:
url: mongodb://book_admin:book_mongo_2026@127.0.0.1:27018/book_mcp?authSource=admin
db_name: book_mcp
rustfs:
endpoint: https://fs.code-lab.cn
default_bucket: scan
wordparse:
url: http://127.0.0.1:8089/api/v3/server_office_file/file/parse
timeout: 600
```
## MCP 工具列表(15 个)
| # | 工具 | 类别 |
|---|---|---|
| 1 | create_book | 写入 |
| 2 | add_document | 写入 |
| 3 | mark_html_ready | 写入 |
| 4 | mark_json_ready | 写入 |
| 5 | mark_failed | 写入 |
| 6 | update_book_status | 写入 |
| 7 | get_book | 查询 |
| 8 | list_books | 查询 |
| 9 | get_document | 查询 |
| 10 | list_documents | 查询 |
| 11 | get_document_url | 查询 |
| 12 | upload_to_rustfs | 辅助 |
| 13 | soft_delete_book | 辅助 |
| 14 | retry_failed | 辅助 |
| 15 | health | 诊断 |
## 数据流
```
zip 上传 RustFS
↓ create_book(zip_sha256)
→ book.id
↓ 循环每 docx
→ add_document(filename, path)
→ doc.doc_id = sha1(book_id|path)[:16]
→ wordparse.convert_one (cleaner + docx4j + MathJax)
→ RustFS upload → mark_html_ready(url)
→ Agent 解析 HTML → structure.json
→ RustFS upload → mark_json_ready(url)
→ update_book_status("done")
```
## 故障排查
| 现象 | 原因 | 解决 |
|---|---|---|
| `mongo` health=False | 容器没起 | `docker start book-mcp-mongo` |
| `wordparse` health=False | docx4j 容器没起 | `docker start wordparse-test` |
| 8100 端口未 listen | 进程挂了 | `kill $(lsof -ti :8100)` 走 launchd 自动拉起 |
| `add_document` 报 NoneType | 数据库索引问题(已修复) | 重跑 `--init-db` |
| 上传 403 Forbidden | 桶不存在 | 检查 `default_bucket` 是否在 RustFS 实际存在 |
| e2e 测试报 `uk_name_version` | 同名教辅已存在 | 改 name 或 version 加时间戳 |
## 备份
- **MongoDB 数据**:`book-mcp-mongo-data` docker volume(在 docker 里)
- **RustFS 文件**:`https://fs.code-lab.cn/scan/book/<book_id>/...`
- **代码**:`~/WorkSpace/book-mcp/`
## 升级路径
```bash
# 1. 拉新代码
cd ~/WorkSpace/book-mcp
git pull # 假设推到 git
# 2. 升级依赖
/tmp/wordparse_venv/bin/pip install -r requirements.txt --upgrade
# 3. 重启 launchd
kill $(lsof -ti :8100)
sleep 5
ps aux | grep book_mcp_server | grep -v grep
```
## 相关链接
- 设计文档:`docs/book-mcp-design.md`
- Schema:`db/schema_doc.md`
- wordparse 工具:`services/wordparse/`
- 启动日志:`~/.hermes/services/book-mcp/logs/server.log`
- 错误日志:`~/.hermes/services/book-mcp/logs/server.error.log`