@secondlayer/mcp 7.4.0 → 7.5.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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/server.ts
2
2
  import { readFileSync } from "node:fs";
3
- import { dirname, join } from "node:path";
3
+ import { dirname, join as join2 } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
6
 
@@ -15,10 +15,13 @@ function isHostedArchiveUrl(url) {
15
15
  }
16
16
 
17
17
  // src/resources.ts
18
+ import {
19
+ ResourceTemplate
20
+ } from "@modelcontextprotocol/sdk/server/mcp.js";
18
21
  import { CHAIN_TRIGGER_FIELDS } from "@secondlayer/shared";
19
22
  import { BOOTSTRAP_STEP } from "@secondlayer/shared/archive/instance-diagnosis";
20
23
  import { TRAIT_STANDARDS } from "@secondlayer/stacks/clarity";
21
- import { filterFieldsByType } from "@secondlayer/subgraphs";
24
+ import { camelizeKeys, filterFieldsByType } from "@secondlayer/subgraphs";
22
25
  import { TYPE_MAP } from "@secondlayer/subgraphs/schema";
23
26
 
24
27
  // src/lib/client.ts
@@ -179,7 +182,8 @@ var PRODUCT_BLURBS = {
179
182
  streams: "raw chain event firehose — cursor-paginated reads, tip/canonical/reorg checks, bulk parquet dumps",
180
183
  contracts: "trait-based contract discovery and ABIs",
181
184
  subgraphs: "author/scaffold/deploy/query custom indexes",
182
- subscriptions: "webhook delivery on subgraph rows or raw chain events",
185
+ webhooks: "a signed POST to a URL you run, on subgraph rows or raw chain events",
186
+ subscriptions: "a signed POST to a URL you run, on subgraph rows or raw chain events",
183
187
  account: "identity and self-provisioned API keys",
184
188
  codegen: "ORM schemas for the tables you read",
185
189
  instance: "decoder health and empty-index diagnosis",
@@ -192,6 +196,7 @@ var PRODUCT_ORDER = [
192
196
  "streams",
193
197
  "contracts",
194
198
  "subgraphs",
199
+ "webhooks",
195
200
  "subscriptions",
196
201
  "account",
197
202
  "codegen",
@@ -248,7 +253,8 @@ async function buildContext(deps = { clientProvider: getClient }) {
248
253
  streamsTip: orNull(snap?.streamsTip),
249
254
  indexTip: orNull(snap?.indexTip),
250
255
  subgraphs: snap?.subgraphs.value ? snap.subgraphs.value.map(formatSubgraphSummary) : orNull(snap?.subgraphs),
251
- subscriptions: orNull(snap?.subscriptions),
256
+ webhooks: orNull(snap?.webhooks),
257
+ subscriptions: orNull(snap?.webhooks),
252
258
  activeOperations: orNull(snap?.activeOperations),
253
259
  instance: instance2
254
260
  },
@@ -259,7 +265,7 @@ async function buildContext(deps = { clientProvider: getClient }) {
259
265
  }
260
266
  function registerResources(server) {
261
267
  server.resource("context", "secondlayer://context", {
262
- description: "Live agent context — what exists (your subgraphs, subscriptions, account), what you can do, and read-auth tiers. Read this first."
268
+ description: "Live agent context — what exists (your subgraphs, webhooks, account), what you can do, and read-auth tiers. Read this first."
263
269
  }, async () => ({
264
270
  contents: [
265
271
  {
@@ -304,7 +310,7 @@ function registerResources(server) {
304
310
  ]
305
311
  }));
306
312
  server.resource("chain-triggers", "secondlayer://chain-triggers", {
307
- description: "Chain-subscription trigger types and the filter fields each accepts (for subscriptions_create triggers)."
313
+ description: "Chain-webhook trigger types and the filter fields each accepts (for webhooks_create triggers)."
308
314
  }, async () => ({
309
315
  contents: [
310
316
  {
@@ -314,6 +320,73 @@ function registerResources(server) {
314
320
  }
315
321
  ]
316
322
  }));
323
+ server.resource("samples", new ResourceTemplate("secondlayer://samples/{contractId}", {
324
+ list: undefined
325
+ }), {
326
+ description: "Decoded camelCase print samples for a contract (schema + up to 5 events). Use before writing a print_event handler."
327
+ }, async (uri, variables) => {
328
+ const contractId = String(Array.isArray(variables.contractId) ? variables.contractId[0] : variables.contractId ?? "");
329
+ const body = await buildSamplesResource(contractId);
330
+ return {
331
+ contents: [
332
+ {
333
+ uri: uri.href,
334
+ mimeType: "application/json",
335
+ text: JSON.stringify(body, null, 2)
336
+ }
337
+ ]
338
+ };
339
+ });
340
+ }
341
+ function jsonSafe(value) {
342
+ if (typeof value === "bigint")
343
+ return value.toString();
344
+ if (value === null || value === undefined)
345
+ return value;
346
+ if (Array.isArray(value))
347
+ return value.map(jsonSafe);
348
+ if (typeof value === "object") {
349
+ const out = {};
350
+ for (const [k, v] of Object.entries(value)) {
351
+ out[k] = jsonSafe(v);
352
+ }
353
+ return out;
354
+ }
355
+ return value;
356
+ }
357
+ async function buildSamplesResource(contractId, clientProvider = getClient) {
358
+ const client = clientProvider();
359
+ const printSchema = await client.index.printSchema(contractId);
360
+ const envelope = await client.index.events.list({
361
+ eventType: "print",
362
+ contractId,
363
+ limit: 5
364
+ });
365
+ const schema = printSchema?.topics.map((t) => ({
366
+ topic: t.topic,
367
+ fields: t.fields.map((f) => ({
368
+ camel: f.camel_name,
369
+ snake: f.name.replace(/-/g, "_"),
370
+ column_type: f.column_type,
371
+ always: f.always_present
372
+ }))
373
+ })) ?? [];
374
+ const samples = (envelope.events ?? []).slice(0, 5).map((ev) => {
375
+ const payload = ev.payload;
376
+ const data = jsonSafe(camelizeKeys(payload?.value));
377
+ return {
378
+ txId: ev.tx_id,
379
+ height: ev.block_height,
380
+ topic: payload?.topic ?? "",
381
+ data: data ?? {}
382
+ };
383
+ });
384
+ return {
385
+ contractId,
386
+ handlerShape: "event.data is camelCase; columns are snake_case",
387
+ schema,
388
+ samples
389
+ };
317
390
  }
318
391
 
319
392
  // src/tools/account.ts
@@ -472,7 +545,11 @@ function registerInstanceTools(server, clientProvider = getClient) {
472
545
  }
473
546
 
474
547
  // src/tools/scaffold.ts
475
- import { generateSubgraphCode } from "@secondlayer/scaffold";
548
+ import {
549
+ generatePrintSchemaSubgraph,
550
+ generateSubgraphCode,
551
+ generateTokenSubgraphFromAbi
552
+ } from "@secondlayer/scaffold";
476
553
  import { z as z6 } from "zod";
477
554
  async function fetchAbi(clientProvider, contractId) {
478
555
  const contract = await clientProvider().contracts.get(contractId, {
@@ -484,14 +561,32 @@ async function fetchAbi(clientProvider, contractId) {
484
561
  if (!abi) {
485
562
  throw new Error(`No ABI available for ${contractId} (abi_status: ${contract.abi_status})`);
486
563
  }
487
- return { functions: abi.functions ?? [] };
564
+ return { functions: abi.functions ?? [], abi };
488
565
  }
489
566
  function registerScaffoldTools(server, clientProvider = getClient) {
490
- 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.", {
567
+ 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.", {
491
568
  contractId: z6.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
492
569
  subgraphName: z6.string().optional().describe("Override the subgraph name (defaults to contract name)")
493
570
  }, async ({ contractId, subgraphName }) => {
494
- const { functions } = await fetchAbi(clientProvider, contractId);
571
+ const printSchema = await clientProvider().index.printSchema(contractId);
572
+ if (printSchema && printSchema.topics.length > 0) {
573
+ const code2 = generatePrintSchemaSubgraph({
574
+ contractId,
575
+ name: subgraphName,
576
+ topics: printSchema.topics,
577
+ sample: printSchema.sample
578
+ });
579
+ return { content: [{ type: "text", text: code2 }] };
580
+ }
581
+ const { functions, abi } = await fetchAbi(clientProvider, contractId);
582
+ const token = generateTokenSubgraphFromAbi({
583
+ contractId,
584
+ abi,
585
+ name: subgraphName
586
+ });
587
+ if (token) {
588
+ return { content: [{ type: "text", text: token }] };
589
+ }
495
590
  const code = generateSubgraphCode(contractId, functions, subgraphName);
496
591
  return { content: [{ type: "text", text: code }] };
497
592
  });
@@ -744,14 +839,42 @@ function registerStreamsTools(server, clientProvider = getClient) {
744
839
  }
745
840
 
746
841
  // src/tools/subgraphs.ts
842
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
843
+ import { tmpdir } from "node:os";
844
+ import { join } from "node:path";
845
+ import { pathToFileURL } from "node:url";
747
846
  import { bundleSubgraphCode } from "@secondlayer/bundler";
847
+ import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
848
+ import {
849
+ runSubgraphTest
850
+ } from "@secondlayer/subgraphs/testing";
748
851
  import { z as z9 } from "zod";
852
+ function eventTypeFor(filter) {
853
+ if (filter.type === "contract_deploy")
854
+ return null;
855
+ const candidate = filter.type === "print_event" ? "print" : filter.type;
856
+ return DECODED_EVENT_TYPES2.includes(candidate) ? candidate : null;
857
+ }
858
+ function skipReason(name, filter) {
859
+ 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.`;
860
+ }
861
+ async function loadBundledDefinition(handlerCode) {
862
+ const dir = await mkdtemp(join(tmpdir(), "sl-mcp-sg-test-"));
863
+ try {
864
+ const file = join(dir, `handler-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`);
865
+ await writeFile(file, handlerCode);
866
+ const mod = await import(pathToFileURL(file).href);
867
+ return mod.default ?? {};
868
+ } finally {
869
+ await rm(dir, { recursive: true, force: true });
870
+ }
871
+ }
749
872
  function registerSubgraphTools(server, clientProvider = getClient) {
750
873
  defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only; read rows with subgraphs_query.", {}, async () => {
751
874
  const { data } = await clientProvider().subgraphs.list();
752
875
  return jsonResponse(data.map(formatSubgraphSummary));
753
876
  });
754
- defineTool(server, "subgraphs_status", "Get full details of a subgraph including schema, health, and table columns.", { name: z9.string().describe("Subgraph name") }, async ({ name }) => {
877
+ 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 }) => {
755
878
  const detail = await clientProvider().subgraphs.status(name);
756
879
  return jsonResponse(detail);
757
880
  });
@@ -847,10 +970,10 @@ function registerSubgraphTools(server, clientProvider = getClient) {
847
970
  const result = await clientProvider().subgraphs.delete(name);
848
971
  return textResponse(result.message);
849
972
  });
850
- 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.", {
973
+ 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.", {
851
974
  code: z9.string().describe("TypeScript source code containing a defineSubgraph() call"),
852
975
  startBlock: z9.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy"),
853
- dryRun: z9.boolean().optional().describe("Validate and preview the deploy (schema/DDL) without writing changes")
976
+ dryRun: z9.boolean().optional().describe("Validate schema/DDL only — does not run handlers. Use subgraphs_test to prove a mapping writes rows.")
854
977
  }, async ({ code, startBlock, dryRun }) => {
855
978
  const bundled = await bundleSubgraphCode(code);
856
979
  const result = await clientProvider().subgraphs.deploy({
@@ -866,24 +989,201 @@ function registerSubgraphTools(server, clientProvider = getClient) {
866
989
  });
867
990
  return jsonResponse(result);
868
991
  });
992
+ 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.", {
993
+ code: z9.string().describe("TypeScript source containing a defineSubgraph() call"),
994
+ fromHeight: z9.number().int().nonnegative().describe("Start block height (inclusive)"),
995
+ toHeight: z9.number().int().nonnegative().optional().describe("End block height (inclusive); default fromHeight+100"),
996
+ limit: z9.number().int().positive().max(200).optional().describe("Max Index rows per source (default 50, max 200)")
997
+ }, async ({ code, fromHeight, toHeight, limit }) => {
998
+ const bundled = await bundleSubgraphCode(code);
999
+ const def = await loadBundledDefinition(bundled.handlerCode);
1000
+ if (!def.handlers || !def.schema) {
1001
+ return jsonResponse({
1002
+ ok: false,
1003
+ code: "NO_SOURCES",
1004
+ matched: 0,
1005
+ written: 0,
1006
+ tables: [],
1007
+ hint: "Bundled module must default-export defineSubgraph() with handlers and schema."
1008
+ }, true);
1009
+ }
1010
+ const sources = bundled.sources ?? {};
1011
+ const to = toHeight ?? fromHeight + 100;
1012
+ const pageLimit = Math.min(limit ?? 50, 200);
1013
+ const index = clientProvider().index;
1014
+ const events = {};
1015
+ const skipped = [];
1016
+ let sourcesTested = 0;
1017
+ for (const [name, filter] of Object.entries(sources)) {
1018
+ if (filter.type === "contract_call") {
1019
+ sourcesTested++;
1020
+ const envelope2 = await index.contractCalls.list({
1021
+ ...filter.contractId ? {
1022
+ contractId: Array.isArray(filter.contractId) ? filter.contractId : filter.contractId
1023
+ } : {},
1024
+ ...filter.functionName ? { functionName: filter.functionName } : {},
1025
+ fromHeight,
1026
+ toHeight: to,
1027
+ limit: pageLimit
1028
+ });
1029
+ events[name] = envelope2.contract_calls;
1030
+ continue;
1031
+ }
1032
+ const eventType = eventTypeFor(filter);
1033
+ if (eventType === null) {
1034
+ skipped.push({ source: name, reason: skipReason(name, filter) });
1035
+ continue;
1036
+ }
1037
+ sourcesTested++;
1038
+ const envelope = await index.events.list({
1039
+ eventType,
1040
+ ...filter.contractId ? {
1041
+ contractId: Array.isArray(filter.contractId) ? filter.contractId : filter.contractId
1042
+ } : {},
1043
+ fromHeight,
1044
+ toHeight: to,
1045
+ limit: pageLimit
1046
+ });
1047
+ events[name] = envelope.events;
1048
+ }
1049
+ if (sourcesTested === 0) {
1050
+ return jsonResponse({
1051
+ ok: false,
1052
+ code: "NO_SOURCES",
1053
+ matched: 0,
1054
+ written: 0,
1055
+ tables: [],
1056
+ hint: "No sources were tested — every source is unreadable from Index.",
1057
+ skipped
1058
+ }, true);
1059
+ }
1060
+ const result = await runSubgraphTest({
1061
+ schema: def.schema,
1062
+ handlers: def.handlers,
1063
+ sources,
1064
+ events
1065
+ });
1066
+ const body = { ...result, skipped };
1067
+ if (skipped.length > 0) {
1068
+ const skipHints = skipped.map((s) => s.reason).join(" ");
1069
+ body.hint = result.hint ? `${result.hint} ${skipHints}` : skipHints;
1070
+ }
1071
+ return jsonResponse(body, !result.ok);
1072
+ });
869
1073
  }
870
1074
 
871
- // src/tools/subscriptions.ts
1075
+ // src/tools/webhooks.ts
872
1076
  import { CHAIN_TRIGGER_TYPES } from "@secondlayer/shared";
873
1077
  import { z as z10 } from "zod";
874
- function registerSubscriptionTools(server, clientProvider = getClient) {
875
- defineTool(server, "subscriptions_list", "List all subscriptions for the current account. Returns summary fields (no secrets).", {}, async () => {
876
- const { data } = await clientProvider().subscriptions.list();
1078
+ function registerWebhookTools(server, clientProvider = getClient) {
1079
+ defineTool(server, "webhooks_list", "List all webhooks for the current account. Returns summary fields (no secrets).", {}, async () => {
1080
+ const { data } = await clientProvider().webhooks.list();
1081
+ return jsonResponse(data);
1082
+ });
1083
+ defineTool(server, "webhooks_get", "Get full detail for a webhook (filter, auth, retry config, circuit state).", { id: z10.string().describe("Webhook id") }, async ({ id }) => {
1084
+ const detail = await clientProvider().webhooks.get(id);
1085
+ return jsonResponse(detail);
1086
+ });
1087
+ defineTool(server, "webhooks_create", "Create a webhook. Two kinds (mutually exclusive): a SUBGRAPH webhook fires on a subgraph table's rows (set subgraphName + tableName + optional filter); a CHAIN webhook fires on raw chain events with no subgraph (set triggers). Returns `signingSecret` ONCE — forward it to the user so they can wire it into their receiver.", {
1088
+ name: z10.string().describe("Human-readable name, unique per account"),
1089
+ subgraphName: z10.string().optional().describe("Subgraph to subscribe to (subgraph webhook)"),
1090
+ tableName: z10.string().optional().describe("Table within the subgraph (subgraph webhook)"),
1091
+ triggers: z10.array(z10.object({
1092
+ type: z10.enum(CHAIN_TRIGGER_TYPES),
1093
+ contractId: z10.string().optional(),
1094
+ functionName: z10.string().optional(),
1095
+ caller: z10.string().optional(),
1096
+ sender: z10.string().optional(),
1097
+ recipient: z10.string().optional(),
1098
+ assetIdentifier: z10.string().optional(),
1099
+ deployer: z10.string().optional(),
1100
+ contractName: z10.string().optional(),
1101
+ topic: z10.string().optional(),
1102
+ lockedAddress: z10.string().optional(),
1103
+ trait: z10.string().optional(),
1104
+ minAmount: z10.union([z10.string(), z10.number()]).optional(),
1105
+ maxAmount: z10.union([z10.string(), z10.number()]).optional()
1106
+ })).optional().describe("Chain triggers (chain webhook) — provide INSTEAD of subgraphName/tableName. Each targets a raw chain event/tx; string fields accept `*` wildcards, `trait` scopes to a SIP/trait. Per-type accepted fields: see the secondlayer://chain-triggers resource. Forward-looking: starts at chain tip, no backfill."),
1107
+ url: z10.string().describe("Webhook URL"),
1108
+ format: z10.enum([
1109
+ "standard-webhooks",
1110
+ "inngest",
1111
+ "trigger",
1112
+ "cloudflare",
1113
+ "cloudevents",
1114
+ "raw"
1115
+ ]).optional().describe("Wire format (default standard-webhooks)"),
1116
+ runtime: z10.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
1117
+ filter: z10.record(z10.string(), z10.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}'),
1118
+ authConfig: z10.record(z10.string(), z10.unknown()).optional().describe('Receiver auth sent with each delivery, e.g. {"type": "bearer", "token": "..."}')
1119
+ }, async (input) => {
1120
+ const res = await clientProvider().webhooks.create(input);
1121
+ return jsonResponse(res);
1122
+ });
1123
+ defineTool(server, "webhooks_update", "Patch a webhook (name, url, filter, authConfig, format, runtime, retry, timeout, concurrency).", {
1124
+ id: z10.string(),
1125
+ name: z10.string().optional().describe("Rename the webhook"),
1126
+ url: z10.string().optional(),
1127
+ filter: z10.record(z10.string(), z10.unknown()).optional(),
1128
+ authConfig: z10.record(z10.string(), z10.unknown()).optional().describe("Receiver auth sent with each delivery (bearer/etc.)"),
1129
+ format: z10.enum([
1130
+ "standard-webhooks",
1131
+ "inngest",
1132
+ "trigger",
1133
+ "cloudflare",
1134
+ "cloudevents",
1135
+ "raw"
1136
+ ]).optional(),
1137
+ runtime: z10.enum(["inngest", "trigger", "cloudflare", "node"]).nullable().optional(),
1138
+ maxRetries: z10.number().int().min(0).optional(),
1139
+ timeoutMs: z10.number().int().min(100).optional(),
1140
+ concurrency: z10.number().int().min(1).optional()
1141
+ }, async ({ id, ...patch }) => {
1142
+ const res = await clientProvider().webhooks.update(id, patch);
1143
+ return jsonResponse(res);
1144
+ });
1145
+ defineTool(server, "webhooks_delete", "Delete a webhook. Pending outbox rows are cascade-deleted.", { id: z10.string() }, async ({ id }) => {
1146
+ const res = await clientProvider().webhooks.delete(id);
1147
+ return jsonResponse(res);
1148
+ });
1149
+ defineTool(server, "webhooks_test", "Send a one-off test webhook to a webhook's URL (built for its format, SSRF-guarded). Logged as a delivery row. Returns {ok, statusCode, error, durationMs, deliveryId}.", { id: z10.string().describe("Webhook id") }, async ({ id }) => {
1150
+ const res = await clientProvider().webhooks.test(id);
1151
+ return jsonResponse(res);
1152
+ });
1153
+ defineTool(server, "webhooks_pause", "Pause a webhook: stops delivery attempts and stops queueing new ones. Nothing is deleted — resume picks the webhook back up from the tip. Verify with webhooks_get (status).", { id: z10.string().describe("Webhook id") }, async ({ id }) => jsonResponse(await clientProvider().webhooks.pause(id)));
1154
+ defineTool(server, "webhooks_resume", "Resume a paused webhook (also clears a tripped circuit breaker). Deliveries restart from the current tip — use webhooks_replay for the blocks missed while paused. Verify with webhooks_get (status).", { id: z10.string().describe("Webhook id") }, async ({ id }) => jsonResponse(await clientProvider().webhooks.resume(id)));
1155
+ defineTool(server, "webhooks_rotate_secret", "Rotate a webhook's signing secret. Returns the NEW `signingSecret` ONCE — forward it to the user; deliveries signed with the old secret stop verifying as soon as this returns, so the receiver must be updated.", { id: z10.string().describe("Webhook id") }, async ({ id }) => jsonResponse(await clientProvider().webhooks.rotateSecret(id)));
1156
+ defineTool(server, "webhooks_deliveries", "List recent delivery attempts for a webhook — status code, attempt count, error, duration, timestamps. THIS IS THE VERIFY CALL: after webhooks_create, webhooks_test, or webhooks_replay, read it to confirm the webhook actually landed (and to see the receiver's response when it didn't) instead of trusting the enqueue result.", { id: z10.string().describe("Webhook id") }, async ({ id }) => jsonResponse(await clientProvider().webhooks.deliveries(id)));
1157
+ defineTool(server, "webhooks_dead", "List a webhook's dead-letter queue — deliveries that exhausted their retries, with the outbox id, payload, and last error. Diagnose the receiver first (webhooks_get for the URL/auth, webhooks_test for a live probe), then requeue rows with webhooks_requeue.", { id: z10.string().describe("Webhook id") }, async ({ id }) => jsonResponse(await clientProvider().webhooks.dead(id)));
1158
+ defineTool(server, "webhooks_requeue", "Requeue ONE dead-lettered delivery for another attempt, by its outbox id from webhooks_dead. Fix the receiver first — a requeue against a still-broken endpoint just dies again. Verify with webhooks_deliveries.", {
1159
+ id: z10.string().describe("Webhook id"),
1160
+ outboxId: z10.string().describe("Outbox id of the dead delivery (from webhooks_dead)")
1161
+ }, async ({ id, outboxId }) => jsonResponse(await clientProvider().webhooks.requeue(id, outboxId)));
1162
+ defineTool(server, "webhooks_replay", "Replay a block range for a webhook. Replays run at 10% of batch capacity — use sparingly. Pass `force` (a short idempotency suffix) to re-run a range that was already replayed.", {
1163
+ id: z10.string(),
1164
+ fromBlock: z10.number().int().nonnegative(),
1165
+ toBlock: z10.number().int().nonnegative(),
1166
+ force: z10.string().optional().describe("Idempotency suffix to force a duplicate replay of the range")
1167
+ }, async ({ id, fromBlock, toBlock, force }) => {
1168
+ const res = await clientProvider().webhooks.replay(id, {
1169
+ fromBlock,
1170
+ toBlock,
1171
+ ...force !== undefined ? { force } : {}
1172
+ });
1173
+ return jsonResponse(res);
1174
+ });
1175
+ defineTool(server, "subscriptions_list", "Deprecated alias of webhooks_list. Removed next minor. List all webhooks for the current account. Returns summary fields (no secrets).", {}, async () => {
1176
+ const { data } = await clientProvider().webhooks.list();
877
1177
  return jsonResponse(data);
878
1178
  });
879
- defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id: z10.string().describe("Subscription id") }, async ({ id }) => {
880
- const detail = await clientProvider().subscriptions.get(id);
1179
+ defineTool(server, "subscriptions_get", "Deprecated alias of webhooks_get. Removed next minor. Get full detail for a webhook (filter, auth, retry config, circuit state).", { id: z10.string().describe("Webhook id") }, async ({ id }) => {
1180
+ const detail = await clientProvider().webhooks.get(id);
881
1181
  return jsonResponse(detail);
882
1182
  });
883
- defineTool(server, "subscriptions_create", "Create a subscription. Two kinds (mutually exclusive): a SUBGRAPH subscription fires on a subgraph table's rows (set subgraphName + tableName + optional filter); a CHAIN subscription fires on raw chain events with no subgraph (set triggers). Returns `signingSecret` ONCE — forward it to the user so they can wire it into their receiver.", {
1183
+ defineTool(server, "subscriptions_create", "Deprecated alias of webhooks_create. Removed next minor. Create a webhook. Two kinds (mutually exclusive): a SUBGRAPH webhook fires on a subgraph table's rows (set subgraphName + tableName + optional filter); a CHAIN webhook fires on raw chain events with no subgraph (set triggers). Returns `signingSecret` ONCE — forward it to the user so they can wire it into their receiver.", {
884
1184
  name: z10.string().describe("Human-readable name, unique per account"),
885
- subgraphName: z10.string().optional().describe("Subgraph to subscribe to (subgraph subscription)"),
886
- tableName: z10.string().optional().describe("Table within the subgraph (subgraph subscription)"),
1185
+ subgraphName: z10.string().optional().describe("Subgraph to subscribe to (subgraph webhook)"),
1186
+ tableName: z10.string().optional().describe("Table within the subgraph (subgraph webhook)"),
887
1187
  triggers: z10.array(z10.object({
888
1188
  type: z10.enum(CHAIN_TRIGGER_TYPES),
889
1189
  contractId: z10.string().optional(),
@@ -899,7 +1199,7 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
899
1199
  trait: z10.string().optional(),
900
1200
  minAmount: z10.union([z10.string(), z10.number()]).optional(),
901
1201
  maxAmount: z10.union([z10.string(), z10.number()]).optional()
902
- })).optional().describe("Chain triggers (chain subscription) — provide INSTEAD of subgraphName/tableName. Each targets a raw chain event/tx; string fields accept `*` wildcards, `trait` scopes to a SIP/trait. Per-type accepted fields: see the secondlayer://chain-triggers resource. Forward-looking: starts at chain tip, no backfill."),
1202
+ })).optional().describe("Chain triggers (chain webhook) — provide INSTEAD of subgraphName/tableName. Each targets a raw chain event/tx; string fields accept `*` wildcards, `trait` scopes to a SIP/trait. Per-type accepted fields: see the secondlayer://chain-triggers resource. Forward-looking: starts at chain tip, no backfill."),
903
1203
  url: z10.string().describe("Webhook URL"),
904
1204
  format: z10.enum([
905
1205
  "standard-webhooks",
@@ -913,12 +1213,12 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
913
1213
  filter: z10.record(z10.string(), z10.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}'),
914
1214
  authConfig: z10.record(z10.string(), z10.unknown()).optional().describe('Receiver auth sent with each delivery, e.g. {"type": "bearer", "token": "..."}')
915
1215
  }, async (input) => {
916
- const res = await clientProvider().subscriptions.create(input);
1216
+ const res = await clientProvider().webhooks.create(input);
917
1217
  return jsonResponse(res);
918
1218
  });
919
- defineTool(server, "subscriptions_update", "Patch a subscription (name, url, filter, authConfig, format, runtime, retry, timeout, concurrency).", {
1219
+ defineTool(server, "subscriptions_update", "Deprecated alias of webhooks_update. Removed next minor. Patch a webhook (name, url, filter, authConfig, format, runtime, retry, timeout, concurrency).", {
920
1220
  id: z10.string(),
921
- name: z10.string().optional().describe("Rename the subscription"),
1221
+ name: z10.string().optional().describe("Rename the webhook"),
922
1222
  url: z10.string().optional(),
923
1223
  filter: z10.record(z10.string(), z10.unknown()).optional(),
924
1224
  authConfig: z10.record(z10.string(), z10.unknown()).optional().describe("Receiver auth sent with each delivery (bearer/etc.)"),
@@ -935,33 +1235,33 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
935
1235
  timeoutMs: z10.number().int().min(100).optional(),
936
1236
  concurrency: z10.number().int().min(1).optional()
937
1237
  }, async ({ id, ...patch }) => {
938
- const res = await clientProvider().subscriptions.update(id, patch);
1238
+ const res = await clientProvider().webhooks.update(id, patch);
939
1239
  return jsonResponse(res);
940
1240
  });
941
- defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id: z10.string() }, async ({ id }) => {
942
- const res = await clientProvider().subscriptions.delete(id);
1241
+ defineTool(server, "subscriptions_delete", "Deprecated alias of webhooks_delete. Removed next minor. Delete a webhook. Pending outbox rows are cascade-deleted.", { id: z10.string() }, async ({ id }) => {
1242
+ const res = await clientProvider().webhooks.delete(id);
943
1243
  return jsonResponse(res);
944
1244
  });
945
- defineTool(server, "subscriptions_test", "Send a one-off test webhook to a subscription's URL (built for its format, SSRF-guarded). Logged as a delivery row. Returns {ok, statusCode, error, durationMs, deliveryId}.", { id: z10.string().describe("Subscription id") }, async ({ id }) => {
946
- const res = await clientProvider().subscriptions.test(id);
1245
+ defineTool(server, "subscriptions_test", "Deprecated alias of webhooks_test. Removed next minor. Send a one-off test webhook to a webhook's URL (built for its format, SSRF-guarded). Logged as a delivery row. Returns {ok, statusCode, error, durationMs, deliveryId}.", { id: z10.string().describe("Webhook id") }, async ({ id }) => {
1246
+ const res = await clientProvider().webhooks.test(id);
947
1247
  return jsonResponse(res);
948
1248
  });
949
- defineTool(server, "subscriptions_pause", "Pause a subscription: stops delivery attempts and stops queueing new ones. Nothing is deleted — resume picks the subscription back up from the tip. Verify with subscriptions_get (status).", { id: z10.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.pause(id)));
950
- defineTool(server, "subscriptions_resume", "Resume a paused subscription (also clears a tripped circuit breaker). Deliveries restart from the current tip — use subscriptions_replay for the blocks missed while paused. Verify with subscriptions_get (status).", { id: z10.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.resume(id)));
951
- defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription's webhook signing secret. Returns the NEW `signingSecret` ONCE — forward it to the user; deliveries signed with the old secret stop verifying as soon as this returns, so the receiver must be updated.", { id: z10.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.rotateSecret(id)));
952
- defineTool(server, "subscriptions_deliveries", "List recent delivery attempts for a subscription — status code, attempt count, error, duration, timestamps. THIS IS THE VERIFY CALL: after subscriptions_create, subscriptions_test, or subscriptions_replay, read it to confirm the webhook actually landed (and to see the receiver's response when it didn't) instead of trusting the enqueue result.", { id: z10.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.deliveries(id)));
953
- defineTool(server, "subscriptions_dead", "List a subscription's dead-letter queue — deliveries that exhausted their retries, with the outbox id, payload, and last error. Diagnose the receiver first (subscriptions_get for the URL/auth, subscriptions_test for a live probe), then requeue rows with subscriptions_requeue.", { id: z10.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.dead(id)));
954
- defineTool(server, "subscriptions_requeue", "Requeue ONE dead-lettered delivery for another attempt, by its outbox id from subscriptions_dead. Fix the receiver first — a requeue against a still-broken endpoint just dies again. Verify with subscriptions_deliveries.", {
955
- id: z10.string().describe("Subscription id"),
956
- outboxId: z10.string().describe("Outbox id of the dead delivery (from subscriptions_dead)")
957
- }, async ({ id, outboxId }) => jsonResponse(await clientProvider().subscriptions.requeue(id, outboxId)));
958
- defineTool(server, "subscriptions_replay", "Replay a block range for a subscription. Replays run at 10% of batch capacity — use sparingly. Pass `force` (a short idempotency suffix) to re-run a range that was already replayed.", {
1249
+ defineTool(server, "subscriptions_pause", "Deprecated alias of webhooks_pause. Removed next minor. Pause a webhook: stops delivery attempts and stops queueing new ones. Nothing is deleted — resume picks the webhook back up from the tip. Verify with webhooks_get (status).", { id: z10.string().describe("Webhook id") }, async ({ id }) => jsonResponse(await clientProvider().webhooks.pause(id)));
1250
+ defineTool(server, "subscriptions_resume", "Deprecated alias of webhooks_resume. Removed next minor. Resume a paused webhook (also clears a tripped circuit breaker). Deliveries restart from the current tip — use webhooks_replay for the blocks missed while paused. Verify with webhooks_get (status).", { id: z10.string().describe("Webhook id") }, async ({ id }) => jsonResponse(await clientProvider().webhooks.resume(id)));
1251
+ defineTool(server, "subscriptions_rotate_secret", "Deprecated alias of webhooks_rotate_secret. Removed next minor. Rotate a webhook's signing secret. Returns the NEW `signingSecret` ONCE — forward it to the user; deliveries signed with the old secret stop verifying as soon as this returns, so the receiver must be updated.", { id: z10.string().describe("Webhook id") }, async ({ id }) => jsonResponse(await clientProvider().webhooks.rotateSecret(id)));
1252
+ defineTool(server, "subscriptions_deliveries", "Deprecated alias of webhooks_deliveries. Removed next minor. List recent delivery attempts for a webhook — status code, attempt count, error, duration, timestamps. THIS IS THE VERIFY CALL: after webhooks_create, webhooks_test, or webhooks_replay, read it to confirm the webhook actually landed (and to see the receiver's response when it didn't) instead of trusting the enqueue result.", { id: z10.string().describe("Webhook id") }, async ({ id }) => jsonResponse(await clientProvider().webhooks.deliveries(id)));
1253
+ defineTool(server, "subscriptions_dead", "Deprecated alias of webhooks_dead. Removed next minor. List a webhook's dead-letter queue — deliveries that exhausted their retries, with the outbox id, payload, and last error. Diagnose the receiver first (webhooks_get for the URL/auth, webhooks_test for a live probe), then requeue rows with webhooks_requeue.", { id: z10.string().describe("Webhook id") }, async ({ id }) => jsonResponse(await clientProvider().webhooks.dead(id)));
1254
+ defineTool(server, "subscriptions_requeue", "Deprecated alias of webhooks_requeue. Removed next minor. Requeue ONE dead-lettered delivery for another attempt, by its outbox id from webhooks_dead. Fix the receiver first — a requeue against a still-broken endpoint just dies again. Verify with webhooks_deliveries.", {
1255
+ id: z10.string().describe("Webhook id"),
1256
+ outboxId: z10.string().describe("Outbox id of the dead delivery (from webhooks_dead)")
1257
+ }, async ({ id, outboxId }) => jsonResponse(await clientProvider().webhooks.requeue(id, outboxId)));
1258
+ defineTool(server, "subscriptions_replay", "Deprecated alias of webhooks_replay. Removed next minor. Replay a block range for a webhook. Replays run at 10% of batch capacity — use sparingly. Pass `force` (a short idempotency suffix) to re-run a range that was already replayed.", {
959
1259
  id: z10.string(),
960
1260
  fromBlock: z10.number().int().nonnegative(),
961
1261
  toBlock: z10.number().int().nonnegative(),
962
1262
  force: z10.string().optional().describe("Idempotency suffix to force a duplicate replay of the range")
963
1263
  }, async ({ id, fromBlock, toBlock, force }) => {
964
- const res = await clientProvider().subscriptions.replay(id, {
1264
+ const res = await clientProvider().webhooks.replay(id, {
965
1265
  fromBlock,
966
1266
  toBlock,
967
1267
  ...force !== undefined ? { force } : {}
@@ -972,7 +1272,7 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
972
1272
 
973
1273
  // src/server.ts
974
1274
  var __dirname2 = dirname(fileURLToPath(import.meta.url));
975
- var pkg = JSON.parse(readFileSync(join(__dirname2, "../package.json"), "utf-8"));
1275
+ var pkg = JSON.parse(readFileSync(join2(__dirname2, "../package.json"), "utf-8"));
976
1276
  function createServer() {
977
1277
  const server = new McpServer({
978
1278
  name: "secondlayer",
@@ -980,7 +1280,7 @@ function createServer() {
980
1280
  });
981
1281
  registerScaffoldTools(server);
982
1282
  registerSubgraphTools(server);
983
- registerSubscriptionTools(server);
1283
+ registerWebhookTools(server);
984
1284
  registerIndexTools(server);
985
1285
  registerStreamsTools(server);
986
1286
  registerContractTools(server);
@@ -999,5 +1299,5 @@ export {
999
1299
  createServer
1000
1300
  };
1001
1301
 
1002
- //# debugId=8023DE628F04195164756E2164756E21
1302
+ //# debugId=BBE82EA735BFF52764756E2164756E21
1003
1303
  //# sourceMappingURL=index.js.map