@seasonkoh/webaz 0.1.24 → 0.1.26

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 (195) hide show
  1. package/README.md +5 -1
  2. package/dist/layer0-foundation/L0-1-database/db-backends/pg-backend.js +51 -0
  3. package/dist/layer0-foundation/L0-1-database/db-backends/sql-dialect-datetime.js +437 -0
  4. package/dist/layer0-foundation/L0-1-database/db-backends/sql-placeholders.js +98 -0
  5. package/dist/layer0-foundation/L0-1-database/db.js +65 -0
  6. package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +13 -11
  7. package/dist/layer0-foundation/L0-2-state-machine/transitions.js +1 -1
  8. package/dist/layer0-foundation/L0-5-manifest/manifest.js +13 -11
  9. package/dist/layer1-agent/L1-1-mcp-server/server.js +288 -208
  10. package/dist/layer1-agent/L1-2-external-anchor/anchor-engine.js +14 -12
  11. package/dist/layer2-business/L2-6-notifications/notification-engine.js +8 -5
  12. package/dist/layer2-business/L2-7-snf/snf-engine.js +16 -14
  13. package/dist/layer2-business/L2-8-feedback/build-feedback-engine.js +18 -10
  14. package/dist/layer2-business/L2-9-contribution/build-reputation-engine.js +37 -23
  15. package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +182 -0
  16. package/dist/layer2-business/L2-9-contribution/build-task-participation.js +47 -0
  17. package/dist/layer2-business/L2-9-contribution/build-task-read.js +222 -0
  18. package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +11 -3
  19. package/dist/layer2-business/L2-9-contribution/canonical-contribution-target.js +16 -0
  20. package/dist/layer2-business/L2-9-contribution/contribution-display-envelope.js +40 -0
  21. package/dist/layer2-business/L2-9-contribution/contribution-score-contract.js +36 -0
  22. package/dist/layer2-business/L2-9-contribution/contribution-score-evidence.js +61 -0
  23. package/dist/layer2-business/L2-9-contribution/github-credential/canonical.js +60 -0
  24. package/dist/layer2-business/L2-9-contribution/github-credential/github-credential.schema.js +140 -0
  25. package/dist/layer2-business/L2-9-contribution/github-credential/github-fetch-adapter.js +437 -0
  26. package/dist/layer2-business/L2-9-contribution/github-credential/self-consistency.js +38 -0
  27. package/dist/layer2-business/L2-9-contribution/github-credential/verifier.js +231 -0
  28. package/dist/layer2-business/L2-9-contribution/github-credential-ingestion-engine.js +145 -0
  29. package/dist/layer2-business/L2-9-contribution/github-credential-store.js +115 -0
  30. package/dist/layer2-business/L2-9-contribution/identity-binding-engine.js +134 -0
  31. package/dist/layer2-business/L2-9-contribution/identity-binding-store.js +101 -0
  32. package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-engine.js +126 -0
  33. package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-store.js +30 -0
  34. package/dist/layer2-business/L2-9-contribution/identity-claim-discovery.js +55 -0
  35. package/dist/layer2-business/L2-9-contribution/identity-claim-engine.js +109 -0
  36. package/dist/layer2-business/L2-9-contribution/identity-claim-fact-precondition.js +22 -0
  37. package/dist/layer2-business/L2-9-contribution/identity-claim-proof-verifier.js +97 -0
  38. package/dist/layer2-business/L2-9-contribution/identity-claim-read.js +59 -0
  39. package/dist/layer2-business/L2-9-contribution/task-proposal-ai-store.js +99 -0
  40. package/dist/layer2-business/L2-9-contribution/task-proposal-draft.js +191 -0
  41. package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +129 -0
  42. package/dist/layer2-business/L2-notes/note-photo-storage.js +4 -2
  43. package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +17 -15
  44. package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +11 -8
  45. package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +9 -8
  46. package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +11 -8
  47. package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +22 -16
  48. package/dist/pwa/acp-feed.js +13 -1
  49. package/dist/pwa/admin-bearer-auth.js +21 -0
  50. package/dist/pwa/contract-fingerprint.js +2 -0
  51. package/dist/pwa/email-delivery.js +127 -0
  52. package/dist/pwa/endpoint-actions.js +5 -1
  53. package/dist/pwa/goal-index.js +8 -8
  54. package/dist/pwa/human-presence.js +62 -0
  55. package/dist/pwa/public/app.js +1485 -283
  56. package/dist/pwa/public/i18n.js +297 -59
  57. package/dist/pwa/public/index.html +1 -0
  58. package/dist/pwa/public/openapi.json +5 -5
  59. package/dist/pwa/public/whitepaper/en/index.html +153 -0
  60. package/dist/pwa/public/whitepaper/zh-CN/index.html +153 -0
  61. package/dist/pwa/rate-limit.js +22 -0
  62. package/dist/pwa/routes/account-deletion.js +15 -13
  63. package/dist/pwa/routes/addresses.js +10 -9
  64. package/dist/pwa/routes/admin-admins.js +13 -14
  65. package/dist/pwa/routes/admin-analytics.js +109 -69
  66. package/dist/pwa/routes/admin-atomic.js +10 -4
  67. package/dist/pwa/routes/admin-catalog.js +13 -11
  68. package/dist/pwa/routes/admin-editor-picks.js +15 -10
  69. package/dist/pwa/routes/admin-events.js +5 -3
  70. package/dist/pwa/routes/admin-health.js +2 -1
  71. package/dist/pwa/routes/admin-moderation.js +50 -29
  72. package/dist/pwa/routes/admin-ops.js +35 -23
  73. package/dist/pwa/routes/admin-protocol-params.js +16 -19
  74. package/dist/pwa/routes/admin-reports.js +23 -21
  75. package/dist/pwa/routes/admin-tokenomics.js +26 -25
  76. package/dist/pwa/routes/admin-users-lifecycle.js +37 -40
  77. package/dist/pwa/routes/admin-users-query.js +65 -53
  78. package/dist/pwa/routes/admin-verifier-flow.js +82 -41
  79. package/dist/pwa/routes/admin-verifier-whitelist.js +55 -27
  80. package/dist/pwa/routes/admin-wallet-ops.js +32 -7
  81. package/dist/pwa/routes/agent-buy.js +46 -22
  82. package/dist/pwa/routes/agent-governance.js +52 -56
  83. package/dist/pwa/routes/ai.js +7 -5
  84. package/dist/pwa/routes/analytics.js +43 -41
  85. package/dist/pwa/routes/anchors.js +19 -20
  86. package/dist/pwa/routes/announcements.js +13 -13
  87. package/dist/pwa/routes/arbitrator.js +97 -31
  88. package/dist/pwa/routes/auction.js +157 -116
  89. package/dist/pwa/routes/auth-login.js +6 -4
  90. package/dist/pwa/routes/auth-read.js +21 -10
  91. package/dist/pwa/routes/auth-register.js +111 -26
  92. package/dist/pwa/routes/auth-sessions.js +12 -11
  93. package/dist/pwa/routes/blocklist.js +16 -15
  94. package/dist/pwa/routes/build-feedback.js +10 -9
  95. package/dist/pwa/routes/build-reputation.js +6 -2
  96. package/dist/pwa/routes/build-tasks.js +45 -13
  97. package/dist/pwa/routes/buyer-feeds.js +27 -25
  98. package/dist/pwa/routes/cart.js +16 -15
  99. package/dist/pwa/routes/charity.js +212 -150
  100. package/dist/pwa/routes/chat.js +42 -43
  101. package/dist/pwa/routes/checkin-tasks.js +10 -9
  102. package/dist/pwa/routes/checkout-helpers.js +12 -10
  103. package/dist/pwa/routes/claim-initiators.js +34 -14
  104. package/dist/pwa/routes/claim-verify.js +86 -53
  105. package/dist/pwa/routes/claim-voting.js +43 -18
  106. package/dist/pwa/routes/contribution-identity.js +164 -0
  107. package/dist/pwa/routes/contribution-score.js +19 -0
  108. package/dist/pwa/routes/coupons.js +19 -16
  109. package/dist/pwa/routes/dashboards.js +18 -16
  110. package/dist/pwa/routes/dispute-cases.js +25 -24
  111. package/dist/pwa/routes/disputes-read.js +45 -51
  112. package/dist/pwa/routes/disputes-write.js +124 -61
  113. package/dist/pwa/routes/evidence.js +9 -9
  114. package/dist/pwa/routes/external-anchors.js +13 -12
  115. package/dist/pwa/routes/feedback.js +29 -33
  116. package/dist/pwa/routes/flash-sales.js +18 -16
  117. package/dist/pwa/routes/follows.js +25 -24
  118. package/dist/pwa/routes/governance-auto-deactivate.js +21 -9
  119. package/dist/pwa/routes/governance-onboarding.js +70 -59
  120. package/dist/pwa/routes/group-buys.js +22 -22
  121. package/dist/pwa/routes/growth.js +34 -31
  122. package/dist/pwa/routes/import-product.js +12 -10
  123. package/dist/pwa/routes/kyc.js +9 -8
  124. package/dist/pwa/routes/leaderboard.js +20 -18
  125. package/dist/pwa/routes/listings.js +23 -22
  126. package/dist/pwa/routes/logistics.js +10 -8
  127. package/dist/pwa/routes/manifests.js +27 -27
  128. package/dist/pwa/routes/me-data.js +23 -21
  129. package/dist/pwa/routes/notifications.js +7 -6
  130. package/dist/pwa/routes/offers.js +30 -12
  131. package/dist/pwa/routes/orders-action.js +51 -29
  132. package/dist/pwa/routes/orders-create.js +75 -20
  133. package/dist/pwa/routes/orders-read.js +21 -20
  134. package/dist/pwa/routes/p2p-products.js +30 -18
  135. package/dist/pwa/routes/payments-governance.js +61 -56
  136. package/dist/pwa/routes/peers.js +9 -8
  137. package/dist/pwa/routes/pin-receipts.js +13 -13
  138. package/dist/pwa/routes/products-aliases.js +12 -10
  139. package/dist/pwa/routes/products-claims.js +36 -17
  140. package/dist/pwa/routes/products-create.js +53 -38
  141. package/dist/pwa/routes/products-crud.js +17 -16
  142. package/dist/pwa/routes/products-links.js +49 -26
  143. package/dist/pwa/routes/products-list.js +6 -4
  144. package/dist/pwa/routes/products-meta.js +40 -39
  145. package/dist/pwa/routes/products-update.js +19 -5
  146. package/dist/pwa/routes/profile-credentials.js +20 -19
  147. package/dist/pwa/routes/profile-identity.js +14 -13
  148. package/dist/pwa/routes/profile-location.js +7 -6
  149. package/dist/pwa/routes/profile-placement.js +20 -19
  150. package/dist/pwa/routes/profile-prefs.js +11 -11
  151. package/dist/pwa/routes/promoter.js +58 -66
  152. package/dist/pwa/routes/public-build-tasks.js +19 -0
  153. package/dist/pwa/routes/public-utils.js +108 -46
  154. package/dist/pwa/routes/push.js +16 -15
  155. package/dist/pwa/routes/ratings.js +92 -32
  156. package/dist/pwa/routes/recover-key.js +66 -26
  157. package/dist/pwa/routes/referral.js +37 -52
  158. package/dist/pwa/routes/reputation.js +3 -2
  159. package/dist/pwa/routes/returns.js +76 -73
  160. package/dist/pwa/routes/reviews.js +41 -18
  161. package/dist/pwa/routes/rewards-apply.js +16 -15
  162. package/dist/pwa/routes/rewards-auto-downgrade.js +9 -7
  163. package/dist/pwa/routes/rewards-escrow-expire.js +7 -5
  164. package/dist/pwa/routes/rfqs.js +163 -85
  165. package/dist/pwa/routes/search.js +16 -14
  166. package/dist/pwa/routes/secondhand.js +25 -22
  167. package/dist/pwa/routes/seller-quota.js +24 -26
  168. package/dist/pwa/routes/share-redirects.js +60 -55
  169. package/dist/pwa/routes/shareables-interactions.js +34 -35
  170. package/dist/pwa/routes/shareables.js +55 -51
  171. package/dist/pwa/routes/shop-referral.js +58 -0
  172. package/dist/pwa/routes/shops.js +25 -20
  173. package/dist/pwa/routes/signaling.js +10 -9
  174. package/dist/pwa/routes/skill-market.js +16 -16
  175. package/dist/pwa/routes/skills.js +15 -14
  176. package/dist/pwa/routes/snf.js +14 -13
  177. package/dist/pwa/routes/tags.js +10 -9
  178. package/dist/pwa/routes/task-proposals.js +121 -0
  179. package/dist/pwa/routes/trial.js +72 -52
  180. package/dist/pwa/routes/trusted-kpi.js +20 -18
  181. package/dist/pwa/routes/url-claim.js +67 -28
  182. package/dist/pwa/routes/users-public.js +62 -70
  183. package/dist/pwa/routes/variants.js +12 -13
  184. package/dist/pwa/routes/verifier-user.js +61 -21
  185. package/dist/pwa/routes/verify-tasks.js +49 -25
  186. package/dist/pwa/routes/waitlist.js +16 -15
  187. package/dist/pwa/routes/wallet-read.js +75 -37
  188. package/dist/pwa/routes/wallet-write.js +12 -9
  189. package/dist/pwa/routes/webauthn.js +25 -26
  190. package/dist/pwa/routes/webhooks.js +26 -26
  191. package/dist/pwa/routes/welcome.js +45 -50
  192. package/dist/pwa/routes/wishlist-qa.js +29 -32
  193. package/dist/pwa/server.js +304 -90
  194. package/dist/version.js +1 -1
  195. package/package.json +76 -3
