@secondlayer/mcp 7.3.0 → 7.4.1

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
@@ -16,7 +16,7 @@ bun add @secondlayer/mcp
16
16
 
17
17
  ## Auth
18
18
 
19
- Most reads are public: `index_*` and `contracts_find` work with no key. Subgraph tools need an `INSTANCE_TOKEN` past loopback; separately, **public** subgraphs are anon-readable over HTTP at `GET /v1/subgraphs/<name>/<table>` (`{ rows, next_cursor, tip }` cursor envelope), while private ones need the instance token (anon → 404). `streams_dumps` needs no key: the dumps manifest is public; the tool only needs `SL_STREAMS_DUMPS_URL` configured. Every other `streams_*` tool is key-mandatory (keyless → 401). Writes (deploy, reindex, delete, subscriptions) need a key: set `INSTANCE_TOKEN` from `secondlayer init`. `SL_API_KEY` is a legacy alias of `INSTANCE_TOKEN`. Read `secondlayer://context` first: it reports auth state and read-auth tiers.
19
+ Most reads are public: `index_*` and `contracts_find` work with no key. Subgraph tools need an `INSTANCE_TOKEN` past loopback; separately, **public** subgraphs are anon-readable over HTTP at `GET /v1/subgraphs/<name>/<table>` (`{ rows, next_cursor, tip }` cursor envelope), while private ones need the instance token (anon → 404). `streams_dumps` needs no key: the dumps manifest is public; the tool only needs `SL_STREAMS_DUMPS_URL` configured. Every other `streams_*` tool is key-mandatory (keyless → 401). Writes (deploy, reindex, delete, subscriptions) need a key: set `INSTANCE_TOKEN` from `secondlayer init`. Hosted credits/quote use `SECONDLAYER_API_KEY` (`sk-sl_*`). Read `secondlayer://context` first: it reports auth state and read-auth tiers.
20
20
 
21
21
  ## Quick Start — Stdio (IDE)
22
22
 
@@ -51,10 +51,9 @@ bunx -p @secondlayer/mcp secondlayer-mcp-http
51
51
 
52
52
  | Variable | Required | Default | Description |
53
53
  | --- | --- | --- | --- |
54
- | `INSTANCE_TOKEN` | Writes only | — | From `secondlayer init`. Required for write tools; reads are public. `SL_API_KEY` is a legacy alias. Not valid for hosted credits. |
55
- | `SL_ARCHIVE_API_KEY` | Hosted credits/quote/latest | — | `sk-sl_*` for `api.secondlayer.tools`. `INSTANCE_TOKEN` is the instance. |
56
- | `SL_API_URL` | No | `http://127.0.0.1:3800` | Instance API. |
57
- | `SECONDLAYER_API_URL` | No | — | Overrides `SL_API_URL`. |
54
+ | `INSTANCE_TOKEN` | Writes only | — | From `secondlayer init`. Required for write tools; reads are public. Not valid for hosted credits. |
55
+ | `SECONDLAYER_API_KEY` | Hosted credits/quote/latest | — | `sk-sl_*` for `api.secondlayer.tools`. `SL_API_KEY` / `SL_ARCHIVE_API_KEY` are one-release fallbacks. |
56
+ | `SECONDLAYER_API_URL` | No | `http://127.0.0.1:3800` | Instance API. `SL_API_URL` is a one-release fallback. |
58
57
  | `SECONDLAYER_BIN` | CLI tools | `secondlayer` on PATH | Path to the CLI binary (`setup`, `bootstrap`, `repair`). |
59
58
  | `SECONDLAYER_CWD` | CLI tools | process cwd | Compose project directory. |
60
59
  | `SECONDLAYER_MCP_PORT` | No | `3100` | HTTP transport port. |
@@ -72,7 +71,7 @@ bunx -p @secondlayer/mcp secondlayer-mcp-http
72
71
  | **Instance** (1) | `instance_status` |
73
72
  | **Archive** (5) | `archive_verify`, `archive_bootstrap`, `archive_repair`, `archive_latest`, `archive_quote` |
74
73
  | **Setup** (1) | `setup` |
