@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 CHANGED
@@ -18,21 +18,49 @@ import { TRAIT_STANDARDS } from "@secondlayer/stacks/clarity";
18
18
  import { TYPE_MAP } from "@secondlayer/subgraphs/schema";
19
19
 
20
20
  // src/lib/client.ts
21
- import { SecondLayer } from "@secondlayer/sdk";
21
+ import { SecondLayer, readX402Receipt, withX402 } from "@secondlayer/sdk";
22
+ import { privateKeyToAccount } from "@secondlayer/stacks/accounts";
22
23
  var instance = null;
23
24
  function readApiKey() {
24
25
  return process.env.SL_API_KEY;
25
26
  }
27
+ function buildFetchImpl() {
28
+ const key = process.env.X402_PRIVATE_KEY;
29
+ if (!key)
30
+ return;
31
+ const account = privateKeyToAccount(key);
32
+ const topUpUsd = Number(process.env.X402_TOPUP_USD ?? "");
33
+ const paid = withX402(fetch, {
34
+ account,
35
+ ...Number.isFinite(topUpUsd) && topUpUsd > 0 ? {
36
+ topUp: {
37
+ usd: topUpUsd,
38
+ whenBelow: Number(process.env.X402_TOPUP_WHEN_BELOW ?? "0.5")
39
+ }
40
+ } : {}
41
+ });
42
+ const wrapped = async (input, init) => {
43
+ const res = await paid(input, init);
44
+ const receipt = readX402Receipt(res);
45
+ if (receipt) {
46
+ console.error(`[x402] paid call settled tier=${receipt.state ?? "confirmed"} txid=${receipt.txid}`);
47
+ }
48
+ return res;
49
+ };
50
+ return wrapped;
51
+ }
26
52
  function getClient() {
27
53
  if (!instance) {
28
54
  const apiKey = readApiKey();
29
55
  const baseUrl = process.env.SECONDLAYER_API_URL;
30
56
  const dumpsBaseUrl = process.env.SL_STREAMS_DUMPS_URL;
57
+ const fetchImpl = buildFetchImpl();
31
58
  instance = new SecondLayer({
32
59
  ...apiKey ? { apiKey } : {},
33
60
  origin: "mcp",
34
61
  ...baseUrl ? { baseUrl } : {},
35
- ...dumpsBaseUrl ? { dumpsBaseUrl } : {}
62
+ ...dumpsBaseUrl ? { dumpsBaseUrl } : {},
63
+ ...fetchImpl ? { fetchImpl } : {}
36
64
  });
37
65
  }
38
66
  return instance;
@@ -173,7 +201,6 @@ var COLUMN_TYPES = [
173
201
  }
174
202
  ];
175
203
  var PRODUCT_BLURBS = {
176
- datasets: "public foundation datasets",
177
204
  index: "decoded L2 events, contract calls, blocks, transactions, stacking, mempool",
178
205
  streams: "raw canonical chain event firehose",
179
206
  contracts: "trait-based contract discovery",
@@ -184,7 +211,6 @@ var PRODUCT_BLURBS = {
184
211
  scaffold: "generate typed contract clients from a deployment or ABI"
185
212
  };
186
213
  var PRODUCT_ORDER = [
187
- "datasets",
188
214
  "index",
189
215
  "streams",
190
216
  "contracts",
@@ -213,11 +239,17 @@ function buildCapabilities() {
213
239
  });
214
240
  return {
215
241
  products,
216
- discoverFirst: "Call datasets_list / index_discover / contracts_find to learn what exists (and which filters each surface accepts) before querying."
242
+ discoverFirst: "Call index_discover / contracts_find to learn what exists (and which filters each surface accepts) before querying."
217
243
  };
218
244
  }
245
+ var X402_CAPABILITY = {
246
+ 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.",
247
+ 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.",
248
+ 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.",
249
+ discovery: "GET /v1/x402/supported on the API host for scheme, assets, prices, quotas, sessions, and prepaid metadata.",
250
+ 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."
251
+ };
219
252
  var READ_AUTH_TIERS = {
220
- datasets: "open — no API key required",
221
253
  index: "anonymous reads allowed; free-tier API keys are rejected (Build+ required)",
222
254
  streams: "API key required (SL_API_KEY) — keyless calls return 401",
223
255
  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"
@@ -227,7 +259,10 @@ async function buildContext(deps = { clientProvider: getClient }) {
227
259
  const orNull = (v) => v == null ? unavailable : v;
228
260
  const snap = await deps.clientProvider().context().catch(() => null);
229
261
  return {
230
- authState: { apiKeySet: Boolean(process.env.SL_API_KEY) },
262
+ authState: {
263
+ apiKeySet: Boolean(process.env.SL_API_KEY),
264
+ x402WalletSet: Boolean(process.env.X402_PRIVATE_KEY)
265
+ },
231
266
  whatExists: {
232
267
  account: orNull(snap?.account),
233
268
  streamsTip: orNull(snap?.streamsTip),
@@ -239,7 +274,8 @@ async function buildContext(deps = { clientProvider: getClient }) {
239
274
  activeOperations: orNull(snap?.activeOperations)
240
275
  },
241
276
  whatYouCanDo: buildCapabilities(),
242
- readAuthTiers: READ_AUTH_TIERS
277
+ readAuthTiers: READ_AUTH_TIERS,
278
+ payPerCall: X402_CAPABILITY
243
279
  };
244
280
  }
245
281
  function registerResources(server) {
@@ -427,43 +463,20 @@ function registerContractTools(server, clientProvider = getClient) {
427
463
  });
428
464
  }
429
465
 
430
- // src/tools/datasets.ts
431
- import { z as z3 } from "zod/v4";
432
- function registerDatasetTools(server, clientProvider = getClient) {
433
- 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 () => {
434
- const catalog = await clientProvider().datasets.listDatasets();
435
- return jsonResponse(catalog);
436
- });
437
- 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.', {
438
- slug: z3.string().describe("Dataset slug from datasets_list (e.g. stx-transfers)"),
439
- filters: z3.record(z3.string(), z3.string()).optional().describe("Documented per-dataset query params (snake_case values)"),
440
- limit: z3.number().optional().describe("Max rows for this page"),
441
- cursor: z3.string().optional().describe("Opaque cursor from a prior response's next_cursor")
442
- }, async ({ slug, filters, limit, cursor }) => {
443
- const params = { ...filters ?? {} };
444
- if (limit !== undefined)
445
- params.limit = limit;
446
- if (cursor !== undefined)
447
- params.cursor = cursor;
448
- const result = await clientProvider().datasets.get(slug, params);
449
- return jsonResponse(result);
450
- });
451
- }
452
-
453
466
  // src/tools/index.ts
