@secondlayer/mcp 3.5.0 → 3.7.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
@@ -10,6 +10,8 @@ import { fileURLToPath } from "node:url";
10
10
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
11
 
12
12
  // src/resources.ts
13
+ import { CHAIN_TRIGGER_FIELDS, DECODED_EVENT_TYPES } from "@secondlayer/shared";
14
+ import { TRAIT_STANDARDS } from "@secondlayer/stacks/clarity";
13
15
  import { TYPE_MAP } from "@secondlayer/subgraphs/schema";
14
16
 
15
17
  // src/lib/client.ts
@@ -22,10 +24,12 @@ function getClient() {
22
24
  if (!instance) {
23
25
  const apiKey = readApiKey();
24
26
  const baseUrl = process.env.SECONDLAYER_API_URL;
27
+ const dumpsBaseUrl = process.env.SL_STREAMS_DUMPS_URL;
25
28
  instance = new SecondLayer({
26
29
  ...apiKey ? { apiKey } : {},
27
30
  origin: "mcp",
28
- ...baseUrl ? { baseUrl } : {}
31
+ ...baseUrl ? { baseUrl } : {},
32
+ ...dumpsBaseUrl ? { dumpsBaseUrl } : {}
29
33
  });
30
34
  }
31
35
  return instance;
@@ -104,6 +108,11 @@ function defineTool(server, name, description, schema, handler) {
104
108
  }
105
109
 
106
110
  // src/resources.ts
111
+ var TRAIT_BLURBS = {
112
+ "sip-009": "Non-fungible token (NFT) standard",
113
+ "sip-010": "Fungible token standard",
114
+ "sip-013": "Semi-fungible token standard"
115
+ };
107
116
  var FILTERS_REFERENCE = [
108
117
  {
109
118
  type: "stx_transfer",
@@ -164,7 +173,8 @@ var PRODUCT_BLURBS = {
164
173
  contracts: "trait-based contract discovery",
165
174
  subgraphs: "author/deploy/query custom indexes",
166
175
  subscriptions: "webhook delivery on subgraph rows or raw chain events",
167
- account: "identity, plan, billing, and API keys",
176
+ account: "identity, plan, billing, usage, spend caps, and API keys",
177
+ project: "create/manage projects and view their team",
168
178
  scaffold: "generate typed contract clients from a deployment or ABI"
169
179
  };
170
180
  var PRODUCT_ORDER = [
@@ -175,6 +185,7 @@ var PRODUCT_ORDER = [
175
185
  "subgraphs",
176
186
  "subscriptions",
177
187
  "account",
188
+ "project",
178
189
  "scaffold"
179
190
  ];
180
191
  function buildCapabilities() {
@@ -196,7 +207,7 @@ function buildCapabilities() {
196
207
  });
197
208
  return {
198
209
  products,
199
- discoverFirst: "Call datasets_list / contracts_find to learn what exists before querying."
210
+ discoverFirst: "Call datasets_list / index_discover / contracts_find to learn what exists (and which filters each surface accepts) before querying."
200
211
  };
201
212
  }
202
213
  var READ_AUTH_TIERS = {
@@ -217,6 +228,8 @@ async function buildContext(deps = { clientProvider: getClient }) {
217
228
  indexTip: orNull(snap?.indexTip),
218
229
  subgraphs: snap?.subgraphs ? snap.subgraphs.map(formatSubgraphSummary) : unavailable,
219
230
  subscriptions: orNull(snap?.subscriptions),
231
+ projects: orNull(snap?.projects),
232
+ apiKeys: orNull(snap?.apiKeys),
220
233
  activeOperations: orNull(snap?.activeOperations)
221
234
  },
222
235
  whatYouCanDo: buildCapabilities(),
@@ -253,6 +266,56 @@ function registerResources(server) {
253
266
  }
254
267
  ]
255
268
  }));
269
+ server.resource("traits", "secondlayer://traits", {
270
+ description: "SIP trait standards the platform can classify and scaffold against — the valid values for contracts_find `trait` and subgraph scaffold `--trait`."
271
+ }, async () => ({
272
+ contents: [
273
+ {
274
+ uri: "secondlayer://traits",
275
+ mimeType: "application/json",
276
+ text: JSON.stringify({
277
+ traits: TRAIT_STANDARDS.map((id) => ({
278
+ id,
279
+ description: TRAIT_BLURBS[id]
280
+ }))
281
+ }, null, 2)
282
+ }
283
+ ]
284
+ }));
285
+ server.resource("streams-filters", "secondlayer://streams-filters", {
286
+ description: "Streams firehose vocabulary — the decoded event types and the filter fields accepted by streams_events / streams_consume."
287
+ }, async () => ({
288
+ contents: [
289
+ {
290
+ uri: "secondlayer://streams-filters",
291
+ mimeType: "application/json",
292
+ text: JSON.stringify({
293
+ eventTypes: [...DECODED_EVENT_TYPES],
294
+ filters: {
295
+ types: "include only these event types (array)",
296
+ notTypes: "exclude these event types (array)",
297
+ contractId: "match a contract id",
298
+ sender: "match the sender principal",
299
+ recipient: "match the recipient principal",
300
+ assetIdentifier: "match the asset identifier (contract::asset)",
301
+ fromHeight: "start block height (inclusive)",
302
+ toHeight: "end block height (inclusive)"
303
+ }
304
+ }, null, 2)
305
+ }
306
+ ]
307
+ }));
308
+ server.resource("chain-triggers", "secondlayer://chain-triggers", {
309
+ description: "Chain-subscription trigger types and the filter fields each accepts (for subscriptions_create triggers)."
310
+ }, async () => ({
311
+ contents: [
312
+ {
313
+ uri: "secondlayer://chain-triggers",
314
+ mimeType: "application/json",
315
+ text: JSON.stringify(CHAIN_TRIGGER_FIELDS, null, 2)
316
+ }
317
+ ]
318
+ }));
256
319
  }
