@seasonkoh/webaz 0.1.25 → 0.1.27

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 (97) hide show
  1. package/LICENSE +2 -2
  2. package/NOTICE +24 -3
  3. package/README.md +74 -328
  4. package/README.zh-CN.md +419 -0
  5. package/dist/layer0-foundation/L0-2-state-machine/genuine-sale.js +21 -0
  6. package/dist/layer0-foundation/L0-5-manifest/manifest.js +8 -3
  7. package/dist/layer1-agent/L1-1-mcp-server/auth.js +13 -1
  8. package/dist/layer1-agent/L1-1-mcp-server/server.js +164 -177
  9. package/dist/layer2-business/L2-9-contribution/admin-coordination-ingestion-engine.js +181 -0
  10. package/dist/layer2-business/L2-9-contribution/admin-coordination-resolver.js +114 -0
  11. package/dist/layer2-business/L2-9-contribution/admin-coordination-store.js +251 -0
  12. package/dist/layer2-business/L2-9-contribution/admin-operator-claim-workflow.js +390 -0
  13. package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +33 -0
  14. package/dist/layer2-business/L2-9-contribution/build-task-participation.js +6 -2
  15. package/dist/layer2-business/L2-9-contribution/build-task-quota.js +337 -0
  16. package/dist/layer2-business/L2-9-contribution/build-task-read.js +25 -2
  17. package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +58 -8
  18. package/dist/layer2-business/L2-9-contribution/canonical-contribution-target.js +1 -1
  19. package/dist/layer2-business/L2-9-contribution/contribution-facts-read.js +66 -0
  20. package/dist/layer2-business/L2-9-contribution/identity-claim-discovery.js +55 -0
  21. package/dist/layer2-business/L2-9-contribution/task-proposal-ai-store.js +99 -0
  22. package/dist/layer2-business/L2-9-contribution/task-proposal-draft.js +360 -0
  23. package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +29 -4
  24. package/dist/ledger.js +1 -1
  25. package/dist/pwa/admin-audit.js +38 -0
  26. package/dist/pwa/admin-bearer-auth.js +21 -0
  27. package/dist/pwa/anti-abuse-thresholds.js +135 -0
  28. package/dist/pwa/cf-origin-guard.js +33 -0
  29. package/dist/pwa/contract-fingerprint.js +1 -0
  30. package/dist/pwa/data/onboarding-cases.js +2 -2
  31. package/dist/pwa/data/onboarding-quiz.js +1 -1
  32. package/dist/pwa/economic-participation.js +2 -2
  33. package/dist/pwa/email-delivery.js +127 -0
  34. package/dist/pwa/integration-contract.js +46 -4
  35. package/dist/pwa/internal/pv-settlement.js +12 -0
  36. package/dist/pwa/internal/wallet-signer.js +26 -0
  37. package/dist/pwa/public/app.js +1607 -912
  38. package/dist/pwa/public/i18n.js +284 -68
  39. package/dist/pwa/public/index.html +1 -1
  40. package/dist/pwa/public/openapi.json +4760 -2769
  41. package/dist/pwa/public/whitepaper/en/index.html +153 -0
  42. package/dist/pwa/public/whitepaper/zh-CN/index.html +153 -0
  43. package/dist/pwa/pv-kill-switch.js +31 -0
  44. package/dist/pwa/routes/admin-admins.js +48 -1
  45. package/dist/pwa/routes/admin-analytics.js +1 -10
  46. package/dist/pwa/routes/admin-atomic.js +7 -14
  47. package/dist/pwa/routes/admin-moderation.js +25 -1
  48. package/dist/pwa/routes/admin-operator-claims.js +280 -0
  49. package/dist/pwa/routes/admin-ops.js +13 -2
  50. package/dist/pwa/routes/admin-reports.js +4 -26
  51. package/dist/pwa/routes/admin-tokenomics.js +2 -76
  52. package/dist/pwa/routes/admin-users-lifecycle.js +1 -14
  53. package/dist/pwa/routes/admin-users-query.js +35 -2
  54. package/dist/pwa/routes/admin-wallet-ops.js +26 -3
  55. package/dist/pwa/routes/auction.js +4 -2
  56. package/dist/pwa/routes/auth-read.js +11 -6
  57. package/dist/pwa/routes/auth-register.js +84 -24
  58. package/dist/pwa/routes/build-task-quota.js +113 -0
  59. package/dist/pwa/routes/claim-verify.js +15 -11
  60. package/dist/pwa/routes/contribution-facts.js +18 -0
  61. package/dist/pwa/routes/contribution-identity.js +17 -0
  62. package/dist/pwa/routes/dispute-cases.js +5 -4
  63. package/dist/pwa/routes/growth.js +4 -4
  64. package/dist/pwa/routes/orders-action.js +46 -23
  65. package/dist/pwa/routes/orders-create.js +1 -1
  66. package/dist/pwa/routes/products-meta.js +19 -6
  67. package/dist/pwa/routes/profile-credentials.js +7 -4
  68. package/dist/pwa/routes/profile-placement.js +8 -9
  69. package/dist/pwa/routes/promoter.js +11 -44
  70. package/dist/pwa/routes/public-build-tasks.js +5 -1
  71. package/dist/pwa/routes/public-utils.js +9 -12
  72. package/dist/pwa/routes/ratings.js +64 -4
  73. package/dist/pwa/routes/recover-key.js +58 -19
  74. package/dist/pwa/routes/referral.js +9 -50
  75. package/dist/pwa/routes/rewards-apply.js +3 -2
  76. package/dist/pwa/routes/share-redirects.js +5 -4
  77. package/dist/pwa/routes/shareables-interactions.js +2 -1
  78. package/dist/pwa/routes/shop-referral.js +6 -5
  79. package/dist/pwa/routes/shops.js +5 -2
  80. package/dist/pwa/routes/task-proposals.js +159 -7
  81. package/dist/pwa/routes/trial.js +4 -2
  82. package/dist/pwa/routes/users-public.js +1 -14
  83. package/dist/pwa/routes/wallet-read.js +3 -15
  84. package/dist/pwa/routes/webauthn.js +1 -1
  85. package/dist/pwa/server.js +223 -478
  86. package/dist/settlement-math.js +3 -3
  87. package/dist/version.js +6 -4
  88. package/package.json +62 -8
  89. package/dist/index.js +0 -182
  90. package/dist/pwa/public/docs/ECONOMIC-MODEL.md +0 -287
  91. package/dist/pwa/public/docs/INTEGRATOR.md +0 -67
  92. package/dist/pwa/public/docs/META-RULES-FULL.md +0 -543
  93. package/dist/test-dispute.js +0 -153
  94. package/dist/test-manifest.js +0 -61
  95. package/dist/test-mcp-tools.js +0 -135
  96. package/dist/test-reputation.js +0 -116
  97. package/dist/test-skill-market.js +0 -101
@@ -43,6 +43,15 @@ const TELEMETRY_ENABLED = (process.env.WEBAZ_TELEMETRY ?? 'off').toLowerCase() =
43
43
  // P0 不迁移任何工具(NETWORK_TOOLS 为空)→ 一切仍走本地 = 零行为变化;P1/P2 逐个把工具名加入集合切到网络。
44
44
  const WEBAZ_API_URL = (process.env.WEBAZ_API_URL ?? 'https://webaz.xyz').replace(/\/+$/, '');
45
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
+ }
46
55
  const WEBAZ_MODE_ENV = (process.env.WEBAZ_MODE ?? '').toLowerCase();
47
56
  // 模式:显式 WEBAZ_MODE 优先;否则有 api_key → network,无 key → network_readonly(装完即见真网络)。
48
57
  // network_readonly(L1 onboarding,2026-06-08):无 key 默认。公共读匿名打 webaz.xyz(真 catalog/协议),
