@bsbofmusic/agent-reach-mcp 0.1.1 → 0.2.0

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.
Files changed (3) hide show
  1. package/README.md +246 -130
  2. package/index.js +165 -25
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,252 +1,368 @@
1
- Agent-Reach MCP (stdio)
1
+ ````md
2
+ # Agent-Reach MCP (stdio) — Full Mirror Edition
2
3
 
3
- A production-ready MCP (Model Context Protocol) stdio server for OpenCode / CC-Switch.
4
+ 这是一个给 **OpenCode / CC-Switch** 用的 MCPModel Context Protocolstdio 服务器。目标很明确:
4
5
 
5
- This package automatically installs and upgrades Agent-Reach on every tool call, ensuring you always run the latest version from GitHub main.
6
+ > 导入 MCP 后,就能 **完整复刻 Agent-Reach 的全部能力**(通过 `reach_exec` 透传任意子命令)
7
+ > ✅ 并且满足你要求的:**简单 / 快捷 / 强大 / 永远最新**(每次调用前自动更新 Agent-Reach)
6
8
 
7
- Designed for:
9
+ ---
8
10
 
9
- One-click activation via npx
11
+ ## 你将得到什么
10
12
 
11
- Always-latest behavior
13
+ 启用这个 MCP 后,OpenCode 会多出一组工具(tools):
12
14
 
13
- ✅ Self-healing Python environment
15
+ - `reach_ensure`:自愈环境 + 更新到最新 Agent-Reach
16
+ - `reach_doctor`:执行 `agent-reach doctor`
17
+ - `reach_read`:执行 `agent-reach read <url>`
18
+ - ✅ `reach_exec`:执行 **任意** `agent-reach <subcommand> ...args`(全功能复刻关键)
19
+ - `reach_list_commands`:执行 `agent-reach --help` 并解析出可用子命令列表(帮助模型“知道自己能干什么”)
14
20
 
15
- Reusable across machines
21
+ 你不需要让 OpenCode 记住安装过程;它每次调用都会先把 Agent-Reach 升级到最新再执行。
16
22
 
17
- ✅ Zero manual dependency management
23
+ ---
18
24
 
19
- Works with OpenCode + CC-Switch
25
+ ## 核心理念:MCP = “能力注入”,不是“记忆依赖”
20
26
 
21
- 🚀 What This Package Does
27
+ OpenCode 重启会“失忆”,但 MCP 是持久配置。
22
28
 
23
- When enabled as an MCP server:
29
+ - CC-Switch 负责:开关 / 配置 / 切换
30
+ - MCP server 负责:环境自愈 + 能力暴露
31
+ - Agent-Reach 负责:实际的多平台读/搜/配置能力
24
32
 
25
- Creates a dedicated Python virtual environment under your user cache directory
33
+ 所以正确心智模型是:
26
34
 
27
- Automatically upgrades Agent-Reach from:
35
+ > OpenCode 不需要记住怎么安装
36
+ > 它只要知道:有一个 MCP tool 叫 `reach_exec`,能运行 Agent-Reach 的任何命令
28
37
 
29
- https://github.com/Panniantong/agent-reach/archive/main.zip
38
+ ---
30
39
 
31
- Executes Agent-Reach CLI commands
40
+ ## 工作原理(非常重要)
32
41
 
33
- Returns structured output to the LLM via MCP
42
+ 每次你调用任意一个 tool(包括 `reach_exec`),MCP 都会:
34
43
 
35
- This guarantees:
44
+ 1) 在用户缓存目录创建/复用 Python venv
45
+ 2) 执行(永远最新策略):
46
+ - `pip install -U https://github.com/Panniantong/agent-reach/archive/main.zip`
47
+ 3) 然后运行对应的 `agent-reach ...` 命令
48
+ 4) 把 stdout/stderr 回传给 OpenCode
36
49
 