257
320
 
258
321
  // src/tools/account.ts
@@ -277,7 +340,7 @@ function registerAccountTools(server) {
277
340
  const result = await apiRequest("PATCH", "/api/accounts/me", body);
278
341
  return jsonResponse(result);
279
342
  });
280
- defineTool(server, "account_billing", "Show the account's plan and subscription/billing status. Requires an API key.", {}, async () => {
343
+ defineTool(server, "account_billing", "Show the account's plan and subscription/billing status. Requires an API key. (Plan upgrade / Stripe portal / checkout are deliberately not agent tools — they are session-only human-payment flows; use account_set_caps to bound spend.)", {}, async () => {
281
344
  const result = await apiRequest("GET", "/api/billing/status");
282
345
  return jsonResponse(result);
283
346
  });
@@ -285,6 +348,23 @@ function registerAccountTools(server) {
285
348
  product: z.enum(["streams", "index"]).optional().describe("Key scope (default streams)"),
286
349
  name: z.string().optional().describe("Optional label for the key")
287
350
  }, async ({ product, name }) => jsonResponse(await getClient().apiKeys.create({ product, name })));
351
+ 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()));
352
+ 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)));
353
+ 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")));
354
+ 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")));
355
+ 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.", {
356
+ monthlyCapCents: z.number().int().nonnegative().optional().describe("Total monthly spend cap in cents"),
357
+ computeCapCents: z.number().int().nonnegative().optional().describe("Compute spend cap in cents"),
358
+ storageCapCents: z.number().int().nonnegative().optional().describe("Storage spend cap in cents"),
359
+ alertThresholdPct: z.number().int().min(1).max(100).optional().describe("Percent of cap that triggers an alert (1-100)")
360
+ }, async (caps) => {
361
+ const body = {};
362
+ for (const [k, v] of Object.entries(caps)) {
363
+ if (v !== undefined)
364
+ body[k] = v;
365
+ }
366
+ return jsonResponse(await apiRequest("PATCH", "/api/billing/caps", body));
367
+ });
288
368
  }
289
369
 
290
370
  // src/tools/contracts.ts
@@ -306,7 +386,7 @@ function registerDatasetTools(server, clientProvider = getClient) {
306
386
  const catalog = await clientProvider().datasets.listDatasets();
307
387
  return jsonResponse(catalog);
308
388
  });
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.', {
389
+ 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
390
  slug: z3.string().describe("Dataset slug from datasets_list (e.g. stx-transfers)"),
311
391
  filters: z3.record(z3.string(), z3.string()).optional().describe("Documented per-dataset query params (snake_case values)"),
312
392
  limit: z3.number().optional().describe("Max rows for this page"),
@@ -317,19 +397,19 @@ function registerDatasetTools(server, clientProvider = getClient) {
317
397
  params.limit = limit;
318
398
  if (cursor !== undefined)
319
399
  params.cursor = cursor;
320
- const result = await clientProvider().datasets.query(slug, params);
400
+ const result = await clientProvider().datasets.get(slug, params);
321
401
  return jsonResponse(result);
322
402
  });
323
403
  }
324
404
 
325
405
  // src/tools/index.ts
326
- import { DECODED_EVENT_TYPES } from "@secondlayer/shared";
406
+ import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
327
407
  import {
328
- generateIndexSchema,
329
- INDEX_CODEGEN_TABLES
408
+ INDEX_CODEGEN_TABLES,
409
+ generateIndexSchema
330
410
  } from "@secondlayer/subgraphs";
331
411
  import { z as z4 } from "zod/v4";
332
- var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES;
412
+ var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES2;
333
413
  var rangeFilters = {
334
414
  contractId: z4.string().optional().describe("Filter by contract id"),
335
415
  fromHeight: z4.number().optional().describe("Start block height (inclusive)"),
@@ -356,17 +436,19 @@ function registerIndexTools(server, clientProvider = getClient) {
356
436
  recipient: z4.string().optional().describe("Filter by recipient principal"),
357
437
  assetIdentifier: z4.string().optional().describe("Filter by asset identifier (contract::asset)")
358
438
  }, async (params) => jsonResponse(await clientProvider().index.nftTransfers.list(params)));
