beacon-mfg-mcp 1.10.0 → 1.11.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/RELEASE.md +55 -5
  2. package/package.json +1 -1
  3. package/server.py +71 -0
package/RELEASE.md CHANGED
@@ -1,8 +1,57 @@
1
- # Beacon-MFG 只读 MCP 服务 · v1.10.0
1
+ # Beacon-MFG 只读 MCP 服务 · v1.11.0
2
2
 
3
3
  面向 Agent 的供应商名录检索服务:**只读**,不参与交易、不写入任何数据、不持有密钥。
4
4
 
5
- ## 本次更新(v1.10.0)
5
+ ## 本次更新(v1.11.0)
6
+
7
+ **一句话:让 Agent 知道「这个行业的分类有多可信」——新增证据等级,
8
+ 不再把「按采集关键词反推的码」当成「核实过的行业」讲给客户。**
9
+
10
+ ### 为什么
11
+
12
+ 全库 21 万条里,只有 **34 条**的行业归类有权威依据(工商登记 / 人工认证)。
13
+ 98.4% 是推断的,其中 **28.5% 是按当年抓数据时的搜索词反推**——
14
+ 用「软件开发」搜地图,整栋软件大厦里的公司都被打了 6513 应用软件开发。
15
+
16
+ 以前这两者在检索结果里长得一模一样。Agent 于是会把一家建设工程公司
17
+ 当成软件公司推给用户——**这比答「没找到」伤害大得多**:
18
+ 答没找到,用户会自己去找;答错了,用户会拿它做采购决定。
19
+
20
+ ### 新增字段
21
+
22
+ | 位置 | 字段 | 说明 |
23
+ |---|---|---|
24
+ | `get_vendor` → `vendor.industry_evidence` | `{tier,label,source,confidence,code,name,caveat}` | 完整证据块 |
25
+ | `search_vendors` → `results[].industry_source` | `string \| null` | 归类的来源枚举 |
26
+ | `search_vendors` → `industry_note` | `string` | 口径说明:`results[].gb` 是归档位置,不等于已核实行业 |
27
+
28
+ 等级由 `industry.source` 推导,**不落盘**(source 一变等级自动跟着变):
29
+
30
+ | tier | 含义 | 全库占比 |
31
+ |---|---|---|
32
+ | **A** | 工商登记 / 人工认证 | 0.02% |
33
+ | **B** | 公司名推断(「某某精密机械有限公司」) | 64.5% |
34
+ | **C** | 国标词匹配 / POI 标签 / 原品类兜底 | 6.0% |
35
+ | **D** | 采集关键词反推 | 28.5% |
36
+
37
+ C/D 自带 `caveat` 文案,明确提示「可作线索,不宜当精确分类引用」。
38
+
39
+ ### 使用方法
40
+
41
+ 拿行业给用户下结论之前,先看 `tier`:
42
+
43
+ - `A` / `B` → 可以直接说「这家做 X」
44
+ - `C` / `D` → 说「名录上归在 X,**行业待核**」,并优先用能力卡、厂名、工艺去佐证
45
+
46
+ ### 兼容性
47
+
48
+ - 只**新增**字段,原有 10 个工具的参数与既有返回结构**无变化**,老客户端忽略新字段即可。
49
+ - `get_vendor` 的 `gb_source`(`"shard"` = 国标码取自分片归属)语义未变,
50
+ 与本次新增的 `industry_source` 是两件事,不要混用。
51
+ - 数据侧同步改动:全库重新核对名称与国标的矛盾(1,835 → 510 条),
52
+ 指纹层新增 `isrc` 字段并重算 `et`(生产/流通/服务)。
53
+
54
+ ## 本次更新(v1.10.0,已发布)
6
55
 
7
56
  **一句话:Agent 终于能「把人带到正确的入口」——新增 `vendor_onboarding`,
8
57
  用户说「我是某某公司的、想入驻」时,Agent 不再手足无措 —— 但**仍然不代填**。
@@ -106,17 +155,18 @@
106
155
  装完即降级为「无身份」——服务端开强制鉴权后所有 npx 用户会集体 401。
107
156
  - 服务端同步:匿名 200 次 / verified 2000 次(滚动 24h),L2 能力卡另有 300 条/日上限。