454
467
  import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
455
468
  import {
456
469
  INDEX_CODEGEN_TABLES,
457
470
  generateIndexSchema
458
471
  } from "@secondlayer/subgraphs";
459
- import { z as z4 } from "zod/v4";
472
+ import { z as z3 } from "zod/v4";
460
473
  var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES2;
461
474
  var rangeFilters = {
462
- contractId: z4.string().optional().describe("Filter by contract id"),
463
- fromHeight: z4.number().optional().describe("Start block height (inclusive)"),
464
- toHeight: z4.number().optional().describe("End block height (inclusive)"),
465
- cursor: z4.string().optional().describe("Opaque cursor from a prior response's next_cursor"),
466
- limit: z4.number().optional().describe("Max rows for this page")
475
+ contractId: z3.string().optional().describe("Filter by contract id"),
476
+ fromHeight: z3.number().optional().describe("Start block height (inclusive)"),
477
+ toHeight: z3.number().optional().describe("End block height (inclusive)"),
478
+ cursor: z3.string().optional().describe("Opaque cursor from a prior response's next_cursor"),
479
+ limit: z3.number().optional().describe("Max rows for this page")
467
480
  };
468
481
  var heightFilters = {
469
482
  fromHeight: rangeFilters.fromHeight,
@@ -475,89 +488,95 @@ var notFound = (message) => jsonResponse({ error: { type: "not_found", status: 4
475
488
  function registerIndexTools(server, clientProvider = getClient) {
476
489
  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).", {
477
490
  ...rangeFilters,
478
- sender: z4.string().optional().describe("Filter by sender principal"),
479
- recipient: z4.string().optional().describe("Filter by recipient principal")
491
+ sender: z3.string().optional().describe("Filter by sender principal"),
492
+ recipient: z3.string().optional().describe("Filter by recipient principal")
480
493
  }, async (params) => jsonResponse(await clientProvider().index.ftTransfers.list(params)));
481
494
  defineTool(server, "index_nft_transfers", "List decoded SIP-009 non-fungible-token transfers from the Index. Anonymous reads allowed (free-tier keys rejected).", {
482
495
  ...rangeFilters,
483
- sender: z4.string().optional().describe("Filter by sender principal"),
484
- recipient: z4.string().optional().describe("Filter by recipient principal"),
485
- assetIdentifier: z4.string().optional().describe("Filter by asset identifier (contract::asset)")
496
+ sender: z3.string().optional().describe("Filter by sender principal"),
497
+ recipient: z3.string().optional().describe("Filter by recipient principal"),
498
+ assetIdentifier: z3.string().optional().describe("Filter by asset identifier (contract::asset)")
486
499
  }, async (params) => jsonResponse(await clientProvider().index.nftTransfers.list(params)));
487
500
  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.", {
488
- eventType: z4.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
501
+ eventType: z3.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
489
502
  ...rangeFilters,
490
- sender: z4.string().optional().describe("Filter by sender principal"),
491
- recipient: z4.string().optional().describe("Filter by recipient principal"),
492
- assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable"),
493
- trait: z4.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId; contract-keyed event types only.")
503
+ sender: z3.string().optional().describe("Filter by sender principal"),
504
+ recipient: z3.string().optional().describe("Filter by recipient principal"),
505
+ assetIdentifier: z3.string().optional().describe("Filter by asset identifier where applicable"),
506
+ 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.")
494
507
  }, async (params) => jsonResponse(await clientProvider().index.events.list(params)));
495
508
  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.", {
496
509
  ...rangeFilters,
497
- functionName: z4.string().optional().describe("Filter by called function name"),
498
- sender: z4.string().optional().describe("Filter by caller principal"),
499
- trait: z4.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
510
+ functionName: z3.string().optional().describe("Filter by called function name"),
511
+ sender: z3.string().optional().describe("Filter by caller principal"),
512
+ trait: z3.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
500
513
  }, async (params) => jsonResponse(await clientProvider().index.contractCalls.list(params)));
501
514
  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)));
502
515
  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)));
503
516
  defineTool(server, "index_block", "Get a single block from the Index by height or block hash. Returns not_found if unknown.", {
504
- ref: z4.string().describe("Block height (digits) or block hash (0x… string)")
517
+ ref: z3.string().describe("Block height (digits) or block hash (0x… string)")
505
518
  }, async ({ ref }) => {
506
519
  const block = await clientProvider().index.blocks.get(/^\d+$/.test(ref) ? Number(ref) : ref);
507
520
  return block ? jsonResponse(block) : notFound(`No block for ref ${ref}`);
508
521
  });
509
522
  defineTool(server, "index_transactions", "List decoded transactions from the Index. Filter by type, sender, or contract. Anonymous reads allowed (free-tier keys rejected).", {
510
523
  ...rangeFilters,
511
- type: z4.string().optional().describe("Filter by transaction type"),
512
- sender: z4.string().optional().describe("Filter by sender principal")
524
+ type: z3.string().optional().describe("Filter by transaction type"),
525
+ sender: z3.string().optional().describe("Filter by sender principal")
513
526
  }, async (params) => jsonResponse(await clientProvider().index.transactions.list(params)));
514
- defineTool(server, "index_transaction", "Get a single transaction from the Index by tx_id. Returns not_found if unknown.", { txId: z4.string().describe("Transaction id (0x… hash)") }, async ({ txId }) => {
527
+ 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 }) => {
515
528
  const tx = await clientProvider().index.transactions.get(txId);
516
529
  return tx ? jsonResponse(tx) : notFound(`No transaction for ${txId}`);
517
530
  });
518
- 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: z4.string().describe("Transaction id (0x… hash)") }, async ({ txId }) => {
531
+ 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 }) => {
519
532
  const proof = await clientProvider().index.transactions.getProof(txId);
520
533
  return proof ? jsonResponse(proof) : notFound(`No proof available for ${txId}`);
521
534
  });
522
535
  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).", {
523
536
  ...heightFilters,
524
- functionName: z4.string().optional().describe("Filter by PoX function name"),
525
- stacker: z4.string().optional().describe("Filter by stacker principal"),
526
- caller: z4.string().optional().describe("Filter by caller principal")
537
+ functionName: z3.string().optional().describe("Filter by PoX function name"),
538
+ stacker: z3.string().optional().describe("Filter by stacker principal"),
539
+ caller: z3.string().optional().describe("Filter by caller principal")
527
540
  }, async (params) => jsonResponse(await clientProvider().index.stacking.list(params)));