@@ -22,6 +22,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
22
22
  import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, // #B.1 a — MCP 三大原语之 Prompts
23
23
  GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
24
24
  import { initDatabase, generateId } from '../../layer0-foundation/L0-1-database/schema.js';
25
+ import { setSeamDb } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam(本进程注入)
25
26
  import { transition, getOrderStatus, initSystemUser, } from '../../layer0-foundation/L0-2-state-machine/engine.js';
26
27
  import { initDisputeSchema, createDispute, respondToDispute, getDisputeDetails, getOrderDispute, getOpenDisputes, } from '../../layer3-trust/L3-1-dispute-engine/dispute-engine.js';
27
28
  import { initNotificationSchema, notifyTransition, getNotifications, getUnreadCount, markRead, } from '../../layer2-business/L2-6-notifications/notification-engine.js';
@@ -42,6 +43,15 @@ const TELEMETRY_ENABLED = (process.env.WEBAZ_TELEMETRY ?? 'off').toLowerCase() =
42
43
  // P0 不迁移任何工具(NETWORK_TOOLS 为空)→ 一切仍走本地 = 零行为变化;P1/P2 逐个把工具名加入集合切到网络。
43
44
  const WEBAZ_API_URL = (process.env.WEBAZ_API_URL ?? 'https://webaz.xyz').replace(/\/+$/, '');
44
45
  const WEBAZ_API_KEY = process.env.WEBAZ_API_KEY ?? '';
46
+ // F6 (dogfood R2): keyed MCP handlers resolve api_key as explicit args.api_key > env WEBAZ_API_KEY >
47
+ // '' (→ the existing typed API_KEY_REQUIRED guards). Explicit ALWAYS wins; env never overrides an explicit
48
+ // key. Keyless actions (list/discover/detail/suggest/browse/get_campaign…) gate their public branches
49
+ // separately and never call this, so a configured env key does NOT change the public read boundary.
50
+ // The key is never printed/returned/logged.
51
+ export function resolveMcpApiKey(args, envKey = WEBAZ_API_KEY) {
52
+ const explicit = typeof args?.api_key === 'string' ? args.api_key.trim() : '';
53
+ return explicit || envKey;
54
+ }
45
55
  const WEBAZ_MODE_ENV = (process.env.WEBAZ_MODE ?? '').toLowerCase();
46
56
  // 模式:显式 WEBAZ_MODE 优先;否则有 api_key → network,无 key → network_readonly(装完即见真网络)。
47
57
  // network_readonly(L1 onboarding,2026-06-08):无 key 默认。公共读匿名打 webaz.xyz(真 catalog/协议),
@@ -184,6 +194,7 @@ function modeBanner() {
184
194
  }
185
195
  // ─── 初始化 ──────────────────────────────────────────────────
186
196
  const db = initDatabase();
197
+ setSeamDb(db); // RFC-016 Phase 1:注入异步 DB seam(本进程)—— 共享引擎迁 seam 后 MCP 进程也能用,否则 dbOne/dbAll 抛"未初始化"
187
198
  initSystemUser(db);
188
199
  initDisputeSchema(db);
189
200
  initNotificationSchema(db);
@@ -323,7 +334,7 @@ No auth required, no parameters needed.
323
334
 
324
335
  Roles: buyer (browse/order/confirm) | seller (list/accept/ship) | logistics (pickup/transit/deliver) | reviewer (reviews) | arbitrator (disputes/rulings).
325
336
 
326
- ⚠️ **MCP register limitations (anti-bot, by design)**: does NOT set placement_id/sponsor_id — referral/PV chain NOT built via MCP. To build chain: user must arrive via webaz_share_link \`?ref=<uid>\` URL clicked in browser (PWA flow). region defaults 'global'; valid: singapore/china/usa/malaysia/indonesia/thailand/vietnam/taiwan/hk/global.`,
337
+ ⚠️ **MCP register limitations (anti-bot, by design)**: does NOT set placement_id/sponsor_id — referral/PV chain NOT built via MCP. To build chain: user must arrive via webaz_share_link \`/i/<permanent_code>\` (or \`?ref=<permanent_code>\`) URL clicked in browser (PWA flow). region defaults 'global'; valid: singapore/china/usa/malaysia/indonesia/thailand/vietnam/taiwan/hk/global.`,
327
338
  inputSchema: {
328
339
  type: 'object',
329
340
  properties: {
@@ -401,11 +412,11 @@ Skipping is allowed but agent then carries price/stock-race risk itself.`,
401
412
  inputSchema: {
402
413
  type: 'object',
403
414
  properties: {
404
- api_key: { type: 'string', description: "Buyer's api_key" },
415
+ api_key: { type: 'string', description: "Buyer's api_key (or omit and set the WEBAZ_API_KEY env var)" },
405
416
  product_id: { type: 'string', description: 'Product ID (from webaz_search)' },
406
417
  quantity: { type: 'number', description: 'Quantity, default 1' },
407
418
  },
408
- required: ['api_key', 'product_id'],
419
+ required: ['product_id'],
409
420
  },
410
421
  },
411
422
  {
@@ -421,7 +432,7 @@ Actions: create (title/description/price) | mine | update (product_id + changed
421
432
  inputSchema: {
422
433
  type: 'object',
423
434
  properties: {
424
- api_key: { type: 'string', description: "Seller's api_key" },
435
+ api_key: { type: 'string', description: "Seller's api_key (or omit and set the WEBAZ_API_KEY env var)" },
425
436
  action: {
426
437
  type: 'string',
427
438
  enum: ['create', 'mine', 'update', 'delist', 'relist', 'trash', 'delete'],
@@ -477,7 +488,7 @@ Actions: create (title/description/price) | mine | update (product_id + changed
477
488
  description: '[S4] Product-origin claims (challengeable). E.g. {"made_in":"Kyoto JP","material":"100% cotton GOTS-cert","certs":[{"name":"GOTS","sha256":"<64-hex>"}]}. Total JSON ≤4KB; any cert sha256 must be 64-hex. Any buyer can challenge.',
478
489
  },
479
490
  },
480
- required: ['api_key'],
491
+ required: [],
481
492
  },
482
493
  },
483
494
  {
@@ -499,7 +510,7 @@ Options:
499
510
  inputSchema: {
500
511
  type: 'object',
501
512
  properties: {
502
- api_key: { type: 'string', description: "Buyer's api_key" },
513
+ api_key: { type: 'string', description: "Buyer's api_key (or omit and set the WEBAZ_API_KEY env var)" },
503
514
  product_id: { type: 'string', description: 'Product ID to buy (from webaz_search)' },
504
515
  quantity: { type: 'number', description: 'Quantity, default 1' },
505
516
  shipping_address: { type: 'string', description: 'Shipping address' },
@@ -524,7 +535,7 @@ Options:
524
535
  description: '[B5] Per-order donation pct (0 / 0.5 / 1 / 2 / 5). Computed separately + into charity_fund, posted on order complete.',
525
536
  },
526
537
  },
527
- required: ['api_key', 'product_id', 'shipping_address'],
538
+ required: ['product_id', 'shipping_address'],
528
539
  },
529
540
  },
530
541
  {
@@ -532,15 +543,15 @@ Options:
532
543
  // was ~927 chars, now ~430 chars
533
544
  description: `STATUS TRANSITIONS on an order — NOT for editing order content (price/qty/address immutable after creation). Each role can only perform their own actions.
534
545
 
535
- - **Seller**: accept (24h after payment) | ship (needs tracking, within handling time)
536
- - **Logistics**: pickup (48h after ship) | transit | deliver (needs proof description)
546
+ - **Seller**: accept (24h after payment) | ship (needs tracking/notes, within handling time) | pickup/transit/deliver ONLY when order.logistics_id is empty (Phase-1 self-fulfill; seller carries logistics responsibility) | decline (actively refuse a PAID order instead of silent timeout; requires decline_reason_code — objective codes [stock_consumed_concurrent/stale_price_snapshot/force_majeure] go to a PROVISIONAL fault you must then contest within the window, NOT auto-cleared; subjective codes [price_regret/cherry_pick/other] settle immediately as seller-fault + buyer refund) | contest_decline (open human arbitration on an objective-claimed provisional fault, within the contest window, to be cleared to no-fault; pass evidence_description — window expiry finalizes as fault)
547
+ - **Logistics**: pickup (48h after ship) | transit | deliver (needs proof description) when assigned or claiming an unassigned shipped order
537
548
  - **Buyer**: confirm (→ fund settlement) | dispute (needs reason; freezes funds → arbitration)
538
549
 
539
550
  Missing deadline → protocol auto-marks party in default.`,
540
551
  inputSchema: {
541
552
  type: 'object',
542
553
  properties: {
543
- api_key: { type: 'string', description: "Operator's api_key" },
554
+ api_key: { type: 'string', description: "Operator's api_key (or omit and set the WEBAZ_API_KEY env var)" },
544
555
  order_id: { type: 'string', description: 'Order ID' },
545
556
  action: {
546
557
  type: 'string',
@@ -558,7 +569,7 @@ Missing deadline → protocol auto-marks party in default.`,
558
569
  description: 'Evidence description (recommended for ship/pickup/deliver; required for dispute)',
559
570
  },
560
571
  },
561
- required: ['api_key', 'order_id', 'action'],
572
+ required: ['order_id', 'action'],
562
573
  },
563
574
  },