37
- 🔁 Always running latest Agent-Reach
50
+ ### venv 位置(默认)
51
+ - Windows:`%LOCALAPPDATA%\agent-reach-mcp\runtime\venv`
52
+ - macOS:`~/Library/Caches/agent-reach-mcp/runtime/venv`
53
+ - Linux:`~/.cache/agent-reach-mcp/runtime/venv`
38
54
 
39
- 🔧 No manual pip management
55
+ ---
40
56
 
41
- ♻️ Environment automatically recreated if broken
57
+ ## 环境要求
42
58
 
43
- 💡 Fully stateless from OpenCode’s perspective
59
+ - Node.js >= 18
60
+ - Python 3(系统 PATH 里能找到 `python`,Windows 也可用 `py`)
61
+ - 能访问 GitHub(用于拉 main.zip)
44
62
 
45
- 🧠 Architecture Overview
46
- OpenCode
47
-
48
- MCP (stdio)
49
-
50
- Node.js server (this package)
51
-
52
- Auto-managed Python venv (user cache)
53
-
54
- Latest Agent-Reach (pip install -U main.zip)
63
+ > 说明:某些平台能力(例如需要 cookie / 代理 / docker 的)属于 Agent-Reach 上游能力,你需要按 doctor 输出提示去配。
55
64
 
56
- The Python environment is stored in:
65
+ ---
57
66
 
58
- Windows:
67
+ ## 安装与本地验证(开发者)
59
68
 
60
- %LOCALAPPDATA%\agent-reach-mcp\runtime\venv
69
+ 在项目目录:
61
70
 
62
- macOS:
63
-
64
- ~/Library/Caches/agent-reach-mcp/runtime/venv
65
-
66
- Linux:
67
-
68
- ~/.cache/agent-reach-mcp/runtime/venv
69
- 📦 Installation (Local Development)
71
+ ```bash
70
72
  npm install
71
73
  node index.js
74
+ ````
72
75
 
73
- If the process starts without crashing, it is working correctly (stdio server waits for input).
76
+ stdio MCP server 启动后会等待输入,这属于正常现象。
74
77
 
75
- 📤 Publish to npm
78
+ ---
76
79
 
77
- 1️⃣ Edit package.json name:
80
+ ## 发布到 npm(手动,无 CI)
78
81
 
79
- "name": "@your-scope/agent-reach-mcp"
82
+ ### 1) 修改 package.json
80
83
 
81
- or without scope:
84
+ 确保:
82
85
 
83
- "name": "agent-reach-mcp-yourname"
86
+ * `name` 是你自己的包名(建议 scope 包:`@你的scope/agent-reach-mcp`)
87
+ * `version` 每次发布都 +1(npm 禁止同版本覆盖)
84
88
 
85
- 2️⃣ Publish:
89
+ ### 2) 发布
86
90
 
91
+ ```bash
87
92
  npm login
88
93
  npm publish --access public
89
- ⚙️ Using With CC-Switch
94
+ ```
95
+
96
+ ---
97
+
98
+ ## CC-Switch / OpenCode 配置方式(你要的“一键复活”)
90
99
 
91
- Add this configuration:
100
+ CC-Switch 新建 MCP 配置:
92
101
 
102
+ ```json
93
103
  {
94
104
  "command": "npx",
95
105
  "args": ["-y", "@your-scope/agent-reach-mcp@latest"],
96
106
  "type": "stdio"
97
107
  }