528
541
  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).", {
529
- sender: z4.string().optional().describe("Filter by sender principal"),
530
- type: z4.string().optional().describe("Filter by transaction type"),
531
- contractId: z4.string().optional().describe("Filter to pending calls to a single contract"),
532
- cursor: z4.string().optional().describe("Opaque cursor from a prior response's next_cursor"),
533
- limit: z4.number().optional().describe("Max rows for this page")
542
+ sender: z3.string().optional().describe("Filter by sender principal"),
543
+ type: z3.string().optional().describe("Filter by transaction type"),
544
+ contractId: z3.string().optional().describe("Filter to pending calls to a single contract"),
545
+ cursor: z3.string().optional().describe("Opaque cursor from a prior response's next_cursor"),
546
+ limit: z3.number().optional().describe("Max rows for this page")
534
547
  }, async (params) => jsonResponse(await clientProvider().index.mempool.list(params)));
535
- 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: z4.string().describe("Transaction id (0x… hash)") }, async ({ txId }) => {
548
+ 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 }) => {
536
549
  const tx = await clientProvider().index.mempool.get(txId);
537
550
  return tx ? jsonResponse(tx) : notFound(`No pending tx for ${txId}`);
538
551
  });
539
552
  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()));
540
553
  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()));
541
554
  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(", ")}.`, {
542
- target: z4.enum(["prisma", "kysely", "drizzle", "json-schema"]).optional().describe("Output target (default kysely)"),
543
- tables: z4.array(z4.string()).optional().describe("Subset of Index tables (default: all)"),
544
- schemaName: z4.string().optional().describe("Postgres schema to qualify table names with")
555
+ target: z3.enum(["prisma", "kysely", "drizzle", "json-schema"]).optional().describe("Output target (default kysely)"),
556
+ tables: z3.array(z3.string()).optional().describe("Subset of Index tables (default: all)"),
557
+ schemaName: z3.string().optional().describe("Postgres schema to qualify table names with")
545
558
  }, async ({ target = "kysely", tables, schemaName }) => {
546
559
  const out = generateIndexSchema(target, { tables, schemaName });
547
560
  return { content: [{ type: "text", text: out }] };
548
561
  });
562
+ 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.", {
563
+ requests: z3.array(z3.object({
564
+ path: z3.string().describe("Public /v1 read path"),
565
+ params: z3.record(z3.string(), z3.string()).optional()
566
+ })).min(1).max(10).describe("Read descriptors, executed concurrently")
567
+ }, async (params) => jsonResponse(await clientProvider().batch(params.requests)));
549
568
  }
550
569
 
551
570
  // src/tools/projects.ts
552
- import { z as z5 } from "zod/v4";
571
+ import { z as z4 } from "zod/v4";
553
572
  function registerProjectTools(server, clientProvider = getClient) {
554
573
  defineTool(server, "project_list", "List the account's projects. Requires an account-level (owner) API key.", {}, async () => jsonResponse(await clientProvider().projects.list()));
555
- defineTool(server, "project_get", "Get a single project by slug.", { slug: z5.string().describe("Project slug") }, async ({ slug }) => jsonResponse(await clientProvider().projects.get(slug)));
574
+ defineTool(server, "project_get", "Get a single project by slug.", { slug: z4.string().describe("Project slug") }, async ({ slug }) => jsonResponse(await clientProvider().projects.get(slug)));
556
575
  defineTool(server, "project_create", "Create a project. The slug is derived from the name when omitted. Requires an account-level (owner) API key.", {
557
- name: z5.string().describe("Project name"),
558
- slug: z5.string().optional().describe("URL slug (auto-derived from name if omitted)"),
559
- network: z5.string().optional().describe("Network (e.g. mainnet, testnet)"),
560
- nodeRpc: z5.string().optional().describe("Custom node RPC URL")
576
+ name: z4.string().describe("Project name"),
577
+ slug: z4.string().optional().describe("URL slug (auto-derived from name if omitted)"),
578
+ network: z4.string().optional().describe("Network (e.g. mainnet, testnet)"),
579
+ nodeRpc: z4.string().optional().describe("Custom node RPC URL")
561
580
  }, async ({ name, slug, network, nodeRpc }) => jsonResponse(await clientProvider().projects.create({
562
581
  name,
563
582
  slug,
@@ -565,12 +584,12 @@ function registerProjectTools(server, clientProvider = getClient) {
565
584
  nodeRpc
566
585
  })));
567
586
  defineTool(server, "project_update", "Update a project. `slug` selects the project; `newSlug` renames it. Requires an account-level (owner) API key.", {
568
- slug: z5.string().describe("Slug of the project to update"),
569
- newSlug: z5.string().optional().describe("New slug (renames the project)"),
570
- name: z5.string().optional().describe("New display name"),
571
- network: z5.string().optional().describe("Network (e.g. mainnet, testnet)"),
572
- nodeRpc: z5.string().optional().describe("Custom node RPC URL"),
573
- settings: z5.record(z5.string(), z5.unknown()).optional().describe("Arbitrary project settings to merge")
587
+ slug: z4.string().describe("Slug of the project to update"),
588
+ newSlug: z4.string().optional().describe("New slug (renames the project)"),
589
+ name: z4.string().optional().describe("New display name"),
590
+ network: z4.string().optional().describe("Network (e.g. mainnet, testnet)"),
591
+ nodeRpc: z4.string().optional().describe("Custom node RPC URL"),
592
+ settings: z4.record(z4.string(), z4.unknown()).optional().describe("Arbitrary project settings to merge")
574
593
  }, async ({ slug, newSlug, name, network, nodeRpc, settings }) => jsonResponse(await clientProvider().projects.update(slug, {
575
594
  ...newSlug !== undefined ? { slug: newSlug } : {},
576
595
  ...name !== undefined ? { name } : {},
@@ -578,8 +597,8 @@ function registerProjectTools(server, clientProvider = getClient) {
578
597
  ...nodeRpc !== undefined ? { nodeRpc } : {},
579
598
  ...settings !== undefined ? { settings } : {}
580
599
  })));
581
- defineTool(server, "project_delete", "Delete a project permanently. The account's last remaining project cannot be deleted.", { slug: z5.string().describe("Project slug") }, async ({ slug }) => jsonResponse(await clientProvider().projects.delete(slug)));
582
- defineTool(server, "project_team_list", "List a project's team members and pending invitations.", { slug: z5.string().describe("Project slug") }, async ({ slug }) => jsonResponse(await clientProvider().projects.team(slug)));
600
+ 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)));
601
+ 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)));
583
602
  }
584
603
 
585
604
  // src/tools/scaffold.ts
@@ -588,7 +607,7 @@ import {
588
607
  generateTraitSubgraph
589
608
  } from "@secondlayer/scaffold";
590
609
  import { TRAIT_STANDARDS as TRAIT_STANDARDS2 } from "@secondlayer/stacks/clarity";
591
- import { z as z6 } from "zod/v4";
610
+ import { z as z5 } from "zod/v4";
592
611
  async function fetchAbi(contractId) {
593
612
  const contract = await getClient().contracts.get(contractId, {
594
613
  includeAbi: true
@@ -603,24 +622,24 @@ async function fetchAbi(contractId) {
603
622
  }
604
623
  function registerScaffoldTools(server) {
605
624
  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.", {
606
- trait: z6.enum(TRAIT_STANDARDS2).describe("SIP standard to index (sip-009 | sip-010 | sip-013)"),
607
- subgraphName: z6.string().optional().describe("Override the subgraph name (defaults to <trait>-transfers)")
625
+ trait: z5.enum(TRAIT_STANDARDS2).describe("SIP standard to index (sip-009 | sip-010 | sip-013)"),
626
+ subgraphName: z5.string().optional().describe("Override the subgraph name (defaults to <trait>-transfers)")
608
627
  }, async ({ trait, subgraphName }) => {
609
628
  const code = generateTraitSubgraph({ trait, name: subgraphName });
610
629
  return { content: [{ type: "text", text: code }] };
611
630
  });
612
631
  defineTool(server, "scaffold_from_contract", "Generate a subgraph scaffold from a deployed Stacks contract. Fetches the ABI automatically.", {
613
- contractId: z6.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
614
- subgraphName: z6.string().optional().describe("Override the subgraph name (defaults to contract name)")
632
+ contractId: z5.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
633
+ subgraphName: z5.string().optional().describe("Override the subgraph name (defaults to contract name)")
615
634
  }, async ({ contractId, subgraphName }) => {
616
635
  const { functions, maps } = await fetchAbi(contractId);
617
636
  const code = generateSubgraphCode(contractId, functions, subgraphName, maps);
618
637
  return { content: [{ type: "text", text: code }] };
619
638
  });
620
639
  defineTool(server, "scaffold_from_abi", "Generate a subgraph scaffold from a provided ABI JSON. Use when you already have the ABI.", {
621
- abi: z6.string().describe("ABI JSON string (the full contract ABI object)"),
622
- contractId: z6.string().describe("Fully qualified contract ID"),
623
- subgraphName: z6.string().optional().describe("Override the subgraph name")
640
+ abi: z5.string().describe("ABI JSON string (the full contract ABI object)"),
641
+ contractId: z5.string().describe("Fully qualified contract ID"),
642
+ subgraphName: z5.string().optional().describe("Override the subgraph name")
624
643
  }, async ({ abi, contractId, subgraphName }) => {
625
644
  let parsed;
626
645
  try {
@@ -639,7 +658,7 @@ function registerScaffoldTools(server) {
639
658
  // src/tools/streams.ts
640
659
  import { AuthError } from "@secondlayer/sdk";
641
660
  import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES3 } from "@secondlayer/shared";
642
- import { z as z7 } from "zod/v4";
661
+ import { z as z6 } from "zod/v4";
643
662
  var STREAMS_EVENT_TYPES = DECODED_EVENT_TYPES3;
644
663
  async function withStreamsAuthHint(fn) {
645
664
  try {
@@ -654,28 +673,28 @@ async function withStreamsAuthHint(fn) {
654
673
  function registerStreamsTools(server, clientProvider = getClient) {
655
674
  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())));
656
675
  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.", {
657
- types: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
658
- notTypes: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude (applied after types)"),
659
- contractId: z7.string().optional().describe("Filter by contract id"),
660
- sender: z7.string().optional().describe("Filter by sender principal"),
661
- recipient: z7.string().optional().describe("Filter by recipient principal"),
662
- assetIdentifier: z7.string().optional().describe("Filter by asset identifier"),
663
- fromHeight: z7.number().optional().describe("Start block height (inclusive)"),
664
- toHeight: z7.number().optional().describe("End block height (inclusive)"),
665
- cursor: z7.string().optional().describe("Opaque cursor from a prior response"),
666
- limit: z7.number().optional().describe("Max events for this page")
676
+ types: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
677
+ notTypes: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude (applied after types)"),
678
+ contractId: z6.string().optional().describe("Filter by contract id"),
679
+ sender: z6.string().optional().describe("Filter by sender principal"),
680
+ recipient: z6.string().optional().describe("Filter by recipient principal"),
681
+ assetIdentifier: z6.string().optional().describe("Filter by asset identifier"),
682
+ fromHeight: z6.number().optional().describe("Start block height (inclusive)"),
683
+ toHeight: z6.number().optional().describe("End block height (inclusive)"),
684
+ cursor: z6.string().optional().describe("Opaque cursor from a prior response"),
685
+ limit: z6.number().optional().describe("Max events for this page")
667
686
  }, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.events.list(params))));
668
687
  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).", {
669
- fromCursor: z7.string().optional().describe("Resume cursor from a prior consume (omit to start at genesis)"),
670
- types: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
671
- notTypes: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude"),
672
- contractId: z7.string().optional().describe("Filter by contract id"),
673
- sender: z7.string().optional().describe("Filter by sender principal"),
674
- recipient: z7.string().optional().describe("Filter by recipient principal"),
675
- assetIdentifier: z7.string().optional().describe("Filter by asset identifier"),
676
- batchSize: z7.number().optional().describe("Events per page (1–1000, default 100)"),
677
- maxPages: z7.number().optional().describe("Max pages to walk this call (1–20, default 5)"),
678
- finalizedOnly: z7.boolean().optional().describe("Only emit finalized events; skip reorg handling")
688
+ fromCursor: z6.string().optional().describe("Resume cursor from a prior consume (omit to start at genesis)"),
689
+ types: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
690
+ notTypes: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude"),
691
+ contractId: z6.string().optional().describe("Filter by contract id"),
692
+ sender: z6.string().optional().describe("Filter by sender principal"),
693
+ recipient: z6.string().optional().describe("Filter by recipient principal"),
694
+ assetIdentifier: z6.string().optional().describe("Filter by asset identifier"),
695
+ batchSize: z6.number().optional().describe("Events per page (1–1000, default 100)"),
696
+ maxPages: z6.number().optional().describe("Max pages to walk this call (1–20, default 5)"),
697
+ finalizedOnly: z6.boolean().optional().describe("Only emit finalized events; skip reorg handling")
679
698
  }, async (params) => withStreamsAuthHint(async () => {
680
699
  const maxPages = Math.min(Math.max(params.maxPages ?? 5, 1), 20);
681
700
  const batchSize = Math.min(Math.max(params.batchSize ?? 100, 1), 1000);
@@ -707,15 +726,15 @@ function registerStreamsTools(server, clientProvider = getClient) {
707
726
  pages: result.pages
708
727
  });
709
728
  }));
710
- defineTool(server, "streams_event_by_txid", "List all Streams events emitted by a single transaction. Streams requires an API key (SL_API_KEY).", { txId: z7.string().describe("Transaction id (0x… hash)") }, async ({ txId }) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.events.byTxId(txId))));
729
+ 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))));
711
730
  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).", {
712
- heightOrHash: z7.string().describe("Block height (digits) or block hash (0x… string)")
731
+ heightOrHash: z6.string().describe("Block height (digits) or block hash (0x… string)")
713
732
  }, async ({ heightOrHash }) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.blocks.events(/^\d+$/.test(heightOrHash) ? Number(heightOrHash) : heightOrHash))));
714
733
  defineTool(server, "streams_reorgs", "List chain reorgs observed by Streams since a cursor. Streams requires an API key (SL_API_KEY).", {
715
- since: z7.string().describe("Cursor to list reorgs since (block:index or ISO timestamp)"),
716
- limit: z7.number().optional().describe("Max reorgs to return")
734
+ since: z6.string().describe("Cursor to list reorgs since (block:index or ISO timestamp)"),
735
+ limit: z6.number().optional().describe("Max reorgs to return")
717
736
  }, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.reorgs.list(params))));
718
- 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: z7.number().describe("Block height") }, async ({ height }) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.canonical(height))));
737
+ 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))));
719
738
  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())));
720
739
  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()));
721
740
  }
@@ -728,7 +747,7 @@ import {
728
747
  generateKyselySchema,
729
748
  generatePrismaSchema
730
749
  } from "@secondlayer/subgraphs";
731
- import { z as z8 } from "zod/v4";
750
+ import { z as z7 } from "zod/v4";
732
751
  function registerSubgraphTools(server, clientProvider = getClient) {
733
752
  defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only, including visibility — public subgraphs are anon-readable at /v1/subgraphs/<name>.", {}, async () => {
734
753
  const { data } = await clientProvider().subgraphs.list();
@@ -741,16 +760,16 @@ function registerSubgraphTools(server, clientProvider = getClient) {
741
760
  ]
742
761
  };
743
762
  });
744
- defineTool(server, "subgraphs_get", "Get full details of a subgraph including schema, health, and table columns.", { name: z8.string().describe("Subgraph name") }, async ({ name }) => {
763
+ defineTool(server, "subgraphs_get", "Get full details of a subgraph including schema, health, and table columns.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
745
764
  const detail = await clientProvider().subgraphs.get(name);
746
765
  return {
747
766
  content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
748
767
  };
749
768
  });
750
769
  defineTool(server, "subgraphs_spec", "Get generated API documentation for a subgraph. Defaults to compact agent schema; supports OpenAPI JSON and Markdown.", {
751
- name: z8.string().describe("Subgraph name"),
752
- format: z8.enum(["agent", "openapi", "markdown"]).optional().describe("Spec format to return. Defaults to agent."),
753
- serverUrl: z8.string().optional().describe("Override the server URL embedded in generated docs.")
770
+ name: z7.string().describe("Subgraph name"),
771
+ format: z7.enum(["agent", "openapi", "markdown"]).optional().describe("Spec format to return. Defaults to agent."),
772
+ serverUrl: z7.string().optional().describe("Override the server URL embedded in generated docs.")
754
773
  }, async ({ name, format = "agent", serverUrl }) => {
755
774
  const options = serverUrl ? { serverUrl } : undefined;
756
775
  const spec = format === "openapi" ? await clientProvider().subgraphs.openapi(name, options) : format === "markdown" ? await clientProvider().subgraphs.markdown(name, options) : await clientProvider().subgraphs.schema(name, options);
@@ -764,15 +783,15 @@ function registerSubgraphTools(server, clientProvider = getClient) {
764
783
  };
765
784
  });
766
785
  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.', {
767
- name: z8.string().describe("Subgraph name"),
768
- table: z8.string().describe("Table name"),
769
- filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
770
- sort: z8.string().optional().describe("Column to sort by"),
771
- order: z8.enum(["asc", "desc"]).optional().describe("Sort order"),
772
- limit: z8.number().max(200).optional().describe("Max rows (default 50, max 200)"),
773
- offset: z8.number().optional().describe("Offset for pagination"),
774
- fields: z8.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
775
- count: z8.boolean().optional().describe("If true, return row count instead of rows")
786
+ name: z7.string().describe("Subgraph name"),
787
+ table: z7.string().describe("Table name"),
788
+ filters: z7.record(z7.string(), z7.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
789
+ sort: z7.string().optional().describe("Column to sort by"),
790
+ order: z7.enum(["asc", "desc"]).optional().describe("Sort order"),
791
+ limit: z7.number().max(200).optional().describe("Max rows (default 50, max 200)"),
792
+ offset: z7.number().optional().describe("Offset for pagination"),
793
+ fields: z7.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
794
+ count: z7.boolean().optional().describe("If true, return row count instead of rows")
776
795
  }, async ({
777
796
  name,
778
797
  table,
@@ -805,14 +824,14 @@ function registerSubgraphTools(server, clientProvider = getClient) {
805
824
  };
806
825
  });
807
826
  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.`, {
808
- name: z8.string().describe("Subgraph name"),
809
- table: z8.string().describe("Table name"),
810
- filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
811
- count: z8.boolean().optional().describe("Include COUNT(*) of matching rows"),
812
- countDistinct: z8.array(z8.string()).optional().describe("Columns to count distinct values of"),
813
- sum: z8.array(z8.string()).optional().describe("Numeric columns to sum (lossless string result)"),
814
- min: z8.array(z8.string()).optional().describe("Numeric columns to take the minimum of"),
815
- max: z8.array(z8.string()).optional().describe("Numeric columns to take the maximum of")
827
+ name: z7.string().describe("Subgraph name"),
828
+ table: z7.string().describe("Table name"),
829
+ filters: z7.record(z7.string(), z7.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
830
+ count: z7.boolean().optional().describe("Include COUNT(*) of matching rows"),
831
+ countDistinct: z7.array(z7.string()).optional().describe("Columns to count distinct values of"),
832
+ sum: z7.array(z7.string()).optional().describe("Numeric columns to sum (lossless string result)"),
833
+ min: z7.array(z7.string()).optional().describe("Numeric columns to take the minimum of"),
834
+ max: z7.array(z7.string()).optional().describe("Numeric columns to take the maximum of")
816
835
  }, async ({ name, table, filters, count, countDistinct, sum, min, max }) => {
817
836
  const result = await clientProvider().subgraphs.queryTableAggregate(name, table, { filters, count, countDistinct, sum, min, max });
818
837
  return {
@@ -820,9 +839,9 @@ function registerSubgraphTools(server, clientProvider = getClient) {
820
839
  };
821
840
  });
822
841
  defineTool(server, "subgraphs_reindex", "Reindex a subgraph from a specific block range. Returns an operationId — poll subgraphs_operation to track progress to completion.", {
823
- name: z8.string().describe("Subgraph name"),
824
- fromBlock: z8.number().optional().describe("Start block (defaults to beginning)"),
825
- toBlock: z8.number().optional().describe("End block (defaults to latest)")
842
+ name: z7.string().describe("Subgraph name"),
843
+ fromBlock: z7.number().optional().describe("Start block (defaults to beginning)"),
844
+ toBlock: z7.number().optional().describe("End block (defaults to latest)")
826
845
  }, async ({ name, fromBlock, toBlock }) => {
827
846
  const result = await clientProvider().subgraphs.reindex(name, {
828
847
  fromBlock,
@@ -833,8 +852,8 @@ function registerSubgraphTools(server, clientProvider = getClient) {
833
852
  };
834
853
  });
835
854
  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.", {
836
- name: z8.string().describe("Subgraph name"),
837
- operationId: z8.string().optional().describe("Operation id from reindex/backfill/stop; omit to list recent operations")
855
+ name: z7.string().describe("Subgraph name"),
856
+ operationId: z7.string().optional().describe("Operation id from reindex/backfill/stop; omit to list recent operations")
838
857
  }, async ({ name, operationId }) => {
839
858
  const result = operationId ? await clientProvider().subgraphs.getOperation(name, operationId) : await clientProvider().subgraphs.operations(name);
840
859
  return {
@@ -842,9 +861,9 @@ function registerSubgraphTools(server, clientProvider = getClient) {
842
861
  };
843
862
  });
844
863
  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.", {
845
- name: z8.string().describe("Subgraph name"),
846
- fromBlock: z8.number().int().nonnegative().describe("Start block (inclusive)"),
847
- toBlock: z8.number().int().nonnegative().describe("End block (inclusive)")
864
+ name: z7.string().describe("Subgraph name"),
865
+ fromBlock: z7.number().int().nonnegative().describe("Start block (inclusive)"),
866
+ toBlock: z7.number().int().nonnegative().describe("End block (inclusive)")
848
867
  }, async ({ name, fromBlock, toBlock }) => {
849
868
  const result = await clientProvider().subgraphs.backfill(name, {
850
869
  fromBlock,
@@ -854,17 +873,17 @@ function registerSubgraphTools(server, clientProvider = getClient) {
854
873
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
855
874
  };
856
875
  });
857
- 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: z8.string().describe("Subgraph name") }, async ({ name }) => {
876
+ 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 }) => {
858
877
  const result = await clientProvider().subgraphs.stop(name);
859
878
  return {
860
879
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
861
880
  };
862
881
  });
