subgraph-registry-mcp 0.4.2 → 0.6.0
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.
- package/README.md +42 -2
- package/data/registry.db +0 -0
- package/package.json +2 -2
- package/src/index.js +104 -15
package/README.md
CHANGED
|
@@ -18,8 +18,48 @@ Agents querying The Graph need to discover and select the right subgraph before
|
|
|
18
18
|
2. **Fetches** the GraphQL schema for every deployment
|
|
19
19
|
3. **Classifies** each subgraph by domain, protocol type, canonical entities, and schema family
|
|
20
20
|
4. **Scores** reliability using on-chain signals (query fees, volume, curation, stake)
|
|
21
|
-
5. **
|
|
22
|
-
6. **
|
|
21
|
+
5. **Returns x402 + legacy query URLs** — agents can pay $0.01 USDC on Base per query (no API key) or use a Studio key
|
|
22
|
+
6. **Publishes** as SQLite database + REST API + MCP server
|
|
23
|
+
7. **Generates** visual dashboards and bot-readable category files (auto-updated with each sync)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Querying with x402 (no API key)
|
|
28
|
+
|
|
29
|
+
Every result includes `query_url_x402` alongside the legacy `query_url`. The Graph's public x402 gateway (live since 2026-05-08) accepts **$0.01 USDC on Base** per query with zero signup.
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
// An x402-native agent — discovery to data in two calls
|
|
33
|
+
const { recommendations } = await mcp.call("recommend_subgraph", {
|
|
34
|
+
goal: "find DEX trades on Arbitrum",
|
|
35
|
+
});
|
|
36
|
+
const top = recommendations[0];
|
|
37
|
+
|
|
38
|
+
// POST your GraphQL query. The first call returns HTTP 402 with a
|
|
39
|
+
// base64 `payment-required` header; the x402 client signs the
|
|
40
|
+
// EIP-3009 USDC transfer on Base and retries automatically.
|
|
41
|
+
const data = await x402Fetch(top.query_url_x402, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
body: JSON.stringify({ query: "{ swaps(first: 5) { id amountUSD } }" }),
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Pricing manifest returned per subgraph:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"amount_usd": 0.01,
|
|
52
|
+
"asset": "USDC",
|
|
53
|
+
"asset_contract": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
54
|
+
"chain": "base",
|
|
55
|
+
"network": "eip155:8453",
|
|
56
|
+
"pay_to": "0x79DC34E41B2b591078d3dE222C43EcaaBD52FcCB",
|
|
57
|
+
"scheme": "exact",
|
|
58
|
+
"asset_transfer_method": "eip3009"
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Client libraries: [`@graphprotocol/client-x402`](https://www.npmjs.com/package/@graphprotocol/client-x402), `x402-fetch`, or any generic x402 wrapper.
|
|
23
63
|
|
|
24
64
|
---
|
|
25
65
|
|
package/data/registry.db
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "subgraph-registry-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"mcpName": "io.github.PaulieB14/subgraph-registry-mcp",
|
|
5
|
-
"description": "MCP server for agent-friendly subgraph discovery on The Graph Network. 14,733 classified subgraphs with query
|
|
5
|
+
"description": "MCP server for agent-friendly subgraph discovery on The Graph Network. 14,733 classified subgraphs with x402 query URLs ($0.01 USDC on Base, no API key required), reliability scoring, and protocol classification.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"subgraph-registry-mcp": "src/index.js"
|
package/src/index.js
CHANGED
|
@@ -24,8 +24,9 @@ import Database from "better-sqlite3";
|
|
|
24
24
|
import express from "express";
|
|
25
25
|
import { fileURLToPath } from "url";
|
|
26
26
|
import { dirname, join } from "path";
|
|
27
|
-
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
27
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
28
28
|
import { get as httpsGet } from "https";
|
|
29
|
+
import { createHash } from "crypto";
|
|
29
30
|
|
|
30
31
|
const __filename = fileURLToPath(import.meta.url);
|
|
31
32
|
const __dirname = dirname(__filename);
|
|
@@ -34,8 +35,79 @@ const DB_PATH = join(DATA_DIR, "registry.db");
|
|
|
34
35
|
const GITHUB_DB_URL =
|
|
35
36
|
"https://github.com/PaulieB14/subgraph-registry/raw/main/python/data/registry.db";
|
|
36
37
|
|
|
38
|
+
// SHA-256 of the registry.db shipped with this npm version. Any download or
|
|
39
|
+
// pre-bundled copy that doesn't match this hash is rejected — protects users
|
|
40
|
+
// against a compromised GitHub repo or man-in-the-middle on the download.
|
|
41
|
+
//
|
|
42
|
+
// HOW TO UPDATE WHEN REBUILDING THE REGISTRY:
|
|
43
|
+
// 1. Run the crawler to rebuild python/data/registry.db
|
|
44
|
+
// 2. shasum -a 256 python/data/registry.db
|
|
45
|
+
// 3. Paste the new hash here and bump package.json version
|
|
46
|
+
// 4. Update SKILL.md "Verifying the registry" section
|
|
47
|
+
const EXPECTED_DB_SHA256 =
|
|
48
|
+
"f81b79c53cc13c3428472024187fc7fd502f7418f5da20f0a6e01807dd4011c6";
|
|
49
|
+
// Skip-verification escape hatch (set to "1" only if you're rebuilding the DB
|
|
50
|
+
// locally and know what you're doing — never set in agent-runtime defaults).
|
|
51
|
+
const SKIP_VERIFY = process.env.SUBGRAPH_REGISTRY_SKIP_VERIFY === "1";
|
|
52
|
+
|
|
53
|
+
// ── x402 gateway constants ─────────────────────────────────
|
|
54
|
+
// The Graph's public x402 gateway (live since 2026-05-08) lets agents pay
|
|
55
|
+
// per-query in USDC on Base without any API key. POST GraphQL to query_url_x402
|
|
56
|
+
// and the gateway returns HTTP 402 with a payment manifest; an x402 client
|
|
57
|
+
// (e.g. @graphprotocol/client-x402, x402-fetch) signs the EIP-3009 USDC
|
|
58
|
+
// transfer and retries automatically.
|
|
59
|
+
const X402_GATEWAY_BASE = "https://gateway.thegraph.com/api/x402";
|
|
60
|
+
const X402_PRICING = {
|
|
61
|
+
amount_usd: 0.01,
|
|
62
|
+
asset: "USDC",
|
|
63
|
+
asset_contract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
|
|
64
|
+
chain: "base",
|
|
65
|
+
network: "eip155:8453",
|
|
66
|
+
pay_to: "0x79DC34E41B2b591078d3dE222C43EcaaBD52FcCB", // Graph x402 gateway
|
|
67
|
+
scheme: "exact",
|
|
68
|
+
asset_transfer_method: "eip3009",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function buildQueryEndpoints(subgraphId) {
|
|
72
|
+
return {
|
|
73
|
+
query_url: `https://gateway.thegraph.com/api/[api-key]/subgraphs/id/${subgraphId}`,
|
|
74
|
+
query_url_x402: `${X402_GATEWAY_BASE}/subgraphs/id/${subgraphId}`,
|
|
75
|
+
pricing: X402_PRICING,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
37
79
|
// ── Download DB from GitHub if missing ─────────────────────
|
|
38
80
|
|
|
81
|
+
function sha256OfFile(path) {
|
|
82
|
+
const h = createHash("sha256");
|
|
83
|
+
h.update(readFileSync(path));
|
|
84
|
+
return h.digest("hex");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function verifyDbOrThrow(path) {
|
|
88
|
+
if (SKIP_VERIFY) {
|
|
89
|
+
console.error(
|
|
90
|
+
"SUBGRAPH_REGISTRY_SKIP_VERIFY=1 — skipping registry.db hash check."
|
|
91
|
+
);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const actual = sha256OfFile(path);
|
|
95
|
+
if (actual !== EXPECTED_DB_SHA256) {
|
|
96
|
+
// Refuse to load a registry that doesn't match the known-good hash.
|
|
97
|
+
// Delete the file so the next run gets a fresh download attempt instead
|
|
98
|
+
// of caching a poisoned copy.
|
|
99
|
+
try { unlinkSync(path); } catch (_) {}
|
|
100
|
+
throw new Error(
|
|
101
|
+
`registry.db SHA-256 mismatch.\n` +
|
|
102
|
+
` expected: ${EXPECTED_DB_SHA256}\n` +
|
|
103
|
+
` actual: ${actual}\n` +
|
|
104
|
+
`The downloaded registry does not match the version pinned to this ` +
|
|
105
|
+
`npm package. Refusing to load. If you intentionally rebuilt the DB ` +
|
|
106
|
+
`locally, set SUBGRAPH_REGISTRY_SKIP_VERIFY=1 to bypass.`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
39
111
|
function downloadFile(url, dest) {
|
|
40
112
|
return new Promise((resolve, reject) => {
|
|
41
113
|
const follow = (u) => {
|
|
@@ -62,11 +134,16 @@ function downloadFile(url, dest) {
|
|
|
62
134
|
}
|
|
63
135
|
|
|
64
136
|
async function ensureDb() {
|
|
65
|
-
if (existsSync(DB_PATH))
|
|
137
|
+
if (existsSync(DB_PATH)) {
|
|
138
|
+
verifyDbOrThrow(DB_PATH);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
66
141
|
mkdirSync(DATA_DIR, { recursive: true });
|
|
67
142
|
console.error("Registry not found locally. Downloading from GitHub...");
|
|
68
143
|
await downloadFile(GITHUB_DB_URL, DB_PATH);
|
|
69
|
-
console.error("Downloaded registry.db");
|
|
144
|
+
console.error("Downloaded registry.db — verifying SHA-256...");
|
|
145
|
+
verifyDbOrThrow(DB_PATH);
|
|
146
|
+
console.error("Registry verified OK.");
|
|
70
147
|
}
|
|
71
148
|
|
|
72
149
|
// ── Database ───────────────────────────────────────────────
|
|
@@ -159,7 +236,7 @@ function searchSubgraphs({
|
|
|
159
236
|
entity_count: r.entity_count,
|
|
160
237
|
canonical_entities: JSON.parse(r.canonical_entities),
|
|
161
238
|
powered_by_substreams: Boolean(r.powered_by_substreams),
|
|
162
|
-
|
|
239
|
+
...buildQueryEndpoints(r.id),
|
|
163
240
|
});
|
|
164
241
|
if (results.length >= limit) break;
|
|
165
242
|
}
|
|
@@ -167,7 +244,7 @@ function searchSubgraphs({
|
|
|
167
244
|
return {
|
|
168
245
|
total: results.length,
|
|
169
246
|
subgraphs: results,
|
|
170
|
-
query_instructions: "
|
|
247
|
+
query_instructions: "Two ways to query: (a) RECOMMENDED — POST GraphQL to query_url_x402 and pay $0.01 USDC on Base per query via x402 (no API key required; gateway returns HTTP 402 with a payment manifest, use an x402 client like @graphprotocol/client-x402 to sign and retry). (b) LEGACY — replace [api-key] in query_url with a Graph API key from https://thegraph.com/studio/apikeys/. Call get_subgraph_detail first for the schema.",
|
|
171
248
|
};
|
|
172
249
|
}
|
|
173
250
|
|
|
@@ -253,7 +330,7 @@ function recommendSubgraph({ goal, chain = "" }) {
|
|
|
253
330
|
reliability_score: r.reliability_score,
|
|
254
331
|
ipfs_hash: r.ipfs_hash,
|
|
255
332
|
canonical_entities: JSON.parse(r.canonical_entities),
|
|
256
|
-
|
|
333
|
+
...buildQueryEndpoints(r.id),
|
|
257
334
|
});
|
|
258
335
|
if (recommendations.length >= 5) break;
|
|
259
336
|
}
|
|
@@ -282,12 +359,24 @@ function getSubgraphDetail({ subgraph_id }) {
|
|
|
282
359
|
if (!result.description && result.auto_description) {
|
|
283
360
|
result.description = result.auto_description;
|
|
284
361
|
}
|
|
285
|
-
|
|
362
|
+
const endpoints = buildQueryEndpoints(result.id);
|
|
363
|
+
result.query_url = endpoints.query_url;
|
|
364
|
+
result.query_url_x402 = endpoints.query_url_x402;
|
|
365
|
+
result.pricing = endpoints.pricing;
|
|
286
366
|
result.query_instructions = {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
367
|
+
recommended: "x402",
|
|
368
|
+
x402: {
|
|
369
|
+
url: endpoints.query_url_x402,
|
|
370
|
+
payment: endpoints.pricing,
|
|
371
|
+
flow: "POST GraphQL to url. Gateway returns HTTP 402 with a base64 payment-required header containing the payment manifest. Sign $0.01 USDC on Base with an x402 client and retry. No API key, no signup.",
|
|
372
|
+
client_libraries: ["@graphprotocol/client-x402", "x402-fetch"],
|
|
373
|
+
example_query: "{ pools(first: 5, orderBy: totalValueLockedUSD, orderDirection: desc) { id token0 { symbol } token1 { symbol } totalValueLockedUSD } }",
|
|
374
|
+
},
|
|
375
|
+
api_key_legacy: {
|
|
376
|
+
url: endpoints.query_url,
|
|
377
|
+
flow: "Get an API key from https://thegraph.com/studio/apikeys/, replace [api-key] in the url, then POST GraphQL.",
|
|
378
|
+
},
|
|
379
|
+
schema_hint: "Use the all_entities field above to see what entities and fields are available to query.",
|
|
291
380
|
};
|
|
292
381
|
return result;
|
|
293
382
|
}
|
|
@@ -319,7 +408,7 @@ const TOOLS = [
|
|
|
319
408
|
{
|
|
320
409
|
name: "search_subgraphs",
|
|
321
410
|
description:
|
|
322
|
-
"Search and filter the classified subgraph registry (15,500+ subgraphs). Filter by domain (defi, nfts, dao, gaming, identity, infrastructure, social, analytics), network (mainnet, arbitrum-one, base, matic, bsc, optimism, avalanche), protocol_type (dex, lending, bridge, staking, options, perpetuals, nft-marketplace, yield-aggregator, governance, name-service), canonical entity type (liquidity_pool, trade, token, position, vault, loan, collateral, liquidation, nft_collection, nft_item, nft_sale, proposal, delegate, domain_name, account, transaction, daily_snapshot, hourly_snapshot), or free-text keyword. Returns subgraphs ranked by reliability score
|
|
411
|
+
"Search and filter the classified subgraph registry (15,500+ subgraphs). Filter by domain (defi, nfts, dao, gaming, identity, infrastructure, social, analytics), network (mainnet, arbitrum-one, base, matic, bsc, optimism, avalanche), protocol_type (dex, lending, bridge, staking, options, perpetuals, nft-marketplace, yield-aggregator, governance, name-service), canonical entity type (liquidity_pool, trade, token, position, vault, loan, collateral, liquidation, nft_collection, nft_item, nft_sale, proposal, delegate, domain_name, account, transaction, daily_snapshot, hourly_snapshot), or free-text keyword. Returns subgraphs ranked by reliability score. Each result includes query_url_x402 (POST GraphQL and pay $0.01 USDC on Base per query — no API key needed) and a legacy query_url (Studio API key required).",
|
|
323
412
|
inputSchema: {
|
|
324
413
|
type: "object",
|
|
325
414
|
properties: {
|
|
@@ -336,7 +425,7 @@ const TOOLS = [
|
|
|
336
425
|
{
|
|
337
426
|
name: "recommend_subgraph",
|
|
338
427
|
description:
|
|
339
|
-
"Given a natural-language goal like 'find DEX trades on Arbitrum' or 'get lending liquidation data', returns the best matching subgraphs with reliability scores
|
|
428
|
+
"Given a natural-language goal like 'find DEX trades on Arbitrum' or 'get lending liquidation data', returns the best matching subgraphs with reliability scores. Automatically infers domain and protocol type from the goal. Each result includes query_url_x402 (preferred — POST GraphQL, pay $0.01 USDC on Base per query, no API key) and a legacy query_url for Studio-key flows.",
|
|
340
429
|
inputSchema: {
|
|
341
430
|
type: "object",
|
|
342
431
|
properties: {
|
|
@@ -349,7 +438,7 @@ const TOOLS = [
|
|
|
349
438
|
{
|
|
350
439
|
name: "get_subgraph_detail",
|
|
351
440
|
description:
|
|
352
|
-
"Get full classification detail for a specific subgraph by its subgraph ID or IPFS hash. Returns domain, protocol type, canonical entities, all entity names with field counts, reliability score, signal data, query
|
|
441
|
+
"Get full classification detail for a specific subgraph by its subgraph ID or IPFS hash. Returns domain, protocol type, canonical entities, all entity names with field counts, reliability score, signal data, both query URLs (x402 and legacy), the x402 pricing manifest ($0.01 USDC on Base), and step-by-step instructions for both query paths.",
|
|
353
442
|
inputSchema: {
|
|
354
443
|
type: "object",
|
|
355
444
|
properties: {
|
|
@@ -378,7 +467,7 @@ const HANDLERS = {
|
|
|
378
467
|
|
|
379
468
|
function createServer() {
|
|
380
469
|
const server = new Server(
|
|
381
|
-
{ name: "subgraph-registry", version: "0.
|
|
470
|
+
{ name: "subgraph-registry", version: "0.6.0" },
|
|
382
471
|
{ capabilities: { tools: {} } }
|
|
383
472
|
);
|
|
384
473
|
|