beacon-mfg-mcp 1.12.4 → 1.12.6

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/MCP_HTTP.md +178 -0
  2. package/README.md +13 -1
  3. package/package.json +3 -2
package/MCP_HTTP.md ADDED
@@ -0,0 +1,178 @@
1
+ # 远程 MCP 端点(POST /mcp)
2
+
3
+ > 2026-11-13 上线。给「不想装 Node / 不会装 Node」的人一条接入路径。
4
+
5
+ ## 1. 为什么要有它
6
+
7
+ 在此之前分发只有 `packages.stdio` 一条:想用的人必须
8
+
9
+ ```
10
+ node ≥ 18 → npx beacon-mfg-mcp → 客户端里配一个本地进程
11
+ ```
12
+
13
+ 而 Registry 首页 100 条里 **93 条**声明了 `remotes`。我们不但没有,还在用最重的那条路径。
14
+
15
+ 结果是可预测的:真正想查供应商的人(采购、外贸、货代)不会为了查 20 家公司去装开发者环境。
16
+ 2026-11-13 的暴露面审计显示「注册后三天,真实外部使用 0」—— 不是流量问题,是**接入路径只有一条,
17
+ 而且没人走**。
18
+
19
+ 远程端点把门槛从「装 Node 环境」降到「粘一个链接」。它修的是路径,不是流量。
20
+
21
+ ## 2. 端点契约
22
+
23
+ ```
24
+ POST https://beacon-mfg.pages.dev/mcp
25
+ Content-Type: application/json
26
+ ```
27
+
28
+ 传输是 **streamable-http 的无状态子集**:
29
+
30
+ | 项 | 实现 |
31
+ |---|---|
32
+ | 传输 | 单次 POST ↔ 单个 JSON 响应(`application/json`,不升级 SSE) |
33
+ | 会话 | **无** `Mcp-Session-Id`。每请求自带完整上下文 |
34
+ | `GET /mcp` | 405 + `Allow: POST`(无状态实现没有服务端推送流,不假装支持) |
35
+ | 批量 | 不支持,明确返回 `batch_not_supported`(不静默只处理第一个) |
36
+ | 通知(无 `id`) | 202 + 空 body(`notifications/initialized` 走这里) |
37
+ | 协议版本 | 认识 `2025-06-18` / `2025-03-26` / `2024-11-05`,其余回落最新 |
38
+
39
+ 支持的方法:`initialize`、`ping`、`tools/list`、`tools/call`。其余 `-32601`。
40
+
41
+ ### 为什么无状态
42
+
43
+ 有状态要 Durable Object,而 Pages Functions 只有一个无状态 worker。
44
+ 而 `tools/call` 本来就是**一问一答**,没有会话照样正确。
45
+ 代价只有一个:服务端主动推送(SSE 通知、sampling)做不了 —— 那就明说 405,而不是挂一个假的 GET 长连接。
46
+
47
+ ## 3. 身份与配额:纯 IP
48
+
49
+ 不要求注册、不认 `X-Beacon-Cid`、不发令牌。aid 由**出口 IP** 派生:
50
+
51
+ ```
52
+ ipk = base64url(sha256("ip|" + salt + "|" + ip))[:16]
53
+ aid = hex(sha256("mcp|" + ipk))[:16] // 16 位 hex,满足 agentId 的格式要求
54
+ ```
55
+
56
+ 于是额度、审计、逐条计量**全部复用 `/v1` 那套**,不需要新表、新维度、新报表:
57
+
58
+ | 闸 | 值 | 说明 |
59
+ |---|---|---|
60
+ | `V1_DAILY.anon` | 1,000 条 / 24h / IP | 一条记录算一条;重复看同一家不重复计 |
61
+ | `V1_IP_DAILY` | 20,000 条 / 24h / IP | 防「同 IP 换 aid 消耗」,两条闸量同一个池子 |
62
+ | `IP_DAILY_OP` | 5,000 次操作 / 24h / IP | 防「同 IP 换 cid 刷」。**操作数与记录数是两条尺子**,混算的后果见下 |
63
+
64
+ 一个真实买家一轮「搜一次 + 翻十家 + 看十张卡」≈ 21 条,1,000 条 ≈ 47 轮/天。
65
+
66
+ **换 IP 才能换额度,换 header 无效。** 顺带实测确认:外部伪造 `cf-connecting-ip` 会被
67
+ Cloudflare 直接拒(403 / error code 1000),所以这条闸没有「客户端自己声明 IP」的漏洞。
68
+
69
+ > **2026-11-13 修的一处真事故**:`beacon_ip_op` 里一行代表**一条记录**(`ipChargeV1` 按条写),
70
+ > 而门禁原来用 `COUNT(*)` 同时当「操作数」用 —— 一次检索回 200 条就抵掉 200 次操作。
71
+ > 实测本机 IP 一天取 7,351 条,把 `IP_DAILY_OP`(5000) 撞满,之后所有受门禁保护的路径
72
+ > (`/data/manifest.json`、`/skills/registry/capability/*`)全 429,部署校验读成
73
+ > 「6/19 项未通过」。现在两条尺子分开:记录数按行数(上限 20000),操作数按
74
+ > `#` 前缀归并(上限 5000),429 回执里带 `dimension: ops | records` 指明是哪一条。
75
+
76
+ ### 审计口径
77
+
78
+ `logUsage` 的 `tool` 带 `mcp.` 前缀(`mcp.search_vendors` 等),`layer` 取 handler 给的
79
+ (`index` / `record` / `capability`)。这样 D1 报表里能把**远程 MCP** 与**本地 stdio / App** 分开 ——
80
+ 否则「远程端点到底有没有人来」永远答不了,而那正是做这件事的原因。
81
+
82
+ 工具执行抛异常时**也写一行**(status 500)。第一版在 catch 里直接 return,等于最严重的那类失败静默。
83
+
84
+ ## 4. 已上线的工具(7 个:3 个取数 + 4 个寻源/指路)
85
+
86
+ | 工具 | 映射到 | 说明 |
87
+ |---|---|---|
88
+ | `search_vendors` | `handleSearch` → `/v1/search` | 关键词/城市/国标码检索 |
89
+ | `get_vendor` | `handleRec` → `/v1/rec/<token>` | 单家完整档案 |
90
+ | `get_capability_card` | `handleCap` → `/v1/cap/<token>` | 能力卡;`found:false` 是正常结果,不算错误 |
91
+ | `start_sourcing` | `handleSearch` + D1 会话 | 含糊需求 → 宽召回 + 1~2 个澄清问题,返回 `session_id` |
92
+ | `answer_sourcing` | 会话续接 + 重检 | 把澄清答案写回,**重新检索**(不是在旧结果里二次过滤) |
93
+ | `refine_sourcing` | 会话快照 | `best` / `more` / `filter` / `details` |
94
+ | `vendor_onboarding` | 纯静态文案 | 入驻/认领指路(控制台深链 + Agent HTTP 契约) |
95
+
96
+ **复用而不是重写**:检索打分、脱敏、逐条计量都在原 handler 里。在这里重写一份必然与 stdio 版分叉,
97
+ 而分叉的检索结果比慢一点糟得多。实现方式见 `pages/_worker.js` 的 `mcpInnerRequest()`。
98
+
99
+ ### 4.1 多轮寻源:会话在哪、活多久、谁的
100
+
101
+ 协议层的「无状态」没变(仍然不签 `Mcp-Session-Id`)。多轮状态是**业务状态**,落在 D1 的
102
+ `beacon_mcp_session` 表,用 `session_id` 显式续接:
103
+
104
+ | 项 | 值 |
105
+ |---|---|
106
+ | TTL | 2 小时(`expires`),过期即读不到、并在下一次 `start_sourcing` 时被清 |
107
+ | 归属 | `aid`(由出口 IP 派生)。SQL 里带 `AND aid = ?`,别的 IP 拿到 `sid` 也读不到 |
108
+ | 容量 | 单 IP 最多 20 个活会话(按最后使用时间淘汰),一轮最多存 40 家候选 |
109
+ | 存什么 | 一句话 `demand` + 候选的**筛选字段**(名称/城市/区县/国标码/工艺/材料/认证/是否有电话/灯牌档) |
110
+ | **不存什么** | 电话、坐标、来源 URL。电话是「一条一个额度」的取证动作(`get_vendor`),存进会话等于开第二条免费取号通道 |
111
+
112
+ **城市先从需求里认。** `start_sourcing("东莞注塑代工,最好有 ISO9001")` 里的「东莞」会被
113
+ 认成 `city` 过滤条件(词表直接用检索侧的 `catalog/tree.json → cities`,所以认得就一定查得到),
114
+ 回执里用 `city` 字段明说是哪一座 —— 否则 agent 没法解释「为什么结果全是东莞的」。
115
+ 认不出来才由澄清问题去问。
116
+
117
+ > 这条是线上实测补的:第一版没做提取,同一句需求返回的第一名是**重庆**的公司。
118
+ > 它不是「问得浅」,是**方向错了** —— 比少问一句严重得多。
119
+
120
+ **澄清问题由候选集自己生成,不是背行业知识表。** 只问三件事:城市、工艺、认证 ——
121
+ 选项全部取自这一轮候选,所以不会出现「选项里没有这个城市」,也不会问名录里没有的字段
122
+ (交期、数量、开票方式一律不问)。这比本地 stdio 版(`skills/rfq-kernel/industry/*.json`
123
+ 的 10 个行业 pack)问得浅,是刻意的取舍:**与其问得像人却答不出,不如只问能兑现的**。
124
+
125
+ ### 4.2 没上的 4 类,以及为什么
126
+
127
+ | 工具 | 缺什么 |
128
+ |---|---|
129
+ | `read_notes` / `report_note` | 语义是「本地笔记」,远程端点没有本地盘(要么改语义,要么不做) |
130
+ | `suggest_filters` / `list_industries` | 依赖国标货架名称表(`industry-index.json` 6MB),要单独做缓存层 |
131
+
132
+ **没上的工具不出现在 `tools/list` 里** —— 让 agent 看不到,比让它调了才报错好。
133
+
134
+ ## 5. 验收清单
135
+
136
+ ```bash
137
+ B=https://beacon-mfg.pages.dev
138
+
139
+ # 握手
140
+ curl -s -X POST -H "Content-Type: application/json" \
141
+ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}' \
142
+ $B/mcp
143
+
144
+ # 工具清单(应恰好 7 个)
145
+ curl -s -X POST -H "Content-Type: application/json" \
146
+ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' $B/mcp
147
+
148
+ # 检索
149
+ curl -s -X POST -H "Content-Type: application/json" \
150
+ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_vendors","arguments":{"query":"输送线","city":"上海"}}}' \
151
+ $B/mcp
152
+
153
+ # 多轮寻源:start 拿 session_id,再 answer / refine(两条命令要在**同一个出口 IP** 下执行)
154
+ curl -s -X POST -H "Content-Type: application/json" \
155
+ -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"start_sourcing","arguments":{"demand_text":"东莞注塑代工,最好有 ISO9001"}}}' \
156
+ $B/mcp
157
+
158
+ # 方向性检查:GET 必须 405,不是 200 也不是 404
159
+ curl -s -o /dev/null -w "%{http_code}\n" $B/mcp
160
+ ```
161
+
162
+ 真正的客户端验收(比 curl 可信):
163
+
164
+ ```bash
165
+ npx @modelcontextprotocol/inspector
166
+ # Transport: Streamable HTTP URL: https://beacon-mfg.pages.dev/mcp
167
+ ```
168
+
169
+ ## 6. 已知边界
170
+
171
+ 1. **无状态 = 无推送**。需要 sampling / elicitation / 服务端通知的客户端功能用不了。
172
+ 2. **一次性工具调用没做去重**。客户端重试 = 重复计量(与 `/v1` 同口径,不为重试开豁免)。
173
+ 3. **`initialize` 的版本号** `MCP_SERVER_VERSION` 硬编码在 `_worker.js`,与 `mcp/package.json`
174
+ 必须同步。不一致只影响回执里显示的数字,但会让「线上是不是新版」变成猜谜 —— 已纳入
175
+ `mcp/check_release.py` 闸门。
176
+ 4. **preview 环境跑不了**:Pages 的 preview 不继承 secrets,`BEACON_JWT_SECRET` 为空
177
+ → 端点 fail-closed 返回 503 `server_misconfigured`。这是刻意的(签不出令牌就用不了),
178
+ 所以远程端点只能在生产验证。
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Beacon-MFG 只读 MCP 服务
2
2
 