75
- | **Credits** (1) | `credits_balance` (needs `SL_ARCHIVE_API_KEY`) |
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,
@@ -84,7 +83,7 @@ Periphery surfaces (single block/tx lookups, mempool, stacking, proofs,
84
83
  credits/caps, live Streams SSE) are REST-only: see the OpenAPI spec at the API
85
84
  host. Following the chain over MCP means polling `streams_events` with a cursor.
86
85
 
87
- Point the server at your instance with `SL_API_URL` (default
86
+ Point the server at your instance with `SECONDLAYER_API_URL` (default
88
87
  `http://127.0.0.1:3800`). Writes use `INSTANCE_TOKEN` from
89
88
  `secondlayer init`. `account_*` tools appear only when the server is pointed at
90
89
  `https://api.secondlayer.tools`.
package/dist/bin-http.js CHANGED
@@ -8,7 +8,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
8
8
 
9
9
  // src/server.ts
10
10
  import { readFileSync } from "node:fs";
11
- import { dirname, join } from "node:path";
11
+ import { dirname, join as join2 } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
14
 
@@ -23,16 +23,23 @@ function isHostedArchiveUrl(url) {
23
23
  }
24
24
 
25
25
  // src/resources.ts
26
+ import {
27
+ ResourceTemplate
28
+ } from "@modelcontextprotocol/sdk/server/mcp.js";
26
29
  import { CHAIN_TRIGGER_FIELDS } from "@secondlayer/shared";
27
30
  import { BOOTSTRAP_STEP } from "@secondlayer/shared/archive/instance-diagnosis";
28
31
  import { TRAIT_STANDARDS } from "@secondlayer/stacks/clarity";
29
- import { filterFieldsByType } from "@secondlayer/subgraphs";
32
+ import { camelizeKeys, filterFieldsByType } from "@secondlayer/subgraphs";
30
33
  import { TYPE_MAP } from "@secondlayer/subgraphs/schema";
31
34
 
32
35
  // src/lib/client.ts
33
- import { SecondLayer, resolveApiKey } from "@secondlayer/sdk";
36
+ import {
37
+ SecondLayer,
38
+ resolveAccountKey,
39
+ resolveApiKey
40
+ } from "@secondlayer/sdk";
34
41
  var instance = null;
35
- var HOSTED_KEY_HINT = "set SL_ARCHIVE_API_KEY (sk-sl_*) for credits; INSTANCE_TOKEN is the instance";
42
+ var HOSTED_KEY_HINT = "set SECONDLAYER_API_KEY (sk-sl_*) for credits; INSTANCE_TOKEN is the instance";
36
43
  var DEFAULT_ARCHIVE_OPS_URL = "https://api.secondlayer.tools";
37
44
  function readApiKey() {
38
45
  return resolveApiKey();
@@ -52,25 +59,30 @@ function getClient() {
52
59
  return instance;
53
60
  }
54
61
  function readArchiveApiKey() {
55
- const key = process.env.SL_ARCHIVE_API_KEY;
56
- return key && key.length > 0 ? key : undefined;
62
+ return resolveAccountKey();
57
63
  }
58
64
  function getArchiveOpsClient() {
59
- const apiKey = readArchiveApiKey();
60
- if (!apiKey) {
65
+ const accountKey = readArchiveApiKey();
66
+ if (!accountKey) {
61
67
  throw new Error(HOSTED_KEY_HINT);
62
68
  }
63
69
  const archiveOpsUrl = process.env.SL_CREDITS_API_URL || process.env.ARCHIVE_OPS_API_URL || DEFAULT_ARCHIVE_OPS_URL;
64
70
  return new SecondLayer({
65
- apiKey,
71
+ accountKey,
66
72
  origin: "mcp",
67
73
  archiveOpsUrl
68
74
  });
69
75
  }
70
- var keyHint = " — set INSTANCE_TOKEN from `sl init` for writes (SL_API_KEY is a legacy alias)";
76
+ var keyHint = " — set INSTANCE_TOKEN from `sl init` for writes";
71
77
  async function apiRequest(method, path, body) {
72
- const apiKey = readApiKey();
73
78
  const baseUrl = process.env.SECONDLAYER_API_URL || process.env.SL_API_URL || "http://127.0.0.1:3800";
79
+ let host = "";
80
+ try {
81
+ host = new URL(baseUrl).hostname;
82
+ } catch {
83
+ host = "";
84
+ }
85
+ const apiKey = host === "api.secondlayer.tools" ? resolveAccountKey() : readApiKey();
74
86
  const res = await fetch(`${baseUrl}${path}`, {
75
87
  method,
76
88
  headers: {
@@ -313,6 +325,73 @@ function registerResources(server) {
313
325
  }
314
326
  ]
315
327
  }));
328
+ server.resource("samples", new ResourceTemplate("secondlayer://samples/{contractId}", {
329
+ list: undefined
330
+ }), {
331
+ description: "Decoded camelCase print samples for a contract (schema + up to 5 events). Use before writing a print_event handler."
332
+ }, async (uri, variables) => {
333
+ const contractId = String(Array.isArray(variables.contractId) ? variables.contractId[0] : variables.contractId ?? "");
334
+ const body = await buildSamplesResource(contractId);
335
+ return {
336
+ contents: [
337
+ {
338
+ uri: uri.href,
339
+ mimeType: "application/json",
340
+ text: JSON.stringify(body, null, 2)
341
+ }
342
+ ]
343
+ };
344
+ });
345
+ }
346
+ function jsonSafe(value) {
347
+ if (typeof value === "bigint")
348
+ return value.toString();
349
+ if (value === null || value === undefined)
350
+ return value;
351
+ if (Array.isArray(value))
352
+ return value.map(jsonSafe);
353
+ if (typeof value === "object") {
354
+ const out = {};
355
+ for (const [k, v] of Object.entries(value)) {
356
+ out[k] = jsonSafe(v);
357
+ }
358
+ return out;
359
+ }
360
+ return value;
361
+ }
362
+ async function buildSamplesResource(contractId, clientProvider = getClient) {
363
+ const client = clientProvider();
364
+ const printSchema = await client.index.printSchema(contractId);
365
+ const envelope = await client.index.events.list({
366
+ eventType: "print",
367
+ contractId,
368
+ limit: 5
369
+ });
370
+ const schema = printSchema?.topics.map((t) => ({
371
+ topic: t.topic,
372
+ fields: t.fields.map((f) => ({
373
+ camel: f.camel_name,
374
+ snake: f.name.replace(/-/g, "_"),
375
+ column_type: f.column_type,
376
+ always: f.always_present
377
+ }))
378
+ })) ?? [];
379
+ const samples = (envelope.events ?? []).slice(0, 5).map((ev) => {
380
+ const payload = ev.payload;
381
+ const data = jsonSafe(camelizeKeys(payload?.value));
382
+ return {
383
+ txId: ev.tx_id,
384
+ height: ev.block_height,
385
+ topic: payload?.topic ?? "",
386
+ data: data ?? {}
387
+ };
388
+ });
389
+ return {
390
+ contractId,
391
+ handlerShape: "event.data is camelCase; columns are snake_case",
392
+ schema,
393
+ samples
394
+ };
316
395
  }
317
396
 
318
397
  // src/tools/account.ts
@@ -342,14 +421,14 @@ function registerArchiveTools(server, clientProvider = getClient, archiveOpsClie
342
421
  toBlock,
343
422
  target
344
423
  })));
