feat(mcp): the paid-depth seam and its structured refusal - #11219
Merged
Conversation
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
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
metagraphed-registry-sync-api | af49a1d | Aug 14 2026, 01:17 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
metagraphed-wss-lb | af49a1d | Aug 14 2026, 01:17 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
metagraphed-data-api | af49a1d | Aug 14 2026, 01:17 PM |
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #11219 +/- ##
=======================================
Coverage 95.51% 95.51%
=======================================
Files 742 743 +1
Lines 45308 45329 +21
Branches 16648 16658 +10
=======================================
+ Hits 43277 43298 +21
Misses 526 526
Partials 1505 1505
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 this repo 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:
applyTieredRateLimitresolves the tier at the MCP dispatch seam, andauthTierhas ridden every$mcp_tool_callsince #8967 — which is exactly why phase 3 could be decided from data instead of taste.Phase 2 — the structured refusal.
payment_requiredrides the existing tool-error path with apaymentblock: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 actually read: the tool description andpublic/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, aboveget_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/90dopen to everyone, and only1yandall— the two whole-rollup scans — need a paid key.Phase 4 (x402) is the evaluate-after step in the issue; the seam it needed now exists and the refusal shape is where a challenge will live.
Two traps closed by tests
"enterprise","__proto__",""all rank below free — otherwise a typo in an account record becomes free access to every paid depth.allparses todays: 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-resetssrc/mcp-tier-gate.ts(100%) and every changedsrc/mcp-server.tsline. The one partial branch an earlier revision had — a?? "anonymous"fallback — was removed rather than ignored:applyTieredRateLimitalways sets a tier, so the arm was unreachable by construction.tools/call: anonymous1y→payment_requiredwith the block; the same anonymous caller at30d→ never gated; the gated tool still appears intools/list