359
- defineTool(server, "index_events", "List decoded chain events from the Index by event type. Use this for event types without a dedicated tool (stx_*, ft_mint/burn, nft_mint/burn, print). For ft/nft transfers prefer index_ft_transfers / index_nft_transfers.", {
439
+ defineTool(server, "index_events", "List decoded chain events from the Index by event type. Use this for event types without a dedicated tool (stx_*, ft_mint/burn, nft_mint/burn, print), and for trait-scoped queries: set `trait` (e.g. sip-010) to match all contracts conforming to a standard — pair with contracts_find to discover traits. For ft/nft transfers without a trait prefer index_ft_transfers / index_nft_transfers.", {
360
440
  eventType: z4.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
361
441
  ...rangeFilters,
362
442
  sender: z4.string().optional().describe("Filter by sender principal"),
363
443
  recipient: z4.string().optional().describe("Filter by recipient principal"),
364
- assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable")
444
+ assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable"),
445
+ trait: z4.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId; contract-keyed event types only.")
365
446
  }, async (params) => jsonResponse(await clientProvider().index.events.list(params)));
366
- defineTool(server, "index_contract_calls", "List decoded contract calls from the Index (function name, args, result). Note: contract-call cursors are a SEPARATE keyspace from event cursors — they are not interchangeable.", {
447
+ defineTool(server, "index_contract_calls", "List decoded contract calls from the Index (function name, args, result). Set `trait` (e.g. sip-010) to match calls to all contracts conforming to a standard. Note: contract-call cursors are a SEPARATE keyspace from event cursors — they are not interchangeable.", {
367
448
  ...rangeFilters,
368
449
  functionName: z4.string().optional().describe("Filter by called function name"),
369
- sender: z4.string().optional().describe("Filter by caller principal")
450
+ sender: z4.string().optional().describe("Filter by caller principal"),
451
+ trait: z4.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
370
452
  }, async (params) => jsonResponse(await clientProvider().index.contractCalls.list(params)));
371
453
  defineTool(server, "index_canonical", "List the canonical Stacks block sequence from the Index (height + hash). Anonymous reads allowed (free-tier keys rejected).", { ...heightFilters }, async (params) => jsonResponse(await clientProvider().index.canonical.list(params)));
372
454
  defineTool(server, "index_blocks", "List decoded blocks from the Index. Anonymous reads allowed (free-tier keys rejected).", { ...heightFilters }, async (params) => jsonResponse(await clientProvider().index.blocks.list(params)));
@@ -402,6 +484,7 @@ function registerIndexTools(server, clientProvider = getClient) {
402
484
  const tx = await clientProvider().index.mempool.get(txId);
403
485
  return tx ? jsonResponse(tx) : notFound(`No pending tx for ${txId}`);
404
486
  });
487
+ defineTool(server, "index_discover", "Discover the Index vocabulary: every event type and its columns, allowed/equality filters, and required-non-null fields (and which types accept `trait`). Read this before building Index queries instead of guessing filters. Anonymous read.", {}, async () => jsonResponse(await clientProvider().index.discover()));
405
488
  defineTool(server, "index_usage", "Your own Index consumption (decoded events today + this month) and tier limits. Requires a Build+ API key (anonymous reads can't report usage).", {}, async () => jsonResponse(await clientProvider().index.usage()));
406
489
  defineTool(server, "index_codegen", `Generate a typed schema (Prisma, Kysely, Drizzle, or JSON-Schema) for the public Index domain tables so they can be mirrored into a BYO database with full types. Returns the schema as text. Tables: ${INDEX_CODEGEN_TABLES.join(", ")}.`, {
407
490
  target: z4.enum(["prisma", "kysely", "drizzle", "json-schema"]).optional().describe("Output target (default kysely)"),
@@ -413,9 +496,43 @@ function registerIndexTools(server, clientProvider = getClient) {
413
496
  });
414
497
  }
415
498
 
499
+ // src/tools/projects.ts
500
+ import { z as z5 } from "zod/v4";
501
+ function registerProjectTools(server, clientProvider = getClient) {
502
+ defineTool(server, "project_list", "List the account's projects. Requires an account-level (owner) API key.", {}, async () => jsonResponse(await clientProvider().projects.list()));
503
+ defineTool(server, "project_get", "Get a single project by slug.", { slug: z5.string().describe("Project slug") }, async ({ slug }) => jsonResponse(await clientProvider().projects.get(slug)));
504
+ defineTool(server, "project_create", "Create a project. The slug is derived from the name when omitted. Requires an account-level (owner) API key.", {
505
+ name: z5.string().describe("Project name"),
506
+ slug: z5.string().optional().describe("URL slug (auto-derived from name if omitted)"),
507
+ network: z5.string().optional().describe("Network (e.g. mainnet, testnet)"),
508
+ nodeRpc: z5.string().optional().describe("Custom node RPC URL")
509
+ }, async ({ name, slug, network, nodeRpc }) => jsonResponse(await clientProvider().projects.create({
510
+ name,
511
+ slug,
512
+ network,
513
+ nodeRpc
514
+ })));
515
+ defineTool(server, "project_update", "Update a project. `slug` selects the project; `newSlug` renames it. Requires an account-level (owner) API key.", {
516
+ slug: z5.string().describe("Slug of the project to update"),
517
+ newSlug: z5.string().optional().describe("New slug (renames the project)"),
518
+ name: z5.string().optional().describe("New display name"),
519
+ network: z5.string().optional().describe("Network (e.g. mainnet, testnet)"),
520
+ nodeRpc: z5.string().optional().describe("Custom node RPC URL"),
521
+ settings: z5.record(z5.string(), z5.unknown()).optional().describe("Arbitrary project settings to merge")
522
+ }, async ({ slug, newSlug, name, network, nodeRpc, settings }) => jsonResponse(await clientProvider().projects.update(slug, {
523
+ ...newSlug !== undefined ? { slug: newSlug } : {},
524
+ ...name !== undefined ? { name } : {},
525
+ ...network !== undefined ? { network } : {},
526
+ ...nodeRpc !== undefined ? { nodeRpc } : {},
527
+ ...settings !== undefined ? { settings } : {}
528
+ })));
529
+ 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)));
530
+ 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)));
531
+ }
532
+
416
533
  // src/tools/scaffold.ts
