GRC-007: Compass - A Decentralised Subgraph-MCP Gateway on Horizon

GRC-007: Compass — A Decentralised Subgraph-MCP Gateway on Horizon
Stage: RFC (Request for Comment)
GRC: 007
Authors: @cargopete (Petko Pavlovski)
Related: GRC-005: Dispatch · GRC-006: Mainline · MCP specification · GIP-0066: Graph Horizon · GIP-0054: GraphTally


Summary

This GRC introduces Compass — a Horizon data service that exposes every subgraph an indexer serves as a discoverable, pay-per-call Model Context Protocol (MCP) tool, settled in GRT via GraphTally (TAP v2) or in USDC via x402. Any AI agent that speaks MCP — Claude, Cursor, OpenAI Agents, Eliza, AutoGPT, and the rest of the growing ecosystem — can query 50,000+ subgraphs for under a cent each, with no API key, no centralised operator, and no bespoke integration.

The contract is on Arbitrum Sepolia. The gateway is fully functional end-to-end. This GRC is posted to share the design openly and explore whether the Graph community wants this to become an official Horizon data service.


Background

AI agents are becoming the dominant consumer of structured on-chain data. Every major AI lab ships an agent framework. Every enterprise running LLMs is building autonomous workflows. The common thread: agents need to read structured facts about the world — token balances, DAO proposals, lending positions, NFT ownership, DEX prices, ENS records — and they need to read them programmatically, cheaply, and reliably.

The Graph has already solved the data side. 50,000+ subgraphs, decades of indexed chain history, a battle-tested query layer, a decentralised operator network. The problem is that agents cannot talk to it. The Graph speaks GraphQL. Agents speak tools.

MCP is the emerging standard for agent-tool communication. Anthropic published the spec in late 2024. By Q1 2025, every major agent framework had MCP support. MCP defines a JSON-RPC 2.0 protocol over HTTP where a server exposes a list of tools — typed, described, callable — and a client (the agent) discovers and invokes them. The match between MCP tools and subgraph entities is almost embarrassingly direct: a subgraph with domains, registrations, and transfers entities becomes three MCP tools — ens_mainnet__domains, ens_mainnet__registrations, ens_mainnet__transfers — each with typed arguments derived from the schema.

The question this experiment asks is: can The Graph’s Horizon framework host an MCP data service that makes this match real, without new payment primitives, without a centralised operator, and without breaking anything that exists today? The answer, as implemented: yes.


The Key Insight

Graph-node does not need to change. MCP does not need to change. The gap is a translation layer and a payment layer.

Translation: GraphQL __schema introspection already gives us everything needed to generate typed MCP tools. Query a subgraph’s schema, enumerate the root query fields, map each field’s arguments to JSON Schema — you have a complete MCP tool description. Compass does this at startup for every configured subgraph. An agent running tools/list gets one tool per entity per subgraph, with correct argument types, optional/required flags, and a description. Running tools/call with typed arguments produces a GraphQL query automatically, forwards it to graph-node, and returns the result. No GraphQL knowledge required on the agent side.

Payment: TAP receipts already work. Agents sign an EIP-712 receipt per call, indexers accumulate them into RAVs, RAVs settle on-chain via MCPDataService.collect()GraphTallyCollectorPaymentsEscrow → GRT to indexer. This is byte-for-byte the same flow as Dispatch and Subgraph queries. Compass adds a second payment rail for agents that do not hold GRT: x402 USDC-on-Base, where the agent pays a USDC transfer on Base and includes the transaction hash in the request header. The gateway verifies the on-chain transfer before forwarding the query.


Design

5.1 MCPDataService.sol

The contract inherits from the same DataService stack as SubgraphService and RPCDataService: DataService + DataServiceFees + DataServicePausableUpgradeable (GIP-0066). No new base contracts. No new payment primitives. Deployed as a UUPS upgradeable proxy on Arbitrum.

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.27;

