@secondlayer/mcp 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -164,7 +164,8 @@ var PRODUCT_BLURBS = {
164
164
  contracts: "trait-based contract discovery",
165
165
  subgraphs: "author/deploy/query custom indexes",
166
166
  subscriptions: "webhook delivery on subgraph rows or raw chain events",
167
- account: "identity, plan, billing, and API keys",
167
+ account: "identity, plan, billing, usage, spend caps, and API keys",
168
+ project: "create/manage projects and view their team",
168
169
  scaffold: "generate typed contract clients from a deployment or ABI"
169
170
  };
170
171
  var PRODUCT_ORDER = [
@@ -175,6 +176,7 @@ var PRODUCT_ORDER = [
175
176
  "subgraphs",
176
177
  "subscriptions",
177
178
  "account",
179
+ "project",
178
180
  "scaffold"
179
181
  ];
180
182
  function buildCapabilities() {
@@ -217,6 +219,8 @@ async function buildContext(deps = { clientProvider: getClient }) {
217
219
  indexTip: orNull(snap?.indexTip),
218
220
  subgraphs: snap?.subgraphs ? snap.subgraphs.map(formatSubgraphSummary) : unavailable,
219
221
  subscriptions: orNull(snap?.subscriptions),
222
+ projects: orNull(snap?.projects),
223
+ apiKeys: orNull(snap?.apiKeys),
220
224
  activeOperations: orNull(snap?.activeOperations)
221
225
  },
222
226
  whatYouCanDo: buildCapabilities(),
@@ -285,6 +289,23 @@ function registerAccountTools(server) {
285
289
  product: z.enum(["streams", "index"]).optional().describe("Key scope (default streams)"),
286
290
  name: z.string().optional().describe("Optional label for the key")
287
291
  }, async ({ product, name }) => jsonResponse(await getClient().apiKeys.create({ product, name })));
292
+ defineTool(server, "account_list_keys", "List the account's API keys (metadata only — prefix, name, status, product, tier, last used; never the plaintext). Requires an account-level (owner) API key.", {}, async () => jsonResponse(await getClient().apiKeys.list()));
293
+ defineTool(server, "account_revoke_key", "Revoke an API key by id. Requests using that key stop working immediately. Requires an account-level (owner) API key.", { id: z.string().describe("Key id from account_list_keys") }, async ({ id }) => jsonResponse(await getClient().apiKeys.revoke(id)));
294
+ defineTool(server, "account_usage", "Show the current billing period's usage snapshot: spend (with cap/projection), compute, storage, and per-project breakdown. Requires an API key.", {}, async () => jsonResponse(await apiRequest("GET", "/api/accounts/usage")));
295
+ defineTool(server, "account_get_caps", "Show the account's spend caps and alert threshold. Requires an account-level (owner) API key.", {}, async () => jsonResponse(await apiRequest("GET", "/api/billing/caps")));
296
+ defineTool(server, "account_set_caps", "Set the account's spend caps and alert threshold (no payment — just bounds spend). Requires an account-level (owner) API key.", {
297
+ monthlyCapCents: z.number().int().nonnegative().optional().describe("Total monthly spend cap in cents"),
298
+ computeCapCents: z.number().int().nonnegative().optional().describe("Compute spend cap in cents"),
299
+ storageCapCents: z.number().int().nonnegative().optional().describe("Storage spend cap in cents"),
300
+ alertThresholdPct: z.number().int().min(1).max(100).optional().describe("Percent of cap that triggers an alert (1-100)")
301
+ }, async (caps) => {
302
+ const body = {};
303
+ for (const [k, v] of Object.entries(caps)) {
304
+ if (v !== undefined)
305
+ body[k] = v;
306
+ }
307
+ return jsonResponse(await apiRequest("PATCH", "/api/billing/caps", body));
308
+ });
288
309
  }
289
310
 
290
311
  // src/tools/contracts.ts
@@ -306,7 +327,7 @@ function registerDatasetTools(server, clientProvider = getClient) {
306
327
  const catalog = await clientProvider().datasets.listDatasets();
307
328
  return jsonResponse(catalog);
308
329
  });
309
- defineTool(server, "datasets_query", 'Query a cursor-paginated Foundation Dataset by slug (e.g. "stx-transfers", "sbtc/events", "pox-4/calls", "bns/events"). Filters are passed through as documented query params (e.g. {"sender": "SP...", "from_block": "150000"}). Returns { rows, next_cursor, tip }. Call datasets_list first to discover slugs.', {
330
+ 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.', {
310
331
  slug: z3.string().describe("Dataset slug from datasets_list (e.g. stx-transfers)"),
311
332
  filters: z3.record(z3.string(), z3.string()).optional().describe("Documented per-dataset query params (snake_case values)"),
312
333
  limit: z3.number().optional().describe("Max rows for this page"),
@@ -317,7 +338,7 @@ function registerDatasetTools(server, clientProvider = getClient) {
317
338
  params.limit = limit;
318
339
  if (cursor !== undefined)
319
340
  params.cursor = cursor;
320
- const result = await clientProvider().datasets.query(slug, params);
341
+ const result = await clientProvider().datasets.get(slug, params);
321
342
  return jsonResponse(result);
322
343
  });
323
344
  }