417
534
  import { generateSubgraphCode } from "@secondlayer/scaffold";
418
- import { z as z5 } from "zod/v4";
535
+ import { z as z6 } from "zod/v4";
419
536
  var API_BASE = process.env.SECONDLAYER_API_URL || "https://api.secondlayer.tools";
420
537
  async function fetchAbi(contractId) {
421
538
  const res = await fetch(`${API_BASE}/api/node/contracts/${contractId}/abi`, {
@@ -434,17 +551,17 @@ async function fetchAbi(contractId) {
434
551
  }
435
552
  function registerScaffoldTools(server) {
436
553
  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)")
554
+ contractId: z6.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
555
+ subgraphName: z6.string().optional().describe("Override the subgraph name (defaults to contract name)")
439
556
  }, async ({ contractId, subgraphName }) => {
440
557
  const { functions, maps } = await fetchAbi(contractId);
441
558
  const code = generateSubgraphCode(contractId, functions, subgraphName, maps);
442
559
  return { content: [{ type: "text", text: code }] };
443
560
  });
444
561
  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")
562
+ abi: z6.string().describe("ABI JSON string (the full contract ABI object)"),
563
+ contractId: z6.string().describe("Fully qualified contract ID"),
564
+ subgraphName: z6.string().optional().describe("Override the subgraph name")
448
565
  }, async ({ abi, contractId, subgraphName }) => {
449
566
  let parsed;
450
567
  try {
@@ -462,9 +579,9 @@ function registerScaffoldTools(server) {
462
579
 
463
580
  // src/tools/streams.ts
464
581
  import { AuthError } from "@secondlayer/sdk";
465
- import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
466
- import { z as z6 } from "zod/v4";
467
- var STREAMS_EVENT_TYPES = DECODED_EVENT_TYPES2;
582
+ import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES3 } from "@secondlayer/shared";
583
+ import { z as z7 } from "zod/v4";
584
+ var STREAMS_EVENT_TYPES = DECODED_EVENT_TYPES3;
468
585
  async function withStreamsAuthHint(fn) {
469
586
  try {
470
587
  return await fn();
@@ -478,28 +595,28 @@ async function withStreamsAuthHint(fn) {
478
595
  function registerStreamsTools(server, clientProvider = getClient) {
479
596
  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
597
  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")
598
+ types: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
599
+ notTypes: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude (applied after types)"),
600
+ contractId: z7.string().optional().describe("Filter by contract id"),
601
+ sender: z7.string().optional().describe("Filter by sender principal"),
602
+ recipient: z7.string().optional().describe("Filter by recipient principal"),
603
+ assetIdentifier: z7.string().optional().describe("Filter by asset identifier"),
604
+ fromHeight: z7.number().optional().describe("Start block height (inclusive)"),
605
+ toHeight: z7.number().optional().describe("End block height (inclusive)"),
606
+ cursor: z7.string().optional().describe("Opaque cursor from a prior response"),
607
+ limit: z7.number().optional().describe("Max events for this page")
491
608
  }, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.events.list(params))));
492
609
  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")
610
+ fromCursor: z7.string().optional().describe("Resume cursor from a prior consume (omit to start at genesis)"),
611
+ types: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
612
+ notTypes: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude"),
613
+ contractId: z7.string().optional().describe("Filter by contract id"),
614
+ sender: z7.string().optional().describe("Filter by sender principal"),
615
+ recipient: z7.string().optional().describe("Filter by recipient principal"),
616
+ assetIdentifier: z7.string().optional().describe("Filter by asset identifier"),
617
+ batchSize: z7.number().optional().describe("Events per page (1–1000, default 100)"),
618
+ maxPages: z7.number().optional().describe("Max pages to walk this call (1–20, default 5)"),
619
+ finalizedOnly: z7.boolean().optional().describe("Only emit finalized events; skip reorg handling")
503
620
  }, async (params) => withStreamsAuthHint(async () => {
504
621
  const maxPages = Math.min(Math.max(params.maxPages ?? 5, 1), 20);
505
622
  const batchSize = Math.min(Math.max(params.batchSize ?? 100, 1), 1000);
@@ -531,26 +648,28 @@ function registerStreamsTools(server, clientProvider = getClient) {
531
648
  pages: result.pages
532
649
  });
533
650
  }));
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))));
651
+ 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
652
  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)")
