@secondlayer/mcp 10.0.0 → 11.0.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/README.md CHANGED
@@ -55,7 +55,7 @@ bunx -p @secondlayer/mcp secondlayer-mcp-http
55
55
  | `INSTANCE_TOKEN` | Writes on your instance | — | From `secondlayer init`. Required for write tools elsewhere than `api.secondlayer.tools`; reads are public. Not sent to the hosted API. |
56
56
  | `SECONDLAYER_API_KEY` | Hosted API, credits/quote/latest | — | `sk-sl_*`, used automatically when `SECONDLAYER_API_URL` is `https://api.secondlayer.tools`. |
57
57
  | `SECONDLAYER_API_URL` | No | `http://127.0.0.1:3800` | Instance API, or `https://api.secondlayer.tools` for hosted. |
58
- | `SECONDLAYER_BIN` | CLI tools | `secondlayer` on PATH | Path to the CLI binary (`setup`, `bootstrap`, `repair`). |
58
+ | `SECONDLAYER_BIN` | CLI tools | `secondlayer` on PATH | Path to the CLI binary (`bootstrap`, `repair`). |
59
59
  | `SECONDLAYER_CWD` | CLI tools | process cwd | Compose project directory. |
60
60
  | `SECONDLAYER_MCP_PORT` | No | `3100` | HTTP transport port. |
61
61
  | `SECONDLAYER_MCP_SECRET` | No | — | Bearer token for HTTP auth. Disabled if unset. |
@@ -71,12 +71,11 @@ bunx -p @secondlayer/mcp secondlayer-mcp-http
71
71
  | **Contracts** (2) | `contracts_find`, `contracts_get_abi` |
72
72
  | **Instance** (1) | `instance_status` |
73
73
  | **Archive** (5) | `archive_verify`, `archive_bootstrap`, `archive_repair`, `archive_latest`, `archive_quote` |
74
- | **Setup** (1) | `setup` |
75
74
  | **Credits** (1) | `credits_balance` (needs `SECONDLAYER_API_KEY`) |
76
75
  | **Account** (2) | `account_whoami`, `account_create_key` (only when pointed at `https://api.secondlayer.tools`) |
77
76
 
78
77
  Verify after mutating: `subgraphs_operations` for deploy/reindex/backfill/stop,
79
- `webhooks_deliveries` for create/test/replay. Empty index: `setup` or
78
+ `webhooks_deliveries` for create/test/replay. Empty index:
80
79
  `archive_bootstrap`, poll `instance_status` until decoders are ok, then
81
80
  `archive_verify`, then `codegen_index_schema`.
82
81
 
package/dist/bin-http.js CHANGED
@@ -226,7 +226,6 @@ var PRODUCT_BLURBS = {
226
226
  codegen: "ORM schemas for the tables you read",
227
227
  instance: "decoder health and empty-index diagnosis",
228
228
  archive: "signed archive verify, bootstrap/repair, quote, latest pointer",
229
- setup: "one-command self-host onboarding",
230
229
  credits: "archive credits balance at api.secondlayer.tools"
231
230
  };
232
231
  var PRODUCT_ORDER = [
@@ -239,7 +238,6 @@ var PRODUCT_ORDER = [
239
238
  "codegen",
240
239
  "instance",
241
240
  "archive",
242
- "setup",
243
241
  "credits"
244
242
  ];
245
243
  function buildCapabilities() {
@@ -444,229 +442,22 @@ function registerAccountTools(server) {
444
442
  }, async ({ name }) => jsonResponse(await apiRequest("POST", "/api/keys", { name })));
445
443
  }
446
444
 
447
- // src/tools/archive.ts
445
+ // src/tools/archive-cli.ts
448
446
  import { z as z2 } from "zod";
