beacon-mfg-mcp 1.12.8 → 1.12.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/MCP_HTTP.md CHANGED
@@ -163,6 +163,28 @@ agent 必须据此决定「答有 / 答无 / 答未覆盖」——只念 `total_
163
163
  > 同批实测的对照组:`search_vendors("盈科", city="苏州")` → 前三名正是盈科在苏州/太仓/常熟的
164
164
  > 三家分所,`gb=7231`。**排序本身没坏**,坏在拿不到货时用单字凑数。
165
165
 
166
+ ### 4.4 入参别名与未知参数(1.12.9)
167
+
168
+ 工具的参数名是**固定**的(`search_vendors` 用 `query`,不是 `keyword`)。为了让「差一点」
169
+ 的调用不至于变成一次静默的错误结果,入口做了两件事:
170
+
171
+ | 情形 | 行为 | 回执里能看到 |
172
+ |---|---|---|
173
+ | 近义词(`keyword`/`q`/`term`/`name` → `query`;`token`/`vendor_id` → `id`;`query` → `demand_text`…) | 自动归一,调用正常执行 | `arg_aliases: {"keyword":"query"}` |
174
+ | 归一不了的陌生参数名,**且该工具的主参数为空** | 拒绝执行:`unknown_argument` + 点名正确参数名 | 不返回任何记录,**不消耗额度** |
175
+ | 陌生参数名,但主参数已给 | 放行,结果有效 | `unknown_args: [...]` + `hint` 说明「已忽略」 |
176
+ | 正式名与别名同时给 | 正式名优先,别名丢弃 | `arg_aliases` 为空 |
177
+
178
+ > **为什么必须拦。** 2026-11-14 实测:按 `keyword` 调 `search_vendors`(schema 里叫 `query`),
179
+ > 服务端把未知参数静默丢掉,只剩 `city` 成立 → 返回 3000 条与关键词毫无关系的记录
180
+ > (洗衣店 / 装饰加工厂 / 私厨),`terms_hit: 0`,**既无 error 也无 warning**。
181
+ > 这种「看起来成功」的错比 400 更坏:agent 会把「小白熊洗衣」当成素粉店念给用户。
182
+ >
183
+ > 同批还修了一个更隐蔽的:查不到的 token 走的是上游纯文本 `Not found`(404),
184
+ > `resp.json()` 一抛就被当成「工具崩了」报 `-32603 tool_failed`(线上原话是
185
+ > `Unexpected token 'N', "Not found" is not valid JSON`)。现在统一翻成
186
+ > `{"error":"not_found", "hint":"...用 search_vendors 重新取 token..."}`。
187
+
166
188
  ## 5. 验收清单
167
189
 
168
190
  ```bash
package/README.md CHANGED
@@ -46,6 +46,11 @@
46
46
 
47
47
  所有返回都是 JSON 文本。找不到时返回 `{ "error": ... }` 或 `has_card:false`,不会抛协议错。
48
48
 
49
+ **参数名写了近义词也能用**(`keyword`/`q`/`term`/`name` → `query`,`token`/`vendor_id` → `id`,
50
+ `query` → `demand_text`…),回执里用 `arg_aliases` 说明被改写成了什么。远程端点还多一层:
51
+ 归一不了的陌生参数名,如果该工具的主参数为空,直接回 `unknown_argument` 并点名正确参数 ——
52
+ **不会**退化成「只按剩下的条件检索」然后给你一堆无关记录。见 `MCP_HTTP.md` §4.4。
53
+
49
54
  ### `linked_entity`:招牌名 ≠ 工商登记名
50
55
 
51
56
  名录里的 `company` 是**门店招牌/经营名**(来自地图、黄页、展会),而工商登记名往往更长、