653
+ heightOrHash: z7.string().describe("Block height (digits) or block hash (0x… string)")
537
654
  }, async ({ heightOrHash }) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.blocks.events(/^\d+$/.test(heightOrHash) ? Number(heightOrHash) : heightOrHash))));
538
655
  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")
656
+ since: z7.string().describe("Cursor to list reorgs since (block:index or ISO timestamp)"),
657
+ limit: z7.number().optional().describe("Max reorgs to return")
541
658
  }, 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))));
659
+ 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
660
  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())));
661
+ defineTool(server, "streams_dumps", "List the Streams bulk parquet dumps manifest — coverage range, latest_finalized_cursor, and per-file metadata (block range, row count, size, sha256, signed URL). This is the cold backfill path for downloading all raw data; fetch the file URLs directly (e.g. with DuckDB). Requires the dumps base URL to be configured (SL_STREAMS_DUMPS_URL).", {}, async () => jsonResponse(await clientProvider().streams.dumps.list()));
544
662
  }
545
663
 
546
664
  // src/tools/subgraphs.ts
547
665
  import { bundleSubgraphCode } from "@secondlayer/bundler";
666
+ import { ByoBreakingChangeError } from "@secondlayer/sdk";
548
667
  import {
549
668
  generateDrizzleSchema,
550
669
  generateKyselySchema,
551
670
  generatePrismaSchema
552
671
  } from "@secondlayer/subgraphs";
553
- import { z as z7 } from "zod/v4";
672
+ import { z as z8 } from "zod/v4";
554
673
  function registerSubgraphTools(server, clientProvider = getClient) {
555
674
  defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only.", {}, async () => {
556
675
  const { data } = await clientProvider().subgraphs.list();
@@ -563,16 +682,16 @@ function registerSubgraphTools(server, clientProvider = getClient) {
563
682
  ]
564
683
  };
565
684
  });
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 }) => {
685
+ 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
686
  const detail = await clientProvider().subgraphs.get(name);
568
687
  return {
569
688
  content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
570
689
  };
571
690
  });
572
691
  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.")
692
+ name: z8.string().describe("Subgraph name"),
693
+ format: z8.enum(["agent", "openapi", "markdown"]).optional().describe("Spec format to return. Defaults to agent."),
694
+ serverUrl: z8.string().optional().describe("Override the server URL embedded in generated docs.")
576
695
  }, async ({ name, format = "agent", serverUrl }) => {
577
696
  const options = serverUrl ? { serverUrl } : undefined;
578
697
  const spec = format === "openapi" ? await clientProvider().subgraphs.openapi(name, options) : format === "markdown" ? await clientProvider().subgraphs.markdown(name, options) : await clientProvider().subgraphs.schema(name, options);
@@ -585,16 +704,16 @@ function registerSubgraphTools(server, clientProvider = getClient) {
585
704
  ]
586
705
  };
587
706
  });
588
- 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")
707
+ defineTool(server, "subgraphs_query", 'Query rows from a subgraph table (max 200 rows). Filters support operators: "amount.gte": "1000", "sender.neq": "SP...", "name.like": "%token%". Available operators: eq, neq, gt, gte, lt, lte, like. To TAIL new rows (no streaming over MCP): sort=_id, order=desc for the latest, then poll forward with the filter {"_id.gt": "<last _id seen>"}, order=asc. Fetch one row by id with {"_id": "<id>"}.', {
708
+ name: z8.string().describe("Subgraph name"),
709
+ table: z8.string().describe("Table name"),
710
+ filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
711
+ sort: z8.string().optional().describe("Column to sort by"),
712
+ order: z8.enum(["asc", "desc"]).optional().describe("Sort order"),
713
+ limit: z8.number().max(200).optional().describe("Max rows (default 50, max 200)"),
714
+ offset: z8.number().optional().describe("Offset for pagination"),
715
+ fields: z8.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
716
+ count: z8.boolean().optional().describe("If true, return row count instead of rows")
598
717
  }, async ({
599
718
  name,
600
719
  table,
@@ -627,14 +746,14 @@ function registerSubgraphTools(server, clientProvider = getClient) {
627
746
  };
628
747
  });
629
748
  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")
749
+ name: z8.string().describe("Subgraph name"),
750
+ table: z8.string().describe("Table name"),
751
+ filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
752
+ count: z8.boolean().optional().describe("Include COUNT(*) of matching rows"),
753
+ countDistinct: z8.array(z8.string()).optional().describe("Columns to count distinct values of"),
754
+ sum: z8.array(z8.string()).optional().describe("Numeric columns to sum (lossless string result)"),
755
+ min: z8.array(z8.string()).optional().describe("Numeric columns to take the minimum of"),
756
+ max: z8.array(z8.string()).optional().describe("Numeric columns to take the maximum of")
638
757
  }, async ({ name, table, filters, count, countDistinct, sum, min, max }) => {
639
758
  const result = await clientProvider().subgraphs.queryTableAggregate(name, table, { filters, count, countDistinct, sum, min, max });
640
759
  return {
@@ -642,9 +761,9 @@ function registerSubgraphTools(server, clientProvider = getClient) {
642
761
  };
643
762
  });