@@ -413,9 +434,43 @@ function registerIndexTools(server, clientProvider = getClient) {
413
434
  });
414
435
  }
415
436
 
437
+ // src/tools/projects.ts
438
+ import { z as z5 } from "zod/v4";
439
+ function registerProjectTools(server, clientProvider = getClient) {
440
+ defineTool(server, "project_list", "List the account's projects. Requires an account-level (owner) API key.", {}, async () => jsonResponse(await clientProvider().projects.list()));
441
+ defineTool(server, "project_get", "Get a single project by slug.", { slug: z5.string().describe("Project slug") }, async ({ slug }) => jsonResponse(await clientProvider().projects.get(slug)));
442
+ defineTool(server, "project_create", "Create a project. The slug is derived from the name when omitted. Requires an account-level (owner) API key.", {
443
+ name: z5.string().describe("Project name"),
444
+ slug: z5.string().optional().describe("URL slug (auto-derived from name if omitted)"),
445
+ network: z5.string().optional().describe("Network (e.g. mainnet, testnet)"),
446
+ nodeRpc: z5.string().optional().describe("Custom node RPC URL")
447
+ }, async ({ name, slug, network, nodeRpc }) => jsonResponse(await clientProvider().projects.create({
448
+ name,
449
+ slug,
450
+ network,
451
+ nodeRpc
452
+ })));
453
+ defineTool(server, "project_update", "Update a project. `slug` selects the project; `newSlug` renames it. Requires an account-level (owner) API key.", {
454
+ slug: z5.string().describe("Slug of the project to update"),
455
+ newSlug: z5.string().optional().describe("New slug (renames the project)"),
456
+ name: z5.string().optional().describe("New display name"),
457
+ network: z5.string().optional().describe("Network (e.g. mainnet, testnet)"),
458
+ nodeRpc: z5.string().optional().describe("Custom node RPC URL"),
459
+ settings: z5.record(z5.string(), z5.unknown()).optional().describe("Arbitrary project settings to merge")
460
+ }, async ({ slug, newSlug, name, network, nodeRpc, settings }) => jsonResponse(await clientProvider().projects.update(slug, {
461
+ ...newSlug !== undefined ? { slug: newSlug } : {},
462
+ ...name !== undefined ? { name } : {},
463
+ ...network !== undefined ? { network } : {},
464
+ ...nodeRpc !== undefined ? { nodeRpc } : {},
465
+ ...settings !== undefined ? { settings } : {}
466
+ })));
467
+ 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)));
468
+ 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)));
469
+ }
470
+
416
471
  // src/tools/scaffold.ts
417
472
  import { generateSubgraphCode } from "@secondlayer/scaffold";
418
- import { z as z5 } from "zod/v4";
473
+ import { z as z6 } from "zod/v4";
419
474
  var API_BASE = process.env.SECONDLAYER_API_URL || "https://api.secondlayer.tools";
