@secondlayer/mcp 10.0.1 → 11.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -20,6 +20,63 @@ function isHostedArchiveUrl(url) {
20
20
  }
21
21
  }
22
22
 
23
+ // src/lib/tool.ts
24
+ var registeredToolNames = new Set;
25
+ var serverToolNames = new WeakMap;
26
+ var serverToolFilters = new WeakMap;
27
+ function getRegisteredToolNames(server) {
28
+ return server ? [...serverToolNames.get(server) ?? []] : [...registeredToolNames];
29
+ }
30
+ function restrictTools(server, allow) {
31
+ serverToolFilters.set(server, allow);
32
+ }
33
+ function envelopeFields(err) {
34
+ if (!err || typeof err !== "object")
35
+ return {};
36
+ const e = err;
37
+ const body = e.body && typeof e.body === "object" ? e.body : {};
38
+ const code = typeof e.code === "string" ? e.code : typeof body.code === "string" ? body.code : undefined;
39
+ const request_id = typeof body.request_id === "string" ? body.request_id : undefined;
40
+ const fb = body.feedback;
41
+ const feedback = fb && typeof fb === "object" && typeof fb.url === "string" ? { url: fb.url } : undefined;
42
+ return {
43
+ ...code ? { code } : {},
44
+ ...request_id ? { request_id } : {},
45
+ ...feedback ? { feedback } : {}
46
+ };
47
+ }
48
+ function defineTool(server, name, description, schema, handler) {
49
+ const wrappedHandler = async (args) => {
50
+ try {
51
+ return await handler(args);
52
+ } catch (err) {
53
+ const message = err instanceof Error ? err.message : String(err);
54
+ const status = err instanceof Error && "status" in err ? err.status : 0;
55
+ const type = status === 401 ? "unauthorized" : status === 404 ? "not_found" : status === 429 ? "rate_limited" : status >= 500 ? "server_error" : "error";
56
+ return {
57
+ content: [
58
+ {
59
+ type: "text",
60
+ text: JSON.stringify({
61
+ error: { type, status, message, ...envelopeFields(err) }
62
+ })
63
+ }
64
+ ],
65
+ isError: true
66
+ };
67
+ }
68
+ };
69
+ const allow = serverToolFilters.get(server);
70
+ if (allow && !allow(name))
71
+ return;
72
+ if (!allow)
73
+ registeredToolNames.add(name);
74
+ const names = serverToolNames.get(server) ?? new Set;
75
+ names.add(name);
76
+ serverToolNames.set(server, names);
77
+ server.tool(name, description, schema, wrappedHandler);
78
+ }
79
+
23
80
  // src/resources.ts
