@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-http.js CHANGED
@@ -13,6 +13,8 @@ import { fileURLToPath } from "node:url";
13
13
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
14
 
15
15
  // src/resources.ts
16
+ import { CHAIN_TRIGGER_FIELDS, DECODED_EVENT_TYPES } from "@secondlayer/shared";
17
+ import { TRAIT_STANDARDS } from "@secondlayer/stacks/clarity";
16
18
  import { TYPE_MAP } from "@secondlayer/subgraphs/schema";
17
19
 
18
20
  // src/lib/client.ts
@@ -25,10 +27,12 @@ function getClient() {
25
27
  if (!instance) {
26
28
  const apiKey = readApiKey();
27
29
  const baseUrl = process.env.SECONDLAYER_API_URL;
30
+ const dumpsBaseUrl = process.env.SL_STREAMS_DUMPS_URL;
28
31
  instance = new SecondLayer({
29
32
  ...apiKey ? { apiKey } : {},
30
33
  origin: "mcp",
31
- ...baseUrl ? { baseUrl } : {}
34
+ ...baseUrl ? { baseUrl } : {},
35
+ ...dumpsBaseUrl ? { dumpsBaseUrl } : {}
32
36
  });
33
37
  }
34
38
  return instance;
@@ -107,6 +111,11 @@ function defineTool(server, name, description, schema, handler) {
107
111
  }
108
112
 
109
113
  // src/resources.ts
114
+ var TRAIT_BLURBS = {
115
+ "sip-009": "Non-fungible token (NFT) standard",
116
+ "sip-010": "Fungible token standard",
117
+ "sip-013": "Semi-fungible token standard"
118
+ };
110
119
  var FILTERS_REFERENCE = [
111
120
  {
112
121
  type: "stx_transfer",
@@ -167,7 +176,8 @@ var PRODUCT_BLURBS = {
167
176
  contracts: "trait-based contract discovery",
168
177
  subgraphs: "author/deploy/query custom indexes",
169
178
  subscriptions: "webhook delivery on subgraph rows or raw chain events",
170
- account: "identity, plan, billing, and API keys",
179
+ account: "identity, plan, billing, usage, spend caps, and API keys",
180
+ project: "create/manage projects and view their team",
171
181
  scaffold: "generate typed contract clients from a deployment or ABI"
172
182
  };
173
183
  var PRODUCT_ORDER = [
@@ -178,6 +188,7 @@ var PRODUCT_ORDER = [
178
188
  "subgraphs",
179
189
  "subscriptions",
180
190
  "account",
191
+ "project",
181
192
  "scaffold"
182
193
  ];
183
194
  function buildCapabilities() {
@@ -199,7 +210,7 @@ function buildCapabilities() {
199
210
  });
200
211
  return {
201
212
  products,
202
- discoverFirst: "Call datasets_list / contracts_find to learn what exists before querying."
213
+ discoverFirst: "Call datasets_list / index_discover / contracts_find to learn what exists (and which filters each surface accepts) before querying."
203
214
  };
204
215
  }
205
216
  var READ_AUTH_TIERS = {
@@ -220,6 +231,8 @@ async function buildContext(deps = { clientProvider: getClient }) {
220
231
  indexTip: orNull(snap?.indexTip),
221
232
  subgraphs: snap?.subgraphs ? snap.subgraphs.map(formatSubgraphSummary) : unavailable,
222
233
  subscriptions: orNull(snap?.subscriptions),
234
+ projects: orNull(snap?.projects),
235
+ apiKeys: orNull(snap?.apiKeys),
223
236
  activeOperations: orNull(snap?.activeOperations)
224
237
  },
225
238
  whatYouCanDo: buildCapabilities(),
@@ -256,6 +269,56 @@ function registerResources(server) {
256
269
  }
257
270
  ]
258
271
  }));
272
+ server.resource("traits", "secondlayer://traits", {
273
+ description: "SIP trait standards the platform can classify and scaffold against — the valid values for contracts_find `trait` and subgraph scaffold `--trait`."
274
+ }, async () => ({
275
+ contents: [
276
+ {
277
+ uri: "secondlayer://traits",
278
+ mimeType: "application/json",
279
+ text: JSON.stringify({
280
+ traits: TRAIT_STANDARDS.map((id) => ({
281
+ id,
282
+ description: TRAIT_BLURBS[id]
283
+ }))
284
+ }, null, 2)
285
+ }
286
+ ]
287
+ }));
288
+ server.resource("streams-filters", "secondlayer://streams-filters", {
289
+ description: "Streams firehose vocabulary — the decoded event types and the filter fields accepted by streams_events / streams_consume."
290
+ }, async () => ({
291
+ contents: [
292
+ {
293
+ uri: "secondlayer://streams-filters",
294
+ mimeType: "application/json",
295
+ text: JSON.stringify({
296
+ eventTypes: [...DECODED_EVENT_TYPES],
297
+ filters: {
298
+ types: "include only these event types (array)",
299
+ notTypes: "exclude these event types (array)",
300
+ contractId: "match a contract id",
301
+ sender: "match the sender principal",
302
+ recipient: "match the recipient principal",
303
+ assetIdentifier: "match the asset identifier (contract::asset)",
304
+ fromHeight: "start block height (inclusive)",
305
+ toHeight: "end block height (inclusive)"
306
+ }
307
+ }, null, 2)
308
+ }
309
+ ]
310
+ }));
311
+ server.resource("chain-triggers", "secondlayer://chain-triggers", {
312
+ description: "Chain-subscription trigger types and the filter fields each accepts (for subscriptions_create triggers)."
313
+ }, async () => ({
314
+ contents: [
315
+ {
316
+ uri: "secondlayer://chain-triggers",
317
+ mimeType: "application/json",
318
+ text: JSON.stringify(CHAIN_TRIGGER_FIELDS, null, 2)
319
+ }
320
+ ]
321
+ }));
259
322
  }