108
+ ```
109
+
110
+ 然后重启 OpenCode。
111
+
112
+ > 之后只要你更新 npm 包版本,用户侧 `@latest` 会在下次启动时拉到新版本(通常如此;依赖 npx 缓存策略)。
113
+
114
+ ---
115
+
116
+ # Tools 使用说明(强烈建议认真看)
117
+
118
+ ## 0) 第一次使用推荐流程
98
119
 
99
- Then:
120
+ 1. `reach_ensure`(确保环境就绪)
121
+ 2. `reach_doctor`(看哪些渠道通、怎么配)
122
+ 3. `reach_list_commands`(让 OpenCode 知道有哪些子命令)
123
+ 4. 用 `reach_exec` 跑你要的平台命令
100
124
 
101
- Enable MCP
125
+ ---
102
126
 
103
- Restart OpenCode
127
+ ## 1) reach_ensure
104
128
 
105
- Tools will be automatically available
129
+ 用途:只做环境准备 + 更新。
106
130
 
107
- 🛠 Available MCP Tools
108
- 1️⃣ reach_ensure
131
+ 它等价于:
109
132
 
110
- Ensures:
133
+ * 创建 venv
134
+ * pip 工具链更新
135
+ * pip 安装/升级 Agent-Reach main.zip
111
136
 
112
- Python venv exists
137
+ 适用场景:
113
138
 
114
- Agent-Reach upgraded to latest
139
+ * 第一次启用 MCP
140
+ * 你怀疑环境坏了
141
+ * 你想强制刷新到最新
115
142
 
116
- Returns installation logs.
143
+ ---
117
144
 
118
- 2️⃣ reach_doctor
145
+ ## 2) reach_doctor
119
146
 
120
- Runs:
147
+ 用途:诊断所有渠道状态。
121
148
 
149
+ 它会执行:
150
+
151
+ ```bash
122
152
  agent-reach doctor
153
+ ```
154
+
155
+ 适用场景:
156
+
157
+ * 为什么某个平台读不到?
158
+ * 需要 cookie 吗?
159
+ * 需要代理吗?
160
+ * 需要 docker 吗?
161
+
162
+ doctor 是 Agent-Reach 官方推荐的“总检命令”。
163
+
164
+ ---
123
165
 
124
- Automatically upgrades before execution.
166
+ ## 3) reach_read
125
167
 
126
- 3️⃣ reach_read
168
+ 用途:读一个链接(网页/视频/仓库等,交给 Agent-Reach 判断)。
127
169
 
128
- Runs:
170
+ 它会执行:
129
171
 
172
+ ```bash
130
173
  agent-reach read <url>