644
763
  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)")
764
+ name: z8.string().describe("Subgraph name"),
765
+ fromBlock: z8.number().optional().describe("Start block (defaults to beginning)"),
766
+ toBlock: z8.number().optional().describe("End block (defaults to latest)")
648
767
  }, async ({ name, fromBlock, toBlock }) => {
649
768
  const result = await clientProvider().subgraphs.reindex(name, {
650
769
  fromBlock,
@@ -655,48 +774,100 @@ function registerSubgraphTools(server, clientProvider = getClient) {
655
774
  };
656
775
  });
657
776
  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")
777
+ name: z8.string().describe("Subgraph name"),
778
+ operationId: z8.string().optional().describe("Operation id from reindex/backfill/stop; omit to list recent operations")
660
779
  }, async ({ name, operationId }) => {
661
780
  const result = operationId ? await clientProvider().subgraphs.getOperation(name, operationId) : await clientProvider().subgraphs.operations(name);
662
781
  return {
663
782
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
664
783
  };
665
784
  });
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 }] };
785
+ 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.", {
786
+ name: z8.string().describe("Subgraph name"),
787
+ fromBlock: z8.number().int().nonnegative().describe("Start block (inclusive)"),
788
+ toBlock: z8.number().int().nonnegative().describe("End block (inclusive)")
789
+ }, async ({ name, fromBlock, toBlock }) => {
790
+ const result = await clientProvider().subgraphs.backfill(name, {
791
+ fromBlock,
792
+ toBlock
793
+ });
794
+ return {
795
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
796
+ };
669
797
  });
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 } : {}
798
+ 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 }) => {
799
+ const result = await clientProvider().subgraphs.stop(name);
800
+ return {
801
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
802
+ };
803
+ });
804
+ 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.", {
805
+ name: z8.string().describe("Subgraph name"),
806
+ limit: z8.number().int().positive().optional().describe("Max gaps to return"),
807
+ offset: z8.number().int().nonnegative().optional().describe("Pagination offset"),
808
+ resolved: z8.boolean().optional().describe("Filter by resolved state (omit for unresolved only)")
809
+ }, async ({ name, limit, offset, resolved }) => {
810
+ const result = await clientProvider().subgraphs.gaps(name, {
811
+ limit,
812
+ offset,
813
+ resolved
684
814
  });
685
815
  return {
686
816
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
687
817
  };
688
818
  });
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 }) => {
819
+ defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z8.string().describe("Subgraph name") }, async ({ name }) => {
820
+ const result = await clientProvider().subgraphs.delete(name);
821
+ return { content: [{ type: "text", text: result.message }] };
822
+ });
823
+ 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.", {
824
+ code: z8.string().describe("TypeScript source code containing a defineSubgraph() call"),
825
+ startBlock: z8.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy"),
826
+ databaseUrl: z8.string().optional().describe("BYO data plane: Postgres connection string to host the subgraph's schema and rows in your own database"),
827
+ dryRun: z8.boolean().optional().describe("Validate and preview the deploy (schema/DDL, BYO connection) without writing changes")
828
+ }, async ({ code, startBlock, databaseUrl, dryRun }) => {
829
+ const bundled = await bundleSubgraphCode(code);
830
+ try {
831
+ const result = await clientProvider().subgraphs.deploy({
832
+ name: bundled.name,
833
+ version: bundled.version,
834
+ description: bundled.description,
835
+ sources: bundled.sources,
836
+ schema: bundled.schema,
837
+ handlerCode: bundled.handlerCode,
838
+ sourceCode: code,
839
+ ...startBlock !== undefined ? { startBlock } : {},
840
+ ...databaseUrl !== undefined ? { databaseUrl } : {},
841
+ ...dryRun !== undefined ? { dryRun } : {}
842
+ });
843
+ return {
844
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
845
+ };
846
+ } catch (err) {
847
+ if (err instanceof ByoBreakingChangeError) {
848
+ return {
849
+ content: [
850
+ {
851
+ type: "text",
852
+ text: JSON.stringify({ byoBreakingChange: true, ...err.details }, null, 2)
853
+ }
854
+ ]
855
+ };
856
+ }
857
+ throw err;
858
+ }
859
+ });
860
+ 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
861
  const source = await clientProvider().subgraphs.getSource(name);
691
862
  return {
692
863
  content: [{ type: "text", text: JSON.stringify(source, null, 2) }]
693
864
  };