108
157
 
109
- ## 九个 tool(8 只读 + 1 身份自助)
158
+ ## 十个 tool(9 只读 + 1 身份自助)
110
159
 
111
160
  | tool | 用途 |
112
161
  |---|---|
113
- | `search_vendors` | 按关键词 / 城市 / 国标码检索 |
114
- | `get_vendor` | 按 id 取完整档案 |
162
+ | `search_vendors` | 按关键词 / 城市 / 国标码 / 坐标检索 |
163
+ | `get_vendor` | 按 id 取完整档案(含 `industry_evidence` 证据等级) |
115
164
  | `get_capability_card` | 按 id 取能力卡 |
116
165
  | `suggest_filters` | 把口语需求映射成候选国标小类码 |
117
166
  | `list_industries` | 浏览有数据的国标小类货架 |
118
167
  | `start_sourcing` / `answer_sourcing` / `refine_sourcing` | 多轮采购寻源 |
119
168
  | `account` | 查额度 / 绑手机号升 verified / 解绑(`action`) |
169
+ | `vendor_onboarding` | 拉起入驻/认领:给深链 + 步骤 + 边界清单(**不代填**) |
120
170
 
121
171
  ## 安装
122
172
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beacon-mfg-mcp",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "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).",
5
5
  "keywords": [
6
6
  "mcp",
package/server.py CHANGED
@@ -2351,6 +2351,11 @@ def _rec_summary(rec: Dict[str, Any]) -> Dict[str, Any]:
2351
2351
  "city": rec.get("city"),
2352
2352
  "district": rec.get("dist"),
2353
2353
  "gb": rec.get("gb"),
2354
+ # 行业归类的证据来源(2026-10-24)。指纹分片带 isrc 时才有值;
2355
+ # 没有就留 null —— 调用方要精确证据等级请用 get_vendor 的 industry_evidence。
2356
+ # ⚠ 读的是 isrc 不是 src:src 是**数据信源**(gov_list/public_directory),
2357
+ # 两件事必须分开,混用会让 App 的绿色 G 标和这里的证据等级互相干扰。
2358
+ "industry_source": rec.get("isrc"),
2354
2359
  # 主体经济类型:生产(A/B/C/D/E) / 流通(F/G) / 服务(H~S)。
2355
2360
  # 全行业口径下这是唯一能跨门类表达「这家主体在干什么性质的事」的信号 ——
2356
2361
  # 旧的「是否制造商」只能区分 C 与非 C,服务类主体占六成却全挤在一筐里。
@@ -3280,6 +3285,14 @@ def search_vendors(query: str = "", city: str = "", gb: str = "",
3280
3285
  "某酒店(中央商务区店) 前面);score 是**能力画像分**"
3281
3286
  "(有已发布能力卡才有分,无卡为 0),**不是相关度**,不要拿它反推排序。")
3282
3287
  + (_geo_note(rad) if lat_v is not None else ""),
3288
+ # 行业证据:search 只返回指纹摘要,拿不到完整的 industry.source。
3289
+ # 但 agent 很容易把 results[].gb 当成「核实过的行业」直接讲给客户 ——
3290
+ # 全库 28.5% 的归类其实是按采集关键词反推的。把口径写死在这里,
3291
+ # 和 order_note 同一立场:别让调用方猜。
3292
+ "industry_note": ("results[].gb 是这家**归档所在的国标小类**,不等于已核实的行业。"
3293
+ "要拿行业下结论前,先 get_vendor 看 industry_evidence.tier:"
3294
+ "A=工商登记/人工认证、B=公司名推断、C=词表兜底、D=采集关键词反推。"
3295
+ "C/D 级别的行业只能当线索,向用户表述时应带上「行业待核」。"),
3283
3296
  # 坐标回显 + 圈外剔除数(仅坐标查询时出现)。
3284
3297
  **({"geo": {"lat": lat_v, "lng": lng_v, "radius_km": rad,
3285
3298
  "records_with_coords": geo_ncoord}} if lat_v is not None else {}),
@@ -3307,6 +3320,60 @@ def _pick(*vals: Any) -> Any:
3307
3320
  return None
3308
3321
 
3309
3322
 
3323
+ # ── 行业归类的证据等级(2026-10-24)──────────────────────────────────
3324
+ # 全库 210,555 条里只有 34 条的行业有权威依据(工商登记 / 人工认证),
3325
+ # 98.4% 是推断的。但检索结果里这两者长得一模一样 —— agent 会把
3326
+ # 「用采集关键词反推出来的码」当成「确认过的行业」讲给客户。
3327
+ # 那比答"没有"伤害更大:答"没有"用户会自己去找,答错了用户会拿它做采购决定。
3328
+ #
3329
+ # 所以给每条记录附一个等级,让调用方自己决定信到什么程度。
3330
+ # 等级**完全由 industry.source 推导、不落盘**:source 一变等级自动跟着变,
3331
+ # 不会出现「数据里存着旧等级、source 已经改过」的自相矛盾。
3332
+ #
3333
+ # A 权威 工商登记 / 人工认证
3334
+ # B 名称 公司名推断("苏州某某精密机械有限公司" → 通用设备制造)
3335
+ # C 兜底 国标特征词 / POI 标签 / 原品类
3336
+ # D 采集 按抓取时的搜索词反推 —— 最弱的一档,占全库 28.5%
3337
+ _EVIDENCE_TIER = {
3338
+ "gov_registry": ("A", "工商登记"),
3339
+ "registration": ("A", "工商登记"),
3340
+ "certification": ("A", "人工认证"),
3341
+ "name": ("B", "公司名称推断"),
3342
+ "gb_token": ("C", "国标特征词匹配"),
3343
+ "keyword": ("C", "POI 标签词"),
3344
+ "category_fallback": ("C", "原品类兜底"),
3345
+ "company_website": ("C", "企业官网"),
3346
+ "search_keyword": ("D", "采集关键词反推"),
3347
+ }
3348
+ _EVIDENCE_CAVEAT = {
3349
+ "C": "行业依据较弱,可能只判到门类;可作线索,不宜当精确分类引用。",
3350
+ "D": "按采集时的搜索词反推,与这家实际做什么可能无关"
3351
+ "(当年用「软件开发」搜地图,整栋软件大厦里的公司都被打了 6513)。"
3352
+ "多结果并列时请降权,或向用户明示「行业待核」。",
3353
+ }
3354
+
3355
+
3356
+ def _industry_evidence(rec: Dict[str, Any]) -> Optional[Dict[str, Any]]:
3357
+ """把 industry.source 折成可读的证据等级;没有 industry 就返回 None。"""
3358
+ ind = rec.get("industry")
3359
+ if not isinstance(ind, dict) or not ind.get("code"):
3360
+ return None
3361
+ src = (ind.get("source") or "").strip()
3362
+ tier, label = _EVIDENCE_TIER.get(src, ("C", src or "未知来源"))
3363
+ out: Dict[str, Any] = {
3364
+ "tier": tier,
3365
+ "label": label,
3366
+ "source": src,
3367
+ "confidence": ind.get("confidence"),
3368
+ "code": ind.get("code"),
3369
+ "name": ind.get("name"),
3370
+ }
3371
+ cav = _EVIDENCE_CAVEAT.get(tier)
3372
+ if cav:
3373
+ out["caveat"] = cav
3374
+ return out
3375
+
3376
+
3310
3377
  def normalize_vendor(rec: Dict[str, Any]) -> Dict[str, Any]:
3311
3378
  """zh 分片存在两套历史结构,下游 agent 只认一种,在这里展平(只增不删)。
3312
3379
 
@@ -3323,6 +3390,10 @@ def normalize_vendor(rec: Dict[str, Any]) -> Dict[str, Any]:
3323
3390
  out["province"] = _pick(rec.get("province"), region.get("province"))
3324
3391
  out["gb"] = rec.get("gb") if rec.get("gb") not in (None, "") else None
3325
3392
  out["keywords"] = _pick(rec.get("keywords"), rec.get("products"))
3393
+ # 行业证据等级:只增不删的新字段,老调用方忽略即可(见 _EVIDENCE_TIER)
3394
+ ev = _industry_evidence(rec)
3395
+ if ev:
3396
+ out["industry_evidence"] = ev
3326
3397
  if not out.get("gb"):
3327
3398
  out["gb_unclassified"] = True
3328
3399
  return out