420
475
  async function fetchAbi(contractId) {
421
476
  const res = await fetch(`${API_BASE}/api/node/contracts/${contractId}/abi`, {
@@ -434,17 +489,17 @@ async function fetchAbi(contractId) {
434
489
  }
435
490
  function registerScaffoldTools(server) {
436
491
  defineTool(server, "scaffold_from_contract", "Generate a subgraph scaffold from a deployed Stacks contract. Fetches the ABI automatically.", {
437
- contractId: z5.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
438
- subgraphName: z5.string().optional().describe("Override the subgraph name (defaults to contract name)")
492
+ contractId: z6.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
493
+ subgraphName: z6.string().optional().describe("Override the subgraph name (defaults to contract name)")
439
494
  }, async ({ contractId, subgraphName }) => {
440
495
  const { functions, maps } = await fetchAbi(contractId);
441
496
  const code = generateSubgraphCode(contractId, functions, subgraphName, maps);
442
497
  return { content: [{ type: "text", text: code }] };
443
498
  });
444
499
  defineTool(server, "scaffold_from_abi", "Generate a subgraph scaffold from a provided ABI JSON. Use when you already have the ABI.", {
445
- abi: z5.string().describe("ABI JSON string (the full contract ABI object)"),
446
- contractId: z5.string().describe("Fully qualified contract ID"),
447
- subgraphName: z5.string().optional().describe("Override the subgraph name")
500
+ abi: z6.string().describe("ABI JSON string (the full contract ABI object)"),
501
+ contractId: z6.string().describe("Fully qualified contract ID"),
502
+ subgraphName: z6.string().optional().describe("Override the subgraph name")
448
503
  }, async ({ abi, contractId, subgraphName }) => {
449
504
  let parsed;
450
505
  try {
@@ -463,7 +518,7 @@ function registerScaffoldTools(server) {
463
518
  // src/tools/streams.ts
464
519
  import { AuthError } from "@secondlayer/sdk";
465
520
  import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
466
- import { z as z6 } from "zod/v4";
521
+ import { z as z7 } from "zod/v4";
467
522
  var STREAMS_EVENT_TYPES = DECODED_EVENT_TYPES2;
468
523
  async function withStreamsAuthHint(fn) {
469
524
  try {
@@ -478,28 +533,28 @@ async function withStreamsAuthHint(fn) {
478
533
  function registerStreamsTools(server, clientProvider = getClient) {
479
534
  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())));
480
535
  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.", {
481
- types: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
482
- notTypes: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude (applied after types)"),
483
- contractId: z6.string().optional().describe("Filter by contract id"),
484
- sender: z6.string().optional().describe("Filter by sender principal"),
485
- recipient: z6.string().optional().describe("Filter by recipient principal"),
486
- assetIdentifier: z6.string().optional().describe("Filter by asset identifier"),
487
- fromHeight: z6.number().optional().describe("Start block height (inclusive)"),
488
- toHeight: z6.number().optional().describe("End block height (inclusive)"),
489
- cursor: z6.string().optional().describe("Opaque cursor from a prior response"),
490
- limit: z6.number().optional().describe("Max events for this page")
536
+ types: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
537
+ notTypes: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude (applied after types)"),
538
+ contractId: z7.string().optional().describe("Filter by contract id"),
539
+ sender: z7.string().optional().describe("Filter by sender principal"),
540
+ recipient: z7.string().optional().describe("Filter by recipient principal"),
541
+ assetIdentifier: z7.string().optional().describe("Filter by asset identifier"),
542
+ fromHeight: z7.number().optional().describe("Start block height (inclusive)"),
543
+ toHeight: z7.number().optional().describe("End block height (inclusive)"),
544
+ cursor: z7.string().optional().describe("Opaque cursor from a prior response"),
545
+ limit: z7.number().optional().describe("Max events for this page")
491
546
  }, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.events.list(params))));
492
547
  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).", {
493
- fromCursor: z6.string().optional().describe("Resume cursor from a prior consume (omit to start at genesis)"),
494
- types: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
495
- notTypes: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude"),
496
- contractId: z6.string().optional().describe("Filter by contract id"),
497
- sender: z6.string().optional().describe("Filter by sender principal"),
498
- recipient: z6.string().optional().describe("Filter by recipient principal"),
499
- assetIdentifier: z6.string().optional().describe("Filter by asset identifier"),
500
- batchSize: z6.number().optional().describe("Events per page (1–1000, default 100)"),
501
- maxPages: z6.number().optional().describe("Max pages to walk this call (1–20, default 5)"),
502
- finalizedOnly: z6.boolean().optional().describe("Only emit finalized events; skip reorg handling")
548
+ fromCursor: z7.string().optional().describe("Resume cursor from a prior consume (omit to start at genesis)"),
549
+ types: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
550
+ notTypes: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude"),
551
+ contractId: z7.string().optional().describe("Filter by contract id"),
552
+ sender: z7.string().optional().describe("Filter by sender principal"),
553
+ recipient: z7.string().optional().describe("Filter by recipient principal"),
554
+ assetIdentifier: z7.string().optional().describe("Filter by asset identifier"),
555
+ batchSize: z7.number().optional().describe("Events per page (1–1000, default 100)"),
556
+ maxPages: z7.number().optional().describe("Max pages to walk this call (1–20, default 5)"),
557
+ finalizedOnly: z7.boolean().optional().describe("Only emit finalized events; skip reorg handling")
503
558
  }, async (params) => withStreamsAuthHint(async () => {
504
559
  const maxPages = Math.min(Math.max(params.maxPages ?? 5, 1), 20);
505
560
  const batchSize = Math.min(Math.max(params.batchSize ?? 100, 1), 1000);
@@ -531,26 +586,27 @@ function registerStreamsTools(server, clientProvider = getClient) {
531
586
  pages: result.pages
532
587
  });
533
588
  }));
534
- 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))));
589
+ 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))));
535
590
  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).", {