174
+ ```
131
175
 
132
- Automatically upgrades before execution.
176
+ 输入:
133
177
 
134
- Example input:
178
+ ```json
179
+ { "url": "https://example.com" }
180
+ ```
135
181
 
136
- {
137
- "url": "https://example.com"
138
- }
139
- 🔄 Always Latest Behavior
182
+ ### 常见例子
183
+
184
+ * 读普通网页:
185
+
186
+ ```json
187
+ { "url": "https://example.com" }
188
+ ```
189
+
190
+ * 获取 YouTube 教程字幕/内容(Agent-Reach README 的示例就是 read YouTube):
140
191
 
141
- This package intentionally prioritizes:
192
+ ```json
193
+ { "url": "https://youtube.com/watch?v=xxx" }
194
+ ```
142
195
 
143
- Latest > Stability
196
+ ---
144
197
 
145
- On every tool call:
198
+ ## 4) reach_exec ✅(全功能复刻关键)
146
199
 
147
- pip install -U https://github.com/Panniantong/agent-reach/archive/main.zip
200
+ 这是“完整复刻”的核心。它可以透传执行任何 Agent-Reach 子命令:
148
201
 
149
- This ensures:
202
+ 等价于:
150
203
 
151
- New features immediately available
204
+ ```bash
205
+ agent-reach <subcommand> ...args
206
+ ```
152
207
 
153
- Bug fixes applied instantly
208
+ 输入格式:
154
209
 
155
- No stale installs
210
+ ```json
211
+ {
212
+ "subcommand": "doctor",
213
+ "args": [],
214
+ "timeoutMs": 600000
215
+ }
216
+ ```
217
+
218
+ ### 4.1 安装(对应 Agent-Reach README 的安装方式)
219
+
220
+ > 虽然 MCP 自己会安装 main.zip,但 Agent-Reach 还有 “install” 用来装系统依赖/配置渠道(很重要)
221
+
222
+ * 全自动安装(默认推荐):
156
223
 
157
- ⚠️ If upstream breaks, you get upstream behavior immediately (by design).
224
+ ```json
225
+ { "subcommand": "install", "args": ["--env=auto"] }
226
+ ```
158
227
 
159
- 🛡 Stability Notes
228
+ * 安全模式(不自动改系统,只提示缺什么):
160
229
 
161
- This MCP server:
230
+ ```json
231
+ { "subcommand": "install", "args": ["--env=auto", "--safe"] }
232
+ ```
162
233
 
163
- Automatically recreates venv if deleted
234
+ * Dry Run(预览将做什么,不执行):
164
235
 
165
- Upgrades pip tooling
236
+ ```json
237
+ { "subcommand": "install", "args": ["--env=auto", "--dry-run"] }
238
+ ```
239
+
240
+ ### 4.2 Twitter/X:配置 cookie + 搜索推文(来自官方 README FAQ)
241
+
242
+ * 配置 twitter cookie(你需要把 Cookie-Editor 导出的 cookie 字符串填进去):
243
+
244
+ ```json
245
+ {
246
+ "subcommand": "configure",
247
+ "args": ["twitter-cookies", "your_cookies_here"]
248
+ }
249
+ ```
166
250
 
167
- Fallbacks to python -m agent_reach if console script missing
251
+ * 搜索推文:
168
252
 
169
- Captures stdout/stderr
253
+ ```json
254
+ {
255
+ "subcommand": "search-twitter",
256
+ "args": ["关键词"]
257
+ }
258
+ ```
170
259
 
171
- Handles timeouts
260
+ ### 4.3 代理配置(解决 Reddit 数据中心 IP 403 等,来自官方 README FAQ)
172
261
 
173
- Returns structured MCP errors
262
+ ```json
263
+ {
264
+ "subcommand": "configure",
265
+ "args": ["proxy", "http://user:pass@ip:port"]
266
+ }
267
+ ```
174
268
 
175
- 🧩 Requirements
269
+ ### 4.4 小红书:搜索(来自官方 README FAQ)
176
270
 
177
- Node.js >= 18
271
+ ```json
272
+ {
273
+ "subcommand": "search-xhs",
274
+ "args": ["关键词"]
275
+ }
276
+ ```
178
277
 
179
- Python 3 available in PATH
278
+ > 小红书往往需要 docker / MCP 服务配套,先跑 `reach_doctor` 看提示。
180
279
 
181
- pip available
280
+ ### 4.5 读任意链接(等价 reach_read,但更通用)
182
281
 
183
- Internet connection
282
+ ```json
283
+ {
284
+ "subcommand": "read",
285
+ "args": ["https://github.com/Panniantong/Agent-Reach"]
286
+ }
287
+ ```
184
288
 
185
- 🔍 Why Use MCP Instead of Direct CLI?
289
+ ---
186
290
 
187
- Because:
291
+ ## 5) reach_list_commands(推荐让 OpenCode“自发现”能力)
188
292
 
189
- OpenCode forgets memory after restart
293
+ 用途:列出当前安装的 Agent-Reach 版本支持哪些子命令。
190
294
 
191
- MCP ensures environment is rebuilt automatically
295
+ 为什么需要它?
192
296
 
193
- Tools become persistent capabilities
297
+ * Agent-Reach 在快速迭代
298
+ * 你坚持“永远最新”
299
+ * 所以命令集合可能随时变化
300
+ * `reach_list_commands` 能让 OpenCode 在**没有记忆**的情况下,仍然准确知道有哪些子命令
194
301
 
195
- One-click activation via CC-Switch
302
+ 推荐用法:
196
303
 
197
- No manual installation per machine
304
+ 1. 启用 MCP 后先跑一次 `reach_list_commands`
305
+ 2. OpenCode 会得到一份命令列表
306
+ 3. 后续就用 `reach_exec` 调用列表里的命令
198
307
 
199
- 🏗 Design Philosophy
308
+ ---
200
309
 
201
- Minimal
310
+ # 推荐验证计划(你可以照抄给 OpenCode 执行)
202
311
 
203
- Stateless
312
+ ## Phase 1:基础健康
204
313
 
205
- Self-healing
314
+ 1. `reach_ensure`
315
+ 2. `reach_doctor`
316
+ 3. `reach_read` 读一个普通网页
206
317
 
207
- Always current
318
+ 预期:能成功安装/更新 + doctor 输出渠道状态 + read 有返回内容
208
319
 
209
- Reusable anywhere via npx
320
+ ## Phase 2:全能力复刻
210
321
 
211
- 📄 License
322
+ 1. `reach_list_commands`
323
+ 2. 从列表里挑 3 个子命令用 `reach_exec` 跑(例如 install / configure / search-* / read)
212
324
 
213
- MIT
325
+ ## Phase 3:按需补齐配置
214
326
 
215
- 💡 Example OpenCode Prompt
327
+ * Twitter 需要 cookie:用 `configure twitter-cookies ...`
328
+ * 服务器访问 Reddit/B站可能需要代理:用 `configure proxy ...`
329
+ * 小红书可能需要 docker:按 doctor 指引
216
330
 
217
- After enabling MCP:
331
+ ---
218
332
 
219
- Use reach_read to analyze https://example.com
333
+ # 常见问题(FAQ)
220
334
 
221
- Or:
335
+ ## Q1: 为什么我明明启用了 MCP,但某个平台用不了?
222
336
 
223
- Run reach_doctor and summarize health status
224
- 🔮 Future Extensions (Optional)
337
+ A:平台能力取决于 Agent-Reach 的“渠道状态”,不是 MCP 本身。跑 `reach_doctor` 看缺什么(cookie、代理、docker、登录等)。
225
338
 
226
- Possible future improvements:
339
+ ## Q2: 为什么每次调用都要更新?会不会慢?
227
340
 
228
- Version pin mode
341
+ A:这是你选择的策略:Always-Latest。首次或上游更新时会慢,但能保证永远最新。如果以后你要“快且稳”,可以改成“每日一次更新/或失败回滚”。
229
342
 
230
- Automatic rollback on failure
343
+ ## Q3: 如何避免 OpenCode 重启后不知道有哪些命令?
231
344
 
232
- Dependency preflight checks
345
+ A:用 `reach_list_commands`。这是为“无记忆模型”专门做的能力自发现入口。
233
346
 
234
- Docker isolation mode
347
+ ## Q4: 我想要更友好的工具(比如 reach_search_twitter / reach_search_xhs)要不要做?
235
348
 
236
- Logging level control
349
+ A:可以做,但不是必须。`reach_exec` 已经完整复刻;单独工具只是“更强的 schema 引导”。
237
350
 
238
- Configurable update strategy
351
+ ---
239
352
 
240
- Summary
353
+ # License
241
354
 
242
- You now have:
355
+ MIT
243
356
 
244
- A reusable MCP server
357
+ ```
245
358
 