@@ -403,11 +412,11 @@ Skipping is allowed but agent then carries price/stock-race risk itself.`,
403
412
  inputSchema: {
404
413
  type: 'object',
405
414
  properties: {
406
- 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)" },
407
416
  product_id: { type: 'string', description: 'Product ID (from webaz_search)' },
408
417
  quantity: { type: 'number', description: 'Quantity, default 1' },
409
418
  },
410
- required: ['api_key', 'product_id'],
419
+ required: ['product_id'],
411
420
  },
412
421
  },
413
422
  {
@@ -423,7 +432,7 @@ Actions: create (title/description/price) | mine | update (product_id + changed
423
432
  inputSchema: {
424
433
  type: 'object',
425
434
  properties: {
426
- 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)" },
427
436
  action: {
428
437
  type: 'string',
429
438
  enum: ['create', 'mine', 'update', 'delist', 'relist', 'trash', 'delete'],
@@ -479,7 +488,7 @@ Actions: create (title/description/price) | mine | update (product_id + changed
479
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.',
480
489
  },
481
490
  },
482
- required: ['api_key'],
491
+ required: [],
483
492
  },
484
493
  },
485
494
  {
@@ -501,7 +510,7 @@ Options:
501
510
  inputSchema: {
502
511
  type: 'object',
503
512
  properties: {
504
- 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)" },
505
514
  product_id: { type: 'string', description: 'Product ID to buy (from webaz_search)' },
506
515
  quantity: { type: 'number', description: 'Quantity, default 1' },
507
516
  shipping_address: { type: 'string', description: 'Shipping address' },
@@ -512,7 +521,7 @@ Options:
512
521
  },
513
522
  promoter_api_key: {
514
523
  type: 'string',
515
- description: "Referrer api_key (optional). ⚠️ Only L1 recorded (direct referrer, 70% commission); L2/L3 can't be inferred via MCP, redirects per region rule (singapore-like high max_levels charity chain_gap; global max_levels=1 → global_fund region cap). Full 7:2:1 three-tier chain requires buyer clicking ?ref= URL from webaz_share_link (creates product_share_attribution).",
524
+ description: "Referrer api_key (optional). ⚠️ Only L1 recorded (direct referrer, 70% commission); L2/L3 can't be inferred via MCP, so the undelivered L2/L3 portions go to commission_reserve (protocol reserve, in-only). Full 7:2:1 three-tier chain requires buyer clicking ?ref= URL from webaz_share_link (creates product_share_attribution).",
516
525
  },
517
526
  // B2 隐私购物
518
527
  anonymous_recipient: {
@@ -526,7 +535,7 @@ Options:
526
535
  description: '[B5] Per-order donation pct (0 / 0.5 / 1 / 2 / 5). Computed separately + into charity_fund, posted on order complete.',
527
536
  },
528
537
  },
529
- required: ['api_key', 'product_id', 'shipping_address'],
538
+ required: ['product_id', 'shipping_address'],
530
539
  },
531
540
  },
532
541
  {
@@ -534,15 +543,15 @@ Options:
534
543
  // was ~927 chars, now ~430 chars
535
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.
536
545
 
537
- - **Seller**: accept (24h after payment) | ship (needs tracking, within handling time)
538
- - **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
539
548
  - **Buyer**: confirm (→ fund settlement) | dispute (needs reason; freezes funds → arbitration)
540
549
 
541
550
  Missing deadline → protocol auto-marks party in default.`,
542
551
  inputSchema: {
543
552
  type: 'object',
544
553
  properties: {
545
- 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)" },
546
555
  order_id: { type: 'string', description: 'Order ID' },
547
556
  action: {
548
557
  type: 'string',
@@ -560,7 +569,7 @@ Missing deadline → protocol auto-marks party in default.`,
560
569
  description: 'Evidence description (recommended for ship/pickup/deliver; required for dispute)',
561
570
  },
562
571
  },
563
- required: ['api_key', 'order_id', 'action'],
572
+ required: ['order_id', 'action'],
564
573
  },
565
574
  },
566
575
  {
@@ -570,10 +579,10 @@ Missing deadline → protocol auto-marks party in default.`,
570
579
  inputSchema: {
571
580
  type: 'object',
572
581
  properties: {
573
- 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)" },
574
583
  order_id: { type: 'string', description: 'Order ID' },
575
584
  },
576
- required: ['api_key', 'order_id'],
585
+ required: ['order_id'],
577
586
  },
578
587
  },
579
588
  {
@@ -591,14 +600,14 @@ Actions:
591
600
  inputSchema: {
592
601
  type: 'object',
593
602
  properties: {
594
- 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)' },
595
604
  action: {
596
605
  type: 'string',
597
606
  enum: ['view', 'deposits', 'withdrawals', 'income'],
598
607
  description: 'Action type (default: view)',
599
608
  },
600
609
  },
601
- required: ['api_key'],
610
+ required: [],
602
611
  },
603
612
  },
604
613
  {
@@ -610,11 +619,11 @@ Actions:
610
619
  inputSchema: {
611
620
  type: 'object',
612
621
  properties: {
613
- 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)' },
614
623
  unread: { type: 'boolean', description: 'Return only unread (default false)' },
615
624
  mark_read: { type: 'boolean', description: 'Auto-mark read after call (default false)' },
616
625
  },
617
- required: ['api_key'],
626
+ required: [],
618
627
  },
619
628
  },
620
629
  {
@@ -637,7 +646,7 @@ Protocol auto-judges (no human): respondent silent 48h → favor initiator; arbi
637
646
  inputSchema: {
638
647
  type: 'object',
639
648
  properties: {
640
- 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)" },
641
650
  action: {
642
651
  type: 'string',
643
652
  enum: ['view', 'list_open', 'respond', 'add_evidence', 'arbitrate'],
@@ -668,7 +677,7 @@ Protocol auto-judges (no human): respondent silent 48h → favor initiator; arbi
668
677
  },
669
678
  ruling_reason: { type: 'string', description: 'Ruling reason (required for arbitrate; permanently recorded on-chain)' },
670
679
  },
671
- required: ['api_key', 'action'],
680
+ required: ['action'],
672
681
  },
673
682
  },
674
683
  {
@@ -695,7 +704,7 @@ Actions:
695
704
  inputSchema: {
696
705
  type: 'object',
697
706
  properties: {
698
- 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)' },
699
708
  action: {
700
709
  type: 'string',
701
710
  enum: ['create', 'view', 'mine', 'submit_seller_evidence', 'available', 'vote', 'eligibility', 'verifier_status', 'apply', 'withdraw_application', 'appeal'],
@@ -718,7 +727,7 @@ Actions:
718
727
  // appeal
719
728
  reason: { type: 'string', description: 'Appeal reason (required for appeal, ≤500 chars)' },
720
729
  },
721
- required: ['api_key', 'action'],
730
+ required: ['action'],
722
731
  },
723
732
  },
724
733
  {
@@ -743,7 +752,7 @@ Actions: list (no auth) | publish (seller) | subscribe / unsubscribe (buyer) | m
743
752
  inputSchema: {
744
753
  type: 'object',
745
754
  properties: {
746
- 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)' },
747
756
  action: {
748
757
  type: 'string',
749
758
  enum: ['list', 'publish', 'subscribe', 'unsubscribe', 'my_skills', 'my_subs'],
@@ -807,7 +816,7 @@ Public-profile actions:
807
816
  inputSchema: {
808
817
  type: 'object',
809
818
  properties: {
810
- 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)' },
811
820
  action: {
812
821
  type: 'string',
813
822
  enum: ['view', 'add_role', 'switch_role', 'view_user', 'feed'],
@@ -841,10 +850,10 @@ Use revoke when: PERMANENT decommission of agent/device, OR want access death NO
841
850
  inputSchema: {
842
851
  type: 'object',
843
852
  properties: {
844
- 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)' },
845
854
  reason: { type: 'string', description: 'Optional: leaked / lost_device / rotation / unspecified' },
846
855
  },
847
- required: ['api_key'],
856
+ required: [],
848
857
  },
849
858
  },
850
859
  {
@@ -856,10 +865,10 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
856
865
  inputSchema: {
857
866
  type: 'object',
858
867
  properties: {
859
- 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)' },
860
869
  reason: { type: 'string', description: 'Optional: rotation / leaked / scheduled' },
861
870
  },
862
- required: ['api_key'],
871
+ required: [],
863
872
  },
864
873
  },
865
874
  {
@@ -872,9 +881,9 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
872
881
  inputSchema: {
873
882
  type: 'object',
874
883
  properties: {
875
- 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)' },
876
885
  },
877
- required: ['api_key'],
886
+ required: [],
878
887
  },
879
888
  },
880
889
  {
@@ -887,15 +896,15 @@ Safer than \`webaz_revoke_key\` — atomic swap, no access gap.`,
887
896
  inputSchema: {
888
897
  type: 'object',
889
898
  properties: {
890
- 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)' },
891
900
  product_id: { type: 'string', description: 'Product to promote (from webaz_search)' },
892
901
  side: {
893
902
  type: 'string',
894
- enum: ['left', 'right', 'auto'],
895
- 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选择已下线。',
896
905
  },
897
906
  },
898
- required: ['api_key', 'product_id'],
907
+ required: ['product_id'],
899
908
  },
900
909
  },
901
910
  {
@@ -909,12 +918,12 @@ Actions: list | block | unblock.`,
909
918
  inputSchema: {
910
919
  type: 'object',
911
920
  properties: {
912
- 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)' },
913
922
  action: { type: 'string', enum: ['list', 'block', 'unblock'], description: 'list: my blocked users | block: add | unblock: remove' },
914
923
  user_id: { type: 'string', description: 'Target user id (required for block/unblock)' },
915
924
  reason: { type: 'string', description: 'Optional reason for block (e.g. "fake product", "abuse")' },
916
925
  },
917
- required: ['api_key', 'action'],
926
+ required: ['action'],
918
927
  },
919
928
  },
920
929
  {
@@ -923,11 +932,11 @@ Actions: list | block | unblock.`,
923
932
  inputSchema: {
924
933
  type: 'object',
925
934
  properties: {
926
- 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)' },
927
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' },
928
937
  user_id: { type: 'string', description: 'Target user (required for follow/unfollow/status)' },
929
938
  },
930
- required: ['api_key', 'action'],
939
+ required: ['action'],
931
940
  },