449
- var FLOW = ["bootstrap", "repair"];
450
- function registerArchiveTools(server, clientProvider = getClient, archiveOpsClientProvider = getArchiveOpsClient) {
451
- 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.", {
452
- against: z2.string().describe("Archive manifest URL"),
453
- fromBlock: z2.number().int().nonnegative().optional().describe("First height (inclusive)"),
454
- toBlock: z2.number().int().nonnegative().optional().describe("Last height (inclusive)"),
455
- target: z2.string().optional().describe("Verify target (default raw)")
456
- }, async ({ against, fromBlock, toBlock, target }) => jsonResponse(await clientProvider().archive.verify({
457
- against,
458
- fromBlock,
459
- toBlock,
460
- target
461
- })));
462
- 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.", {
463
- against: z2.string().optional().describe("Override latest.json URL (default official tree)")
464
- }, async ({ against }) => jsonResponse(await archiveOpsClientProvider().archive.latest(against)));
465
- 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.", {
466
- paths: z2.array(z2.string()).describe("Partition paths to price"),
467
- flow: z2.enum(FLOW).describe("bootstrap or repair")
468
- }, async ({ paths, flow }) => jsonResponse(await archiveOpsClientProvider().archive.quote({ paths, flow })));
469
- 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()));
470
- }
471
-
472
- // src/tools/codegen.ts
473
- import {
474
- generateIndexSchema
475
- } from "@secondlayer/subgraphs";
476
- import { z as z3 } from "zod";
477
- var TARGETS = ["prisma", "kysely", "drizzle", "json-schema"];
478
- function registerCodegenTools(server) {
479
- 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.", {
480
- target: z3.enum(TARGETS).optional().describe("Output flavor (default kysely)"),
481
- tables: z3.array(z3.string()).optional().describe("Restrict to these Index tables (default: all)"),
482
- schemaName: z3.string().optional().describe("Postgres schema to qualify table names with"),
483
- datasourceEnv: z3.string().optional().describe("Prisma only: datasource url env var (default DATABASE_URL)")
484
- }, ({ target, tables, schemaName, datasourceEnv }) => textResponse(generateIndexSchema(target ?? "kysely", {
485
- tables,
486
- schemaName,
487
- datasourceEnv
488
- })));
489
- }
490
-
491
- // src/tools/contracts.ts
492
- import { z as z4 } from "zod";
493
- function registerContractTools(server, clientProvider = getClient) {
494
- 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.', {
495
- trait: z4.string().describe('Required. Trait to match (e.g. "sip-010").'),
496
- conformance: z4.enum(["declared", "inferred", "any"]).optional().describe("declared = parsed from source, inferred = ABI shape-match, any = either (default)"),
497
- include: z4.literal("abi").optional().describe(`Set to "abi" to include each contract's full ABI`),
498
- limit: z4.number().optional().describe("Page size, 1–500 (default 100)"),
499
- cursor: z4.string().optional().describe("Opaque cursor from a prior response's next_cursor")
500
- }, async (params) => jsonResponse(await clientProvider().contracts.list(params)));
501
- 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.", {
502
- contractId: z4.string().describe("Fully qualified contract id (e.g. SP….amm-pool-v2-01)")
503
- }, async ({ contractId }) => {
504
- const contract = await clientProvider().contracts.get(contractId, {
505
- includeAbi: true
506
- });
507
- return contract ? jsonResponse(contract) : jsonResponse({
508
- error: {
509
- type: "not_found",
510
- status: 404,
511
- message: `Contract not in registry: ${contractId}`
512
- }
513
- }, true);
514
- });
515
- }
516
-
517
- // src/tools/index.ts
518
- import { DECODED_EVENT_TYPES } from "@secondlayer/shared";
519
- import { z as z5 } from "zod";
520
- var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES;
521
- var rangeFilters = {
522
- contractId: z5.string().optional().describe("Filter by contract id"),
523
- fromHeight: z5.number().optional().describe("Start block height (inclusive)"),
524
- toHeight: z5.number().optional().describe("End block height (inclusive)"),
525
- cursor: z5.string().optional().describe("Opaque cursor from a prior response's next_cursor"),
526
- limit: z5.number().optional().describe("Max rows for this page")
527
- };
528
- var heightFilters = {
529
- fromHeight: rangeFilters.fromHeight,
530
- toHeight: rangeFilters.toHeight,
531
- cursor: rangeFilters.cursor,
532
- limit: rangeFilters.limit
533
- };
534
- function registerIndexTools(server, clientProvider = getClient) {
535
- defineTool(server, "index_ft_transfers", "List decoded SIP-010 fungible-token transfers from Index. Reads are keyless.", {
536
- ...rangeFilters,
537
- sender: z5.string().optional().describe("Filter by sender principal"),
538
- recipient: z5.string().optional().describe("Filter by recipient principal"),
539
- assetIdentifier: z5.string().optional().describe("Filter by asset identifier (contract::asset), e.g. SP…sbtc-token::sbtc-token")
540
- }, async (params) => jsonResponse(await clientProvider().index.ftTransfers.list(params)));
541
- defineTool(server, "index_nft_transfers", "List decoded SIP-009 non-fungible-token transfers from Index. Reads are keyless.", {
542
- ...rangeFilters,
543
- sender: z5.string().optional().describe("Filter by sender principal"),
544
- recipient: z5.string().optional().describe("Filter by recipient principal"),
545
- assetIdentifier: z5.string().optional().describe("Filter by asset identifier (contract::asset)")
546
- }, async (params) => jsonResponse(await clientProvider().index.nftTransfers.list(params)));
547
- 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.", {
548
- eventType: z5.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
549
- ...rangeFilters,
550
- sender: z5.string().optional().describe("Filter by sender principal"),
551
- recipient: z5.string().optional().describe("Filter by recipient principal"),
552
- assetIdentifier: z5.string().optional().describe("Filter by asset identifier where applicable"),
553
- 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."),
554
- 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.")
555
- }, async (params) => jsonResponse(await clientProvider().index.events.list(params)));
556
- 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.", {
557
- ...rangeFilters,
558
- functionName: z5.string().optional().describe("Filter by called function name"),
559
- sender: z5.string().optional().describe("Filter by caller principal"),
560
- trait: z5.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
561
- }, async (params) => jsonResponse(await clientProvider().index.contractCalls.list(params)));
562
- defineTool(server, "index_blocks", "List decoded blocks from Index. Reads are keyless.", { ...heightFilters }, async (params) => jsonResponse(await clientProvider().index.blocks.list(params)));
563
- defineTool(server, "index_transactions", "List decoded transactions from Index. Filter by type, sender, or contract. Reads are keyless.", {
564
- ...rangeFilters,
565
- type: z5.string().optional().describe("Filter by transaction type"),
566
- sender: z5.string().optional().describe("Filter by sender principal")
567
- }, async (params) => jsonResponse(await clientProvider().index.transactions.list(params)));
568
- 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.", {
569
- contractId: z5.string().describe("Contract id (SP….name) whose print events to profile")
570
- }, async (params) => jsonResponse(await clientProvider().index.printSchema(params.contractId)));
571
- 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()));
572
- 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.", {
573
- requests: z5.array(z5.object({
574
- path: z5.string().describe("Public /v1 read path"),
575
- params: z5.record(z5.string(), z5.string()).optional()
576
- })).min(1).max(10).describe("Read descriptors, executed concurrently")
577
- }, async (params) => jsonResponse(await clientProvider().batch(params.requests)));
578
- }
579
-
580
- // src/tools/instance.ts
581
- function registerInstanceTools(server, clientProvider = getClient) {
582
- 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 () => {
583
- const client = clientProvider();
584
- const status = await client.instance.status();
585
- const diagnosis = await client.instance.diagnose();
586
- return jsonResponse({ status, diagnosis });
587
- });
588
- }
589
-
590
- // src/tools/scaffold.ts
591
- import {
592
- generatePrintSchemaSubgraph,
593
- generateSubgraphCode,
594
- generateTokenSubgraphFromAbi
595
- } from "@secondlayer/scaffold";
596
- import { z as z6 } from "zod";
597
- async function fetchAbi(clientProvider, contractId) {
598
- const contract = await clientProvider().contracts.get(contractId, {
599
- includeAbi: true
600
- });
601
- if (!contract)
602
- throw new Error(`Contract not found: ${contractId}`);
603
- const abi = contract.abi;
604
- if (!abi) {
605
- throw new Error(`No ABI available for ${contractId} (abi_status: ${contract.abi_status})`);
606
- }
607
- return { functions: abi.functions ?? [], abi };
608
- }
609
- function registerScaffoldTools(server, clientProvider = getClient) {
610
- defineTool(server, "subgraphs_scaffold", "Generate a subgraph from observed print events when available; ABI token transfers or public functions only when the contract has never printed. Review the TypeScript, run subgraphs_test until ok: true with written >= 1, read skipped, then subgraphs_deploy. dryRun is DDL only.", {
611
- contractId: z6.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
612
- subgraphName: z6.string().optional().describe("Override the subgraph name (defaults to contract name)")
613
- }, async ({ contractId, subgraphName }) => {
614
- const printSchema = await clientProvider().index.printSchema(contractId);
615
- if (printSchema && printSchema.topics.length > 0) {
616
- const code = generatePrintSchemaSubgraph({
617
- contractId,
618
- name: subgraphName,
619
- topics: printSchema.topics,
620
- sample: printSchema.sample
621
- });
622
- return { content: [{ type: "text", text: code }] };
623
- }
624
- const { functions, abi } = await fetchAbi(clientProvider, contractId);
625
- const token = generateTokenSubgraphFromAbi({
626
- contractId,
627
- abi,
628
- name: subgraphName
629
- });
630
- if (token) {
631
- return { content: [{ type: "text", text: token }] };
632
- }
633
- const code = generateSubgraphCode(contractId, functions, subgraphName);
634
- return { content: [{ type: "text", text: code }] };
635
- });
636
- }
637
-
638
- // src/tools/setup.ts
639
- import { z as z7 } from "zod";
640
447
 