24
81
  import {
25
82
  ResourceTemplate
@@ -80,37 +137,40 @@ function getArchiveOpsClient() {
80
137
  function keyHint(baseUrl = resolveBaseUrl()) {
81
138
  return isHostedApiUrl(baseUrl) ? " — set SECONDLAYER_API_KEY (an sk-sl_ key from secondlayer.tools/account/keys)" : " — set INSTANCE_TOKEN from `sl init` for writes";
82
139
  }
83
- async function apiRequest(method, path, body) {
84
- const baseUrl = resolveBaseUrl();
85
- const apiKey = readApiKey();
86
- const res = await fetch(`${baseUrl}${path}`, {
87
- method,
88
- headers: {
89
- "Content-Type": "application/json",
90
- ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
91
- },
92
- body: body ? JSON.stringify(body) : undefined
93
- });
94
- if (!res.ok) {
95
- const text = await res.text().catch(() => "");
96
- const needsKey = !apiKey && (res.status === 401 || res.status === 403);
97
- let parsed;
98
- try {
99
- parsed = text ? JSON.parse(text) : undefined;
100
- } catch {
101
- parsed = undefined;
102
- }
103
- const code = parsed && typeof parsed === "object" && typeof parsed.code === "string" ? parsed.code : undefined;
104
- throw Object.assign(new Error((text || `HTTP ${res.status}`) + (needsKey ? keyHint(baseUrl) : "")), {
105
- status: res.status,
106
- ...parsed !== undefined ? { body: parsed } : {},
107
- ...code ? { code } : {}
140
+ function createApiRequest(config) {
141
+ const doFetch = config.fetchImpl ?? ((...args) => fetch(...args));
142
+ return async (method, path, body) => {
143
+ const { baseUrl, apiKey } = config;
144
+ const res = await doFetch(`${baseUrl}${path}`, {
145
+ method,
146
+ headers: {
147
+ "Content-Type": "application/json",
148
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
149
+ },
150
+ body: body ? JSON.stringify(body) : undefined
108
151
  });
109
- }
110
- if (res.status === 204)
111
- return;
112
- return res.json();
152
+ if (!res.ok) {
153
+ const text = await res.text().catch(() => "");
154
+ const needsKey = !apiKey && (res.status === 401 || res.status === 403);
155
+ let parsed;
156
+ try {
157
+ parsed = text ? JSON.parse(text) : undefined;
158
+ } catch {
159
+ parsed = undefined;
160
+ }
161
+ const code = parsed && typeof parsed === "object" && typeof parsed.code === "string" ? parsed.code : undefined;
162
+ throw Object.assign(new Error((text || `HTTP ${res.status}`) + (needsKey ? keyHint(baseUrl) : "")), {
163
+ status: res.status,
164
+ ...parsed !== undefined ? { body: parsed } : {},
165
+ ...code ? { code } : {}
166
+ });
167
+ }
168
+ if (res.status === 204)
169
+ return;
170
+ return res.json();
171
+ };
113
172
  }
173
+ var apiRequest = (method, path, body) => createApiRequest({ baseUrl: resolveBaseUrl(), apiKey: readApiKey() })(method, path, body);
114
174
 
115
175
  // src/lib/format.ts
116
176
  function formatSubgraphSummary(s) {
@@ -141,51 +201,6 @@ function textResponse(text, isError) {
141
201
  };
142
202
  }
143
203
 
144
- // src/lib/tool.ts
145
- var registeredToolNames = new Set;
146
- function getRegisteredToolNames() {
147
- return [...registeredToolNames];
148
- }
149
- function envelopeFields(err) {
150
- if (!err || typeof err !== "object")
151
- return {};
152
- const e = err;
153
- const body = e.body && typeof e.body === "object" ? e.body : {};
154
- const code = typeof e.code === "string" ? e.code : typeof body.code === "string" ? body.code : undefined;
155
- const request_id = typeof body.request_id === "string" ? body.request_id : undefined;
156
- const fb = body.feedback;
157
- const feedback = fb && typeof fb === "object" && typeof fb.url === "string" ? { url: fb.url } : undefined;
158
- return {
159
- ...code ? { code } : {},
160
- ...request_id ? { request_id } : {},
161
- ...feedback ? { feedback } : {}
162
- };
163
- }
164
- function defineTool(server, name, description, schema, handler) {
165
- const wrappedHandler = async (args) => {
166
- try {
167
- return await handler(args);
168
- } catch (err) {
169
- const message = err instanceof Error ? err.message : String(err);
170
- const status = err instanceof Error && "status" in err ? err.status : 0;
171
- const type = status === 401 ? "unauthorized" : status === 404 ? "not_found" : status === 429 ? "rate_limited" : status >= 500 ? "server_error" : "error";
172
- return {
173
- content: [
174
- {
175
- type: "text",
176
- text: JSON.stringify({
177
- error: { type, status, message, ...envelopeFields(err) }
178
- })
179
- }
180
- ],
181
- isError: true
182
- };
183
- }
184
- };
185
- registeredToolNames.add(name);
186
- server.tool(name, description, schema, wrappedHandler);
187
- }
188
-
189
204
  // src/resources.ts
190
205
  var TRAIT_BLURBS = {
191
206
  "sip-009": "Non-fungible token (NFT) standard",
@@ -223,7 +238,6 @@ var PRODUCT_BLURBS = {
223
238
  codegen: "ORM schemas for the tables you read",
224
239
  instance: "decoder health and empty-index diagnosis",
225
240
  archive: "signed archive verify, bootstrap/repair, quote, latest pointer",
226
- setup: "one-command self-host onboarding",
227
241
  credits: "archive credits balance at api.secondlayer.tools"
228
242
  };
229
243
  var PRODUCT_ORDER = [
@@ -236,12 +250,11 @@ var PRODUCT_ORDER = [
236
250
  "codegen",
237
251
  "instance",
238
252
  "archive",
239
- "setup",
240
253
  "credits"
241
254
  ];
242
- function buildCapabilities() {
255
+ function buildCapabilities(server) {
243
256
  const byPrefix = new Map;
244
- for (const name of getRegisteredToolNames()) {
257
+ for (const name of getRegisteredToolNames(server)) {
245
258
  const sep = name.indexOf("_");
246
259
  const prefix = sep === -1 ? name : name.slice(0, sep);
247
260
  const tools = byPrefix.get(prefix) ?? [];
@@ -262,7 +275,11 @@ function buildCapabilities() {
262
275
  discoverFirst: "Call index_discover / contracts_find to learn what exists (and which filters each surface accepts) before querying."
263
276
  };
264
277
  }
265
- function readAuthTiers(credential) {
278
+ function readAuthTiers(credential, hosted) {
279
+ if (hosted) {
280
+ const reads = `keyed: every read uses ${credential} and is metered to the account's credits (the first 1M rows each month are free)`;
281
+ return { index: reads, streams: reads };
282
+ }
266
283
  const reads = `keyless while the API is only reachable on loopback; ${credential} is required once it is published past loopback`;
267
284
  return {
268
285
  index: reads,
@@ -273,8 +290,9 @@ function readAuthTiers(credential) {
273
290
  function credentialLabel() {
274
291
  return isHostedApiUrl2(resolveBaseUrl2()) ? "SECONDLAYER_API_KEY (an sk-sl_ account key)" : "INSTANCE_TOKEN";
275
292
  }
293
+ var HOSTED_CREDENTIAL = "the caller's OAuth sign-in (or sk-sl_ account key)";
276
294
  async function buildContext(deps = { clientProvider: getClient }) {
277
- const credential = credentialLabel();
295
+ const credential = deps.hosted ? HOSTED_CREDENTIAL : credentialLabel();
278
296
  const unavailable = `unavailable: set ${credential}`;
279
297
  const orNull = (field) => {
280
298
  if (!field)
@@ -287,7 +305,7 @@ async function buildContext(deps = { clientProvider: getClient }) {
287
305
  const instance = snap?.instance?.value ? snap.instance.value : orNull(snap?.instance);
288
306
  return {
289
307
  authState: {
290
- apiKeySet: Boolean(readApiKey())
308
+ apiKeySet: deps.hosted ? true : Boolean(readApiKey())
291
309
  },
292
310
  whatExists: {
293
311
  account: orNull(snap?.account),
@@ -298,12 +316,13 @@ async function buildContext(deps = { clientProvider: getClient }) {
298
316
  activeOperations: orNull(snap?.activeOperations),
299
317
  instance
300
318
  },
301
- whatYouCanDo: buildCapabilities(),
302
- readAuthTiers: readAuthTiers(credential),
319
+ whatYouCanDo: buildCapabilities(deps.server),
320
+ readAuthTiers: readAuthTiers(credential, Boolean(deps.hosted)),
303
321
  ...snap?.instance?.value?.state === "empty-index" ? { nextStep: BOOTSTRAP_STEP } : {}
304
322
  };
305
323
  }
306
- function registerResources(server) {
324
+ function registerResources(server, deps = { clientProvider: getClient }) {
325
+ const contextDeps = deps.hosted ? { ...deps, server } : deps;
307
326
  server.resource("context", "secondlayer://context", {
308
327
  description: "Live agent context — what exists (your subgraphs, webhooks, account), what you can do, and read-auth tiers. Read this first."
309
328
  }, async () => ({
@@ -311,7 +330,7 @@ function registerResources(server) {
311
330
  {
312
331
  uri: "secondlayer://context",
313
332
  mimeType: "application/json",
314
- text: JSON.stringify(await buildContext(), null, 2)
333
+ text: JSON.stringify(await buildContext(contextDeps), null, 2)
315
334
  }
316
335
  ]
317
336
  }));
@@ -349,24 +368,26 @@ function registerResources(server) {
349
368
  }
350
369
  ]
351
370
  }));
352
- server.resource("chain-triggers", "secondlayer://chain-triggers", {
353
- description: "Chain-webhook trigger types and the filter fields each accepts (for webhooks_create triggers)."
354
- }, async () => ({
355
- contents: [
356
- {
357
- uri: "secondlayer://chain-triggers",
358
- mimeType: "application/json",
359
- text: JSON.stringify(CHAIN_TRIGGER_FIELDS, null, 2)
360
- }
361
- ]
362
- }));
371
+ if (!deps.hosted) {
372
+ server.resource("chain-triggers", "secondlayer://chain-triggers", {
373
+ description: "Chain-webhook trigger types and the filter fields each accepts (for webhooks_create triggers)."
374
+ }, async () => ({
375
+ contents: [
376
+ {
377
+ uri: "secondlayer://chain-triggers",
378
+ mimeType: "application/json",
379
+ text: JSON.stringify(CHAIN_TRIGGER_FIELDS, null, 2)
380
+ }
381
+ ]
382
+ }));
383
+ }
363
384
  server.resource("samples", new ResourceTemplate("secondlayer://samples/{contractId}", {
364
385
  list: undefined
365
386
  }), {
366
387
  description: "Decoded camelCase print samples for a contract (schema + up to 5 events). Use before writing a print_event handler."
367
388
  }, async (uri, variables) => {
368
389
  const contractId = String(Array.isArray(variables.contractId) ? variables.contractId[0] : variables.contractId ?? "");
369
- const body = await buildSamplesResource(contractId);
390
+ const body = await buildSamplesResource(contractId, deps.clientProvider);
370
391
  return {
371
392
  contents: [
372
393
  {
@@ -431,239 +452,32 @@ async function buildSamplesResource(contractId, clientProvider = getClient) {
431
452
 
432
453
  // src/tools/account.ts
433
454
  import { z } from "zod";
434
- function registerAccountTools(server) {
455
+ function registerAccountTools(server, request = apiRequest) {
435
456
  defineTool(server, "account_whoami", "Show the archive account email at api.secondlayer.tools, not the self-host instance.", {}, async () => {
436
- const result = await apiRequest("GET", "/api/accounts/me");
457
+ const result = await request("GET", "/api/accounts/me");
437
458
  return jsonResponse(result);
438
459
  });
439
460
  defineTool(server, "account_create_key", "Mint an account key (sk-sl_*) for archive credits, quote, and fetch at api.secondlayer.tools. The returned `key` is shown ONCE.", {
440
461
  name: z.string().optional().describe("Optional label for the key")
441
- }, async ({ name }) => jsonResponse(await apiRequest("POST", "/api/keys", { name })));
462
+ }, async ({ name }) => jsonResponse(await request("POST", "/api/keys", { name })));
442
463
  }
443
464
 
444
- // src/tools/archive.ts
465
+ // src/tools/archive-cli.ts
445
466
  import { z as z2 } from "zod";
446
- var FLOW = ["bootstrap", "repair"];
447
- function registerArchiveTools(server, clientProvider = getClient, archiveOpsClientProvider = getArchiveOpsClient) {
448
- 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.", {
449
- against: z2.string().describe("Archive manifest URL"),
450
- fromBlock: z2.number().int().nonnegative().optional().describe("First height (inclusive)"),
451
- toBlock: z2.number().int().nonnegative().optional().describe("Last height (inclusive)"),
452
- target: z2.string().optional().describe("Verify target (default raw)")
453
- }, async ({ against, fromBlock, toBlock, target }) => jsonResponse(await clientProvider().archive.verify({
454
- against,
455
- fromBlock,
456
- toBlock,
457
- target
458
- })));
459
- 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.", {
460
- against: z2.string().optional().describe("Override latest.json URL (default official tree)")
461
- }, async ({ against }) => jsonResponse(await archiveOpsClientProvider().archive.latest(against)));
462
- 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.", {
463
- paths: z2.array(z2.string()).describe("Partition paths to price"),
464
- flow: z2.enum(FLOW).describe("bootstrap or repair")
465
- }, async ({ paths, flow }) => jsonResponse(await archiveOpsClientProvider().archive.quote({ paths, flow })));
466
- 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()));
467
- }
468
-
469
- // src/tools/codegen.ts
470
- import {
471
- generateIndexSchema
472
- } from "@secondlayer/subgraphs";
473
- import { z as z3 } from "zod";
474
- var TARGETS = ["prisma", "kysely", "drizzle", "json-schema"];
475
- function registerCodegenTools(server) {
476
- 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.", {
477
- target: z3.enum(TARGETS).optional().describe("Output flavor (default kysely)"),
478
- tables: z3.array(z3.string()).optional().describe("Restrict to these Index tables (default: all)"),
479
- schemaName: z3.string().optional().describe("Postgres schema to qualify table names with"),
480
- datasourceEnv: z3.string().optional().describe("Prisma only: datasource url env var (default DATABASE_URL)")
481
- }, ({ target, tables, schemaName, datasourceEnv }) => textResponse(generateIndexSchema(target ?? "kysely", {
482
- tables,
483
- schemaName,
484
- datasourceEnv
485
- })));
486
- }
487
-
488
- // src/tools/contracts.ts
489
- import { z as z4 } from "zod";
490
- function registerContractTools(server, clientProvider = getClient) {
491
- 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.', {
492
- trait: z4.string().describe('Required. Trait to match (e.g. "sip-010").'),
493
- conformance: z4.enum(["declared", "inferred", "any"]).optional().describe("declared = parsed from source, inferred = ABI shape-match, any = either (default)"),
494
- include: z4.literal("abi").optional().describe(`Set to "abi" to include each contract's full ABI`),
495
- limit: z4.number().optional().describe("Page size, 1–500 (default 100)"),
496
- cursor: z4.string().optional().describe("Opaque cursor from a prior response's next_cursor")
497
- }, async (params) => jsonResponse(await clientProvider().contracts.list(params)));
498
- 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.", {
499
- contractId: z4.string().describe("Fully qualified contract id (e.g. SP….amm-pool-v2-01)")
500
- }, async ({ contractId }) => {
501
- const contract = await clientProvider().contracts.get(contractId, {
502
- includeAbi: true
503
- });
504
- return contract ? jsonResponse(contract) : jsonResponse({
505
- error: {
506
- type: "not_found",
507
- status: 404,
508
- message: `Contract not in registry: ${contractId}`
509
- }
510
- }, true);
511
- });
512
- }
513
-
514
- // src/tools/index.ts
515
- import { DECODED_EVENT_TYPES } from "@secondlayer/shared";
516
- import { z as z5 } from "zod";
517
- var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES;
518
- var rangeFilters = {
519
- contractId: z5.string().optional().describe("Filter by contract id"),
520
- fromHeight: z5.number().optional().describe("Start block height (inclusive)"),
521
- toHeight: z5.number().optional().describe("End block height (inclusive)"),
522
- cursor: z5.string().optional().describe("Opaque cursor from a prior response's next_cursor"),
523
- limit: z5.number().optional().describe("Max rows for this page")
524
- };
525
- var heightFilters = {
526
- fromHeight: rangeFilters.fromHeight,
527
- toHeight: rangeFilters.toHeight,
528
- cursor: rangeFilters.cursor,
529
- limit: rangeFilters.limit
530
- };
531
- function registerIndexTools(server, clientProvider = getClient) {
532
- defineTool(server, "index_ft_transfers", "List decoded SIP-010 fungible-token transfers from Index. Reads are keyless.", {
533
- ...rangeFilters,
534
- sender: z5.string().optional().describe("Filter by sender principal"),
535
- recipient: z5.string().optional().describe("Filter by recipient principal"),
536
- assetIdentifier: z5.string().optional().describe("Filter by asset identifier (contract::asset), e.g. SP…sbtc-token::sbtc-token")
537
- }, async (params) => jsonResponse(await clientProvider().index.ftTransfers.list(params)));
538
- defineTool(server, "index_nft_transfers", "List decoded SIP-009 non-fungible-token transfers from Index. Reads are keyless.", {
539
- ...rangeFilters,
540
- sender: z5.string().optional().describe("Filter by sender principal"),
541
- recipient: z5.string().optional().describe("Filter by recipient principal"),
542
- assetIdentifier: z5.string().optional().describe("Filter by asset identifier (contract::asset)")
543
- }, async (params) => jsonResponse(await clientProvider().index.nftTransfers.list(params)));
544
- 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.", {
545
- eventType: z5.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
546
- ...rangeFilters,
547
- sender: z5.string().optional().describe("Filter by sender principal"),
548
- recipient: z5.string().optional().describe("Filter by recipient principal"),
549
- assetIdentifier: z5.string().optional().describe("Filter by asset identifier where applicable"),
550
- 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."),
551
- 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.")
552
- }, async (params) => jsonResponse(await clientProvider().index.events.list(params)));
553
- 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.", {
554
- ...rangeFilters,
555
- functionName: z5.string().optional().describe("Filter by called function name"),
556
- sender: z5.string().optional().describe("Filter by caller principal"),
557
- trait: z5.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
558
- }, async (params) => jsonResponse(await clientProvider().index.contractCalls.list(params)));
559
- defineTool(server, "index_blocks", "List decoded blocks from Index. Reads are keyless.", { ...heightFilters }, async (params) => jsonResponse(await clientProvider().index.blocks.list(params)));
560
- defineTool(server, "index_transactions", "List decoded transactions from Index. Filter by type, sender, or contract. Reads are keyless.", {
561
- ...rangeFilters,
562
- type: z5.string().optional().describe("Filter by transaction type"),
563
- sender: z5.string().optional().describe("Filter by sender principal")
564
- }, async (params) => jsonResponse(await clientProvider().index.transactions.list(params)));
565
- 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.", {
566
- contractId: z5.string().describe("Contract id (SP….name) whose print events to profile")
567
- }, async (params) => jsonResponse(await clientProvider().index.printSchema(params.contractId)));
568
- 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()));
569
- 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.", {
570
- requests: z5.array(z5.object({
571
- path: z5.string().describe("Public /v1 read path"),
572
- params: z5.record(z5.string(), z5.string()).optional()
573
- })).min(1).max(10).describe("Read descriptors, executed concurrently")
574
- }, async (params) => jsonResponse(await clientProvider().batch(params.requests)));
575
- }
576
-
577
- // src/tools/instance.ts
578
- function registerInstanceTools(server, clientProvider = getClient) {
579
- 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 () => {
580
- const client = clientProvider();
581
- const status = await client.instance.status();
582
- const diagnosis = await client.instance.diagnose();
583
- return jsonResponse({ status, diagnosis });
584
- });
585
- }
586
-
587
- // src/tools/scaffold.ts
588
- import {
589
- generatePrintSchemaSubgraph,
590
- generateSubgraphCode,
591
- generateTokenSubgraphFromAbi
592
- } from "@secondlayer/scaffold";
593
- import { z as z6 } from "zod";
594
- async function fetchAbi(clientProvider, contractId) {
595
- const contract = await clientProvider().contracts.get(contractId, {
596
- includeAbi: true
597
- });
598
- if (!contract)
599
- throw new Error(`Contract not found: ${contractId}`);
600
- const abi = contract.abi;
601
- if (!abi) {
602
- throw new Error(`No ABI available for ${contractId} (abi_status: ${contract.abi_status})`);
603
- }
604
- return { functions: abi.functions ?? [], abi };
605
- }
606
- function registerScaffoldTools(server, clientProvider = getClient) {
607
- 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.", {
608
- contractId: z6.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
609
- subgraphName: z6.string().optional().describe("Override the subgraph name (defaults to contract name)")
610
- }, async ({ contractId, subgraphName }) => {
611
- const printSchema = await clientProvider().index.printSchema(contractId);
612
- if (printSchema && printSchema.topics.length > 0) {
613
- const code = generatePrintSchemaSubgraph({
614
- contractId,
615
- name: subgraphName,
616
- topics: printSchema.topics,
617
- sample: printSchema.sample
618
- });
619
- return { content: [{ type: "text", text: code }] };
620
- }
621
- const { functions, abi } = await fetchAbi(clientProvider, contractId);
622
- const token = generateTokenSubgraphFromAbi({
623
- contractId,
624
- abi,
625
- name: subgraphName
626
- });
627
- if (token) {
628
- return { content: [{ type: "text", text: token }] };
629
- }
630
- const code = generateSubgraphCode(contractId, functions, subgraphName);
631
- return { content: [{ type: "text", text: code }] };
632
- });
633
- }
634
-
635
- // src/tools/setup.ts
636
- import { z as z7 } from "zod";
637
467
 