932
941
  },
933
942
  {
@@ -941,12 +950,12 @@ USE THIS for "what's popular near me / 我附近 / 同城" — geo-aggregated, n
941
950
  inputSchema: {
942
951
  type: 'object',
943
952
  properties: {
944
- 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)' },
945
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' },
946
955
  lat: { type: 'number', description: 'Latitude -90..90 (for set_location, auto-truncated to 0.1°)' },
947
956
  lng: { type: 'number', description: 'Longitude -180..180 (for set_location, auto-truncated to 0.1°)' },
948
957
  },
949
- required: ['api_key', 'action'],
958
+ required: ['action'],
950
959
  },
951
960
  },
952
961
  {
@@ -958,12 +967,12 @@ USE THIS for "what's popular near me / 我附近 / 同城" — geo-aggregated, n
958
967
  inputSchema: {
959
968
  type: 'object',
960
969
  properties: {
961
- 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)' },
962
971
  action: { type: 'string', enum: ['read', 'set'], description: 'read: get current default | set: update' },
963
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.' },
964
973
  region: { type: 'string', description: 'Region tag for shipping match (e.g. "global", "china", "SG"). Optional for set. ≤ 40 chars.' },
965
974
  },
966
- required: ['api_key', 'action'],
975
+ required: ['action'],
967
976
  },
968
977
  },
969
978
  {
@@ -979,7 +988,7 @@ Actions: list_mine | add (external_url + product/anchor) | delete | by_product |
979
988
  inputSchema: {
980
989
  type: 'object',
981
990
  properties: {
982
- 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)' },
983
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' },
984
993
  external_url: { type: 'string', description: 'For action=add' },
985
994
  title: { type: 'string', description: 'For action=add (optional)' },
@@ -988,7 +997,7 @@ Actions: list_mine | add (external_url + product/anchor) | delete | by_product |
988
997
  related_anchor: { type: 'string', description: 'For action=add or by_anchor' },
989
998
  shareable_id: { type: 'string', description: 'For action=delete' },
990
999
  },
991
- required: ['api_key', 'action'],
1000
+ required: ['action'],
992
1001
  },
993
1002
  },
994
1003
  // ── P3 RFQ / bid / chat / auto_bid(MCP 通过 HTTP 调 PWA,复用所有校验+状态机)────
@@ -1017,7 +1026,7 @@ Shipping address falls back to webaz_default_address if omitted.`,
1017
1026
  inputSchema: {
1018
1027
  type: 'object',
1019
1028
  properties: {
1020
- 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)' },
1021
1030
  action: { type: 'string', enum: ['create', 'mine', 'browse', 'detail', 'award', 'cancel'] },
1022
1031
  // create
1023
1032
  title: { type: 'string' },
@@ -1036,7 +1045,7 @@ Shipping address falls back to webaz_default_address if omitted.`,
1036
1045
  rfq_id: { type: 'string' },
1037
1046
  bid_id: { type: 'string', description: 'Optional for award — if omitted, auto-pick current lowest bid' },
1038
1047
  },
1039
- required: ['api_key', 'action'],
1048
+ required: ['action'],
1040
1049
  },
1041
1050
  },