contract MCPDataService is
    OwnableUpgradeable,
    UUPSUpgradeable,
    DataService,
    DataServiceFees,
    DataServicePausableUpgradeable,
    IMCPDataService
{
    uint256 public constant DEFAULT_MIN_PROVISION  = 1_000e18;  // 1,000 GRT
    uint256 public constant BURN_CUT_PPM           = 10_000;    // 1%
    uint256 public constant DATA_SERVICE_CUT_PPM   = 10_000;    // 1%
    uint64  public constant MIN_THAWING_PERIOD     = 14 days;
    uint256 public constant STAKE_TO_FEES_RATIO    = 5;         // matches SubgraphService

    struct SubgraphEntry {
        bytes32 deploymentId;    // keccak of CID multihash bytes
        uint128 pricePerQuery;   // GRT wei per MCP tool call
        string  endpoint;        // indexer's Compass gateway URL
        bool    active;
    }

    mapping(address => bool)           public registeredProviders;
    mapping(address => address)        public paymentsDestination;
    mapping(address => SubgraphEntry[]) internal _providerSubgraphs;

    function register(address serviceProvider, bytes calldata data) external override;
    function addSubgraph(address serviceProvider, bytes calldata data) external override;
    function removeSubgraph(address serviceProvider, bytes calldata data) external override;
    function collect(address serviceProvider, IGraphPayments.PaymentTypes, bytes calldata data)
        external override returns (uint256 fees);
}

Key parameter choices and rationale:

Parameter Value Rationale vs. SubgraphService / Dispatch
DEFAULT_MIN_PROVISION 1,000 GRT Lower than SubgraphService (100k) and Dispatch (10k) — MCP serving requires no syncing or archive; any indexer already running graph-node can participate. Raise in Phase 2 once demand is demonstrated.
STAKE_TO_FEES_RATIO 5:1 Matches SubgraphService. A provider with 1,000 GRT can collect 200 GRT before provision is fully locked.
MIN_THAWING_PERIOD 14 days Matches Dispatch.
BURN_CUT_PPM 10,000 (1%) Deflationary signal; identical mechanism to SubgraphService.
DATA_SERVICE_CUT_PPM 10,000 (1%) Protocol revenue; same pattern as SubgraphService.

slash() reverts unconditionally — same position as Dispatch at launch. MCP responses are not cryptographically verifiable on-chain in the general case. See §5.4 on verification.

5.2 MCP Protocol

Compass implements MCP Streamable HTTP — JSON-RPC 2.0 over a single HTTP endpoint. Three methods matter:

tools/list — returns all tools. No payment required. Each tool has a name, description, and a JSON Schema for its input arguments. Tools are named {subgraph_name}__{query_field} (e.g. ens_mainnet__domains). If schema introspection fails at startup (graph-node unreachable), a generic fallback tool is registered that accepts a raw query argument.

tools/call — executes a tool. Requires a tap-receipt header (GRT) or x-payment header (USDC). Typed arguments are mapped to a GraphQL query automatically. A _fields argument lets the caller specify which fields to return. A _query argument overrides everything for complex queries.

HTTP 402 — returned when neither payment header is present. The body is an x402 payment spec listing both rails (TAP/GRT and USDC/Base), so any x402-capable agent can handle payment automatically without prior configuration.

5.3 Schema Introspection → Typed Tools

At startup, the gateway sends a __schema introspection query to each configured subgraph and derives one MCP tool per root query field:

Subgraph schema:
  Query.domains(first: Int, skip: Int, where: Domain_filter, orderBy: Domain_orderBy): [Domain!]!
  Query.registrations(first: Int, ...): [Registration!]!

→ MCP tools:
  ens_mainnet__domains      { first, skip, where, orderBy, _fields, _query }
  ens_mainnet__registrations { first, skip, where, orderBy, _fields, _query }

Each argument’s type is preserved in the JSON Schema. Filter types (Domain_filter) become object with all filter fields enumerated. Enum types (Domain_orderBy) become string with all valid values listed. This gives agents enough type information to construct valid calls without reading GraphQL documentation.

This is the part that makes Compass qualitatively different from a generic “GraphQL over HTTP” proxy: the agent does not need to know GraphQL, does not need to know a subgraph’s schema, and does not need any Compass-specific SDK. It just calls a tool by name with JSON arguments.

5.4 Payment Model

Primary rail — TAP / GRT (gas-free batch settlement):

Agent signs TAP receipt (GRT wei, EIP-712)
    └── tap-receipt header on tools/call
            └── compass-gateway validates: data_service, service_provider, value, sig
                    └── forwards GraphQL to graph-node → returns result

Indexer accumulates receipts → submits RAV → MCPDataService.collect()
    └── GraphTallyCollector → PaymentsEscrow → GraphPayments
            └── distributes: protocol tax / data service cut / delegator cut / indexer