345
- defineTool(server, "archive_latest", "Signed pointer to the official archive tree (hosted). Needs SL_ARCHIVE_API_KEY (sk-sl_*), not INSTANCE_TOKEN. Use the returned origin as --against for archive_bootstrap. Do not download parquet.", {
424
+ 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.", {
346
425
  against: z2.string().optional().describe("Override latest.json URL (default official tree)")
347
426
  }, async ({ against }) => jsonResponse(await archiveOpsClientProvider().archive.latest(against)));
348
- defineTool(server, "archive_quote", "Price a hosted archive fetch (metered). Needs SL_ARCHIVE_API_KEY (sk-sl_*), not INSTANCE_TOKEN. Quote happens here; bootstrap/repair also quote inside the CLI. Do not download parquet.", {
427
+ 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.", {
349
428
  paths: z2.array(z2.string()).describe("Partition paths to price"),
350
429
  flow: z2.enum(FLOW).describe("bootstrap or repair")
351
430
  }, async ({ paths, flow }) => jsonResponse(await archiveOpsClientProvider().archive.quote({ paths, flow })));
352
- defineTool(server, "credits_balance", "Archive credits balance at api.secondlayer.tools. Needs SL_ARCHIVE_API_KEY (sk-sl_*); INSTANCE_TOKEN is the instance.", {}, async () => jsonResponse(await archiveOpsClientProvider().archive.credits.balance()));
431
+ 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()));
353
432
  }