246
- Publishable npm package
359
+ ---
247
360
 
248
- Always-latest Agent-Reach integration
361
+ ### 我对齐了哪些“真实命令名”(可核对)
362
+ 这些命令名来自 Agent-Reach 最新 README 中的安装方式与 FAQ:`configure twitter-cookies`、`search-twitter`、`configure proxy`、`search-xhs`、`read`、`doctor`、`install --env=auto/--safe/--dry-run`。 :contentReference[oaicite:1]{index=1}
249
363
 
250
- Zero manual maintenance workflow
364
+ 如果你愿意,我还可以再给你一个**更强的 README 增强版**:把 `reach_list_commands` 的输出示例也写进去,并给一份“OpenCode Prompt 模板”(让模型自动:list_commands → 选命令 → exec)。
365
+ ::contentReference[oaicite:2]{index=2}
366
+ ```
251
367
 
252
- One-click OpenCode integration
368
+ [1]: https://github.com/Panniantong/Agent-Reach "GitHub - Panniantong/Agent-Reach: Give your AI agent eyes to see the entire internet. Read & search Twitter, Reddit, YouTube, GitHub, Bilibili, XiaoHongShu — one CLI, zero API fees."
package/index.js CHANGED
@@ -12,12 +12,16 @@ import {
12
12
  } from "@modelcontextprotocol/sdk/types.js";
13
13
 
14
14
  /**
15
- * A minimal, robust MCP stdio server that:
16
- * - Creates a Python venv under user cache dir
17
- * - On EVERY tool call, upgrades Agent-Reach from GitHub main.zip
18
- * - Executes `agent-reach` via the venv (fallback to python -m ...)
15
+ * Agent-Reach MCP (stdio) Full capability proxy
19
16
  *
20
- * NOTE: This favors "latest" over strict stability, per your requirement.
17
+ * Goals:
18
+ * - Import MCP => "full Agent-Reach" via reach_exec (generic passthrough)
19
+ * - Still provide ergonomic basics: ensure / doctor / read
20
+ * - On EVERY tool call: auto-upgrade Agent-Reach from GitHub main.zip (always latest)
21
+ *
22
+ * Notes:
23
+ * - This favors "latest" over strict stability, per your requirement.
24
+ * - It manages its own python venv in user cache.
21
25
  */
22
26
 
23
27
  function isWindows() {
@@ -101,8 +105,6 @@ async function createVenvIfMissing(root) {
101
105
 
102
106
  if (existsSync(pythonExe)) return;
103
107
 
104
- // Try creating venv using system python.
105
- // Windows: python, then py -3
106
108
  const candidates = isWindows()
107
109
  ? [
108
110
  { cmd: "python", args: ["-m", "venv", venvDir] },
@@ -120,7 +122,9 @@ async function createVenvIfMissing(root) {
120
122
  }
121
123
 
122
124
  if (!existsSync(pythonExe)) {
123
- const tried = candidates.map((c) => `${c.cmd} ${c.args.join(" ")}`).join(" | ");
125
+ const tried = candidates
126
+ .map((c) => `${c.cmd} ${c.args.join(" ")}`)
127
+ .join(" | ");
124
128
  const err = last?.stderr || "";
125
129
  throw new Error(
126
130
  `Failed to create Python venv.\nTried: ${tried}\nLast stderr:\n${err}`
@@ -133,15 +137,19 @@ async function ensureLatestAgentReach(root) {
133
137
  const { pythonExe, agentReachExe } = venvPaths(root);
134
138
 
135
139
  // Upgrade pip tooling (best effort)
136
- await run(pythonExe, ["-m", "pip", "install", "-U", "pip", "setuptools", "wheel"], {
137
- timeoutMs: 5 * 60 * 1000,
138
- });
140
+ await run(
141
+ pythonExe,
142
+ ["-m", "pip", "install", "-U", "pip", "setuptools", "wheel"],
143
+ { timeoutMs: 5 * 60 * 1000 }
144
+ );
139
145
 
140
- // Your "always latest" requirement:
146
+ // ALWAYS LATEST (main branch zip)
141
147
  const pkgUrl = "https://github.com/Panniantong/agent-reach/archive/main.zip";
142
- const installRes = await run(pythonExe, ["-m", "pip", "install", "-U", pkgUrl], {
143
- timeoutMs: 10 * 60 * 1000,
144
- });
148
+ const installRes = await run(
149
+ pythonExe,
150
+ ["-m", "pip", "install", "-U", pkgUrl],
151
+ { timeoutMs: 10 * 60 * 1000 }
152
+ );
145
153
 
146
154
  let log = `pip install -U ${pkgUrl}\nexit=${installRes.code}\n`;
147
155
  if (installRes.stdout.trim()) log += `stdout:\n${installRes.stdout}\n`;
@@ -158,17 +166,19 @@ async function ensureLatestAgentReach(root) {
158
166
  return { pythonExe, agentReachExe, ensureLog: log };
159
167
  }
160
168
 
161
- async function runAgentReach(root, args) {
162
- const { pythonExe, agentReachExe, ensureLog } = await ensureLatestAgentReach(root);
169
+ async function runAgentReach(root, args, extraOpts = {}) {
170
+ const { pythonExe, agentReachExe, ensureLog } = await ensureLatestAgentReach(
171
+ root
172
+ );
173
+
174
+ const timeoutMs = extraOpts.timeoutMs ?? 10 * 60 * 1000;
163
175
 
164
176
  let execRes;
165
177
  if (existsSync(agentReachExe)) {
166
- execRes = await run(agentReachExe, args, { timeoutMs: 10 * 60 * 1000 });
178
+ execRes = await run(agentReachExe, args, { timeoutMs });
167
179
  } else {
168
- // Fallback: module import. (May vary; best effort.)
169
- execRes = await run(pythonExe, ["-m", "agent_reach", ...args], {
170
- timeoutMs: 10 * 60 * 1000,
171
- });
180
+ // Fallback: module import (best effort)
181
+ execRes = await run(pythonExe, ["-m", "agent_reach", ...args], { timeoutMs });
172
182
  }
173
183
 
174
184
  let out = `# ensure_latest\n${ensureLog}\n`;