564
575
  {
@@ -568,10 +579,10 @@ Missing deadline → protocol auto-marks party in default.`,
568
579
  inputSchema: {
569
580
  type: 'object',
570
581
  properties: {
571
- api_key: { type: 'string', description: "Querier's api_key" },
582
+ api_key: { type: 'string', description: "Querier's api_key (or omit and set the WEBAZ_API_KEY env var)" },
572
583
  order_id: { type: 'string', description: 'Order ID' },
573
584
  },
574
- required: ['api_key', 'order_id'],
585
+ required: ['order_id'],
575
586
  },
576
587
  },
577
588
  {
@@ -589,14 +600,14 @@ Actions:
589
600
  inputSchema: {
590
601
  type: 'object',
591
602
  properties: {
592
- api_key: { type: 'string', description: 'Your api_key' },
603
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
593
604
  action: {
594
605
  type: 'string',
595
606
  enum: ['view', 'deposits', 'withdrawals', 'income'],
596
607
  description: 'Action type (default: view)',
597
608
  },
598
609
  },
599
- required: ['api_key'],
610
+ required: [],
600
611
  },
601
612
  },
602
613
  {
@@ -608,11 +619,11 @@ Actions:
608
619
  inputSchema: {
609
620
  type: 'object',
610
621
  properties: {
611
- api_key: { type: 'string', description: 'Your api_key' },
622
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
612
623
  unread: { type: 'boolean', description: 'Return only unread (default false)' },
613
624
  mark_read: { type: 'boolean', description: 'Auto-mark read after call (default false)' },
614
625
  },
615
- required: ['api_key'],
626
+ required: [],
616
627
  },
617
628
  },
618
629
  {
@@ -635,7 +646,7 @@ Protocol auto-judges (no human): respondent silent 48h → favor initiator; arbi
635
646
  inputSchema: {
636
647
  type: 'object',
637
648
  properties: {
638
- api_key: { type: 'string', description: "Operator's api_key" },
649
+ api_key: { type: 'string', description: "Operator's api_key (or omit and set the WEBAZ_API_KEY env var)" },
639
650
  action: {
640
651
  type: 'string',
641
652
  enum: ['view', 'list_open', 'respond', 'add_evidence', 'arbitrate'],
@@ -666,7 +677,7 @@ Protocol auto-judges (no human): respondent silent 48h → favor initiator; arbi
666
677
  },
667
678
  ruling_reason: { type: 'string', description: 'Ruling reason (required for arbitrate; permanently recorded on-chain)' },
668
679
  },
669
- required: ['api_key', 'action'],
680
+ required: ['action'],
670
681
  },
671
682
  },
672
683
  {
@@ -693,7 +704,7 @@ Actions:
693
704
  inputSchema: {
694
705
  type: 'object',
695
706
  properties: {
696
- api_key: { type: 'string', description: 'Your api_key' },
707
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
697
708
  action: {
698
709
  type: 'string',
699
710
  enum: ['create', 'view', 'mine', 'submit_seller_evidence', 'available', 'vote', 'eligibility', 'verifier_status', 'apply', 'withdraw_application', 'appeal'],
@@ -716,7 +727,7 @@ Actions:
716
727
  // appeal
717
728
  reason: { type: 'string', description: 'Appeal reason (required for appeal, ≤500 chars)' },
718
729
  },
719
- required: ['api_key', 'action'],
730
+ required: ['action'],
720
731
  },
721
732
  },
722
733
  {
@@ -741,7 +752,7 @@ Actions: list (no auth) | publish (seller) | subscribe / unsubscribe (buyer) | m
741
752
  inputSchema: {
742
753
  type: 'object',
743
754
  properties: {
744
- api_key: { type: 'string', description: 'Your api_key (omit for list)' },
755
+ api_key: { type: 'string', description: 'Your api_key (omit for list) (or set the WEBAZ_API_KEY env var)' },
745
756
  action: {
746
757
  type: 'string',
747
758
  enum: ['list', 'publish', 'subscribe', 'unsubscribe', 'my_skills', 'my_subs'],
@@ -805,7 +816,7 @@ Public-profile actions:
805
816
  inputSchema: {
806
817
  type: 'object',
807
818
  properties: {
808
- api_key: { type: 'string', description: 'Your api_key (required for view/add_role/switch_role/view_user; optional for public feed)' },
819
+ api_key: { type: 'string', description: 'Your api_key (required for view/add_role/switch_role/view_user; optional for public feed) (or set the WEBAZ_API_KEY env var)' },
809
820
  action: {
810
821
  type: 'string',
811
822
  enum: ['view', 'add_role', 'switch_role', 'view_user', 'feed'],
@@ -839,10 +850,10 @@ Use revoke when: PERMANENT decommission of agent/device, OR want access death NO
839
850
  inputSchema: {
840
851
  type: 'object',
841
852
  properties: {
842
- api_key: { type: 'string', description: 'Your current api_key (the one to revoke)' },
853
+ api_key: { type: 'string', description: 'Your current api_key (the one to revoke) (or set the WEBAZ_API_KEY env var)' },
843
854
  reason: { type: 'string', description: 'Optional: leaked / lost_device / rotation / unspecified' },
844
855
  },
845
- required: ['api_key'],
856
+ required: [],
846
857
  },
847
858
  },
848
859
  {
@@ -854,46 +865,46 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
854
865
  inputSchema: {
855
866
  type: 'object',
856
867
  properties: {
857
- api_key: { type: 'string', description: 'Your current api_key (will be invalidated after PWA confirm)' },
868
+ api_key: { type: 'string', description: 'Your current api_key (will be invalidated after PWA confirm) (or set the WEBAZ_API_KEY env var)' },
858
869
  reason: { type: 'string', description: 'Optional: rotation / leaked / scheduled' },
859
870
  },
860
- required: ['api_key'],
871
+ required: [],
861
872
  },
862
873
  },
863
874
  {
864
875
  name: 'webaz_referral',
865
876
  description: `View your referral status: 3-tier commission team + earnings + invite links + points-matching tier progress + L1 share permission gate + **rewards_status (RFC-002 §3.5 opt-in state + pending escrow)**.
866
877
 
867
- ⚠️ **Opt-in required (RFC-002)**: rewards default = off. \`rewards_status\` field returns 4-state {opted_in | never_activated | auto_downgraded | deactivated} + pending_escrow tally. Opted-out users still see attribution + tree, but commission held in escrow until activation via PWA #me.
878
+ ⚠️ **Opt-in required (RFC-002)**: rewards default = off. \`rewards_status\` field returns 4-state {opted_in | never_activated | auto_downgraded | deactivated} + pending_escrow tally. Opted-out users still see attribution + tree. Commission destination differs by state: **never_activated / auto_downgraded** → held in pending_commission_escrow, recoverable by (re-)activating within the window via PWA #me; **deactivated** (active opt-out) → future L1/L2/L3 commission goes to commission_reserve / protocol reserve, NOT escrow and NOT recoverable (re-applying only affects future commission).
868
879
 
869
880
  ⚠️ **Consent required**: generating referral links / promoting on a human user's behalf needs the user's explicit authorization. Agent **MUST get explicit consent** before generating referral links / promoting. Do NOT auto-recruit.`,
870
881
  inputSchema: {
871
882
  type: 'object',
872
883
  properties: {
873
- api_key: { type: 'string', description: 'Your api_key' },
884
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
874
885
  },
875
- required: ['api_key'],
886
+ required: [],
876
887
  },
877
888
  },
878
889
  {
879
890
  name: 'webaz_share_link',
880
891
  description: `Generate product share link with your referral attached. Open in any social platform (TikTok/WeChat/Telegram). Clicker registers/buys → counts toward your 3-tier commission (if verified buyer) + points-matching.
881
892
 
882
- ⚠️ **Opt-in required (RFC-002 §3.5)**: this is a valuation-layer action. Caller must have \`rewards_opted_in=1\` (builder-identity opt-in). Opted-out users get \`{error: 'rewards_opt_in_required', missing_requirements, next_steps}\` — direct user to PWA #me to apply.
893
+ ⚠️ **Opt-in required (RFC-002 §3.5)**: this is a valuation-layer (rewards) action, not a contribution gate. Caller must have \`rewards_opted_in=1\` (rewards / share-commission opt-in). Opted-out users get \`{error: 'rewards_opt_in_required', missing_requirements, next_steps}\` — direct user to PWA #me to apply.
883
894
 
884
895
  ⚠️ **Consent required**: this builds a referral chain on the user's behalf. Agent acting for a human user **MUST get explicit consent**. Do NOT auto-generate. See webaz_info.commission_model.`,
885
896
  inputSchema: {
886
897
  type: 'object',
887
898
  properties: {
888
- api_key: { type: 'string', description: 'Your api_key' },
899
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
889
900
  product_id: { type: 'string', description: 'Product to promote (from webaz_search)' },
890
901
  side: {
891
902
  type: 'string',
892
- enum: ['left', 'right', 'auto'],
893
- description: 'Which side of your binary tree the new user lands. auto = weaker leg (default)',
903
+ enum: ['auto'],
904
+ description: 'Deprecated / no-op placement is always automatic (system-decided). Left/right选择已下线。',
894
905
  },
895
906
  },
896
- required: ['api_key', 'product_id'],
907
+ required: ['product_id'],
897
908
  },
898
909
  },
899
910
  {
@@ -907,12 +918,12 @@ Actions: list | block | unblock.`,
907
918
  inputSchema: {
908
919
  type: 'object',
909
920
  properties: {
910
- api_key: { type: 'string', description: 'Your api_key' },
921
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
911
922
  action: { type: 'string', enum: ['list', 'block', 'unblock'], description: 'list: my blocked users | block: add | unblock: remove' },
912
923
  user_id: { type: 'string', description: 'Target user id (required for block/unblock)' },
913
924
  reason: { type: 'string', description: 'Optional reason for block (e.g. "fake product", "abuse")' },
914
925
  },
915
- required: ['api_key', 'action'],
926
+ required: ['action'],
916
927
  },
917
928
  },
918
929
  {
@@ -921,11 +932,11 @@ Actions: list | block | unblock.`,
921
932
  inputSchema: {
922
933
  type: 'object',
923
934
  properties: {
924
- api_key: { type: 'string', description: 'Your api_key' },
935
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
925
936
  action: { type: 'string', enum: ['list', 'follow', 'unfollow', 'status'], description: 'list: my follows + followers | follow/unfollow: change relation | status: check if I follow a user' },
926
937
  user_id: { type: 'string', description: 'Target user (required for follow/unfollow/status)' },
927
938
  },
928
- required: ['api_key', 'action'],
939
+ required: ['action'],
929
940
  },
930
941
  },
931
942
  {
@@ -939,12 +950,12 @@ USE THIS for "what's popular near me / 我附近 / 同城" — geo-aggregated, n
939
950
  inputSchema: {
940
951
  type: 'object',
941
952
  properties: {
942
- api_key: { type: 'string', description: 'Your api_key' },
953
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
943
954
  action: { type: 'string', enum: ['query', 'set_location', 'clear_location'], description: 'query: get aggregated nearby activity | set_location: set your geo cell | clear_location: remove' },
944
955
  lat: { type: 'number', description: 'Latitude -90..90 (for set_location, auto-truncated to 0.1°)' },
945
956
  lng: { type: 'number', description: 'Longitude -180..180 (for set_location, auto-truncated to 0.1°)' },
946
957
  },
947
- required: ['api_key', 'action'],
958
+ required: ['action'],
948
959
  },
949
960
  },
950
961
  {
@@ -956,12 +967,12 @@ USE THIS for "what's popular near me / 我附近 / 同城" — geo-aggregated, n
956
967
  inputSchema: {
957
968
  type: 'object',
958
969
  properties: {
959
- api_key: { type: 'string', description: 'Your api_key' },
970
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
960
971
  action: { type: 'string', enum: ['read', 'set'], description: 'read: get current default | set: update' },
961
972
  text: { type: 'string', description: 'Full address as free-text string (e.g. "John Doe / 1 Test St / Singapore SG / +65 12345678"). Required for set. ≤ 200 chars.' },
962
973
  region: { type: 'string', description: 'Region tag for shipping match (e.g. "global", "china", "SG"). Optional for set. ≤ 40 chars.' },
963
974
  },
964
- required: ['api_key', 'action'],
975
+ required: ['action'],
965
976
  },
966
977
  },
967
978
  {
@@ -977,7 +988,7 @@ Actions: list_mine | add (external_url + product/anchor) | delete | by_product |
977
988
  inputSchema: {
978
989
  type: 'object',
979
990
  properties: {
980
- api_key: { type: 'string', description: 'Your api_key' },
991
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
981
992
  action: { type: 'string', enum: ['list_mine', 'add', 'delete', 'by_product', 'by_anchor'], description: 'list_mine | add (need external_url + product/anchor) | delete | by_product | by_anchor' },
982
993
  external_url: { type: 'string', description: 'For action=add' },
983
994
  title: { type: 'string', description: 'For action=add (optional)' },
@@ -986,7 +997,7 @@ Actions: list_mine | add (external_url + product/anchor) | delete | by_product |
986
997
  related_anchor: { type: 'string', description: 'For action=add or by_anchor' },
987
998
  shareable_id: { type: 'string', description: 'For action=delete' },
988
999
  },
989
- required: ['api_key', 'action'],
1000
+ required: ['action'],
990
1001
  },
991
1002
  },
992
1003
  // ── P3 RFQ / bid / chat / auto_bid(MCP 通过 HTTP 调 PWA,复用所有校验+状态机)────
@@ -1015,7 +1026,7 @@ Shipping address falls back to webaz_default_address if omitted.`,
1015
1026
  inputSchema: {
1016
1027
  type: 'object',
1017
1028
  properties: {
1018
- api_key: { type: 'string', description: 'Your api_key' },
1029
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1019
1030
  action: { type: 'string', enum: ['create', 'mine', 'browse', 'detail', 'award', 'cancel'] },
1020
1031
  // create
1021
1032
  title: { type: 'string' },
@@ -1034,7 +1045,7 @@ Shipping address falls back to webaz_default_address if omitted.`,
1034
1045
  rfq_id: { type: 'string' },
1035
1046
  bid_id: { type: 'string', description: 'Optional for award — if omitted, auto-pick current lowest bid' },
1036
1047
  },
1037
- required: ['api_key', 'action'],
1048
+ required: ['action'],
1038
1049
  },
1039
1050
  },
