@secondlayer/mcp 7.2.1 → 7.4.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
@@ -24,13 +24,20 @@ function isHostedArchiveUrl(url) {
24
24
 
25
25
  // src/resources.ts
26
26
  import { CHAIN_TRIGGER_FIELDS } from "@secondlayer/shared";
27
+ import { BOOTSTRAP_STEP } from "@secondlayer/shared/archive/instance-diagnosis";
27
28
  import { TRAIT_STANDARDS } from "@secondlayer/stacks/clarity";
28
29
  import { filterFieldsByType } from "@secondlayer/subgraphs";
29
30
  import { TYPE_MAP } from "@secondlayer/subgraphs/schema";
30
31
 
31
32
  // src/lib/client.ts
32
- import { SecondLayer, resolveApiKey } from "@secondlayer/sdk";
33
+ import {
34
+ SecondLayer,
35
+ resolveAccountKey,
36
+ resolveApiKey
37
+ } from "@secondlayer/sdk";
33
38
  var instance = null;
39
+ var HOSTED_KEY_HINT = "set SECONDLAYER_API_KEY (sk-sl_*) for credits; INSTANCE_TOKEN is the instance";
40
+ var DEFAULT_ARCHIVE_OPS_URL = "https://api.secondlayer.tools";
34
41
  function readApiKey() {
35
42
  return resolveApiKey();
36
43
  }
@@ -48,10 +55,31 @@ function getClient() {
48
55
  }
49
56
  return instance;
50
57
  }
