@secondlayer/mcp 3.9.0 → 3.10.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/dist/bin-http.js +231 -213
- package/dist/bin-http.js.map +8 -9
- package/dist/bin.js +231 -213
- package/dist/bin.js.map +8 -9
- package/dist/index.js +231 -213
- package/dist/index.js.map +8 -9
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -10,21 +10,49 @@ import { TRAIT_STANDARDS } from "@secondlayer/stacks/clarity";
|
|
|
10
10
|
import { TYPE_MAP } from "@secondlayer/subgraphs/schema";
|
|
11
11
|
|
|
12
12
|
// src/lib/client.ts
|
|
13
|
-
import { SecondLayer } from "@secondlayer/sdk";
|
|
13
|
+
import { SecondLayer, readX402Receipt, withX402 } from "@secondlayer/sdk";
|
|
14
|
+
import { privateKeyToAccount } from "@secondlayer/stacks/accounts";
|
|
14
15
|
var instance = null;
|
|
15
16
|
function readApiKey() {
|
|
16
17
|
return process.env.SL_API_KEY;
|
|
17
18
|
}
|
|
19
|
+
function buildFetchImpl() {
|
|
20
|
+
const key = process.env.X402_PRIVATE_KEY;
|
|
21
|
+
if (!key)
|
|
22
|
+
return;
|
|
23
|
+
const account = privateKeyToAccount(key);
|
|
24
|
+
const topUpUsd = Number(process.env.X402_TOPUP_USD ?? "");
|
|
25
|
+
const paid = withX402(fetch, {
|
|
26
|
+
account,
|
|
27
|
+
...Number.isFinite(topUpUsd) && topUpUsd > 0 ? {
|
|
28
|
+
topUp: {
|
|
29
|
+
usd: topUpUsd,
|
|
30
|
+
whenBelow: Number(process.env.X402_TOPUP_WHEN_BELOW ?? "0.5")
|
|
31
|
+
}
|
|
32
|
+
} : {}
|
|
33
|
+
});
|
|
34
|
+
const wrapped = async (input, init) => {
|
|
35
|
+
const res = await paid(input, init);
|
|
36
|
+
const receipt = readX402Receipt(res);
|
|
37
|
+
if (receipt) {
|
|
38
|
+
console.error(`[x402] paid call settled tier=${receipt.state ?? "confirmed"} txid=${receipt.txid}`);
|
|
39
|
+
}
|
|
40
|
+
return res;
|
|
41
|
+
};
|
|
42
|
+
return wrapped;
|
|
43
|
+
}
|
|
18
44
|
function getClient() {
|
|
19
45
|
if (!instance) {
|
|
20
46
|
const apiKey = readApiKey();
|
|
21
47
|
const baseUrl = process.env.SECONDLAYER_API_URL;
|
|
22
48
|
const dumpsBaseUrl = process.env.SL_STREAMS_DUMPS_URL;
|
|
49
|
+
const fetchImpl = buildFetchImpl();
|
|
23
50
|
instance = new SecondLayer({
|
|
24
51
|
...apiKey ? { apiKey } : {},
|
|
25
52
|
origin: "mcp",
|
|
26
53
|
...baseUrl ? { baseUrl } : {},
|
|
27
|
-
...dumpsBaseUrl ? { dumpsBaseUrl } : {}
|
|
54
|
+
...dumpsBaseUrl ? { dumpsBaseUrl } : {},
|
|
55
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
28
56
|
});
|
|
29
57
|
}
|
|
30
58
|
return instance;
|
|
@@ -165,7 +193,6 @@ var COLUMN_TYPES = [
|
|
|
165
193
|
}
|
|
166
194
|
];
|
|
167
195
|
var PRODUCT_BLURBS = {
|
|
168
|
-
datasets: "public foundation datasets",
|
|
169
196
|
index: "decoded L2 events, contract calls, blocks, transactions, stacking, mempool",
|
|
170
197
|
streams: "raw canonical chain event firehose",
|
|
171
198
|
contracts: "trait-based contract discovery",
|
|
@@ -176,7 +203,6 @@ var PRODUCT_BLURBS = {
|
|
|
176
203
|
scaffold: "generate typed contract clients from a deployment or ABI"
|
|
177
204
|
};
|
|
178
205
|
var PRODUCT_ORDER = [
|
|
179
|
-
"datasets",
|
|
180
206
|
"index",
|
|
181
207
|
"streams",
|
|
182
208
|
"contracts",
|
|
@@ -205,11 +231,17 @@ function buildCapabilities() {
|
|
|
205
231
|
});
|
|
206
232
|
return {
|
|
207
233
|
products,
|
|
208
|
-
discoverFirst: "Call
|
|
234
|
+
discoverFirst: "Call index_discover / contracts_find to learn what exists (and which filters each surface accepts) before querying."
|
|
209
235
|
};
|
|
210
236
|
}
|
|
237
|
+
var X402_CAPABILITY = {
|
|
238
|
+
what: "Index and Streams reads (/v1/index/*, /v1/streams/*) accept x402 v2 pay-per-call (network stacks:1, sponsored transfers — the payer holds sBTC/STX/USDCx, never gas). Index grants 1,000 free reads/day/IP before any 402; a paid Streams call opens a 500-call/1h PAYMENT-SESSION.",
|
|
239
|
+
paidWrites: "POST /v1/subgraphs (x402 $2) deploys a subgraph owned by the paying wallet — live from deploy, 7-day TTL, renewable via POST /v1/subgraphs/{name}/renew ($0.50/wk); claiming the account clears the expiry.",
|
|
240
|
+
prepaid: "POST /v1/x402/deposit?usd=N loads a prepaid tab (PAYMENT-BALANCE token, $0.25 min) — calls debit it instantly, no per-call on-chain settle. GET /v1/x402/balance reads it.",
|
|
241
|
+
discovery: "GET /v1/x402/supported on the API host for scheme, assets, prices, quotas, sessions, and prepaid metadata.",
|
|
242
|
+
autopay: "Set X402_PRIVATE_KEY (a Stacks private key holding an accepted token) and this MCP server pays 402 challenges automatically (sessions replayed). Add X402_TOPUP_USD (and optionally X402_TOPUP_WHEN_BELOW) for an autonomous prepaid tab that refills itself. Receipts are logged per paid call."
|
|
243
|
+
};
|
|
211
244
|
var READ_AUTH_TIERS = {
|
|
212
|
-
datasets: "open — no API key required",
|
|
213
245
|
index: "anonymous reads allowed; free-tier API keys are rejected (Build+ required)",
|
|
214
246
|
streams: "API key required (SL_API_KEY) — keyless calls return 401",
|
|
215
247
|
subgraphs: "public subgraphs anon-readable at /v1/subgraphs/<name>/<table> ({ rows, next_cursor, tip } cursor envelope); private subgraphs need the owning account's API key (anon → 404); writes require an API key"
|
|
@@ -219,7 +251,10 @@ async function buildContext(deps = { clientProvider: getClient }) {
|
|
|
219
251
|
const orNull = (v) => v == null ? unavailable : v;
|
|
220
252
|
const snap = await deps.clientProvider().context().catch(() => null);
|
|
221
253
|
return {
|
|
222
|
-
authState: {
|
|
254
|
+
authState: {
|
|
255
|
+
apiKeySet: Boolean(process.env.SL_API_KEY),
|
|
256
|
+
x402WalletSet: Boolean(process.env.X402_PRIVATE_KEY)
|
|
257
|
+
},
|
|
223
258
|
whatExists: {
|
|
224
259
|
account: orNull(snap?.account),
|
|
225
260
|
streamsTip: orNull(snap?.streamsTip),
|
|
@@ -231,7 +266,8 @@ async function buildContext(deps = { clientProvider: getClient }) {
|
|
|
231
266
|
activeOperations: orNull(snap?.activeOperations)
|
|
232
267
|
},
|
|
233
268
|
whatYouCanDo: buildCapabilities(),
|
|
234
|
-
readAuthTiers: READ_AUTH_TIERS
|
|
269
|
+
readAuthTiers: READ_AUTH_TIERS,
|
|
270
|
+
payPerCall: X402_CAPABILITY
|
|
235
271
|
};
|
|
236
272
|
}
|
|
237
273
|
function registerResources(server) {
|
|
@@ -419,43 +455,20 @@ function registerContractTools(server, clientProvider = getClient) {
|
|
|
419
455
|
});
|
|
420
456
|
}
|
|
421
457
|
|
|
422
|
-
// src/tools/datasets.ts
|
|
423
|
-
import { z as z3 } from "zod/v4";
|
|
424
|
-
function registerDatasetTools(server, clientProvider = getClient) {
|
|
425
|
-
defineTool(server, "datasets_list", "List the Foundation Datasets catalog with freshness — the discovery endpoint for what dataset slugs exist and how current each is. Reads are public.", {}, async () => {
|
|
426
|
-
const catalog = await clientProvider().datasets.listDatasets();
|
|
427
|
-
return jsonResponse(catalog);
|
|
428
|
-
});
|
|
429
|
-
defineTool(server, "datasets_query", 'Query ANY Foundation Dataset by slug — every family returned by datasets_list is queryable, including bespoke ones (bns/resolve with {"fqn":"alice.btc"}, bns/names, bns/namespaces, network-health/summary) and any dataset added later. Accepts family ("sbtc-events") or path ("sbtc/events") slug forms. Filters are passed through as documented query params (e.g. {"sender": "SP...", "from_block": "150000"}). Returns { rows, next_cursor, tip }; single-record datasets like bns/resolve return 0-or-1 rows (empty = not found). Call datasets_list first to discover slugs and their filters.', {
|
|
430
|
-
slug: z3.string().describe("Dataset slug from datasets_list (e.g. stx-transfers)"),
|
|
431
|
-
filters: z3.record(z3.string(), z3.string()).optional().describe("Documented per-dataset query params (snake_case values)"),
|
|
432
|
-
limit: z3.number().optional().describe("Max rows for this page"),
|
|
433
|
-
cursor: z3.string().optional().describe("Opaque cursor from a prior response's next_cursor")
|
|
434
|
-
}, async ({ slug, filters, limit, cursor }) => {
|
|
435
|
-
const params = { ...filters ?? {} };
|
|
436
|
-
if (limit !== undefined)
|
|
437
|
-
params.limit = limit;
|
|
438
|
-
if (cursor !== undefined)
|
|
439
|
-
params.cursor = cursor;
|
|
440
|
-
const result = await clientProvider().datasets.get(slug, params);
|
|
441
|
-
return jsonResponse(result);
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
|
|
445
458
|
// src/tools/index.ts
|
|
446
459
|
import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
|
|
447
460
|
import {
|
|
448
461
|
INDEX_CODEGEN_TABLES,
|
|
449
462
|
generateIndexSchema
|
|
450
463
|
} from "@secondlayer/subgraphs";
|
|
451
|
-
import { z as
|
|
464
|
+
import { z as z3 } from "zod/v4";
|
|
452
465
|
var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES2;
|
|
453
466
|
var rangeFilters = {
|
|
454
|
-
contractId:
|
|
455
|
-
fromHeight:
|
|
456
|
-
toHeight:
|
|
457
|
-
cursor:
|
|
458
|
-
limit:
|
|
467
|
+
contractId: z3.string().optional().describe("Filter by contract id"),
|
|
468
|
+
fromHeight: z3.number().optional().describe("Start block height (inclusive)"),
|
|
469
|
+
toHeight: z3.number().optional().describe("End block height (inclusive)"),
|
|
470
|
+
cursor: z3.string().optional().describe("Opaque cursor from a prior response's next_cursor"),
|
|
471
|
+
limit: z3.number().optional().describe("Max rows for this page")
|
|
459
472
|
};
|
|
460
473
|
var heightFilters = {
|
|
461
474
|
fromHeight: rangeFilters.fromHeight,
|
|
@@ -467,89 +480,95 @@ var notFound = (message) => jsonResponse({ error: { type: "not_found", status: 4
|
|
|
467
480
|
function registerIndexTools(server, clientProvider = getClient) {
|
|
468
481
|
defineTool(server, "index_ft_transfers", "List decoded SIP-010 fungible-token transfers from the Index (L2 decoded layer). Anonymous reads allowed (free-tier API keys are rejected — Build+ required).", {
|
|
469
482
|
...rangeFilters,
|
|
470
|
-
sender:
|
|
471
|
-
recipient:
|
|
483
|
+
sender: z3.string().optional().describe("Filter by sender principal"),
|
|
484
|
+
recipient: z3.string().optional().describe("Filter by recipient principal")
|
|
472
485
|
}, async (params) => jsonResponse(await clientProvider().index.ftTransfers.list(params)));
|
|
473
486
|
defineTool(server, "index_nft_transfers", "List decoded SIP-009 non-fungible-token transfers from the Index. Anonymous reads allowed (free-tier keys rejected).", {
|
|
474
487
|
...rangeFilters,
|
|
475
|
-
sender:
|
|
476
|
-
recipient:
|
|
477
|
-
assetIdentifier:
|
|
488
|
+
sender: z3.string().optional().describe("Filter by sender principal"),
|
|
489
|
+
recipient: z3.string().optional().describe("Filter by recipient principal"),
|
|
490
|
+
assetIdentifier: z3.string().optional().describe("Filter by asset identifier (contract::asset)")
|
|
478
491
|
}, async (params) => jsonResponse(await clientProvider().index.nftTransfers.list(params)));
|
|
479
492
|
defineTool(server, "index_events", "List decoded chain events from the Index by event type. Use this for event types without a dedicated tool (stx_*, ft_mint/burn, nft_mint/burn, print), and for trait-scoped queries: set `trait` (e.g. sip-010) to match all contracts conforming to a standard — pair with contracts_find to discover traits. For ft/nft transfers without a trait prefer index_ft_transfers / index_nft_transfers.", {
|
|
480
|
-
eventType:
|
|
493
|
+
eventType: z3.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
|
|
481
494
|
...rangeFilters,
|
|
482
|
-
sender:
|
|
483
|
-
recipient:
|
|
484
|
-
assetIdentifier:
|
|
485
|
-
trait:
|
|
495
|
+
sender: z3.string().optional().describe("Filter by sender principal"),
|
|
496
|
+
recipient: z3.string().optional().describe("Filter by recipient principal"),
|
|
497
|
+
assetIdentifier: z3.string().optional().describe("Filter by asset identifier where applicable"),
|
|
498
|
+
trait: z3.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId; contract-keyed event types only.")
|
|
486
499
|
}, async (params) => jsonResponse(await clientProvider().index.events.list(params)));
|
|
487
500
|
defineTool(server, "index_contract_calls", "List decoded contract calls from the Index (function name, args, result). Set `trait` (e.g. sip-010) to match calls to all contracts conforming to a standard. Note: contract-call cursors are a SEPARATE keyspace from event cursors — they are not interchangeable.", {
|
|
488
501
|
...rangeFilters,
|
|
489
|
-
functionName:
|
|
490
|
-
sender:
|
|
491
|
-
trait:
|
|
502
|
+
functionName: z3.string().optional().describe("Filter by called function name"),
|
|
503
|
+
sender: z3.string().optional().describe("Filter by caller principal"),
|
|
504
|
+
trait: z3.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
|
|
492
505
|
}, async (params) => jsonResponse(await clientProvider().index.contractCalls.list(params)));
|
|
493
506
|
defineTool(server, "index_canonical", "List the canonical Stacks block sequence from the Index (height + hash). Anonymous reads allowed (free-tier keys rejected).", { ...heightFilters }, async (params) => jsonResponse(await clientProvider().index.canonical.list(params)));
|
|
494
507
|
defineTool(server, "index_blocks", "List decoded blocks from the Index. Anonymous reads allowed (free-tier keys rejected).", { ...heightFilters }, async (params) => jsonResponse(await clientProvider().index.blocks.list(params)));
|
|
495
508
|
defineTool(server, "index_block", "Get a single block from the Index by height or block hash. Returns not_found if unknown.", {
|
|
496
|
-
ref:
|
|
509
|
+
ref: z3.string().describe("Block height (digits) or block hash (0x… string)")
|
|
497
510
|
}, async ({ ref }) => {
|
|
498
511
|
const block = await clientProvider().index.blocks.get(/^\d+$/.test(ref) ? Number(ref) : ref);
|
|
499
512
|
return block ? jsonResponse(block) : notFound(`No block for ref ${ref}`);
|
|
500
513
|
});
|
|
501
514
|
defineTool(server, "index_transactions", "List decoded transactions from the Index. Filter by type, sender, or contract. Anonymous reads allowed (free-tier keys rejected).", {
|
|
502
515
|
...rangeFilters,
|
|
503
|
-
type:
|
|
504
|
-
sender:
|
|
516
|
+
type: z3.string().optional().describe("Filter by transaction type"),
|
|
517
|
+
sender: z3.string().optional().describe("Filter by sender principal")
|
|
505
518
|
}, async (params) => jsonResponse(await clientProvider().index.transactions.list(params)));
|
|
506
|
-
defineTool(server, "index_transaction", "Get a single transaction from the Index by tx_id. Returns not_found if unknown.", { txId:
|
|
519
|
+
defineTool(server, "index_transaction", "Get a single transaction from the Index by tx_id. Returns not_found if unknown.", { txId: z3.string().describe("Transaction id (0x… hash)") }, async ({ txId }) => {
|
|
507
520
|
const tx = await clientProvider().index.transactions.get(txId);
|
|
508
521
|
return tx ? jsonResponse(tx) : notFound(`No transaction for ${txId}`);
|
|
509
522
|
});
|
|
510
|
-
defineTool(server, "index_transaction_proof", "Fetch a transaction's inclusion proof (raw tx + signed Nakamoto block header + merkle path) so it can be verified trustlessly client-side with the SDK's verifyTransactionProof. Returns not_found if the tx/block is unknown; a 503 means the proof can't be assembled on this deployment right now (PROOF_TX_SET_INCOMPLETE, or PROOF_NODE_UNAVAILABLE when the stacks-node header source isn't configured).", { txId:
|
|
523
|
+
defineTool(server, "index_transaction_proof", "Fetch a transaction's inclusion proof (raw tx + signed Nakamoto block header + merkle path) so it can be verified trustlessly client-side with the SDK's verifyTransactionProof. Returns not_found if the tx/block is unknown; a 503 means the proof can't be assembled on this deployment right now (PROOF_TX_SET_INCOMPLETE, or PROOF_NODE_UNAVAILABLE when the stacks-node header source isn't configured).", { txId: z3.string().describe("Transaction id (0x… hash)") }, async ({ txId }) => {
|
|
511
524
|
const proof = await clientProvider().index.transactions.getProof(txId);
|
|
512
525
|
return proof ? jsonResponse(proof) : notFound(`No proof available for ${txId}`);
|
|
513
526
|
});
|
|
514
527
|
defineTool(server, "index_stacking", "List decoded PoX-4 stacking actions from the Index (stack-stx, delegate-stx, etc.). Anonymous reads allowed (free-tier keys rejected).", {
|
|
515
528
|
...heightFilters,
|
|
516
|
-
functionName:
|
|
517
|
-
stacker:
|
|
518
|
-
caller:
|
|
529
|
+
functionName: z3.string().optional().describe("Filter by PoX function name"),
|
|
530
|
+
stacker: z3.string().optional().describe("Filter by stacker principal"),
|
|
531
|
+
caller: z3.string().optional().describe("Filter by caller principal")
|
|
519
532
|
}, async (params) => jsonResponse(await clientProvider().index.stacking.list(params)));
|
|
520
533
|
defineTool(server, "index_mempool", "List pending (unconfirmed) transactions from the Index mempool. Sequence-cursor paginated (no height range). Anonymous reads allowed (free-tier keys rejected).", {
|
|
521
|
-
sender:
|
|
522
|
-
type:
|
|
523
|
-
contractId:
|
|
524
|
-
cursor:
|
|
525
|
-
limit:
|
|
534
|
+
sender: z3.string().optional().describe("Filter by sender principal"),
|
|
535
|
+
type: z3.string().optional().describe("Filter by transaction type"),
|
|
536
|
+
contractId: z3.string().optional().describe("Filter to pending calls to a single contract"),
|
|
537
|
+
cursor: z3.string().optional().describe("Opaque cursor from a prior response's next_cursor"),
|
|
538
|
+
limit: z3.number().optional().describe("Max rows for this page")
|
|
526
539
|
}, async (params) => jsonResponse(await clientProvider().index.mempool.list(params)));
|
|
527
|
-
defineTool(server, "index_mempool_tx", "Get a single pending transaction from the Index mempool by tx_id. Returns not_found once it is mined or dropped.", { txId:
|
|
540
|
+
defineTool(server, "index_mempool_tx", "Get a single pending transaction from the Index mempool by tx_id. Returns not_found once it is mined or dropped.", { txId: z3.string().describe("Transaction id (0x… hash)") }, async ({ txId }) => {
|
|
528
541
|
const tx = await clientProvider().index.mempool.get(txId);
|
|
529
542
|
return tx ? jsonResponse(tx) : notFound(`No pending tx for ${txId}`);
|
|
530
543
|
});
|
|
531
544
|
defineTool(server, "index_discover", "Discover the Index vocabulary: every event type and its columns, allowed/equality filters, and required-non-null fields (and which types accept `trait`). Read this before building Index queries instead of guessing filters. Anonymous read.", {}, async () => jsonResponse(await clientProvider().index.discover()));
|
|
532
545
|
defineTool(server, "index_usage", "Your own Index consumption (decoded events today + this month) and tier limits. Requires a Build+ API key (anonymous reads can't report usage).", {}, async () => jsonResponse(await clientProvider().index.usage()));
|
|
533
546
|
defineTool(server, "index_codegen", `Generate a typed schema (Prisma, Kysely, Drizzle, or JSON-Schema) for the public Index domain tables so they can be mirrored into a BYO database with full types. Returns the schema as text. Tables: ${INDEX_CODEGEN_TABLES.join(", ")}.`, {
|
|
534
|
-
target:
|
|
535
|
-
tables:
|
|
536
|
-
schemaName:
|
|
547
|
+
target: z3.enum(["prisma", "kysely", "drizzle", "json-schema"]).optional().describe("Output target (default kysely)"),
|
|
548
|
+
tables: z3.array(z3.string()).optional().describe("Subset of Index tables (default: all)"),
|
|
549
|
+
schemaName: z3.string().optional().describe("Postgres schema to qualify table names with")
|
|
537
550
|
}, async ({ target = "kysely", tables, schemaName }) => {
|
|
538
551
|
const out = generateIndexSchema(target, { tables, schemaName });
|
|
539
552
|
return { content: [{ type: "text", text: out }] };
|
|
540
553
|
});
|
|
554
|
+
defineTool(server, "batch_query", "Run up to 10 public /v1 reads in one round trip (POST /v1/batch). Each item keeps its own auth/quota/pay-per-call semantics; results return in order with per-item status. Paths must start with /v1/index, /v1/subgraphs, /v1/streams, or /v1/contracts.", {
|
|
555
|
+
requests: z3.array(z3.object({
|
|
556
|
+
path: z3.string().describe("Public /v1 read path"),
|
|
557
|
+
params: z3.record(z3.string(), z3.string()).optional()
|
|
558
|
+
})).min(1).max(10).describe("Read descriptors, executed concurrently")
|
|
559
|
+
}, async (params) => jsonResponse(await clientProvider().batch(params.requests)));
|
|
541
560
|
}
|
|
542
561
|
|
|
543
562
|
// src/tools/projects.ts
|
|
544
|
-
import { z as
|
|
563
|
+
import { z as z4 } from "zod/v4";
|
|
545
564
|
function registerProjectTools(server, clientProvider = getClient) {
|
|
546
565
|
defineTool(server, "project_list", "List the account's projects. Requires an account-level (owner) API key.", {}, async () => jsonResponse(await clientProvider().projects.list()));
|
|
547
|
-
defineTool(server, "project_get", "Get a single project by slug.", { slug:
|
|
566
|
+
defineTool(server, "project_get", "Get a single project by slug.", { slug: z4.string().describe("Project slug") }, async ({ slug }) => jsonResponse(await clientProvider().projects.get(slug)));
|
|
548
567
|
defineTool(server, "project_create", "Create a project. The slug is derived from the name when omitted. Requires an account-level (owner) API key.", {
|
|
549
|
-
name:
|
|
550
|
-
slug:
|
|
551
|
-
network:
|
|
552
|
-
nodeRpc:
|
|
568
|
+
name: z4.string().describe("Project name"),
|
|
569
|
+
slug: z4.string().optional().describe("URL slug (auto-derived from name if omitted)"),
|
|
570
|
+
network: z4.string().optional().describe("Network (e.g. mainnet, testnet)"),
|
|
571
|
+
nodeRpc: z4.string().optional().describe("Custom node RPC URL")
|
|
553
572
|
}, async ({ name, slug, network, nodeRpc }) => jsonResponse(await clientProvider().projects.create({
|
|
554
573
|
name,
|
|
555
574
|
slug,
|
|
@@ -557,12 +576,12 @@ function registerProjectTools(server, clientProvider = getClient) {
|
|
|
557
576
|
nodeRpc
|
|
558
577
|
})));
|
|
559
578
|
defineTool(server, "project_update", "Update a project. `slug` selects the project; `newSlug` renames it. Requires an account-level (owner) API key.", {
|
|
560
|
-
slug:
|
|
561
|
-
newSlug:
|
|
562
|
-
name:
|
|
563
|
-
network:
|
|
564
|
-
nodeRpc:
|
|
565
|
-
settings:
|
|
579
|
+
slug: z4.string().describe("Slug of the project to update"),
|
|
580
|
+
newSlug: z4.string().optional().describe("New slug (renames the project)"),
|
|
581
|
+
name: z4.string().optional().describe("New display name"),
|
|
582
|
+
network: z4.string().optional().describe("Network (e.g. mainnet, testnet)"),
|
|
583
|
+
nodeRpc: z4.string().optional().describe("Custom node RPC URL"),
|
|
584
|
+
settings: z4.record(z4.string(), z4.unknown()).optional().describe("Arbitrary project settings to merge")
|
|
566
585
|
}, async ({ slug, newSlug, name, network, nodeRpc, settings }) => jsonResponse(await clientProvider().projects.update(slug, {
|
|
567
586
|
...newSlug !== undefined ? { slug: newSlug } : {},
|
|
568
587
|
...name !== undefined ? { name } : {},
|
|
@@ -570,8 +589,8 @@ function registerProjectTools(server, clientProvider = getClient) {
|
|
|
570
589
|
...nodeRpc !== undefined ? { nodeRpc } : {},
|
|
571
590
|
...settings !== undefined ? { settings } : {}
|
|
572
591
|
})));
|
|
573
|
-
defineTool(server, "project_delete", "Delete a project permanently. The account's last remaining project cannot be deleted.", { slug:
|
|
574
|
-
defineTool(server, "project_team_list", "List a project's team members and pending invitations.", { slug:
|
|
592
|
+
defineTool(server, "project_delete", "Delete a project permanently. The account's last remaining project cannot be deleted.", { slug: z4.string().describe("Project slug") }, async ({ slug }) => jsonResponse(await clientProvider().projects.delete(slug)));
|
|
593
|
+
defineTool(server, "project_team_list", "List a project's team members and pending invitations.", { slug: z4.string().describe("Project slug") }, async ({ slug }) => jsonResponse(await clientProvider().projects.team(slug)));
|
|
575
594
|
}
|
|
576
595
|
|
|
577
596
|
// src/tools/scaffold.ts
|
|
@@ -580,7 +599,7 @@ import {
|
|
|
580
599
|
generateTraitSubgraph
|
|
581
600
|
} from "@secondlayer/scaffold";
|
|
582
601
|
import { TRAIT_STANDARDS as TRAIT_STANDARDS2 } from "@secondlayer/stacks/clarity";
|
|
583
|
-
import { z as
|
|
602
|
+
import { z as z5 } from "zod/v4";
|
|
584
603
|
async function fetchAbi(contractId) {
|
|
585
604
|
const contract = await getClient().contracts.get(contractId, {
|
|
586
605
|
includeAbi: true
|
|
@@ -595,24 +614,24 @@ async function fetchAbi(contractId) {
|
|
|
595
614
|
}
|
|
596
615
|
function registerScaffoldTools(server) {
|
|
597
616
|
defineTool(server, "scaffold_from_trait", "Generate a deploy-ready subgraph scaffold that indexes EVERY contract conforming to a SIP trait (no specific contract needed) — sip-009 → an nft_transfer source, sip-010/sip-013 → ft_transfer. Use scaffold_from_contract/abi instead when targeting one contract. See the secondlayer://traits resource for valid trait ids.", {
|
|
598
|
-
trait:
|
|
599
|
-
subgraphName:
|
|
617
|
+
trait: z5.enum(TRAIT_STANDARDS2).describe("SIP standard to index (sip-009 | sip-010 | sip-013)"),
|
|
618
|
+
subgraphName: z5.string().optional().describe("Override the subgraph name (defaults to <trait>-transfers)")
|
|
600
619
|
}, async ({ trait, subgraphName }) => {
|
|
601
620
|
const code = generateTraitSubgraph({ trait, name: subgraphName });
|
|
602
621
|
return { content: [{ type: "text", text: code }] };
|
|
603
622
|
});
|
|
604
623
|
defineTool(server, "scaffold_from_contract", "Generate a subgraph scaffold from a deployed Stacks contract. Fetches the ABI automatically.", {
|
|
605
|
-
contractId:
|
|
606
|
-
subgraphName:
|
|
624
|
+
contractId: z5.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
|
|
625
|
+
subgraphName: z5.string().optional().describe("Override the subgraph name (defaults to contract name)")
|
|
607
626
|
}, async ({ contractId, subgraphName }) => {
|
|
608
627
|
const { functions, maps } = await fetchAbi(contractId);
|
|
609
628
|
const code = generateSubgraphCode(contractId, functions, subgraphName, maps);
|
|
610
629
|
return { content: [{ type: "text", text: code }] };
|
|
611
630
|
});
|
|
612
631
|
defineTool(server, "scaffold_from_abi", "Generate a subgraph scaffold from a provided ABI JSON. Use when you already have the ABI.", {
|
|
613
|
-
abi:
|
|
614
|
-
contractId:
|
|
615
|
-
subgraphName:
|
|
632
|
+
abi: z5.string().describe("ABI JSON string (the full contract ABI object)"),
|
|
633
|
+
contractId: z5.string().describe("Fully qualified contract ID"),
|
|
634
|
+
subgraphName: z5.string().optional().describe("Override the subgraph name")
|
|
616
635
|
}, async ({ abi, contractId, subgraphName }) => {
|
|
617
636
|
let parsed;
|
|
618
637
|
try {
|
|
@@ -631,7 +650,7 @@ function registerScaffoldTools(server) {
|
|
|
631
650
|
// src/tools/streams.ts
|
|
632
651
|
import { AuthError } from "@secondlayer/sdk";
|
|
633
652
|
import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES3 } from "@secondlayer/shared";
|
|
634
|
-
import { z as
|
|
653
|
+
import { z as z6 } from "zod/v4";
|
|
635
654
|
var STREAMS_EVENT_TYPES = DECODED_EVENT_TYPES3;
|
|
636
655
|
async function withStreamsAuthHint(fn) {
|
|
637
656
|
try {
|
|
@@ -646,28 +665,28 @@ async function withStreamsAuthHint(fn) {
|
|
|
646
665
|
function registerStreamsTools(server, clientProvider = getClient) {
|
|
647
666
|
defineTool(server, "streams_tip", "Get the current Streams chain tip (latest processed block + lag). Streams requires an API key (SL_API_KEY).", {}, async () => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.tip())));
|
|
648
667
|
defineTool(server, "streams_events", "List raw chain events from the Streams firehose. Streams requires an API key (SL_API_KEY). Filter by event types, principals, contract, asset, or block range; page with cursor.", {
|
|
649
|
-
types:
|
|
650
|
-
notTypes:
|
|
651
|
-
contractId:
|
|
652
|
-
sender:
|
|
653
|
-
recipient:
|
|
654
|
-
assetIdentifier:
|
|
655
|
-
fromHeight:
|
|
656
|
-
toHeight:
|
|
657
|
-
cursor:
|
|
658
|
-
limit:
|
|
668
|
+
types: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
|
|
669
|
+
notTypes: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude (applied after types)"),
|
|
670
|
+
contractId: z6.string().optional().describe("Filter by contract id"),
|
|
671
|
+
sender: z6.string().optional().describe("Filter by sender principal"),
|
|
672
|
+
recipient: z6.string().optional().describe("Filter by recipient principal"),
|
|
673
|
+
assetIdentifier: z6.string().optional().describe("Filter by asset identifier"),
|
|
674
|
+
fromHeight: z6.number().optional().describe("Start block height (inclusive)"),
|
|
675
|
+
toHeight: z6.number().optional().describe("End block height (inclusive)"),
|
|
676
|
+
cursor: z6.string().optional().describe("Opaque cursor from a prior response"),
|
|
677
|
+
limit: z6.number().optional().describe("Max events for this page")
|
|
659
678
|
}, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.events.list(params))));
|
|
660
679
|
defineTool(server, "streams_consume", "Consume a bounded, reorg-aware run of Streams events from a cursor. Walks up to maxPages pages and returns the events, any reorgs observed, and a resume `cursor` to pass back as fromCursor next call — the agent-native consume/resume primitive. Streams requires an API key (SL_API_KEY).", {
|
|
661
|
-
fromCursor:
|
|
662
|
-
types:
|
|
663
|
-
notTypes:
|
|
664
|
-
contractId:
|
|
665
|
-
sender:
|
|
666
|
-
recipient:
|
|
667
|
-
assetIdentifier:
|
|
668
|
-
batchSize:
|
|
669
|
-
maxPages:
|
|
670
|
-
finalizedOnly:
|
|
680
|
+
fromCursor: z6.string().optional().describe("Resume cursor from a prior consume (omit to start at genesis)"),
|
|
681
|
+
types: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
|
|
682
|
+
notTypes: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude"),
|
|
683
|
+
contractId: z6.string().optional().describe("Filter by contract id"),
|
|
684
|
+
sender: z6.string().optional().describe("Filter by sender principal"),
|
|
685
|
+
recipient: z6.string().optional().describe("Filter by recipient principal"),
|
|
686
|
+
assetIdentifier: z6.string().optional().describe("Filter by asset identifier"),
|
|
687
|
+
batchSize: z6.number().optional().describe("Events per page (1–1000, default 100)"),
|
|
688
|
+
maxPages: z6.number().optional().describe("Max pages to walk this call (1–20, default 5)"),
|
|
689
|
+
finalizedOnly: z6.boolean().optional().describe("Only emit finalized events; skip reorg handling")
|
|
671
690
|
}, async (params) => withStreamsAuthHint(async () => {
|
|
672
691
|
const maxPages = Math.min(Math.max(params.maxPages ?? 5, 1), 20);
|
|
673
692
|
const batchSize = Math.min(Math.max(params.batchSize ?? 100, 1), 1000);
|
|
@@ -699,15 +718,15 @@ function registerStreamsTools(server, clientProvider = getClient) {
|
|
|
699
718
|
pages: result.pages
|
|
700
719
|
});
|
|
701
720
|
}));
|
|
702
|
-
defineTool(server, "streams_event_by_txid", "List all Streams events emitted by a single transaction. Streams requires an API key (SL_API_KEY).", { txId:
|
|
721
|
+
defineTool(server, "streams_event_by_txid", "List all Streams events emitted by a single transaction. Streams requires an API key (SL_API_KEY).", { txId: z6.string().describe("Transaction id (0x… hash)") }, async ({ txId }) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.events.byTxId(txId))));
|
|
703
722
|
defineTool(server, "streams_block_events", "List all Streams events in a single block, by height or block hash. Streams requires an API key (SL_API_KEY).", {
|
|
704
|
-
heightOrHash:
|
|
723
|
+
heightOrHash: z6.string().describe("Block height (digits) or block hash (0x… string)")
|
|
705
724
|
}, async ({ heightOrHash }) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.blocks.events(/^\d+$/.test(heightOrHash) ? Number(heightOrHash) : heightOrHash))));
|
|
706
725
|
defineTool(server, "streams_reorgs", "List chain reorgs observed by Streams since a cursor. Streams requires an API key (SL_API_KEY).", {
|
|
707
|
-
since:
|
|
708
|
-
limit:
|
|
726
|
+
since: z6.string().describe("Cursor to list reorgs since (block:index or ISO timestamp)"),
|
|
727
|
+
limit: z6.number().optional().describe("Max reorgs to return")
|
|
709
728
|
}, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.reorgs.list(params))));
|
|
710
|
-
defineTool(server, "streams_canonical", "Get the canonical block at a given height from Streams (height + hashes + is_canonical). Streams requires an API key (SL_API_KEY).", { height:
|
|
729
|
+
defineTool(server, "streams_canonical", "Get the canonical block at a given height from Streams (height + hashes + is_canonical). Streams requires an API key (SL_API_KEY).", { height: z6.number().describe("Block height") }, async ({ height }) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.canonical(height))));
|
|
711
730
|
defineTool(server, "streams_usage", "Your own Streams consumption (events today + this month) and tier limits (rate limit, retention). Streams requires an API key (SL_API_KEY).", {}, async () => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.usage())));
|
|
712
731
|
defineTool(server, "streams_dumps", "List the Streams bulk parquet dumps manifest — coverage range, latest_finalized_cursor, and per-file metadata (block range, row count, size, sha256, signed URL). This is the cold backfill path for downloading all raw data; fetch the file URLs directly (e.g. with DuckDB). Requires the dumps base URL to be configured (SL_STREAMS_DUMPS_URL).", {}, async () => jsonResponse(await clientProvider().streams.dumps.list()));
|
|
713
732
|
}
|
|
@@ -720,7 +739,7 @@ import {
|
|
|
720
739
|
generateKyselySchema,
|
|
721
740
|
generatePrismaSchema
|
|
722
741
|
} from "@secondlayer/subgraphs";
|
|
723
|
-
import { z as
|
|
742
|
+
import { z as z7 } from "zod/v4";
|
|
724
743
|
function registerSubgraphTools(server, clientProvider = getClient) {
|
|
725
744
|
defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only, including visibility — public subgraphs are anon-readable at /v1/subgraphs/<name>.", {}, async () => {
|
|
726
745
|
const { data } = await clientProvider().subgraphs.list();
|
|
@@ -733,16 +752,16 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
733
752
|
]
|
|
734
753
|
};
|
|
735
754
|
});
|
|
736
|
-
defineTool(server, "subgraphs_get", "Get full details of a subgraph including schema, health, and table columns.", { name:
|
|
755
|
+
defineTool(server, "subgraphs_get", "Get full details of a subgraph including schema, health, and table columns.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
|
|
737
756
|
const detail = await clientProvider().subgraphs.get(name);
|
|
738
757
|
return {
|
|
739
758
|
content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
|
|
740
759
|
};
|
|
741
760
|
});
|
|
742
761
|
defineTool(server, "subgraphs_spec", "Get generated API documentation for a subgraph. Defaults to compact agent schema; supports OpenAPI JSON and Markdown.", {
|
|
743
|
-
name:
|
|
744
|
-
format:
|
|
745
|
-
serverUrl:
|
|
762
|
+
name: z7.string().describe("Subgraph name"),
|
|
763
|
+
format: z7.enum(["agent", "openapi", "markdown"]).optional().describe("Spec format to return. Defaults to agent."),
|
|
764
|
+
serverUrl: z7.string().optional().describe("Override the server URL embedded in generated docs.")
|
|
746
765
|
}, async ({ name, format = "agent", serverUrl }) => {
|
|
747
766
|
const options = serverUrl ? { serverUrl } : undefined;
|
|
748
767
|
const spec = format === "openapi" ? await clientProvider().subgraphs.openapi(name, options) : format === "markdown" ? await clientProvider().subgraphs.markdown(name, options) : await clientProvider().subgraphs.schema(name, options);
|
|
@@ -756,15 +775,15 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
756
775
|
};
|
|
757
776
|
});
|
|
758
777
|
defineTool(server, "subgraphs_query", 'Query rows from a subgraph table (max 200 rows). Filters support operators: "amount.gte": "1000", "sender.neq": "SP...", "name.like": "%token%". Available operators: eq, neq, gt, gte, lt, lte, like. To TAIL new rows (no streaming over MCP): sort=_id, order=desc for the latest, then poll forward with the filter {"_id.gt": "<last _id seen>"}, order=asc. Fetch one row by id with {"_id": "<id>"}. Public subgraphs are also keyless over HTTP at GET /v1/subgraphs/<name>/<table> — { rows, next_cursor, tip } envelope, resume with ?cursor=<next_cursor> + _order=asc|desc (no _offset/_sort on /v1); hand that URL to third parties.', {
|
|
759
|
-
name:
|
|
760
|
-
table:
|
|
761
|
-
filters:
|
|
762
|
-
sort:
|
|
763
|
-
order:
|
|
764
|
-
limit:
|
|
765
|
-
offset:
|
|
766
|
-
fields:
|
|
767
|
-
count:
|
|
778
|
+
name: z7.string().describe("Subgraph name"),
|
|
779
|
+
table: z7.string().describe("Table name"),
|
|
780
|
+
filters: z7.record(z7.string(), z7.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
|
|
781
|
+
sort: z7.string().optional().describe("Column to sort by"),
|
|
782
|
+
order: z7.enum(["asc", "desc"]).optional().describe("Sort order"),
|
|
783
|
+
limit: z7.number().max(200).optional().describe("Max rows (default 50, max 200)"),
|
|
784
|
+
offset: z7.number().optional().describe("Offset for pagination"),
|
|
785
|
+
fields: z7.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
|
|
786
|
+
count: z7.boolean().optional().describe("If true, return row count instead of rows")
|
|
768
787
|
}, async ({
|
|
769
788
|
name,
|
|
770
789
|
table,
|
|
@@ -797,14 +816,14 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
797
816
|
};
|
|
798
817
|
});
|
|
799
818
|
defineTool(server, "subgraphs_aggregate", `Compute scalar aggregates over a subgraph table's filtered rows. Supports count, countDistinct, and sum/min/max (numeric columns only — uint/int plus the system _block_height). Filters use the same grammar as subgraphs_query (e.g. {"amount.gte": "1000"}). sum/min/max come back as lossless strings; counts as numbers. With no aggregate requested, returns the row count.`, {
|
|
800
|
-
name:
|
|
801
|
-
table:
|
|
802
|
-
filters:
|
|
803
|
-
count:
|
|
804
|
-
countDistinct:
|
|
805
|
-
sum:
|
|
806
|
-
min:
|
|
807
|
-
max:
|
|
819
|
+
name: z7.string().describe("Subgraph name"),
|
|
820
|
+
table: z7.string().describe("Table name"),
|
|
821
|
+
filters: z7.record(z7.string(), z7.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
|
|
822
|
+
count: z7.boolean().optional().describe("Include COUNT(*) of matching rows"),
|
|
823
|
+
countDistinct: z7.array(z7.string()).optional().describe("Columns to count distinct values of"),
|
|
824
|
+
sum: z7.array(z7.string()).optional().describe("Numeric columns to sum (lossless string result)"),
|
|
825
|
+
min: z7.array(z7.string()).optional().describe("Numeric columns to take the minimum of"),
|
|
826
|
+
max: z7.array(z7.string()).optional().describe("Numeric columns to take the maximum of")
|
|
808
827
|
}, async ({ name, table, filters, count, countDistinct, sum, min, max }) => {
|
|
809
828
|
const result = await clientProvider().subgraphs.queryTableAggregate(name, table, { filters, count, countDistinct, sum, min, max });
|
|
810
829
|
return {
|
|
@@ -812,9 +831,9 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
812
831
|
};
|
|
813
832
|
});
|
|
814
833
|
defineTool(server, "subgraphs_reindex", "Reindex a subgraph from a specific block range. Returns an operationId — poll subgraphs_operation to track progress to completion.", {
|
|
815
|
-
name:
|
|
816
|
-
fromBlock:
|
|
817
|
-
toBlock:
|
|
834
|
+
name: z7.string().describe("Subgraph name"),
|
|
835
|
+
fromBlock: z7.number().optional().describe("Start block (defaults to beginning)"),
|
|
836
|
+
toBlock: z7.number().optional().describe("End block (defaults to latest)")
|
|
818
837
|
}, async ({ name, fromBlock, toBlock }) => {
|
|
819
838
|
const result = await clientProvider().subgraphs.reindex(name, {
|
|
820
839
|
fromBlock,
|
|
@@ -825,8 +844,8 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
825
844
|
};
|
|
826
845
|
});
|
|
827
846
|
defineTool(server, "subgraphs_operation", "Check reindex/backfill progress. With operationId, returns that operation's status (poll until status is completed/failed/cancelled); without it, lists recent operations for the subgraph.", {
|
|
828
|
-
name:
|
|
829
|
-
operationId:
|
|
847
|
+
name: z7.string().describe("Subgraph name"),
|
|
848
|
+
operationId: z7.string().optional().describe("Operation id from reindex/backfill/stop; omit to list recent operations")
|
|
830
849
|
}, async ({ name, operationId }) => {
|
|
831
850
|
const result = operationId ? await clientProvider().subgraphs.getOperation(name, operationId) : await clientProvider().subgraphs.operations(name);
|
|
832
851
|
return {
|
|
@@ -834,9 +853,9 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
834
853
|
};
|
|
835
854
|
});
|
|
836
855
|
defineTool(server, "subgraphs_backfill", "Backfill a subgraph over a block range. Non-destructive forward fill (does not drop existing data) — unlike subgraphs_reindex, and the only data-fill path for BYO subgraphs (reindex is blocked there). Both blocks required. Returns an operationId — poll subgraphs_operation to track progress.", {
|
|
837
|
-
name:
|
|
838
|
-
fromBlock:
|
|
839
|
-
toBlock:
|
|
856
|
+
name: z7.string().describe("Subgraph name"),
|
|
857
|
+
fromBlock: z7.number().int().nonnegative().describe("Start block (inclusive)"),
|
|
858
|
+
toBlock: z7.number().int().nonnegative().describe("End block (inclusive)")
|
|
840
859
|
}, async ({ name, fromBlock, toBlock }) => {
|
|
841
860
|
const result = await clientProvider().subgraphs.backfill(name, {
|
|
842
861
|
fromBlock,
|
|
@@ -846,17 +865,17 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
846
865
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
847
866
|
};
|
|
848
867
|
});
|
|
849
|
-
defineTool(server, "subgraphs_stop", "Cancel an in-flight reindex or backfill operation for a subgraph. Returns the stop request status; poll subgraphs_operation to confirm it reaches a terminal state.", { name:
|
|
868
|
+
defineTool(server, "subgraphs_stop", "Cancel an in-flight reindex or backfill operation for a subgraph. Returns the stop request status; poll subgraphs_operation to confirm it reaches a terminal state.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
|
|
850
869
|
const result = await clientProvider().subgraphs.stop(name);
|
|
851
870
|
return {
|
|
852
871
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
853
872
|
};
|
|
854
873
|
});
|
|
855
874
|
defineTool(server, "subgraphs_gaps", "List indexing gaps (missing block ranges) for a subgraph. Each gap reports start/end/size, reason, and detected/resolved timestamps. Feed an unresolved gap's range into subgraphs_backfill to fill it. Defaults to unresolved gaps.", {
|
|
856
|
-
name:
|
|
857
|
-
limit:
|
|
858
|
-
offset:
|
|
859
|
-
resolved:
|
|
875
|
+
name: z7.string().describe("Subgraph name"),
|
|
876
|
+
limit: z7.number().int().positive().optional().describe("Max gaps to return"),
|
|
877
|
+
offset: z7.number().int().nonnegative().optional().describe("Pagination offset"),
|
|
878
|
+
resolved: z7.boolean().optional().describe("Filter by resolved state (omit for unresolved only)")
|
|
860
879
|
}, async ({ name, limit, offset, resolved }) => {
|
|
861
880
|
const result = await clientProvider().subgraphs.gaps(name, {
|
|
862
881
|
limit,
|
|
@@ -867,16 +886,16 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
867
886
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
868
887
|
};
|
|
869
888
|
});
|
|
870
|
-
defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name:
|
|
889
|
+
defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
|
|
871
890
|
const result = await clientProvider().subgraphs.delete(name);
|
|
872
891
|
return { content: [{ type: "text", text: result.message }] };
|
|
873
892
|
});
|
|
874
893
|
defineTool(server, "subgraphs_deploy", "Deploy a subgraph from TypeScript code. Pass the full defineSubgraph() source — it will be bundled, validated, and deployed. Optional startBlock overrides the source definition for this deploy. Set dryRun to validate and preview the schema/DDL without writing anything. Set databaseUrl to deploy to your own Postgres (BYO data plane) — the server verifies the connection first; with dryRun it returns the DDL + grant script. A breaking BYO schema change is refused and returns a migration plan (drop + rebuild DDL) instead of deploying. Visibility defaults: managed deploys are public (anon-readable at /v1/subgraphs/<name>, name claimed in the global public namespace), BYO deploys are private. Call `subgraphs_reindex` separately if you need a forced reindex.", {
|
|
875
|
-
code:
|
|
876
|
-
startBlock:
|
|
877
|
-
databaseUrl:
|
|
878
|
-
dryRun:
|
|
879
|
-
visibility:
|
|
894
|
+
code: z7.string().describe("TypeScript source code containing a defineSubgraph() call"),
|
|
895
|
+
startBlock: z7.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy"),
|
|
896
|
+
databaseUrl: z7.string().optional().describe("BYO data plane: Postgres connection string to host the subgraph's schema and rows in your own database"),
|
|
897
|
+
dryRun: z7.boolean().optional().describe("Validate and preview the deploy (schema/DDL, BYO connection) without writing changes"),
|
|
898
|
+
visibility: z7.enum(["public", "private"]).optional().describe("Read visibility: public = anon /v1 reads + global name claim; private = owning account's key only. Defaults: managed → public, BYO → private.")
|
|
880
899
|
}, async ({ code, startBlock, databaseUrl, dryRun, visibility }) => {
|
|
881
900
|
const bundled = await bundleSubgraphCode(code);
|
|
882
901
|
try {
|
|
@@ -910,29 +929,29 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
910
929
|
throw err;
|
|
911
930
|
}
|
|
912
931
|
});
|
|
913
|
-
defineTool(server, "subgraphs_publish", "Make a subgraph publicly readable at /v1/subgraphs/<name> — anyone (or any agent) can read it without a key. Claims the name in the global public namespace; fails with PUBLIC_NAME_TAKEN if another account holds it.", { name:
|
|
932
|
+
defineTool(server, "subgraphs_publish", "Make a subgraph publicly readable at /v1/subgraphs/<name> — anyone (or any agent) can read it without a key. Claims the name in the global public namespace; fails with PUBLIC_NAME_TAKEN if another account holds it.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
|
|
914
933
|
const result = await clientProvider().subgraphs.publish(name);
|
|
915
934
|
return {
|
|
916
935
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
917
936
|
};
|
|
918
937
|
});
|
|
919
|
-
defineTool(server, "subgraphs_unpublish", "Make a subgraph private again — /v1 reads then require the owning account's bearer key, and the global public name claim is released.", { name:
|
|
938
|
+
defineTool(server, "subgraphs_unpublish", "Make a subgraph private again — /v1 reads then require the owning account's bearer key, and the global public name claim is released.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
|
|
920
939
|
const result = await clientProvider().subgraphs.unpublish(name);
|
|
921
940
|
return {
|
|
922
941
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
923
942
|
};
|
|
924
943
|
});
|
|
925
|
-
defineTool(server, "subgraphs_read_source", "Fetch the deployed TypeScript source of a subgraph (plus its stored version). Returns a readOnly payload for subgraphs deployed before source capture — in that case the caller should redeploy via CLI before editing.", { name:
|
|
944
|
+
defineTool(server, "subgraphs_read_source", "Fetch the deployed TypeScript source of a subgraph (plus its stored version). Returns a readOnly payload for subgraphs deployed before source capture — in that case the caller should redeploy via CLI before editing.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
|
|
926
945
|
const source = await clientProvider().subgraphs.getSource(name);
|
|
927
946
|
return {
|
|
928
947
|
content: [{ type: "text", text: JSON.stringify(source, null, 2) }]
|
|
929
948
|
};
|
|
930
949
|
});
|
|
931
950
|
defineTool(server, "subgraphs_codegen", "Generate a typed ORM schema (Prisma, Drizzle, or Kysely) for a subgraph's tables so they can be queried from a BYO database. Pass either `code` (defineSubgraph source) or `name` (a deployed subgraph — its captured source is used). Returns the schema as text.", {
|
|
932
|
-
code:
|
|
933
|
-
name:
|
|
934
|
-
target:
|
|
935
|
-
schemaName:
|
|
951
|
+
code: z7.string().optional().describe("defineSubgraph() source (mutually exclusive with name)"),
|
|
952
|
+
name: z7.string().optional().describe("Deployed subgraph name (mutually exclusive with code)"),
|
|
953
|
+
target: z7.enum(["prisma", "drizzle", "kysely"]).optional().describe("ORM target (default prisma)"),
|
|
954
|
+
schemaName: z7.string().optional().describe("Postgres schema name (defaults to subgraph_<name>)")
|
|
936
955
|
}, async ({ code, name, target = "prisma", schemaName }) => {
|
|
937
956
|
if (code && name || !code && !name) {
|
|
938
957
|
throw new Error("Provide exactly one of `code` or `name`.");
|
|
@@ -961,7 +980,7 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
961
980
|
|
|
962
981
|
// src/tools/subscriptions.ts
|
|
963
982
|
import { CHAIN_TRIGGER_TYPES } from "@secondlayer/shared";
|
|
964
|
-
import { z as
|
|
983
|
+
import { z as z8 } from "zod/v4";
|
|
965
984
|
function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
966
985
|
defineTool(server, "subscriptions_list", "List all subscriptions for the current account. Returns summary fields (no secrets).", {}, async () => {
|
|
967
986
|
const { data } = await clientProvider().subscriptions.list();
|
|
@@ -969,34 +988,34 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
|
969
988
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
970
989
|
};
|
|
971
990
|
});
|
|
972
|
-
defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id:
|
|
991
|
+
defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id: z8.string().describe("Subscription id") }, async ({ id }) => {
|
|
973
992
|
const detail = await clientProvider().subscriptions.get(id);
|
|
974
993
|
return {
|
|
975
994
|
content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
|
|
976
995
|
};
|
|
977
996
|
});
|
|
978
997
|
defineTool(server, "subscriptions_create", "Create a subscription. Two kinds (mutually exclusive): a SUBGRAPH subscription fires on a subgraph table's rows (set subgraphName + tableName + optional filter); a CHAIN subscription fires on raw chain events with no subgraph (set triggers). Returns `signingSecret` ONCE — forward it to the user so they can wire it into their receiver.", {
|
|
979
|
-
name:
|
|
980
|
-
subgraphName:
|
|
981
|
-
tableName:
|
|
982
|
-
triggers:
|
|
983
|
-
type:
|
|
984
|
-
contractId:
|
|
985
|
-
functionName:
|
|
986
|
-
caller:
|
|
987
|
-
sender:
|
|
988
|
-
recipient:
|
|
989
|
-
assetIdentifier:
|
|
990
|
-
deployer:
|
|
991
|
-
contractName:
|
|
992
|
-
topic:
|
|
993
|
-
lockedAddress:
|
|
994
|
-
trait:
|
|
995
|
-
minAmount:
|
|
996
|
-
maxAmount:
|
|
998
|
+
name: z8.string().describe("Human-readable name, unique per account"),
|
|
999
|
+
subgraphName: z8.string().optional().describe("Subgraph to subscribe to (subgraph subscription)"),
|
|
1000
|
+
tableName: z8.string().optional().describe("Table within the subgraph (subgraph subscription)"),
|
|
1001
|
+
triggers: z8.array(z8.object({
|
|
1002
|
+
type: z8.enum(CHAIN_TRIGGER_TYPES),
|
|
1003
|
+
contractId: z8.string().optional(),
|
|
1004
|
+
functionName: z8.string().optional(),
|
|
1005
|
+
caller: z8.string().optional(),
|
|
1006
|
+
sender: z8.string().optional(),
|
|
1007
|
+
recipient: z8.string().optional(),
|
|
1008
|
+
assetIdentifier: z8.string().optional(),
|
|
1009
|
+
deployer: z8.string().optional(),
|
|
1010
|
+
contractName: z8.string().optional(),
|
|
1011
|
+
topic: z8.string().optional(),
|
|
1012
|
+
lockedAddress: z8.string().optional(),
|
|
1013
|
+
trait: z8.string().optional(),
|
|
1014
|
+
minAmount: z8.union([z8.string(), z8.number()]).optional(),
|
|
1015
|
+
maxAmount: z8.union([z8.string(), z8.number()]).optional()
|
|
997
1016
|
})).optional().describe("Chain triggers (chain subscription) — provide INSTEAD of subgraphName/tableName. Each targets a raw chain event/tx; string fields accept `*` wildcards, `trait` scopes to a SIP/trait. Per-type accepted fields: see the secondlayer://chain-triggers resource. Forward-looking: starts at chain tip, no backfill."),
|
|
998
|
-
url:
|
|
999
|
-
format:
|
|
1017
|
+
url: z8.string().describe("Webhook URL"),
|
|
1018
|
+
format: z8.enum([
|
|
1000
1019
|
"standard-webhooks",
|
|
1001
1020
|
"inngest",
|
|
1002
1021
|
"trigger",
|
|
@@ -1004,9 +1023,9 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
|
1004
1023
|
"cloudevents",
|
|
1005
1024
|
"raw"
|
|
1006
1025
|
]).optional().describe("Wire format (default standard-webhooks)"),
|
|
1007
|
-
runtime:
|
|
1008
|
-
filter:
|
|
1009
|
-
authConfig:
|
|
1026
|
+
runtime: z8.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
|
|
1027
|
+
filter: z8.record(z8.string(), z8.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}'),
|
|
1028
|
+
authConfig: z8.record(z8.string(), z8.unknown()).optional().describe('Receiver auth sent with each delivery, e.g. {"type": "bearer", "token": "..."}')
|
|
1010
1029
|
}, async (input) => {
|
|
1011
1030
|
const res = await clientProvider().subscriptions.create(input);
|
|
1012
1031
|
return {
|
|
@@ -1014,12 +1033,12 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
|
1014
1033
|
};
|
|
1015
1034
|
});
|
|
1016
1035
|
defineTool(server, "subscriptions_update", "Patch a subscription (name, url, filter, authConfig, format, runtime, retry, timeout, concurrency).", {
|
|
1017
|
-
id:
|
|
1018
|
-
name:
|
|
1019
|
-
url:
|
|
1020
|
-
filter:
|
|
1021
|
-
authConfig:
|
|
1022
|
-
format:
|
|
1036
|
+
id: z8.string(),
|
|
1037
|
+
name: z8.string().optional().describe("Rename the subscription"),
|
|
1038
|
+
url: z8.string().optional(),
|
|
1039
|
+
filter: z8.record(z8.string(), z8.unknown()).optional(),
|
|
1040
|
+
authConfig: z8.record(z8.string(), z8.unknown()).optional().describe("Receiver auth sent with each delivery (bearer/etc.)"),
|
|
1041
|
+
format: z8.enum([
|
|
1023
1042
|
"standard-webhooks",
|
|
1024
1043
|
"inngest",
|
|
1025
1044
|
"trigger",
|
|
@@ -1027,51 +1046,51 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
|
1027
1046
|
"cloudevents",
|
|
1028
1047
|
"raw"
|
|
1029
1048
|
]).optional(),
|
|
1030
|
-
runtime:
|
|
1031
|
-
maxRetries:
|
|
1032
|
-
timeoutMs:
|
|
1033
|
-
concurrency:
|
|
1049
|
+
runtime: z8.enum(["inngest", "trigger", "cloudflare", "node"]).nullable().optional(),
|
|
1050
|
+
maxRetries: z8.number().int().min(0).optional(),
|
|
1051
|
+
timeoutMs: z8.number().int().min(100).optional(),
|
|
1052
|
+
concurrency: z8.number().int().min(1).optional()
|
|
1034
1053
|
}, async ({ id, ...patch }) => {
|
|
1035
1054
|
const res = await clientProvider().subscriptions.update(id, patch);
|
|
1036
1055
|
return {
|
|
1037
1056
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
1038
1057
|
};
|
|
1039
1058
|
});
|
|
1040
|
-
defineTool(server, "subscriptions_pause", "Pause a subscription. Pending rows remain queued until resumed.", { id:
|
|
1059
|
+
defineTool(server, "subscriptions_pause", "Pause a subscription. Pending rows remain queued until resumed.", { id: z8.string() }, async ({ id }) => {
|
|
1041
1060
|
const res = await clientProvider().subscriptions.pause(id);
|
|
1042
1061
|
return {
|
|
1043
1062
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
1044
1063
|
};
|
|
1045
1064
|
});
|
|
1046
|
-
defineTool(server, "subscriptions_resume", "Resume a paused or circuit-open subscription and reset circuit failures.", { id:
|
|
1065
|
+
defineTool(server, "subscriptions_resume", "Resume a paused or circuit-open subscription and reset circuit failures.", { id: z8.string() }, async ({ id }) => {
|
|
1047
1066
|
const res = await clientProvider().subscriptions.resume(id);
|
|
1048
1067
|
return {
|
|
1049
1068
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
1050
1069
|
};
|
|
1051
1070
|
});
|
|
1052
|
-
defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id:
|
|
1071
|
+
defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id: z8.string() }, async ({ id }) => {
|
|
1053
1072
|
const res = await clientProvider().subscriptions.delete(id);
|
|
1054
1073
|
return {
|
|
1055
1074
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
1056
1075
|
};
|
|
1057
1076
|
});
|
|
1058
|
-
defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription signing secret. Returns the new plaintext secret once.", { id:
|
|
1077
|
+
defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription signing secret. Returns the new plaintext secret once.", { id: z8.string() }, async ({ id }) => {
|
|
1059
1078
|
const res = await clientProvider().subscriptions.rotateSecret(id);
|
|
1060
1079
|
return {
|
|
1061
1080
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
1062
1081
|
};
|
|
1063
1082
|
});
|
|
1064
|
-
defineTool(server, "subscriptions_test", "Send a one-off test webhook to a subscription's URL (built for its format, SSRF-guarded). Logged as a delivery row — check subscriptions_recent_deliveries. Returns {ok, statusCode, error, durationMs, deliveryId}.", { id:
|
|
1083
|
+
defineTool(server, "subscriptions_test", "Send a one-off test webhook to a subscription's URL (built for its format, SSRF-guarded). Logged as a delivery row — check subscriptions_recent_deliveries. Returns {ok, statusCode, error, durationMs, deliveryId}.", { id: z8.string().describe("Subscription id") }, async ({ id }) => {
|
|
1065
1084
|
const res = await clientProvider().subscriptions.test(id);
|
|
1066
1085
|
return {
|
|
1067
1086
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
1068
1087
|
};
|
|
1069
1088
|
});
|
|
1070
1089
|
defineTool(server, "subscriptions_replay", "Replay a block range for a subscription. Replays run at 10% of batch capacity — use sparingly. Pass `force` (a short idempotency suffix) to re-run a range that was already replayed.", {
|
|
1071
|
-
id:
|
|
1072
|
-
fromBlock:
|
|
1073
|
-
toBlock:
|
|
1074
|
-
force:
|
|
1090
|
+
id: z8.string(),
|
|
1091
|
+
fromBlock: z8.number().int().nonnegative(),
|
|
1092
|
+
toBlock: z8.number().int().nonnegative(),
|
|
1093
|
+
force: z8.string().optional().describe("Idempotency suffix to force a duplicate replay of the range")
|
|
1075
1094
|
}, async ({ id, fromBlock, toBlock, force }) => {
|
|
1076
1095
|
const res = await clientProvider().subscriptions.replay(id, {
|
|
1077
1096
|
fromBlock,
|
|
@@ -1082,22 +1101,22 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
|
1082
1101
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
1083
1102
|
};
|
|
1084
1103
|
});
|
|
1085
|
-
defineTool(server, "subscriptions_dead", "Return the last 100 dead-letter outbox rows for a subscription.", { id:
|
|
1104
|
+
defineTool(server, "subscriptions_dead", "Return the last 100 dead-letter outbox rows for a subscription.", { id: z8.string() }, async ({ id }) => {
|
|
1086
1105
|
const { data } = await clientProvider().subscriptions.dead(id);
|
|
1087
1106
|
return {
|
|
1088
1107
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1089
1108
|
};
|
|
1090
1109
|
});
|
|
1091
1110
|
defineTool(server, "subscriptions_requeue_dead", "Requeue one dead-letter outbox row for delivery retry.", {
|
|
1092
|
-
id:
|
|
1093
|
-
outboxId:
|
|
1111
|
+
id: z8.string(),
|
|
1112
|
+
outboxId: z8.string()
|
|
1094
1113
|
}, async ({ id, outboxId }) => {
|
|
1095
1114
|
const res = await clientProvider().subscriptions.requeueDead(id, outboxId);
|
|
1096
1115
|
return {
|
|
1097
1116
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
1098
1117
|
};
|
|
1099
1118
|
});
|
|
1100
|
-
defineTool(server, "subscriptions_recent_deliveries", "Return the last 100 delivery attempts (attempt #, status code, duration, truncated response).", { id:
|
|
1119
|
+
defineTool(server, "subscriptions_recent_deliveries", "Return the last 100 delivery attempts (attempt #, status code, duration, truncated response).", { id: z8.string() }, async ({ id }) => {
|
|
1101
1120
|
const { data } = await clientProvider().subscriptions.recentDeliveries(id);
|
|
1102
1121
|
return {
|
|
1103
1122
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
@@ -1118,7 +1137,6 @@ function createServer() {
|
|
|
1118
1137
|
registerSubscriptionTools(server);
|
|
1119
1138
|
registerAccountTools(server);
|
|
1120
1139
|
registerProjectTools(server);
|
|
1121
|
-
registerDatasetTools(server);
|
|
1122
1140
|
registerIndexTools(server);
|
|
1123
1141
|
registerStreamsTools(server);
|
|
1124
1142
|
registerContractTools(server);
|
|
@@ -1129,5 +1147,5 @@ export {
|
|
|
1129
1147
|
createServer
|
|
1130
1148
|
};
|
|
1131
1149
|
|
|
1132
|
-
//# debugId=
|
|
1150
|
+
//# debugId=C349C261EC92DE8964756E2164756E21
|
|
1133
1151
|
//# sourceMappingURL=index.js.map
|