641
448
  // src/lib/exec-cli.ts
642
449
  import {
643
450
  spawn
644
451
  } from "node:child_process";
645
- var ALLOWED = ["setup", "bootstrap", "repair", "verify"];
452
+ var ALLOWED = ["bootstrap", "repair", "verify"];
646
453
  var TIMEOUT_MS = {
647
- setup: 15 * 60 * 1000,
648
454
  bootstrap: 10 * 60 * 1000,
649
455
  repair: 10 * 60 * 1000,
650
456
  verify: 2 * 60 * 1000
651
457
  };
652
- var VALUE_FLAGS = new Set([
653
- "--against",
654
- "--from-block",
655
- "--to-block",
656
- "--network",
657
- "--node-mode",
658
- "--dir"
659
- ]);
660
- var BOOL_FLAGS = new Set(["--yes", "--json", "--apply", "--skip-bootstrap"]);
458
+ var VALUE_FLAGS = new Set(["--against", "--from-block", "--to-block"]);
459
+ var BOOL_FLAGS = new Set(["--yes", "--json", "--apply"]);
661
460
  var FLAGS_BY_CMD = {
662
- setup: new Set([
663
- "--yes",
664
- "--network",
665
- "--node-mode",
666
- "--against",
667
- "--dir",
668
- "--skip-bootstrap"
669
- ]),
670
461
  bootstrap: new Set([
671
462
  "--against",
672
463
  "--yes",
@@ -741,12 +532,8 @@ async function execSecondlayer(subcommand, args, deps = {}) {
741
532
  }
742
533
  assertSafeArgs(subcommand, args);
743
534
  const argv = [subcommand, ...args];
744
- if (subcommand === "setup") {
745
- if (!argv.includes("--yes"))
746
- argv.push("--yes");
747
- } else if (!argv.includes("--json")) {
535
+ if (!argv.includes("--json"))
748
536
  argv.push("--json");
749
- }
750
537
  const env = deps.env ?? process.env;
751
538
  const bin = env.SECONDLAYER_BIN || "secondlayer";
752
539
  const cwd = deps.cwd ?? env.SECONDLAYER_CWD ?? process.cwd();
@@ -794,31 +581,15 @@ timeout after ${timeout}ms`.trim()
794
581
  });
795
582
  }
796
583
 
797
- // src/tools/setup.ts
584
+ // src/tools/archive-cli.ts
798
585
  function cliResponse(result) {
799
586
  return jsonResponse(result, result.code !== 0);
800
587
  }
801
- function registerSetupTools(server, exec = execSecondlayer) {
802
- 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.", {
803
- network: z7.enum(["mainnet", "testnet", "devnet"]).describe("Stacks network"),
804
- nodeMode: z7.enum(["external", "stacks", "full"]).describe("Node mode: external, stacks, or full"),
805
- against: z7.string().optional().describe("Archive manifest URL"),
806
- dir: z7.string().optional().describe("Compose project directory"),
807
- skipBootstrap: z7.boolean().optional().describe("Sync from genesis instead of restoring an archive")
808
- }, async ({ network, nodeMode, against, dir, skipBootstrap }) => {
809
- const args = ["--yes", "--network", network, "--node-mode", nodeMode];
810
- if (against !== undefined)
811
- args.push("--against", against);
812
- if (dir !== undefined)
813
- args.push("--dir", dir);
814
- if (skipBootstrap)
815
- args.push("--skip-bootstrap");
816
- return cliResponse(await exec("setup", args));
817
- });
588
+ function registerArchiveCliTools(server, exec = execSecondlayer) {
818
589
  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.", {
819
- against: z7.string().describe("Archive manifest URL"),
820
- fromBlock: z7.number().int().nonnegative().optional().describe("Restore from this height instead of genesis"),
821
- toBlock: z7.number().int().nonnegative().optional().describe("Stop at this height instead of the archive tip")
590
+ against: z2.string().describe("Archive manifest URL"),
591
+ fromBlock: z2.number().int().nonnegative().optional().describe("Restore from this height instead of genesis"),
592
+ toBlock: z2.number().int().nonnegative().optional().describe("Stop at this height instead of the archive tip")
822
593
  }, async ({ against, fromBlock, toBlock }) => {
823
594
  const args = ["--against", against, "--yes", "--json"];
824
595
  if (fromBlock !== undefined)
@@ -828,10 +599,10 @@ function registerSetupTools(server, exec = execSecondlayer) {
828
599
  return cliResponse(await exec("bootstrap", args));
829
600
  });
830
601
  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.", {
831
- against: z7.string().describe("Archive manifest URL"),
832
- apply: z7.boolean().optional().describe("Write the repair (default plan-only)"),
833
- fromBlock: z7.number().int().nonnegative().optional().describe("First height to consider"),
834
- toBlock: z7.number().int().nonnegative().optional().describe("Last height to consider")
602
+ against: z2.string().describe("Archive manifest URL"),
603
+ apply: z2.boolean().optional().describe("Write the repair (default plan-only)"),
604
+ fromBlock: z2.number().int().nonnegative().optional().describe("First height to consider"),
605
+ toBlock: z2.number().int().nonnegative().optional().describe("Last height to consider")
835
606
  }, async ({ against, apply, fromBlock, toBlock }) => {
836
607
  const args = ["--against", against, "--json"];
837
608
  if (apply)
@@ -844,6 +615,197 @@ function registerSetupTools(server, exec = execSecondlayer) {
844
615
  });
845
616
  }
846
617
 
618
+ // src/tools/archive.ts
619
+ import { z as z3 } from "zod";
620
+ var FLOW = ["bootstrap", "repair"];
621
+ function registerArchiveTools(server, clientProvider = getClient, archiveOpsClientProvider = getArchiveOpsClient) {
622
+ 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.", {
623
+ against: z3.string().describe("Archive manifest URL"),
624
+ fromBlock: z3.number().int().nonnegative().optional().describe("First height (inclusive)"),
625
+ toBlock: z3.number().int().nonnegative().optional().describe("Last height (inclusive)"),
626
+ target: z3.string().optional().describe("Verify target (default raw)")
627
+ }, async ({ against, fromBlock, toBlock, target }) => jsonResponse(await clientProvider().archive.verify({
628
+ against,
629
+ fromBlock,
630
+ toBlock,
631
+ target
632
+ })));
633
+ 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.", {
634
+ against: z3.string().optional().describe("Override latest.json URL (default official tree)")
635
+ }, async ({ against }) => jsonResponse(await archiveOpsClientProvider().archive.latest(against)));
636
+ 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.", {
637
+ paths: z3.array(z3.string()).describe("Partition paths to price"),
638
+ flow: z3.enum(FLOW).describe("bootstrap or repair")
639
+ }, async ({ paths, flow }) => jsonResponse(await archiveOpsClientProvider().archive.quote({ paths, flow })));
640
+ 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()));
641
+ }
642
+
643
+ // src/tools/codegen.ts
644
+ import {
645
+ generateIndexSchema
646
+ } from "@secondlayer/subgraphs";
647
+ import { z as z4 } from "zod";
648
+ var TARGETS = ["prisma", "kysely", "drizzle", "json-schema"];
649
+ function registerCodegenTools(server) {
650
+ 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.", {
651
+ target: z4.enum(TARGETS).optional().describe("Output flavor (default kysely)"),
652
+ tables: z4.array(z4.string()).optional().describe("Restrict to these Index tables (default: all)"),
653
+ schemaName: z4.string().optional().describe("Postgres schema to qualify table names with"),
654
+ datasourceEnv: z4.string().optional().describe("Prisma only: datasource url env var (default DATABASE_URL)")
655
+ }, ({ target, tables, schemaName, datasourceEnv }) => textResponse(generateIndexSchema(target ?? "kysely", {
656
+ tables,
657
+ schemaName,
658
+ datasourceEnv
659
+ })));
660
+ }
661
+
662
+ // src/tools/contracts.ts
663
+ import { z as z5 } from "zod";
664
+ function registerContractTools(server, clientProvider = getClient) {
665
+ 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.', {
666
+ trait: z5.string().describe('Required. Trait to match (e.g. "sip-010").'),
667
+ conformance: z5.enum(["declared", "inferred", "any"]).optional().describe("declared = parsed from source, inferred = ABI shape-match, any = either (default)"),
668
+ include: z5.literal("abi").optional().describe(`Set to "abi" to include each contract's full ABI`),
669
+ limit: z5.number().optional().describe("Page size, 1–500 (default 100)"),
670
+ cursor: z5.string().optional().describe("Opaque cursor from a prior response's next_cursor")
671
+ }, async (params) => jsonResponse(await clientProvider().contracts.list(params)));
672
+ 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.", {
673
+ contractId: z5.string().describe("Fully qualified contract id (e.g. SP….amm-pool-v2-01)")
674
+ }, async ({ contractId }) => {
675
+ const contract = await clientProvider().contracts.get(contractId, {
676
+ includeAbi: true
677
+ });
678
+ return contract ? jsonResponse(contract) : jsonResponse({
679
+ error: {
680
+ type: "not_found",
681
+ status: 404,
682
+ message: `Contract not in registry: ${contractId}`
683
+ }
684
+ }, true);
685
+ });
686
+ }
687
+
688
+ // src/tools/index.ts
689
+ import { DECODED_EVENT_TYPES } from "@secondlayer/shared";
690
+ import { z as z6 } from "zod";
691
+ var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES;
692
+ var rangeFilters = {
693
+ contractId: z6.string().optional().describe("Filter by contract id"),
694
+ fromHeight: z6.number().optional().describe("Start block height (inclusive)"),
695
+ toHeight: z6.number().optional().describe("End block height (inclusive)"),
696
+ cursor: z6.string().optional().describe("Opaque cursor from a prior response's next_cursor"),
697
+ limit: z6.number().optional().describe("Max rows for this page")
698
+ };
699
+ var heightFilters = {
700
+ fromHeight: rangeFilters.fromHeight,
701
+ toHeight: rangeFilters.toHeight,
702
+ cursor: rangeFilters.cursor,
703
+ limit: rangeFilters.limit
704
+ };
705
+ function registerIndexTools(server, clientProvider = getClient) {
706
+ defineTool(server, "index_ft_transfers", "List decoded SIP-010 fungible-token transfers from Index. Reads are keyless.", {
707
+ ...rangeFilters,
708
+ sender: z6.string().optional().describe("Filter by sender principal"),
709
+ recipient: z6.string().optional().describe("Filter by recipient principal"),
710
+ assetIdentifier: z6.string().optional().describe("Filter by asset identifier (contract::asset), e.g. SP…sbtc-token::sbtc-token")
711
+ }, async (params) => jsonResponse(await clientProvider().index.ftTransfers.list(params)));
712
+ defineTool(server, "index_nft_transfers", "List decoded SIP-009 non-fungible-token transfers from Index. Reads are keyless.", {
713
+ ...rangeFilters,
714
+ sender: z6.string().optional().describe("Filter by sender principal"),
715
+ recipient: z6.string().optional().describe("Filter by recipient principal"),
716
+ assetIdentifier: z6.string().optional().describe("Filter by asset identifier (contract::asset)")
717
+ }, async (params) => jsonResponse(await clientProvider().index.nftTransfers.list(params)));
718
+ 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.", {
719
+ eventType: z6.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
720
+ ...rangeFilters,
721
+ sender: z6.string().optional().describe("Filter by sender principal"),
722
+ recipient: z6.string().optional().describe("Filter by recipient principal"),
723
+ assetIdentifier: z6.string().optional().describe("Filter by asset identifier where applicable"),
724
+ trait: z6.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId; contract-keyed event types only."),
725
+ txContext: z6.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.")
726
+ }, async (params) => jsonResponse(await clientProvider().index.events.list(params)));
727
+ 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.", {
728
+ ...rangeFilters,
729
+ functionName: z6.string().optional().describe("Filter by called function name"),
730
+ sender: z6.string().optional().describe("Filter by caller principal"),
731
+ trait: z6.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
732
+ }, async (params) => jsonResponse(await clientProvider().index.contractCalls.list(params)));
733
+ defineTool(server, "index_blocks", "List decoded blocks from Index. Reads are keyless.", { ...heightFilters }, async (params) => jsonResponse(await clientProvider().index.blocks.list(params)));
734
+ defineTool(server, "index_transactions", "List decoded transactions from Index. Filter by type, sender, or contract. Reads are keyless.", {
735
+ ...rangeFilters,
736
+ type: z6.string().optional().describe("Filter by transaction type"),
737
+ sender: z6.string().optional().describe("Filter by sender principal")
738
+ }, async (params) => jsonResponse(await clientProvider().index.transactions.list(params)));
739
+ 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.", {
740
+ contractId: z6.string().describe("Contract id (SP….name) whose print events to profile")
741
+ }, async (params) => jsonResponse(await clientProvider().index.printSchema(params.contractId)));
742
+ 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()));
743
+ 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.", {
744
+ requests: z6.array(z6.object({
745
+ path: z6.string().describe("Public /v1 read path"),
746
+ params: z6.record(z6.string(), z6.string()).optional()
747
+ })).min(1).max(10).describe("Read descriptors, executed concurrently")
748
+ }, async (params) => jsonResponse(await clientProvider().batch(params.requests)));
749
+ }
750
+
751
+ // src/tools/instance.ts
752
+ function registerInstanceTools(server, clientProvider = getClient) {
753
+ defineTool(server, "instance_status", "Decoder health and empty-index diagnosis from GET /public/status. If state is empty-index, next tool is archive_bootstrap. 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 () => {
754
+ const client = clientProvider();
755
+ const status = await client.instance.status();
756
+ const diagnosis = await client.instance.diagnose();
757
+ return jsonResponse({ status, diagnosis });
758
+ });
759
+ }
760
+
761
+ // src/tools/scaffold.ts
762
+ import {
763
+ generatePrintSchemaSubgraph,
764
+ generateSubgraphCode,
765
+ generateTokenSubgraphFromAbi
766
+ } from "@secondlayer/scaffold";
767
+ import { z as z7 } from "zod";
768
+ async function fetchAbi(clientProvider, contractId) {
769
+ const contract = await clientProvider().contracts.get(contractId, {
770
+ includeAbi: true
771
+ });
772
+ if (!contract)
773
+ throw new Error(`Contract not found: ${contractId}`);
774
+ const abi = contract.abi;
775
+ if (!abi) {
776
+ throw new Error(`No ABI available for ${contractId} (abi_status: ${contract.abi_status})`);
777
+ }
778
+ return { functions: abi.functions ?? [], abi };
779
+ }
780
+ function registerScaffoldTools(server, clientProvider = getClient) {
781
+ defineTool(server, "subgraphs_scaffold", "Generate a subgraph from observed print events when available; ABI token transfers or public functions only when the contract has never printed. Review the TypeScript, run subgraphs_test until ok: true with written >= 1, read skipped, then subgraphs_deploy. dryRun is DDL only.", {
782
+ contractId: z7.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
783
+ subgraphName: z7.string().optional().describe("Override the subgraph name (defaults to contract name)")
784
+ }, async ({ contractId, subgraphName }) => {
785
+ const printSchema = await clientProvider().index.printSchema(contractId);
786
+ if (printSchema && printSchema.topics.length > 0) {
787
+ const code = generatePrintSchemaSubgraph({
788
+ contractId,
789
+ name: subgraphName,
790
+ topics: printSchema.topics,
791
+ sample: printSchema.sample
792
+ });
793
+ return { content: [{ type: "text", text: code }] };
794
+ }
795
+ const { functions, abi } = await fetchAbi(clientProvider, contractId);
796
+ const token = generateTokenSubgraphFromAbi({
797
+ contractId,
798
+ abi,
799
+ name: subgraphName
800
+ });
801
+ if (token) {
802
+ return { content: [{ type: "text", text: token }] };
803
+ }
804
+ const code = generateSubgraphCode(contractId, functions, subgraphName);
805
+ return { content: [{ type: "text", text: code }] };
806
+ });
807
+ }
808
+
847
809
  // src/tools/streams.ts