51
- var keyHint = " — set INSTANCE_TOKEN from `sl init` for writes (SL_API_KEY is a legacy alias)";
58
+ function readArchiveApiKey() {
59
+ return resolveAccountKey();
60
+ }
61
+ function getArchiveOpsClient() {
62
+ const accountKey = readArchiveApiKey();
63
+ if (!accountKey) {
64
+ throw new Error(HOSTED_KEY_HINT);
65
+ }
66
+ const archiveOpsUrl = process.env.SL_CREDITS_API_URL || process.env.ARCHIVE_OPS_API_URL || DEFAULT_ARCHIVE_OPS_URL;
67
+ return new SecondLayer({
68
+ accountKey,
69
+ origin: "mcp",
70
+ archiveOpsUrl
71
+ });
72
+ }
73
+ var keyHint = " — set INSTANCE_TOKEN from `sl init` for writes";
52
74
  async function apiRequest(method, path, body) {
53
- const apiKey = readApiKey();
54
75
  const baseUrl = process.env.SECONDLAYER_API_URL || process.env.SL_API_URL || "http://127.0.0.1:3800";
76
+ let host = "";
77
+ try {
78
+ host = new URL(baseUrl).hostname;
79
+ } catch {
80
+ host = "";
81
+ }
82
+ const apiKey = host === "api.secondlayer.tools" ? resolveAccountKey() : readApiKey();
55
83
  const res = await fetch(`${baseUrl}${path}`, {
56
84
  method,
57
85
  headers: {
@@ -161,7 +189,11 @@ var PRODUCT_BLURBS = {
161
189
  subgraphs: "author/scaffold/deploy/query custom indexes",
162
190
  subscriptions: "webhook delivery on subgraph rows or raw chain events",
163
191
  account: "identity and self-provisioned API keys",
164
- codegen: "ORM schemas for the tables you read"
192
+ codegen: "ORM schemas for the tables you read",
193
+ instance: "decoder health and empty-index diagnosis",
194
+ archive: "signed archive verify, bootstrap/repair, quote, latest pointer",
195
+ setup: "one-command self-host onboarding",
196
+ credits: "archive credits balance at api.secondlayer.tools"
165
197
  };
166
198
  var PRODUCT_ORDER = [
167
199
  "index",
@@ -170,12 +202,17 @@ var PRODUCT_ORDER = [
170
202
  "subgraphs",
171
203
  "subscriptions",
172
204
  "account",
173
- "codegen"
205
+ "codegen",
206
+ "instance",
207
+ "archive",
208
+ "setup",
209
+ "credits"
174
210
  ];
175
211
  function buildCapabilities() {
176
212
  const byPrefix = new Map;
177
213
  for (const name of getRegisteredToolNames()) {
178
- const prefix = name.slice(0, name.indexOf("_"));
214
+ const sep = name.indexOf("_");
215
+ const prefix = sep === -1 ? name : name.slice(0, sep);
179
216
  const tools = byPrefix.get(prefix) ?? [];
180
217
  tools.push(name);
181
218
  byPrefix.set(prefix, tools);
@@ -209,6 +246,7 @@ async function buildContext(deps = { clientProvider: getClient }) {
209
246
  return field.error ? `unavailable: ${field.error.message}` : unavailable;
210
247
  };
211
248
  const snap = await deps.clientProvider().context().catch(() => null);
249
+ const instance2 = snap?.instance?.value ? snap.instance.value : orNull(snap?.instance);
212
250
  return {
213
251
  authState: {
214
252
  apiKeySet: Boolean(readApiKey())
@@ -219,10 +257,12 @@ async function buildContext(deps = { clientProvider: getClient }) {
219
257
  indexTip: orNull(snap?.indexTip),
220
258
  subgraphs: snap?.subgraphs.value ? snap.subgraphs.value.map(formatSubgraphSummary) : orNull(snap?.subgraphs),
221
259
  subscriptions: orNull(snap?.subscriptions),
222
- activeOperations: orNull(snap?.activeOperations)
260
+ activeOperations: orNull(snap?.activeOperations),
261
+ instance: instance2
223
262
  },
224
263
  whatYouCanDo: buildCapabilities(),
225
- readAuthTiers: READ_AUTH_TIERS
264
+ readAuthTiers: READ_AUTH_TIERS,
265
+ ...snap?.instance?.value?.state === "empty-index" ? { nextStep: BOOTSTRAP_STEP } : {}
226
266
  };
227
267
  }
228
268
  function registerResources(server) {
@@ -296,18 +336,43 @@ function registerAccountTools(server) {
296
336
  }, async ({ name }) => jsonResponse(await apiRequest("POST", "/api/keys", { name })));
297
337
  }
298
338
 
339
+ // src/tools/archive.ts
340
+ import { z as z2 } from "zod";
341
+ var FLOW = ["bootstrap", "repair"];
342
+ function registerArchiveTools(server, clientProvider = getClient, archiveOpsClientProvider = getArchiveOpsClient) {
343
+ defineTool(server, "archive_verify", "Read-only, free compare of this instance against a signed archive (POST /v1/archive/verify). unanchored means pin a key or check the URL — not that the instance is fine. After bootstrap, poll instance_status until decoders ok, then this, then codegen_index_schema. Do not download parquet.", {
344
+ against: z2.string().describe("Archive manifest URL"),
345
+ fromBlock: z2.number().int().nonnegative().optional().describe("First height (inclusive)"),
346
+ toBlock: z2.number().int().nonnegative().optional().describe("Last height (inclusive)"),
347
+ target: z2.string().optional().describe("Verify target (default raw)")
348
+ }, async ({ against, fromBlock, toBlock, target }) => jsonResponse(await clientProvider().archive.verify({
349
+ against,
350
+ fromBlock,
351
+ toBlock,
352
+ target
353
+ })));
354
+ defineTool(server, "archive_latest", "Signed pointer to the official archive tree (hosted). Needs SECONDLAYER_API_KEY (sk-sl_*), not INSTANCE_TOKEN. Use the returned origin as --against for archive_bootstrap. Do not download parquet.", {
355
+ against: z2.string().optional().describe("Override latest.json URL (default official tree)")
356
+ }, async ({ against }) => jsonResponse(await archiveOpsClientProvider().archive.latest(against)));
357
+ defineTool(server, "archive_quote", "Price a hosted archive fetch (metered). Needs SECONDLAYER_API_KEY (sk-sl_*), not INSTANCE_TOKEN. Quote happens here; bootstrap/repair also quote inside the CLI. Do not download parquet.", {
358
+ paths: z2.array(z2.string()).describe("Partition paths to price"),
359
+ flow: z2.enum(FLOW).describe("bootstrap or repair")
360
+ }, async ({ paths, flow }) => jsonResponse(await archiveOpsClientProvider().archive.quote({ paths, flow })));
361
+ defineTool(server, "credits_balance", "Archive credits balance at api.secondlayer.tools. Needs SECONDLAYER_API_KEY (sk-sl_*); INSTANCE_TOKEN is the instance.", {}, async () => jsonResponse(await archiveOpsClientProvider().archive.credits.balance()));
362
+ }
363
+
299
364
  // src/tools/codegen.ts
300
365
  import {
301
366
  generateIndexSchema
302
367
  } from "@secondlayer/subgraphs";
303
- import { z as z2 } from "zod";
368
+ import { z as z3 } from "zod";
304
369
  var TARGETS = ["prisma", "kysely", "drizzle", "json-schema"];
305
370
  function registerCodegenTools(server) {
306
371
  defineTool(server, "codegen_index_schema", "Generate an ORM schema for the Index tables (blocks, transactions, decoded_events, …) — the same output as `sl codegen index`. Returns the file contents as text; write it wherever your project keeps its schema. Use `tables` to emit only what you read.", {
307
- target: z2.enum(TARGETS).optional().describe("Output flavor (default kysely)"),
308
- tables: z2.array(z2.string()).optional().describe("Restrict to these Index tables (default: all)"),
309
- schemaName: z2.string().optional().describe("Postgres schema to qualify table names with"),
310
- datasourceEnv: z2.string().optional().describe("Prisma only: datasource url env var (default DATABASE_URL)")
372
+ target: z3.enum(TARGETS).optional().describe("Output flavor (default kysely)"),
373
+ tables: z3.array(z3.string()).optional().describe("Restrict to these Index tables (default: all)"),
374
+ schemaName: z3.string().optional().describe("Postgres schema to qualify table names with"),
375
+ datasourceEnv: z3.string().optional().describe("Prisma only: datasource url env var (default DATABASE_URL)")
311
376
  }, ({ target, tables, schemaName, datasourceEnv }) => textResponse(generateIndexSchema(target ?? "kysely", {
312
377
  tables,
313
378
  schemaName,
@@ -316,17 +381,17 @@ function registerCodegenTools(server) {
316
381
  }
317
382
 
318
383
  // src/tools/contracts.ts
319
- import { z as z3 } from "zod";
384
+ import { z as z4 } from "zod";
320
385
  function registerContractTools(server, clientProvider = getClient) {
321
386
  defineTool(server, "contracts_find", 'Discover deployed Stacks contracts conforming to a trait (e.g. "sip-010", "sip-009", "sip-013"). The discovery endpoint for "which contracts implement X". Reads are public.', {
322
- trait: z3.string().describe('Required. Trait to match (e.g. "sip-010").'),
323
- conformance: z3.enum(["declared", "inferred", "any"]).optional().describe("declared = parsed from source, inferred = ABI shape-match, any = either (default)"),
324
- include: z3.literal("abi").optional().describe(`Set to "abi" to include each contract's full ABI`),
325
- limit: z3.number().optional().describe("Page size, 1–500 (default 100)"),
326
- cursor: z3.string().optional().describe("Opaque cursor from a prior response's next_cursor")
387
+ trait: z4.string().describe('Required. Trait to match (e.g. "sip-010").'),
388
+ conformance: z4.enum(["declared", "inferred", "any"]).optional().describe("declared = parsed from source, inferred = ABI shape-match, any = either (default)"),
389
+ include: z4.literal("abi").optional().describe(`Set to "abi" to include each contract's full ABI`),
390
+ limit: z4.number().optional().describe("Page size, 1–500 (default 100)"),
391
+ cursor: z4.string().optional().describe("Opaque cursor from a prior response's next_cursor")
327
392
  }, async (params) => jsonResponse(await clientProvider().contracts.list(params)));
328
393
  defineTool(server, "contracts_get_abi", "Fetch a single deployed contract's ABI from the registry (prod-safe). Returns the contract's metadata + full ABI (functions, maps, variables, fungible/non-fungible tokens). Returns not_found if the contract isn't in the registry. Use it to inspect a contract before subgraphs_scaffold.", {
329
- contractId: z3.string().describe("Fully qualified contract id (e.g. SP….amm-pool-v2-01)")
394
+ contractId: z4.string().describe("Fully qualified contract id (e.g. SP….amm-pool-v2-01)")
330
395
  }, async ({ contractId }) => {
331
396
  const contract = await clientProvider().contracts.get(contractId, {
332
397
  includeAbi: true
@@ -343,14 +408,14 @@ function registerContractTools(server, clientProvider = getClient) {
343
408
 
344
409
  // src/tools/index.ts
345
410
  import { DECODED_EVENT_TYPES } from "@secondlayer/shared";
346
- import { z as z4 } from "zod";
411
+ import { z as z5 } from "zod";
347
412
  var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES;
348
413
  var rangeFilters = {
349
- contractId: z4.string().optional().describe("Filter by contract id"),
350
- fromHeight: z4.number().optional().describe("Start block height (inclusive)"),
351
- toHeight: z4.number().optional().describe("End block height (inclusive)"),
352
- cursor: z4.string().optional().describe("Opaque cursor from a prior response's next_cursor"),
353
- limit: z4.number().optional().describe("Max rows for this page")
414
+ contractId: z5.string().optional().describe("Filter by contract id"),
415
+ fromHeight: z5.number().optional().describe("Start block height (inclusive)"),
416
+ toHeight: z5.number().optional().describe("End block height (inclusive)"),
417
+ cursor: z5.string().optional().describe("Opaque cursor from a prior response's next_cursor"),
418
+ limit: z5.number().optional().describe("Max rows for this page")
354
419
  };
355
420
  var heightFilters = {
356
421
  fromHeight: rangeFilters.fromHeight,
@@ -361,52 +426,62 @@ var heightFilters = {
361
426
  function registerIndexTools(server, clientProvider = getClient) {
362
427
  defineTool(server, "index_ft_transfers", "List decoded SIP-010 fungible-token transfers from Index. Reads are keyless.", {
363
428
  ...rangeFilters,
364
- sender: z4.string().optional().describe("Filter by sender principal"),
365
- recipient: z4.string().optional().describe("Filter by recipient principal"),
366
- assetIdentifier: z4.string().optional().describe("Filter by asset identifier (contract::asset), e.g. SP…sbtc-token::sbtc-token")
429
+ sender: z5.string().optional().describe("Filter by sender principal"),
430
+ recipient: z5.string().optional().describe("Filter by recipient principal"),
431
+ assetIdentifier: z5.string().optional().describe("Filter by asset identifier (contract::asset), e.g. SP…sbtc-token::sbtc-token")
367
432
  }, async (params) => jsonResponse(await clientProvider().index.ftTransfers.list(params)));
368
433
  defineTool(server, "index_nft_transfers", "List decoded SIP-009 non-fungible-token transfers from Index. Reads are keyless.", {
369
434
  ...rangeFilters,
370
- sender: z4.string().optional().describe("Filter by sender principal"),
371
- recipient: z4.string().optional().describe("Filter by recipient principal"),
372
- assetIdentifier: z4.string().optional().describe("Filter by asset identifier (contract::asset)")
435
+ sender: z5.string().optional().describe("Filter by sender principal"),
436
+ recipient: z5.string().optional().describe("Filter by recipient principal"),
437
+ assetIdentifier: z5.string().optional().describe("Filter by asset identifier (contract::asset)")
373
438
  }, async (params) => jsonResponse(await clientProvider().index.nftTransfers.list(params)));
374
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.", {
375
- eventType: z4.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
440
+ eventType: z5.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
376
441
  ...rangeFilters,
377
- sender: z4.string().optional().describe("Filter by sender principal"),
378
- recipient: z4.string().optional().describe("Filter by recipient principal"),
379
- assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable"),
380
- 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."),
381
- txContext: z4.boolean().optional().describe("Join the submitting transaction into each event (tx_sender, tx_type, tx_status, tx_contract_id, tx_function_name). For print events it's the only source of the submitting sender.")
442
+ sender: z5.string().optional().describe("Filter by sender principal"),
443
+ recipient: z5.string().optional().describe("Filter by recipient principal"),
444
+ assetIdentifier: z5.string().optional().describe("Filter by asset identifier where applicable"),
445
+ trait: z5.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId; contract-keyed event types only."),
446
+ txContext: z5.boolean().optional().describe("Join the submitting transaction into each event (tx_sender, tx_type, tx_status, tx_contract_id, tx_function_name). For print events it's the only source of the submitting sender.")
382
447
  }, async (params) => jsonResponse(await clientProvider().index.events.list(params)));
383
448
  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.", {
384
449
  ...rangeFilters,
385
- functionName: z4.string().optional().describe("Filter by called function name"),
386
- sender: z4.string().optional().describe("Filter by caller principal"),
387
- trait: z4.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
450
+ functionName: z5.string().optional().describe("Filter by called function name"),
451
+ sender: z5.string().optional().describe("Filter by caller principal"),
452
+ trait: z5.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
388
453
  }, async (params) => jsonResponse(await clientProvider().index.contractCalls.list(params)));
389
454
  defineTool(server, "index_blocks", "List decoded blocks from Index. Reads are keyless.", { ...heightFilters }, async (params) => jsonResponse(await clientProvider().index.blocks.list(params)));
390
455
  defineTool(server, "index_transactions", "List decoded transactions from Index. Filter by type, sender, or contract. Reads are keyless.", {
391
456
  ...rangeFilters,
392
- type: z4.string().optional().describe("Filter by transaction type"),
393
- sender: z4.string().optional().describe("Filter by sender principal")
457
+ type: z5.string().optional().describe("Filter by transaction type"),
458
+ sender: z5.string().optional().describe("Filter by sender principal")
394
459
  }, async (params) => jsonResponse(await clientProvider().index.transactions.list(params)));
395
460
  defineTool(server, "index_print_schema", "Empirical per-topic print payload schemas for a contract, inferred from sampled on-chain events: each topic's fields with observed Clarity types, decoded TS types, subgraph column types, and presence rates. Use before writing print_event subgraph handlers to learn what's on e.data. Anonymous read.", {
396
- contractId: z4.string().describe("Contract id (SP….name) whose print events to profile")
461
+ contractId: z5.string().describe("Contract id (SP….name) whose print events to profile")
397
462
  }, async (params) => jsonResponse(await clientProvider().index.printSchema(params.contractId)));
398
463
  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()));
399
464
  defineTool(server, "batch_query", "Run up to 10 public /v1 reads in one round trip (POST /v1/batch). Each item keeps its own auth/quota/pay-per-call semantics; results return in order with per-item status. Paths must start with /v1/index, /v1/subgraphs, /v1/streams, or /v1/contracts.", {
400
- requests: z4.array(z4.object({
401
- path: z4.string().describe("Public /v1 read path"),
402
- params: z4.record(z4.string(), z4.string()).optional()
465
+ requests: z5.array(z5.object({
466
+ path: z5.string().describe("Public /v1 read path"),
467
+ params: z5.record(z5.string(), z5.string()).optional()
403
468
  })).min(1).max(10).describe("Read descriptors, executed concurrently")
404
469
  }, async (params) => jsonResponse(await clientProvider().batch(params.requests)));
405
470
  }
406
471
 
472
+ // src/tools/instance.ts
473
+ function registerInstanceTools(server, clientProvider = getClient) {
474
+ defineTool(server, "instance_status", "Decoder health and empty-index diagnosis from GET /public/status. If state is empty-index, next tool is archive_bootstrap or setup. Poll until index.decoders are ok before treating Index as ready — do not start decoders. Then archive_verify, then codegen_index_schema. Do not download parquet.", {}, async () => {
475
+ const client = clientProvider();
476
+ const status = await client.instance.status();
477
+ const diagnosis = await client.instance.diagnose();
478
+ return jsonResponse({ status, diagnosis });
479
+ });
480
+ }
481
+
407
482
  // src/tools/scaffold.ts
408
483
  import { generateSubgraphCode } from "@secondlayer/scaffold";
409
- import { z as z5 } from "zod";
484
+ import { z as z6 } from "zod";
410
485
  async function fetchAbi(clientProvider, contractId) {
411
486
  const contract = await clientProvider().contracts.get(contractId, {
412
487
  includeAbi: true
@@ -421,8 +496,8 @@ async function fetchAbi(clientProvider, contractId) {
421
496
  }
422
497
  function registerScaffoldTools(server, clientProvider = getClient) {
423
498
  defineTool(server, "subgraphs_scaffold", "Generate a subgraph scaffold from a deployed Stacks contract. Fetches the ABI automatically. Returns TypeScript source — review it, then pass it to subgraphs_deploy.", {
424
- contractId: z5.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
425
- subgraphName: z5.string().optional().describe("Override the subgraph name (defaults to contract name)")
499
+ contractId: z6.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
500
+ subgraphName: z6.string().optional().describe("Override the subgraph name (defaults to contract name)")
426
501
  }, async ({ contractId, subgraphName }) => {
427
502
  const { functions } = await fetchAbi(clientProvider, contractId);
428
503
  const code = generateSubgraphCode(contractId, functions, subgraphName);
@@ -430,36 +505,245 @@ function registerScaffoldTools(server, clientProvider = getClient) {
430
505
  });
431
506
  }
432
507
 
508
+ // src/tools/setup.ts
509
+ import { z as z7 } from "zod";
510
+
511
+ // src/lib/exec-cli.ts
512
+ import {
513
+ spawn
514
+ } from "node:child_process";
515
+ var ALLOWED = ["setup", "bootstrap", "repair", "verify"];
516
+ var TIMEOUT_MS = {
517
+ setup: 15 * 60 * 1000,
518
+ bootstrap: 10 * 60 * 1000,
519
+ repair: 10 * 60 * 1000,
520
+ verify: 2 * 60 * 1000
521
+ };
522
+ var VALUE_FLAGS = new Set([
523
+ "--against",
524
+ "--from-block",
525
+ "--to-block",
526
+ "--network",
527
+ "--node-mode",
528
+ "--dir"
529
+ ]);
530
+ var BOOL_FLAGS = new Set(["--yes", "--json", "--apply", "--skip-bootstrap"]);
531
+ var FLAGS_BY_CMD = {
532
+ setup: new Set([
533
+ "--yes",
534
+ "--network",
535
+ "--node-mode",
536
+ "--against",
537
+ "--dir",
538
+ "--skip-bootstrap"
539
+ ]),
540
+ bootstrap: new Set([
541
+ "--against",
542
+ "--yes",
543
+ "--json",
544
+ "--from-block",
545
+ "--to-block"
546
+ ]),
547
+ repair: new Set([
548
+ "--against",
549
+ "--json",
550
+ "--apply",
551
+ "--from-block",
552
+ "--to-block",
553
+ "--yes"
554
+ ]),
555
+ verify: new Set(["--against", "--json", "--from-block", "--to-block"])
556
+ };
557
+
558
+ class ExecCliError extends Error {
559
+ constructor(message) {
560
+ super(message);
561
+ this.name = "ExecCliError";
562
+ }
563
+ }
564
+ function isUnsafeArg(arg) {
565
+ if (arg.includes("\x00"))
566
+ return true;
567
+ if (/[\n\r]/.test(arg))
568
+ return true;
569
+ return /^[;|&]/.test(arg.trim());
570
+ }
571
+ function assertSafeArgs(subcommand, args) {
572
+ const allowed = FLAGS_BY_CMD[subcommand];
573
+ let expectingValue = null;
574
+ for (const arg of args) {
575
+ if (isUnsafeArg(arg)) {
576
+ throw new ExecCliError(`rejected argument: ${JSON.stringify(arg)}`);
577
+ }
578
+ if (expectingValue) {
579
+ if (arg.startsWith("-")) {
580
+ throw new ExecCliError(`${expectingValue} requires a value`);
581
+ }
582
+ expectingValue = null;
583
+ continue;
584
+ }
585
+ if (!arg.startsWith("--") || !allowed.has(arg)) {
586
+ throw new ExecCliError(`flag not allowed for ${subcommand}: ${JSON.stringify(arg)}`);
587
+ }
588
+ if (VALUE_FLAGS.has(arg)) {
589
+ expectingValue = arg;
590
+ } else if (!BOOL_FLAGS.has(arg)) {
591
+ throw new ExecCliError(`flag not allowed for ${subcommand}: ${JSON.stringify(arg)}`);
592
+ }
593
+ }
594
+ if (expectingValue) {
595
+ throw new ExecCliError(`${expectingValue} requires a value`);
596
+ }
597
+ }
598
+ function killProcessGroup(child) {
599
+ const pid = child.pid;
600
+ if (pid && process.platform !== "win32") {
601
+ try {
602
+ process.kill(-pid, "SIGKILL");
603
+ return;
604
+ } catch {}
605
+ }
606
+ child.kill("SIGKILL");
607
+ }
608
+ async function execSecondlayer(subcommand, args, deps = {}) {
609
+ if (!ALLOWED.includes(subcommand)) {
610
+ throw new ExecCliError(`subcommand not allowed: ${subcommand}`);
611
+ }
612
+ assertSafeArgs(subcommand, args);
613
+ const argv = [subcommand, ...args];
614
+ if (subcommand === "setup") {
615
+ if (!argv.includes("--yes"))
616
+ argv.push("--yes");
617
+ } else if (!argv.includes("--json")) {
618
+ argv.push("--json");
619
+ }
620
+ const env = deps.env ?? process.env;
621
+ const bin = env.SECONDLAYER_BIN || "secondlayer";
622
+ const cwd = deps.cwd ?? env.SECONDLAYER_CWD ?? process.cwd();
623
+ const spawnFn = deps.spawn ?? spawn;
624
+ const timeout = deps.timeoutMs ?? TIMEOUT_MS[subcommand];
625
+ return new Promise((resolve) => {
626
+ let stdout = "";
627
+ let stderr = "";
628
+ let settled = false;
629
+ const child = spawnFn(bin, argv, {
630
+ cwd,
631
+ env,
632
+ stdio: ["ignore", "pipe", "pipe"],
633
+ detached: process.platform !== "win32",
634
+ shell: false
635
+ });
636
+ const timer = setTimeout(() => {
637
+ killProcessGroup(child);
638
+ finish({
639
+ code: 124,
640
+ stdout,
641
+ stderr: `${stderr}
642
+ timeout after ${timeout}ms`.trim()
643
+ });
644
+ }, timeout);
645
+ function finish(result) {
646
+ if (settled)
647
+ return;
648
+ settled = true;
649
+ clearTimeout(timer);
650
+ resolve(result);
651
+ }
652
+ child.stdout?.on("data", (chunk) => {
653
+ stdout += String(chunk);
654
+ });
655
+ child.stderr?.on("data", (chunk) => {
656
+ stderr += String(chunk);
657
+ });
658
+ child.on("error", (err) => {
659
+ finish({ code: 127, stdout, stderr: err.message });
660
+ });
661
+ child.on("close", (code) => {
662
+ finish({ code: code ?? 1, stdout, stderr });
663
+ });
664
+ });
665
+ }
666
+
667
+ // src/tools/setup.ts
668
+ function cliResponse(result) {
669
+ return jsonResponse(result, result.code !== 0);
670
+ }
671
+ function registerSetupTools(server, exec = execSecondlayer) {
672
+ defineTool(server, "setup", "Non-interactive self-host onboarding (secondlayer setup --yes). If the index is empty, this or archive_bootstrap fills it. Bootstrap is metered against the official archive. After it returns, poll instance_status until index.decoders are ok, then archive_verify, then codegen_index_schema. Do not download parquet.", {
673
+ network: z7.enum(["mainnet", "testnet", "devnet"]).describe("Stacks network"),
674
+ nodeMode: z7.enum(["external", "stacks", "full"]).describe("Node mode: external, stacks, or full"),
675
+ against: z7.string().optional().describe("Archive manifest URL"),
676
+ dir: z7.string().optional().describe("Compose project directory"),
677
+ skipBootstrap: z7.boolean().optional().describe("Sync from genesis instead of restoring an archive")
678
+ }, async ({ network, nodeMode, against, dir, skipBootstrap }) => {
679
+ const args = ["--yes", "--network", network, "--node-mode", nodeMode];
680
+ if (against !== undefined)
681
+ args.push("--against", against);
682
+ if (dir !== undefined)
683
+ args.push("--dir", dir);
684
+ if (skipBootstrap)
685
+ args.push("--skip-bootstrap");
686
+ return cliResponse(await exec("setup", args));
687
+ });
688
+ defineTool(server, "archive_bootstrap", "Restore verified history into this instance (secondlayer bootstrap --against … --yes --json). Metered against the official archive; quote happens inside the CLI. After bootstrap, poll instance_status until decoders ok, then archive_verify, then codegen_index_schema. Do not download parquet.", {
689
+ against: z7.string().describe("Archive manifest URL"),
690
+ fromBlock: z7.number().int().nonnegative().optional().describe("Restore from this height instead of genesis"),
691
+ toBlock: z7.number().int().nonnegative().optional().describe("Stop at this height instead of the archive tip")
692
+ }, async ({ against, fromBlock, toBlock }) => {
693
+ const args = ["--against", against, "--yes", "--json"];
694
+ if (fromBlock !== undefined)
695
+ args.push("--from-block", String(fromBlock));
696
+ if (toBlock !== undefined)
697
+ args.push("--to-block", String(toBlock));
698
+ return cliResponse(await exec("bootstrap", args));
699
+ });
700
+ defineTool(server, "archive_repair", "Plan (default) or apply a repair of local chain data against a signed archive. Metered when apply fetches from the official archive. After apply, poll instance_status until decoders ok, then archive_verify. Do not download parquet.", {
701
+ against: z7.string().describe("Archive manifest URL"),
702
+ apply: z7.boolean().optional().describe("Write the repair (default plan-only)"),
703
+ fromBlock: z7.number().int().nonnegative().optional().describe("First height to consider"),
704
+ toBlock: z7.number().int().nonnegative().optional().describe("Last height to consider")
705
+ }, async ({ against, apply, fromBlock, toBlock }) => {
706
+ const args = ["--against", against, "--json"];
707
+ if (apply)
708
+ args.push("--apply", "--yes");
709
+ if (fromBlock !== undefined)
710
+ args.push("--from-block", String(fromBlock));
711
+ if (toBlock !== undefined)
712
+ args.push("--to-block", String(toBlock));
713
+ return cliResponse(await exec("repair", args));
714
+ });
715
+ }
716
+
433
717
  // src/tools/streams.ts
434
718
  import { STREAMS_EVENT_TYPES } from "@secondlayer/shared";
435
- import { z as z6 } from "zod";
436
- var filterValue = z6.union([z6.string(), z6.array(z6.string())]);
719
+ import { z as z8 } from "zod";
720
+ var filterValue = z8.union([z8.string(), z8.array(z8.string())]);
437
721
  function registerStreamsTools(server, clientProvider = getClient) {
438
722
  defineTool(server, "streams_tip", "Current canonical chain tip as Streams sees it: block height/hash, burn height, finalized_height, lag_seconds, and the oldest seekable height/cursor for this key's retention window. Call it before a cursor walk to know where the stream ends, and after an ingest to check lag.", {}, async () => jsonResponse(await clientProvider().streams.tip()));
439
723
  defineTool(server, "streams_events", "List raw chain events from Streams — ONE page per call, newest filters applied server-side. Returns { events, next_cursor, tip, reorgs }. To follow the chain live, poll this tool with `cursor` set to the previous response's `next_cursor` (input is exclusive, so no duplicates); there is no open-stream tool because a tool call cannot hold a connection. ALWAYS narrow with types/contractId/sender/recipient — an unfiltered page is the full firehose.", {
440
- cursor: z6.string().optional().describe("Resume strictly after this cursor (`<block_height>:<event_index>`, e.g. 951475:3) — pass a prior response's next_cursor"),
441
- fromHeight: z6.number().int().nonnegative().optional().describe("Start block height (inclusive)"),
442
- toHeight: z6.number().int().nonnegative().optional().describe("End block height (inclusive)"),
443
- types: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
444
- notTypes: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude (applied after types)"),
724
+ cursor: z8.string().optional().describe("Resume strictly after this cursor (`<block_height>:<event_index>`, e.g. 951475:3) — pass a prior response's next_cursor"),
725
+ fromHeight: z8.number().int().nonnegative().optional().describe("Start block height (inclusive)"),
726
+ toHeight: z8.number().int().nonnegative().optional().describe("End block height (inclusive)"),
727
+ types: z8.array(z8.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
728
+ notTypes: z8.array(z8.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude (applied after types)"),
445
729
  contractId: filterValue.optional().describe("Contract id, or a list of contract ids to OR together"),
446
730
  sender: filterValue.optional().describe("Sender principal, or a list to OR together"),
447
731
  recipient: filterValue.optional().describe("Recipient principal, or a list to OR together"),
448
- assetIdentifier: z6.string().optional().describe("Asset identifier (contract::asset), e.g. SP…sbtc-token::sbtc-token"),
449
- limit: z6.number().int().min(1).max(1000).optional().describe("Page size, 1–1000 (default 100)")
732
+ assetIdentifier: z8.string().optional().describe("Asset identifier (contract::asset), e.g. SP…sbtc-token::sbtc-token"),
733
+ limit: z8.number().int().min(1).max(1000).optional().describe("Page size, 1–1000 (default 100)")
450
734
  }, async (params) => jsonResponse(await clientProvider().streams.events.list(params)));
451
735
  defineTool(server, "streams_events_by_tx", "List every event emitted by a single transaction, in emission order. Use it to explain one transaction end to end after finding it in streams_events or the Index.", {
452
- txId: z6.string().describe("Transaction id (0x-prefixed)")
736
+ txId: z8.string().describe("Transaction id (0x-prefixed)")
453
737
  }, async ({ txId }) => jsonResponse(await clientProvider().streams.events.byTxId(txId)));
454
738
  defineTool(server, "streams_block_events", "List every event in one block, addressed by block height or block hash. Use it to audit a block a consumer flagged, or to diff what a reorg replaced.", {
455
- heightOrHash: z6.union([z6.number().int().nonnegative(), z6.string()]).describe("Block height (number) or block hash (0x-prefixed string)")
739
+ heightOrHash: z8.union([z8.number().int().nonnegative(), z8.string()]).describe("Block height (number) or block hash (0x-prefixed string)")
456
740
  }, async ({ heightOrHash }) => jsonResponse(await clientProvider().streams.blocks.events(heightOrHash)));
457
741
  defineTool(server, "streams_canonical", "Get the canonical block at a height: hash, burn block hash, parent, and whether the height is still canonical. Use it to confirm a height you already consumed was not reorged away before you trust derived state.", {
458
- height: z6.number().int().nonnegative().describe("Block height")
742
+ height: z8.number().int().nonnegative().describe("Block height")
459
743
  }, async ({ height }) => jsonResponse(await clientProvider().streams.canonical(height)));
460
744
  defineTool(server, "streams_reorgs", "List reorgs detected since a point in time, newest last, with the new canonical tip for each. Returns { reorgs, next_since } — resume with next_since. Call it when a consumer's derived state disagrees with streams_canonical, or on a schedule to know which heights to reprocess.", {
461
- since: z6.string().describe("Required. ISO 8601 timestamp, or a `next_since` resume token from a prior response"),
462
- limit: z6.number().int().positive().optional().describe("Max reorgs to return")
745
+ since: z8.string().describe("Required. ISO 8601 timestamp, or a `next_since` resume token from a prior response"),
746
+ limit: z8.number().int().positive().optional().describe("Max reorgs to return")
463
747
  }, async ({ since, limit }) => jsonResponse(await clientProvider().streams.reorgs.list({
464
748
  since,
465
749
  ...limit !== undefined ? { limit } : {}
@@ -469,19 +753,19 @@ function registerStreamsTools(server, clientProvider = getClient) {
469
753
 
470
754
  // src/tools/subgraphs.ts
471
755
  import { bundleSubgraphCode } from "@secondlayer/bundler";
472
- import { z as z7 } from "zod";
756
+ import { z as z9 } from "zod";
473
757
  function registerSubgraphTools(server, clientProvider = getClient) {
474
758
  defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only; read rows with subgraphs_query.", {}, async () => {
475
759
  const { data } = await clientProvider().subgraphs.list();
476
760
  return jsonResponse(data.map(formatSubgraphSummary));
477
761
  });
478
- defineTool(server, "subgraphs_status", "Get full details of a subgraph including schema, health, and table columns.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
762
+ defineTool(server, "subgraphs_status", "Get full details of a subgraph including schema, health, and table columns.", { name: z9.string().describe("Subgraph name") }, async ({ name }) => {
479
763
  const detail = await clientProvider().subgraphs.status(name);
480
764
  return jsonResponse(detail);
481
765
  });
482
766
  defineTool(server, "subgraphs_spec", "Fetch a subgraph's self-describing spec — the tables, columns, filters, and read URLs a consumer needs, without guessing. `agent` (default) returns the compact JSON schema built for tool use, `openapi` the OpenAPI document for the subgraph's read routes, `markdown` human-readable docs. Read it before writing queries against an unfamiliar subgraph.", {
483
- name: z7.string().describe("Subgraph name"),
484
- format: z7.enum(["agent", "openapi", "markdown"]).optional().describe("agent = JSON schema for tool use (default), openapi = OpenAPI JSON, markdown = docs.md")
767
+ name: z9.string().describe("Subgraph name"),
768
+ format: z9.enum(["agent", "openapi", "markdown"]).optional().describe("agent = JSON schema for tool use (default), openapi = OpenAPI JSON, markdown = docs.md")
485
769
  }, async ({ name, format }) => {
486
770
  const client = clientProvider();
487
771
  if (format === "openapi")
@@ -491,15 +775,15 @@ function registerSubgraphTools(server, clientProvider = getClient) {
491
775
  return jsonResponse(await client.subgraphs.schema(name));
492
776
  });
493
777
  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 — when tailing with a `fields` list, include "_id" in it or the next poll filter cannot be formed. Fetch one row by id with {"_id": "<id>"}. The same rows are readable over HTTP at GET /v1/subgraphs/<name>/<table> — { rows, next_cursor, tip } envelope, resume with ?cursor=<next_cursor> + _order=asc|desc (no _offset/_sort on /v1); hand that URL to third parties.', {
494
- name: z7.string().describe("Subgraph name"),
495
- table: z7.string().describe("Table name"),
496
- filters: z7.record(z7.string(), z7.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
497
- sort: z7.string().optional().describe("Column to sort by"),
498
- order: z7.enum(["asc", "desc"]).optional().describe("Sort order"),
499
- limit: z7.number().max(200).optional().describe("Max rows (default 50, max 200)"),
500
- offset: z7.number().optional().describe("Offset for pagination"),
501
- fields: z7.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
502
- count: z7.boolean().optional().describe("If true, return row count instead of rows")
778
+ name: z9.string().describe("Subgraph name"),
779
+ table: z9.string().describe("Table name"),
780
+ filters: z9.record(z9.string(), z9.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
781
+ sort: z9.string().optional().describe("Column to sort by"),
782
+ order: z9.enum(["asc", "desc"]).optional().describe("Sort order"),
783
+ limit: z9.number().max(200).optional().describe("Max rows (default 50, max 200)"),
784
+ offset: z9.number().optional().describe("Offset for pagination"),
785
+ fields: z9.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
786
+ count: z9.boolean().optional().describe("If true, return row count instead of rows")
503
787
  }, async ({
504
788
  name,
505
789
  table,
@@ -527,15 +811,15 @@ function registerSubgraphTools(server, clientProvider = getClient) {
527
811
  return jsonResponse(result);
528
812
  });
529
813
  defineTool(server, "subgraphs_reindex", "Reindex a subgraph: drops and rebuilds the whole subgraph from its start block to chain tip. Takes no block range — use subgraphs_backfill to process a specific range. Returns an operationId — check subgraphs_status (health) or the REST operations endpoint to track progress to completion.", {
530
- name: z7.string().describe("Subgraph name")
814
+ name: z9.string().describe("Subgraph name")
531
815
  }, async ({ name }) => {
532
816
  const result = await clientProvider().subgraphs.reindex(name);
533
817
  return jsonResponse(result);
534
818
  });
535
819
  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 — check subgraphs_status (health) or the REST operations endpoint to track progress.", {
536
- name: z7.string().describe("Subgraph name"),
537
- fromBlock: z7.number().int().nonnegative().describe("Start block (inclusive)"),
538
- toBlock: z7.number().int().nonnegative().describe("End block (inclusive)")
820
+ name: z9.string().describe("Subgraph name"),
821
+ fromBlock: z9.number().int().nonnegative().describe("Start block (inclusive)"),
822
+ toBlock: z9.number().int().nonnegative().describe("End block (inclusive)")
539
823
  }, async ({ name, fromBlock, toBlock }) => {
540
824
  const result = await clientProvider().subgraphs.backfill(name, {
541
825
  fromBlock,
@@ -543,15 +827,15 @@ function registerSubgraphTools(server, clientProvider = getClient) {
543
827
  });
544
828
  return jsonResponse(result);
545
829
  });
546
- defineTool(server, "subgraphs_stop", "Cancel an in-flight reindex or backfill operation for a subgraph. Returns the stop request status; check subgraphs_status (health) or the REST operations endpoint to confirm it reaches a terminal state.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
830
+ defineTool(server, "subgraphs_stop", "Cancel an in-flight reindex or backfill operation for a subgraph. Returns the stop request status; check subgraphs_status (health) or the REST operations endpoint to confirm it reaches a terminal state.", { name: z9.string().describe("Subgraph name") }, async ({ name }) => {
547
831
  const result = await clientProvider().subgraphs.stop(name);
548
832
  return jsonResponse(result);
549
833
  });
550
834
  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.", {
551
- name: z7.string().describe("Subgraph name"),
552
- limit: z7.number().int().positive().optional().describe("Max gaps to return"),
553
- offset: z7.number().int().nonnegative().optional().describe("Pagination offset"),
554
- resolved: z7.boolean().optional().describe("Filter by resolved state (omit for unresolved only)")
835
+ name: z9.string().describe("Subgraph name"),
836
+ limit: z9.number().int().positive().optional().describe("Max gaps to return"),
837
+ offset: z9.number().int().nonnegative().optional().describe("Pagination offset"),
838
+ resolved: z9.boolean().optional().describe("Filter by resolved state (omit for unresolved only)")
555
839
  }, async ({ name, limit, offset, resolved }) => {
556
840
  const result = await clientProvider().subgraphs.gaps(name, {
557
841
  limit,
@@ -561,20 +845,20 @@ function registerSubgraphTools(server, clientProvider = getClient) {
561
845
  return jsonResponse(result);
562
846
  });
563
847
  defineTool(server, "subgraphs_operations", "Operation history for a subgraph (deploy/reindex/backfill/stop), newest first — status, block range, progress, and error. THIS IS THE VERIFY CALL: after subgraphs_deploy, subgraphs_reindex, subgraphs_backfill, or subgraphs_stop, poll it with the returned operationId until `status` is terminal (completed/failed/cancelled) before reporting success. Omit operationId for the recent history.", {
564
- name: z7.string().describe("Subgraph name"),
565
- operationId: z7.string().optional().describe("Fetch one operation by id (the operationId returned by reindex/backfill/stop); omit for recent history")
848
+ name: z9.string().describe("Subgraph name"),
849
+ operationId: z9.string().optional().describe("Fetch one operation by id (the operationId returned by reindex/backfill/stop); omit for recent history")
566
850
  }, async ({ name, operationId }) => {
567
851
  const client = clientProvider();
568
852
  return jsonResponse(operationId ? await client.subgraphs.getOperation(name, operationId) : await client.subgraphs.operations(name));
569
853
  });
570
- defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
854
+ defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z9.string().describe("Subgraph name") }, async ({ name }) => {
571
855
  const result = await clientProvider().subgraphs.delete(name);
572
856
  return textResponse(result.message);
573
857
  });
574
858
  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. Call `subgraphs_reindex` separately if you need a forced reindex.", {
575
- code: z7.string().describe("TypeScript source code containing a defineSubgraph() call"),
576
- startBlock: z7.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy"),
577
- dryRun: z7.boolean().optional().describe("Validate and preview the deploy (schema/DDL) without writing changes")
859
+ code: z9.string().describe("TypeScript source code containing a defineSubgraph() call"),
860
+ startBlock: z9.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy"),
861
+ dryRun: z9.boolean().optional().describe("Validate and preview the deploy (schema/DDL) without writing changes")
578
862
  }, async ({ code, startBlock, dryRun }) => {
579
863
  const bundled = await bundleSubgraphCode(code);
580
864
  const result = await clientProvider().subgraphs.deploy({
@@ -594,38 +878,38 @@ function registerSubgraphTools(server, clientProvider = getClient) {
594
878
 
595
879
  // src/tools/subscriptions.ts
596
880
  import { CHAIN_TRIGGER_TYPES } from "@secondlayer/shared";
597
- import { z as z8 } from "zod";
881
+ import { z as z10 } from "zod";
598
882
  function registerSubscriptionTools(server, clientProvider = getClient) {
599
883
  defineTool(server, "subscriptions_list", "List all subscriptions for the current account. Returns summary fields (no secrets).", {}, async () => {
600
884
  const { data } = await clientProvider().subscriptions.list();
601
885
  return jsonResponse(data);
602
886
  });
603
- defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id: z8.string().describe("Subscription id") }, async ({ id }) => {
887
+ defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id: z10.string().describe("Subscription id") }, async ({ id }) => {
604
888
  const detail = await clientProvider().subscriptions.get(id);
605
889
  return jsonResponse(detail);
606
890
  });
607
891
  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.", {
608
- name: z8.string().describe("Human-readable name, unique per account"),
609
- subgraphName: z8.string().optional().describe("Subgraph to subscribe to (subgraph subscription)"),
610
- tableName: z8.string().optional().describe("Table within the subgraph (subgraph subscription)"),
611
- triggers: z8.array(z8.object({
612
- type: z8.enum(CHAIN_TRIGGER_TYPES),
613
- contractId: z8.string().optional(),
614
- functionName: z8.string().optional(),
615
- caller: z8.string().optional(),
616
- sender: z8.string().optional(),
617
- recipient: z8.string().optional(),
618
- assetIdentifier: z8.string().optional(),
619
- deployer: z8.string().optional(),
620
- contractName: z8.string().optional(),
621
- topic: z8.string().optional(),
622
- lockedAddress: z8.string().optional(),
623
- trait: z8.string().optional(),
624
- minAmount: z8.union([z8.string(), z8.number()]).optional(),
625
- maxAmount: z8.union([z8.string(), z8.number()]).optional()
892
+ name: z10.string().describe("Human-readable name, unique per account"),
893
+ subgraphName: z10.string().optional().describe("Subgraph to subscribe to (subgraph subscription)"),
894
+ tableName: z10.string().optional().describe("Table within the subgraph (subgraph subscription)"),
895
+ triggers: z10.array(z10.object({
896
+ type: z10.enum(CHAIN_TRIGGER_TYPES),
897
+ contractId: z10.string().optional(),
898
+ functionName: z10.string().optional(),
899
+ caller: z10.string().optional(),
900
+ sender: z10.string().optional(),
901
+ recipient: z10.string().optional(),
902
+ assetIdentifier: z10.string().optional(),
903
+ deployer: z10.string().optional(),
904
+ contractName: z10.string().optional(),
905
+ topic: z10.string().optional(),
906
+ lockedAddress: z10.string().optional(),
907
+ trait: z10.string().optional(),
908
+ minAmount: z10.union([z10.string(), z10.number()]).optional(),
909
+ maxAmount: z10.union([z10.string(), z10.number()]).optional()
626
910
  })).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."),
627
- url: z8.string().describe("Webhook URL"),
628
- format: z8.enum([
911
+ url: z10.string().describe("Webhook URL"),
912
+ format: z10.enum([
629
913
  "standard-webhooks",
630
914
  "inngest",
631
915
  "trigger",
@@ -633,20 +917,20 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
633
917
  "cloudevents",
634
918
  "raw"
635
919
  ]).optional().describe("Wire format (default standard-webhooks)"),
636
- runtime: z8.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
637
- filter: z8.record(z8.string(), z8.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}'),
638
- authConfig: z8.record(z8.string(), z8.unknown()).optional().describe('Receiver auth sent with each delivery, e.g. {"type": "bearer", "token": "..."}')
920
+ runtime: z10.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
921
+ filter: z10.record(z10.string(), z10.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}'),
922
+ authConfig: z10.record(z10.string(), z10.unknown()).optional().describe('Receiver auth sent with each delivery, e.g. {"type": "bearer", "token": "..."}')
639
923
  }, async (input) => {
640
924
  const res = await clientProvider().subscriptions.create(input);
641
925
  return jsonResponse(res);
642
926
  });
643
927
  defineTool(server, "subscriptions_update", "Patch a subscription (name, url, filter, authConfig, format, runtime, retry, timeout, concurrency).", {
644
- id: z8.string(),
645
- name: z8.string().optional().describe("Rename the subscription"),
646
- url: z8.string().optional(),
647
- filter: z8.record(z8.string(), z8.unknown()).optional(),
648
- authConfig: z8.record(z8.string(), z8.unknown()).optional().describe("Receiver auth sent with each delivery (bearer/etc.)"),
649
- format: z8.enum([
928
+ id: z10.string(),
929
+ name: z10.string().optional().describe("Rename the subscription"),
930
+ url: z10.string().optional(),
931
+ filter: z10.record(z10.string(), z10.unknown()).optional(),
932
+ authConfig: z10.record(z10.string(), z10.unknown()).optional().describe("Receiver auth sent with each delivery (bearer/etc.)"),
933
+ format: z10.enum([
650
934
  "standard-webhooks",
651
935
  "inngest",
652
936
  "trigger",
@@ -654,36 +938,36 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
654
938
  "cloudevents",
655
939
  "raw"
656
940
  ]).optional(),
657
- runtime: z8.enum(["inngest", "trigger", "cloudflare", "node"]).nullable().optional(),
658
- maxRetries: z8.number().int().min(0).optional(),
659
- timeoutMs: z8.number().int().min(100).optional(),
660
- concurrency: z8.number().int().min(1).optional()
941
+ runtime: z10.enum(["inngest", "trigger", "cloudflare", "node"]).nullable().optional(),
942
+ maxRetries: z10.number().int().min(0).optional(),
943
+ timeoutMs: z10.number().int().min(100).optional(),
944
+ concurrency: z10.number().int().min(1).optional()
661
945
  }, async ({ id, ...patch }) => {
662
946
  const res = await clientProvider().subscriptions.update(id, patch);
663
947
  return jsonResponse(res);
664
948
  });
665
- defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id: z8.string() }, async ({ id }) => {
949
+ defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id: z10.string() }, async ({ id }) => {
666
950
  const res = await clientProvider().subscriptions.delete(id);
667
951
  return jsonResponse(res);
668
952
  });
669
- 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. Returns {ok, statusCode, error, durationMs, deliveryId}.", { id: z8.string().describe("Subscription id") }, async ({ id }) => {
953
+ 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. Returns {ok, statusCode, error, durationMs, deliveryId}.", { id: z10.string().describe("Subscription id") }, async ({ id }) => {
670
954
  const res = await clientProvider().subscriptions.test(id);
671
955
  return jsonResponse(res);
672
956
  });
673
- defineTool(server, "subscriptions_pause", "Pause a subscription: stops delivery attempts and stops queueing new ones. Nothing is deleted — resume picks the subscription back up from the tip. Verify with subscriptions_get (status).", { id: z8.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.pause(id)));
674
- defineTool(server, "subscriptions_resume", "Resume a paused subscription (also clears a tripped circuit breaker). Deliveries restart from the current tip — use subscriptions_replay for the blocks missed while paused. Verify with subscriptions_get (status).", { id: z8.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.resume(id)));
675
- defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription's webhook signing secret. Returns the NEW `signingSecret` ONCE — forward it to the user; deliveries signed with the old secret stop verifying as soon as this returns, so the receiver must be updated.", { id: z8.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.rotateSecret(id)));
676
- defineTool(server, "subscriptions_deliveries", "List recent delivery attempts for a subscription — status code, attempt count, error, duration, timestamps. THIS IS THE VERIFY CALL: after subscriptions_create, subscriptions_test, or subscriptions_replay, read it to confirm the webhook actually landed (and to see the receiver's response when it didn't) instead of trusting the enqueue result.", { id: z8.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.deliveries(id)));
677
- defineTool(server, "subscriptions_dead", "List a subscription's dead-letter queue — deliveries that exhausted their retries, with the outbox id, payload, and last error. Diagnose the receiver first (subscriptions_get for the URL/auth, subscriptions_test for a live probe), then requeue rows with subscriptions_requeue.", { id: z8.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.dead(id)));
957
+ defineTool(server, "subscriptions_pause", "Pause a subscription: stops delivery attempts and stops queueing new ones. Nothing is deleted — resume picks the subscription back up from the tip. Verify with subscriptions_get (status).", { id: z10.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.pause(id)));
958
+ defineTool(server, "subscriptions_resume", "Resume a paused subscription (also clears a tripped circuit breaker). Deliveries restart from the current tip — use subscriptions_replay for the blocks missed while paused. Verify with subscriptions_get (status).", { id: z10.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.resume(id)));
959
+ defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription's webhook signing secret. Returns the NEW `signingSecret` ONCE — forward it to the user; deliveries signed with the old secret stop verifying as soon as this returns, so the receiver must be updated.", { id: z10.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.rotateSecret(id)));
960
+ defineTool(server, "subscriptions_deliveries", "List recent delivery attempts for a subscription — status code, attempt count, error, duration, timestamps. THIS IS THE VERIFY CALL: after subscriptions_create, subscriptions_test, or subscriptions_replay, read it to confirm the webhook actually landed (and to see the receiver's response when it didn't) instead of trusting the enqueue result.", { id: z10.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.deliveries(id)));
961
+ defineTool(server, "subscriptions_dead", "List a subscription's dead-letter queue — deliveries that exhausted their retries, with the outbox id, payload, and last error. Diagnose the receiver first (subscriptions_get for the URL/auth, subscriptions_test for a live probe), then requeue rows with subscriptions_requeue.", { id: z10.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.dead(id)));
678
962
  defineTool(server, "subscriptions_requeue", "Requeue ONE dead-lettered delivery for another attempt, by its outbox id from subscriptions_dead. Fix the receiver first — a requeue against a still-broken endpoint just dies again. Verify with subscriptions_deliveries.", {
679
- id: z8.string().describe("Subscription id"),
680
- outboxId: z8.string().describe("Outbox id of the dead delivery (from subscriptions_dead)")
963
+ id: z10.string().describe("Subscription id"),
964
+ outboxId: z10.string().describe("Outbox id of the dead delivery (from subscriptions_dead)")
681
965
  }, async ({ id, outboxId }) => jsonResponse(await clientProvider().subscriptions.requeue(id, outboxId)));
682
966
  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.", {
683
- id: z8.string(),
684
- fromBlock: z8.number().int().nonnegative(),
685
- toBlock: z8.number().int().nonnegative(),
686
- force: z8.string().optional().describe("Idempotency suffix to force a duplicate replay of the range")
967
+ id: z10.string(),
968
+ fromBlock: z10.number().int().nonnegative(),
969
+ toBlock: z10.number().int().nonnegative(),
970
+ force: z10.string().optional().describe("Idempotency suffix to force a duplicate replay of the range")
687
971
  }, async ({ id, fromBlock, toBlock, force }) => {
688
972
  const res = await clientProvider().subscriptions.replay(id, {
689
973
  fromBlock,
@@ -709,6 +993,9 @@ function createServer() {
709
993
  registerStreamsTools(server);
710
994
  registerContractTools(server);
711
995
  registerCodegenTools(server);
996
+ registerInstanceTools(server);
997
+ registerArchiveTools(server);
998
+ registerSetupTools(server);
712
999
  const baseUrl = process.env.SECONDLAYER_API_URL || process.env.SL_API_URL || "http://127.0.0.1:3800";
713
1000
  if (isHostedArchiveUrl(baseUrl)) {
714
1001
  registerAccountTools(server);
@@ -803,5 +1090,5 @@ httpServer.listen(port, () => {
803
1090
  console.error("Warning: SECONDLAYER_MCP_SECRET not set, authentication disabled");
804
1091
  });
805
1092
 
806
- //# debugId=83999A89E39499BE64756E2164756E21
1093
+ //# debugId=978D99EF4EC8A61364756E2164756E21
807
1094
  //# sourceMappingURL=bin-http.js.map