863
882
  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.", {
864
- name: z8.string().describe("Subgraph name"),
865
- limit: z8.number().int().positive().optional().describe("Max gaps to return"),
866
- offset: z8.number().int().nonnegative().optional().describe("Pagination offset"),
867
- resolved: z8.boolean().optional().describe("Filter by resolved state (omit for unresolved only)")
883
+ name: z7.string().describe("Subgraph name"),
884
+ limit: z7.number().int().positive().optional().describe("Max gaps to return"),
885
+ offset: z7.number().int().nonnegative().optional().describe("Pagination offset"),
886
+ resolved: z7.boolean().optional().describe("Filter by resolved state (omit for unresolved only)")
868
887
  }, async ({ name, limit, offset, resolved }) => {
869
888
  const result = await clientProvider().subgraphs.gaps(name, {
870
889
  limit,
@@ -875,16 +894,16 @@ function registerSubgraphTools(server, clientProvider = getClient) {
875
894
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
876
895
  };
877
896
  });
878
- defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z8.string().describe("Subgraph name") }, async ({ name }) => {
897
+ defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
879
898
  const result = await clientProvider().subgraphs.delete(name);
880
899
  return { content: [{ type: "text", text: result.message }] };
881
900
  });
882
901
  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.", {
883
- code: z8.string().describe("TypeScript source code containing a defineSubgraph() call"),
884
- startBlock: z8.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy"),
885
- databaseUrl: z8.string().optional().describe("BYO data plane: Postgres connection string to host the subgraph's schema and rows in your own database"),
886
- dryRun: z8.boolean().optional().describe("Validate and preview the deploy (schema/DDL, BYO connection) without writing changes"),
887
- visibility: z8.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.")
902
+ code: z7.string().describe("TypeScript source code containing a defineSubgraph() call"),
903
+ startBlock: z7.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy"),
904
+ databaseUrl: z7.string().optional().describe("BYO data plane: Postgres connection string to host the subgraph's schema and rows in your own database"),
905
+ dryRun: z7.boolean().optional().describe("Validate and preview the deploy (schema/DDL, BYO connection) without writing changes"),
906
+ 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.")
888
907
  }, async ({ code, startBlock, databaseUrl, dryRun, visibility }) => {
889
908
  const bundled = await bundleSubgraphCode(code);
890
909
  try {
@@ -918,29 +937,29 @@ function registerSubgraphTools(server, clientProvider = getClient) {
918
937
  throw err;
919
938
  }
920
939
  });