354
433
 
355
434
  // src/tools/codegen.ts
@@ -471,7 +550,11 @@ function registerInstanceTools(server, clientProvider = getClient) {
471
550
  }
472
551
 
473
552
  // src/tools/scaffold.ts
474
- import { generateSubgraphCode } from "@secondlayer/scaffold";
553
+ import {
554
+ generatePrintSchemaSubgraph,
555
+ generateSubgraphCode,
556
+ generateTokenSubgraphFromAbi
557
+ } from "@secondlayer/scaffold";
475
558
  import { z as z6 } from "zod";
476
559
  async function fetchAbi(clientProvider, contractId) {
477
560
  const contract = await clientProvider().contracts.get(contractId, {
@@ -483,14 +566,32 @@ async function fetchAbi(clientProvider, contractId) {
483
566
  if (!abi) {
484
567
  throw new Error(`No ABI available for ${contractId} (abi_status: ${contract.abi_status})`);
485
568
  }
486
- return { functions: abi.functions ?? [] };
569
+ return { functions: abi.functions ?? [], abi };
487
570
  }
488
571
  function registerScaffoldTools(server, clientProvider = getClient) {
489
- defineTool(server, "subgraphs_scaffold", "Generate a subgraph scaffold from a deployed Stacks contract. Fetches the ABI automatically. Returns TypeScript source — review it, then pass it to subgraphs_deploy.", {
572
+ 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.", {
490
573
  contractId: z6.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
491
574
  subgraphName: z6.string().optional().describe("Override the subgraph name (defaults to contract name)")
492
575
  }, async ({ contractId, subgraphName }) => {
493
- const { functions } = await fetchAbi(clientProvider, contractId);
576
+ const printSchema = await clientProvider().index.printSchema(contractId);
577
+ if (printSchema && printSchema.topics.length > 0) {
578
+ const code2 = generatePrintSchemaSubgraph({
579
+ contractId,
580
+ name: subgraphName,
581
+ topics: printSchema.topics,
582
+ sample: printSchema.sample
583
+ });
584
+ return { content: [{ type: "text", text: code2 }] };
585
+ }
586
+ const { functions, abi } = await fetchAbi(clientProvider, contractId);
587
+ const token = generateTokenSubgraphFromAbi({
588
+ contractId,
589
+ abi,
590
+ name: subgraphName
591
+ });
592
+ if (token) {
593
+ return { content: [{ type: "text", text: token }] };
594
+ }
494
595
  const code = generateSubgraphCode(contractId, functions, subgraphName);
495
596
  return { content: [{ type: "text", text: code }] };
496
597
  });
@@ -743,14 +844,42 @@ function registerStreamsTools(server, clientProvider = getClient) {
743
844
  }
744
845
 
745
846
  // src/tools/subgraphs.ts
847
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
848
+ import { tmpdir } from "node:os";
849
+ import { join } from "node:path";
850
+ import { pathToFileURL } from "node:url";
746
851
  import { bundleSubgraphCode } from "@secondlayer/bundler";
852
+ import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
853
+ import {
854
+ runSubgraphTest
855
+ } from "@secondlayer/subgraphs/testing";
747
856
  import { z as z9 } from "zod";
857
+ function eventTypeFor(filter) {
858
+ if (filter.type === "contract_deploy")
859
+ return null;
860
+ const candidate = filter.type === "print_event" ? "print" : filter.type;
861
+ return DECODED_EVENT_TYPES2.includes(candidate) ? candidate : null;
862
+ }
863
+ function skipReason(name, filter) {
864
+ return filter.type === "contract_deploy" ? `source "${name}" (contract_deploy) has no Index list endpoint — skipped.` : `source "${name}" (${filter.type}) is not readable from Index — skipped.`;
865
+ }
866
+ async function loadBundledDefinition(handlerCode) {
867
+ const dir = await mkdtemp(join(tmpdir(), "sl-mcp-sg-test-"));
868
+ try {
869
+ const file = join(dir, `handler-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`);
870
+ await writeFile(file, handlerCode);
871
+ const mod = await import(pathToFileURL(file).href);
872
+ return mod.default ?? {};
873
+ } finally {
874
+ await rm(dir, { recursive: true, force: true });
875
+ }
876
+ }
748
877
  function registerSubgraphTools(server, clientProvider = getClient) {
749
878
  defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only; read rows with subgraphs_query.", {}, async () => {
750
879
  const { data } = await clientProvider().subgraphs.list();
751
880
  return jsonResponse(data.map(formatSubgraphSummary));
752
881
  });
753
- defineTool(server, "subgraphs_status", "Get full details of a subgraph including schema, health, and table columns.", { name: z9.string().describe("Subgraph name") }, async ({ name }) => {
882
+ defineTool(server, "subgraphs_status", "Get full details of a subgraph including schema, health (incl. health.emptyMapping when processed events wrote 0 rows), and table columns.", { name: z9.string().describe("Subgraph name") }, async ({ name }) => {
754
883
  const detail = await clientProvider().subgraphs.status(name);
755
884
  return jsonResponse(detail);
756
885
  });
@@ -846,10 +975,10 @@ function registerSubgraphTools(server, clientProvider = getClient) {
846
975
  const result = await clientProvider().subgraphs.delete(name);
847
976
  return textResponse(result.message);
848
977
  });
849
- defineTool(server, "subgraphs_deploy", "Deploy a subgraph from TypeScript code. Pass the full defineSubgraph() source — it will be bundled, validated, and deployed. Optional startBlock overrides the source definition for this deploy. Set dryRun to validate and preview the schema/DDL without writing anything. Call `subgraphs_reindex` separately if you need a forced reindex.", {
978
+ defineTool(server, "subgraphs_deploy", "Deploy a subgraph from TypeScript code. Pass the full defineSubgraph() source — it will be bundled, validated, and deployed. Optional startBlock overrides the source definition for this deploy. dryRun validates schema/DDL only and does not run handlers — call subgraphs_test first and deploy only when it returns ok: true with written >= 1. Call subgraphs_reindex separately if you need a forced reindex.", {
850
979
  code: z9.string().describe("TypeScript source code containing a defineSubgraph() call"),
851
980
  startBlock: z9.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy"),
852
- dryRun: z9.boolean().optional().describe("Validate and preview the deploy (schema/DDL) without writing changes")
981
+ dryRun: z9.boolean().optional().describe("Validate schema/DDL only — does not run handlers. Use subgraphs_test to prove a mapping writes rows.")
853
982
  }, async ({ code, startBlock, dryRun }) => {
854
983
  const bundled = await bundleSubgraphCode(code);
855
984
  const result = await clientProvider().subgraphs.deploy({
@@ -865,6 +994,87 @@ function registerSubgraphTools(server, clientProvider = getClient) {
865
994
  });
866
995
  return jsonResponse(result);
867
996
  });
997
+ defineTool(server, "subgraphs_test", "Prove a subgraph mapping writes rows against Index data before deploy. Pass full defineSubgraph() source plus a height range. Returns ok/written/tables; EMPTY_MAPPING means events matched but 0 rows were written — read hint for observed event.data keys and do not invent fields. subgraphs_deploy dryRun is DDL only; do not deploy until ok: true with written >= 1.", {
998
+ code: z9.string().describe("TypeScript source containing a defineSubgraph() call"),
999
+ fromHeight: z9.number().int().nonnegative().describe("Start block height (inclusive)"),
1000
+ toHeight: z9.number().int().nonnegative().optional().describe("End block height (inclusive); default fromHeight+100"),
1001
+ limit: z9.number().int().positive().max(200).optional().describe("Max Index rows per source (default 50, max 200)")
1002
+ }, async ({ code, fromHeight, toHeight, limit }) => {
1003
+ const bundled = await bundleSubgraphCode(code);
1004
+ const def = await loadBundledDefinition(bundled.handlerCode);
1005
+ if (!def.handlers || !def.schema) {
1006
+ return jsonResponse({
1007
+ ok: false,
1008
+ code: "NO_SOURCES",
1009
+ matched: 0,
1010
+ written: 0,
1011
+ tables: [],
1012
+ hint: "Bundled module must default-export defineSubgraph() with handlers and schema."
1013
+ }, true);
1014
+ }
1015
+ const sources = bundled.sources ?? {};
1016
+ const to = toHeight ?? fromHeight + 100;
1017
+ const pageLimit = Math.min(limit ?? 50, 200);
1018
+ const index = clientProvider().index;
1019
+ const events = {};
1020
+ const skipped = [];
1021
+ let sourcesTested = 0;
1022
+ for (const [name, filter] of Object.entries(sources)) {
1023
+ if (filter.type === "contract_call") {
1024
+ sourcesTested++;
1025
+ const envelope2 = await index.contractCalls.list({
1026
+ ...filter.contractId ? {
1027
+ contractId: Array.isArray(filter.contractId) ? filter.contractId : filter.contractId
1028
+ } : {},
1029
+ ...filter.functionName ? { functionName: filter.functionName } : {},
1030
+ fromHeight,
1031
+ toHeight: to,
1032
+ limit: pageLimit
1033
+ });
1034
+ events[name] = envelope2.contract_calls;
1035
+ continue;
1036
+ }
1037
+ const eventType = eventTypeFor(filter);
1038
+ if (eventType === null) {
1039
+ skipped.push({ source: name, reason: skipReason(name, filter) });
1040
+ continue;
1041
+ }
1042
+ sourcesTested++;
1043
+ const envelope = await index.events.list({
1044
+ eventType,
1045
+ ...filter.contractId ? {
1046
+ contractId: Array.isArray(filter.contractId) ? filter.contractId : filter.contractId
1047
+ } : {},
1048
+ fromHeight,
1049
+ toHeight: to,
1050
+ limit: pageLimit
1051
+ });
1052
+ events[name] = envelope.events;
1053
+ }
1054
+ if (sourcesTested === 0) {
1055
+ return jsonResponse({
1056
+ ok: false,
1057
+ code: "NO_SOURCES",
1058
+ matched: 0,
1059
+ written: 0,
1060
+ tables: [],
1061
+ hint: "No sources were tested — every source is unreadable from Index.",
1062
+ skipped
1063
+ }, true);
1064
+ }
1065
+ const result = await runSubgraphTest({
1066
+ schema: def.schema,
1067
+ handlers: def.handlers,
1068
+ sources,
1069
+ events
1070
+ });
1071
+ const body = { ...result, skipped };
1072
+ if (skipped.length > 0) {
1073
+ const skipHints = skipped.map((s) => s.reason).join(" ");
1074
+ body.hint = result.hint ? `${result.hint} ${skipHints}` : skipHints;
1075
+ }
1076
+ return jsonResponse(body, !result.ok);
1077
+ });
868
1078
  }
869
1079
 
870
1080
  // src/tools/subscriptions.ts
@@ -971,7 +1181,7 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
971
1181
 
972
1182
  // src/server.ts
973
1183
  var __dirname2 = dirname(fileURLToPath(import.meta.url));
974
- var pkg = JSON.parse(readFileSync(join(__dirname2, "../package.json"), "utf-8"));
1184
+ var pkg = JSON.parse(readFileSync(join2(__dirname2, "../package.json"), "utf-8"));
975
1185
  function createServer() {
976
1186
  const server = new McpServer({
977
1187
  name: "secondlayer",
@@ -1081,5 +1291,5 @@ httpServer.listen(port, () => {
1081
1291
  console.error("Warning: SECONDLAYER_MCP_SECRET not set, authentication disabled");
1082
1292
  });
1083
1293
 
1084
- //# debugId=71BD0A1C7A7D21EB64756E2164756E21
1294
+ //# debugId=0C640F6B0FB7B21064756E2164756E21
1085
1295
  //# sourceMappingURL=bin-http.js.map