638
468
  // src/lib/exec-cli.ts
639
469
  import {
640
470
  spawn
641
471
  } from "node:child_process";
642
- var ALLOWED = ["setup", "bootstrap", "repair", "verify"];
472
+ var ALLOWED = ["bootstrap", "repair", "verify"];
643
473
  var TIMEOUT_MS = {
644
- setup: 15 * 60 * 1000,
645
474
  bootstrap: 10 * 60 * 1000,
646
475
  repair: 10 * 60 * 1000,
647
476
  verify: 2 * 60 * 1000
648
477
  };
649
- var VALUE_FLAGS = new Set([
650
- "--against",
651
- "--from-block",
652
- "--to-block",
653
- "--network",
654
- "--node-mode",
655
- "--dir"
656
- ]);
657
- var BOOL_FLAGS = new Set(["--yes", "--json", "--apply", "--skip-bootstrap"]);
478
+ var VALUE_FLAGS = new Set(["--against", "--from-block", "--to-block"]);
479
+ var BOOL_FLAGS = new Set(["--yes", "--json", "--apply"]);
658
480
  var FLAGS_BY_CMD = {
659
- setup: new Set([
660
- "--yes",
661
- "--network",
662
- "--node-mode",
663
- "--against",
664
- "--dir",
665
- "--skip-bootstrap"
666
- ]),
667
481
  bootstrap: new Set([
668
482
  "--against",
669
483
  "--yes",
@@ -738,12 +552,8 @@ async function execSecondlayer(subcommand, args, deps = {}) {
738
552
  }
739
553
  assertSafeArgs(subcommand, args);
740
554
  const argv = [subcommand, ...args];
741
- if (subcommand === "setup") {
742
- if (!argv.includes("--yes"))
743
- argv.push("--yes");
744
- } else if (!argv.includes("--json")) {
555
+ if (!argv.includes("--json"))
745
556
  argv.push("--json");
746
- }
747
557
  const env = deps.env ?? process.env;
748
558
  const bin = env.SECONDLAYER_BIN || "secondlayer";
749
559
  const cwd = deps.cwd ?? env.SECONDLAYER_CWD ?? process.cwd();
@@ -791,31 +601,15 @@ timeout after ${timeout}ms`.trim()
791
601
  });
792
602
  }
793
603
 
794
- // src/tools/setup.ts
604
+ // src/tools/archive-cli.ts
795
605
  function cliResponse(result) {
796
606
  return jsonResponse(result, result.code !== 0);
797
607
  }
798
- function registerSetupTools(server, exec = execSecondlayer) {
799
- 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.", {
800
- network: z7.enum(["mainnet", "testnet", "devnet"]).describe("Stacks network"),
801
- nodeMode: z7.enum(["external", "stacks", "full"]).describe("Node mode: external, stacks, or full"),
802
- against: z7.string().optional().describe("Archive manifest URL"),
803
- dir: z7.string().optional().describe("Compose project directory"),
804
- skipBootstrap: z7.boolean().optional().describe("Sync from genesis instead of restoring an archive")
805
- }, async ({ network, nodeMode, against, dir, skipBootstrap }) => {
806
- const args = ["--yes", "--network", network, "--node-mode", nodeMode];
807
- if (against !== undefined)
808
- args.push("--against", against);
809
- if (dir !== undefined)
810
- args.push("--dir", dir);
811
- if (skipBootstrap)
812
- args.push("--skip-bootstrap");
813
- return cliResponse(await exec("setup", args));
814
- });
608
+ function registerArchiveCliTools(server, exec = execSecondlayer) {
815
609
  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.", {
816
- against: z7.string().describe("Archive manifest URL"),
817
- fromBlock: z7.number().int().nonnegative().optional().describe("Restore from this height instead of genesis"),
818
- toBlock: z7.number().int().nonnegative().optional().describe("Stop at this height instead of the archive tip")
610
+ against: z2.string().describe("Archive manifest URL"),
611
+ fromBlock: z2.number().int().nonnegative().optional().describe("Restore from this height instead of genesis"),
612
+ toBlock: z2.number().int().nonnegative().optional().describe("Stop at this height instead of the archive tip")
819
613
  }, async ({ against, fromBlock, toBlock }) => {
820
614
  const args = ["--against", against, "--yes", "--json"];
821
615
  if (fromBlock !== undefined)
@@ -825,10 +619,10 @@ function registerSetupTools(server, exec = execSecondlayer) {
825
619
  return cliResponse(await exec("bootstrap", args));
826
620
  });
827
621
  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.", {
828
- against: z7.string().describe("Archive manifest URL"),
829
- apply: z7.boolean().optional().describe("Write the repair (default plan-only)"),
830
- fromBlock: z7.number().int().nonnegative().optional().describe("First height to consider"),
831
- toBlock: z7.number().int().nonnegative().optional().describe("Last height to consider")
622
+ against: z2.string().describe("Archive manifest URL"),
623
+ apply: z2.boolean().optional().describe("Write the repair (default plan-only)"),
624
+ fromBlock: z2.number().int().nonnegative().optional().describe("First height to consider"),
625
+ toBlock: z2.number().int().nonnegative().optional().describe("Last height to consider")
832
626
  }, async ({ against, apply, fromBlock, toBlock }) => {
833
627
  const args = ["--against", against, "--json"];
834
628
  if (apply)
@@ -841,6 +635,197 @@ function registerSetupTools(server, exec = execSecondlayer) {
841
635
  });
842
636
  }
843
637
 
638
+ // src/tools/archive.ts
639
+ import { z as z3 } from "zod";
640
+ var FLOW = ["bootstrap", "repair"];
641
+ function registerArchiveTools(server, clientProvider = getClient, archiveOpsClientProvider = getArchiveOpsClient) {
642
+ 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.", {
643
+ against: z3.string().describe("Archive manifest URL"),
644
+ fromBlock: z3.number().int().nonnegative().optional().describe("First height (inclusive)"),
645
+ toBlock: z3.number().int().nonnegative().optional().describe("Last height (inclusive)"),
646
+ target: z3.string().optional().describe("Verify target (default raw)")
647
+ }, async ({ against, fromBlock, toBlock, target }) => jsonResponse(await clientProvider().archive.verify({
648
+ against,
649
+ fromBlock,
650
+ toBlock,
651
+ target
652
+ })));
653
+ 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.", {
654
+ against: z3.string().optional().describe("Override latest.json URL (default official tree)")
655
+ }, async ({ against }) => jsonResponse(await archiveOpsClientProvider().archive.latest(against)));
656
+ 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.", {
657
+ paths: z3.array(z3.string()).describe("Partition paths to price"),
658
+ flow: z3.enum(FLOW).describe("bootstrap or repair")
659
+ }, async ({ paths, flow }) => jsonResponse(await archiveOpsClientProvider().archive.quote({ paths, flow })));
660
+ 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()));
661
+ }
662
+
663
+ // src/tools/codegen.ts
664
+ import {
665
+ generateIndexSchema
666
+ } from "@secondlayer/subgraphs";
667
+ import { z as z4 } from "zod";
668
+ var TARGETS = ["prisma", "kysely", "drizzle", "json-schema"];
669
+ function registerCodegenTools(server) {
670
+ 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.", {
671
+ target: z4.enum(TARGETS).optional().describe("Output flavor (default kysely)"),
672
+ tables: z4.array(z4.string()).optional().describe("Restrict to these Index tables (default: all)"),
673
+ schemaName: z4.string().optional().describe("Postgres schema to qualify table names with"),
674
+ datasourceEnv: z4.string().optional().describe("Prisma only: datasource url env var (default DATABASE_URL)")
675
+ }, ({ target, tables, schemaName, datasourceEnv }) => textResponse(generateIndexSchema(target ?? "kysely", {
676
+ tables,
677
+ schemaName,
678
+ datasourceEnv
679
+ })));
680
+ }
681
+
682
+ // src/tools/contracts.ts
683
+ import { z as z5 } from "zod";
684
+ function registerContractTools(server, clientProvider = getClient) {
685
+ 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.', {
686
+ trait: z5.string().describe('Required. Trait to match (e.g. "sip-010").'),
687
+ conformance: z5.enum(["declared", "inferred", "any"]).optional().describe("declared = parsed from source, inferred = ABI shape-match, any = either (default)"),
688
+ include: z5.literal("abi").optional().describe(`Set to "abi" to include each contract's full ABI`),
689
+ limit: z5.number().optional().describe("Page size, 1–500 (default 100)"),
690
+ cursor: z5.string().optional().describe("Opaque cursor from a prior response's next_cursor")
691
+ }, async (params) => jsonResponse(await clientProvider().contracts.list(params)));
692
+ 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.", {
693
+ contractId: z5.string().describe("Fully qualified contract id (e.g. SP….amm-pool-v2-01)")
694
+ }, async ({ contractId }) => {
695
+ const contract = await clientProvider().contracts.get(contractId, {
696
+ includeAbi: true
697
+ });
698
+ return contract ? jsonResponse(contract) : jsonResponse({
699
+ error: {
700
+ type: "not_found",
701
+ status: 404,
702
+ message: `Contract not in registry: ${contractId}`
703
+ }
704
+ }, true);
705
+ });
706
+ }
707
+
708
+ // src/tools/index.ts
709
+ import { DECODED_EVENT_TYPES } from "@secondlayer/shared";
710
+ import { z as z6 } from "zod";
711
+ var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES;
712
+ var rangeFilters = {
713
+ contractId: z6.string().optional().describe("Filter by contract id"),
714
+ fromHeight: z6.number().optional().describe("Start block height (inclusive)"),
715
+ toHeight: z6.number().optional().describe("End block height (inclusive)"),
716
+ cursor: z6.string().optional().describe("Opaque cursor from a prior response's next_cursor"),
717
+ limit: z6.number().optional().describe("Max rows for this page")
718
+ };
719
+ var heightFilters = {
720
+ fromHeight: rangeFilters.fromHeight,
721
+ toHeight: rangeFilters.toHeight,
722
+ cursor: rangeFilters.cursor,
723
+ limit: rangeFilters.limit
724
+ };
725
+ function registerIndexTools(server, clientProvider = getClient) {
726
+ defineTool(server, "index_ft_transfers", "List decoded SIP-010 fungible-token transfers from Index. Reads are keyless.", {
727
+ ...rangeFilters,
728
+ sender: z6.string().optional().describe("Filter by sender principal"),
729
+ recipient: z6.string().optional().describe("Filter by recipient principal"),
730
+ assetIdentifier: z6.string().optional().describe("Filter by asset identifier (contract::asset), e.g. SP…sbtc-token::sbtc-token")
731
+ }, async (params) => jsonResponse(await clientProvider().index.ftTransfers.list(params)));
732
+ defineTool(server, "index_nft_transfers", "List decoded SIP-009 non-fungible-token transfers from Index. Reads are keyless.", {
733
+ ...rangeFilters,
734
+ sender: z6.string().optional().describe("Filter by sender principal"),
735
+ recipient: z6.string().optional().describe("Filter by recipient principal"),
736
+ assetIdentifier: z6.string().optional().describe("Filter by asset identifier (contract::asset)")
737
+ }, async (params) => jsonResponse(await clientProvider().index.nftTransfers.list(params)));
738
+ 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.", {
739
+ eventType: z6.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
740
+ ...rangeFilters,
741
+ sender: z6.string().optional().describe("Filter by sender principal"),
742
+ recipient: z6.string().optional().describe("Filter by recipient principal"),
743
+ assetIdentifier: z6.string().optional().describe("Filter by asset identifier where applicable"),
744
+ 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."),
745
+ 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.")
746
+ }, async (params) => jsonResponse(await clientProvider().index.events.list(params)));
747
+ 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.", {
748
+ ...rangeFilters,
749
+ functionName: z6.string().optional().describe("Filter by called function name"),
750
+ sender: z6.string().optional().describe("Filter by caller principal"),
751
+ trait: z6.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
752
+ }, async (params) => jsonResponse(await clientProvider().index.contractCalls.list(params)));
753
+ defineTool(server, "index_blocks", "List decoded blocks from Index. Reads are keyless.", { ...heightFilters }, async (params) => jsonResponse(await clientProvider().index.blocks.list(params)));
754
+ defineTool(server, "index_transactions", "List decoded transactions from Index. Filter by type, sender, or contract. Reads are keyless.", {
755
+ ...rangeFilters,
756
+ type: z6.string().optional().describe("Filter by transaction type"),
757
+ sender: z6.string().optional().describe("Filter by sender principal")
758
+ }, async (params) => jsonResponse(await clientProvider().index.transactions.list(params)));
759
+ 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.", {
760
+ contractId: z6.string().describe("Contract id (SP….name) whose print events to profile")
761
+ }, async (params) => jsonResponse(await clientProvider().index.printSchema(params.contractId)));
762
+ 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()));
763
+ 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.", {
764
+ requests: z6.array(z6.object({
765
+ path: z6.string().describe("Public /v1 read path"),
766
+ params: z6.record(z6.string(), z6.string()).optional()
767
+ })).min(1).max(10).describe("Read descriptors, executed concurrently")
768
+ }, async (params) => jsonResponse(await clientProvider().batch(params.requests)));
769
+ }
770
+
771
+ // src/tools/instance.ts
772
+ function registerInstanceTools(server, clientProvider = getClient) {
773
+ 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 () => {
774
+ const client = clientProvider();
775
+ const status = await client.instance.status();
776
+ const diagnosis = await client.instance.diagnose();
777
+ return jsonResponse({ status, diagnosis });
778
+ });
779
+ }
780
+
781
+ // src/tools/scaffold.ts
782
+ import {
783
+ generatePrintSchemaSubgraph,
784
+ generateSubgraphCode,
785
+ generateTokenSubgraphFromAbi
786
+ } from "@secondlayer/scaffold";
787
+ import { z as z7 } from "zod";
788
+ async function fetchAbi(clientProvider, contractId) {
789
+ const contract = await clientProvider().contracts.get(contractId, {
790
+ includeAbi: true
791
+ });
792
+ if (!contract)
793
+ throw new Error(`Contract not found: ${contractId}`);
794
+ const abi = contract.abi;
795
+ if (!abi) {
796
+ throw new Error(`No ABI available for ${contractId} (abi_status: ${contract.abi_status})`);
797
+ }
798
+ return { functions: abi.functions ?? [], abi };
799
+ }
800
+ function registerScaffoldTools(server, clientProvider = getClient) {
801
+ 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.", {
802
+ contractId: z7.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
803
+ subgraphName: z7.string().optional().describe("Override the subgraph name (defaults to contract name)")
804
+ }, async ({ contractId, subgraphName }) => {
805
+ const printSchema = await clientProvider().index.printSchema(contractId);
806
+ if (printSchema && printSchema.topics.length > 0) {
807
+ const code = generatePrintSchemaSubgraph({
808
+ contractId,
809
+ name: subgraphName,
810
+ topics: printSchema.topics,
811
+ sample: printSchema.sample
812
+ });
813
+ return { content: [{ type: "text", text: code }] };
814
+ }
815
+ const { functions, abi } = await fetchAbi(clientProvider, contractId);
816
+ const token = generateTokenSubgraphFromAbi({
817
+ contractId,
818
+ abi,
819
+ name: subgraphName
820
+ });
821
+ if (token) {
822
+ return { content: [{ type: "text", text: token }] };
823
+ }
824
+ const code = generateSubgraphCode(contractId, functions, subgraphName);
825
+ return { content: [{ type: "text", text: code }] };
826
+ });
827
+ }
828
+
844
829
  // src/tools/streams.ts