3
- **English**: Read-only MCP server for finding **Chinese suppliers, manufacturers and business entities** — 210k+ records across all GB/T 4754 industries, bilingual CN/EN, USCC-verified, with an RFQ sourcing kernel. Free, no API key: `npx -y beacon-mfg-mcp`.
3
+ **English**: Read-only MCP server for finding **Chinese suppliers, manufacturers and business entities** — 210k+ records across all GB/T 4754 industries, bilingual CN/EN, USCC-verified, with an RFQ sourcing kernel. Free, no API key. **No install**: connect to the hosted Streamable HTTP endpoint `https://beacon-mfg.pages.dev/mcp`; or run locally with `npx -y beacon-mfg-mcp`.
4
4
 
5
5
  让任意支持 MCP 的主流 agent(Claude Desktop / Cline / Continue / 常见 MCP 客户端 等)能够**检索与调用**
6
6
  灯塔工厂供应商名录,**无需 clone 整个仓库、无需任何密钥、无需第三方依赖**。
@@ -10,6 +10,18 @@
10
10
  > 只记「怎么绕开」,不评数据对错;repo 模式存 `<repo>/mcp/notes/` 由操作员托管,
11
11
  > 联网模式存本机 `~/.beacon-mfg/notes/` 不上传云端)。
12
12
 