260
323
 
261
324
  // src/tools/account.ts
@@ -280,7 +343,7 @@ function registerAccountTools(server) {
280
343
  const result = await apiRequest("PATCH", "/api/accounts/me", body);
281
344
  return jsonResponse(result);
282
345
  });
283
- defineTool(server, "account_billing", "Show the account's plan and subscription/billing status. Requires an API key.", {}, async () => {
346
+ 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 () => {
284
347
  const result = await apiRequest("GET", "/api/billing/status");
285
348
  return jsonResponse(result);
286
349
  });
@@ -288,6 +351,23 @@ function registerAccountTools(server) {
288
351
  product: z.enum(["streams", "index"]).optional().describe("Key scope (default streams)"),
289
352
  name: z.string().optional().describe("Optional label for the key")
290
353
  }, async ({ product, name }) => jsonResponse(await getClient().apiKeys.create({ product, name })));
354
+ 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()));
355
+ 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)));
356
+ 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")));
357
+ 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")));
358
+ 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.", {
359
+ monthlyCapCents: z.number().int().nonnegative().optional().describe("Total monthly spend cap in cents"),
360
+ computeCapCents: z.number().int().nonnegative().optional().describe("Compute spend cap in cents"),
361
+ storageCapCents: z.number().int().nonnegative().optional().describe("Storage spend cap in cents"),
362
+ alertThresholdPct: z.number().int().min(1).max(100).optional().describe("Percent of cap that triggers an alert (1-100)")
363
+ }, async (caps) => {
364
+ const body = {};
365
+ for (const [k, v] of Object.entries(caps)) {
366
+ if (v !== undefined)
367
+ body[k] = v;
368
+ }
369
+ return jsonResponse(await apiRequest("PATCH", "/api/billing/caps", body));
370
+ });
291
371
  }
292
372
 
293
373
  // src/tools/contracts.ts
@@ -309,7 +389,7 @@ function registerDatasetTools(server, clientProvider = getClient) {
309
389
  const catalog = await clientProvider().datasets.listDatasets();
310
390
  return jsonResponse(catalog);
311
391
  });
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.', {
392
+ 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
393
  slug: z3.string().describe("Dataset slug from datasets_list (e.g. stx-transfers)"),
314
394
  filters: z3.record(z3.string(), z3.string()).optional().describe("Documented per-dataset query params (snake_case values)"),
315
395
  limit: z3.number().optional().describe("Max rows for this page"),
@@ -320,19 +400,19 @@ function registerDatasetTools(server, clientProvider = getClient) {
320
400
  params.limit = limit;
321
401
  if (cursor !== undefined)
322
402
  params.cursor = cursor;
323
- const result = await clientProvider().datasets.query(slug, params);
403
+ const result = await clientProvider().datasets.get(slug, params);
324
404
  return jsonResponse(result);
325
405
  });
326
406
  }
327
407
 
328
408
  // src/tools/index.ts
329
- import { DECODED_EVENT_TYPES } from "@secondlayer/shared";
409
+ import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
330
410
  import {
331
- generateIndexSchema,
332
- INDEX_CODEGEN_TABLES
411
+ INDEX_CODEGEN_TABLES,
412
+ generateIndexSchema
333
413
  } from "@secondlayer/subgraphs";
334
414
  import { z as z4 } from "zod/v4";
335
- var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES;
415
+ var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES2;
336
416
  var rangeFilters = {
337
417
  contractId: z4.string().optional().describe("Filter by contract id"),
338
418
  fromHeight: z4.number().optional().describe("Start block height (inclusive)"),
@@ -359,17 +439,19 @@ function registerIndexTools(server, clientProvider = getClient) {
359
439
  recipient: z4.string().optional().describe("Filter by recipient principal"),
360
440
  assetIdentifier: z4.string().optional().describe("Filter by asset identifier (contract::asset)")
361
441
  }, async (params) => jsonResponse(await clientProvider().index.nftTransfers.list(params)));
362
- 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.", {
442
+ 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.", {
363
443
  eventType: z4.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
364
444
  ...rangeFilters,
365
445
  sender: z4.string().optional().describe("Filter by sender principal"),
366
446
  recipient: z4.string().optional().describe("Filter by recipient principal"),
367
- assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable")
447
+ assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable"),
448
+ 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.")
368
449
  }, async (params) => jsonResponse(await clientProvider().index.events.list(params)));