694
865
  });
695
866
  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>)")
867
+ code: z8.string().optional().describe("defineSubgraph() source (mutually exclusive with name)"),
868
+ name: z8.string().optional().describe("Deployed subgraph name (mutually exclusive with code)"),
869
+ target: z8.enum(["prisma", "drizzle", "kysely"]).optional().describe("ORM target (default prisma)"),
870
+ schemaName: z8.string().optional().describe("Postgres schema name (defaults to subgraph_<name>)")
700
871
  }, async ({ code, name, target = "prisma", schemaName }) => {
701
872
  if (code && name || !code && !name) {
702
873
  throw new Error("Provide exactly one of `code` or `name`.");
@@ -725,7 +896,7 @@ function registerSubgraphTools(server, clientProvider = getClient) {
725
896
 
726
897
  // src/tools/subscriptions.ts
727
898
  import { CHAIN_TRIGGER_TYPES } from "@secondlayer/shared";
728
- import { z as z8 } from "zod/v4";
899
+ import { z as z9 } from "zod/v4";
729
900
  function registerSubscriptionTools(server, clientProvider = getClient) {
730
901
  defineTool(server, "subscriptions_list", "List all subscriptions for the current account. Returns summary fields (no secrets).", {}, async () => {
731
902
  const { data } = await clientProvider().subscriptions.list();
@@ -733,34 +904,34 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
733
904
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
734
905
  };
735
906
  });
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 }) => {
907
+ 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
908
  const detail = await clientProvider().subscriptions.get(id);
738
909
  return {
739
910
  content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
740
911
  };
741
912
  });
742
913
  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()
761
- })).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([
914
+ name: z9.string().describe("Human-readable name, unique per account"),
915
+ subgraphName: z9.string().optional().describe("Subgraph to subscribe to (subgraph subscription)"),
916
+ tableName: z9.string().optional().describe("Table within the subgraph (subgraph subscription)"),
917
+ triggers: z9.array(z9.object({
918
+ type: z9.enum(CHAIN_TRIGGER_TYPES),
919
+ contractId: z9.string().optional(),
920
+ functionName: z9.string().optional(),
921
+ caller: z9.string().optional(),
922
+ sender: z9.string().optional(),
923
+ recipient: z9.string().optional(),
924
+ assetIdentifier: z9.string().optional(),
925
+ deployer: z9.string().optional(),
926
+ contractName: z9.string().optional(),
927
+ topic: z9.string().optional(),
928
+ lockedAddress: z9.string().optional(),
929
+ trait: z9.string().optional(),
930
+ minAmount: z9.union([z9.string(), z9.number()]).optional(),
931
+ maxAmount: z9.union([z9.string(), z9.number()]).optional()
932
+ })).optional().describe("Chain triggers (chain subscription) — provide INSTEAD of subgraphName/tableName. Each targets a raw chain event/tx; string fields accept `*` wildcards, `trait` scopes to a SIP/trait. Per-type accepted fields: see the secondlayer://chain-triggers resource. Forward-looking: starts at chain tip, no backfill."),
933
+ url: z9.string().describe("Webhook URL"),
934
+ format: z9.enum([
764
935
  "standard-webhooks",
765
936
  "inngest",
766
937
  "trigger",
@@ -768,19 +939,22 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
768
939
  "cloudevents",
769
940
  "raw"
770
941
  ]).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..."}')
942
+ runtime: z9.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
943
+ filter: z9.record(z9.string(), z9.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}'),
944
+ authConfig: z9.record(z9.string(), z9.unknown()).optional().describe('Receiver auth sent with each delivery, e.g. {"type": "bearer", "token": "..."}')
773
945
  }, async (input) => {
774
946
  const res = await clientProvider().subscriptions.create(input);
775
947
  return {
776
948
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
777
949
  };
778
950
  });