@@ -1,58 +1,58 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';
3
- // beacon-mfg-mcp 启动器:本 MCP 服务是纯 Python 标准库实现,这里只负责
4
- // ① 在用户机器上找到 Python 解释器并拉起 server.py(stdio 协议)
5
- // ② 顺手踢一个**独立进程**去做节流的自更新检查
6
- const { spawnSync, spawn } = require('child_process');
7
- const path = require('path');
8
-
9
- const serverPy = path.join(__dirname, '..', 'server.py');
10
- const selfUpdateJs = path.join(__dirname, 'selfupdate.js');
11
-
12
- // 踢起自更新检查:detached + unref,主进程既不等它也不被它拖住。
13
- // 为什么必须是独立进程而不是本进程里的 async 函数:
14
- // 下面 spawnSync 是**同步阻塞**的,会冻结整个事件循环 —— async 的自更新
15
- // 在 server 运行期间推不动,等它能跑时本进程已经 process.exit() 了,
16
- // 更新逻辑永远跑不完。独立进程不受此影响。
17
- // 失败、离线、没权限一律静默:它只影响「下次启动是不是最新版」。
18
- try {
19
- const u = spawn(process.execPath, [selfUpdateJs], {
20
- stdio: 'ignore',
21
- detached: true,
22
- windowsHide: true,
23
- });
24
- u.on('error', () => { /* 没有 node?不可能,自身就是 node 起的 */ });
25
- u.unref();
26
- } catch (_) { /* 自更新是加分项,绝不能影响 MCP 启动 */ }
27
-
28
- // 探测可用的 Python 解释器(优先 python3,兼容 python / py)
29
- const candidates = ['python3', 'python', 'py'];
30
- let chosen = null;
31
- for (const c of candidates) {
32
- const r = spawnSync(c, ['--version'], { stdio: 'ignore' });
33
- if (r.status === 0) {
34
- chosen = c;
35
- break;
36
- }
37
- }
38
- if (!chosen) {
39
- process.stderr.write(
40
- '[beacon-mfg-mcp] 未找到 Python 3。请先安装 Python 3.8+ 并加入 PATH,再重试。\n'
41
- );
42
- process.exit(1);
43
- }
44
-
45
- const child = spawnSync(chosen, [serverPy, ...process.argv.slice(2)], {
46
- stdio: 'inherit',
47
- // 显式锁定 UTF-8:中文 Windows 上 python 默认按 GBK 写 stdout,
48
- // MCP 客户端按 UTF-8 解码会乱码。从启动器层面保证子进程用 UTF-8 输出。
49
- env: Object.assign({}, process.env, {
50
- PYTHONUTF8: '1',
51
- PYTHONIOENCODING: 'utf-8',
52
- // 不写 __pycache__:本服务由 npm 全局安装、跑在任意用户机器上,
53
- // 字节码会落进包目录(或只读盘上 EPERM 报错)。服务是短命进程,
54
- // 字节码缓存无收益,关掉更干净。
55
- PYTHONDONTWRITEBYTECODE: '1',
56
- }),
57
- });
58
- process.exit(child.status === null ? 1 : child.status);
2
+ 'use strict';
3
+ // beacon-mfg-mcp 启动器:本 MCP 服务是纯 Python 标准库实现,这里只负责
4
+ // ① 在用户机器上找到 Python 解释器并拉起 server.py(stdio 协议)
5
+ // ② 顺手踢一个**独立进程**去做节流的自更新检查
6
+ const { spawnSync, spawn } = require('child_process');
7
+ const path = require('path');
8
+
9
+ const serverPy = path.join(__dirname, '..', 'server.py');
10
+ const selfUpdateJs = path.join(__dirname, 'selfupdate.js');
11
+
12
+ // 踢起自更新检查:detached + unref,主进程既不等它也不被它拖住。
13
+ // 为什么必须是独立进程而不是本进程里的 async 函数:
14
+ // 下面 spawnSync 是**同步阻塞**的,会冻结整个事件循环 —— async 的自更新
15
+ // 在 server 运行期间推不动,等它能跑时本进程已经 process.exit() 了,
16
+ // 更新逻辑永远跑不完。独立进程不受此影响。
17
+ // 失败、离线、没权限一律静默:它只影响「下次启动是不是最新版」。
18
+ try {
19
+ const u = spawn(process.execPath, [selfUpdateJs], {
20
+ stdio: 'ignore',
21
+ detached: true,
22
+ windowsHide: true,
23
+ });
24
+ u.on('error', () => { /* 没有 node?不可能,自身就是 node 起的 */ });
25
+ u.unref();
26
+ } catch (_) { /* 自更新是加分项,绝不能影响 MCP 启动 */ }
27
+
28
+ // 探测可用的 Python 解释器(优先 python3,兼容 python / py)
29
+ const candidates = ['python3', 'python', 'py'];
30
+ let chosen = null;
31
+ for (const c of candidates) {
32
+ const r = spawnSync(c, ['--version'], { stdio: 'ignore' });
33
+ if (r.status === 0) {
34
+ chosen = c;
35
+ break;
36
+ }
37
+ }
38
+ if (!chosen) {
39
+ process.stderr.write(
40
+ '[beacon-mfg-mcp] 未找到 Python 3。请先安装 Python 3.8+ 并加入 PATH,再重试。\n'
41
+ );
42
+ process.exit(1);
43
+ }
44
+
45
+ const child = spawnSync(chosen, [serverPy, ...process.argv.slice(2)], {
46
+ stdio: 'inherit',
47
+ // 显式锁定 UTF-8:中文 Windows 上 python 默认按 GBK 写 stdout,
48
+ // MCP 客户端按 UTF-8 解码会乱码。从启动器层面保证子进程用 UTF-8 输出。
49
+ env: Object.assign({}, process.env, {
50
+ PYTHONUTF8: '1',
51
+ PYTHONIOENCODING: 'utf-8',
52
+ // 不写 __pycache__:本服务由 npm 全局安装、跑在任意用户机器上,
53
+ // 字节码会落进包目录(或只读盘上 EPERM 报错)。服务是短命进程,
54
+ // 字节码缓存无收益,关掉更干净。
55
+ PYTHONDONTWRITEBYTECODE: '1',
56
+ }),
57
+ });
58
+ process.exit(child.status === null ? 1 : child.status);