848
810
  function registerStreamsTools(server, clientProvider = getClient) {
849
811
  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()));
@@ -901,7 +863,7 @@ function registerSubgraphTools(server, clientProvider = getClient) {
901
863
  return textResponse(await client.subgraphs.markdown(name));
902
864
  return jsonResponse(await client.subgraphs.schema(name));
903
865
  });
904
- defineTool(server, "subgraphs_query", 'Query rows from a subgraph table (max 200 rows), reading the open /v1 surface — { rows, next_cursor, tip } envelope, cursor-paginated (no offset). 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 response\'s nextCursor resumes the same page: pass it back as `cursor` on the next call. The same rows are readable over HTTP at GET /v1/subgraphs/<name>/<table>; hand that URL to third parties.', {
866
+ defineTool(server, "subgraphs_query", 'Query rows from a subgraph table (max 200 rows), reading the open /v1 surface — { rows, next_cursor, tip } envelope, cursor-paginated (no offset). Filters support operators: "amount.gte": "1000", "sender.neq": "SP...", "name.like": "%token%". Available operators: eq, neq, gt, gte, lt, lte, like. Every page with rows returns next_cursor (null only when empty): pass it back as `cursor` to read the next page. To TAIL new rows (no streaming over MCP), poll with cursor = next_cursor ?? the cursor you sent. Fetch one row by id with {"_id": "<id>"}. The same rows are readable over HTTP at GET /v1/subgraphs/<name>/<table>; hand that URL to third parties.', {
905
867
  name: z8.string().describe("Subgraph name"),
906
868
  table: z8.string().describe("Table name"),
907
869
  filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
@@ -1192,7 +1154,7 @@ var TOOL_GROUPS = [
1192
1154
  { group: "codegen", register: registerCodegenTools },
1193
1155
  { group: "instance", register: registerInstanceTools },
1194
1156
  { group: "archive", register: registerArchiveTools },
1195
- { group: "setup", register: registerSetupTools },
1157
+ { group: "archive", register: registerArchiveCliTools },
1196
1158
  { group: "account", register: registerAccountTools, hostedOnly: true }
1197
1159
  ];
1198
1160
 
@@ -1300,5 +1262,5 @@ httpServer.listen(port, () => {
1300
1262
  console.error("Warning: SECONDLAYER_MCP_SECRET not set, authentication disabled");
1301
1263
  });
1302
1264
 
1303
- //# debugId=327D609CB47BF60264756E2164756E21
1265
+ //# debugId=52D7E49B3C2D102864756E2164756E21
1304
1266
  //# sourceMappingURL=bin-http.js.map