1042
1051
  {
@@ -1050,7 +1059,7 @@ Actions: submit (rfq_id + price + qty_offered + fulfillment_type; optional eta/n
1050
1059
  inputSchema: {
1051
1060
  type: 'object',
1052
1061
  properties: {
1053
- 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)' },
1054
1063
  action: { type: 'string', enum: ['submit', 'patch', 'cancel', 'list_mine'] },
1055
1064
  rfq_id: { type: 'string' },
1056
1065
  bid_id: { type: 'string' },
@@ -1061,7 +1070,7 @@ Actions: submit (rfq_id + price + qty_offered + fulfillment_type; optional eta/n
1061
1070
  note: { type: 'string' },
1062
1071
  offer_id: { type: 'string', description: 'Optional; reference existing offer' },
1063
1072
  },
1064
- required: ['api_key', 'action'],
1073
+ required: ['action'],
1065
1074
  },
1066
1075
  },
1067
1076
  {
@@ -1081,7 +1090,7 @@ webaz_blocklist hides from search but does NOT auto-silence existing convs (busi
1081
1090
  inputSchema: {
1082
1091
  type: 'object',
1083
1092
  properties: {
1084
- api_key: { type: 'string' },
1093
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1085
1094
  action: { type: 'string', enum: ['start', 'list', 'read', 'send', 'mark_read', 'block'] },
1086
1095
  kind: { type: 'string', enum: ['order', 'rfq', 'listing_qa'] },
1087
1096
  context_id: { type: 'string' },
@@ -1089,7 +1098,7 @@ webaz_blocklist hides from search but does NOT auto-silence existing convs (busi
1089
1098
  conversation_id: { type: 'string' },
1090
1099
  body: { type: 'string', description: 'Message body for send action (≤2000 chars)' },
1091
1100
  },
1092
- required: ['api_key', 'action'],
1101
+ required: ['action'],
1093
1102
  },
1094
1103
  },
1095
1104
  {
@@ -1129,7 +1138,7 @@ Actions (15):
1129
1138
  inputSchema: {
1130
1139
  type: 'object',
1131
1140
  properties: {
1132
- api_key: { type: 'string' },
1141
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1133
1142
  action: { type: 'string', enum: ['list', 'detail', 'create', 'claim', 'proof', 'confirm', 'disclose', 'cancel', 'me', 'stories', 'leaderboard', 'repay', 'repay_respond', 'donate', 'fund'] },
1134
1143
  wish_id: { type: 'string' },
1135
1144
  fulfillment_id: { type: 'string' },
@@ -1165,7 +1174,7 @@ Actions: create (seller, needs title/price/stock + content_hash sha256 + content
1165
1174
  inputSchema: {
1166
1175
  type: 'object',
1167
1176
  properties: {
1168
- api_key: { type: 'string' },
1177
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1169
1178
  action: { type: 'string', enum: ['create', 'list', 'detail', 'patch'] },
1170
1179
  product_id: { type: 'string' },
1171
1180
  title: { type: 'string' }, price: { type: 'number' }, stock: { type: 'number' },
@@ -1190,11 +1199,11 @@ Actions: toggle (same endpoint; 2nd call auto-unlikes) | status (my like status
1190
1199
  inputSchema: {
1191
1200
  type: 'object',
1192
1201
  properties: {
1193
- api_key: { type: 'string' },
1202
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1194
1203
  action: { type: 'string', enum: ['toggle', 'status'] },
1195
1204
  shareable_id: { type: 'string' },
1196
1205
  },
1197
- required: ['api_key', 'action', 'shareable_id'],
1206
+ required: ['action', 'shareable_id'],
1198
1207
  },
1199
1208
  },
1200
1209
  {
@@ -1239,7 +1248,7 @@ Actions:
1239
1248
  inputSchema: {
1240
1249
  type: 'object',
1241
1250
  properties: {
1242
- api_key: { type: 'string' },
1251
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1243
1252
  action: { type: 'string', enum: ['create', 'browse', 'mine', 'detail', 'bid', 'cancel'] },
1244
1253
  title: { type: 'string' },
1245
1254
  qty: { type: 'number' },
@@ -1253,7 +1262,7 @@ Actions:
1253
1262
  auction_id: { type: 'string' },
1254
1263
  price: { type: 'number' },
1255
1264
  },
1256
- required: ['api_key', 'action'],
1265
+ required: ['action'],
1257
1266
  },
1258
1267
  },
1259
1268
  {
@@ -1267,7 +1276,7 @@ Actions: get | set (categories[] / regions[] / max_eta_h / bid_strategy) | disab
1267
1276
  inputSchema: {
1268
1277
  type: 'object',
1269
1278
  properties: {
1270
- api_key: { type: 'string' },
1279
+ api_key: { type: 'string', description: 'Your api_key (or set the WEBAZ_API_KEY env var)' },
1271
1280
  action: { type: 'string', enum: ['get', 'set', 'disable'] },
1272
1281
  categories: { type: 'array', items: { type: 'string' } },
1273
1282
  regions: { type: 'array', items: { type: 'string' } },
@@ -1280,7 +1289,7 @@ Actions: get | set (categories[] / regions[] / max_eta_h / bid_strategy) | disab
1280
1289
  cooldown_min: { type: 'number' },
1281
1290
  enabled: { type: 'boolean' },
1282
1291
  },
1283
- required: ['api_key', 'action'],
1292
+ required: ['action'],
1284
1293
  },
1285
1294
  },
1286
1295
  {
@@ -1300,7 +1309,7 @@ Actions: list (no auth, filters: kind/billing/query) | detail (public, no conten
1300
1309
  inputSchema: {
1301
1310
  type: 'object',
1302
1311
  properties: {
1303
- 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)' },
1304
1313
  action: {
1305
1314
  type: 'string',
1306
1315
  enum: ['list', 'detail', 'publish', 'update', 'delist', 'resubmit', 'purchase', 'read', 'my_skills', 'library'],
@@ -1337,7 +1346,7 @@ Enums: **category** phone/computer/appliance/furniture/clothing/book/toy/sports/
1337
1346
  inputSchema: {
1338
1347
  type: 'object',
1339
1348
  properties: {
1340
- 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)' },
1341
1350
  action: { type: 'string', enum: ['browse', 'detail', 'publish', 'update', 'mine', 'buy'], description: 'Action to execute' },
1342
1351
  item_id: { type: 'string', description: 'Item ID (required for detail/update/buy)' },
1343
1352
  // publish / update
@@ -1386,7 +1395,7 @@ Seller actions:
1386
1395
  inputSchema: {
1387
1396
  type: 'object',
1388
1397
  properties: {
1389
- 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)' },
1390
1399
  action: { type: 'string', enum: ['get_campaign', 'apply', 'link_note', 'my_claims', 'create_campaign', 'cancel_campaign', 'my_campaigns', 'campaign_claims'], description: 'Action to execute' },
1391
1400
  product_id: { type: 'string', description: 'Product ID (required for get_campaign/apply/create_campaign/cancel_campaign)' },
1392
1401
  claim_id: { type: 'string', description: 'Claim ID (required for link_note)' },
@@ -1415,7 +1424,7 @@ Gate by type: ux_issue/bug (reporting = using) → login only, NO Passkey, anyon
1415
1424
  type: 'object',
1416
1425
  properties: {
1417
1426
  action: { type: 'string', enum: ['submit', 'my', 'get'], description: 'submit (default) | my | get' },
1418
- 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)" },
1419
1428
  type: { type: 'string', enum: ['ux_issue', 'bug', 'proposal'], description: 'submit: kind of feedback' },
1420
1429
  area: { type: 'string', description: 'submit: which feature, e.g. search / order / dispute' },
1421
1430
  severity: { type: 'string', enum: ['low', 'annoying', 'blocking'], description: 'submit: for ux_issue/bug' },
@@ -1423,7 +1432,7 @@ Gate by type: ux_issue/bug (reporting = using) → login only, NO Passkey, anyon
1423
1432
  text: { type: 'string', description: 'submit: the feedback / idea (≥5 chars)' },
1424
1433
  feedback_id: { type: 'string', description: 'get: the feedback id' },
1425
1434
  },
1426
- required: ['api_key'],
1435
+ required: [],
1427
1436
  },
1428
1437
  },
1429
1438
  {
@@ -1435,7 +1444,8 @@ Discovery + suggesting need NO api_key (anyone / any agent can browse and propos
1435
1444
  Actions:
1436
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.
1437
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.
1438
- - 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.
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 (but pass your key to LINK it to your account so you can track it via my_suggestions).
1448
+ - my_suggestions: your OWN past proposals + their review status / public_reply / next_action (api_key). Agent-readable 回执 so a proposer-agent can act on the maintainer's decision (needs_info → resubmit; converted → see converted_ref).
1439
1449
  - 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).
1440
1450
  - 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.
1441
1451
  - status: tasks you hold (api_key).
@@ -1445,12 +1455,12 @@ Coordinates + records only — NO merge/reward; acceptance (done) = human mainta
1445
1455
  inputSchema: {
1446
1456
  type: 'object',
1447
1457
  properties: {
1448
- action: { type: 'string', enum: ['list_open', 'detail', 'suggest', 'claim', 'submit', 'status', 'profile'], description: 'list_open (default) | detail | suggest | claim | submit | status | profile' },
1449
- api_key: { type: 'string', description: 'claim/submit/status/profile: your api_key (accountable identity). NOT needed for list_open/detail/suggest.' },
1458
+ action: { type: 'string', enum: ['list_open', 'detail', 'suggest', 'my_suggestions', 'claim', 'submit', 'status', 'profile'], description: 'list_open (default) | detail | suggest | my_suggestions | claim | submit | status | profile' },
1459
+ 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)' },
1450
1460
  task_id: { type: 'string', description: 'detail / claim / submit: the task id' },
1451
1461
  area: { type: 'string', description: 'list_open: area filter / suggest: suggested area (e.g. search / docs / mcp)' },
1452
1462
  risk_level: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], description: 'list_open: optional risk filter' },
1453
- auto_claimable: { type: 'boolean', description: 'list_open: optional filter — only auto-claimable (true) or manual-claim (false) tasks' },
1463
+ auto_claimable: { type: 'boolean', description: 'list_open: optional filter — true returns tasks an agent can just do (auto-claimable AND with a real effort estimate; matches the derived claimability=auto_claimable), false returns manual-claim tasks. A task with a placeholder (unknown) estimate is treated as manual_review even if its raw auto_claimable flag is true, so it is excluded from true.' },
1454
1464
  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.' },
1455
1465
  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' },
1456
1466
  max_duration_minutes: { type: 'number', description: 'list_open: optional filter — only tasks whose estimated max duration fits within this many minutes (your idle time)' },
@@ -1473,7 +1483,7 @@ Coordinates + records only — NO merge/reward; acceptance (done) = human mainta
1473
1483
  // RFC-004: webaz_feedback — agent-native "use → build" 反馈(双模;仅 NETWORK 能送达)
1474
1484
  async function handleFeedback(args) {
1475
1485
  const action = args.action || 'submit';
1476
- const apiKey = args.api_key;
1486
+ const apiKey = resolveMcpApiKey(args);
1477
1487
  if (!apiKey)
1478
1488
  return { error: 'api_key required' };
1479
1489
  if (toolBackend('webaz_feedback') !== 'network') {
@@ -1509,18 +1519,22 @@ async function handleFeedback(args) {
1509
1519
  }
1510
1520
  // RFC-006 断点1(b)交接:从【可信】canonical 目标(API 响应里,绝不硬编码/不取自 task metadata)构造"怎么真正
1511
1521
  // 动手"。人的编码 agent 做 git/PR;Passkey 真人担责。sandbox 运行 / 本地草稿不算正式参与。
1512
- function buildContributeHandoff(cct, taskId) {
1522
+ function buildContributeHandoff(cct, taskId, caseId) {
1513
1523
  const c = (cct ?? {});
1514
- const repoUrl = c.canonical_github_url || 'https://github.com/seasonsagents-art/webaz';
1515
- const baseRepo = c.expected_pr_base_repo || c.canonical_repository_full_name || 'seasonsagents-art/webaz';
1524
+ const repoUrl = c.canonical_github_url || 'https://github.com/webaz-protocol/webaz';
1525
+ const baseRepo = c.expected_pr_base_repo || c.canonical_repository_full_name || 'webaz-protocol/webaz';
1516
1526
  const baseBranch = c.base_branch || 'main';
1527
+ // case_id threads proposal → task → PR. = the source proposal id when this task came from a proposal,
1528
+ // else the task id itself. Quote it in the PR so the whole case stays traceable end to end.
1529
+ const cid = caseId || taskId;
1517
1530
  return {
1531
+ case_id: cid,
1518
1532
  canonical_repo: baseRepo,
1519
1533
  repo: repoUrl,
1520
1534
  base_branch: baseBranch,
1521
1535
  start_here: 'Read AGENTS.md (project map + before-you-code + PR flow), then CONTRIBUTING.md.',
1522
1536
  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.',
1523
- 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.`,
1537
+ submit_pr: `Open a PR whose BASE repo is ${baseRepo} (${repoUrl}), base branch ${baseBranch}. Reference case ${cid} in the PR title/body so the proposal → task → PR chain stays traceable. If any target repo differs from this canonical repo, STOP and ask the human — never contribute to a non-canonical repository.`,
1524
1538
  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.',
1525
1539
  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.`,
1526
1540
  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.',
@@ -1531,7 +1545,7 @@ function buildContributeHandoff(cct, taskId) {
1531
1545
  // 端口 #329/#331);claim/submit/status/profile 需 key(真实可问责身份,走受 #330 守卫的 member 端口)。
1532
1546
  export async function handleContribute(args) {
1533
1547
  const action = args.action || 'list_open';
1534
- const apiKey = args.api_key;
1548
+ const apiKey = resolveMcpApiKey(args);
1535
1549
  if (toolBackend('webaz_contribute') !== 'network') {
1536
1550
  return {
1537
1551
  _mode: 'sandbox',
@@ -1571,7 +1585,7 @@ export async function handleContribute(args) {
1571
1585
  return { error: 'task_id required for action=detail' };
1572
1586
  const r = await apiCall('/api/public/build-tasks/' + encodeURIComponent(tid));
1573
1587
  if (!r.error && r.task)
1574
- r.agent_handoff = buildContributeHandoff(r.canonical_contribution_target, tid);
1588
+ r.agent_handoff = buildContributeHandoff(r.canonical_contribution_target, tid, r.task.case_id);
1575
1589
  return r;
1576
1590
  }
1577
1591
  if (action === 'suggest') {
@@ -1583,6 +1597,7 @@ export async function handleContribute(args) {
1583
1597
  return { error: 'summary (the reason) required for action=suggest' };
1584
1598
  const r = await apiCall('/api/public/task-proposals', {
1585
1599
  method: 'POST',
1600
+ apiKey, // optional — when present, links the proposal to the submitter so it shows up in action=my_suggestions (still works anonymously)
1586
1601
  body: {
1587
1602
  title, summary,
1588
1603
  suggested_area: args.area ?? args.suggested_area,
@@ -1591,6 +1606,8 @@ export async function handleContribute(args) {
1591
1606
  proposer_github_login: args.proposer_github_login,
1592
1607
  },
1593
1608
  });
1609
+ if (!r.error && r.linked_to_account)
1610
+ r._next = 'Track this proposal\'s review status + reply: webaz_contribute action=my_suggestions api_key=<key>.';
1594
1611
  // typed errors (RATE_LIMITED / DUPLICATE_PROPOSAL / validation) are already mapped by apiCall; the
1595
1612
  // success response already carries the route-level `proposal_notice` (suggestion ≠ contribution/reward).
1596
1613
  return r;
@@ -1603,6 +1620,13 @@ export async function handleContribute(args) {
1603
1620
  };
1604
1621
  if (action === 'status')
1605
1622
  return apiCall('/api/build-tasks?mine=1', { apiKey });
1623
+ if (action === 'my_suggestions') {
1624
+ // your OWN past proposals + review status/public_reply/next_action (agent-readable 回执). Own rows only (server-enforced).
1625
+ const r = await apiCall('/api/me/task-proposals', { apiKey });
1626
+ if (!r.error)
1627
+ r._next = 'Each item carries status + public_reply + next_action. needs_info → resubmit via action=suggest referencing the id; converted → see converted_ref.';
1628
+ return r;
1629
+ }
1606
1630
  if (action === 'profile')
1607
1631
  return apiCall('/api/build-reputation/me', { apiKey });
1608
1632
  if (action === 'claim') {
@@ -1721,8 +1745,11 @@ async function handleInfo() {
1721
1745
  // 连接两个场景:用协议(本工具) ↔ 改协议(开发协作)。想改 WebAZ 本身的 agent 从这里进。
1722
1746
  for_contributors: {
1723
1747
  note: 'Want to change WebAZ itself (not just use it)? This is an open, agent-native protocol — AI-authored PRs are welcome, with accountability. / 想改 WebAZ 本身(不只是用)?这是开放的 agent 原生协议,欢迎 AI 提 PR,但需问责。',
1724
- repo: 'https://github.com/seasonsagents-art/webaz',
1748
+ repo: 'https://github.com/webaz-protocol/webaz',
1725
1749
  start_here: 'AGENTS.md (project map + before-you-code + PR flow) → CONTRIBUTING.md (full guide)',
1750
+ // 低门槛路径:无需 api_key、无需 clone 仓库,直接经协议发现任务 / 提建议(对外 well-known 入口也有,见 agent_quickstart)。
1751
+ no_key_path: 'No api_key needed to START contributing: discover open tasks and submit a suggestion via webaz_contribute action=list_open / action=suggest (mirrors GET /api/public/build-tasks + POST /api/public/task-proposals). / 无需 key 即可起步:webaz_contribute action=list_open 发现开放任务、action=suggest 提建议。',
1752
+ contribution_boundary: 'A suggestion is a proposal in the maintainer review inbox — NOT a contribution fact, NOT formal participation, and NOT any economic or redemption right; recorded contribution is facts / evidence / attribution only (RFC-017). / 建议只是进入维护者审阅箱的提议,不是贡献事实、不是正式参与、不构成任何经济或兑现权利;记录的贡献只是事实/证据/归属(RFC-017)。',
1726
1753
  ai_accountability: 'AI-authored PRs: add 🤖🤖🤖 to the PR title; the agent must be triggered by a Passkey-bound human (webazer) who is accountable. / AI 提 PR:标题加 🤖🤖🤖,且须由已绑 Passkey 的真人(webazer)触发并担责。',
1727
1754
  },
1728
1755
  // NETWORK 模式:真网络 live 状态(best-effort 拉自 webaz.xyz);SANDBOX 模式为 null。
@@ -1732,10 +1759,10 @@ async function handleInfo() {
1732
1759
  // 佣金机制 —— 纯功能性描述(怎么运作),不做"自证清白"式辩护。
1733
1760
  commission_model: {
1734
1761
  split: '7:2:1 — L1 70% / L2 20% / L3 10% of an order\'s commission_pool',
1735
- 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).',
1762
+ 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 → commission_reserve / protocol reserve).',
1736
1763
  attribution: 'EXPLICIT per-order — commission goes to the promoter attributed at purchase time, not derived from the buyer\'s sponsor chain.',
1737
1764
  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).',
1738
- redirect_rules: 'chain_gap (no L / invalid sponsor) charity_fund; level beyond the region cap → global_fund.',
1765
+ redirect_rules: 'all undelivered commission (chain_gap / no L / invalid sponsor / level beyond the region cap / max_levels=0 / opt-out / escrow expiry) commission_reserve (protocol reserve, in-only; use decided by governance).',
1739
1766
  l1_gate: 'the promoter must be a verified buyer (≥1 completed order) to receive commission, otherwise that share redirects.',
1740
1767
  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.',
1741
1768
  },
@@ -2138,11 +2165,11 @@ async function handleVerifyPrice(args) {
2138
2165
  if (toolBackend('webaz_verify_price') === 'network') {
2139
2166
  return apiCall('/api/verify-price', {
2140
2167
  method: 'POST',
2141
- apiKey: args.api_key,
2168
+ apiKey: resolveMcpApiKey(args),
2142
2169
  body: { product_id: args.product_id, quantity: Number(args.quantity ?? 1) },
2143
2170
  });
2144
2171
  }
2145
- const auth = requireAuth(db, args.api_key);
2172
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2146
2173
  if ('error' in auth)
2147
2174
  return auth;
2148
2175
  const { user } = auth;
@@ -2188,7 +2215,7 @@ async function handleVerifyPrice(args) {
2188
2215
  async function handleListProduct(args) {
2189
2216
  // Wave 3 audit P0: 加 action 分发 — agent 卖家能完整管理目录(不止 create)
2190
2217
  const action = args.action || 'create';
2191
- const apiKey = args.api_key;
2218
+ const apiKey = resolveMcpApiKey(args);
2192
2219
  if (!apiKey)
2193
2220
  return { error: 'api_key required' };
2194
2221
  // RFC-003 P2b: NETWORK 模式 — 卖家目录管理全部转发生产端点(单一真相源)
@@ -2366,9 +2393,9 @@ async function handlePlaceOrder(args) {
2366
2393
  body.shipping_address = args.shipping_address;
2367
2394
  if (args.donation_pct != null)
2368
2395
  body.donation_pct = args.donation_pct;
2369
- return apiCall('/api/orders', { method: 'POST', apiKey: args.api_key, body });
2396
+ return apiCall('/api/orders', { method: 'POST', apiKey: resolveMcpApiKey(args), body });
2370
2397
  }
2371
- const auth = requireAuth(db, args.api_key);
2398
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2372
2399
  if ('error' in auth)
2373
2400
  return auth;
2374
2401
  const { user } = auth;
@@ -2535,7 +2562,7 @@ async function handleUpdateOrder(args) {
2535
2562
  return { error: 'order_id and action required' };
2536
2563
  return apiCall(`/api/orders/${encodeURIComponent(orderId)}/action`, {
2537
2564
  method: 'POST',
2538
- apiKey: args.api_key,
2565
+ apiKey: resolveMcpApiKey(args),
2539
2566
  body: {
2540
2567
  action,
2541
2568
  notes: args.notes ?? '',
@@ -2545,7 +2572,7 @@ async function handleUpdateOrder(args) {
2545
2572
  },
2546
2573
  });
2547
2574
  }
2548
- const auth = requireAuth(db, args.api_key);
2575
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2549
2576
  if ('error' in auth)
2550
2577
  return auth;
2551
2578
  const { user } = auth;
@@ -2559,7 +2586,7 @@ async function handleUpdateOrder(args) {
2559
2586
  // agent-native 协议要求"哪个接口进结果一致"。MCP confirm 不再自己结算,
2560
2587
  // 走 PWA /api/orders/:id/action 的 settleOrder + settleCommission(authoritative)。
2561
2588
  if (action === 'confirm') {
2562
- const apiKey = args.api_key;
2589
+ const apiKey = resolveMcpApiKey(args);
2563
2590
  const result = await pwaApi('POST', `/orders/${encodeURIComponent(orderId)}/action`, apiKey, {
2564
2591
  action: 'confirm',
2565
2592
  notes,
@@ -2668,9 +2695,9 @@ async function handleGetStatus(args) {
2668
2695
  const orderId = args.order_id;
2669
2696
  if (!orderId)
2670
2697
  return { error: 'order_id required' };
2671
- return apiCall(`/api/orders/${encodeURIComponent(orderId)}`, { apiKey: args.api_key });
2698
+ return apiCall(`/api/orders/${encodeURIComponent(orderId)}`, { apiKey: resolveMcpApiKey(args) });
2672
2699
  }
2673
- const auth = requireAuth(db, args.api_key);
2700
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2674
2701
  if ('error' in auth)
2675
2702
  return auth;
2676
2703
  const statusInfo = getOrderStatus(db, args.order_id);
@@ -2707,7 +2734,7 @@ async function handleGetStatus(args) {
2707
2734
  async function handleWallet(args) {
2708
2735
  // Wave 3 audit P0: 加 action 分发 — agent 能查充值/提现/收入历史(写操作仍 UI-only 走 2FA)
2709
2736
  const action = args.action || 'view';
2710
- const apiKey = args.api_key;
2737
+ const apiKey = resolveMcpApiKey(args);
2711
2738
  if (!apiKey)
2712
2739
  return { error: 'api_key required' };
2713
2740
  // RFC-003 Batch 4:NETWORK 模式 → webaz.xyz 真网络【只读】(Bearer api_key)。
@@ -2775,14 +2802,14 @@ async function handleWallet(args) {
2775
2802
  async function handleNotifications(args) {
2776
2803
  // RFC-003 Batch 1:NETWORK 模式 → 调 webaz.xyz 真网络通知端点(Bearer api_key);SANDBOX 走本地。
2777
2804
  if (toolBackend('webaz_notifications') === 'network') {
2778
- const apiKey = String(args.api_key || '');
2805
+ const apiKey = resolveMcpApiKey(args);
2779
2806
  if (!apiKey)
2780
2807
  return { error: 'api_key required' };
2781
2808
  if (args.mark_read)
2782
2809
  await apiCall('/api/notifications/read', { method: 'POST', apiKey });
2783
2810
  return await apiCall('/api/notifications' + (args.unread === true ? '?unread=1' : ''), { apiKey });
2784
2811
  }
2785
- const auth = requireAuth(db, args.api_key);
2812
+ const auth = requireAuth(db, resolveMcpApiKey(args));
2786
2813
  if ('error' in auth)
2787
2814
  return auth;
2788
2815
  const { user } = auth;
@@ -2806,7 +2833,7 @@ async function handleNotifications(args) {
2806
2833
  }
2807
2834
  // ─── 争议处理 ─────────────────────────────────────────────────
2808
2835
  async function handleDispute(args) {
2809
- const apiKey = args.api_key;
2836
+ const apiKey = resolveMcpApiKey(args);
2810
2837
  if (!apiKey)
2811
2838
  return { error: 'api_key required' };
2812
2839
  const action = args.action;
@@ -2969,7 +2996,7 @@ async function handleDispute(args) {
2969
2996
  }
2970
2997
  // ─── 索赔验证(claim-verification)处理 — Wave 6 新增 ────────────
2971
2998
  async function handleClaimVerify(args) {
2972
- const apiKey = args.api_key;
2999
+ const apiKey = resolveMcpApiKey(args);
2973
3000
  if (!apiKey)
2974
3001
  return { error: 'api_key required' };
2975
3002
  const action = String(args.action || '');
@@ -3057,7 +3084,7 @@ async function handleSkill(args) {
3057
3084
  const action = args.action;
3058
3085
  // RFC-003 Batch 3:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地引擎。
3059
3086
  if (toolBackend('webaz_skill') === 'network') {
3060
- const apiKey = String(args.api_key || '');
3087
+ const apiKey = resolveMcpApiKey(args);
3061
3088
  if (action === 'list') {
3062
3089
  const qs = new URLSearchParams();
3063
3090
  if (args.skill_type)
@@ -3094,8 +3121,8 @@ async function handleSkill(args) {
3094
3121
  // ── 浏览 Skill 市场 ────────────────────────────────────────
3095
3122
  if (action === 'list') {
3096
3123
  let userId;
3097
- if (args.api_key) {
3098
- const a = requireAuth(db, args.api_key);
3124
+ if (resolveMcpApiKey(args)) {
3125
+ const a = requireAuth(db, resolveMcpApiKey(args));
3099
3126
  if (!('error' in a))
3100
3127
  userId = a.user.id;
3101
3128
  }
@@ -3112,7 +3139,7 @@ async function handleSkill(args) {
3112
3139
  };
3113
3140
  }
3114
3141
  // 以下操作需要身份验证
3115
- const auth = requireAuth(db, args.api_key);
3142
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3116
3143
  if ('error' in auth)
3117
3144
  return auth;
3118
3145
  const { user } = auth;
@@ -3253,7 +3280,7 @@ function handleMyKey(args) {
3253
3280
  }
3254
3281
  async function handleProfile(args) {
3255
3282
  const action = args.action;
3256
- const apiKey = String(args.api_key || '');
3283
+ const apiKey = resolveMcpApiKey(args);
3257
3284
  // RFC-003 Batch 1:NETWORK 模式 → 全部 action 调 webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3258
3285
  if (toolBackend('webaz_profile') === 'network') {
3259
3286
  if (action === 'view_user') {
@@ -3350,7 +3377,7 @@ async function handleProfile(args) {
3350
3377
  function handleRevokeKey(args) {
3351
3378
  // RFC-003 Batch 5:NETWORK 模式 → 不本地校验 key(PWA 会鉴权),直接返回 Passkey 撤销指引。
3352
3379
  if (toolBackend('webaz_revoke_key') === 'network') {
3353
- const apiKey = String(args.api_key || '');
3380
+ const apiKey = resolveMcpApiKey(args);
3354
3381
  const reason = (args.reason || 'unspecified').trim().slice(0, 100);
3355
3382
  return {
3356
3383
  _mode: 'network',
@@ -3368,7 +3395,7 @@ function handleRevokeKey(args) {
3368
3395
  },
3369
3396
  };
3370
3397
  }
3371
- const auth = requireAuth(db, args.api_key);
3398
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3372
3399
  if ('error' in auth)
3373
3400
  return auth;
3374
3401
  const { user } = auth;
@@ -3392,7 +3419,7 @@ function handleRevokeKey(args) {
3392
3419
  function handleRotateKey(args) {
3393
3420
  // RFC-003 Batch 5:NETWORK 模式 → 不本地校验 key(PWA 会鉴权),直接返回 Passkey 轮换指引。
3394
3421
  if (toolBackend('webaz_rotate_key') === 'network') {
3395
- const apiKey = String(args.api_key || '');
3422
+ const apiKey = resolveMcpApiKey(args);
3396
3423
  const reason = (args.reason || 'rotation').trim().slice(0, 100);
3397
3424
  return {
3398
3425
  _mode: 'network',
@@ -3409,7 +3436,7 @@ function handleRotateKey(args) {
3409
3436
  },
3410
3437
  };
3411
3438
  }
3412
- const auth = requireAuth(db, args.api_key);
3439
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3413
3440
  if ('error' in auth)
3414
3441
  return auth;
3415
3442
  const { user } = auth;
@@ -3429,16 +3456,16 @@ function handleRotateKey(args) {
3429
3456
  },
3430
3457
  };
3431
3458
  }
3432
- // ─── 推广 / 双轨 (Tokenomics) ───────────────────────────────────
3459
+ // ─── 推广 / 推荐网络 (Tokenomics) ───────────────────────────────────
3433
3460
  async function handleReferral(args) {
3434
3461
  // RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络聚合(Bearer api_key);SANDBOX 走本地。
3435
3462
  if (toolBackend('webaz_referral') === 'network') {
3436
- const apiKey = String(args.api_key || '');
3463
+ const apiKey = resolveMcpApiKey(args);
3437
3464
  if (!apiKey)
3438
3465
  return { error: 'api_key required' };
3439
3466
  return await apiCall('/api/referral/me', { apiKey });
3440
3467
  }
3441
- const auth = requireAuth(db, args.api_key);
3468
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3442
3469
  if ('error' in auth)
3443
3470
  return auth;
3444
3471
  const { user } = auth;
@@ -3456,16 +3483,9 @@ async function handleReferral(args) {
3456
3483
  const completed = db.prepare("SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND status = 'completed'").get(userId).n;
3457
3484
  const override = db.prepare("SELECT l1_share_override FROM users WHERE id = ?").get(userId)?.l1_share_override ?? 0;
3458
3485
  const canL1 = override === 1 || (override === 0 && completed > 0);
3459
- // 原子能双轨
3486
+ // Neutral participation record only — placement position + per-leg PV. Matching-rewards engine excised (#401):
3487
+ // no Score / tier / pair-volume / payout is read or exposed.
3460
3488
  const me = db.prepare("SELECT total_left_pv, total_right_pv, left_child_id, right_child_id, placement_id, placement_side FROM users WHERE id = ?").get(userId);
3461
- const score = db.prepare(`
3462
- SELECT COALESCE(SUM(CASE WHEN settled_at IS NULL THEN score ELSE 0 END),0) as pending,
3463
- COALESCE(SUM(CASE WHEN settled_at IS NOT NULL THEN waz_amount ELSE 0 END),0) as settled_waz
3464
- FROM binary_score_records WHERE user_id = ?
3465
- `).get(userId);
3466
- const tiers = db.prepare("SELECT tier, pv_threshold, score_per_hit FROM binary_tier_config WHERE active=1 ORDER BY tier ASC").all();
3467
- const pair = Math.min(Number(me?.total_left_pv ?? 0), Number(me?.total_right_pv ?? 0));
3468
- const nextTier = tiers.find(t => t.pv_threshold > pair);
3469
3489
  // invite / share links use permanent_code ONLY — never usr_xxx. (sandbox users have one from register.)
3470
3490
  const permaCode = db.prepare("SELECT permanent_code FROM users WHERE id = ?").get(userId)?.permanent_code || null;
3471
3491
  return {
@@ -3487,17 +3507,12 @@ async function handleReferral(args) {
3487
3507
  l1: byLevel[1], l2: byLevel[2], l3: byLevel[3],
3488
3508
  grand_total: byLevel[1].total + byLevel[2].total + byLevel[3].total,
3489
3509
  },
3490
- binary: {
3491
- left_invite_link: permaCode ? `/i/${permaCode}-L` : null,
3492
- right_invite_link: permaCode ? `/i/${permaCode}-R` : null,
3493
- platform_left: permaCode ? `/?placement=${permaCode}&side=left` : null, // 仅 PV 条线
3494
- platform_right: permaCode ? `/?placement=${permaCode}&side=right` : null,
3510
+ placement: {
3511
+ // Neutral participation/attribution record: a single referral code + per-leg PV. No matching rewards.
3512
+ referral_link: permaCode ? `/i/${permaCode}` : null,
3495
3513
  total_left_pv: Number(me?.total_left_pv ?? 0),
3496
3514
  total_right_pv: Number(me?.total_right_pv ?? 0),
3497
- pair_volume: pair,
3498
- next_tier: nextTier ? { tier: nextTier.tier, pv_threshold: nextTier.pv_threshold, score_per_hit: nextTier.score_per_hit, pv_needed: nextTier.pv_threshold - pair } : null,
3499
- score_pending: score.pending,
3500
- waz_total_earned: score.settled_waz,
3515
+ note: 'total_left_pv / total_right_pv are a participation / attribution record only — not income, not redeemable, no entitlement.',
3501
3516
  },
3502
3517
  rewards_status: (() => {
3503
3518
  // RFC-002 §3.5 — 4 states + pending escrow visibility (PR-4)
@@ -3540,23 +3555,21 @@ async function handleReferral(args) {
3540
3555
  async function handleShareLink(args) {
3541
3556
  // RFC-003 #1122:NETWORK 模式 → 调 webaz.xyz 的 /api/share-link(服务端同款计算);SANDBOX 走本地。
3542
3557
  if (toolBackend('webaz_share_link') === 'network') {
3543
- const apiKey = String(args.api_key || '');
3558
+ const apiKey = resolveMcpApiKey(args);
3544
3559
  if (!apiKey)
3545
3560
  return { error: 'api_key required' };
3546
3561
  if (!args.product_id)
3547
3562
  return { error: 'product_id required' };
3563
+ // pre-public 去左右码:不再向 /api/share-link 转发 side(放置永远自动)
3548
3564
  const qs = new URLSearchParams({ product_id: String(args.product_id) });
3549
- if (args.side)
3550
- qs.set('side', String(args.side));
3551
3565
  return await apiCall('/api/share-link?' + qs.toString(), { apiKey });
3552
3566
  }
3553
- const auth = requireAuth(db, args.api_key);
3567
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3554
3568
  if ('error' in auth)
3555
3569
  return auth;
3556
3570
  const { user } = auth;
3557
3571
  const userId = user.id;
3558
3572
  const productId = args.product_id;
3559
- const sideArg = args.side || 'auto';
3560
3573
  // RFC-002 §3.5 valuation-layer gate — share_link generation requires opt-in
3561
3574
  const optIn = db.prepare("SELECT rewards_opted_in FROM users WHERE id = ?").get(userId)?.rewards_opted_in ?? 0;
3562
3575
  if (optIn !== 1) {
@@ -3589,32 +3602,7 @@ async function handleShareLink(args) {
3589
3602
  const product = db.prepare("SELECT id, title, price, commission_rate FROM products WHERE id = ? AND status='active'").get(productId);
3590
3603
  if (!product)
3591
3604
  return { error: '商品不存在或已下架' };
3592
- let side = 'right';
3593
- if (sideArg === 'left' || sideArg === 'right') {
3594
- side = sideArg;
3595
- }
3596
- else {
3597
- // auto = 与 PWA pickPreferredSide 对齐:尊重 placement_pref(team_count | pv_count)
3598
- // 老版只看 total_left_pv vs total_right_pv,team_count 用户被错算
3599
- const u = db.prepare("SELECT placement_pref, total_left_pv, total_right_pv, left_count, right_count FROM users WHERE id = ?")
3600
- .get(userId);
3601
- const pref = u?.placement_pref || 'team_count';
3602
- if (pref === 'pv_count') {
3603
- const since = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 19).replace('T', ' ');
3604
- const w = db.prepare(`SELECT COALESCE(SUM(consumed_left_pv),0) AS l, COALESCE(SUM(consumed_right_pv),0) AS r
3605
- FROM binary_score_records WHERE user_id = ? AND created_at >= ?`)
3606
- .get(userId, since);
3607
- const leftPv = Number(u?.total_left_pv ?? 0) + Number(w.l);
3608
- const rightPv = Number(u?.total_right_pv ?? 0) + Number(w.r);
3609
- side = leftPv <= rightPv ? 'left' : 'right';
3610
- }
3611
- else {
3612
- // team_count: 整棵子树人数(增量维护的 left_count/right_count,与 PWA pickPreferredSide 对齐)。
3613
- // 2026-06-04 修:旧版沿单条脊链数(countLeg)名实不符 → 选边失真。分享链接的 side 会被注册时
3614
- // 当 placement_side 显式采用,必须与 PWA joinPowerLeg 用同一指标,否则两路径不一致。
3615
- side = (Number(u?.left_count ?? 0) <= Number(u?.right_count ?? 0)) ? 'left' : 'right';
3616
- }
3617
- }
3605
+ // pre-public 去左右码:分享链接不再携带 side(放置侧别由注册时系统自动决定)
3618
3606
  const completed = db.prepare("SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND status = 'completed'").get(userId).n;
3619
3607
  const override = db.prepare("SELECT l1_share_override FROM users WHERE id = ?").get(userId)?.l1_share_override ?? 0;
3620
3608
  const canL1 = override === 1 || (override === 0 && completed > 0);
@@ -3623,13 +3611,12 @@ async function handleShareLink(args) {
3623
3611
  const permaCode = db.prepare("SELECT permanent_code FROM users WHERE id = ?").get(userId)?.permanent_code || null;
3624
3612
  if (!permaCode)
3625
3613
  return { error: 'permanent_code_missing — cannot build a share link; re-register or contact support', error_code: 'PERMANENT_CODE_MISSING' };
3626
- const link = `/?ref=${permaCode}&side=${side}#order-product/${productId}`;
3614
+ const link = `/?ref=${permaCode}#order-product/${productId}`;
3627
3615
  return {
3628
3616
  product: { id: product.id, title: product.title, price: product.price, commission_rate: rate },
3629
3617
  share_link: link,
3630
3618
  full_url_hint: 'Prepend webaz.xyz (production) or http://localhost:3000 (local) to get the absolute URL',
3631
- side,
3632
- binary_explanation: `New user via this link → placed in your ${side === 'left' ? '🔵 left' : '🟢 right'} subtree (tail anchor)`,
3619
+ placement_note: 'New user via this link → placement is recorded automatically by the system (no left/right choice).',
3633
3620
  commission_eligibility: canL1
3634
3621
  ? `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`
3635
3622
  : 'You are NOT verified yet (need 1 completed purchase). 3-tier commission will be skipped, but points-matching still builds.',
@@ -3640,7 +3627,7 @@ async function handleShareLink(args) {
3640
3627
  async function handleBlocklist(args) {
3641
3628
  // RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3642
3629
  if (toolBackend('webaz_blocklist') === 'network') {
3643
- const apiKey = String(args.api_key || '');
3630
+ const apiKey = resolveMcpApiKey(args);
3644
3631
  if (!apiKey)
3645
3632
  return { error: 'api_key required' };
3646
3633
  const act = String(args.action || '');
@@ -3655,7 +3642,7 @@ async function handleBlocklist(args) {
3655
3642
  return await apiCall('/api/blocklist/' + uid, { method: 'DELETE', apiKey });
3656
3643
  return { error: `unknown action: ${act}` };
3657
3644
  }
3658
- const auth = requireAuth(db, args.api_key);
3645
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3659
3646
  if ('error' in auth)
3660
3647
  return auth;
3661
3648
  const { user } = auth;
@@ -3694,7 +3681,7 @@ async function handleBlocklist(args) {
3694
3681
  async function handleFollows(args) {
3695
3682
  // RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3696
3683
  if (toolBackend('webaz_follows') === 'network') {
3697
- const apiKey = String(args.api_key || '');
3684
+ const apiKey = resolveMcpApiKey(args);
3698
3685
  if (!apiKey)
3699
3686
  return { error: 'api_key required' };
3700
3687
  const act = String(args.action || '');
@@ -3711,7 +3698,7 @@ async function handleFollows(args) {
3711
3698
  return await apiCall('/api/follows/' + uid + '/status', { apiKey });
3712
3699
  return { error: `unknown action: ${act}` };
3713
3700
  }
3714
- const auth = requireAuth(db, args.api_key);
3701
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3715
3702
  if ('error' in auth)
3716
3703
  return auth;
3717
3704
  const { user } = auth;
@@ -3760,7 +3747,7 @@ async function handleNearby(args) {
3760
3747
  const action = String(args.action || '');
3761
3748
  // RFC-003 Batch 1:NETWORK 模式 → 调 webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3762
3749
  if (toolBackend('webaz_nearby') === 'network') {
3763
- const apiKey = String(args.api_key || '');
3750
+ const apiKey = resolveMcpApiKey(args);
3764
3751
  if (!apiKey)
3765
3752
  return { error: 'api_key required' };
3766
3753
  if (action === 'set_location')
@@ -3778,7 +3765,7 @@ async function handleNearby(args) {
3778
3765
  }
3779
3766
  return { error: `unknown action: ${action}` };
3780
3767
  }
3781
- const auth = requireAuth(db, args.api_key);
3768
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3782
3769
  if ('error' in auth)
3783
3770
  return auth;
3784
3771
  const { user } = auth;
@@ -3829,7 +3816,7 @@ async function handleNearby(args) {
3829
3816
  async function handleDefaultAddress(args) {
3830
3817
  // RFC-003 Batch 2:NETWORK 模式 → webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3831
3818
  if (toolBackend('webaz_default_address') === 'network') {
3832
- const apiKey = String(args.api_key || '');
3819
+ const apiKey = resolveMcpApiKey(args);
3833
3820
  if (!apiKey)
3834
3821
  return { error: 'api_key required' };
3835
3822
  const act = String(args.action || '');
@@ -3848,7 +3835,7 @@ async function handleDefaultAddress(args) {
3848
3835
  }
3849
3836
  return { error: `unknown action: ${act}` };
3850
3837
  }
3851
- const auth = requireAuth(db, args.api_key);
3838
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3852
3839
  if ('error' in auth)
3853
3840
  return auth;
3854
3841
  const { user } = auth;
@@ -3889,7 +3876,7 @@ async function handleShareables(args) {
3889
3876
  const action = String(args.action || '');
3890
3877
  // RFC-003 Batch 1:NETWORK 模式 → 调 webaz.xyz 真网络(Bearer api_key);SANDBOX 走本地。
3891
3878
  if (toolBackend('webaz_shareables') === 'network') {
3892
- const apiKey = String(args.api_key || '');
3879
+ const apiKey = resolveMcpApiKey(args);
3893
3880
  if (!apiKey)
3894
3881
  return { error: 'api_key required' };
3895
3882
  if (action === 'list_mine')
@@ -3917,7 +3904,7 @@ async function handleShareables(args) {
3917
3904
  }
3918
3905
  return { error: `unknown action: ${action}` };
3919
3906
  }
3920
- const auth = requireAuth(db, args.api_key);
3907
+ const auth = requireAuth(db, resolveMcpApiKey(args));
3921
3908
  if ('error' in auth)
3922
3909
  return auth;
3923
3910
  const { user } = auth;
@@ -4073,7 +4060,7 @@ async function pwaApi(method, path, apiKey, body) {
4073
4060
  }
4074
4061
  }
4075
4062
  async function handleSecondhand(args) {
4076
- const apiKey = String(args.api_key || '');
4063
+ const apiKey = resolveMcpApiKey(args);
4077
4064
  const action = String(args.action || '');
4078
4065
  const isPublic = action === 'browse' || action === 'detail';
4079
4066
  if (!isPublic) {
@@ -4144,7 +4131,7 @@ async function handleSecondhand(args) {
4144
4131
  }
4145
4132
  }
4146
4133
  async function handleTrial(args) {
4147
- const apiKey = String(args.api_key || '');
4134
+ const apiKey = resolveMcpApiKey(args);
4148
4135
  const action = String(args.action || '');
4149
4136
  const isPublic = action === 'get_campaign';
4150
4137
  if (!isPublic) {
@@ -4197,7 +4184,7 @@ async function handleTrial(args) {
4197
4184
  }
4198
4185
  }
4199
4186
  async function handleSkillMarket(args) {
4200
- const apiKey = String(args.api_key || '');
4187
+ const apiKey = resolveMcpApiKey(args);
4201
4188
  const action = String(args.action || '');
4202
4189
  const isPublic = action === 'list' || action === 'detail';
4203
4190
  if (!isPublic) {
@@ -4270,7 +4257,7 @@ async function handleSkillMarket(args) {
4270
4257
  }
4271
4258
  }
4272
4259
  async function handleRfq(args) {
4273
- const apiKey = String(args.api_key || '');
4260
+ const apiKey = resolveMcpApiKey(args);
4274
4261
  const action = String(args.action || '');
4275
4262
  if (!apiKey)
4276
4263
  return { error: 'api_key required' };
@@ -4319,7 +4306,7 @@ async function handleRfq(args) {
4319
4306
  }
4320
4307
  }
4321
4308
  async function handleBid(args) {
4322
- const apiKey = String(args.api_key || '');
4309
+ const apiKey = resolveMcpApiKey(args);
4323
4310
  const action = String(args.action || '');
4324
4311
  if (!apiKey)
4325
4312
  return { error: 'api_key required' };
@@ -4393,7 +4380,7 @@ async function handleBid(args) {
4393
4380
  }
4394
4381
  }
4395
4382
  async function handleChat(args) {
4396
- const apiKey = String(args.api_key || '');
4383
+ const apiKey = resolveMcpApiKey(args);
4397
4384
  const action = String(args.action || '');
4398
4385
  if (!apiKey)
4399
4386
  return { error: 'api_key required' };
@@ -4435,7 +4422,7 @@ async function handleChat(args) {
4435
4422
  }
4436
4423
  }
4437
4424
  async function handleAutoBidSkill(args) {
4438
- const apiKey = String(args.api_key || '');
4425
+ const apiKey = resolveMcpApiKey(args);
4439
4426
  const action = String(args.action || '');
4440
4427
  if (!apiKey)
4441
4428
  return { error: 'api_key required' };
@@ -4558,7 +4545,7 @@ async function handleCharity(args) {
4558
4545
  return readEndpoint('webaz_charity', '/charity/leaderboard');
4559
4546
  if (action === 'fund')
4560
4547
  return readEndpoint('webaz_charity', '/charity/fund');
4561
- const apiKey = String(args.api_key || '');
4548
+ const apiKey = resolveMcpApiKey(args);
4562
4549
  if (!apiKey)
4563
4550
  return { error: 'api_key required for this action' };
4564
4551
  if (toolBackend('webaz_charity') !== 'network') {
@@ -4621,7 +4608,7 @@ async function handleP2pProduct(args) {
4621
4608
  return { error: String(e.message) };
4622
4609
  }
4623
4610
  }
4624
- const apiKey = String(args.api_key || '');
4611
+ const apiKey = resolveMcpApiKey(args);
4625
4612
  if (!apiKey)
4626
4613
  return { error: 'api_key required for create/patch' };
4627
4614
  const auth = requireAuth(db, apiKey);
@@ -4648,7 +4635,7 @@ async function handleP2pProduct(args) {
4648
4635
  return { error: `unknown action: ${action}` };
4649
4636
  }
4650
4637
  async function handleLike(args) {
4651
- const apiKey = String(args.api_key || '');
4638
+ const apiKey = resolveMcpApiKey(args);
4652
4639
  const action = String(args.action || '');
4653
4640
  const sid = String(args.shareable_id || '');
4654
4641
  if (!apiKey || !sid)
@@ -4674,7 +4661,7 @@ async function handleLeaderboard(args) {
4674
4661
  return readEndpoint('webaz_leaderboard', '/leaderboard?kind=' + kind + '&limit=' + limit);
4675
4662
  }
4676
4663
  async function handleAuction(args) {
4677
- const apiKey = String(args.api_key || '');
4664
+ const apiKey = resolveMcpApiKey(args);
4678
4665
  const action = String(args.action || '');
4679
4666
  if (!apiKey)
4680
4667
  return { error: 'api_key required' };
@@ -5132,7 +5119,7 @@ function addHours(date, hours) {
5132
5119
  function recordToolCall(tool, args, result, latencyMs) {
5133
5120
  let userId = null;
5134
5121
  try {
5135
- const apiKey = args.api_key;
5122
+ const apiKey = resolveMcpApiKey(args);
5136
5123
  if (apiKey) {
5137
5124
  const row = db.prepare('SELECT id FROM users WHERE api_key = ?').get(apiKey);
5138
5125
  if (row)