369
- 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.", {
450
+ 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.", {
370
451
  ...rangeFilters,
371
452
  functionName: z4.string().optional().describe("Filter by called function name"),
372
- sender: z4.string().optional().describe("Filter by caller principal")
453
+ sender: z4.string().optional().describe("Filter by caller principal"),
454
+ trait: z4.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
373
455
  }, async (params) => jsonResponse(await clientProvider().index.contractCalls.list(params)));
374
456
  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)));
375
457
  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)));
@@ -405,6 +487,7 @@ function registerIndexTools(server, clientProvider = getClient) {
405
487
  const tx = await clientProvider().index.mempool.get(txId);
406
488
  return tx ? jsonResponse(tx) : notFound(`No pending tx for ${txId}`);
407
489
  });
490
+ 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()));
408
491
  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()));
409
492
  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(", ")}.`, {
410
493
  target: z4.enum(["prisma", "kysely", "drizzle", "json-schema"]).optional().describe("Output target (default kysely)"),
@@ -416,9 +499,43 @@ function registerIndexTools(server, clientProvider = getClient) {
416
499
  });
417
500
  }
418
501
 
502
+ // src/tools/projects.ts
503
+ import { z as z5 } from "zod/v4";
504
+ function registerProjectTools(server, clientProvider = getClient) {
505
+ defineTool(server, "project_list", "List the account's projects. Requires an account-level (owner) API key.", {}, async () => jsonResponse(await clientProvider().projects.list()));
506
+ defineTool(server, "project_get", "Get a single project by slug.", { slug: z5.string().describe("Project slug") }, async ({ slug }) => jsonResponse(await clientProvider().projects.get(slug)));
507
+ defineTool(server, "project_create", "Create a project. The slug is derived from the name when omitted. Requires an account-level (owner) API key.", {
508
+ name: z5.string().describe("Project name"),
509
+ slug: z5.string().optional().describe("URL slug (auto-derived from name if omitted)"),
510
+ network: z5.string().optional().describe("Network (e.g. mainnet, testnet)"),
511
+ nodeRpc: z5.string().optional().describe("Custom node RPC URL")
512
+ }, async ({ name, slug, network, nodeRpc }) => jsonResponse(await clientProvider().projects.create({
513
+ name,
514
+ slug,
515
+ network,
516
+ nodeRpc
517
+ })));
518
+ defineTool(server, "project_update", "Update a project. `slug` selects the project; `newSlug` renames it. Requires an account-level (owner) API key.", {
519
+ slug: z5.string().describe("Slug of the project to update"),
520
+ newSlug: z5.string().optional().describe("New slug (renames the project)"),
521
+ name: z5.string().optional().describe("New display name"),
522
+ network: z5.string().optional().describe("Network (e.g. mainnet, testnet)"),
523
+ nodeRpc: z5.string().optional().describe("Custom node RPC URL"),
524
+ settings: z5.record(z5.string(), z5.unknown()).optional().describe("Arbitrary project settings to merge")
525
+ }, async ({ slug, newSlug, name, network, nodeRpc, settings }) => jsonResponse(await clientProvider().projects.update(slug, {
526
+ ...newSlug !== undefined ? { slug: newSlug } : {},
527
+ ...name !== undefined ? { name } : {},
528
+ ...network !== undefined ? { network } : {},
529
+ ...nodeRpc !== undefined ? { nodeRpc } : {},
530
+ ...settings !== undefined ? { settings } : {}
531
+ })));
532
+ 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)));
533
+ 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)));
534
+ }
535
+
419
536
  // src/tools/scaffold.ts
420
537
  import { generateSubgraphCode } from "@secondlayer/scaffold";
421
- import { z as z5 } from "zod/v4";
538
+ import { z as z6 } from "zod/v4";
422
539
  var API_BASE = process.env.SECONDLAYER_API_URL || "https://api.secondlayer.tools";
423
540
  async function fetchAbi(contractId) {
424
541
  const res = await fetch(`${API_BASE}/api/node/contracts/${contractId}/abi`, {
@@ -437,17 +554,17 @@ async function fetchAbi(contractId) {
437
554
  }
438
555
  function registerScaffoldTools(server) {
439
556
  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)")
557
+ contractId: z6.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
558
+ subgraphName: z6.string().optional().describe("Override the subgraph name (defaults to contract name)")
442
559
  }, async ({ contractId, subgraphName }) => {
443
560
  const { functions, maps } = await fetchAbi(contractId);
444
561
  const code = generateSubgraphCode(contractId, functions, subgraphName, maps);
445
562
  return { content: [{ type: "text", text: code }] };
446
563
  });
447
564
  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")
565
+ abi: z6.string().describe("ABI JSON string (the full contract ABI object)"),
566
+ contractId: z6.string().describe("Fully qualified contract ID"),
567
+ subgraphName: z6.string().optional().describe("Override the subgraph name")
451
568
  }, async ({ abi, contractId, subgraphName }) => {
452
569
  let parsed;
453
570
  try {
@@ -465,9 +582,9 @@ function registerScaffoldTools(server) {
465
582
 
466
583
  // src/tools/streams.ts
467
584
  import { AuthError } from "@secondlayer/sdk";
468
- import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
469
- import { z as z6 } from "zod/v4";
470
- var STREAMS_EVENT_TYPES = DECODED_EVENT_TYPES2;
585
+ import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES3 } from "@secondlayer/shared";
586
+ import { z as z7 } from "zod/v4";
587
+ var STREAMS_EVENT_TYPES = DECODED_EVENT_TYPES3;
471
588
  async function withStreamsAuthHint(fn) {
472
589
  try {
473
590
  return await fn();
@@ -481,28 +598,28 @@ async function withStreamsAuthHint(fn) {
481
598
  function registerStreamsTools(server, clientProvider = getClient) {
482
599
  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
600
  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")
601
+ types: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
602
+ notTypes: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude (applied after types)"),
603
+ contractId: z7.string().optional().describe("Filter by contract id"),
604
+ sender: z7.string().optional().describe("Filter by sender principal"),
605
+ recipient: z7.string().optional().describe("Filter by recipient principal"),
606
+ assetIdentifier: z7.string().optional().describe("Filter by asset identifier"),
607
+ fromHeight: z7.number().optional().describe("Start block height (inclusive)"),
608
+ toHeight: z7.number().optional().describe("End block height (inclusive)"),
609
+ cursor: z7.string().optional().describe("Opaque cursor from a prior response"),
610
+ limit: z7.number().optional().describe("Max events for this page")
494
611
  }, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.events.list(params))));
495
612
  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")
613
+ fromCursor: z7.string().optional().describe("Resume cursor from a prior consume (omit to start at genesis)"),
614
+ types: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
615
+ notTypes: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude"),
616
+ contractId: z7.string().optional().describe("Filter by contract id"),
617
+ sender: z7.string().optional().describe("Filter by sender principal"),
618
+ recipient: z7.string().optional().describe("Filter by recipient principal"),
619
+ assetIdentifier: z7.string().optional().describe("Filter by asset identifier"),
620
+ batchSize: z7.number().optional().describe("Events per page (1–1000, default 100)"),
621
+ maxPages: z7.number().optional().describe("Max pages to walk this call (1–20, default 5)"),
622
+ finalizedOnly: z7.boolean().optional().describe("Only emit finalized events; skip reorg handling")
506
623
  }, async (params) => withStreamsAuthHint(async () => {
507
624
  const maxPages = Math.min(Math.max(params.maxPages ?? 5, 1), 20);
508
625
  const batchSize = Math.min(Math.max(params.batchSize ?? 100, 1), 1000);
@@ -534,26 +651,28 @@ function registerStreamsTools(server, clientProvider = getClient) {
534
651
  pages: result.pages
535
652
  });
536
653
  }));
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))));
654
+ 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
655
  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)")
656
+ heightOrHash: z7.string().describe("Block height (digits) or block hash (0x… string)")
540
657
  }, async ({ heightOrHash }) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.blocks.events(/^\d+$/.test(heightOrHash) ? Number(heightOrHash) : heightOrHash))));
541
658
  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")
659
+ since: z7.string().describe("Cursor to list reorgs since (block:index or ISO timestamp)"),
660
+ limit: z7.number().optional().describe("Max reorgs to return")
544
661
  }, 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))));
662
+ 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
663
  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())));
664
+ 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()));
547
665
  }
548
666
 
549
667
  // src/tools/subgraphs.ts
550
668
  import { bundleSubgraphCode } from "@secondlayer/bundler";
669
+ import { ByoBreakingChangeError } from "@secondlayer/sdk";
551
670
  import {
552
671
  generateDrizzleSchema,
553
672
  generateKyselySchema,
554
673
  generatePrismaSchema
555
674
  } from "@secondlayer/subgraphs";
556
- import { z as z7 } from "zod/v4";
675
+ import { z as z8 } from "zod/v4";
557
676
  function registerSubgraphTools(server, clientProvider = getClient) {
558
677
  defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only.", {}, async () => {
559
678
  const { data } = await clientProvider().subgraphs.list();
@@ -566,16 +685,16 @@ function registerSubgraphTools(server, clientProvider = getClient) {
566
685
  ]
567
686
  };
568
687
  });
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 }) => {
688
+ 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
689
  const detail = await clientProvider().subgraphs.get(name);
571
690
  return {
572
691
  content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
573
692
  };
574
693
  });
575
694
  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.")
695
+ name: z8.string().describe("Subgraph name"),
696
+ format: z8.enum(["agent", "openapi", "markdown"]).optional().describe("Spec format to return. Defaults to agent."),
697
+ serverUrl: z8.string().optional().describe("Override the server URL embedded in generated docs.")
579
698
  }, async ({ name, format = "agent", serverUrl }) => {
580
699
  const options = serverUrl ? { serverUrl } : undefined;
581
700
  const spec = format === "openapi" ? await clientProvider().subgraphs.openapi(name, options) : format === "markdown" ? await clientProvider().subgraphs.markdown(name, options) : await clientProvider().subgraphs.schema(name, options);
@@ -588,16 +707,16 @@ function registerSubgraphTools(server, clientProvider = getClient) {
588
707
  ]
589
708
  };
590
709
  });
591
- 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")
710
+ 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>"}.', {
711
+ name: z8.string().describe("Subgraph name"),
712
+ table: z8.string().describe("Table name"),
713
+ filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
714
+ sort: z8.string().optional().describe("Column to sort by"),
715
+ order: z8.enum(["asc", "desc"]).optional().describe("Sort order"),
716
+ limit: z8.number().max(200).optional().describe("Max rows (default 50, max 200)"),
717
+ offset: z8.number().optional().describe("Offset for pagination"),
718
+ fields: z8.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
719
+ count: z8.boolean().optional().describe("If true, return row count instead of rows")
601
720
  }, async ({
602
721
  name,
603
722
  table,
@@ -630,14 +749,14 @@ function registerSubgraphTools(server, clientProvider = getClient) {
630
749
  };
631
750
  });
632
751
  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.`, {
633
- name: z7.string().describe("Subgraph name"),
634
- table: z7.string().describe("Table name"),
635
- filters: z7.record(z7.string(), z7.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
636
- count: z7.boolean().optional().describe("Include COUNT(*) of matching rows"),
637
- countDistinct: z7.array(z7.string()).optional().describe("Columns to count distinct values of"),
638
- sum: z7.array(z7.string()).optional().describe("Numeric columns to sum (lossless string result)"),
639
- min: z7.array(z7.string()).optional().describe("Numeric columns to take the minimum of"),
640
- max: z7.array(z7.string()).optional().describe("Numeric columns to take the maximum of")
752
+ name: z8.string().describe("Subgraph name"),
753
+ table: z8.string().describe("Table name"),
754
+ filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
755
+ count: z8.boolean().optional().describe("Include COUNT(*) of matching rows"),
756
+ countDistinct: z8.array(z8.string()).optional().describe("Columns to count distinct values of"),
757
+ sum: z8.array(z8.string()).optional().describe("Numeric columns to sum (lossless string result)"),
758
+ min: z8.array(z8.string()).optional().describe("Numeric columns to take the minimum of"),
759
+ max: z8.array(z8.string()).optional().describe("Numeric columns to take the maximum of")
641
760
  }, async ({ name, table, filters, count, countDistinct, sum, min, max }) => {
642
761
  const result = await clientProvider().subgraphs.queryTableAggregate(name, table, { filters, count, countDistinct, sum, min, max });
643
762
  return {
@@ -645,9 +764,9 @@ function registerSubgraphTools(server, clientProvider = getClient) {
645
764
  };
646
765
  });
647
766
  defineTool(server, "subgraphs_reindex", "Reindex a subgraph from a specific block range. Returns an operationId — poll subgraphs_operation to track progress to completion.", {
648
- name: z7.string().describe("Subgraph name"),
649
- fromBlock: z7.number().optional().describe("Start block (defaults to beginning)"),
650
- toBlock: z7.number().optional().describe("End block (defaults to latest)")
767
+ name: z8.string().describe("Subgraph name"),
768
+ fromBlock: z8.number().optional().describe("Start block (defaults to beginning)"),
769
+ toBlock: z8.number().optional().describe("End block (defaults to latest)")
651
770
  }, async ({ name, fromBlock, toBlock }) => {
652
771
  const result = await clientProvider().subgraphs.reindex(name, {
653
772
  fromBlock,
@@ -658,48 +777,100 @@ function registerSubgraphTools(server, clientProvider = getClient) {
658
777
  };
659
778
  });
660
779
  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.", {
661
- name: z7.string().describe("Subgraph name"),
662
- operationId: z7.string().optional().describe("Operation id from reindex/backfill/stop; omit to list recent operations")
780
+ name: z8.string().describe("Subgraph name"),
781
+ operationId: z8.string().optional().describe("Operation id from reindex/backfill/stop; omit to list recent operations")
663
782
  }, async ({ name, operationId }) => {
664
783
  const result = operationId ? await clientProvider().subgraphs.getOperation(name, operationId) : await clientProvider().subgraphs.operations(name);
665
784
  return {
666
785
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
667
786
  };
668
787
  });
669
- defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
670
- const result = await clientProvider().subgraphs.delete(name);
671
- return { content: [{ type: "text", text: result.message }] };
788
+ 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.", {
789
+ name: z8.string().describe("Subgraph name"),
790
+ fromBlock: z8.number().int().nonnegative().describe("Start block (inclusive)"),
791
+ toBlock: z8.number().int().nonnegative().describe("End block (inclusive)")
792
+ }, async ({ name, fromBlock, toBlock }) => {
793
+ const result = await clientProvider().subgraphs.backfill(name, {
794
+ fromBlock,
795
+ toBlock
796
+ });
797
+ return {
798
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
799
+ };
672
800
  });