This is identical to the TAP flow on SubgraphService and Dispatch. The indexer’s existing indexer-tap-agent can aggregate Compass receipts alongside subgraph query receipts with a config change.

Secondary rail — x402 / USDC-on-Base (agent-discovery path):

Agent pays USDC on Base (any wallet / Coinbase Commerce)
    └── x-payment: {"scheme":"exact","network":"base","payload":{"txHash":"0x..."}}
            └── compass-gateway calls eth_getTransactionReceipt on Base RPC
                    └── verifies USDC Transfer log: to == indexer, amount >= required
                            └── forwards GraphQL to graph-node → returns result

The x402 rail exists for agents that do not hold GRT. Coinbase AgentKit, and any other x402-capable agent, can pay USDC on Base directly without a separate GRT onboarding step. This lowers the barrier for agent developers trying Compass for the first time. GRT/TAP remains the primary rail for production indexers — it settles gas-free in batch and integrates with the existing GraphTally collector infrastructure.

Verification: Compass currently does not slash. An agent can detect a wrong response by querying multiple indexers and comparing results (the x402 receipt includes the indexer’s address, making this straightforward). On-chain fraud proofs for subgraph query responses would require full POI re-derivation — the same hard problem SubgraphService solves with dispute rounds. That is out of scope for this GRC. Economic security (stake lock + reputation routing) is the current model.

5.5 Indexer Integration

From an indexer’s perspective, Compass is a sidecar binary. It connects to their existing graph-node and validates payments before forwarding queries. No changes to graph-node. No changes to the indexer’s Horizon provision. The additional steps are:

  1. Add compass.toml listing the subgraphs to serve
  2. Run compass register — provisions GRT into MCPDataService and registers each deployment
  3. Run compass run (or docker compose up) — starts the gateway sidecar

Indexers already running the StakeSquid docker stack can join the same Docker network with a one-line compose addition.


Developer Experience

For AI agents:

// Discover what data is available
POST https://compass.your-indexer.example.com/
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
// Returns: ens_mainnet__domains, ens_mainnet__registrations, uniswap_v3__pools, ...

// Query typed, with auto-built GraphQL
POST https://compass.your-indexer.example.com/
tap-receipt: {"receipt":{...},"signature":"0x..."}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{
  "name":"ens_mainnet__domains",
  "arguments":{"first":5,"orderBy":"createdAt","_fields":"id name labelName"}
}}
// Returns: {"domains": [{"id":"0x...","name":"vitalik.eth","labelName":"vitalik"},...]}

No GraphQL. No subgraph schema knowledge. No API key. The agent discovers tools, inspects their typed schemas, and calls them with JSON. This is what MCP is designed for.

For indexers:

# One-time: register on-chain
INDEXER_PRIVATE_KEY=0x... compass register --config compass.toml

# Ongoing: start serving
compass run

# or with Docker
docker compose up -d

Relation to Existing Horizon Data Services

Compass is the third community-built Horizon data service:

Service GRC Data type Consumer Payment
SubgraphService Indexed subgraph data dApps, dashboards GRT (TAP)
Dispatch GRC-005 Raw JSON-RPC (eth_call etc.) dApps, wallets GRT (TAP)
Mainline GRC-006 Raw Firehose block streams Substreams, graph-node GRT (TAP)
Compass GRC-007 Structured subgraph data as MCP tools AI agents GRT (TAP) + USDC (x402)

Compass reuses RPCDataService.sol as its direct template. MCPDataService.sol is approximately 60 lines of delta from RPCDataService.sol — the chain-tier registry is replaced by a subgraph-deployment registry, and the collect() path is identical.

The x402 secondary rail is novel relative to Dispatch and Mainline. It is not proposed as a replacement for GRT/TAP — it is an onramp for the agent ecosystem, which is largely USDC-native today. Long-term, as GRT becomes more accessible to agent wallets, the primary rail dominates.


Implementation Status

The gateway is fully functional. What is live today:

Component Status
MCPDataService.sol — Foundry tests :white_check_mark:
MCPDataService.sol — Arbitrum Sepolia deploy :white_check_mark:
tools/list and tools/call end-to-end :white_check_mark:
TAP receipt validation (EIP-712, full) :white_check_mark:
x402 USDC-on-Base payment verification :white_check_mark:
HTTP 402 payment spec (dual-rail x402 response) :white_check_mark:
Per-entity MCP tools from __schema introspection :white_check_mark:
Docker Compose deploy (StakeSquid-compatible) :white_check_mark:
compass run CLI :soon_arrow: (in progress)
compass register CLI :soon_arrow: (in progress)
Arbitrum One mainnet deploy :soon_arrow:
Slashing ✗ — not planned for Phase 1
GRT issuance rewards ✗ — requires governance approval