536
- heightOrHash: z6.string().describe("Block height (digits) or block hash (0x… string)")
591
+ heightOrHash: z7.string().describe("Block height (digits) or block hash (0x… string)")
537
592
  }, async ({ heightOrHash }) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.blocks.events(/^\d+$/.test(heightOrHash) ? Number(heightOrHash) : heightOrHash))));
538
593
  defineTool(server, "streams_reorgs", "List chain reorgs observed by Streams since a cursor. Streams requires an API key (SL_API_KEY).", {
539
- since: z6.string().describe("Cursor to list reorgs since (block:index or ISO timestamp)"),
540
- limit: z6.number().optional().describe("Max reorgs to return")
594
+ since: z7.string().describe("Cursor to list reorgs since (block:index or ISO timestamp)"),
595
+ limit: z7.number().optional().describe("Max reorgs to return")
541
596
  }, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.reorgs.list(params))));
542
- 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))));
597
+ 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))));
543
598
  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())));
544
599
  }
545
600
 
546
601
  // src/tools/subgraphs.ts
547
602
  import { bundleSubgraphCode } from "@secondlayer/bundler";
603
+ import { ByoBreakingChangeError } from "@secondlayer/sdk";
548
604
  import {
549
605
  generateDrizzleSchema,
550
606
  generateKyselySchema,
551
607
  generatePrismaSchema
552
608
  } from "@secondlayer/subgraphs";
553
- import { z as z7 } from "zod/v4";
609
+ import { z as z8 } from "zod/v4";
554
610
  function registerSubgraphTools(server, clientProvider = getClient) {
555
611
  defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only.", {}, async () => {
556
612
  const { data } = await clientProvider().subgraphs.list();
@@ -563,16 +619,16 @@ function registerSubgraphTools(server, clientProvider = getClient) {
563
619
  ]
564
620
  };
565
621
  });