921
- 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: z8.string().describe("Subgraph name") }, async ({ name }) => {
940
+ 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 }) => {
922
941
  const result = await clientProvider().subgraphs.publish(name);
923
942
  return {
924
943
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
925
944
  };
926
945
  });
927
- 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: z8.string().describe("Subgraph name") }, async ({ name }) => {
946
+ 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 }) => {
928
947
  const result = await clientProvider().subgraphs.unpublish(name);
929
948
  return {
930
949
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
931
950
  };
932
951
  });
933
- 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: z8.string().describe("Subgraph name") }, async ({ name }) => {
952
+ 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 }) => {
934
953
  const source = await clientProvider().subgraphs.getSource(name);
935
954
  return {
936
955
  content: [{ type: "text", text: JSON.stringify(source, null, 2) }]
937
956
  };
938
957
  });
939
958
  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.", {
940
- code: z8.string().optional().describe("defineSubgraph() source (mutually exclusive with name)"),
941
- name: z8.string().optional().describe("Deployed subgraph name (mutually exclusive with code)"),
942
- target: z8.enum(["prisma", "drizzle", "kysely"]).optional().describe("ORM target (default prisma)"),
943
- schemaName: z8.string().optional().describe("Postgres schema name (defaults to subgraph_<name>)")
959
+ code: z7.string().optional().describe("defineSubgraph() source (mutually exclusive with name)"),
960
+ name: z7.string().optional().describe("Deployed subgraph name (mutually exclusive with code)"),
961
+ target: z7.enum(["prisma", "drizzle", "kysely"]).optional().describe("ORM target (default prisma)"),
962
+ schemaName: z7.string().optional().describe("Postgres schema name (defaults to subgraph_<name>)")
944
963
  }, async ({ code, name, target = "prisma", schemaName }) => {
945
964
  if (code && name || !code && !name) {
946
965
  throw new Error("Provide exactly one of `code` or `name`.");
@@ -969,7 +988,7 @@ function registerSubgraphTools(server, clientProvider = getClient) {
969
988
 
970
989
  // src/tools/subscriptions.ts
971
990
  import { CHAIN_TRIGGER_TYPES } from "@secondlayer/shared";
972
- import { z as z9 } from "zod/v4";
991
+ import { z as z8 } from "zod/v4";
973
992
  function registerSubscriptionTools(server, clientProvider = getClient) {
974
993
  defineTool(server, "subscriptions_list", "List all subscriptions for the current account. Returns summary fields (no secrets).", {}, async () => {
975
994
  const { data } = await clientProvider().subscriptions.list();
@@ -977,34 +996,34 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
977
996
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
978
997
  };
979
998
  });
980
- defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id: z9.string().describe("Subscription id") }, async ({ id }) => {
999
+ defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id: z8.string().describe("Subscription id") }, async ({ id }) => {
981
1000
  const detail = await clientProvider().subscriptions.get(id);
982
1001
  return {
983
1002
  content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
984
1003
  };
985
1004
  });
986
1005
  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.", {
987
- name: z9.string().describe("Human-readable name, unique per account"),
988
- subgraphName: z9.string().optional().describe("Subgraph to subscribe to (subgraph subscription)"),
989
- tableName: z9.string().optional().describe("Table within the subgraph (subgraph subscription)"),
990
- triggers: z9.array(z9.object({
991
- type: z9.enum(CHAIN_TRIGGER_TYPES),
992
- contractId: z9.string().optional(),
993
- functionName: z9.string().optional(),
994
- caller: z9.string().optional(),
995
- sender: z9.string().optional(),
996
- recipient: z9.string().optional(),
997
- assetIdentifier: z9.string().optional(),
998
- deployer: z9.string().optional(),
999
- contractName: z9.string().optional(),
1000
- topic: z9.string().optional(),
1001
- lockedAddress: z9.string().optional(),
1002
- trait: z9.string().optional(),
1003
- minAmount: z9.union([z9.string(), z9.number()]).optional(),
1004
- maxAmount: z9.union([z9.string(), z9.number()]).optional()
1006
+ name: z8.string().describe("Human-readable name, unique per account"),
1007
+ subgraphName: z8.string().optional().describe("Subgraph to subscribe to (subgraph subscription)"),
1008
+ tableName: z8.string().optional().describe("Table within the subgraph (subgraph subscription)"),
1009
+ triggers: z8.array(z8.object({
1010
+ type: z8.enum(CHAIN_TRIGGER_TYPES),
1011
+ contractId: z8.string().optional(),
1012
+ functionName: z8.string().optional(),
1013
+ caller: z8.string().optional(),
1014
+ sender: z8.string().optional(),
1015
+ recipient: z8.string().optional(),
1016
+ assetIdentifier: z8.string().optional(),
1017
+ deployer: z8.string().optional(),
1018
+ contractName: z8.string().optional(),
1019
+ topic: z8.string().optional(),
1020
+ lockedAddress: z8.string().optional(),
1021
+ trait: z8.string().optional(),
1022
+ minAmount: z8.union([z8.string(), z8.number()]).optional(),
1023
+ maxAmount: z8.union([z8.string(), z8.number()]).optional()
1005
1024
  })).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."),
1006
- url: z9.string().describe("Webhook URL"),
1007
- format: z9.enum([
1025
+ url: z8.string().describe("Webhook URL"),
1026
+ format: z8.enum([
1008
1027
  "standard-webhooks",
1009
1028
  "inngest",
1010
1029
  "trigger",
@@ -1012,9 +1031,9 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
1012
1031
  "cloudevents",
1013
1032
  "raw"
1014
1033
  ]).optional().describe("Wire format (default standard-webhooks)"),
1015
- runtime: z9.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
1016
- filter: z9.record(z9.string(), z9.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}'),
1017
- authConfig: z9.record(z9.string(), z9.unknown()).optional().describe('Receiver auth sent with each delivery, e.g. {"type": "bearer", "token": "..."}')
1034
+ runtime: z8.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
1035
+ filter: z8.record(z8.string(), z8.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}'),
1036
+ authConfig: z8.record(z8.string(), z8.unknown()).optional().describe('Receiver auth sent with each delivery, e.g. {"type": "bearer", "token": "..."}')
1018
1037
  }, async (input) => {
1019
1038
  const res = await clientProvider().subscriptions.create(input);
1020
1039
  return {
@@ -1022,12 +1041,12 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
1022
1041
  };
1023
1042
  });
1024
1043
  defineTool(server, "subscriptions_update", "Patch a subscription (name, url, filter, authConfig, format, runtime, retry, timeout, concurrency).", {
1025
- id: z9.string(),
1026
- name: z9.string().optional().describe("Rename the subscription"),
1027
- url: z9.string().optional(),
1028
- filter: z9.record(z9.string(), z9.unknown()).optional(),
1029
- authConfig: z9.record(z9.string(), z9.unknown()).optional().describe("Receiver auth sent with each delivery (bearer/etc.)"),
1030
- format: z9.enum([
1044
+ id: z8.string(),
1045
+ name: z8.string().optional().describe("Rename the subscription"),
1046
+ url: z8.string().optional(),
1047
+ filter: z8.record(z8.string(), z8.unknown()).optional(),
1048
+ authConfig: z8.record(z8.string(), z8.unknown()).optional().describe("Receiver auth sent with each delivery (bearer/etc.)"),
1049
+ format: z8.enum([
1031
1050
  "standard-webhooks",
1032
1051
  "inngest",
1033
1052
  "trigger",
@@ -1035,51 +1054,51 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
1035
1054
  "cloudevents",
1036
1055
  "raw"
1037
1056
  ]).optional(),
1038
- runtime: z9.enum(["inngest", "trigger", "cloudflare", "node"]).nullable().optional(),
1039
- maxRetries: z9.number().int().min(0).optional(),
1040
- timeoutMs: z9.number().int().min(100).optional(),
1041
- concurrency: z9.number().int().min(1).optional()
1057
+ runtime: z8.enum(["inngest", "trigger", "cloudflare", "node"]).nullable().optional(),
1058
+ maxRetries: z8.number().int().min(0).optional(),
1059
+ timeoutMs: z8.number().int().min(100).optional(),
1060
+ concurrency: z8.number().int().min(1).optional()
1042
1061
  }, async ({ id, ...patch }) => {
1043
1062
  const res = await clientProvider().subscriptions.update(id, patch);
1044
1063
  return {
1045
1064
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
1046
1065
  };
1047
1066
  });
1048
- defineTool(server, "subscriptions_pause", "Pause a subscription. Pending rows remain queued until resumed.", { id: z9.string() }, async ({ id }) => {
1067
+ defineTool(server, "subscriptions_pause", "Pause a subscription. Pending rows remain queued until resumed.", { id: z8.string() }, async ({ id }) => {
1049
1068
  const res = await clientProvider().subscriptions.pause(id);
1050
1069
  return {
1051
1070
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
1052
1071
  };
1053
1072
  });
1054
- defineTool(server, "subscriptions_resume", "Resume a paused or circuit-open subscription and reset circuit failures.", { id: z9.string() }, async ({ id }) => {
1073
+ defineTool(server, "subscriptions_resume", "Resume a paused or circuit-open subscription and reset circuit failures.", { id: z8.string() }, async ({ id }) => {
1055
1074
  const res = await clientProvider().subscriptions.resume(id);
1056
1075
  return {
1057
1076
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
1058
1077
  };
1059
1078
  });
1060
- defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id: z9.string() }, async ({ id }) => {
1079
+ defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id: z8.string() }, async ({ id }) => {
1061
1080
  const res = await clientProvider().subscriptions.delete(id);
1062
1081
  return {
1063
1082
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
1064
1083
  };
1065
1084
  });
1066
- defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription signing secret. Returns the new plaintext secret once.", { id: z9.string() }, async ({ id }) => {
1085
+ defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription signing secret. Returns the new plaintext secret once.", { id: z8.string() }, async ({ id }) => {
1067
1086
  const res = await clientProvider().subscriptions.rotateSecret(id);
1068
1087
  return {
1069
1088
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
1070
1089
  };
1071
1090
  });
1072
- 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: z9.string().describe("Subscription id") }, async ({ id }) => {
1091
+ 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 }) => {
1073
1092
  const res = await clientProvider().subscriptions.test(id);
1074
1093
  return {
1075
1094
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
1076
1095
  };
1077
1096
  });
1078
1097
  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.", {
1079
- id: z9.string(),
1080
- fromBlock: z9.number().int().nonnegative(),
1081
- toBlock: z9.number().int().nonnegative(),
1082
- force: z9.string().optional().describe("Idempotency suffix to force a duplicate replay of the range")
1098
+ id: z8.string(),
1099
+ fromBlock: z8.number().int().nonnegative(),
1100
+ toBlock: z8.number().int().nonnegative(),
1101
+ force: z8.string().optional().describe("Idempotency suffix to force a duplicate replay of the range")
1083
1102
  }, async ({ id, fromBlock, toBlock, force }) => {
1084
1103
  const res = await clientProvider().subscriptions.replay(id, {
1085
1104
  fromBlock,
@@ -1090,22 +1109,22 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
1090
1109
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
1091
1110
  };
1092
1111
  });
1093
- defineTool(server, "subscriptions_dead", "Return the last 100 dead-letter outbox rows for a subscription.", { id: z9.string() }, async ({ id }) => {
1112
+ defineTool(server, "subscriptions_dead", "Return the last 100 dead-letter outbox rows for a subscription.", { id: z8.string() }, async ({ id }) => {
1094
1113
  const { data } = await clientProvider().subscriptions.dead(id);
1095
1114
  return {
1096
1115
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
1097
1116
  };
1098
1117
  });
1099
1118
  defineTool(server, "subscriptions_requeue_dead", "Requeue one dead-letter outbox row for delivery retry.", {
1100
- id: z9.string(),
1101
- outboxId: z9.string()
1119
+ id: z8.string(),
1120
+ outboxId: z8.string()
1102
1121
  }, async ({ id, outboxId }) => {
1103
1122
  const res = await clientProvider().subscriptions.requeueDead(id, outboxId);
1104
1123
  return {
1105
1124
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
1106
1125
  };
1107
1126
  });
1108
- defineTool(server, "subscriptions_recent_deliveries", "Return the last 100 delivery attempts (attempt #, status code, duration, truncated response).", { id: z9.string() }, async ({ id }) => {
1127
+ defineTool(server, "subscriptions_recent_deliveries", "Return the last 100 delivery attempts (attempt #, status code, duration, truncated response).", { id: z8.string() }, async ({ id }) => {
1109
1128
  const { data } = await clientProvider().subscriptions.recentDeliveries(id);
1110
1129
  return {
1111
1130
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
@@ -1126,7 +1145,6 @@ function createServer() {
1126
1145
  registerSubscriptionTools(server);
1127
1146
  registerAccountTools(server);
1128
1147
  registerProjectTools(server);
1129
- registerDatasetTools(server);
1130
1148
  registerIndexTools(server);
1131
1149
  registerStreamsTools(server);
1132
1150
  registerContractTools(server);
@@ -1220,5 +1238,5 @@ httpServer.listen(port, () => {
1220
1238
  console.error("Warning: SECONDLAYER_MCP_SECRET not set, authentication disabled");
1221
1239
  });
1222
1240
 
1223
- //# debugId=38C3E42509C42A3264756E2164756E21
1241
+ //# debugId=C1AFC98747A5C42164756E2164756E21
1224
1242
  //# sourceMappingURL=bin-http.js.map