673
- 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.", {
674
- code: z7.string().describe("TypeScript source code containing a defineSubgraph() call"),
675
- startBlock: z7.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy")
676
- }, async ({ code, startBlock }) => {
677
- const bundled = await bundleSubgraphCode(code);
678
- const result = await clientProvider().subgraphs.deploy({
679
- name: bundled.name,
680
- version: bundled.version,
681
- description: bundled.description,
682
- sources: bundled.sources,
683
- schema: bundled.schema,
684
- handlerCode: bundled.handlerCode,
685
- sourceCode: code,
686
- ...startBlock !== undefined ? { startBlock } : {}
801
+ 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 }) => {
802
+ const result = await clientProvider().subgraphs.stop(name);
803
+ return {
804
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
805
+ };
806
+ });
807
+ 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.", {
808
+ name: z8.string().describe("Subgraph name"),
809
+ limit: z8.number().int().positive().optional().describe("Max gaps to return"),
810
+ offset: z8.number().int().nonnegative().optional().describe("Pagination offset"),
811
+ resolved: z8.boolean().optional().describe("Filter by resolved state (omit for unresolved only)")
812
+ }, async ({ name, limit, offset, resolved }) => {
813
+ const result = await clientProvider().subgraphs.gaps(name, {
814
+ limit,
815
+ offset,
816
+ resolved
687
817
  });
688
818
  return {
689
819
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
690
820
  };
691
821
  });
