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