13
+ > **两种接入形态,工具数不同**:
14
+ > - **远程端点**(`https://beacon-mfg.pages.dev/mcp`,Streamable HTTP,无需安装)——
15
+ > 提供 7 个工具:`search_vendors` / `get_vendor` / `get_capability_card`,
16
+ > 多轮寻源 `start_sourcing` / `answer_sourcing` / `refine_sourcing`,以及 `vendor_onboarding`。
17
+ > 额度按**出口 IP** 计(1,000 条记录/24h),不要注册、不要 API Key;寻源会话 2 小时过期,
18
+ > 且只能在**同一个出口 IP** 下续接。
19
+ > - **本地 stdio**(`npx -y beacon-mfg-mcp`)—— 下表全部 12 个,多轮寻源的澄清问题由行业 pack 驱动
20
+ > (远程版只问「答了能过滤」的城市/工艺/认证,选项取自候选集本身),另有踩坑留言区。
21
+ >
22
+ > 远程端点没上的工具**不会出现在 `tools/list` 里**(让 agent 看不到,比让它调了才报错好)。
23
+ > 契约与验收清单见 https://github.com/eiry16/beacon-mfg/blob/main/mcp/MCP_HTTP.md。
24
+
13
25
  ## 提供的 tool(共 12 个:8 个只读检索 + 1 个身份自助 + 1 个入驻入口 + 2 个留言区读写)
14
26
 
15
27
  | tool | 用途 | 关键参数 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beacon-mfg-mcp",
3
- "version": "1.12.4",
3
+ "version": "1.12.6",
4
4
  "mcpName": "io.github.eiry16/beacon-mfg-mcp",
5
5
  "description": "Read-only MCP server exposing the BeaconMFG灯塔 directory of business entities across ALL GB/T 4754 industry divisions (manufacturing, services, retail, logistics, catering, IT, and more) to any MCP-compatible agent. No signup and no API key: an anonymous identity is generated on first run, phone binding is optional (only needed for L2 capability cards).",
6
6
  "keywords": [
@@ -40,7 +40,8 @@
40
40
  "rfq-kernel/",
41
41
  "copy-rfk.js",
42
42
  "notes/",
43
- "README.md"
43
+ "README.md",
44
+ "MCP_HTTP.md"
44
45
  ],
45
46
  "scripts": {
46
47
  "prepack": "node copy-rfk.js",