Workspace crates:

Crate Purpose
compass-core Config, shared types, errors
compass-tap TAP v2 EIP-712 receipt validation
compass-mcp MCP Streamable HTTP JSON-RPC 2.0 types
compass-x402 HTTP 402 builder + USDC-on-Base verification
compass-schema __schema introspection → typed MCP tools + query builder
compass-gateway Main binary: axum server, dual-rail payment, graph-node forwarding
compass-cli compass run and compass register

What This Means for The Graph

Nothing in the existing network changes. SubgraphService, HorizonStaking, GraphTallyCollector, and PaymentsEscrow are all reused unchanged. Existing indexers, curators, and delegators are unaffected.

Existing Graph indexers are the natural first movers. If you are already staking GRT, running graph-node, and serving subgraphs — you can serve those same subgraphs to AI agents with a sidecar binary and a one-time registration transaction.

The data is already there. 50,000+ subgraphs are indexed today. Compass does not require new subgraphs, new deployments, or new indexing work. Any subgraph already served by a graph-node can be exposed as an MCP tool the same day an indexer runs compass register.

The strategic implication is that The Graph is positioned to become the default data layer for the AI agent economy — but only if agents can actually reach the data. Subgraphs behind a GraphQL API are invisible to a Claude agent or an OpenAI Agents workflow. Subgraphs behind an MCP endpoint are first-class tools. Compass is the bridge.


Open Questions

On pricing: The default of 10,000,000,000,000 GRT wei (~0.00001 GRT, ~$0.000001) per tool call is a placeholder calibrated to be cheap enough not to block adoption. What is the right price for a production subgraph query? Should filter-heavy queries (large where clauses) carry a higher price than simple first: 5 lookups? Should pricing be per-entity or per-byte?

On the x402 rail: USDC-on-Base settlement does not integrate with the existing TAP/GraphTallyCollector stack — it is a direct on-chain transfer verified by the gateway. This means x402 payments are not aggregated into RAVs and do not settle through PaymentsEscrow. Is this acceptable long-term, or should there be a mechanism to convert x402 USDC receipts into GRT-denominated TAP receipts at the gateway level? Alternatively, is x402 intentionally a separate revenue stream?

On the governance of the chain ID list for x402: The x402 rail currently only verifies USDC transfers on Base. Should other chains be supported (Ethereum mainnet, Arbitrum One)? Who decides which chains and which ERC-20 tokens are acceptable payment?

On discovery: How should agents discover Compass endpoints? The current model is operator-announced (the indexer publishes their endpoint URL). A network subgraph indexing ProviderRegistered and SubgraphAdded events on MCPDataService would enable permissionless discovery — any agent could find all available Compass providers and their subgraph offerings without configuration. Should this be a first-class deliverable?

On the _query escape hatch: The _query argument lets callers bypass the auto-generated GraphQL and submit raw queries. This is useful for complex queries (nested fragments, multiple entities in one call) but weakens the typed-tool abstraction. Should raw queries be gated differently — e.g., a higher price, a separate tool, or disabled by default?

On minimum stake: 1,000 GRT minimum provision with a 5:1 ratio means a provider can collect 200 GRT before their full provision is locked. Is 200 GRT enough economic skin-in-the-game given that slashing is not implemented? The minimum can be raised in a governance transaction without a contract upgrade.

On the subgraph-discovery network subgraph: Should the Compass network subgraph be deployed to The Graph Studio as part of mainnet launch, or is it an indexer-operated concern?


Deployed Addresses

Contract Network Address
MCPDataService (proxy) Arbitrum Sepolia (421614) see contracts/ deploy output
HorizonStaking (existing) Arbitrum One (42161) 0x00669A4CF01450B64E8A2A20E9b1FCB71E61eF03
GraphTallyCollector (existing) Arbitrum One (42161) 0x8f69F5C07477Ac46FBc491B1E6D91E2bb0111A9e

Arbitrum One mainnet deploy is pending the compass-cli release and final review. Addresses will be updated here at launch.

GitHub: GitHub - cargopete/compass · GitHub