yuque-ai-mcp 2.7.6 → 2.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -18
- package/README_CN.md +87 -18
- package/SKILL.md +13 -11
- package/dist/board/create-board.js +2 -2
- package/dist/board/create-board.js.map +1 -1
- package/dist/common/api-client.d.ts +12 -0
- package/dist/common/api-client.js +53 -17
- package/dist/common/api-client.js.map +1 -1
- package/dist/common/config.d.ts +2 -1
- package/dist/common/config.js +4 -2
- package/dist/common/config.js.map +1 -1
- package/dist/common/diff.d.ts +12 -0
- package/dist/common/diff.js +46 -0
- package/dist/common/diff.js.map +1 -0
- package/dist/common/errors.js +3 -2
- package/dist/common/errors.js.map +1 -1
- package/dist/common/export-common.d.ts +2 -2
- package/dist/common/export-common.js +7 -10
- package/dist/common/export-common.js.map +1 -1
- package/dist/common/format.d.ts +45 -0
- package/dist/common/format.js +22 -0
- package/dist/common/format.js.map +1 -1
- package/dist/common/html-cleaner.d.ts +18 -0
- package/dist/common/html-cleaner.js +95 -0
- package/dist/common/html-cleaner.js.map +1 -0
- package/dist/common/toc-cache.d.ts +10 -24
- package/dist/common/toc-cache.js +30 -76
- package/dist/common/toc-cache.js.map +1 -1
- package/dist/common/toc-ops.d.ts +108 -0
- package/dist/common/toc-ops.js +387 -0
- package/dist/common/toc-ops.js.map +1 -0
- package/dist/common/validate.d.ts +2 -0
- package/dist/common/validate.js +8 -0
- package/dist/common/validate.js.map +1 -1
- package/dist/common/web-request.js +12 -2
- package/dist/common/web-request.js.map +1 -1
- package/dist/crawler/fetch.js +4 -8
- package/dist/crawler/fetch.js.map +1 -1
- package/dist/doc/batch-get-docs.js +13 -10
- package/dist/doc/batch-get-docs.js.map +1 -1
- package/dist/doc/copy-doc.d.ts +3 -4
- package/dist/doc/copy-doc.js +26 -135
- package/dist/doc/copy-doc.js.map +1 -1
- package/dist/doc/create-doc.js +9 -30
- package/dist/doc/create-doc.js.map +1 -1
- package/dist/doc/delete-doc.js +2 -2
- package/dist/doc/delete-doc.js.map +1 -1
- package/dist/doc/diff-doc.js +5 -45
- package/dist/doc/diff-doc.js.map +1 -1
- package/dist/doc/embed-url.js +5 -0
- package/dist/doc/embed-url.js.map +1 -1
- package/dist/doc/export-doc.d.ts +5 -5
- package/dist/doc/export-doc.js +18 -53
- package/dist/doc/export-doc.js.map +1 -1
- package/dist/doc/export-resources.d.ts +15 -0
- package/dist/doc/export-resources.js +114 -0
- package/dist/doc/export-resources.js.map +1 -0
- package/dist/doc/get-doc.js +6 -2
- package/dist/doc/get-doc.js.map +1 -1
- package/dist/doc/import-file-utils.d.ts +19 -0
- package/dist/doc/import-file-utils.js +137 -13
- package/dist/doc/import-file-utils.js.map +1 -1
- package/dist/doc/import-file.d.ts +2 -6
- package/dist/doc/import-file.js +30 -166
- package/dist/doc/import-file.js.map +1 -1
- package/dist/doc/import-url.d.ts +3 -1
- package/dist/doc/import-url.js +23 -108
- package/dist/doc/import-url.js.map +1 -1
- package/dist/doc/index.d.ts +1 -0
- package/dist/doc/index.js +3 -1
- package/dist/doc/index.js.map +1 -1
- package/dist/doc/list-docs.js +5 -2
- package/dist/doc/list-docs.js.map +1 -1
- package/dist/doc/update-doc.js +2 -2
- package/dist/doc/update-doc.js.map +1 -1
- package/dist/doc/version-detail.js +4 -6
- package/dist/doc/version-detail.js.map +1 -1
- package/dist/doc/versions.js +4 -6
- package/dist/doc/versions.js.map +1 -1
- package/dist/group/delete-user.js +2 -2
- package/dist/group/delete-user.js.map +1 -1
- package/dist/group/list-users.js +7 -4
- package/dist/group/list-users.js.map +1 -1
- package/dist/group/update-user.js +3 -4
- package/dist/group/update-user.js.map +1 -1
- package/dist/mine/editor-center.js +5 -1
- package/dist/mine/editor-center.js.map +1 -1
- package/dist/mine/get-book-stacks.js +5 -1
- package/dist/mine/get-book-stacks.js.map +1 -1
- package/dist/note/create-note.js +3 -2
- package/dist/note/create-note.js.map +1 -1
- package/dist/note/get-note.js +6 -0
- package/dist/note/get-note.js.map +1 -1
- package/dist/note/list-notes.js +1 -2
- package/dist/note/list-notes.js.map +1 -1
- package/dist/note/update-note.js +6 -0
- package/dist/note/update-note.js.map +1 -1
- package/dist/recycle/destroy-recycle.js +9 -1
- package/dist/recycle/destroy-recycle.js.map +1 -1
- package/dist/recycle/list-recycles.js +5 -1
- package/dist/recycle/list-recycles.js.map +1 -1
- package/dist/recycle/restore-recycle.js +9 -1
- package/dist/recycle/restore-recycle.js.map +1 -1
- package/dist/repo/batch-get-repos.js +14 -6
- package/dist/repo/batch-get-repos.js.map +1 -1
- package/dist/repo/copy-repo.js +8 -14
- package/dist/repo/copy-repo.js.map +1 -1
- package/dist/repo/create-repo.js +2 -2
- package/dist/repo/create-repo.js.map +1 -1
- package/dist/repo/delete-repo.js +3 -2
- package/dist/repo/delete-repo.js.map +1 -1
- package/dist/repo/export-repo.js +1 -1
- package/dist/repo/export-repo.js.map +1 -1
- package/dist/repo/get-repo.js +3 -2
- package/dist/repo/get-repo.js.map +1 -1
- package/dist/repo/list-repos.js +3 -2
- package/dist/repo/list-repos.js.map +1 -1
- package/dist/repo/update-repo.js +3 -2
- package/dist/repo/update-repo.js.map +1 -1
- package/dist/rss/fetch-feed.js +2 -2
- package/dist/rss/fetch-feed.js.map +1 -1
- package/dist/search/index.d.ts +1 -0
- package/dist/search/index.js +3 -1
- package/dist/search/index.js.map +1 -1
- package/dist/search/rag-search-utils.d.ts +12 -4
- package/dist/search/rag-search-utils.js +33 -25
- package/dist/search/rag-search-utils.js.map +1 -1
- package/dist/search/rag-search.d.ts +1 -1
- package/dist/search/rag-search.js +28 -12
- package/dist/search/rag-search.js.map +1 -1
- package/dist/search/search.js +5 -4
- package/dist/search/search.js.map +1 -1
- package/dist/search/web-search.d.ts +16 -0
- package/dist/search/web-search.js +129 -0
- package/dist/search/web-search.js.map +1 -0
- package/dist/toc/batch-update.d.ts +1 -1
- package/dist/toc/batch-update.js +2 -184
- package/dist/toc/batch-update.js.map +1 -1
- package/dist/toc/get-toc.js +3 -2
- package/dist/toc/get-toc.js.map +1 -1
- package/dist/upload/upload-attachment.js +15 -12
- package/dist/upload/upload-attachment.js.map +1 -1
- package/dist/user/get-groups.js +5 -5
- package/dist/user/get-groups.js.map +1 -1
- package/dist/user/get-user.js +5 -0
- package/dist/user/get-user.js.map +1 -1
- package/dist/user/hello.js +2 -2
- package/dist/user/hello.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
<h1 align="center">yuque-ai-mcp</h1>
|
|
6
6
|
<p align="center">
|
|
7
|
-
<b>
|
|
7
|
+
<b>63 fine-grained MCP tools for the full Yuque OpenAPI</b>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
10
|
<p align="center">
|
|
11
|
-
<a href="https://github.com/yehuoshun/yuque-ai-mcp"><img src="https://img.shields.io/badge/version-2.7.
|
|
11
|
+
<a href="https://github.com/yehuoshun/yuque-ai-mcp"><img src="https://img.shields.io/badge/version-2.7.8-blue" alt="version" /></a>
|
|
12
12
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license" /></a>
|
|
13
|
-
<a href="https://github.com/yehuoshun/yuque-ai-skills"><img src="https://img.shields.io/badge/skills-
|
|
13
|
+
<a href="https://github.com/yehuoshun/yuque-ai-skills"><img src="https://img.shields.io/badge/skills-63%20guides-orange" alt="skills" /></a>
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
16
|
<p align="center">
|
|
@@ -19,15 +19,15 @@
|
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
A full-featured Yuque (语雀) MCP Server built on the [Model Context Protocol](https://modelcontextprotocol.io/). Provides
|
|
22
|
+
A full-featured Yuque (语雀) MCP Server built on the [Model Context Protocol](https://modelcontextprotocol.io/). Provides 63 fine-grained tools across 15 domains — every Yuque OpenAPI endpoint as a dedicated tool.
|
|
23
23
|
|
|
24
24
|
## Why
|
|
25
25
|
|
|
26
|
-
- **19 →
|
|
26
|
+
- **19 → 63 tools** — 3x more coverage than the official [yuque-mcp-server](https://github.com/yuque/yuque-mcp-server)
|
|
27
27
|
- **Dual transport** — stdio + HTTP SSE, shared registry, zero downtime on reload
|
|
28
28
|
- **Modular architecture** — 15 domains, barrel exports, single source of truth registry
|
|
29
29
|
- **Full API coverage** — group, recycle, upload, statistics, versions, boards — all the missing pieces
|
|
30
|
-
- **[Skill layer](https://github.com/yehuoshun/yuque-ai-skills)** —
|
|
30
|
+
- **[Skill layer](https://github.com/yehuoshun/yuque-ai-skills)** — 63 usage guides for AI agents
|
|
31
31
|
|
|
32
32
|
## Table of Contents
|
|
33
33
|
|
|
@@ -61,10 +61,10 @@ npm run dev:http # HTTP SSE mode (http://localhost:3099)
|
|
|
61
61
|
|
|
62
62
|
| Domain | Tools | Highlights |
|
|
63
63
|
|--------|-------|------------|
|
|
64
|
-
| **doc** |
|
|
64
|
+
| **doc** | 15 | CRUD, versions, diff, batch get, import URL/file, cross-book copy, export, resource download |
|
|
65
65
|
| **repo** | 8 | CRUD, batch get, cross-book copy, full export (TOC-structure + INDEX/GRAPH) |
|
|
66
66
|
| **toc** | 3 | Get, update, batch update (createTitle/appendNode/removeNode/moveNode) |
|
|
67
|
-
| **search** |
|
|
67
|
+
| **search** | 3 | General search + RAG-enhanced search + Cookie web search |
|
|
68
68
|
| **user** | 3 | User info, heartbeat, group list |
|
|
69
69
|
| **group** | 3 | Member list, role change, delete member |
|
|
70
70
|
| **statistic** | 4 | Group/member/repo/doc statistics |
|
|
@@ -76,21 +76,89 @@ npm run dev:http # HTTP SSE mode (http://localhost:3099)
|
|
|
76
76
|
| **rss** | 3 | Source list, fetch + dedup + save, schedule analysis |
|
|
77
77
|
| **crawler** | 4 | Fetch, CSS extract, dedup save, schedule analysis |
|
|
78
78
|
| **kv** | 4 | Get, set, delete, list — incremental sharding, 250KB/doc limit |
|
|
79
|
-
| **Total** | **
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
| **Total** | **63** | |
|
|
80
|
+
|
|
81
|
+
### All 63 Tools
|
|
82
|
+
|
|
83
|
+
| Tool | Domain | Description |
|
|
84
|
+
|------|--------|-------------|
|
|
85
|
+
| `yuque_hello` | user | 心跳检测,验证 Token 有效性 |
|
|
86
|
+
| `yuque_get_user` | user | 获取当前 Token 的用户详情 |
|
|
87
|
+
| `yuque_get_user_groups` | user | 获取用户所属的团队列表 |
|
|
88
|
+
| `yuque_search` | search | 通用搜索文档/知识库 |
|
|
89
|
+
| `yuque_rag_search` | search | RAG 检索增强搜索 + 自动获取文档内容 |
|
|
90
|
+
| `yuque_web_search` | search | Cookie 态 Web 搜索,返回完整文档对象 + 精确总数 + 高亮摘要 |
|
|
91
|
+
| `yuque_get_group_users` | group | 获取团队成员列表 |
|
|
92
|
+
| `yuque_update_group_user` | group | 变更团队成员角色 |
|
|
93
|
+
| `yuque_delete_group_user` | group | 删除团队成员 |
|
|
94
|
+
| `yuque_list_docs` | doc | 获取知识库文档列表 |
|
|
95
|
+
| `yuque_create_doc` | doc | 创建文档 |
|
|
96
|
+
| `yuque_get_doc` | doc | 获取文档详情(支持 ID 或 slug) |
|
|
97
|
+
| `yuque_update_doc` | doc | 更新文档 |
|
|
98
|
+
| `yuque_delete_doc` | doc | 删除文档 |
|
|
99
|
+
| `yuque_batch_get_docs` | doc | 批量获取文档详情(max 20) |
|
|
100
|
+
| `yuque_get_doc_versions` | doc | 获取文档历史版本列表 |
|
|
101
|
+
| `yuque_get_doc_version_detail` | doc | 获取文档历史版本详情 |
|
|
102
|
+
| `yuque_diff_doc_versions` | doc | 对比两个版本的行级差异 |
|
|
103
|
+
| `yuque_copy_doc` | doc | 单文档跨库复制 |
|
|
104
|
+
| `yuque_export_doc` | doc | 导出单篇文档为 Markdown 文件 |
|
|
105
|
+
| `yuque_export_resources` | doc | 下载文档中的图片/附件到本地 |
|
|
106
|
+
| `yuque_import_url` | doc | 从网页 URL 导入文档 |
|
|
107
|
+
| `yuque_import_file` | doc | 从本地文件导入文档 |
|
|
108
|
+
| `yuque_embed_url` | doc | 生成文档嵌入阅读器 URL |
|
|
109
|
+
| `yuque_get_toc` | toc | 获取知识库目录 |
|
|
110
|
+
| `yuque_update_toc` | toc | 更新知识库目录 |
|
|
111
|
+
| `yuque_batch_update_toc` | toc | 批量更新目录(createTitle/appendNode/removeNode/moveNode/prependDoc) |
|
|
112
|
+
| `yuque_list_repos` | repo | 获取知识库列表(用户/团队) |
|
|
113
|
+
| `yuque_create_repo` | repo | 创建知识库 |
|
|
114
|
+
| `yuque_get_repo` | repo | 获取知识库详情 |
|
|
115
|
+
| `yuque_update_repo` | repo | 更新知识库 |
|
|
116
|
+
| `yuque_delete_repo` | repo | 删除知识库 |
|
|
117
|
+
| `yuque_batch_get_repos` | repo | 批量获取知识库详情(max 20) |
|
|
118
|
+
| `yuque_copy_repo` | repo | 批量跨库复制(LLM 分类 + 目录重建) |
|
|
119
|
+
| `yuque_export_repo` | repo | 批量导出知识库为 Markdown(按 TOC 目录结构) |
|
|
120
|
+
| `yuque_get_group_statistics` | statistic | 获取团队汇总统计数据 |
|
|
121
|
+
| `yuque_get_member_statistics` | statistic | 获取团队成员统计数据 |
|
|
122
|
+
| `yuque_get_book_statistics` | statistic | 获取团队知识库统计数据 |
|
|
123
|
+
| `yuque_get_doc_statistics` | statistic | 获取团队文档统计数据 |
|
|
124
|
+
| `yuque_list_notes` | note | 获取小记列表 |
|
|
125
|
+
| `yuque_get_note` | note | 获取小记详情 |
|
|
126
|
+
| `yuque_create_note` | note | 创建小记 |
|
|
127
|
+
| `yuque_update_note` | note | 更新小记 |
|
|
128
|
+
| `yuque_list_recycles` | recycle | 列出回收站项目 |
|
|
129
|
+
| `yuque_restore_recycle` | recycle | 恢复回收站项目 |
|
|
130
|
+
| `yuque_destroy_recycle` | recycle | 彻底删除回收站项目 |
|
|
131
|
+
| `yuque_upload_attachment` | upload | 上传文件到语雀 CDN |
|
|
132
|
+
| `yuque_get_board` | board | 获取文档中的画板资源 |
|
|
133
|
+
| `yuque_create_board` | board | 在文档中创建画板资源 |
|
|
134
|
+
| `yuque_update_board` | board | 更新文档中的画板资源 |
|
|
135
|
+
| `yuque_rss_list_sources` | rss | 列出所有可用 RSS 数据源及 feed 类型 |
|
|
136
|
+
| `yuque_rss_fetch` | rss | 抓取 RSS/Atom Feed,解析后去重写入语雀 |
|
|
137
|
+
| `yuque_rss_schedule` | rss | 分析更新频率,生成推荐抓取时间 |
|
|
138
|
+
| `yuque_crawl_fetch` | crawler | 抓取网页原始 HTML |
|
|
139
|
+
| `yuque_crawl_extract` | crawler | CSS 选择器从 HTML 提取内容 |
|
|
140
|
+
| `yuque_crawl_save` | crawler | 去重 + 写入语雀 |
|
|
141
|
+
| `yuque_crawl_schedule` | crawler | 分析爬虫抓取频率,生成推荐抓取时间 |
|
|
142
|
+
| `yuque_get_book_stacks` | mine | 获取知识库分组(书架)列表 |
|
|
143
|
+
| `yuque_get_editor_center` | mine | 获取个人编辑中心全景数据 |
|
|
144
|
+
| `yuque_kv_get` | kv | 读取 KV 命名空间的完整 JSON map(分片合并) |
|
|
145
|
+
| `yuque_kv_set` | kv | 增量设置 key-value,超 250KB 自动分片 |
|
|
146
|
+
| `yuque_kv_delete` | kv | 遍历分片查找并删除 key |
|
|
147
|
+
| `yuque_kv_list` | kv | 列出已配置的 KV 命名空间 |
|
|
148
|
+
|
|
149
|
+
See [SKILL.md](SKILL.md) or [yuque-ai-skills](https://github.com/yehuoshun/yuque-ai-skills) for full tool documentation with parameters and examples.
|
|
82
150
|
|
|
83
151
|
## vs Official
|
|
84
152
|
|
|
85
153
|
| Feature | Official yuque-mcp-server | yuque-ai-mcp |
|
|
86
154
|
|---------|--------------------------|--------------|
|
|
87
|
-
| Tools | 19 | **
|
|
155
|
+
| Tools | 19 | **63** |
|
|
88
156
|
| Granularity | Coarse | **Fine-grained** (1 tool / endpoint) |
|
|
89
157
|
| Group, Recycle, Upload, Statistics | ❌ | ✅ |
|
|
90
158
|
| Versions, Diff, Cross-book Copy | ❌ | ✅ |
|
|
91
159
|
| Transport | stdio only | **stdio + HTTP SSE** |
|
|
92
160
|
| Config | Env var | **config.json** (token + cookie) |
|
|
93
|
-
| Skill Layer | ❌ | ✅
|
|
161
|
+
| Skill Layer | ❌ | ✅ 63 guides |
|
|
94
162
|
|
|
95
163
|
## Architecture
|
|
96
164
|
|
|
@@ -98,7 +166,7 @@ See [SKILL.md](SKILL.md) or [yuque-ai-skills](https://github.com/yehuoshun/yuque
|
|
|
98
166
|
server/src/
|
|
99
167
|
├── common/ # Shared: config, errors, types, format, validate,
|
|
100
168
|
│ # api-client, web-request, register-tools, copy/export/schedule common,
|
|
101
|
-
│ # repo-capacity (auto-expand), toc-cache (
|
|
169
|
+
│ # repo-capacity (auto-expand), toc-cache (configurable TTL), text-utils
|
|
102
170
|
├── user/ search/ group/ doc/ toc/ repo/ statistic/
|
|
103
171
|
├── note/ recycle/ upload/ board/ rss/ crawler/ mine/ kv/
|
|
104
172
|
├── index.ts # stdio entry
|
|
@@ -127,8 +195,8 @@ server/src/
|
|
|
127
195
|
},
|
|
128
196
|
"namespaces": {
|
|
129
197
|
"cnblogs": {
|
|
130
|
-
"book_id": [
|
|
131
|
-
"kv_slugs": [
|
|
198
|
+
"book_id": [0],
|
|
199
|
+
"kv_slugs": [],
|
|
132
200
|
"schedule_slugs": []
|
|
133
201
|
}
|
|
134
202
|
}
|
|
@@ -137,8 +205,8 @@ server/src/
|
|
|
137
205
|
"enabled": true,
|
|
138
206
|
"namespaces": {
|
|
139
207
|
"my-source": {
|
|
140
|
-
"book_id": [
|
|
141
|
-
"kv_slugs": [
|
|
208
|
+
"book_id": [0],
|
|
209
|
+
"kv_slugs": [],
|
|
142
210
|
"schedule_slugs": []
|
|
143
211
|
}
|
|
144
212
|
}
|
|
@@ -149,6 +217,7 @@ server/src/
|
|
|
149
217
|
- `book_id`: Target repo ID array — last element is the active repo. Auto-expands when full (5000 docs).
|
|
150
218
|
- `kv_slugs`: KV dedup shard docs (`{book_id}/{doc_id}` format)
|
|
151
219
|
- `schedule_slugs`: Schedule config docs
|
|
220
|
+
- `toc_cache_ttl_minutes`: TOC cache TTL in minutes (default 60). Set higher to reduce API calls, lower for fresher data.
|
|
152
221
|
|
|
153
222
|
## Error Handling
|
|
154
223
|
|
package/README_CN.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<h1 align="center">yuque-ai-mcp</h1>
|
|
3
3
|
<p align="center">
|
|
4
|
-
<b>
|
|
4
|
+
<b>63 个细粒度 MCP 工具,覆盖语雀 OpenAPI 全部能力</b>
|
|
5
5
|
</p>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
|
-
<a href="https://github.com/yehuoshun/yuque-ai-mcp"><img src="https://img.shields.io/badge/版本-2.7.
|
|
9
|
+
<a href="https://github.com/yehuoshun/yuque-ai-mcp"><img src="https://img.shields.io/badge/版本-2.7.8-blue" alt="version" /></a>
|
|
10
10
|
<a href="LICENSE"><img src="https://img.shields.io/badge/许可-MIT-green" alt="license" /></a>
|
|
11
|
-
<a href="https://github.com/yehuoshun/yuque-ai-skills"><img src="https://img.shields.io/badge/skills-
|
|
11
|
+
<a href="https://github.com/yehuoshun/yuque-ai-skills"><img src="https://img.shields.io/badge/skills-63%20指导-orange" alt="skills" /></a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
14
|
<p align="center">
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
-
基于 [Model Context Protocol](https://modelcontextprotocol.io/) 的语雀全功能 MCP Server。
|
|
20
|
+
基于 [Model Context Protocol](https://modelcontextprotocol.io/) 的语雀全功能 MCP Server。63 个工具,15 个域——每个语雀 OpenAPI 端点一个专用工具。
|
|
21
21
|
|
|
22
22
|
## 为什么选这个
|
|
23
23
|
|
|
24
|
-
- **19 →
|
|
24
|
+
- **19 → 63 工具** — 比官方 [yuque-mcp-server](https://github.com/yuque/yuque-mcp-server) 多 3 倍覆盖
|
|
25
25
|
- **双传输模式** — stdio + HTTP SSE,共享注册中心,修改无需重启
|
|
26
26
|
- **模块化架构** — 15 个域,barrel export,唯一注册中心
|
|
27
27
|
- **完整 API 覆盖** — 团队、回收站、上传、统计、版本、画板——补全官方缺失
|
|
28
|
-
- **[Skill 层](https://github.com/yehuoshun/yuque-ai-skills)** —
|
|
28
|
+
- **[Skill 层](https://github.com/yehuoshun/yuque-ai-skills)** — 63 个 AI Agent 使用指导
|
|
29
29
|
|
|
30
30
|
## 目录
|
|
31
31
|
|
|
@@ -60,10 +60,10 @@ npm run dev:http # HTTP SSE 模式 (http://localhost:3099)
|
|
|
60
60
|
|
|
61
61
|
| 域 | 工具数 | 亮点 |
|
|
62
62
|
|--------|-------|------------|
|
|
63
|
-
| **doc** |
|
|
63
|
+
| **doc** | 15 | CRUD、版本管理、Diff、批量获取、URL/文件导入、跨库复制、导出、资源下载 |
|
|
64
64
|
| **repo** | 8 | CRUD、批量获取、跨库复制、全量导出(TOC 结构 + INDEX/GRAPH) |
|
|
65
65
|
| **toc** | 3 | 获取、更新、批量更新(createTitle/appendNode/removeNode/moveNode) |
|
|
66
|
-
| **search** |
|
|
66
|
+
| **search** | 3 | 通用搜索 + RAG 增强搜索 + Cookie Web 搜索 |
|
|
67
67
|
| **user** | 3 | 用户信息、心跳、团队列表 |
|
|
68
68
|
| **group** | 3 | 成员列表、角色变更、删除成员 |
|
|
69
69
|
| **statistic** | 4 | 团队/成员/知识库/文档统计 |
|
|
@@ -75,21 +75,89 @@ npm run dev:http # HTTP SSE 模式 (http://localhost:3099)
|
|
|
75
75
|
| **rss** | 3 | 数据源列表、抓取+去重+写入、定时策略分析 |
|
|
76
76
|
| **crawler** | 4 | 抓取、CSS 提取、去重写入、定时策略分析 |
|
|
77
77
|
| **kv** | 4 | 增删查列——增量分片,单文档 250KB 上限 |
|
|
78
|
-
| **合计** | **
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
| **合计** | **63** | |
|
|
79
|
+
|
|
80
|
+
### 全部 63 个工具
|
|
81
|
+
|
|
82
|
+
| 工具 | 域 | 说明 |
|
|
83
|
+
|------|--------|-------------|
|
|
84
|
+
| `yuque_hello` | user | 心跳检测,验证 Token 有效性 |
|
|
85
|
+
| `yuque_get_user` | user | 获取当前 Token 的用户详情 |
|
|
86
|
+
| `yuque_get_user_groups` | user | 获取用户所属的团队列表 |
|
|
87
|
+
| `yuque_search` | search | 通用搜索文档/知识库 |
|
|
88
|
+
| `yuque_rag_search` | search | RAG 检索增强搜索 + 自动获取文档内容 |
|
|
89
|
+
| `yuque_web_search` | search | Cookie 态 Web 搜索,返回完整文档对象 + 精确总数 + 高亮摘要 |
|
|
90
|
+
| `yuque_get_group_users` | group | 获取团队成员列表 |
|
|
91
|
+
| `yuque_update_group_user` | group | 变更团队成员角色 |
|
|
92
|
+
| `yuque_delete_group_user` | group | 删除团队成员 |
|
|
93
|
+
| `yuque_list_docs` | doc | 获取知识库文档列表 |
|
|
94
|
+
| `yuque_create_doc` | doc | 创建文档 |
|
|
95
|
+
| `yuque_get_doc` | doc | 获取文档详情(支持 ID 或 slug) |
|
|
96
|
+
| `yuque_update_doc` | doc | 更新文档 |
|
|
97
|
+
| `yuque_delete_doc` | doc | 删除文档 |
|
|
98
|
+
| `yuque_batch_get_docs` | doc | 批量获取文档详情(max 20) |
|
|
99
|
+
| `yuque_get_doc_versions` | doc | 获取文档历史版本列表 |
|
|
100
|
+
| `yuque_get_doc_version_detail` | doc | 获取文档历史版本详情 |
|
|
101
|
+
| `yuque_diff_doc_versions` | doc | 对比两个版本的行级差异 |
|
|
102
|
+
| `yuque_copy_doc` | doc | 单文档跨库复制 |
|
|
103
|
+
| `yuque_export_doc` | doc | 导出单篇文档为 Markdown 文件 |
|
|
104
|
+
| `yuque_export_resources` | doc | 下载文档中的图片/附件到本地 |
|
|
105
|
+
| `yuque_import_url` | doc | 从网页 URL 导入文档 |
|
|
106
|
+
| `yuque_import_file` | doc | 从本地文件导入文档 |
|
|
107
|
+
| `yuque_embed_url` | doc | 生成文档嵌入阅读器 URL |
|
|
108
|
+
| `yuque_get_toc` | toc | 获取知识库目录 |
|
|
109
|
+
| `yuque_update_toc` | toc | 更新知识库目录 |
|
|
110
|
+
| `yuque_batch_update_toc` | toc | 批量更新目录(createTitle/appendNode/removeNode/moveNode/prependDoc) |
|
|
111
|
+
| `yuque_list_repos` | repo | 获取知识库列表(用户/团队) |
|
|
112
|
+
| `yuque_create_repo` | repo | 创建知识库 |
|
|
113
|
+
| `yuque_get_repo` | repo | 获取知识库详情 |
|
|
114
|
+
| `yuque_update_repo` | repo | 更新知识库 |
|
|
115
|
+
| `yuque_delete_repo` | repo | 删除知识库 |
|
|
116
|
+
| `yuque_batch_get_repos` | repo | 批量获取知识库详情(max 20) |
|
|
117
|
+
| `yuque_copy_repo` | repo | 批量跨库复制(LLM 分类 + 目录重建) |
|
|
118
|
+
| `yuque_export_repo` | repo | 批量导出知识库为 Markdown(按 TOC 目录结构) |
|
|
119
|
+
| `yuque_get_group_statistics` | statistic | 获取团队汇总统计数据 |
|
|
120
|
+
| `yuque_get_member_statistics` | statistic | 获取团队成员统计数据 |
|
|
121
|
+
| `yuque_get_book_statistics` | statistic | 获取团队知识库统计数据 |
|
|
122
|
+
| `yuque_get_doc_statistics` | statistic | 获取团队文档统计数据 |
|
|
123
|
+
| `yuque_list_notes` | note | 获取小记列表 |
|
|
124
|
+
| `yuque_get_note` | note | 获取小记详情 |
|
|
125
|
+
| `yuque_create_note` | note | 创建小记 |
|
|
126
|
+
| `yuque_update_note` | note | 更新小记 |
|
|
127
|
+
| `yuque_list_recycles` | recycle | 列出回收站项目 |
|
|
128
|
+
| `yuque_restore_recycle` | recycle | 恢复回收站项目 |
|
|
129
|
+
| `yuque_destroy_recycle` | recycle | 彻底删除回收站项目 |
|
|
130
|
+
| `yuque_upload_attachment` | upload | 上传文件到语雀 CDN |
|
|
131
|
+
| `yuque_get_board` | board | 获取文档中的画板资源 |
|
|
132
|
+
| `yuque_create_board` | board | 在文档中创建画板资源 |
|
|
133
|
+
| `yuque_update_board` | board | 更新文档中的画板资源 |
|
|
134
|
+
| `yuque_rss_list_sources` | rss | 列出所有可用 RSS 数据源及 feed 类型 |
|
|
135
|
+
| `yuque_rss_fetch` | rss | 抓取 RSS/Atom Feed,解析后去重写入语雀 |
|
|
136
|
+
| `yuque_rss_schedule` | rss | 分析更新频率,生成推荐抓取时间 |
|
|
137
|
+
| `yuque_crawl_fetch` | crawler | 抓取网页原始 HTML |
|
|
138
|
+
| `yuque_crawl_extract` | crawler | CSS 选择器从 HTML 提取内容 |
|
|
139
|
+
| `yuque_crawl_save` | crawler | 去重 + 写入语雀 |
|
|
140
|
+
| `yuque_crawl_schedule` | crawler | 分析爬虫抓取频率,生成推荐抓取时间 |
|
|
141
|
+
| `yuque_get_book_stacks` | mine | 获取知识库分组(书架)列表 |
|
|
142
|
+
| `yuque_get_editor_center` | mine | 获取个人编辑中心全景数据 |
|
|
143
|
+
| `yuque_kv_get` | kv | 读取 KV 命名空间的完整 JSON map(分片合并) |
|
|
144
|
+
| `yuque_kv_set` | kv | 增量设置 key-value,超 250KB 自动分片 |
|
|
145
|
+
| `yuque_kv_delete` | kv | 遍历分片查找并删除 key |
|
|
146
|
+
| `yuque_kv_list` | kv | 列出已配置的 KV 命名空间 |
|
|
147
|
+
|
|
148
|
+
完整工具文档(含参数和示例)见 [SKILL.md](SKILL.md) 或 [yuque-ai-skills](https://github.com/yehuoshun/yuque-ai-skills)。
|
|
81
149
|
|
|
82
150
|
## 与官方对比
|
|
83
151
|
|
|
84
152
|
| 功能 | 官方 yuque-mcp-server | yuque-ai-mcp |
|
|
85
153
|
|---------|--------------------------|--------------|
|
|
86
|
-
| 工具数 | 19 | **
|
|
154
|
+
| 工具数 | 19 | **63** |
|
|
87
155
|
| 粒度 | 粗粒度 | **细粒度**(1 端点 = 1 工具) |
|
|
88
156
|
| 团队、回收站、上传、统计 | ❌ | ✅ |
|
|
89
157
|
| 版本、Diff、跨库复制 | ❌ | ✅ |
|
|
90
158
|
| 传输模式 | 仅 stdio | **stdio + HTTP SSE** |
|
|
91
159
|
| 配置 | 环境变量 | **config.json**(token + cookie) |
|
|
92
|
-
| Skill 层 | ❌ | ✅
|
|
160
|
+
| Skill 层 | ❌ | ✅ 63 指导 |
|
|
93
161
|
|
|
94
162
|
## 架构
|
|
95
163
|
|
|
@@ -97,7 +165,7 @@ npm run dev:http # HTTP SSE 模式 (http://localhost:3099)
|
|
|
97
165
|
server/src/
|
|
98
166
|
├── common/ # 公共:config, errors, types, format, validate,
|
|
99
167
|
│ # api-client, web-request, register-tools, copy/export/schedule,
|
|
100
|
-
│ # repo-capacity(自动扩容), toc-cache
|
|
168
|
+
│ # repo-capacity(自动扩容), toc-cache(可配置 TTL), text-utils
|
|
101
169
|
├── user/ search/ group/ doc/ toc/ repo/ statistic/
|
|
102
170
|
├── note/ recycle/ upload/ board/ rss/ crawler/ mine/ kv/
|
|
103
171
|
├── index.ts # stdio 入口
|
|
@@ -126,8 +194,8 @@ server/src/
|
|
|
126
194
|
},
|
|
127
195
|
"namespaces": {
|
|
128
196
|
"cnblogs": {
|
|
129
|
-
"book_id": [
|
|
130
|
-
"kv_slugs": [
|
|
197
|
+
"book_id": [0],
|
|
198
|
+
"kv_slugs": [],
|
|
131
199
|
"schedule_slugs": []
|
|
132
200
|
}
|
|
133
201
|
}
|
|
@@ -136,8 +204,8 @@ server/src/
|
|
|
136
204
|
"enabled": true,
|
|
137
205
|
"namespaces": {
|
|
138
206
|
"my-source": {
|
|
139
|
-
"book_id": [
|
|
140
|
-
"kv_slugs": [
|
|
207
|
+
"book_id": [0],
|
|
208
|
+
"kv_slugs": [],
|
|
141
209
|
"schedule_slugs": []
|
|
142
210
|
}
|
|
143
211
|
}
|
|
@@ -148,6 +216,7 @@ server/src/
|
|
|
148
216
|
- `book_id`:目标知识库 ID 数组,最后一个为当前活跃仓库。满 5000 篇自动扩容追加。
|
|
149
217
|
- `kv_slugs`:KV 去重分片文档(`{book_id}/{doc_id}` 格式)
|
|
150
218
|
- `schedule_slugs`:定时策略配置文档
|
|
219
|
+
- `toc_cache_ttl_minutes`:TOC 缓存 TTL(分钟),默认 60。调大可减少 API 调用,调小可获取更新鲜的数据。
|
|
151
220
|
|
|
152
221
|
## 错误处理
|
|
153
222
|
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# yuque-ai-mcp
|
|
2
2
|
|
|
3
|
-
语雀全功能 MCP Server,
|
|
3
|
+
语雀全功能 MCP Server,63 个工具 / 15 个域。当用户提到「语雀」「yuque」「知识库」「文档」「团队」等关键词时触发。
|
|
4
4
|
|
|
5
5
|
## 触发场景
|
|
6
6
|
|
|
@@ -25,11 +25,12 @@
|
|
|
25
25
|
| `yuque_get_user` | 获取当前 Token 的用户详情 |
|
|
26
26
|
| `yuque_get_user_groups` | 获取用户所属的团队列表 |
|
|
27
27
|
|
|
28
|
-
### search(
|
|
28
|
+
### search(3 工具)
|
|
29
29
|
| 工具 | 说明 |
|
|
30
30
|
|------|------|
|
|
31
31
|
| `yuque_search` | 通用搜索文档/知识库 |
|
|
32
32
|
| `yuque_rag_search` | RAG 检索增强搜索 + 自动获取文档内容 |
|
|
33
|
+
| `yuque_web_search` | Cookie 态 Web 搜索,返回完整文档对象 + 精确总数 + 高亮摘要 |
|
|
33
34
|
|
|
34
35
|
### group(3 工具)
|
|
35
36
|
| 工具 | 说明 |
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
| `yuque_update_group_user` | 变更团队成员角色 |
|
|
39
40
|
| `yuque_delete_group_user` | 删除团队成员 |
|
|
40
41
|
|
|
41
|
-
### doc(
|
|
42
|
+
### doc(15 工具)
|
|
42
43
|
| 工具 | 说明 |
|
|
43
44
|
|------|------|
|
|
44
45
|
| `yuque_list_docs` | 获取知识库文档列表 |
|
|
@@ -51,7 +52,8 @@
|
|
|
51
52
|
| `yuque_get_doc_version_detail` | 获取文档历史版本详情 |
|
|
52
53
|
| `yuque_diff_doc_versions` | 对比两个版本的行级差异 |
|
|
53
54
|
| `yuque_copy_doc` | 单文档跨库复制 |
|
|
54
|
-
| `yuque_export_doc` | 导出单篇文档为 Markdown
|
|
55
|
+
| `yuque_export_doc` | 导出单篇文档为 Markdown 文件(不含资源下载,需调 yuque_export_resources) |
|
|
56
|
+
| `yuque_export_resources` | 下载文档中的图片/附件到本地目录,返回 URL→本地路径映射 |
|
|
55
57
|
| `yuque_import_url` | 从网页 URL 导入文档 |
|
|
56
58
|
| `yuque_import_file` | 从本地文件导入文档(direct/upload_assets/embed_assets) |
|
|
57
59
|
| `yuque_embed_url` | 生成文档嵌入阅读器 URL |
|
|
@@ -160,19 +162,19 @@
|
|
|
160
162
|
"enabled": true,
|
|
161
163
|
"namespaces": {
|
|
162
164
|
"cnblogs": {
|
|
163
|
-
"book_id":
|
|
164
|
-
"kv_slugs": [
|
|
165
|
-
"schedule_slugs": [
|
|
165
|
+
"book_id": 0,
|
|
166
|
+
"kv_slugs": [],
|
|
167
|
+
"schedule_slugs": []
|
|
166
168
|
}
|
|
167
169
|
}
|
|
168
170
|
},
|
|
169
171
|
"crawler": {
|
|
170
172
|
"enabled": true,
|
|
171
173
|
"namespaces": {
|
|
172
|
-
"
|
|
173
|
-
"book_id":
|
|
174
|
-
"kv_slugs": [
|
|
175
|
-
"schedule_slugs": [
|
|
174
|
+
"my-source": {
|
|
175
|
+
"book_id": 0,
|
|
176
|
+
"kv_slugs": [],
|
|
177
|
+
"schedule_slugs": []
|
|
176
178
|
}
|
|
177
179
|
}
|
|
178
180
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { apiPost } from "../common/api-client.js";
|
|
7
7
|
import { handleApiCall } from "../common/format.js";
|
|
8
|
-
import { requiredString } from "../common/validate.js";
|
|
8
|
+
import { check, requiredString } from "../common/validate.js";
|
|
9
9
|
export const boardCreate = {
|
|
10
10
|
name: "yuque_create_board",
|
|
11
11
|
description: "Create a board resource (mindmap/flowchart/architecture diagram) in a document. POST /yfm/boards. 详见 references/api/board_api.md",
|
|
@@ -22,7 +22,7 @@ export const boardCreate = {
|
|
|
22
22
|
},
|
|
23
23
|
async handler(args) {
|
|
24
24
|
// @validate
|
|
25
|
-
const __v = requiredString(args?.dsl, "dsl");
|
|
25
|
+
const __v = check(requiredString(args?.type, "type"), requiredString(args?.dsl, "dsl"));
|
|
26
26
|
if (__v)
|
|
27
27
|
return __v;
|
|
28
28
|
const docId = args?.doc_id;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-board.js","sourceRoot":"","sources":["../../src/board/create-board.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"create-board.js","sourceRoot":"","sources":["../../src/board/create-board.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE9D,MAAM,CAAC,MAAM,WAAW,GAAY;IAClC,IAAI,EAAE,oBAAoB;IAC1B,WAAW,EAAE,kIAAkI;IAE/I,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,2CAA2C,EAAE;YACpF,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+CAA+C,EAAE;YACrF,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gEAAgE,EAAE;YACvG,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,2DAA2D,EAAE;YACjG,oBAAoB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,sEAAsE,EAAE;SAC9H;QACD,QAAQ,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC;KAC1B;IAED,KAAK,CAAC,OAAO,CAAC,IAAI;QAChB,YAAY;QACZ,MAAM,GAAG,GAAG,KAAK,CACf,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAClC,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CACjC,CAAC;QACF,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;QACpB,MAAM,KAAK,GAAG,IAAI,EAAE,MAA4B,CAAC;QACjD,MAAM,GAAG,GAAG,IAAI,EAAE,GAAyB,CAAC;QAC5C,MAAM,IAAI,GAAG,IAAI,EAAE,IAAc,CAAC;QAClC,MAAM,GAAG,GAAG,IAAI,EAAE,GAAa,CAAC;QAChC,MAAM,WAAW,GAAG,IAAI,EAAE,oBAA0C,CAAC;QAErE,IAAI,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;YACnB,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,0CAA0C,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;gBAC1H,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAA4B,EAAE,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QAC/E,IAAI,KAAK;YAAE,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC;QAClC,IAAI,GAAG;YAAE,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC;QAC3B,IAAI,WAAW;YAAE,OAAO,CAAC,oBAAoB,GAAG,WAAW,CAAC;QAE5D,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC;QACnE,OAAO,aAAa,CAAC,IAAI,EAAE,SAAgB,CAAC,CAAC;IAC/C,CAAC;CACF,CAAC"}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* 减少工具文件中的重复代码。
|
|
6
6
|
* 重试策略:指数退避 1s → 2s → 4s,最多 3 次。
|
|
7
7
|
*/
|
|
8
|
+
/** 执行 fetch 并自动重试。导出供外部 URL 请求(crawler/rss/import-url)复用 */
|
|
9
|
+
export declare function fetchWithRetry(url: string, options: RequestInit, context: string, attempt?: number, timeoutMs?: number): Promise<Response>;
|
|
8
10
|
/** API 调用结果的类型 guard:检查是否是 MCP error */
|
|
9
11
|
export declare function isErrorResult(result: unknown): result is {
|
|
10
12
|
content: Array<{
|
|
@@ -21,6 +23,16 @@ export declare function apiPost(path: string, body: Record<string, unknown>, con
|
|
|
21
23
|
export declare function apiPut(path: string, body: Record<string, unknown>, context?: string): Promise<unknown>;
|
|
22
24
|
/** DELETE 请求 */
|
|
23
25
|
export declare function apiDelete(path: string, context?: string): Promise<unknown>;
|
|
26
|
+
/** 上传文件到语雀 CDN(FormData,不设 Content-Type 让浏览器自动设 boundary) */
|
|
27
|
+
export declare function apiUpload(path: string, formData: FormData, context?: string): Promise<unknown>;
|
|
28
|
+
/** 下载文件(返回 ArrayBuffer,用于导出文件下载) */
|
|
29
|
+
export declare function apiDownload(path: string, params?: Record<string, string>, context?: string): Promise<ArrayBuffer | {
|
|
30
|
+
content: Array<{
|
|
31
|
+
type: "text";
|
|
32
|
+
text: string;
|
|
33
|
+
}>;
|
|
34
|
+
isError: true;
|
|
35
|
+
}>;
|
|
24
36
|
/** POST 请求:支持 404 时 fallback 到备用路径(用于 user/group 端点自动切换) */
|
|
25
37
|
export declare function apiPostWithFallback(path: string, fallbackPath: string, body: Record<string, unknown>, context?: string): Promise<unknown>;
|
|
26
38
|
/** GET 请求:支持 404 时 fallback 到备用路径 */
|
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { loadConfig } from "./config.js";
|
|
9
9
|
import { handleApiError } from "./errors.js";
|
|
10
|
-
const cfg = loadConfig();
|
|
11
10
|
/** 最大重试次数 */
|
|
12
11
|
const MAX_RETRIES = 3;
|
|
13
12
|
/** 基延迟(ms) */
|
|
14
13
|
const BASE_DELAY = 1000;
|
|
14
|
+
/** 单次请求超时(ms) */
|
|
15
|
+
const REQUEST_TIMEOUT = 30_000;
|
|
15
16
|
/** 是否应该重试:网络异常、429、5xx */
|
|
16
17
|
function shouldRetry(res) {
|
|
17
18
|
if (!res)
|
|
@@ -22,17 +23,19 @@ function shouldRetry(res) {
|
|
|
22
23
|
function sleep(ms) {
|
|
23
24
|
return new Promise((r) => setTimeout(r, ms));
|
|
24
25
|
}
|
|
25
|
-
/** 执行 fetch
|
|
26
|
-
async function fetchWithRetry(url, options, context, attempt = 1) {
|
|
26
|
+
/** 执行 fetch 并自动重试。导出供外部 URL 请求(crawler/rss/import-url)复用 */
|
|
27
|
+
export async function fetchWithRetry(url, options, context, attempt = 1, timeoutMs = REQUEST_TIMEOUT) {
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
27
30
|
try {
|
|
28
|
-
const res = await fetch(url, options);
|
|
31
|
+
const res = await fetch(url, { ...options, signal: controller.signal });
|
|
29
32
|
if (shouldRetry(res) && attempt <= MAX_RETRIES) {
|
|
30
33
|
const delay = BASE_DELAY * Math.pow(2, attempt - 1);
|
|
31
34
|
// 重试日志仅在 DEBUG 环境变量设置时输出
|
|
32
35
|
if (process.env.DEBUG)
|
|
33
36
|
console.error(`[RETRY] ${context} 第 ${attempt} 次失败 (${res.status}),${delay}ms 后重试`);
|
|
34
37
|
await sleep(delay);
|
|
35
|
-
return fetchWithRetry(url, options, context, attempt + 1);
|
|
38
|
+
return fetchWithRetry(url, options, context, attempt + 1, timeoutMs);
|
|
36
39
|
}
|
|
37
40
|
return res;
|
|
38
41
|
}
|
|
@@ -42,10 +45,13 @@ async function fetchWithRetry(url, options, context, attempt = 1) {
|
|
|
42
45
|
if (process.env.DEBUG)
|
|
43
46
|
console.error(`[RETRY] ${context} 第 ${attempt} 次网络异常,${delay}ms 后重试`);
|
|
44
47
|
await sleep(delay);
|
|
45
|
-
return fetchWithRetry(url, options, context, attempt + 1);
|
|
48
|
+
return fetchWithRetry(url, options, context, attempt + 1, timeoutMs);
|
|
46
49
|
}
|
|
47
50
|
throw err;
|
|
48
51
|
}
|
|
52
|
+
finally {
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
}
|
|
49
55
|
}
|
|
50
56
|
/** API 调用结果的类型 guard:检查是否是 MCP error */
|
|
51
57
|
export function isErrorResult(result) {
|
|
@@ -69,11 +75,11 @@ function networkError(context, err) {
|
|
|
69
75
|
}
|
|
70
76
|
/** 标准请求头(Token 认证) */
|
|
71
77
|
function authHeaders(extra) {
|
|
72
|
-
return { "X-Auth-Token":
|
|
78
|
+
return { "X-Auth-Token": loadConfig().token, ...extra };
|
|
73
79
|
}
|
|
74
80
|
/** 构建完整 URL */
|
|
75
81
|
function buildUrl(path, params) {
|
|
76
|
-
const url = `${
|
|
82
|
+
const url = `${loadConfig().api_base}${path}`;
|
|
77
83
|
if (!params || Object.keys(params).length === 0)
|
|
78
84
|
return url;
|
|
79
85
|
const qs = new URLSearchParams(params);
|
|
@@ -97,7 +103,7 @@ export async function apiGet(path, params, context) {
|
|
|
97
103
|
export async function apiPost(path, body, context) {
|
|
98
104
|
const ctx = context ?? `POST ${path}`;
|
|
99
105
|
try {
|
|
100
|
-
const url = `${
|
|
106
|
+
const url = `${loadConfig().api_base}${path}`;
|
|
101
107
|
const res = await fetchWithRetry(url, {
|
|
102
108
|
method: "POST",
|
|
103
109
|
headers: authHeaders({ "Content-Type": "application/json" }),
|
|
@@ -115,7 +121,7 @@ export async function apiPost(path, body, context) {
|
|
|
115
121
|
export async function apiPut(path, body, context) {
|
|
116
122
|
const ctx = context ?? `PUT ${path}`;
|
|
117
123
|
try {
|
|
118
|
-
const url = `${
|
|
124
|
+
const url = `${loadConfig().api_base}${path}`;
|
|
119
125
|
const res = await fetchWithRetry(url, {
|
|
120
126
|
method: "PUT",
|
|
121
127
|
headers: authHeaders({ "Content-Type": "application/json" }),
|
|
@@ -133,7 +139,7 @@ export async function apiPut(path, body, context) {
|
|
|
133
139
|
export async function apiDelete(path, context) {
|
|
134
140
|
const ctx = context ?? `DELETE ${path}`;
|
|
135
141
|
try {
|
|
136
|
-
const url = `${
|
|
142
|
+
const url = `${loadConfig().api_base}${path}`;
|
|
137
143
|
const res = await fetchWithRetry(url, {
|
|
138
144
|
method: "DELETE",
|
|
139
145
|
headers: authHeaders(),
|
|
@@ -146,19 +152,50 @@ export async function apiDelete(path, context) {
|
|
|
146
152
|
return networkError(ctx, err);
|
|
147
153
|
}
|
|
148
154
|
}
|
|
155
|
+
/** 上传文件到语雀 CDN(FormData,不设 Content-Type 让浏览器自动设 boundary) */
|
|
156
|
+
export async function apiUpload(path, formData, context) {
|
|
157
|
+
const ctx = context ?? `UPLOAD ${path}`;
|
|
158
|
+
try {
|
|
159
|
+
const url = `${loadConfig().api_base}${path}`;
|
|
160
|
+
const res = await fetchWithRetry(url, {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: authHeaders(),
|
|
163
|
+
body: formData,
|
|
164
|
+
}, ctx);
|
|
165
|
+
if (!res.ok)
|
|
166
|
+
return handleApiError(res, ctx);
|
|
167
|
+
return res.json();
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
return networkError(ctx, err);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** 下载文件(返回 ArrayBuffer,用于导出文件下载) */
|
|
174
|
+
export async function apiDownload(path, params, context) {
|
|
175
|
+
const ctx = context ?? `DOWNLOAD ${path}`;
|
|
176
|
+
try {
|
|
177
|
+
const url = buildUrl(path, params);
|
|
178
|
+
const res = await fetchWithRetry(url, { headers: authHeaders() }, ctx);
|
|
179
|
+
if (!res.ok)
|
|
180
|
+
return handleApiError(res, ctx);
|
|
181
|
+
return res.arrayBuffer();
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
return networkError(ctx, err);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
149
187
|
/** POST 请求:支持 404 时 fallback 到备用路径(用于 user/group 端点自动切换) */
|
|
150
188
|
export async function apiPostWithFallback(path, fallbackPath, body, context) {
|
|
151
189
|
const ctx = context ?? `POST ${path}`;
|
|
152
190
|
try {
|
|
153
|
-
|
|
154
|
-
let url = `${cfg.api_base}${path}`;
|
|
191
|
+
let url = `${loadConfig().api_base}${path}`;
|
|
155
192
|
let res = await fetchWithRetry(url, {
|
|
156
193
|
method: "POST",
|
|
157
194
|
headers: authHeaders({ "Content-Type": "application/json" }),
|
|
158
195
|
body: JSON.stringify(body),
|
|
159
196
|
}, ctx);
|
|
160
197
|
if (res.status === 404) {
|
|
161
|
-
url = `${
|
|
198
|
+
url = `${loadConfig().api_base}${fallbackPath}`;
|
|
162
199
|
res = await fetchWithRetry(url, {
|
|
163
200
|
method: "POST",
|
|
164
201
|
headers: authHeaders({ "Content-Type": "application/json" }),
|
|
@@ -177,14 +214,13 @@ export async function apiPostWithFallback(path, fallbackPath, body, context) {
|
|
|
177
214
|
export async function apiGetWithFallback(path, fallbackPath, params, context) {
|
|
178
215
|
const ctx = context ?? `GET ${path}`;
|
|
179
216
|
try {
|
|
180
|
-
const cfg = loadConfig();
|
|
181
217
|
const qs = params && Object.keys(params).length > 0
|
|
182
218
|
? `?${new URLSearchParams(params)}`
|
|
183
219
|
: "";
|
|
184
|
-
let url = `${
|
|
220
|
+
let url = `${loadConfig().api_base}${path}${qs}`;
|
|
185
221
|
let res = await fetchWithRetry(url, { headers: authHeaders() }, ctx);
|
|
186
222
|
if (res.status === 404) {
|
|
187
|
-
url = `${
|
|
223
|
+
url = `${loadConfig().api_base}${fallbackPath}${qs}`;
|
|
188
224
|
res = await fetchWithRetry(url, { headers: authHeaders() }, ctx);
|
|
189
225
|
}
|
|
190
226
|
if (!res.ok)
|