845
830
  function registerStreamsTools(server, clientProvider = getClient) {
846
831
  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()));
@@ -1179,35 +1164,103 @@ function registerWebhookTools(server, clientProvider = getClient) {
1179
1164
  }
1180
1165
 
1181
1166
  // src/tool-groups.ts
1167
+ var ENV_TOOL_DEPS = {
1168
+ clientProvider: getClient,
1169
+ archiveOpsClientProvider: getArchiveOpsClient,
1170
+ apiRequest
1171
+ };
1182
1172
  var TOOL_GROUPS = [
1183
- { group: "scaffold", register: registerScaffoldTools },
1184
- { group: "subgraphs", register: registerSubgraphTools },
1185
- { group: "webhooks", register: registerWebhookTools },
1186
- { group: "index", register: registerIndexTools },
1187
- { group: "streams", register: registerStreamsTools },
1188
- { group: "contracts", register: registerContractTools },
1189
- { group: "codegen", register: registerCodegenTools },
1190
- { group: "instance", register: registerInstanceTools },
1191
- { group: "archive", register: registerArchiveTools },
1192
- { group: "setup", register: registerSetupTools },
1193
- { group: "account", register: registerAccountTools, hostedOnly: true }
1173
+ {
1174
+ group: "scaffold",
1175
+ register: (s, d = ENV_TOOL_DEPS) => registerScaffoldTools(s, d.clientProvider),
1176
+ hosted: true
1177
+ },
1178
+ {
1179
+ group: "subgraphs",
1180
+ register: (s, d = ENV_TOOL_DEPS) => registerSubgraphTools(s, d.clientProvider),
1181
+ hosted: false
1182
+ },
1183
+ {
1184
+ group: "webhooks",
1185
+ register: (s, d = ENV_TOOL_DEPS) => registerWebhookTools(s, d.clientProvider),
1186
+ hosted: false
1187
+ },
1188
+ {
1189
+ group: "index",
1190
+ register: (s, d = ENV_TOOL_DEPS) => registerIndexTools(s, d.clientProvider),
1191
+ hosted: true
1192
+ },
1193
+ {
1194
+ group: "streams",
1195
+ register: (s, d = ENV_TOOL_DEPS) => registerStreamsTools(s, d.clientProvider),
1196
+ hosted: true
1197
+ },
1198
+ {
1199
+ group: "contracts",
1200
+ register: (s, d = ENV_TOOL_DEPS) => registerContractTools(s, d.clientProvider),
1201
+ hosted: true
1202
+ },
1203
+ { group: "codegen", register: (s) => registerCodegenTools(s), hosted: false },
1204
+ {
1205
+ group: "instance",
1206
+ register: (s, d = ENV_TOOL_DEPS) => registerInstanceTools(s, d.clientProvider),
1207
+ hosted: false
1208
+ },
1209
+ {
1210
+ group: "archive",
1211
+ register: (s, d = ENV_TOOL_DEPS) => registerArchiveTools(s, d.clientProvider, d.archiveOpsClientProvider),
1212
+ hosted: false
1213
+ },
1214
+ {
1215
+ group: "archive",
1216
+ register: (s) => registerArchiveCliTools(s),
1217
+ hosted: false
1218
+ },
1219
+ {
1220
+ group: "account",
1221
+ register: (s, d = ENV_TOOL_DEPS) => registerAccountTools(s, d.apiRequest),
1222
+ hostedOnly: true,
1223
+ hosted: ["account_whoami"]
1224
+ }
1194
1225
  ];