@@ -179,13 +189,55 @@ async function runAgentReach(root, args) {
179
189
  return { out, code: execRes.code };
180
190
  }
181
191
 
192
+ /**
193
+ * Best-effort command listing:
194
+ * - Try: `agent-reach --help`
195
+ * - Parse lines that look like subcommands.
196
+ */
197
+ function parseCommandsFromHelp(helpText) {
198
+ const lines = helpText.split(/\r?\n/);
199
+ const commands = new Set();
200
+
201
+ // Common patterns:
202
+ // "Commands:" section in Click/Typer
203
+ // indented: "read ..." or "doctor ..."
204
+ let inCommands = false;
205
+ for (const raw of lines) {
206
+ const line = raw.trimEnd();
207
+
208
+ if (/^Commands:\s*$/i.test(line.trim())) {
209
+ inCommands = true;
210
+ continue;
211
+ }
212
+ if (inCommands && line.trim() === "") {
213
+ // stop after an empty line (often ends section)
214
+ // (but some help formats include blank lines; be conservative)
215
+ // We'll not hard-stop here.
216
+ }
217
+
218
+ if (inCommands) {
219
+ const m = line.match(/^\s{0,4}([a-zA-Z0-9][a-zA-Z0-9_-]+)\s{2,}.*$/);
220
+ if (m?.[1]) commands.add(m[1]);
221
+ } else {
222
+ // Some CLIs list commands without explicit "Commands:" header
223
+ const m = line.match(/^\s{0,4}([a-zA-Z0-9][a-zA-Z0-9_-]+)\s{2,}.*$/);
224
+ if (m?.[1] && !/^(Usage|Options|Arguments):?$/i.test(m[1])) {
225
+ // Only add if it doesn't look like a header.
226
+ // But to avoid noise, we won't add outside commands section.
227
+ }
228
+ }
229
+ }
230
+
231
+ return Array.from(commands).sort();
232
+ }
233
+
182
234
  function text(content) {
183
235
  return [{ type: "text", text: content }];
184
236
  }