1040
1051
  {
@@ -1048,7 +1059,7 @@ Actions: submit (rfq_id + price + qty_offered + fulfillment_type; optional eta/n
1048
1059
  inputSchema: {
1049
1060
  type: 'object',
1050
1061
  properties: {
1051
- api_key: { type: 'string', description: 'Seller api_key' },
1062
+ api_key: { type: 'string', description: 'Seller api_key (or set the WEBAZ_API_KEY env var)' },
1052
1063
  action: { type: 'string', enum: ['submit', 'patch', 'cancel', 'list_mine'] },
1053
1064
  rfq_id: { type: 'string' },
1054
1065
  bid_id: { type: 'string' },
@@ -1059,7 +1070,7 @@ Actions: submit (rfq_id + price + qty_offered + fulfillment_type; optional eta/n
1059
1070
  note: { type: 'string' },
1060
1071
  offer_id: { type: 'string', description: 'Optional; reference existing offer' },
1061
1072
  },
1062
- required: ['api_key', 'action'],
1073
+ required: ['action'],
1063
1074
  },
1064
1075
  },
1065
1076
  {
@@ -1079,7 +1090,7 @@ webaz_blocklist hides from search but does NOT auto-silence existing convs (busi
1079
1090
  inputSchema: {
1080
1091
  type: 'object',
1081
1092
  properties: {
1082
- api_key: { type: 'string' },
1093
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1083
1094
  action: { type: 'string', enum: ['start', 'list', 'read', 'send', 'mark_read', 'block'] },
1084
1095
  kind: { type: 'string', enum: ['order', 'rfq', 'listing_qa'] },
1085
1096
  context_id: { type: 'string' },
@@ -1087,7 +1098,7 @@ webaz_blocklist hides from search but does NOT auto-silence existing convs (busi
1087
1098
  conversation_id: { type: 'string' },
1088
1099
  body: { type: 'string', description: 'Message body for send action (≤2000 chars)' },
1089
1100
  },
1090
- required: ['api_key', 'action'],
1101
+ required: ['action'],
1091
1102
  },
1092
1103
  },
1093
1104
  {
@@ -1127,7 +1138,7 @@ Actions (15):
1127
1138
  inputSchema: {
1128
1139
  type: 'object',
1129
1140
  properties: {
1130
- api_key: { type: 'string' },
1141
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1131
1142
  action: { type: 'string', enum: ['list', 'detail', 'create', 'claim', 'proof', 'confirm', 'disclose', 'cancel', 'me', 'stories', 'leaderboard', 'repay', 'repay_respond', 'donate', 'fund'] },
1132
1143
  wish_id: { type: 'string' },
1133
1144
  fulfillment_id: { type: 'string' },
@@ -1163,7 +1174,7 @@ Actions: create (seller, needs title/price/stock + content_hash sha256 + content
1163
1174
  inputSchema: {
1164
1175
  type: 'object',
1165
1176
  properties: {
1166
- api_key: { type: 'string' },
1177
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1167
1178
  action: { type: 'string', enum: ['create', 'list', 'detail', 'patch'] },
1168
1179
  product_id: { type: 'string' },
1169
1180
  title: { type: 'string' }, price: { type: 'number' }, stock: { type: 'number' },
@@ -1188,11 +1199,11 @@ Actions: toggle (same endpoint; 2nd call auto-unlikes) | status (my like status
1188
1199
  inputSchema: {
1189
1200
  type: 'object',
1190
1201
  properties: {
1191
- api_key: { type: 'string' },
1202
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1192
1203
  action: { type: 'string', enum: ['toggle', 'status'] },
1193
1204
  shareable_id: { type: 'string' },
1194
1205
  },
1195
- required: ['api_key', 'action', 'shareable_id'],
1206
+ required: ['action', 'shareable_id'],
1196
1207
  },
1197
1208
  },
1198
1209
  {
@@ -1237,7 +1248,7 @@ Actions:
1237
1248
  inputSchema: {
1238
1249
  type: 'object',
1239
1250
  properties: {
1240
- api_key: { type: 'string' },
1251
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1241
1252
  action: { type: 'string', enum: ['create', 'browse', 'mine', 'detail', 'bid', 'cancel'] },
1242
1253
  title: { type: 'string' },
1243
1254
  qty: { type: 'number' },
@@ -1251,7 +1262,7 @@ Actions:
1251
1262
  auction_id: { type: 'string' },
1252
1263
  price: { type: 'number' },
1253
1264
  },
1254
- required: ['api_key', 'action'],
1265
+ required: ['action'],
1255
1266
  },
1256
1267
  },
1257
1268
  {
@@ -1265,7 +1276,7 @@ Actions: get | set (categories[] / regions[] / max_eta_h / bid_strategy) | disab
1265
1276
  inputSchema: {
1266
1277
  type: 'object',
1267
1278
  properties: {
1268
- api_key: { type: 'string' },
1279
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1269
1280
  action: { type: 'string', enum: ['get', 'set', 'disable'] },
1270
1281
  categories: { type: 'array', items: { type: 'string' } },
1271
1282
  regions: { type: 'array', items: { type: 'string' } },
@@ -1278,7 +1289,7 @@ Actions: get | set (categories[] / regions[] / max_eta_h / bid_strategy) | disab
1278
1289
  cooldown_min: { type: 'number' },
1279
1290
  enabled: { type: 'boolean' },
1280
1291
  },
1281
- required: ['api_key', 'action'],
1292
+ required: ['action'],
1282
1293
  },
1283
1294
  },
1284
1295
  {
@@ -1298,7 +1309,7 @@ Actions: list (no auth, filters: kind/billing/query) | detail (public, no conten
1298
1309
  inputSchema: {
1299
1310
  type: 'object',
1300
1311
  properties: {
1301
- api_key: { type: 'string', description: 'Your api_key (omit for list/detail)' },
1312
+ api_key: { type: 'string', description: 'Your api_key (omit for list/detail) (or set the WEBAZ_API_KEY env var)' },
1302
1313
  action: {
1303
1314
  type: 'string',
1304
1315
  enum: ['list', 'detail', 'publish', 'update', 'delist', 'resubmit', 'purchase', 'read', 'my_skills', 'library'],
@@ -1335,7 +1346,7 @@ Enums: **category** phone/computer/appliance/furniture/clothing/book/toy/sports/
1335
1346
  inputSchema: {
1336
1347
  type: 'object',
1337
1348
  properties: {
1338
- api_key: { type: 'string', description: 'Your api_key (omit for browse/detail)' },
1349
+ api_key: { type: 'string', description: 'Your api_key (omit for browse/detail) (or set the WEBAZ_API_KEY env var)' },
1339
1350
  action: { type: 'string', enum: ['browse', 'detail', 'publish', 'update', 'mine', 'buy'], description: 'Action to execute' },
1340
1351
  item_id: { type: 'string', description: 'Item ID (required for detail/update/buy)' },
1341
1352
  // publish / update
@@ -1384,7 +1395,7 @@ Seller actions:
1384
1395
  inputSchema: {
1385
1396
  type: 'object',
1386
1397
  properties: {
1387
- api_key: { type: 'string', description: 'Your api_key (omit for get_campaign)' },
1398
+ api_key: { type: 'string', description: 'Your api_key (omit for get_campaign) (or set the WEBAZ_API_KEY env var)' },
1388
1399
  action: { type: 'string', enum: ['get_campaign', 'apply', 'link_note', 'my_claims', 'create_campaign', 'cancel_campaign', 'my_campaigns', 'campaign_claims'], description: 'Action to execute' },
1389
1400
  product_id: { type: 'string', description: 'Product ID (required for get_campaign/apply/create_campaign/cancel_campaign)' },
1390
1401
  claim_id: { type: 'string', description: 'Claim ID (required for link_note)' },
@@ -1413,7 +1424,7 @@ Gate by type: ux_issue/bug (reporting = using) → login only, NO Passkey, anyon
1413
1424
  type: 'object',
1414
1425
  properties: {
1415
1426
  action: { type: 'string', enum: ['submit', 'my', 'get'], description: 'submit (default) | my | get' },
1416
- api_key: { type: 'string', description: "User's api_key (real person required)" },
1427
+ api_key: { type: 'string', description: "User's api_key (real person required; or set the WEBAZ_API_KEY env var)" },
1417
1428
  type: { type: 'string', enum: ['ux_issue', 'bug', 'proposal'], description: 'submit: kind of feedback' },
1418
1429
  area: { type: 'string', description: 'submit: which feature, e.g. search / order / dispute' },
1419
1430
  severity: { type: 'string', enum: ['low', 'annoying', 'blocking'], description: 'submit: for ux_issue/bug' },
@@ -1421,33 +1432,49 @@ Gate by type: ux_issue/bug (reporting = using) → login only, NO Passkey, anyon
1421
1432
  text: { type: 'string', description: 'submit: the feedback / idea (≥5 chars)' },
1422
1433
  feedback_id: { type: 'string', description: 'get: the feedback id' },
1423
1434
  },
1424
- required: ['api_key'],
1435
+ required: [],
1425
1436
  },
1426
1437
  },
1427
1438
  {
1428
1439
  name: 'webaz_contribute',
1429
- description: `Coordinate building WebAZ itself (RFC-006) — a claim board so contributors don't collide. Check BEFORE starting work on an area. Day-to-day small changes; large ones go via RFC. 协调"谁在做什么"防撞车.
1440
+ description: `Coordinate building WebAZ itself (RFC-006 / RFC-017) — a public task board so contributors don't collide. Check BEFORE starting work on an area. 协调"谁在做什么"防撞车.
1441
+
1442
+ Discovery + suggesting need NO api_key (anyone / any agent can browse and propose). Claiming + submitting need an api_key (a real, accountable identity).
1430
1443
 
1431
1444
  Actions:
1432
- - list_open (default): open tasks (opt. area filter)
1433
- - claim: take an open task; provenance=human|ai_assisted|ai_authored (self-declared, not detected); auto-expires ~7d if not submitted. Returns a **handoff** (repo + AGENTS.md + PR flow) point a coding agent at it to actually do the work; the human needn't know git.
1434
- - submit: mark in_review with pr_ref
1435
- - status: tasks you hold (claimed/in_review)
1436
- - profile: your build dashboard KPI/tier/restrictions+appeal, self-view (private, no public leaderboard)
1445
+ - list_open (default): open public tasks (opt. filters: area / risk_level / auto_claimable / required_capabilities / agent_capabilities / max_duration_minutes / estimated_context_size / estimated_agent_budget — estimated_agent_budget is a resource/effort estimate, NOT a payment). Each task carries its execution boundary + the trusted canonical contribution target. NO api_key needed.
1446
+ - detail: one task's full execution boundary (allowed/forbidden paths, prohibited actions, acceptance criteria, verification commands, deliverables, definition_of_done) + the canonical repo to PR to + a copy-ready agent_handoff. NO api_key needed.
1447
+ - suggest: propose a NEW task (title + summary/reason; opt. area/expected_outcome/source_ref/github_login). It enters the maintainer inbox — it is a suggestion, NOT a contribution fact / reward / participation, and never auto-becomes a task. NO api_key needed.
1448
+ - claim: take an open task (api_key); provenance=human|ai_assisted|ai_authored (self-declared, not detected); auto-expires ~7d if not submitted. Returns a handoff — point a coding agent at it; the human needn't know git but stays accountable (Passkey).
1449
+ - submit: mark in_review with pr_ref + verification_summary (api_key). The PR's base repo MUST be the canonical WebAZ repo, and a verification_summary (what you ran/verified) is REQUIRED — both server-enforced. A human maintainer reviews next; done ≠ merge.
1450
+ - status: tasks you hold (api_key).
1451
+ - profile: your build dashboard — KPI/tier/restrictions+appeal, private self-view (api_key).
1437
1452
 
1438
- Coordinates + records only — NO merge/reward; acceptance (done) = human maintainer. build_reputation is a SEPARATE pool, never gates verifier/arbitrator. Login required; NETWORK only.`,
1453
+ Coordinates + records only — NO merge/reward; acceptance (done) = human maintainer. Contribution value is uncommitted (RFC-017 I-12). build_reputation is a SEPARATE pool, never gates verifier/arbitrator. NETWORK only (contribution is a real-network act; sandbox has nothing to coordinate with).`,
1439
1454
  inputSchema: {
1440
1455
  type: 'object',
1441
1456
  properties: {
1442
- action: { type: 'string', enum: ['list_open', 'claim', 'submit', 'status', 'profile'], description: 'list_open (default) | claim | submit | status | profile' },
1443
- api_key: { type: 'string', description: "User's api_key (accountable identity)" },
1444
- task_id: { type: 'string', description: 'claim / submit: the task id' },
1445
- area: { type: 'string', description: 'list_open: optional area filter (e.g. search / docs / mcp)' },
1457
+ action: { type: 'string', enum: ['list_open', 'detail', 'suggest', 'claim', 'submit', 'status', 'profile'], description: 'list_open (default) | detail | suggest | claim | submit | status | profile' },
1458
+ api_key: { type: 'string', description: 'claim/submit/status/profile: your api_key (accountable identity). NOT needed for list_open/detail/suggest. (or set the WEBAZ_API_KEY env var)' },
1459
+ task_id: { type: 'string', description: 'detail / claim / submit: the task id' },
1460
+ area: { type: 'string', description: 'list_open: area filter / suggest: suggested area (e.g. search / docs / mcp)' },
1461
+ risk_level: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], description: 'list_open: optional risk filter' },
1462
+ auto_claimable: { type: 'boolean', description: 'list_open: optional filter — only auto-claimable (true) or manual-claim (false) tasks' },
1463
+ required_capabilities: { type: 'string', description: 'list_open: optional filter — comma-separated; matches tasks that REQUIRE ALL of the listed capabilities (superset/AND match on the task requirement). For "tasks my agent can do", use agent_capabilities instead.' },
1464
+ agent_capabilities: { type: 'string', description: 'list_open: optional filter — capabilities your agent HAS (comma-separated); matches tasks whose required_capabilities are a SUBSET of these, i.e. tasks your agent can actually do' },
1465
+ max_duration_minutes: { type: 'number', description: 'list_open: optional filter — only tasks whose estimated max duration fits within this many minutes (your idle time)' },
1466
+ estimated_context_size: { type: 'string', enum: ['small', 'medium', 'large'], description: 'list_open: optional filter — task estimated context size' },
1467
+ estimated_agent_budget: { type: 'string', enum: ['minimal', 'small', 'moderate', 'large', 'xlarge'], description: 'list_open: optional filter — task estimated agent budget (resource/effort estimate, not a payment)' },
1446
1468
  provenance: { type: 'string', enum: ['human', 'ai_assisted', 'ai_authored'], description: 'claim: self-declared authorship (default human)' },
1447
- pr_ref: { type: 'string', description: 'submit: your PR link or number' },
1469
+ pr_ref: { type: 'string', description: 'submit: your PR link or number (must target the canonical repo)' },
1470
+ verification_summary: { type: 'string', description: 'submit (REQUIRED): summarize what you ran/verified — the task verification_commands you ran and their results' },
1448
1471
  note: { type: 'string', description: 'submit: optional note' },
1472
+ title: { type: 'string', description: 'suggest: task title (≥3 chars)' },
1473
+ summary: { type: 'string', description: 'suggest: why it is worth doing / what it solves (the reason)' },
1474
+ expected_outcome: { type: 'string', description: 'suggest: optional — what should be true when done' },
1475
+ source_ref: { type: 'string', description: 'suggest: optional reference link (reference only; does NOT set the target repo)' },
1476
+ proposer_github_login: { type: 'string', description: 'suggest: optional — your GitHub login' },
1449
1477
  },
1450
- required: ['api_key'],
1451
1478
  },
1452
1479
  },
1453
1480
  ];
@@ -1455,7 +1482,7 @@ Coordinates + records only — NO merge/reward; acceptance (done) = human mainta
1455
1482
  // RFC-004: webaz_feedback — agent-native "use → build" 反馈(双模;仅 NETWORK 能送达)
1456
1483
  async function handleFeedback(args) {
1457
1484
  const action = args.action || 'submit';
1458
- const apiKey = args.api_key;
1485
+ const apiKey = resolveMcpApiKey(args);
1459
1486
  if (!apiKey)
1460
1487
  return { error: 'api_key required' };
1461
1488
  if (toolBackend('webaz_feedback') !== 'network') {
@@ -1489,19 +1516,100 @@ async function handleFeedback(args) {
1489
1516
  },
1490
1517
  });
1491
1518
  }
1492
- // RFC-006 Gap 1: webaz_contribute 协调"谁在做什么"(双模;仅 NETWORK)
1493
- async function handleContribute(args) {
1519
+ // RFC-006 断点1(b)交接:从【可信】canonical 目标(API 响应里,绝不硬编码/不取自 task metadata)构造"怎么真正
1520
+ // 动手"。人的编码 agent 做 git/PR;Passkey 真人担责。sandbox 运行 / 本地草稿不算正式参与。
1521
+ function buildContributeHandoff(cct, taskId) {
1522
+ const c = (cct ?? {});
1523
+ const repoUrl = c.canonical_github_url || 'https://github.com/seasonsagents-art/webaz';
1524
+ const baseRepo = c.expected_pr_base_repo || c.canonical_repository_full_name || 'seasonsagents-art/webaz';
1525
+ const baseBranch = c.base_branch || 'main';
1526
+ return {
1527
+ canonical_repo: baseRepo,
1528
+ repo: repoUrl,
1529
+ base_branch: baseBranch,
1530
+ start_here: 'Read AGENTS.md (project map + before-you-code + PR flow), then CONTRIBUTING.md.',
1531
+ do_the_work: 'Point a coding agent (e.g. Claude Code) at the repo on a single-topic branch. The buyer/shopping agent is not the coding agent — hand off to one.',
1532
+ submit_pr: `Open a PR whose BASE repo is ${baseRepo} (${repoUrl}), base branch ${baseBranch}. If any target repo differs from this canonical repo, STOP and ask the human — never contribute to a non-canonical repository.`,
1533
+ pr_flow: 'Commit with DCO sign-off (git commit -s). If AI-authored, mark the PR per the meta-rule. Humans merge — no auto-merge.',
1534
+ then: `When the PR is open, report it back: webaz_contribute action=submit task_id=${taskId} pr_ref=#<N> verification_summary="<the verification_commands you ran + their results>". Both pr_ref and verification_summary are required.`,
1535
+ not_participation: 'A sandbox run or a local-only draft is NOT participation and is NOT a contribution; only a merged PR (or recognized issue/task/RFC) on the canonical repo enters the contribution record.',
1536
+ human_note: "You don't need to know git — your coding agent does it; you (the Passkey-bound human) stay accountable.",
1537
+ };
1538
+ }
1539
+ // RFC-006/RFC-017: webaz_contribute — 协调"谁在做什么"。NETWORK only. Discovery + suggest 无需 key(打公开
1540
+ // 端口 #329/#331);claim/submit/status/profile 需 key(真实可问责身份,走受 #330 守卫的 member 端口)。
1541
+ export async function handleContribute(args) {
1494
1542
  const action = args.action || 'list_open';
1495
- const apiKey = args.api_key;
1496
- if (!apiKey)
1497
- return { error: 'api_key required' };
1543
+ const apiKey = resolveMcpApiKey(args);
1498
1544
  if (toolBackend('webaz_contribute') !== 'network') {
1499
1545
  return {
1500
1546
  _mode: 'sandbox',
1501
- error: 'SANDBOX 模式无协调对象 —— 协调要在真实项目上才有意义。请设 WEBAZ_API_KEY 切到 NETWORK 模式。 / Coordination needs NETWORK mode; set WEBAZ_API_KEY.',
1547
+ error: 'SANDBOX 模式无协调对象 —— 协调要在真实项目上才有意义。请设 WEBAZ_API_KEY 切到 NETWORK 模式(或不设 key 默认 network_readonly 也可浏览/建议)。 / Coordination needs the live network; sandbox has nothing to coordinate with.',
1502
1548
  error_code: 'CONTRIBUTE_NEEDS_NETWORK',
1503
1549
  };
1504
1550
  }
1551
+ // ── keyless discovery + suggest (public surface; same trusted canonical target as the PWA) ──
1552
+ if (action === 'list_open') {
1553
+ // public endpoint already restricts to audience=public + status=open; only pass the optional filters.
1554
+ const qs = new URLSearchParams();
1555
+ if (args.area)
1556
+ qs.set('area', String(args.area));
1557
+ if (args.risk_level)
1558
+ qs.set('risk_level', String(args.risk_level));
1559
+ if (args.auto_claimable !== undefined)
1560
+ qs.set('auto_claimable', String(Boolean(args.auto_claimable)));
1561
+ if (args.required_capabilities)
1562
+ qs.set('required_capabilities', String(args.required_capabilities));
1563
+ if (args.agent_capabilities !== undefined)
1564
+ qs.set('agent_capabilities', String(args.agent_capabilities)); // forward even '' so the route fail-closes (typed 400), never silently returns the full list
1565
+ if (args.max_duration_minutes !== undefined)
1566
+ qs.set('max_duration_minutes', String(args.max_duration_minutes));
1567
+ if (args.estimated_context_size)
1568
+ qs.set('estimated_context_size', String(args.estimated_context_size));
1569
+ if (args.estimated_agent_budget)
1570
+ qs.set('estimated_agent_budget', String(args.estimated_agent_budget));
1571
+ const q = qs.toString();
1572
+ const r = await apiCall('/api/public/build-tasks' + (q ? '?' + q : ''));
1573
+ if (!r.error)
1574
+ r._next = 'Pick a task, then: webaz_contribute action=detail task_id=<id> for its full execution boundary + the canonical repo to PR to; then action=claim task_id=<id> api_key=<key> to take it (claiming needs an account).';
1575
+ return r;
1576
+ }
1577
+ if (action === 'detail') {
1578
+ const tid = args.task_id;
1579
+ if (!tid)
1580
+ return { error: 'task_id required for action=detail' };
1581
+ const r = await apiCall('/api/public/build-tasks/' + encodeURIComponent(tid));
1582
+ if (!r.error && r.task)
1583
+ r.agent_handoff = buildContributeHandoff(r.canonical_contribution_target, tid);
1584
+ return r;
1585
+ }
1586
+ if (action === 'suggest') {
1587
+ const title = (args.title ?? '').trim();
1588
+ const summary = (args.summary ?? args.note ?? '').trim();
1589
+ if (title.length < 3)
1590
+ return { error: 'title required (≥3 chars) for action=suggest' };
1591
+ if (summary.length < 1)
1592
+ return { error: 'summary (the reason) required for action=suggest' };
1593
+ const r = await apiCall('/api/public/task-proposals', {
1594
+ method: 'POST',
1595
+ body: {
1596
+ title, summary,
1597
+ suggested_area: args.area ?? args.suggested_area,
1598
+ expected_outcome: args.expected_outcome,
1599
+ source_ref: args.source_ref,
1600
+ proposer_github_login: args.proposer_github_login,
1601
+ },
1602
+ });
1603
+ // typed errors (RATE_LIMITED / DUPLICATE_PROPOSAL / validation) are already mapped by apiCall; the
1604
+ // success response already carries the route-level `proposal_notice` (suggestion ≠ contribution/reward).
1605
+ return r;
1606
+ }
1607
+ // ── participation: a real, accountable identity is required ──
1608
+ if (!apiKey)
1609
+ return {
1610
+ error: `api_key required for action=${action} — set WEBAZ_API_KEY (request an invite at ${WEBAZ_API_URL}/#welcome). Discovery (list_open / detail) and suggest work WITHOUT a key.`,
1611
+ error_code: 'API_KEY_REQUIRED',
1612
+ };
1505
1613
  if (action === 'status')
1506
1614
  return apiCall('/api/build-tasks?mine=1', { apiKey });
1507
1615
  if (action === 'profile')
@@ -1513,31 +1621,25 @@ async function handleContribute(args) {
1513
1621
  const r = await apiCall('/api/build-tasks/' + encodeURIComponent(tid) + '/claim', {
1514
1622
  method: 'POST', apiKey, body: { provenance: args.provenance },
1515
1623
  });
1516
- // RFC-006 断点1(b)交接:认领成功后直接下发"怎么真正动手",让贡献者的【编码 agent】接手 git/PR。
1517
- // 关键:人不必会 git——人的编码 agent( Claude Code)做;人(Passkey 真人)担责。
1518
- if (!r.error) {
1519
- r.handoff = {
1520
- repo: 'https://github.com/seasonsagents-art/webaz',
1521
- start_here: 'Read AGENTS.md (project map + before-you-code + PR flow), then CONTRIBUTING.md.',
1522
- do_the_work: 'Point a coding agent (e.g. Claude Code) at the repo; work on a single-topic branch. The buyer/shopping agent is not the coding agent — hand off to one.',
1523
- pr_flow: 'Commit with DCO sign-off (git commit -s). If AI-authored, add 🤖🤖🤖 to the PR title + a meta-rule trace. Humans merge — no auto-merge.',
1524
- then: `When the PR is open, report it back: webaz_contribute action=submit task_id=${tid} pr_ref=#<N>.`,
1525
- human_note: "You don't need to know git — your coding agent does it; you (the Passkey-bound human) stay accountable.",
1526
- };
1527
- }
1624
+ if (!r.error)
1625
+ r.handoff = buildContributeHandoff(r.canonical_contribution_target, tid);
1528
1626
  return r;
1529
1627
  }
1530
1628
  if (action === 'submit') {
1531
1629
  const tid = args.task_id;
1532
1630
  if (!tid)
1533
1631
  return { error: 'task_id required for action=submit' };
1534
- return apiCall('/api/build-tasks/' + encodeURIComponent(tid) + '/submit', {
1535
- method: 'POST', apiKey, body: { pr_ref: args.pr_ref, note: args.note },
1632
+ const vs = (args.verification_summary ?? '').trim();
1633
+ if (vs.length < 1)
1634
+ return { error: 'verification_summary required for action=submit — summarize what you ran/verified (the task verification_commands and their results)', error_code: 'VERIFICATION_SUMMARY_REQUIRED' };
1635
+ const r = await apiCall('/api/build-tasks/' + encodeURIComponent(tid) + '/submit', {
1636
+ method: 'POST', apiKey, body: { pr_ref: args.pr_ref, note: args.note, verification_summary: vs },
1536
1637
  });
1638
+ if (!r.error)
1639
+ r._next = 'A human maintainer reviews next — acceptance (done) is manual and done ≠ merge. Track it with webaz_contribute action=status.';
1640
+ return r;
1537
1641
  }
1538
- // list_open(默认)
1539
- const q = args.area ? '?status=open&area=' + encodeURIComponent(String(args.area)) : '?status=open';
1540
- return apiCall('/api/build-tasks' + q, { apiKey });
1642
+ return { error: 'unknown action: ' + action };
1541
1643
  }
1542
1644
  async function handleInfo() {
1543
1645
  const summary = getManifestSummary();
@@ -1641,10 +1743,10 @@ async function handleInfo() {
1641
1743
  split: '7:2:1 — L1 70% / L2 20% / L3 10% of an order\'s commission_pool',
1642
1744
  jurisdiction_tiers: 'Tiers are graded by the order region\'s max_levels — NOT a uniform 3 tiers everywhere. e.g. global region max_levels=1 → L1 only; singapore (etc.) max_levels=3 → up to L3. A region may also be 0 (no commission tiers; pool → community fund).',
1643
1745
  attribution: 'EXPLICIT per-order — commission goes to the promoter attributed at purchase time, not derived from the buyer\'s sponsor chain.',
1644
- how_to_attribute: 'L1: webaz_place_order(promoter_api_key) records the direct promoter. Full L2/L3 chain requires the buyer to arrive via a webaz_share_link ?ref= URL clicked in a browser (builds product_share_attribution).',
1746
+ how_to_attribute: 'L1: webaz_place_order(promoter_api_key) records the direct promoter. Full L2/L3 chain requires the buyer to arrive via a webaz_share_link /i/<permanent_code> (?ref=<permanent_code>) URL clicked in a browser (builds product_share_attribution).',
1645
1747
  redirect_rules: 'chain_gap (no L / invalid sponsor) → charity_fund; level beyond the region cap → global_fund.',
1646
1748
  l1_gate: 'the promoter must be a verified buyer (≥1 completed order) to receive commission, otherwise that share redirects.',
1647
- opt_in: 'Participation is opt-in (RFC-002): default = off. A user applies (Passkey + ≥1 completed order); attribution is always recorded, but commission settlement is gated until opt-in (pending held in pending_commission_escrow, 30d grace). See docs/rfcs/RFC-002-rewards-opt-in.md.',
1749
+ opt_in: 'Participation is opt-in (RFC-002): default = off. A user applies (Passkey + ≥1 completed order); attribution is always recorded. Commission destination is state-dependent: never_activated / auto_downgraded held in pending_commission_escrow (30d grace), recoverable by (re-)activating within the window, else swept to commission_reserve; deactivated (active opt-out) → future commission goes directly to commission_reserve, NOT escrow and NOT recoverable. Never to charity_fund. See docs/rfcs/RFC-002-rewards-opt-in.md.',
1648
1750
  },
1649
1751
  // QA 轮 3 FAIL:roles 漏 reviewer。register 工具支持 5 个角色,info 必须列全。
1650
1752
  roles: {
@@ -1957,8 +2059,8 @@ async function handleSearch(args) {
1957
2059
  : '没有找到匹配的商品';
1958
2060
  return { found: 0, message: hint, products: [], matched_by: 'strict_no_match' };
1959
2061
  }
1960
- const enriched = products.map((p) => {
1961
- const boost = getSearchBoost(db, p.seller_id);
2062
+ const enriched = await Promise.all(products.map(async (p) => {
2063
+ const boost = await getSearchBoost(db, p.seller_id);
1962
2064
  const rep_level = p.rep_level || 'new';
1963
2065
  const rep_points = Number(p.rep_points) || 0;
1964
2066
  const completion = Number(p.completion_count) || 0;
@@ -1983,7 +2085,7 @@ async function handleSearch(args) {
1983
2085
  const firstSaleBoost = firstSold && (Date.now() - new Date(firstSold.replace(' ', 'T') + 'Z').getTime()) < 14 * 86400_000 ? 5 : 0;
1984
2086
  const score = completion * 0.5 + rep_points * 0.1 + sharer * 2.0 + freshness + firstSaleBoost - dispute * 5.0;
1985
2087
  return { ...p, _boost: boost, _rep_level: rep_level, _rep_points: rep_points, _score: score, _freshness: freshness, _first_sale_boost: firstSaleBoost };
1986
- });
2088
+ }));
1987
2089
  const sorted = sortMode === 'trending'
1988
2090
  ? enriched.sort((a, b) => b._score - a._score || b._boost - a._boost).slice(0, limit)
1989
2091
  : enriched.slice(0, limit);
@@ -2045,11 +2147,11 @@ async function handleVerifyPrice(args) {
2045
2147
  if (toolBackend('webaz_verify_price') === 'network') {
2046
2148
  return apiCall('/api/verify-price', {
2047
2149
  method: 'POST',
2048
- apiKey: args.api_key,
2150
+ apiKey: resolveMcpApiKey(args),
2049
2151
  body: { product_id: args.product_id, quantity: Number(args.quantity ?? 1) },
2050
2152
  });
2051
2153
  }
2052
- const auth = requireAuth(db, args.api_key);
2154
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2053
2155
  if ('error' in auth)
2054
2156
  return auth;
2055
2157
  const { user } = auth;
@@ -2095,7 +2197,7 @@ async function handleVerifyPrice(args) {
2095
2197
  async function handleListProduct(args) {
2096
2198
  // Wave 3 audit P0: 加 action 分发 — agent 卖家能完整管理目录(不止 create)
2097
2199
  const action = args.action || 'create';
2098
- const apiKey = args.api_key;
2200
+ const apiKey = resolveMcpApiKey(args);
2099
2201
  if (!apiKey)
2100
2202
  return { error: 'api_key required' };
2101
2203
  // RFC-003 P2b: NETWORK 模式 — 卖家目录管理全部转发生产端点(单一真相源)
@@ -2212,7 +2314,7 @@ async function handleListProduct(args) {
2212
2314
  // stake 在 place_order 那一刻按"该订单总额 × stake_rate"现锁,订单结算时退该笔,违约时扣该笔。
2213
2315
  // 这样每个 active 订单都有独立 stake 担保,多 stock 商品也不会被空头薅。
2214
2316
  // product.stake_amount 字段保留为"indicative rate × price"(前端展示用),不强制 lock。
2215
- const stakeDiscount = getStakeDiscount(db, user.id);
2317
+ const stakeDiscount = await getStakeDiscount(db, user.id);
2216
2318
  const stakeRate = Math.max(0.05, 0.15 - stakeDiscount); // 最低 5%,声誉越高折扣越大
2217
2319
  const stakeAmount = Math.round(price * stakeRate * 100) / 100; // indicative only; actual lock per-order
2218
2320
  const id = generateId('prd');
@@ -2273,9 +2375,9 @@ async function handlePlaceOrder(args) {
2273
2375
  body.shipping_address = args.shipping_address;
2274
2376
  if (args.donation_pct != null)
2275
2377
  body.donation_pct = args.donation_pct;
2276
- return apiCall('/api/orders', { method: 'POST', apiKey: args.api_key, body });
2378
+ return apiCall('/api/orders', { method: 'POST', apiKey: resolveMcpApiKey(args), body });
2277
2379
  }
2278
- const auth = requireAuth(db, args.api_key);
2380
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2279
2381
  if ('error' in auth)
2280
2382
  return auth;
2281
2383
  const { user } = auth;
@@ -2442,7 +2544,7 @@ async function handleUpdateOrder(args) {
2442
2544
  return { error: 'order_id and action required' };
2443
2545
  return apiCall(`/api/orders/${encodeURIComponent(orderId)}/action`, {
2444
2546
  method: 'POST',
2445
- apiKey: args.api_key,
2547
+ apiKey: resolveMcpApiKey(args),
2446
2548
  body: {
2447
2549
  action,
2448
2550
  notes: args.notes ?? '',
@@ -2452,7 +2554,7 @@ async function handleUpdateOrder(args) {
2452
2554
  },
2453
2555
  });
2454
2556
  }
2455
- const auth = requireAuth(db, args.api_key);
2557
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2456
2558
  if ('error' in auth)
2457
2559
  return auth;
2458
2560
  const { user } = auth;
@@ -2466,7 +2568,7 @@ async function handleUpdateOrder(args) {
2466
2568
  // agent-native 协议要求"哪个接口进结果一致"。MCP confirm 不再自己结算,
2467
2569
  // 走 PWA /api/orders/:id/action 的 settleOrder + settleCommission(authoritative)。
2468
2570
  if (action === 'confirm') {
2469
- const apiKey = args.api_key;
2571
+ const apiKey = resolveMcpApiKey(args);
2470
2572
  const result = await pwaApi('POST', `/orders/${encodeURIComponent(orderId)}/action`, apiKey, {
2471
2573
  action: 'confirm',
2472
2574
  notes,
@@ -2575,9 +2677,9 @@ async function handleGetStatus(args) {
2575
2677
  const orderId = args.order_id;
2576
2678
  if (!orderId)
2577
2679
  return { error: 'order_id required' };
2578
- return apiCall(`/api/orders/${encodeURIComponent(orderId)}`, { apiKey: args.api_key });
2680
+ return apiCall(`/api/orders/${encodeURIComponent(orderId)}`, { apiKey: resolveMcpApiKey(args) });
2579
2681
  }
2580
- const auth = requireAuth(db, args.api_key);
2682
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2581
2683
  if ('error' in auth)
2582
2684
  return auth;
2583
2685
  const statusInfo = getOrderStatus(db, args.order_id);
@@ -2614,7 +2716,7 @@ async function handleGetStatus(args) {
2614
2716
  async function handleWallet(args) {
2615
2717
  // Wave 3 audit P0: 加 action 分发 — agent 能查充值/提现/收入历史(写操作仍 UI-only 走 2FA)
2616
2718
  const action = args.action || 'view';
2617
- const apiKey = args.api_key;
2719
+ const apiKey = resolveMcpApiKey(args);
2618
2720
  if (!apiKey)
2619
2721
  return { error: 'api_key required' };
2620
2722
  // RFC-003 Batch 4:NETWORK 模式 → webaz.xyz 真网络【只读】(Bearer api_key)。
@@ -2682,20 +2784,20 @@ async function handleWallet(args) {
2682
2784
  async function handleNotifications(args) {
2683
2785
  // RFC-003 Batch 1:NETWORK 模式 → 调 webaz.xyz 真网络通知端点(Bearer api_key);SANDBOX 走本地。
2684
2786
  if (toolBackend('webaz_notifications') === 'network') {
2685
- const apiKey = String(args.api_key || '');
2787
+ const apiKey = resolveMcpApiKey(args);
2686
2788
  if (!apiKey)
2687
2789
  return { error: 'api_key required' };
2688
2790
  if (args.mark_read)
2689
2791
  await apiCall('/api/notifications/read', { method: 'POST', apiKey });
2690
2792
  return await apiCall('/api/notifications' + (args.unread === true ? '?unread=1' : ''), { apiKey });
2691
2793
  }
2692
- const auth = requireAuth(db, args.api_key);
2794
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2693
2795
  if ('error' in auth)
2694
2796
  return auth;
2695
2797
  const { user } = auth;
2696
2798
  const onlyUnread = args.unread === true;
2697
- const notifs = getNotifications(db, user.id, onlyUnread, 30);
2698
- const unread = getUnreadCount(db, user.id);
2799
+ const notifs = await getNotifications(db, user.id, onlyUnread, 30);
2800
+ const unread = await getUnreadCount(db, user.id);
2699
2801
  if (args.mark_read) {
2700
2802
  markRead(db, user.id);
2701
2803
  }
@@ -2713,7 +2815,7 @@ async function handleNotifications(args) {
2713
2815
  }
2714
2816
  // ─── 争议处理 ─────────────────────────────────────────────────
2715
2817
  async function handleDispute(args) {
2716
- const apiKey = args.api_key;
2818
+ const apiKey = resolveMcpApiKey(args);
2717
2819
  if (!apiKey)
2718
2820
  return { error: 'api_key required' };
2719
2821
  const action = args.action;
@@ -2782,9 +2884,9 @@ async function handleDispute(args) {
2782
2884
  // ── 查看争议详情 ────────────────────────────────────────────
2783
2885
  if (action === 'view') {
2784
2886
  let dispute = args.dispute_id
2785
- ? getDisputeDetails(db, args.dispute_id)
2887
+ ? await getDisputeDetails(db, args.dispute_id)
2786
2888
  : args.order_id
2787
- ? getOrderDispute(db, args.order_id)
2889
+ ? await getOrderDispute(db, args.order_id)
2788
2890
  : null;
2789
2891
  if (!dispute)
2790
2892
  return { error: '找不到争议记录,请提供 dispute_id 或 order_id' };
@@ -2824,7 +2926,7 @@ async function handleDispute(args) {
2824
2926
  if (user.role !== 'arbitrator') {
2825
2927
  return { error: '只有仲裁员可以查看所有待处理争议' };
2826
2928
  }
2827
- const disputes = getOpenDisputes(db);
2929
+ const disputes = await getOpenDisputes(db);
2828
2930
  return {
2829
2931
  open_count: disputes.length,
2830
2932
  disputes: disputes.map(d => ({
@@ -2848,7 +2950,7 @@ async function handleDispute(args) {
2848
2950
  // 如有证据描述,先创建证据记录
2849
2951
  const evidenceIds = [];
2850
2952
  if (args.evidence_description) {
2851
- const dispute = getDisputeDetails(db, args.dispute_id);
2953
+ const dispute = await getDisputeDetails(db, args.dispute_id);
2852
2954
  if (dispute) {
2853
2955
  const eid = generateId('evt');
2854
2956
  db.prepare(`
@@ -2876,7 +2978,7 @@ async function handleDispute(args) {
2876
2978
  }
2877
2979
  // ─── 索赔验证(claim-verification)处理 — Wave 6 新增 ────────────
2878
2980
  async function handleClaimVerify(args) {
2879
- const apiKey = args.api_key;
2981
+ const apiKey = resolveMcpApiKey(args);
2880
2982
  if (!apiKey)
2881
2983
  return { error: 'api_key required' };
2882
2984
  const action = String(args.action || '');
@@ -2964,7 +3066,7 @@ async function handleSkill(args) {
2964
3066
  const action = args.action;
2965
3067
  // RFC-003 Batch 3:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地引擎。
2966
3068
  if (toolBackend('webaz_skill') === 'network') {
2967
- const apiKey = String(args.api_key || '');
3069
+ const apiKey = resolveMcpApiKey(args);
2968
3070
  if (action === 'list') {
2969
3071
  const qs = new URLSearchParams();
2970
3072
  if (args.skill_type)
@@ -3001,12 +3103,12 @@ async function handleSkill(args) {
3001
3103
  // ── 浏览 Skill 市场 ────────────────────────────────────────
3002
3104
  if (action === 'list') {
3003
3105
  let userId;
3004
- if (args.api_key) {
3005
- const a = requireAuth(db, args.api_key);
3106
+ if (resolveMcpApiKey(args)) {
3107
+ const a = requireAuth(db, resolveMcpApiKey(args));
3006
3108
  if (!('error' in a))
3007
3109
  userId = a.user.id;
3008
3110
  }
3009
- const skills = listSkills(db, {
3111
+ const skills = await listSkills(db, {
3010
3112
  skillType: args.skill_type,
3011
3113
  query: args.query,
3012
3114
  subscriberId: userId,
@@ -3019,7 +3121,7 @@ async function handleSkill(args) {
3019
3121
  };
3020
3122
  }
3021
3123
  // 以下操作需要身份验证
3022
- const auth = requireAuth(db, args.api_key);
3124
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3023
3125
  if ('error' in auth)
3024
3126
  return auth;
3025
3127
  const { user } = auth;
@@ -3064,7 +3166,7 @@ async function handleSkill(args) {
3064
3166
  }
3065
3167
  // ── 我发布的 Skill ────────────────────────────────────────
3066
3168
  if (action === 'my_skills') {
3067
- const skills = getMySkills(db, user.id);
3169
+ const skills = await getMySkills(db, user.id);
3068
3170
  return {
3069
3171
  total: skills.length,
3070
3172
  skills: skills.map(formatSkillForAgent),
@@ -3073,7 +3175,7 @@ async function handleSkill(args) {
3073
3175
  }
3074
3176
  // ── 我订阅的 Skill ────────────────────────────────────────
3075
3177
  if (action === 'my_subs') {
3076
- const skills = getMySubscriptions(db, user.id);
3178
+ const skills = await getMySubscriptions(db, user.id);
3077
3179
  return {
3078
3180
  total: skills.length,
3079
3181
  subscriptions: skills.map(formatSkillForAgent),
@@ -3160,7 +3262,7 @@ function handleMyKey(args) {
3160
3262
  }
3161
3263
  async function handleProfile(args) {
3162
3264
  const action = args.action;
3163
- const apiKey = String(args.api_key || '');
3265
+ const apiKey = resolveMcpApiKey(args);
3164
3266
  // RFC-003 Batch 1:NETWORK 模式 → 全部 action 调 webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3165
3267
  if (toolBackend('webaz_profile') === 'network') {
3166
3268
  if (action === 'view_user') {
@@ -3257,7 +3359,7 @@ async function handleProfile(args) {
3257
3359
  function handleRevokeKey(args) {
3258
3360
  // RFC-003 Batch 5:NETWORK 模式 → 不本地校验 key(PWA 会鉴权),直接返回 Passkey 撤销指引。
3259
3361
  if (toolBackend('webaz_revoke_key') === 'network') {
3260
- const apiKey = String(args.api_key || '');
3362
+ const apiKey = resolveMcpApiKey(args);
3261
3363
  const reason = (args.reason || 'unspecified').trim().slice(0, 100);
3262
3364
  return {
3263
3365
  _mode: 'network',
@@ -3275,7 +3377,7 @@ function handleRevokeKey(args) {
3275
3377
  },
3276
3378
  };
3277
3379
  }
3278
- const auth = requireAuth(db, args.api_key);
3380
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3279
3381
  if ('error' in auth)
3280
3382
  return auth;
3281
3383
  const { user } = auth;
@@ -3299,7 +3401,7 @@ function handleRevokeKey(args) {
3299
3401
  function handleRotateKey(args) {
3300
3402
  // RFC-003 Batch 5:NETWORK 模式 → 不本地校验 key(PWA 会鉴权),直接返回 Passkey 轮换指引。
3301
3403
  if (toolBackend('webaz_rotate_key') === 'network') {
3302
- const apiKey = String(args.api_key || '');
3404
+ const apiKey = resolveMcpApiKey(args);
3303
3405
  const reason = (args.reason || 'rotation').trim().slice(0, 100);
3304
3406
  return {
3305
3407
  _mode: 'network',
@@ -3316,7 +3418,7 @@ function handleRotateKey(args) {
3316
3418
  },
3317
3419
  };
3318
3420
  }
3319
- const auth = requireAuth(db, args.api_key);
3421
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3320
3422
  if ('error' in auth)
3321
3423
  return auth;
3322
3424
  const { user } = auth;
@@ -3340,12 +3442,12 @@ function handleRotateKey(args) {
3340
3442
  async function handleReferral(args) {
3341
3443
  // RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络聚合(Bearer api_key);SANDBOX 走本地。
3342
3444
  if (toolBackend('webaz_referral') === 'network') {
3343
- const apiKey = String(args.api_key || '');
3445
+ const apiKey = resolveMcpApiKey(args);
3344
3446
  if (!apiKey)
3345
3447
  return { error: 'api_key required' };
3346
3448
  return await apiCall('/api/referral/me', { apiKey });
3347
3449
  }
3348
- const auth = requireAuth(db, args.api_key);
3450
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3349
3451
  if ('error' in auth)
3350
3452
  return auth;
3351
3453
  const { user } = auth;
@@ -3373,10 +3475,14 @@ async function handleReferral(args) {
3373
3475
  const tiers = db.prepare("SELECT tier, pv_threshold, score_per_hit FROM binary_tier_config WHERE active=1 ORDER BY tier ASC").all();
3374
3476
  const pair = Math.min(Number(me?.total_left_pv ?? 0), Number(me?.total_right_pv ?? 0));
3375
3477
  const nextTier = tiers.find(t => t.pv_threshold > pair);
3478
+ // invite / share links use permanent_code ONLY — never usr_xxx. (sandbox users have one from register.)
3479
+ const permaCode = db.prepare("SELECT permanent_code FROM users WHERE id = ?").get(userId)?.permanent_code || null;
3376
3480
  return {
3377
3481
  user_id: userId,
3378
3482
  name: user.name,
3379
- base_referral_link: `/?ref=${userId}`, // 仅推土机
3483
+ invite_code: permaCode,
3484
+ invite_unavailable_reason: permaCode ? null : 'permanent_code_missing — re-register or contact support',
3485
+ base_referral_link: permaCode ? `/i/${permaCode}` : null, // 仅推土机
3380
3486
  region: user.region ?? 'global',
3381
3487
  permissions: {
3382
3488
  can_earn_l1_commission: canL1,
@@ -3391,10 +3497,8 @@ async function handleReferral(args) {
3391
3497
  grand_total: byLevel[1].total + byLevel[2].total + byLevel[3].total,
3392
3498
  },
3393
3499
  binary: {
3394
- left_invite_link: `/?ref=${userId}&side=left`,
3395
- right_invite_link: `/?ref=${userId}&side=right`,
3396
- platform_left: `/?placement=${userId}&side=left`, // 仅 PV 条线
3397
- platform_right: `/?placement=${userId}&side=right`,
3500
+ // pre-public 去左右码:只暴露唯一的推荐码;放置侧别由系统自动决定(无 left/right 选择)
3501
+ referral_link: permaCode ? `/i/${permaCode}` : null,
3398
3502
  total_left_pv: Number(me?.total_left_pv ?? 0),
3399
3503
  total_right_pv: Number(me?.total_right_pv ?? 0),
3400
3504
  pair_volume: pair,
@@ -3414,7 +3518,7 @@ async function handleReferral(args) {
3414
3518
  }
3415
3519
  else if (lastAction === 'deactivate') {
3416
3520
  state = 'deactivated';
3417
- note = 'You actively deactivated rewards. Future commissions redirect directly to charity_fund (no escrow). You can re-apply via PWA #me.';
3521
+ note = 'You actively deactivated rewards. Future L1/L2/L3 commissions go to commission_reserve / protocol reserve, not charity_fund and not pending escrow. Re-applying only affects future commissions.';
3418
3522
  }
3419
3523
  else if (lastAction === 'auto_downgrade') {
3420
3524
  state = 'auto_downgraded';
@@ -3432,7 +3536,7 @@ async function handleReferral(args) {
3432
3536
  note,
3433
3537
  pending_escrow: { count: pending.n, total_amount: pending.total },
3434
3538
  expired_to_charity: { count: expired.n, total_amount: expired.total },
3435
- spec: 'RFC-002 §3.5 — rewards opt-in / 共建身份申请制',
3539
+ spec: 'RFC-002 §3.5 — rewards / share-commission opt-in (RFC-002)',
3436
3540
  };
3437
3541
  })(),
3438
3542
  tip: canL1
@@ -3443,23 +3547,21 @@ async function handleReferral(args) {
3443
3547
  async function handleShareLink(args) {
3444
3548
  // RFC-003 #1122:NETWORK 模式 → 调 webaz.xyz 的 /api/share-link(服务端同款计算);SANDBOX 走本地。
3445
3549
  if (toolBackend('webaz_share_link') === 'network') {
3446
- const apiKey = String(args.api_key || '');
3550
+ const apiKey = resolveMcpApiKey(args);
3447
3551
  if (!apiKey)
3448
3552
  return { error: 'api_key required' };
3449
3553
  if (!args.product_id)
3450
3554
  return { error: 'product_id required' };
3555
+ // pre-public 去左右码:不再向 /api/share-link 转发 side(放置永远自动)
3451
3556
  const qs = new URLSearchParams({ product_id: String(args.product_id) });
3452
- if (args.side)
3453
- qs.set('side', String(args.side));
3454
3557
  return await apiCall('/api/share-link?' + qs.toString(), { apiKey });
3455
3558
  }
3456
- const auth = requireAuth(db, args.api_key);
3559
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3457
3560
  if ('error' in auth)
3458
3561
  return auth;
3459
3562
  const { user } = auth;
3460
3563
  const userId = user.id;
3461
3564
  const productId = args.product_id;
3462
- const sideArg = args.side || 'auto';
3463
3565
  // RFC-002 §3.5 valuation-layer gate — share_link generation requires opt-in
3464
3566
  const optIn = db.prepare("SELECT rewards_opted_in FROM users WHERE id = ?").get(userId)?.rewards_opted_in ?? 0;
3465
3567
  if (optIn !== 1) {
@@ -3480,10 +3582,10 @@ async function handleShareLink(args) {
3480
3582
  missing.push('application_not_submitted');
3481
3583
  return {
3482
3584
  error: 'rewards_opt_in_required',
3483
- message: 'Share-link generation is a valuation-layer action — requires builder-identity opt-in (RFC-002 §3.5)',
3585
+ message: 'Share-link generation is a valuation-layer (rewards / share-link) action, NOT a contribution gate — requires rewards / share-commission opt-in (RFC-002 §3.5)',
3484
3586
  missing_requirements: missing,
3485
3587
  next_steps: [
3486
- 'Open PWA #me → tap "申请共建身份 / Apply for builder identity"',
3588
+ 'Open PWA #me → tap "申请分享分润 / Enable share-commission opt-in"',
3487
3589
  'Read the 8-second disclosure (cannot skip)',
3488
3590
  'Submit application — pre-checks run server-side',
3489
3591
  ],
@@ -3492,43 +3594,21 @@ async function handleShareLink(args) {
3492
3594
  const product = db.prepare("SELECT id, title, price, commission_rate FROM products WHERE id = ? AND status='active'").get(productId);
3493
3595
  if (!product)
3494
3596
  return { error: '商品不存在或已下架' };
3495
- let side = 'right';
3496
- if (sideArg === 'left' || sideArg === 'right') {
3497
- side = sideArg;
3498
- }
3499
- else {
3500
- // auto = 与 PWA pickPreferredSide 对齐:尊重 placement_pref(team_count | pv_count)
3501
- // 老版只看 total_left_pv vs total_right_pv,team_count 用户被错算
3502
- const u = db.prepare("SELECT placement_pref, total_left_pv, total_right_pv, left_count, right_count FROM users WHERE id = ?")
3503
- .get(userId);
3504
- const pref = u?.placement_pref || 'team_count';
3505
- if (pref === 'pv_count') {
3506
- const since = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 19).replace('T', ' ');
3507
- const w = db.prepare(`SELECT COALESCE(SUM(consumed_left_pv),0) AS l, COALESCE(SUM(consumed_right_pv),0) AS r
3508
- FROM binary_score_records WHERE user_id = ? AND created_at >= ?`)
3509
- .get(userId, since);
3510
- const leftPv = Number(u?.total_left_pv ?? 0) + Number(w.l);
3511
- const rightPv = Number(u?.total_right_pv ?? 0) + Number(w.r);
3512
- side = leftPv <= rightPv ? 'left' : 'right';
3513
- }
3514
- else {
3515
- // team_count: 整棵子树人数(增量维护的 left_count/right_count,与 PWA pickPreferredSide 对齐)。
3516
- // 2026-06-04 修:旧版沿单条脊链数(countLeg)名实不符 → 选边失真。分享链接的 side 会被注册时
3517
- // 当 placement_side 显式采用,必须与 PWA joinPowerLeg 用同一指标,否则两路径不一致。
3518
- side = (Number(u?.left_count ?? 0) <= Number(u?.right_count ?? 0)) ? 'left' : 'right';
3519
- }
3520
- }
3597
+ // pre-public 去左右码:分享链接不再携带 side(放置侧别由注册时系统自动决定)
3521
3598
  const completed = db.prepare("SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND status = 'completed'").get(userId).n;
3522
3599
  const override = db.prepare("SELECT l1_share_override FROM users WHERE id = ?").get(userId)?.l1_share_override ?? 0;
3523
3600
  const canL1 = override === 1 || (override === 0 && completed > 0);
3524
3601
  const rate = Number(product.commission_rate ?? 0);
3525
- const link = `/?ref=${userId}&side=${side}#order-product/${productId}`;
3602
+ // share ref uses permanent_code ONLY — never usr_xxx
3603
+ const permaCode = db.prepare("SELECT permanent_code FROM users WHERE id = ?").get(userId)?.permanent_code || null;
3604
+ if (!permaCode)
3605
+ return { error: 'permanent_code_missing — cannot build a share link; re-register or contact support', error_code: 'PERMANENT_CODE_MISSING' };
3606
+ const link = `/?ref=${permaCode}#order-product/${productId}`;
3526
3607
  return {
3527
3608
  product: { id: product.id, title: product.title, price: product.price, commission_rate: rate },
3528
3609
  share_link: link,
3529
3610
  full_url_hint: 'Prepend webaz.xyz (production) or http://localhost:3000 (local) to get the absolute URL',
3530
- side,
3531
- binary_explanation: `New user via this link → placed in your ${side === 'left' ? '🔵 left' : '🟢 right'} subtree (tail anchor)`,
3611
+ placement_note: 'New user via this link → placement is recorded automatically by the system (no left/right choice).',
3532
3612
  commission_eligibility: canL1
3533
3613
  ? `You will earn 3-tier commission: L1=${(rate * 0.70 * 100).toFixed(1)}% L2=${(rate * 0.20 * 100).toFixed(1)}% L3=${(rate * 0.10 * 100).toFixed(1)}% of sale price`
3534
3614
  : 'You are NOT verified yet (need 1 completed purchase). 3-tier commission will be skipped, but points-matching still builds.',
@@ -3539,7 +3619,7 @@ async function handleShareLink(args) {
3539
3619
  async function handleBlocklist(args) {
3540
3620
  // RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3541
3621
  if (toolBackend('webaz_blocklist') === 'network') {
3542
- const apiKey = String(args.api_key || '');
3622
+ const apiKey = resolveMcpApiKey(args);
3543
3623
  if (!apiKey)
3544
3624
  return { error: 'api_key required' };
3545
3625
  const act = String(args.action || '');
@@ -3554,7 +3634,7 @@ async function handleBlocklist(args) {
3554
3634
  return await apiCall('/api/blocklist/' + uid, { method: 'DELETE', apiKey });
3555
3635
  return { error: `unknown action: ${act}` };
3556
3636
  }
3557
- const auth = requireAuth(db, args.api_key);
3637
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3558
3638
  if ('error' in auth)
3559
3639
  return auth;
3560
3640
  const { user } = auth;
@@ -3593,7 +3673,7 @@ async function handleBlocklist(args) {
3593
3673
  async function handleFollows(args) {
3594
3674
  // RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3595
3675
  if (toolBackend('webaz_follows') === 'network') {
3596
- const apiKey = String(args.api_key || '');
3676
+ const apiKey = resolveMcpApiKey(args);
3597
3677
  if (!apiKey)
3598
3678
  return { error: 'api_key required' };
3599
3679
  const act = String(args.action || '');
@@ -3610,7 +3690,7 @@ async function handleFollows(args) {
3610
3690
  return await apiCall('/api/follows/' + uid + '/status', { apiKey });
3611
3691
  return { error: `unknown action: ${act}` };
3612
3692
  }
3613
- const auth = requireAuth(db, args.api_key);
3693
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3614
3694
  if ('error' in auth)
3615
3695
  return auth;
3616
3696
  const { user } = auth;
@@ -3659,7 +3739,7 @@ async function handleNearby(args) {
3659
3739
  const action = String(args.action || '');
3660
3740
  // RFC-003 Batch 1:NETWORK 模式 → 调 webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3661
3741
  if (toolBackend('webaz_nearby') === 'network') {
3662
- const apiKey = String(args.api_key || '');
3742
+ const apiKey = resolveMcpApiKey(args);
3663
3743
  if (!apiKey)
3664
3744
  return { error: 'api_key required' };
3665
3745
  if (action === 'set_location')
@@ -3677,7 +3757,7 @@ async function handleNearby(args) {
3677
3757
  }
3678
3758
  return { error: `unknown action: ${action}` };
3679
3759
  }
3680
- const auth = requireAuth(db, args.api_key);
3760
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3681
3761
  if ('error' in auth)
3682
3762
  return auth;
3683
3763
  const { user } = auth;
@@ -3728,7 +3808,7 @@ async function handleNearby(args) {
3728
3808
  async function handleDefaultAddress(args) {
3729
3809
  // RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3730
3810
  if (toolBackend('webaz_default_address') === 'network') {
3731
- const apiKey = String(args.api_key || '');
3811
+ const apiKey = resolveMcpApiKey(args);
3732
3812
  if (!apiKey)
3733
3813
  return { error: 'api_key required' };
3734
3814
  const act = String(args.action || '');
@@ -3747,7 +3827,7 @@ async function handleDefaultAddress(args) {
3747
3827
  }
3748
3828
  return { error: `unknown action: ${act}` };
3749
3829
  }
3750
- const auth = requireAuth(db, args.api_key);
3830
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3751
3831
  if ('error' in auth)
3752
3832
  return auth;
3753
3833
  const { user } = auth;
@@ -3788,7 +3868,7 @@ async function handleShareables(args) {
3788
3868
  const action = String(args.action || '');
3789
3869
  // RFC-003 Batch 1:NETWORK 模式 → 调 webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3790
3870
  if (toolBackend('webaz_shareables') === 'network') {
3791
- const apiKey = String(args.api_key || '');
3871
+ const apiKey = resolveMcpApiKey(args);
3792
3872
  if (!apiKey)
3793
3873
  return { error: 'api_key required' };
3794
3874
  if (action === 'list_mine')
@@ -3816,7 +3896,7 @@ async function handleShareables(args) {
3816
3896
  }
3817
3897
  return { error: `unknown action: ${action}` };
3818
3898
  }
3819
- const auth = requireAuth(db, args.api_key);
3899
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3820
3900
  if ('error' in auth)
3821
3901
  return auth;
3822
3902
  const { user } = auth;
@@ -3972,7 +4052,7 @@ async function pwaApi(method, path, apiKey, body) {
3972
4052
  }
3973
4053
  }
3974
4054
  async function handleSecondhand(args) {
3975
- const apiKey = String(args.api_key || '');
4055
+ const apiKey = resolveMcpApiKey(args);
3976
4056
  const action = String(args.action || '');
3977
4057
  const isPublic = action === 'browse' || action === 'detail';
3978
4058
  if (!isPublic) {
@@ -4043,7 +4123,7 @@ async function handleSecondhand(args) {
4043
4123
  }
4044
4124
  }
4045
4125
  async function handleTrial(args) {
4046
- const apiKey = String(args.api_key || '');
4126
+ const apiKey = resolveMcpApiKey(args);
4047
4127
  const action = String(args.action || '');
4048
4128
  const isPublic = action === 'get_campaign';
4049
4129
  if (!isPublic) {
@@ -4096,7 +4176,7 @@ async function handleTrial(args) {
4096
4176
  }
4097
4177
  }
4098
4178
  async function handleSkillMarket(args) {
4099
- const apiKey = String(args.api_key || '');
4179
+ const apiKey = resolveMcpApiKey(args);
4100
4180
  const action = String(args.action || '');
4101
4181
  const isPublic = action === 'list' || action === 'detail';
4102
4182
  if (!isPublic) {
@@ -4169,7 +4249,7 @@ async function handleSkillMarket(args) {
4169
4249
  }
4170
4250
  }
4171
4251
  async function handleRfq(args) {
4172
- const apiKey = String(args.api_key || '');
4252
+ const apiKey = resolveMcpApiKey(args);
4173
4253
  const action = String(args.action || '');
4174
4254
  if (!apiKey)
4175
4255
  return { error: 'api_key required' };
@@ -4218,7 +4298,7 @@ async function handleRfq(args) {
4218
4298
  }
4219
4299
  }
4220
4300
  async function handleBid(args) {
4221
- const apiKey = String(args.api_key || '');
4301
+ const apiKey = resolveMcpApiKey(args);
4222
4302
  const action = String(args.action || '');
4223
4303
  if (!apiKey)
4224
4304
  return { error: 'api_key required' };
@@ -4292,7 +4372,7 @@ async function handleBid(args) {
4292
4372
  }
4293
4373
  }
4294
4374
  async function handleChat(args) {
4295
- const apiKey = String(args.api_key || '');
4375
+ const apiKey = resolveMcpApiKey(args);
4296
4376
  const action = String(args.action || '');
4297
4377
  if (!apiKey)
4298
4378
  return { error: 'api_key required' };
@@ -4334,7 +4414,7 @@ async function handleChat(args) {
4334
4414
  }
4335
4415
  }
4336
4416
  async function handleAutoBidSkill(args) {
4337
- const apiKey = String(args.api_key || '');
4417
+ const apiKey = resolveMcpApiKey(args);
4338
4418
  const action = String(args.action || '');
4339
4419
  if (!apiKey)
4340
4420
  return { error: 'api_key required' };
@@ -4457,7 +4537,7 @@ async function handleCharity(args) {
4457
4537
  return readEndpoint('webaz_charity', '/charity/leaderboard');
4458
4538
  if (action === 'fund')
4459
4539
  return readEndpoint('webaz_charity', '/charity/fund');
4460
- const apiKey = String(args.api_key || '');
4540
+ const apiKey = resolveMcpApiKey(args);
4461
4541
  if (!apiKey)
4462
4542
  return { error: 'api_key required for this action' };
4463
4543
  if (toolBackend('webaz_charity') !== 'network') {
@@ -4520,7 +4600,7 @@ async function handleP2pProduct(args) {
4520
4600
  return { error: String(e.message) };
4521
4601
  }
4522
4602
  }
4523
- const apiKey = String(args.api_key || '');
4603
+ const apiKey = resolveMcpApiKey(args);
4524
4604
  if (!apiKey)
4525
4605
  return { error: 'api_key required for create/patch' };
4526
4606
  const auth = requireAuth(db, apiKey);
@@ -4547,7 +4627,7 @@ async function handleP2pProduct(args) {
4547
4627
  return { error: `unknown action: ${action}` };
4548
4628
  }
4549
4629
  async function handleLike(args) {
4550
- const apiKey = String(args.api_key || '');
4630
+ const apiKey = resolveMcpApiKey(args);
4551
4631
  const action = String(args.action || '');
4552
4632
  const sid = String(args.shareable_id || '');
4553
4633
  if (!apiKey || !sid)
@@ -4573,7 +4653,7 @@ async function handleLeaderboard(args) {
4573
4653
  return readEndpoint('webaz_leaderboard', '/leaderboard?kind=' + kind + '&limit=' + limit);
4574
4654
  }
4575
4655
  async function handleAuction(args) {
4576
- const apiKey = String(args.api_key || '');
4656
+ const apiKey = resolveMcpApiKey(args);
4577
4657
  const action = String(args.action || '');
4578
4658
  if (!apiKey)
4579
4659
  return { error: 'api_key required' };
@@ -4706,7 +4786,7 @@ export async function startMCPServer() {
4706
4786
  if (request.params.uri !== MANIFEST_URI) {
4707
4787
  throw new Error(`未知资源:${request.params.uri}`);
4708
4788
  }
4709
- const manifest = generateManifest(db);
4789
+ const manifest = await generateManifest(db);
4710
4790
  return {
4711
4791
  contents: [
4712
4792
  {
@@ -5031,7 +5111,7 @@ function addHours(date, hours) {
5031
5111
  function recordToolCall(tool, args, result, latencyMs) {
5032
5112
  let userId = null;
5033
5113
  try {
5034
- const apiKey = args.api_key;
5114
+ const apiKey = resolveMcpApiKey(args);
5035
5115
  if (apiKey) {
5036
5116
  const row = db.prepare('SELECT id FROM users WHERE api_key = ?').get(apiKey);
5037
5117
  if (row)