Skip to content

feat(mcp): the paid-depth seam and its structured refusal - #11218

Closed
JSONbored wants to merge 1 commit into
mainfrom
feat/paid-tier-seam
Closed

feat(mcp): the paid-depth seam and its structured refusal#11218
JSONbored wants to merge 1 commit into
mainfrom
feat/paid-tier-seam

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Closes #11179.

Tier gates depth, never visibility. Every tool stays listed and callable at every tier; what a key buys is how far back a call may read. A paid tool hidden from free callers is a tool nobody discovers or upgrades for — the same absence-reads-as-nonexistence failure measured twice already (unregistered mutations in #11146, empty captured schemas in #11148).

What shipped

Phase 1 was already in place and is verified, not rebuilt: applyTieredRateLimit resolves the tier at the MCP dispatch seam, and authTier has ridden every $mcp_tool_call since #8967 — which is exactly why phase 3 could be decided from data.

Phase 2 — the structured refusal. payment_required rides the existing tool-error path with a payment block: tier, required_tier, boundary, limit, requested, upgrade_url. The message carries the upgrade URL too, so a client that renders only prose still shows its human something actionable. The block is shaped so an x402 challenge later joins it rather than introducing a second error vocabulary. Published where agents read: the tool description and public/skills/bittensor/SKILL.md.

Phase 3 — the first boundary, chosen from usage. Over the 30 days to 2026-08-14, the economics family carried the most distinct callers of any tool (get_subnet_economics: 52 callers, above get_subnet_health's 47 on a third the volume) — economic history is the demand centre the issue predicted. Effectively all measured traffic is anonymous, so the free depth stays a real product rather than a teaser: 7d/30d/90d are open to every caller, and only 1y and all — the two whole-rollup scans — require a paid key.

Phase 4 (x402) is explicitly the evaluate-after step in the issue; the seam it needed now exists, and the refusal shape is the place a challenge will live.

Two traps closed by tests

  • An unrecognised tier clears nothing. "enterprise", "__proto__", "" all rank below free — otherwise a typo in an account record becomes free access to every paid depth.
  • An unbounded window crosses every finite ceiling. all parses to days: null; treating null as "no request" would have made the deepest read the one thing that passed free.

Validation

  • typecheck, lint, format:check, validate:mcp (240 tools), validate:unreferenced-exports (731, at the ceiling — not raised), validate:module-state-resets
  • Full suite: 918 files / 20,932 tests passed
  • Diff-intersection coverage: 0 uncovered lines and 0 uncovered branch arms across src/mcp-tier-gate.ts and the changed src/mcp-server.ts lines
  • The boundary is proven end-to-end through a real tools/call: anonymous 1ypayment_required with the block; the same anonymous caller at 30d → never gated; the gated tool still appears in tools/list

Tier gates DEPTH, never visibility. Every tool stays listed and callable at
every tier; a call past a paid boundary answers a structured
`payment_required` naming the boundary, the tier that clears it, and where to
get one -- so an agent relays an actionable upgrade path instead of reporting
a dead end. A paid tool hidden from free callers is a tool nobody discovers
or upgrades for, which is the same absence-reads-as-nonexistence failure this
repo measured twice in #11146 and #11148.

The tier resolver already existed at the MCP dispatch seam (authTier has
ridden every $mcp_tool_call since #8967), so the seam is the refusal plus the
first boundary: history windows on get_economics_trends. That surface was
CHOSEN FROM USAGE -- over the 30 days to 2026-08-14 the economics family
carried the most distinct callers of any tool (get_subnet_economics: 52,
above get_subnet_health's 47 on a third the volume). Effectively all traffic
is anonymous, so the free depth stays a real product: 7d/30d/90d are open to
everyone and only `1y` and `all` -- the two whole-rollup scans -- need a key.

An unrecognised tier clears nothing, and an unbounded window crosses every
finite ceiling. The `payment` block is shaped so an x402 challenge later
joins it rather than introducing a second error vocabulary.

Closes #11179
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 14, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
metagraphed-data-api d1e1669 Aug 14 2026, 01:07 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 14, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
metagraphed-registry-sync-api d1e1669 Aug 14 2026, 01:07 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 14, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
metagraphed-wss-lb d1e1669 Aug 14 2026, 01:07 PM

@superagent-security

Copy link
Copy Markdown

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.45455% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 95.51%. Comparing base (20e8af1) to head (d1e1669).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/mcp-server.ts 66.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11218      +/-   ##
==========================================
- Coverage   95.51%   95.51%   -0.01%     
==========================================
  Files         742      743       +1     
  Lines       45308    45330      +22     
  Branches    16648    16659      +11     
==========================================
+ Hits        43277    43298      +21     
  Misses        526      526              
- Partials     1505     1506       +1     
Files with missing lines Coverage Δ
src/mcp-tier-gate.ts 100.00% <100.00%> (ø)
src/mcp-server.ts 93.99% <66.66%> (-0.03%) ⬇️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JSONbored

Copy link
Copy Markdown
Owner Author

Closing on red codecov/patch rather than pushing over it. One partial branch: ctx?.authTier ?? "anonymous" invented a fallback this file explicitly rejects two thousand lines down (applyTieredRateLimit always sets a tier), so the arm was unreachable by construction. Removed rather than ignored; reopening with 0 uncovered lines and 0 partial branches.

@JSONbored JSONbored closed this Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

design(monetization): paid MCP access — one server, tiered depth, and where x402 fits

1 participant