1226
+ function isHostedTool(group, tool) {
1227
+ return group.hosted === true || Array.isArray(group.hosted) && group.hosted.includes(tool);
1228
+ }
1195
1229
 
1196
1230
  // src/server.ts
1197
1231
  var __dirname2 = dirname(fileURLToPath(import.meta.url));
1198
1232
  var pkg = JSON.parse(readFileSync(join2(__dirname2, "../package.json"), "utf-8"));
1199
- function createServer() {
1233
+ function createServer(options = {}) {
1200
1234
  const server = new McpServer({
1201
1235
  name: "secondlayer",
1202
1236
  version: pkg.version
1203
1237
  });
1204
- const hosted = isHostedArchiveUrl(resolveBaseUrl3());
1238
+ const deps = {
1239
+ clientProvider: options.clientProvider ?? ENV_TOOL_DEPS.clientProvider,
1240
+ archiveOpsClientProvider: options.archiveOpsClientProvider ?? ENV_TOOL_DEPS.archiveOpsClientProvider,
1241
+ apiRequest: options.apiRequest ?? ENV_TOOL_DEPS.apiRequest
1242
+ };
1243
+ if (options.hosted) {
1244
+ for (const group of TOOL_GROUPS) {
1245
+ if (group.hosted === false)
1246
+ continue;
1247
+ restrictTools(server, (name) => isHostedTool(group, name));
1248
+ group.register(server, deps);
1249
+ }
1250
+ restrictTools(server, () => false);
1251
+ registerResources(server, {
1252
+ clientProvider: deps.clientProvider,
1253
+ hosted: true
1254
+ });
1255
+ return server;
1256
+ }
1257
+ const hostedApi = isHostedArchiveUrl(resolveBaseUrl3());
1205
1258
  for (const { register, hostedOnly } of TOOL_GROUPS) {
1206
- if (hostedOnly && !hosted)
1259
+ if (hostedOnly && !hostedApi)
1207
1260
  continue;
1208
- register(server);
1261
+ register(server, deps);
1209
1262
  }
1210
- registerResources(server);
1263
+ registerResources(server, { clientProvider: deps.clientProvider });
1211
1264
  return server;
1212
1265
  }
1213
1266
 
@@ -1216,5 +1269,5 @@ var server = createServer();
1216
1269
  var transport = new StdioServerTransport;
1217
1270
  await server.connect(transport);
1218
1271
 
1219
- //# debugId=A9F8E1BED778D12264756E2164756E21
1272
+ //# debugId=B633445D908DEC9264756E2164756E21
1220
1273
  //# sourceMappingURL=bin.js.map