692
- 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 }) => {
822
+ defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z8.string().describe("Subgraph name") }, async ({ name }) => {
823
+ const result = await clientProvider().subgraphs.delete(name);
824
+ return { content: [{ type: "text", text: result.message }] };
825
+ });
826
+ 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.", {
827
+ code: z8.string().describe("TypeScript source code containing a defineSubgraph() call"),
828
+ startBlock: z8.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy"),
829
+ databaseUrl: z8.string().optional().describe("BYO data plane: Postgres connection string to host the subgraph's schema and rows in your own database"),
830
+ dryRun: z8.boolean().optional().describe("Validate and preview the deploy (schema/DDL, BYO connection) without writing changes")
831
+ }, async ({ code, startBlock, databaseUrl, dryRun }) => {
832
+ const bundled = await bundleSubgraphCode(code);
833
+ try {
834
+ const result = await clientProvider().subgraphs.deploy({
835
+ name: bundled.name,
836
+ version: bundled.version,
837
+ description: bundled.description,
838
+ sources: bundled.sources,
839
+ schema: bundled.schema,
840
+ handlerCode: bundled.handlerCode,
841
+ sourceCode: code,
842
+ ...startBlock !== undefined ? { startBlock } : {},
843
+ ...databaseUrl !== undefined ? { databaseUrl } : {},
844
+ ...dryRun !== undefined ? { dryRun } : {}
845
+ });
846
+ return {
847
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
848
+ };
849
+ } catch (err) {
850
+ if (err instanceof ByoBreakingChangeError) {
851
+ return {
852
+ content: [
853
+ {
854
+ type: "text",
855
+ text: JSON.stringify({ byoBreakingChange: true, ...err.details }, null, 2)
856
+ }
857
+ ]
858
+ };
859
+ }
860
+ throw err;
861
+ }
862
+ });
863
+ 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 }) => {
693
864
  const source = await clientProvider().subgraphs.getSource(name);
694
865
  return {
695
866
  content: [{ type: "text", text: JSON.stringify(source, null, 2) }]
696
867
  };
697
868
  });