779
- 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([
951
+ defineTool(server, "subscriptions_update", "Patch a subscription (name, url, filter, authConfig, format, runtime, retry, timeout, concurrency).", {
952
+ id: z9.string(),
953
+ name: z9.string().optional().describe("Rename the subscription"),
954
+ url: z9.string().optional(),
955
+ filter: z9.record(z9.string(), z9.unknown()).optional(),
956
+ authConfig: z9.record(z9.string(), z9.unknown()).optional().describe("Receiver auth sent with each delivery (bearer/etc.)"),
957
+ format: z9.enum([
784
958
  "standard-webhooks",
785
959
  "inngest",
786
960
  "trigger",
@@ -788,75 +962,77 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
788
962
  "cloudevents",
789
963
  "raw"
790
964
  ]).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()
965
+ runtime: z9.enum(["inngest", "trigger", "cloudflare", "node"]).nullable().optional(),
966
+ maxRetries: z9.number().int().min(0).optional(),
967
+ timeoutMs: z9.number().int().min(100).optional(),
968
+ concurrency: z9.number().int().min(1).optional()
795
969
  }, async ({ id, ...patch }) => {
796
970
  const res = await clientProvider().subscriptions.update(id, patch);
797
971
  return {
798
972
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
799
973
  };
800
974
  });
801
- defineTool(server, "subscriptions_pause", "Pause a subscription. Pending rows remain queued until resumed.", { id: z8.string() }, async ({ id }) => {
975
+ defineTool(server, "subscriptions_pause", "Pause a subscription. Pending rows remain queued until resumed.", { id: z9.string() }, async ({ id }) => {
802
976
  const res = await clientProvider().subscriptions.pause(id);
803
977
  return {
804
978
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
805
979
  };
806
980
  });
807
- defineTool(server, "subscriptions_resume", "Resume a paused or circuit-open subscription and reset circuit failures.", { id: z8.string() }, async ({ id }) => {
981
+ defineTool(server, "subscriptions_resume", "Resume a paused or circuit-open subscription and reset circuit failures.", { id: z9.string() }, async ({ id }) => {
808
982
  const res = await clientProvider().subscriptions.resume(id);
809
983
  return {
810
984
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
811
985
  };
812
986
  });
813
- defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id: z8.string() }, async ({ id }) => {
987
+ defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id: z9.string() }, async ({ id }) => {
814
988
  const res = await clientProvider().subscriptions.delete(id);
815
989
  return {
816
990
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
817
991
  };
818
992
  });
819
- defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription signing secret. Returns the new plaintext secret once.", { id: z8.string() }, async ({ id }) => {
993
+ defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription signing secret. Returns the new plaintext secret once.", { id: z9.string() }, async ({ id }) => {
820
994
  const res = await clientProvider().subscriptions.rotateSecret(id);
821
995
  return {
822
996
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
823
997
  };
824
998
  });
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 }) => {
999
+ 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
1000
  const res = await clientProvider().subscriptions.test(id);
827
1001
  return {
828
1002
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
829
1003
  };
830
1004
  });
831
- 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()
835
- }, async ({ id, fromBlock, toBlock }) => {
1005
+ defineTool(server, "subscriptions_replay", "Replay a block range for a subscription. Replays run at 10% of batch capacity — use sparingly. Pass `force` (a short idempotency suffix) to re-run a range that was already replayed.", {
1006
+ id: z9.string(),
1007
+ fromBlock: z9.number().int().nonnegative(),
1008
+ toBlock: z9.number().int().nonnegative(),
1009
+ force: z9.string().optional().describe("Idempotency suffix to force a duplicate replay of the range")
1010
+ }, async ({ id, fromBlock, toBlock, force }) => {
836
1011
  const res = await clientProvider().subscriptions.replay(id, {
837
1012
  fromBlock,
838
- toBlock
1013
+ toBlock,
1014
+ ...force !== undefined ? { force } : {}
839
1015
  });
840
1016
  return {
841
1017
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
842
1018
  };
843
1019
  });
844
- defineTool(server, "subscriptions_dead", "Return the last 100 dead-letter outbox rows for a subscription.", { id: z8.string() }, async ({ id }) => {
1020
+ defineTool(server, "subscriptions_dead", "Return the last 100 dead-letter outbox rows for a subscription.", { id: z9.string() }, async ({ id }) => {
845
1021
  const { data } = await clientProvider().subscriptions.dead(id);
846
1022
  return {
847
1023
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
848
1024
  };
849
1025
  });
850
1026
  defineTool(server, "subscriptions_requeue_dead", "Requeue one dead-letter outbox row for delivery retry.", {
851
- id: z8.string(),
852
- outboxId: z8.string()
1027
+ id: z9.string(),
1028
+ outboxId: z9.string()
853
1029
  }, async ({ id, outboxId }) => {
854
1030
  const res = await clientProvider().subscriptions.requeueDead(id, outboxId);
855
1031
  return {
856
1032
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
857
1033
  };
858
1034
  });
859
- defineTool(server, "subscriptions_recent_deliveries", "Return the last 100 delivery attempts (attempt #, status code, duration, truncated response).", { id: z8.string() }, async ({ id }) => {
1035
+ defineTool(server, "subscriptions_recent_deliveries", "Return the last 100 delivery attempts (attempt #, status code, duration, truncated response).", { id: z9.string() }, async ({ id }) => {
860
1036
  const { data } = await clientProvider().subscriptions.recentDeliveries(id);
861
1037
  return {
862
1038
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
@@ -876,6 +1052,7 @@ function createServer() {
876
1052
  registerSubgraphTools(server);
877
1053
  registerSubscriptionTools(server);
878
1054
  registerAccountTools(server);
1055
+ registerProjectTools(server);
879
1056
  registerDatasetTools(server);
880
1057
  registerIndexTools(server);
881
1058
  registerStreamsTools(server);
@@ -889,5 +1066,5 @@ var server = createServer();
889
1066
  var transport = new StdioServerTransport;
890
1067
  await server.connect(transport);
891
1068
 
892
- //# debugId=43FD93326BB3318D64756E2164756E21
1069
+ //# debugId=A418D04474BCA8ED64756E2164756E21
893
1070
  //# sourceMappingURL=bin.js.map