566
- defineTool(server, "subgraphs_get", "Get full details of a subgraph including schema, health, and table columns.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
622
+ defineTool(server, "subgraphs_get", "Get full details of a subgraph including schema, health, and table columns.", { name: z8.string().describe("Subgraph name") }, async ({ name }) => {
567
623
  const detail = await clientProvider().subgraphs.get(name);
568
624
  return {
569
625
  content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
570
626
  };
571
627
  });
572
628
  defineTool(server, "subgraphs_spec", "Get generated API documentation for a subgraph. Defaults to compact agent schema; supports OpenAPI JSON and Markdown.", {
573
- name: z7.string().describe("Subgraph name"),
574
- format: z7.enum(["agent", "openapi", "markdown"]).optional().describe("Spec format to return. Defaults to agent."),
575
- serverUrl: z7.string().optional().describe("Override the server URL embedded in generated docs.")
629
+ name: z8.string().describe("Subgraph name"),
630
+ format: z8.enum(["agent", "openapi", "markdown"]).optional().describe("Spec format to return. Defaults to agent."),
631
+ serverUrl: z8.string().optional().describe("Override the server URL embedded in generated docs.")
576
632
  }, async ({ name, format = "agent", serverUrl }) => {
577
633
  const options = serverUrl ? { serverUrl } : undefined;
578
634
  const spec = format === "openapi" ? await clientProvider().subgraphs.openapi(name, options) : format === "markdown" ? await clientProvider().subgraphs.markdown(name, options) : await clientProvider().subgraphs.schema(name, options);
@@ -586,15 +642,15 @@ function registerSubgraphTools(server, clientProvider = getClient) {
586
642
  };
587
643
  });
588
644
  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.', {
589
- name: z7.string().describe("Subgraph name"),
590
- table: z7.string().describe("Table name"),
591
- filters: z7.record(z7.string(), z7.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
592
- sort: z7.string().optional().describe("Column to sort by"),
593
- order: z7.enum(["asc", "desc"]).optional().describe("Sort order"),
594
- limit: z7.number().max(200).optional().describe("Max rows (default 50, max 200)"),
595
- offset: z7.number().optional().describe("Offset for pagination"),
596
- fields: z7.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
597
- count: z7.boolean().optional().describe("If true, return row count instead of rows")
645
+ name: z8.string().describe("Subgraph name"),
646
+ table: z8.string().describe("Table name"),
647
+ filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
648
+ sort: z8.string().optional().describe("Column to sort by"),
649
+ order: z8.enum(["asc", "desc"]).optional().describe("Sort order"),
650
+ limit: z8.number().max(200).optional().describe("Max rows (default 50, max 200)"),
651
+ offset: z8.number().optional().describe("Offset for pagination"),
652
+ fields: z8.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
653
+ count: z8.boolean().optional().describe("If true, return row count instead of rows")
598
654
  }, async ({
599
655
  name,
600
656
  table,
@@ -627,14 +683,14 @@ function registerSubgraphTools(server, clientProvider = getClient) {
627
683
  };
628
684
  });
629
685
  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.`, {
630
- name: z7.string().describe("Subgraph name"),
631
- table: z7.string().describe("Table name"),
632
- filters: z7.record(z7.string(), z7.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
633
- count: z7.boolean().optional().describe("Include COUNT(*) of matching rows"),
634
- countDistinct: z7.array(z7.string()).optional().describe("Columns to count distinct values of"),
635
- sum: z7.array(z7.string()).optional().describe("Numeric columns to sum (lossless string result)"),
636
- min: z7.array(z7.string()).optional().describe("Numeric columns to take the minimum of"),
637
- max: z7.array(z7.string()).optional().describe("Numeric columns to take the maximum of")
686
+ name: z8.string().describe("Subgraph name"),
687
+ table: z8.string().describe("Table name"),
688
+ filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
689
+ count: z8.boolean().optional().describe("Include COUNT(*) of matching rows"),
690
+ countDistinct: z8.array(z8.string()).optional().describe("Columns to count distinct values of"),
691
+ sum: z8.array(z8.string()).optional().describe("Numeric columns to sum (lossless string result)"),
692
+ min: z8.array(z8.string()).optional().describe("Numeric columns to take the minimum of"),
693
+ max: z8.array(z8.string()).optional().describe("Numeric columns to take the maximum of")
638
694
  }, async ({ name, table, filters, count, countDistinct, sum, min, max }) => {
639
695
  const result = await clientProvider().subgraphs.queryTableAggregate(name, table, { filters, count, countDistinct, sum, min, max });
640
696
  return {
@@ -642,9 +698,9 @@ function registerSubgraphTools(server, clientProvider = getClient) {
642
698
  };
643
699
  });
644
700
  defineTool(server, "subgraphs_reindex", "Reindex a subgraph from a specific block range. Returns an operationId — poll subgraphs_operation to track progress to completion.", {
645
- name: z7.string().describe("Subgraph name"),
646
- fromBlock: z7.number().optional().describe("Start block (defaults to beginning)"),
647
- toBlock: z7.number().optional().describe("End block (defaults to latest)")
701
+ name: z8.string().describe("Subgraph name"),
702
+ fromBlock: z8.number().optional().describe("Start block (defaults to beginning)"),
703
+ toBlock: z8.number().optional().describe("End block (defaults to latest)")
648
704
  }, async ({ name, fromBlock, toBlock }) => {
649
705
  const result = await clientProvider().subgraphs.reindex(name, {
650
706
  fromBlock,
@@ -655,48 +711,100 @@ function registerSubgraphTools(server, clientProvider = getClient) {
655
711
  };
656
712
  });
657
713
  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.", {
658
- name: z7.string().describe("Subgraph name"),
659
- operationId: z7.string().optional().describe("Operation id from reindex/backfill/stop; omit to list recent operations")
714
+ name: z8.string().describe("Subgraph name"),
715
+ operationId: z8.string().optional().describe("Operation id from reindex/backfill/stop; omit to list recent operations")
660
716
  }, async ({ name, operationId }) => {
661
717
  const result = operationId ? await clientProvider().subgraphs.getOperation(name, operationId) : await clientProvider().subgraphs.operations(name);
662
718
  return {
663
719
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
664
720
  };
665
721
  });
666
- defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
667
- const result = await clientProvider().subgraphs.delete(name);
668
- return { content: [{ type: "text", text: result.message }] };
722
+ 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.", {
723
+ name: z8.string().describe("Subgraph name"),
724
+ fromBlock: z8.number().int().nonnegative().describe("Start block (inclusive)"),
725
+ toBlock: z8.number().int().nonnegative().describe("End block (inclusive)")
726
+ }, async ({ name, fromBlock, toBlock }) => {
727
+ const result = await clientProvider().subgraphs.backfill(name, {
728
+ fromBlock,
729
+ toBlock
730
+ });
731
+ return {
732
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
733
+ };
669
734
  });
670
- 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. Call `subgraphs_reindex` separately if you need a forced reindex.", {
671
- code: z7.string().describe("TypeScript source code containing a defineSubgraph() call"),
672
- startBlock: z7.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy")
673
- }, async ({ code, startBlock }) => {
674
- const bundled = await bundleSubgraphCode(code);
675
- const result = await clientProvider().subgraphs.deploy({
676
- name: bundled.name,
677
- version: bundled.version,
678
- description: bundled.description,
679
- sources: bundled.sources,
680
- schema: bundled.schema,
681
- handlerCode: bundled.handlerCode,
682
- sourceCode: code,
683
- ...startBlock !== undefined ? { startBlock } : {}
735
+ 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 }) => {
736
+ const result = await clientProvider().subgraphs.stop(name);
737
+ return {
738
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
739
+ };
740
+ });
741
+ 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.", {
742
+ name: z8.string().describe("Subgraph name"),
743
+ limit: z8.number().int().positive().optional().describe("Max gaps to return"),
744
+ offset: z8.number().int().nonnegative().optional().describe("Pagination offset"),
745
+ resolved: z8.boolean().optional().describe("Filter by resolved state (omit for unresolved only)")
746
+ }, async ({ name, limit, offset, resolved }) => {
747
+ const result = await clientProvider().subgraphs.gaps(name, {
748
+ limit,
749
+ offset,
750
+ resolved
684
751
  });
685
752
  return {
686
753
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
687
754
  };
688
755
  });
689
- 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 }) => {
756
+ defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z8.string().describe("Subgraph name") }, async ({ name }) => {
757
+ const result = await clientProvider().subgraphs.delete(name);
758
+ return { content: [{ type: "text", text: result.message }] };
759
+ });
760
+ 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. Call `subgraphs_reindex` separately if you need a forced reindex.", {
761
+ code: z8.string().describe("TypeScript source code containing a defineSubgraph() call"),
762
+ startBlock: z8.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy"),
763
+ databaseUrl: z8.string().optional().describe("BYO data plane: Postgres connection string to host the subgraph's schema and rows in your own database"),
764
+ dryRun: z8.boolean().optional().describe("Validate and preview the deploy (schema/DDL, BYO connection) without writing changes")
765
+ }, async ({ code, startBlock, databaseUrl, dryRun }) => {
766
+ const bundled = await bundleSubgraphCode(code);
767
+ try {
768
+ const result = await clientProvider().subgraphs.deploy({
769
+ name: bundled.name,
770
+ version: bundled.version,
771
+ description: bundled.description,
772
+ sources: bundled.sources,
773
+ schema: bundled.schema,
774
+ handlerCode: bundled.handlerCode,
775
+ sourceCode: code,
776
+ ...startBlock !== undefined ? { startBlock } : {},
777
+ ...databaseUrl !== undefined ? { databaseUrl } : {},
778
+ ...dryRun !== undefined ? { dryRun } : {}
779
+ });
780
+ return {
781
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
782
+ };
783
+ } catch (err) {
784
+ if (err instanceof ByoBreakingChangeError) {
785
+ return {
786
+ content: [
787
+ {
788
+ type: "text",
789
+ text: JSON.stringify({ byoBreakingChange: true, ...err.details }, null, 2)
790
+ }
791
+ ]
792
+ };
793
+ }
794
+ throw err;
795
+ }
796
+ });
797
+ 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 }) => {
690
798
  const source = await clientProvider().subgraphs.getSource(name);
691
799
  return {
692
800
  content: [{ type: "text", text: JSON.stringify(source, null, 2) }]
693
801
  };
694
802
  });
695
803
  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.", {
696
- code: z7.string().optional().describe("defineSubgraph() source (mutually exclusive with name)"),
697
- name: z7.string().optional().describe("Deployed subgraph name (mutually exclusive with code)"),
698
- target: z7.enum(["prisma", "drizzle", "kysely"]).optional().describe("ORM target (default prisma)"),
699
- schemaName: z7.string().optional().describe("Postgres schema name (defaults to subgraph_<name>)")
804
+ code: z8.string().optional().describe("defineSubgraph() source (mutually exclusive with name)"),
805
+ name: z8.string().optional().describe("Deployed subgraph name (mutually exclusive with code)"),
806
+ target: z8.enum(["prisma", "drizzle", "kysely"]).optional().describe("ORM target (default prisma)"),
807
+ schemaName: z8.string().optional().describe("Postgres schema name (defaults to subgraph_<name>)")
700
808
  }, async ({ code, name, target = "prisma", schemaName }) => {
701
809
  if (code && name || !code && !name) {
702
810
  throw new Error("Provide exactly one of `code` or `name`.");
@@ -725,7 +833,7 @@ function registerSubgraphTools(server, clientProvider = getClient) {
725
833
 
726
834
  // src/tools/subscriptions.ts
727
835
  import { CHAIN_TRIGGER_TYPES } from "@secondlayer/shared";
728
- import { z as z8 } from "zod/v4";
836
+ import { z as z9 } from "zod/v4";
729
837
  function registerSubscriptionTools(server, clientProvider = getClient) {
730
838
  defineTool(server, "subscriptions_list", "List all subscriptions for the current account. Returns summary fields (no secrets).", {}, async () => {
731
839
  const { data } = await clientProvider().subscriptions.list();
@@ -733,34 +841,34 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
733
841
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
734
842
  };
735
843
  });
736
- defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id: z8.string().describe("Subscription id") }, async ({ id }) => {
844
+ defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id: z9.string().describe("Subscription id") }, async ({ id }) => {
737
845
  const detail = await clientProvider().subscriptions.get(id);
738
846
  return {
739
847
  content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
740
848
  };
741
849
  });
742
850
  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.", {
743
- name: z8.string().describe("Human-readable name, unique per account"),
744
- subgraphName: z8.string().optional().describe("Subgraph to subscribe to (subgraph subscription)"),
745
- tableName: z8.string().optional().describe("Table within the subgraph (subgraph subscription)"),
746
- triggers: z8.array(z8.object({
747
- type: z8.enum(CHAIN_TRIGGER_TYPES),
748
- contractId: z8.string().optional(),
749
- functionName: z8.string().optional(),
750
- caller: z8.string().optional(),
751
- sender: z8.string().optional(),
752
- recipient: z8.string().optional(),
753
- assetIdentifier: z8.string().optional(),
754
- deployer: z8.string().optional(),
755
- contractName: z8.string().optional(),
756
- topic: z8.string().optional(),
757
- lockedAddress: z8.string().optional(),
758
- trait: z8.string().optional(),
759
- minAmount: z8.union([z8.string(), z8.number()]).optional(),
760
- maxAmount: z8.union([z8.string(), z8.number()]).optional()
851
+ name: z9.string().describe("Human-readable name, unique per account"),
852
+ subgraphName: z9.string().optional().describe("Subgraph to subscribe to (subgraph subscription)"),
853
+ tableName: z9.string().optional().describe("Table within the subgraph (subgraph subscription)"),
854
+ triggers: z9.array(z9.object({
855
+ type: z9.enum(CHAIN_TRIGGER_TYPES),
856
+ contractId: z9.string().optional(),
857
+ functionName: z9.string().optional(),
858
+ caller: z9.string().optional(),
859
+ sender: z9.string().optional(),
860
+ recipient: z9.string().optional(),
861
+ assetIdentifier: z9.string().optional(),
862
+ deployer: z9.string().optional(),
863
+ contractName: z9.string().optional(),
864
+ topic: z9.string().optional(),
865
+ lockedAddress: z9.string().optional(),
866
+ trait: z9.string().optional(),
867
+ minAmount: z9.union([z9.string(), z9.number()]).optional(),
868
+ maxAmount: z9.union([z9.string(), z9.number()]).optional()
761
869
  })).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. Forward-looking: starts at chain tip, no backfill."),
762
- url: z8.string().describe("Webhook URL"),
763
- format: z8.enum([
870
+ url: z9.string().describe("Webhook URL"),
871
+ format: z9.enum([
764
872
  "standard-webhooks",
765
873
  "inngest",
766
874
  "trigger",
@@ -768,8 +876,8 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
768
876
  "cloudevents",
769
877
  "raw"
770
878
  ]).optional().describe("Wire format (default standard-webhooks)"),
771
- runtime: z8.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
772
- filter: z8.record(z8.string(), z8.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}')
879
+ runtime: z9.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
880
+ filter: z9.record(z9.string(), z9.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}')
773
881
  }, async (input) => {
774
882
  const res = await clientProvider().subscriptions.create(input);
775
883
  return {
@@ -777,10 +885,10 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
777
885
  };
778
886
  });
779
887
  defineTool(server, "subscriptions_update", "Patch a subscription (url, filter, format, runtime, retry, timeout, concurrency).", {
780
- id: z8.string(),
781
- url: z8.string().optional(),
782
- filter: z8.record(z8.string(), z8.unknown()).optional(),
783
- format: z8.enum([
888
+ id: z9.string(),
889
+ url: z9.string().optional(),
890
+ filter: z9.record(z9.string(), z9.unknown()).optional(),
891
+ format: z9.enum([
784
892
  "standard-webhooks",
785
893
  "inngest",
786
894
  "trigger",
@@ -788,50 +896,50 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
788
896
  "cloudevents",
789
897
  "raw"
790
898
  ]).optional(),
791
- runtime: z8.enum(["inngest", "trigger", "cloudflare", "node"]).nullable().optional(),
792
- maxRetries: z8.number().int().min(0).optional(),
793
- timeoutMs: z8.number().int().min(100).optional(),
794
- concurrency: z8.number().int().min(1).optional()
899
+ runtime: z9.enum(["inngest", "trigger", "cloudflare", "node"]).nullable().optional(),
900
+ maxRetries: z9.number().int().min(0).optional(),
901
+ timeoutMs: z9.number().int().min(100).optional(),
902
+ concurrency: z9.number().int().min(1).optional()
795
903
  }, async ({ id, ...patch }) => {
796
904
  const res = await clientProvider().subscriptions.update(id, patch);
797
905
  return {
798
906
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
799
907
  };
800
908
  });
801
- defineTool(server, "subscriptions_pause", "Pause a subscription. Pending rows remain queued until resumed.", { id: z8.string() }, async ({ id }) => {
909
+ defineTool(server, "subscriptions_pause", "Pause a subscription. Pending rows remain queued until resumed.", { id: z9.string() }, async ({ id }) => {
802
910
  const res = await clientProvider().subscriptions.pause(id);
803
911
  return {
804
912
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
805
913
  };
806
914
  });
807
- defineTool(server, "subscriptions_resume", "Resume a paused or circuit-open subscription and reset circuit failures.", { id: z8.string() }, async ({ id }) => {
915
+ defineTool(server, "subscriptions_resume", "Resume a paused or circuit-open subscription and reset circuit failures.", { id: z9.string() }, async ({ id }) => {
808
916
  const res = await clientProvider().subscriptions.resume(id);
809
917
  return {
810
918
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
811
919
  };
812
920
  });
813
- defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id: z8.string() }, async ({ id }) => {
921
+ defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id: z9.string() }, async ({ id }) => {
814
922
  const res = await clientProvider().subscriptions.delete(id);
815
923
  return {
816
924
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
817
925
  };
818
926
  });
819
- defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription signing secret. Returns the new plaintext secret once.", { id: z8.string() }, async ({ id }) => {
927
+ defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription signing secret. Returns the new plaintext secret once.", { id: z9.string() }, async ({ id }) => {
820
928
  const res = await clientProvider().subscriptions.rotateSecret(id);
821
929
  return {
822
930
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
823
931
  };
824
932
  });
825
- 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 }) => {
933
+ 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 }) => {
826
934
  const res = await clientProvider().subscriptions.test(id);
827
935
  return {
828
936
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
829
937
  };
830
938
  });
831
939
  defineTool(server, "subscriptions_replay", "Replay a block range for a subscription. Replays run at 10% of batch capacity — use sparingly.", {
832
- id: z8.string(),
833
- fromBlock: z8.number().int().nonnegative(),
834
- toBlock: z8.number().int().nonnegative()
940
+ id: z9.string(),
941
+ fromBlock: z9.number().int().nonnegative(),
942
+ toBlock: z9.number().int().nonnegative()
835
943
  }, async ({ id, fromBlock, toBlock }) => {
836
944
  const res = await clientProvider().subscriptions.replay(id, {
837
945
  fromBlock,
@@ -841,22 +949,22 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
841
949
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
842
950
  };
843
951
  });
844
- defineTool(server, "subscriptions_dead", "Return the last 100 dead-letter outbox rows for a subscription.", { id: z8.string() }, async ({ id }) => {
952
+ defineTool(server, "subscriptions_dead", "Return the last 100 dead-letter outbox rows for a subscription.", { id: z9.string() }, async ({ id }) => {
845
953
  const { data } = await clientProvider().subscriptions.dead(id);
846
954
  return {
847
955
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
848
956
  };
849
957
  });
850
958
  defineTool(server, "subscriptions_requeue_dead", "Requeue one dead-letter outbox row for delivery retry.", {
851
- id: z8.string(),
852
- outboxId: z8.string()
959
+ id: z9.string(),
960
+ outboxId: z9.string()
853
961
  }, async ({ id, outboxId }) => {
854
962
  const res = await clientProvider().subscriptions.requeueDead(id, outboxId);
855
963
  return {
856
964
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
857
965
  };
858
966
  });
859
- defineTool(server, "subscriptions_recent_deliveries", "Return the last 100 delivery attempts (attempt #, status code, duration, truncated response).", { id: z8.string() }, async ({ id }) => {
967
+ defineTool(server, "subscriptions_recent_deliveries", "Return the last 100 delivery attempts (attempt #, status code, duration, truncated response).", { id: z9.string() }, async ({ id }) => {
860
968
  const { data } = await clientProvider().subscriptions.recentDeliveries(id);
861
969
  return {
862
970
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
@@ -876,6 +984,7 @@ function createServer() {
876
984
  registerSubgraphTools(server);
877
985
  registerSubscriptionTools(server);
878
986
  registerAccountTools(server);
987
+ registerProjectTools(server);
879
988
  registerDatasetTools(server);
880
989
  registerIndexTools(server);
881
990
  registerStreamsTools(server);
@@ -889,5 +998,5 @@ var server = createServer();
889
998
  var transport = new StdioServerTransport;
890
999
  await server.connect(transport);
891
1000
 
892
- //# debugId=43FD93326BB3318D64756E2164756E21
1001
+ //# debugId=95B17975F7D570CF64756E2164756E21
893
1002
  //# sourceMappingURL=bin.js.map