698
869
  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.", {
699
- code: z7.string().optional().describe("defineSubgraph() source (mutually exclusive with name)"),
700
- name: z7.string().optional().describe("Deployed subgraph name (mutually exclusive with code)"),
701
- target: z7.enum(["prisma", "drizzle", "kysely"]).optional().describe("ORM target (default prisma)"),
702
- schemaName: z7.string().optional().describe("Postgres schema name (defaults to subgraph_<name>)")
870
+ code: z8.string().optional().describe("defineSubgraph() source (mutually exclusive with name)"),
871
+ name: z8.string().optional().describe("Deployed subgraph name (mutually exclusive with code)"),
872
+ target: z8.enum(["prisma", "drizzle", "kysely"]).optional().describe("ORM target (default prisma)"),
873
+ schemaName: z8.string().optional().describe("Postgres schema name (defaults to subgraph_<name>)")
703
874
  }, async ({ code, name, target = "prisma", schemaName }) => {
704
875
  if (code && name || !code && !name) {
705
876
  throw new Error("Provide exactly one of `code` or `name`.");
@@ -728,7 +899,7 @@ function registerSubgraphTools(server, clientProvider = getClient) {
728
899
 
729
900
  // src/tools/subscriptions.ts
730
901
  import { CHAIN_TRIGGER_TYPES } from "@secondlayer/shared";
731
- import { z as z8 } from "zod/v4";
902
+ import { z as z9 } from "zod/v4";
732
903
  function registerSubscriptionTools(server, clientProvider = getClient) {
733
904
  defineTool(server, "subscriptions_list", "List all subscriptions for the current account. Returns summary fields (no secrets).", {}, async () => {
734
905
  const { data } = await clientProvider().subscriptions.list();
@@ -736,34 +907,34 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
736
907
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
737
908
  };
738
909
  });
739
- defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id: z8.string().describe("Subscription id") }, async ({ id }) => {
910
+ defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id: z9.string().describe("Subscription id") }, async ({ id }) => {
740
911
  const detail = await clientProvider().subscriptions.get(id);
741
912
  return {
742
913
  content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
743
914
  };
744
915
  });
745
916
  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.", {
746
- name: z8.string().describe("Human-readable name, unique per account"),
747
- subgraphName: z8.string().optional().describe("Subgraph to subscribe to (subgraph subscription)"),
748
- tableName: z8.string().optional().describe("Table within the subgraph (subgraph subscription)"),
749
- triggers: z8.array(z8.object({
750
- type: z8.enum(CHAIN_TRIGGER_TYPES),
751
- contractId: z8.string().optional(),
752
- functionName: z8.string().optional(),
753
- caller: z8.string().optional(),
754
- sender: z8.string().optional(),
755
- recipient: z8.string().optional(),
756
- assetIdentifier: z8.string().optional(),
757
- deployer: z8.string().optional(),
758
- contractName: z8.string().optional(),
759
- topic: z8.string().optional(),
760
- lockedAddress: z8.string().optional(),
761
- trait: z8.string().optional(),
762
- minAmount: z8.union([z8.string(), z8.number()]).optional(),
763
- maxAmount: z8.union([z8.string(), z8.number()]).optional()
764
- })).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."),
765
- url: z8.string().describe("Webhook URL"),
766
- format: z8.enum([
917
+ name: z9.string().describe("Human-readable name, unique per account"),
918
+ subgraphName: z9.string().optional().describe("Subgraph to subscribe to (subgraph subscription)"),
919
+ tableName: z9.string().optional().describe("Table within the subgraph (subgraph subscription)"),
920
+ triggers: z9.array(z9.object({
921
+ type: z9.enum(CHAIN_TRIGGER_TYPES),
922
+ contractId: z9.string().optional(),
923
+ functionName: z9.string().optional(),
924
+ caller: z9.string().optional(),
925
+ sender: z9.string().optional(),
926
+ recipient: z9.string().optional(),
927
+ assetIdentifier: z9.string().optional(),
928
+ deployer: z9.string().optional(),
929
+ contractName: z9.string().optional(),
930
+ topic: z9.string().optional(),
931
+ lockedAddress: z9.string().optional(),
932
+ trait: z9.string().optional(),
933
+ minAmount: z9.union([z9.string(), z9.number()]).optional(),
934
+ maxAmount: z9.union([z9.string(), z9.number()]).optional()
935
+ })).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."),
936
+ url: z9.string().describe("Webhook URL"),
937
+ format: z9.enum([
767
938
  "standard-webhooks",
768
939
  "inngest",
769
940
  "trigger",
@@ -771,19 +942,22 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
771
942
  "cloudevents",
772
943
  "raw"
773
944
  ]).optional().describe("Wire format (default standard-webhooks)"),
774
- runtime: z8.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
775
- filter: z8.record(z8.string(), z8.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}')
945
+ runtime: z9.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
946
+ filter: z9.record(z9.string(), z9.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}'),
947
+ authConfig: z9.record(z9.string(), z9.unknown()).optional().describe('Receiver auth sent with each delivery, e.g. {"type": "bearer", "token": "..."}')
776
948
  }, async (input) => {
777
949
  const res = await clientProvider().subscriptions.create(input);
778
950
  return {
779
951
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
780
952
  };
781
953
  });
782
- defineTool(server, "subscriptions_update", "Patch a subscription (url, filter, format, runtime, retry, timeout, concurrency).", {
783
- id: z8.string(),
784
- url: z8.string().optional(),
785
- filter: z8.record(z8.string(), z8.unknown()).optional(),
786
- format: z8.enum([
954
+ defineTool(server, "subscriptions_update", "Patch a subscription (name, url, filter, authConfig, format, runtime, retry, timeout, concurrency).", {
955
+ id: z9.string(),
956
+ name: z9.string().optional().describe("Rename the subscription"),
957
+ url: z9.string().optional(),
958
+ filter: z9.record(z9.string(), z9.unknown()).optional(),
959
+ authConfig: z9.record(z9.string(), z9.unknown()).optional().describe("Receiver auth sent with each delivery (bearer/etc.)"),
960
+ format: z9.enum([
787
961
  "standard-webhooks",
788
962
  "inngest",
789
963
  "trigger",
@@ -791,75 +965,77 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
791
965
  "cloudevents",
792
966
  "raw"
793
967
  ]).optional(),
794
- runtime: z8.enum(["inngest", "trigger", "cloudflare", "node"]).nullable().optional(),
795
- maxRetries: z8.number().int().min(0).optional(),
796
- timeoutMs: z8.number().int().min(100).optional(),
797
- concurrency: z8.number().int().min(1).optional()
968
+ runtime: z9.enum(["inngest", "trigger", "cloudflare", "node"]).nullable().optional(),
969
+ maxRetries: z9.number().int().min(0).optional(),
970
+ timeoutMs: z9.number().int().min(100).optional(),
971
+ concurrency: z9.number().int().min(1).optional()
798
972
  }, async ({ id, ...patch }) => {
799
973
  const res = await clientProvider().subscriptions.update(id, patch);
800
974
  return {
801
975
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
802
976
  };
803
977
  });
804
- defineTool(server, "subscriptions_pause", "Pause a subscription. Pending rows remain queued until resumed.", { id: z8.string() }, async ({ id }) => {
978
+ defineTool(server, "subscriptions_pause", "Pause a subscription. Pending rows remain queued until resumed.", { id: z9.string() }, async ({ id }) => {
805
979
  const res = await clientProvider().subscriptions.pause(id);
806
980
  return {
807
981
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
808
982
  };
809
983
  });
810
- defineTool(server, "subscriptions_resume", "Resume a paused or circuit-open subscription and reset circuit failures.", { id: z8.string() }, async ({ id }) => {
984
+ defineTool(server, "subscriptions_resume", "Resume a paused or circuit-open subscription and reset circuit failures.", { id: z9.string() }, async ({ id }) => {
811
985
  const res = await clientProvider().subscriptions.resume(id);
812
986
  return {
813
987
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
814
988
  };
815
989
  });
816
- defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id: z8.string() }, async ({ id }) => {
990
+ defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id: z9.string() }, async ({ id }) => {
817
991
  const res = await clientProvider().subscriptions.delete(id);
818
992
  return {
819
993
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
820
994
  };
821
995
  });
822
- defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription signing secret. Returns the new plaintext secret once.", { id: z8.string() }, async ({ id }) => {
996
+ defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription signing secret. Returns the new plaintext secret once.", { id: z9.string() }, async ({ id }) => {
823
997
  const res = await clientProvider().subscriptions.rotateSecret(id);
824
998
  return {
825
999
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
826
1000
  };
827
1001
  });
828
- 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 }) => {
1002
+ 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 }) => {
829
1003
  const res = await clientProvider().subscriptions.test(id);
830
1004
  return {
831
1005
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
832
1006
  };
833
1007
  });
834
- defineTool(server, "subscriptions_replay", "Replay a block range for a subscription. Replays run at 10% of batch capacity — use sparingly.", {
835
- id: z8.string(),
836
- fromBlock: z8.number().int().nonnegative(),
837
- toBlock: z8.number().int().nonnegative()
838
- }, async ({ id, fromBlock, toBlock }) => {
1008
+ 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.", {
1009
+ id: z9.string(),
1010
+ fromBlock: z9.number().int().nonnegative(),
1011
+ toBlock: z9.number().int().nonnegative(),
1012
+ force: z9.string().optional().describe("Idempotency suffix to force a duplicate replay of the range")
1013
+ }, async ({ id, fromBlock, toBlock, force }) => {
839
1014
  const res = await clientProvider().subscriptions.replay(id, {
840
1015
  fromBlock,
841
- toBlock
1016
+ toBlock,
1017
+ ...force !== undefined ? { force } : {}
842
1018
  });
843
1019
  return {
844
1020
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
845
1021
  };
846
1022
  });
847
- defineTool(server, "subscriptions_dead", "Return the last 100 dead-letter outbox rows for a subscription.", { id: z8.string() }, async ({ id }) => {
1023
+ defineTool(server, "subscriptions_dead", "Return the last 100 dead-letter outbox rows for a subscription.", { id: z9.string() }, async ({ id }) => {
848
1024
  const { data } = await clientProvider().subscriptions.dead(id);
849
1025
  return {
850
1026
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
851
1027
  };
852
1028
  });
853
1029
  defineTool(server, "subscriptions_requeue_dead", "Requeue one dead-letter outbox row for delivery retry.", {
854
- id: z8.string(),
855
- outboxId: z8.string()
1030
+ id: z9.string(),
1031
+ outboxId: z9.string()
856
1032
  }, async ({ id, outboxId }) => {
857
1033
  const res = await clientProvider().subscriptions.requeueDead(id, outboxId);
858
1034
  return {
859
1035
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
860
1036
  };
861
1037
  });
862
- defineTool(server, "subscriptions_recent_deliveries", "Return the last 100 delivery attempts (attempt #, status code, duration, truncated response).", { id: z8.string() }, async ({ id }) => {
1038
+ defineTool(server, "subscriptions_recent_deliveries", "Return the last 100 delivery attempts (attempt #, status code, duration, truncated response).", { id: z9.string() }, async ({ id }) => {
863
1039
  const { data } = await clientProvider().subscriptions.recentDeliveries(id);
864
1040
  return {
865
1041
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
@@ -879,6 +1055,7 @@ function createServer() {
879
1055
  registerSubgraphTools(server);
880
1056
  registerSubscriptionTools(server);
881
1057
  registerAccountTools(server);
1058
+ registerProjectTools(server);
882
1059
  registerDatasetTools(server);
883
1060
  registerIndexTools(server);
884
1061
  registerStreamsTools(server);
@@ -973,5 +1150,5 @@ httpServer.listen(port, () => {
973
1150
  console.error("Warning: SECONDLAYER_MCP_SECRET not set, authentication disabled");
974
1151
  });
975
1152
 
976
- //# debugId=4D9C7C1445EF7B1C64756E2164756E21
1153
+ //# debugId=330454369B037F3564756E2164756E21
977
1154
  //# sourceMappingURL=bin-http.js.map