185
237
 
186
238
  async function main() {
187
239
  const server = new Server(
188
- { name: "agent-reach-mcp", version: "0.1.0" },
240
+ { name: "agent-reach-mcp", version: "0.2.0" },
189
241
  { capabilities: { tools: {} } }
190
242
  );
191
243
 
@@ -218,6 +270,38 @@ async function main() {
218
270
  required: ["url"],
219
271
  },
220
272
  },
273
+ {
274
+ name: "reach_exec",
275
+ description:
276
+ "Execute ANY Agent-Reach subcommand. Example: {subcommand:'search-xhs', args:['关键词']} or {subcommand:'read', args:['https://...']}. Auto-updates Agent-Reach first.",
277
+ inputSchema: {
278
+ type: "object",
279
+ properties: {
280
+ subcommand: {
281
+ type: "string",
282
+ description:
283
+ "Agent-Reach subcommand name, e.g. 'search-xhs', 'read', 'doctor', etc.",
284
+ },
285
+ args: {
286
+ type: "array",
287
+ items: { type: "string" },
288
+ description: "Arguments passed to the subcommand",
289
+ },
290
+ timeoutMs: {
291
+ type: "number",
292
+ description:
293
+ "Optional timeout in milliseconds for the command execution (default ~10 minutes).",
294
+ },
295
+ },
296
+ required: ["subcommand"],
297
+ },
298
+ },
299
+ {
300
+ name: "reach_list_commands",
301
+ description:
302
+ "List available Agent-Reach subcommands by parsing `agent-reach --help`. Auto-updates Agent-Reach first.",
303
+ inputSchema: { type: "object", properties: {} },
304
+ },
221
305
  ],
222
306
  };
223
307
  });
@@ -240,12 +324,68 @@ async function main() {
240
324
  if (name === "reach_read") {
241
325
  const url = String(input.url ?? "");
242
326
  if (!url || !/^https?:\/\//i.test(url)) {
243
- return { content: text("Invalid url. Must start with http(s)://"), isError: true };
327
+ return {
328
+ content: text("Invalid url. Must start with http(s)://"),
329
+ isError: true,
330
+ };
244
331
  }
245
- const { out, code } = await runAgentReach(runtimeRoot, ["read", url]);
332
+ const { out, code } = await runAgentReach(runtimeRoot, ["read", url], {
333
+ timeoutMs: 10 * 60 * 1000,
334
+ });
246
335
  return { content: text(out), isError: code !== 0 };
247
336
  }
248
337
 
338
+ if (name === "reach_exec") {
339
+ const subcommand = String(input.subcommand ?? "").trim();
340
+ const args = Array.isArray(input.args) ? input.args.map(String) : [];
341
+ const timeoutMs =
342
+ typeof input.timeoutMs === "number" && input.timeoutMs > 0
343
+ ? Math.floor(input.timeoutMs)
344
+ : 10 * 60 * 1000;
345
+
346
+ if (!subcommand) {
347
+ return {
348
+ content: text("Missing required field: subcommand"),
349
+ isError: true,
350
+ };
351
+ }
352
+
353
+ // Minimal safety: prevent command injection by disallowing whitespace in subcommand
354
+ // (args are passed as array so they are safe).
355
+ if (/\s/.test(subcommand)) {
356
+ return {
357
+ content: text("Invalid subcommand (contains whitespace)."),
358
+ isError: true,
359
+ };
360
+ }
361
+
362
+ const { out, code } = await runAgentReach(
363
+ runtimeRoot,
364
+ [subcommand, ...args],
365
+ { timeoutMs }
366
+ );
367
+ return { content: text(out), isError: code !== 0 };
368
+ }
369
+
370
+ if (name === "reach_list_commands") {
371
+ const { out, code } = await runAgentReach(runtimeRoot, ["--help"], {
372
+ timeoutMs: 2 * 60 * 1000,
373
+ });
374
+
375
+ // Parse only from stdout/stderr combined output we already include.
376
+ const helpText = out;
377
+ const commands = parseCommandsFromHelp(helpText);
378
+
379
+ const payload = [
380
+ "# parsed_commands",
381
+ commands.length ? commands.join("\n") : "(no commands parsed; see help output below)",
382
+ "",
383
+ out,
384
+ ].join("\n");
385
+
386
+ return { content: text(payload), isError: code !== 0 };
387
+ }
388
+
249
389
  return { content: text(`Unknown tool: ${name}`), isError: true };
250
390
  } catch (e) {
251
391
  const msg = e?.stack || e?.message || String(e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsbofmusic/agent-reach-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "MCP stdio server that auto-installs/updates Agent-Reach and exposes reach_* tools.",
5
5
  "license": "MIT",
6
6
  "type": "module",