@secondlayer/mcp 0.4.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin-http.js CHANGED
@@ -13,10 +13,94 @@ import { fileURLToPath } from "node:url";
13
13
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
14
 
15
15
  // src/resources.ts
16
- import { templates } from "@secondlayer/subgraphs/templates";
17
-
18
- // src/tools/streams.ts
19
- import { z } from "zod/v4";
16
+ import { templates as subgraphTemplates } from "@secondlayer/subgraphs/templates";
17
+ import { templates as workflowTemplates } from "@secondlayer/workflows/templates";
18
+ var FILTERS_REFERENCE = [
19
+ { type: "stx_transfer", fields: ["sender", "recipient", "minAmount", "maxAmount"] },
20
+ { type: "stx_mint", fields: ["recipient", "minAmount"] },
21
+ { type: "stx_burn", fields: ["sender", "minAmount"] },
22
+ { type: "stx_lock", fields: ["lockedAddress", "minAmount"] },
23
+ { type: "ft_transfer", fields: ["sender", "recipient", "assetIdentifier", "minAmount", "maxAmount"] },
24
+ { type: "ft_mint", fields: ["recipient", "assetIdentifier", "minAmount"] },
25
+ { type: "ft_burn", fields: ["sender", "assetIdentifier", "minAmount"] },
26
+ { type: "nft_transfer", fields: ["sender", "recipient", "assetIdentifier", "tokenId"] },
27
+ { type: "nft_mint", fields: ["recipient", "assetIdentifier", "tokenId"] },
28
+ { type: "nft_burn", fields: ["sender", "assetIdentifier", "tokenId"] },
29
+ { type: "contract_call", fields: ["contract", "function"] },
30
+ { type: "contract_deploy", fields: ["contract"] },
31
+ { type: "print_event", fields: ["contract", "event", "contains"] }
32
+ ];
33
+ var COLUMN_TYPES = [
34
+ {
35
+ type: "uint",
36
+ sqlType: "bigint",
37
+ description: "Unsigned integer (Clarity uint)"
38
+ },
39
+ {
40
+ type: "int",
41
+ sqlType: "bigint",
42
+ description: "Signed integer (Clarity int)"
43
+ },
44
+ { type: "text", sqlType: "text", description: "UTF-8 string" },
45
+ {
46
+ type: "principal",
47
+ sqlType: "text",
48
+ description: "Stacks address (standard or contract)"
49
+ },
50
+ { type: "bool", sqlType: "boolean", description: "Boolean value" },
51
+ { type: "json", sqlType: "jsonb", description: "Arbitrary JSON data" },
52
+ {
53
+ options: ["nullable", "indexed", "search"],
54
+ description: "Column options: nullable allows NULL, indexed creates a B-tree index, search enables full-text search"
55
+ }
56
+ ];
57
+ function registerResources(server) {
58
+ server.resource("filters", "secondlayer://filters", { description: "Event filter types and their available fields" }, async () => ({
59
+ contents: [
60
+ {
61
+ uri: "secondlayer://filters",
62
+ mimeType: "application/json",
63
+ text: JSON.stringify(FILTERS_REFERENCE, null, 2)
64
+ }
65
+ ]
66
+ }));
67
+ server.resource("column-types", "secondlayer://column-types", { description: "Subgraph column types, SQL mappings, and options" }, async () => ({
68
+ contents: [
69
+ {
70
+ uri: "secondlayer://column-types",
71
+ mimeType: "application/json",
72
+ text: JSON.stringify(COLUMN_TYPES, null, 2)
73
+ }
74
+ ]
75
+ }));
76
+ server.resource("templates", "secondlayer://templates", {
77
+ description: "Available subgraph and workflow templates with descriptions and categories"
78
+ }, async () => ({
79
+ contents: [
80
+ {
81
+ uri: "secondlayer://templates",
82
+ mimeType: "application/json",
83
+ text: JSON.stringify([
84
+ ...subgraphTemplates.map(({ id, name, description, category }) => ({
85
+ kind: "subgraph",
86
+ id,
87
+ name,
88
+ description,
89
+ category
90
+ })),
91
+ ...workflowTemplates.map(({ id, name, description, category, trigger }) => ({
92
+ kind: "workflow",
93
+ id,
94
+ name,
95
+ description,
96
+ category,
97
+ trigger
98
+ }))
99
+ ], null, 2)
100
+ }
101
+ ]
102
+ }));
103
+ }
20
104
 
21
105
  // src/lib/client.ts
22
106
  import { SecondLayer } from "@secondlayer/sdk";
@@ -27,7 +111,12 @@ function getClient() {
27
111
  if (!apiKey) {
28
112
  throw new Error("SECONDLAYER_API_KEY environment variable is required. " + "Get your key at https://app.secondlayer.tools/settings/api-keys");
29
113
  }
30
- instance = new SecondLayer({ apiKey });
114
+ const baseUrl = process.env.SECONDLAYER_API_URL;
115
+ instance = new SecondLayer({
116
+ apiKey,
117
+ origin: "mcp",
118
+ ...baseUrl ? { baseUrl } : {}
119
+ });
31
120
  }
32
121
  return instance;
33
122
  }
@@ -56,16 +145,6 @@ async function apiRequest(method, path, body) {
56
145
  }
57
146
 
58
147
  // src/lib/format.ts
59
- function formatStreamSummary(s) {
60
- return {
61
- id: s.id,
62
- name: s.name,
63
- status: s.status,
64
- endpointUrl: s.endpointUrl,
65
- totalDeliveries: s.totalDeliveries,
66
- failedDeliveries: s.failedDeliveries
67
- };
68
- }
69
148
  function formatSubgraphSummary(s) {
70
149
  return {
71
150
  name: s.name,
@@ -74,16 +153,6 @@ function formatSubgraphSummary(s) {
74
153
  lastProcessedBlock: s.lastProcessedBlock
75
154
  };
76
155
  }
77
- function formatDeliverySummary(d) {
78
- return {
79
- id: d.id,
80
- blockHeight: d.blockHeight,
81
- status: d.status,
82
- statusCode: d.statusCode,
83
- attempts: d.attempts,
84
- createdAt: d.createdAt
85
- };
86
- }
87
156
  function withCap(items, cap) {
88
157
  return {
89
158
  items: items.slice(0, cap),
@@ -91,6 +160,12 @@ function withCap(items, cap) {
91
160
  total: items.length
92
161
  };
93
162
  }
163
+ function jsonResponse(data, isError) {
164
+ return {
165
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
166
+ ...isError && { isError: true }
167
+ };
168
+ }
94
169
 
95
170
  // src/lib/tool.ts
96
171
  function defineTool(server, name, description, schema, handler) {
@@ -115,514 +190,17 @@ function defineTool(server, name, description, schema, handler) {
115
190
  server.tool(name, description, schema, wrappedHandler);
116
191
  }
117
192
 
118
- // src/tools/streams.ts
119
- var FilterSchema = z.discriminatedUnion("type", [
120
- z.object({
121
- type: z.literal("stx_transfer"),
122
- sender: z.string().optional(),
123
- recipient: z.string().optional(),
124
- minAmount: z.number().optional(),
125
- maxAmount: z.number().optional()
126
- }),
127
- z.object({
128
- type: z.literal("stx_mint"),
129
- recipient: z.string().optional(),
130
- minAmount: z.number().optional()
131
- }),
132
- z.object({
133
- type: z.literal("stx_burn"),
134
- sender: z.string().optional(),
135
- minAmount: z.number().optional()
136
- }),
137
- z.object({
138
- type: z.literal("stx_lock"),
139
- lockedAddress: z.string().optional(),
140
- minAmount: z.number().optional()
141
- }),
142
- z.object({
143
- type: z.literal("ft_transfer"),
144
- sender: z.string().optional(),
145
- recipient: z.string().optional(),
146
- assetIdentifier: z.string().optional(),
147
- minAmount: z.number().optional()
148
- }),
149
- z.object({
150
- type: z.literal("ft_mint"),
151
- recipient: z.string().optional(),
152
- assetIdentifier: z.string().optional(),
153
- minAmount: z.number().optional()
154
- }),
155
- z.object({
156
- type: z.literal("ft_burn"),
157
- sender: z.string().optional(),
158
- assetIdentifier: z.string().optional(),
159
- minAmount: z.number().optional()
160
- }),
161
- z.object({
162
- type: z.literal("nft_transfer"),
163
- sender: z.string().optional(),
164
- recipient: z.string().optional(),
165
- assetIdentifier: z.string().optional(),
166
- tokenId: z.string().optional()
167
- }),
168
- z.object({
169
- type: z.literal("nft_mint"),
170
- recipient: z.string().optional(),
171
- assetIdentifier: z.string().optional(),
172
- tokenId: z.string().optional()
173
- }),
174
- z.object({
175
- type: z.literal("nft_burn"),
176
- sender: z.string().optional(),
177
- assetIdentifier: z.string().optional(),
178
- tokenId: z.string().optional()
179
- }),
180
- z.object({
181
- type: z.literal("contract_call"),
182
- contractId: z.string().optional(),
183
- functionName: z.string().optional(),
184
- caller: z.string().optional()
185
- }),
186
- z.object({
187
- type: z.literal("contract_deploy"),
188
- deployer: z.string().optional(),
189
- contractName: z.string().optional()
190
- }),
191
- z.object({
192
- type: z.literal("print_event"),
193
- contractId: z.string().optional(),
194
- topic: z.string().optional(),
195
- contains: z.string().optional()
196
- })
197
- ]);
198
- function registerStreamTools(server) {
199
- defineTool(server, "streams_list", "List all webhook streams. Returns summary fields only.", {
200
- status: z.enum(["active", "inactive", "paused", "failed"]).optional().describe("Filter by status")
201
- }, async ({ status }) => {
202
- const { streams } = await getClient().streams.list(status ? { status } : undefined);
203
- return {
204
- content: [
205
- {
206
- type: "text",
207
- text: JSON.stringify(streams.map(formatStreamSummary), null, 2)
208
- }
209
- ]
210
- };
211
- });
212
- defineTool(server, "streams_get", "Get full details of a stream by ID (accepts UUID prefix).", { id: z.string().describe("Stream UUID or prefix") }, async ({ id }) => {
213
- const stream = await getClient().streams.get(id);
214
- return {
215
- content: [{ type: "text", text: JSON.stringify(stream, null, 2) }]
216
- };
217
- });
218
- defineTool(server, "streams_create", "Create a new webhook stream with filters.", {
219
- name: z.string().describe("Stream name"),
220
- endpointUrl: z.string().describe("Webhook endpoint URL"),
221
- filters: z.array(FilterSchema).min(1).describe("Event filters (at least one required)")
222
- }, async ({ name, endpointUrl, filters }) => {
223
- const result = await getClient().streams.create({
224
- name,
225
- endpointUrl,
226
- filters
227
- });
228
- return {
229
- content: [
230
- {
231
- type: "text",
232
- text: JSON.stringify({ id: result.stream.id, signingSecret: result.signingSecret }, null, 2)
233
- }
234
- ]
235
- };
236
- });
237
- defineTool(server, "streams_update", "Update a stream's name, endpoint, or filters.", {
238
- id: z.string().describe("Stream UUID or prefix"),
239
- name: z.string().optional().describe("New name"),
240
- endpointUrl: z.string().optional().describe("New endpoint URL"),
241
- filters: z.array(FilterSchema).min(1).optional().describe("New filters")
242
- }, async ({ id, name, endpointUrl, filters }) => {
243
- const data = {};
244
- if (name !== undefined)
245
- data.name = name;
246
- if (endpointUrl !== undefined)
247
- data.endpointUrl = endpointUrl;
248
- if (filters !== undefined)
249
- data.filters = filters;
250
- const stream = await getClient().streams.update(id, data);
251
- return {
252
- content: [
253
- {
254
- type: "text",
255
- text: JSON.stringify(formatStreamSummary(stream), null, 2)
256
- }
257
- ]
258
- };
259
- });
260
- defineTool(server, "streams_delete", "Delete a stream permanently.", { id: z.string().describe("Stream UUID or prefix") }, async ({ id }) => {
261
- await getClient().streams.delete(id);
262
- return { content: [{ type: "text", text: `Stream ${id} deleted.` }] };
263
- });
264
- defineTool(server, "streams_toggle", "Enable or disable a stream.", {
265
- id: z.string().describe("Stream UUID or prefix"),
266
- enabled: z.boolean().describe("true to enable, false to disable")
267
- }, async ({ id, enabled }) => {
268
- const stream = enabled ? await getClient().streams.enable(id) : await getClient().streams.disable(id);
269
- return {
270
- content: [
271
- {
272
- type: "text",
273
- text: JSON.stringify({ id: stream.id, status: stream.status }, null, 2)
274
- }
275
- ]
276
- };
277
- });
278
- defineTool(server, "streams_deliveries", "List recent deliveries for a stream (max 25).", {
279
- id: z.string().describe("Stream UUID or prefix"),
280
- limit: z.number().max(25).optional().describe("Max results (default 25)"),
281
- status: z.string().optional().describe("Filter by delivery status")
282
- }, async ({ id, limit, status }) => {
283
- const { deliveries } = await getClient().streams.listDeliveries(id, {
284
- limit: limit ?? 25,
285
- status
286
- });
287
- const result = withCap(deliveries.map(formatDeliverySummary), 25);
288
- return {
289
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
290
- };
291
- });
292
- defineTool(server, "streams_pause_all", "Pause all active streams. Without confirm: true, returns a preview of streams that would be paused.", {
293
- confirm: z.boolean().optional().describe("Set to true to execute. Omit or false for preview only.")
294
- }, async ({ confirm }) => {
295
- if (!confirm) {
296
- const { streams } = await getClient().streams.list({
297
- status: "active"
298
- });
299
- const names = streams.map((s) => s.name);
300
- return {
301
- content: [
302
- {
303
- type: "text",
304
- text: JSON.stringify({ preview: true, count: names.length, streams: names }, null, 2)
305
- }
306
- ]
307
- };
308
- }
309
- const result = await getClient().streams.pauseAll();
310
- return {
311
- content: [
312
- {
313
- type: "text",
314
- text: JSON.stringify({
315
- paused: result.paused,
316
- streams: result.streams.map(formatStreamSummary)
317
- }, null, 2)
318
- }
319
- ]
320
- };
321
- });
322
- defineTool(server, "streams_resume_all", "Resume all paused streams. Without confirm: true, returns a preview of streams that would be resumed.", {
323
- confirm: z.boolean().optional().describe("Set to true to execute. Omit or false for preview only.")
324
- }, async ({ confirm }) => {
325
- if (!confirm) {
326
- const { streams } = await getClient().streams.list({
327
- status: "paused"
328
- });
329
- const names = streams.map((s) => s.name);
330
- return {
331
- content: [
332
- {
333
- type: "text",
334
- text: JSON.stringify({ preview: true, count: names.length, streams: names }, null, 2)
335
- }
336
- ]
337
- };
338
- }
339
- const result = await getClient().streams.resumeAll();
340
- return {
341
- content: [
342
- {
343
- type: "text",
344
- text: JSON.stringify({
345
- resumed: result.resumed,
346
- streams: result.streams.map(formatStreamSummary)
347
- }, null, 2)
348
- }
349
- ]
350
- };
351
- });
352
- defineTool(server, "streams_replay", "Replay blocks through a stream, re-delivering events for a block range.", {
353
- id: z.string().describe("Stream UUID or prefix"),
354
- fromBlock: z.number().describe("Start block height (inclusive)"),
355
- toBlock: z.number().describe("End block height (inclusive)")
356
- }, async ({ id, fromBlock, toBlock }) => {
357
- const result = await apiRequest("POST", `/api/streams/${id}/replay`, { fromBlock, toBlock });
358
- return {
359
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
360
- };
361
- });
362
- defineTool(server, "streams_rotate_secret", "Rotate the signing secret for a stream. Returns the new secret.", { id: z.string().describe("Stream UUID or prefix") }, async ({ id }) => {
363
- const result = await getClient().streams.rotateSecret(id);
364
- return {
365
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
366
- };
367
- });
368
- }
369
-
370
- // src/resources.ts
371
- var FILTERS_REFERENCE = FilterSchema.options.map((opt) => ({
372
- type: opt.shape.type.def.values[0],
373
- fields: Object.keys(opt.shape).filter((k) => k !== "type")
374
- }));
375
- var COLUMN_TYPES = [
376
- {
377
- type: "uint",
378
- sqlType: "bigint",
379
- description: "Unsigned integer (Clarity uint)"
380
- },
381
- {
382
- type: "int",
383
- sqlType: "bigint",
384
- description: "Signed integer (Clarity int)"
385
- },
386
- { type: "text", sqlType: "text", description: "UTF-8 string" },
387
- {
388
- type: "principal",
389
- sqlType: "text",
390
- description: "Stacks address (standard or contract)"
391
- },
392
- { type: "bool", sqlType: "boolean", description: "Boolean value" },
393
- { type: "json", sqlType: "jsonb", description: "Arbitrary JSON data" },
394
- {
395
- options: ["nullable", "indexed", "search"],
396
- description: "Column options: nullable allows NULL, indexed creates a B-tree index, search enables full-text search"
397
- }
398
- ];
399
- function registerResources(server) {
400
- server.resource("filters", "secondlayer://filters", { description: "Stream filter types and their available fields" }, async () => ({
401
- contents: [
402
- {
403
- uri: "secondlayer://filters",
404
- mimeType: "application/json",
405
- text: JSON.stringify(FILTERS_REFERENCE, null, 2)
406
- }
407
- ]
408
- }));
409
- server.resource("column-types", "secondlayer://column-types", { description: "Subgraph column types, SQL mappings, and options" }, async () => ({
410
- contents: [
411
- {
412
- uri: "secondlayer://column-types",
413
- mimeType: "application/json",
414
- text: JSON.stringify(COLUMN_TYPES, null, 2)
415
- }
416
- ]
417
- }));
418
- server.resource("templates", "secondlayer://templates", {
419
- description: "Available subgraph templates with descriptions and categories"
420
- }, async () => ({
421
- contents: [
422
- {
423
- uri: "secondlayer://templates",
424
- mimeType: "application/json",
425
- text: JSON.stringify(templates.map(({ id, name, description, category }) => ({
426
- id,
427
- name,
428
- description,
429
- category
430
- })), null, 2)
431
- }
432
- ]
433
- }));
434
- }
435
-
436
193
  // src/tools/account.ts
437
194
  function registerAccountTools(server) {
438
195
  defineTool(server, "account_whoami", "Show the authenticated account's email and plan.", {}, async () => {
439
196
  const result = await apiRequest("GET", "/api/accounts/me");
440
- return {
441
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
442
- };
197
+ return jsonResponse(result);
443
198
  });
444
199
  }
445
200
 
446
201
  // src/tools/scaffold.ts
447
- import { z as z2 } from "zod/v4";
448
-
449
- // src/lib/scaffold-generate.ts
450
- function isAbiBuffer(t) {
451
- return typeof t === "object" && t !== null && "buff" in t;
452
- }
453
- function isAbiStringAscii(t) {
454
- return typeof t === "object" && t !== null && "string-ascii" in t;
455
- }
456
- function isAbiStringUtf8(t) {
457
- return typeof t === "object" && t !== null && "string-utf8" in t;
458
- }
459
- function isAbiOptional(t) {
460
- return typeof t === "object" && t !== null && "optional" in t;
461
- }
462
- function isAbiTuple(t) {
463
- return typeof t === "object" && t !== null && "tuple" in t;
464
- }
465
- function isAbiList(t) {
466
- return typeof t === "object" && t !== null && "list" in t;
467
- }
468
- function isAbiResponse(t) {
469
- return typeof t === "object" && t !== null && "response" in t;
470
- }
471
- function mapType(abiType, nullable) {
472
- if (typeof abiType === "string") {
473
- switch (abiType) {
474
- case "uint128":
475
- return { type: "uint", nullable };
476
- case "int128":
477
- return { type: "int", nullable };
478
- case "principal":
479
- case "trait_reference":
480
- return { type: "principal", nullable };
481
- case "bool":
482
- return { type: "boolean", nullable };
483
- default: {
484
- const s = abiType;
485
- if (s.includes("uint"))
486
- return { type: "uint", nullable };
487
- if (s.includes("int"))
488
- return { type: "int", nullable };
489
- if (s.includes("string") || s.includes("ascii") || s.includes("utf8")) {
490
- return { type: "text", nullable };
491
- }
492
- if (s.includes("buff"))
493
- return { type: "text", nullable };
494
- return { type: "jsonb", nullable };
495
- }
496
- }
497
- }
498
- if (isAbiBuffer(abiType))
499
- return { type: "text", nullable };
500
- if (isAbiStringAscii(abiType) || isAbiStringUtf8(abiType)) {
501
- return { type: "text", nullable };
502
- }
503
- if (isAbiOptional(abiType))
504
- return mapType(abiType.optional, true);
505
- if (isAbiList(abiType) || isAbiTuple(abiType))
506
- return { type: "jsonb", nullable };
507
- if (isAbiResponse(abiType)) {
508
- return mapType(abiType.response.ok, nullable);
509
- }
510
- return { type: "jsonb", nullable };
511
- }
512
- function clarityTypeToSubgraphColumn(abiType) {
513
- return mapType(abiType, false);
514
- }
515
- function toSnake(name) {
516
- return name.replace(/-/g, "_");
517
- }
518
- function toCamel(name) {
519
- return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
520
- }
521
- function buildColumns(args) {
522
- if (args.length === 0)
523
- return " _placeholder: { type: 'text' }";
524
- return args.map((arg) => {
525
- const mapped = clarityTypeToSubgraphColumn(arg.type);
526
- const nullable = mapped.nullable ? ", nullable: true" : "";
527
- return ` ${toSnake(arg.name)}: { type: '${mapped.type}'${nullable} }`;
528
- }).join(`,
529
- `);
530
- }
531
- function buildInsertCall(tableName, args) {
532
- if (args.length === 0) {
533
- return ` ctx.insert('${tableName}', {
534
- sender: ctx.tx.sender,
535
- });`;
536
- }
537
- const mappings = args.map((arg) => {
538
- return ` ${toSnake(arg.name)}: event.${toCamel(arg.name)}`;
539
- });
540
- return ` ctx.insert('${tableName}', {
541
- ${mappings.join(`,
542
- `)},
543
- });`;
544
- }
545
- function generateSubgraphCode(contractId, functions, subgraphName, events) {
546
- const contractParts = contractId.split(".");
547
- const contractName = contractParts[contractParts.length - 1] ?? contractId;
548
- const name = subgraphName ?? contractName;
549
- const publicFunctions = functions.filter((f) => f.access === "public");
550
- const hasEvents = events && events.length > 0;
551
- if (publicFunctions.length === 0 && !hasEvents) {
552
- return `// No public functions or events selected for ${contractId}`;
553
- }
554
- const tableDefs = [];
555
- if (hasEvents) {
556
- for (const ev of events) {
557
- const tableName = toSnake(ev.name);
558
- let columns;
559
- if (isAbiTuple(ev.value)) {
560
- columns = buildColumns(ev.value.tuple);
561
- } else {
562
- columns = ` value: { type: '${clarityTypeToSubgraphColumn(ev.value).type}' }`;
563
- }
564
- tableDefs.push(` ${tableName}: {
565
- columns: {
566
- ${columns}
567
- }
568
- }`);
569
- }
570
- }
571
- for (const fn of publicFunctions) {
572
- const tableName = toSnake(fn.name);
573
- const columns = buildColumns(fn.args);
574
- tableDefs.push(` ${tableName}: {
575
- columns: {
576
- ${columns}
577
- }
578
- }`);
579
- }
580
- const schemaBlock = tableDefs.join(`,
581
- `);
582
- const sourceEntries = [`{ contract: '${contractId}' }`];
583
- const handlerEntries = [];
584
- if (hasEvents) {
585
- for (const ev of events) {
586
- const tableName = toSnake(ev.name);
587
- let insertCall;
588
- if (isAbiTuple(ev.value)) {
589
- insertCall = buildInsertCall(tableName, ev.value.tuple);
590
- } else {
591
- insertCall = ` ctx.insert('${tableName}', {
592
- value: event.value,
593
- });`;
594
- }
595
- handlerEntries.push(` '${contractId}::${ev.name}': async (event, ctx) => {
596
- ${insertCall}
597
- }`);
598
- }
599
- }
600
- for (const fn of publicFunctions) {
601
- const tableName = toSnake(fn.name);
602
- const insertCall = buildInsertCall(tableName, fn.args);
603
- handlerEntries.push(` '${contractId}::${fn.name}': async (event, ctx) => {
604
- ${insertCall}
605
- }`);
606
- }
607
- const handlersBlock = handlerEntries.join(`,
608
-
609
- `);
610
- return `import { defineSubgraph } from '@secondlayer/subgraphs';
611
-
612
- export default defineSubgraph({
613
- name: '${name}',
614
- sources: [${sourceEntries.join(", ")}],
615
- schema: {
616
- ${schemaBlock}
617
- },
618
- handlers: {
619
- ${handlersBlock}
620
- }
621
- });
622
- `;
623
- }
624
-
625
- // src/tools/scaffold.ts
202
+ import { generateSubgraphCode } from "@secondlayer/scaffold";
203
+ import { z } from "zod/v4";
626
204
  var API_BASE = process.env.SECONDLAYER_API_URL || "https://api.secondlayer.tools";
627
205
  async function fetchAbi(contractId) {
628
206
  const res = await fetch(`${API_BASE}/api/node/contracts/${contractId}/abi`, {
@@ -641,17 +219,17 @@ async function fetchAbi(contractId) {
641
219
  }
642
220
  function registerScaffoldTools(server) {
643
221
  defineTool(server, "scaffold_from_contract", "Generate a subgraph scaffold from a deployed Stacks contract. Fetches the ABI automatically.", {
644
- contractId: z2.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
645
- subgraphName: z2.string().optional().describe("Override the subgraph name (defaults to contract name)")
222
+ contractId: z.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
223
+ subgraphName: z.string().optional().describe("Override the subgraph name (defaults to contract name)")
646
224
  }, async ({ contractId, subgraphName }) => {
647
225
  const { functions, maps } = await fetchAbi(contractId);
648
226
  const code = generateSubgraphCode(contractId, functions, subgraphName, maps);
649
227
  return { content: [{ type: "text", text: code }] };
650
228
  });
651
229
  defineTool(server, "scaffold_from_abi", "Generate a subgraph scaffold from a provided ABI JSON. Use when you already have the ABI.", {
652
- abi: z2.string().describe("ABI JSON string (the full contract ABI object)"),
653
- contractId: z2.string().describe("Fully qualified contract ID"),
654
- subgraphName: z2.string().optional().describe("Override the subgraph name")
230
+ abi: z.string().describe("ABI JSON string (the full contract ABI object)"),
231
+ contractId: z.string().describe("Fully qualified contract ID"),
232
+ subgraphName: z.string().optional().describe("Override the subgraph name")
655
233
  }, async ({ abi, contractId, subgraphName }) => {
656
234
  let parsed;
657
235
  try {
@@ -668,51 +246,8 @@ function registerScaffoldTools(server) {
668
246
  }
669
247
 
670
248
  // src/tools/subgraphs.ts
671
- import { z as z3 } from "zod/v4";
672
-
673
- // src/lib/bundle.ts
674
- import { validateSubgraphDefinition } from "@secondlayer/subgraphs/validate";
675
- import esbuild from "esbuild";
676
- async function bundleSubgraphCode(code) {
677
- let result;
678
- try {
679
- result = await esbuild.build({
680
- stdin: { contents: code, loader: "ts", resolveDir: process.cwd() },
681
- bundle: true,
682
- platform: "node",
683
- format: "esm",
684
- external: ["@secondlayer/subgraphs"],
685
- write: false
686
- });
687
- } catch (err) {
688
- throw new Error(`Bundle failed: ${err instanceof Error ? err.message : String(err)}`);
689
- }
690
- const handlerCode = new TextDecoder().decode(result.outputFiles[0].contents);
691
- let mod;
692
- try {
693
- const dataUri = `data:text/javascript;base64,${Buffer.from(handlerCode).toString("base64")}`;
694
- mod = await import(dataUri);
695
- } catch (err) {
696
- throw new Error(`Module evaluation failed: ${err instanceof Error ? err.message : String(err)}`);
697
- }
698
- const def = mod.default ?? mod;
699
- let validated;
700
- try {
701
- validated = validateSubgraphDefinition(def);
702
- } catch (err) {
703
- throw new Error(`Validation failed: ${err instanceof Error ? err.message : String(err)}`);
704
- }
705
- return {
706
- name: validated.name,
707
- version: validated.version,
708
- description: validated.description,
709
- sources: validated.sources,
710
- schema: validated.schema,
711
- handlerCode
712
- };
713
- }
714
-
715
- // src/tools/subgraphs.ts
249
+ import { bundleSubgraphCode } from "@secondlayer/bundler";
250
+ import { z as z2 } from "zod/v4";
716
251
  function registerSubgraphTools(server) {
717
252
  defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only.", {}, async () => {
718
253
  const { data } = await getClient().subgraphs.list();
@@ -725,22 +260,22 @@ function registerSubgraphTools(server) {
725
260
  ]
726
261
  };
727
262
  });
728
- defineTool(server, "subgraphs_get", "Get full details of a subgraph including schema, health, and table columns.", { name: z3.string().describe("Subgraph name") }, async ({ name }) => {
263
+ defineTool(server, "subgraphs_get", "Get full details of a subgraph including schema, health, and table columns.", { name: z2.string().describe("Subgraph name") }, async ({ name }) => {
729
264
  const detail = await getClient().subgraphs.get(name);
730
265
  return {
731
266
  content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
732
267
  };
733
268
  });
734
269
  defineTool(server, "subgraphs_query", 'Query rows from a subgraph table (max 200 rows). Filters support operators: "amount.gte": "1000", "sender.neq": "SP...", "name.like": "%token%". Available operators: eq, neq, gt, gte, lt, lte, like.', {
735
- name: z3.string().describe("Subgraph name"),
736
- table: z3.string().describe("Table name"),
737
- filters: z3.record(z3.string(), z3.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
738
- sort: z3.string().optional().describe("Column to sort by"),
739
- order: z3.enum(["asc", "desc"]).optional().describe("Sort order"),
740
- limit: z3.number().max(200).optional().describe("Max rows (default 50, max 200)"),
741
- offset: z3.number().optional().describe("Offset for pagination"),
742
- fields: z3.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
743
- count: z3.boolean().optional().describe("If true, return row count instead of rows")
270
+ name: z2.string().describe("Subgraph name"),
271
+ table: z2.string().describe("Table name"),
272
+ filters: z2.record(z2.string(), z2.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
273
+ sort: z2.string().optional().describe("Column to sort by"),
274
+ order: z2.enum(["asc", "desc"]).optional().describe("Sort order"),
275
+ limit: z2.number().max(200).optional().describe("Max rows (default 50, max 200)"),
276
+ offset: z2.number().optional().describe("Offset for pagination"),
277
+ fields: z2.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
278
+ count: z2.boolean().optional().describe("If true, return row count instead of rows")
744
279
  }, async ({
745
280
  name,
746
281
  table,
@@ -773,9 +308,9 @@ function registerSubgraphTools(server) {
773
308
  };
774
309
  });
775
310
  defineTool(server, "subgraphs_reindex", "Reindex a subgraph from a specific block range.", {
776
- name: z3.string().describe("Subgraph name"),
777
- fromBlock: z3.number().optional().describe("Start block (defaults to beginning)"),
778
- toBlock: z3.number().optional().describe("End block (defaults to latest)")
311
+ name: z2.string().describe("Subgraph name"),
312
+ fromBlock: z2.number().optional().describe("Start block (defaults to beginning)"),
313
+ toBlock: z2.number().optional().describe("End block (defaults to latest)")
779
314
  }, async ({ name, fromBlock, toBlock }) => {
780
315
  const result = await getClient().subgraphs.reindex(name, {
781
316
  fromBlock,
@@ -785,13 +320,13 @@ function registerSubgraphTools(server) {
785
320
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
786
321
  };
787
322
  });
788
- defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z3.string().describe("Subgraph name") }, async ({ name }) => {
323
+ defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z2.string().describe("Subgraph name") }, async ({ name }) => {
789
324
  const result = await getClient().subgraphs.delete(name);
790
325
  return { content: [{ type: "text", text: result.message }] };
791
326
  });
792
327
  defineTool(server, "subgraphs_deploy", "Deploy a subgraph from TypeScript code. Pass the full defineSubgraph() source — it will be bundled, validated, and deployed.", {
793
- code: z3.string().describe("TypeScript source code containing a defineSubgraph() call"),
794
- reindex: z3.boolean().optional().describe("Force reindex on breaking schema change (drops and rebuilds all data)")
328
+ code: z2.string().describe("TypeScript source code containing a defineSubgraph() call"),
329
+ reindex: z2.boolean().optional().describe("Force reindex on breaking schema change (drops and rebuilds all data)")
795
330
  }, async ({ code, reindex }) => {
796
331
  const bundled = await bundleSubgraphCode(code);
797
332
  const result = await getClient().subgraphs.deploy({
@@ -801,16 +336,33 @@ function registerSubgraphTools(server) {
801
336
  sources: bundled.sources,
802
337
  schema: bundled.schema,
803
338
  handlerCode: bundled.handlerCode,
339
+ sourceCode: code,
804
340
  reindex
805
341
  });
806
342
  return {
807
343
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
808
344
  };
809
345
  });
346
+ defineTool(server, "subgraphs_read_source", "Fetch the deployed TypeScript source of a subgraph (plus its stored version). Returns a readOnly payload for subgraphs deployed before source capture — in that case the caller should redeploy via CLI before editing.", { name: z2.string().describe("Subgraph name") }, async ({ name }) => {
347
+ const source = await getClient().subgraphs.getSource(name);
348
+ return {
349
+ content: [{ type: "text", text: JSON.stringify(source, null, 2) }]
350
+ };
351
+ });
810
352
  }
811
353
 
812
354
  // src/tools/workflows.ts
813
- import { z as z4 } from "zod/v4";
355
+ import { bundleWorkflowCode } from "@secondlayer/bundler";
356
+ import {
357
+ generateWorkflowCode
358
+ } from "@secondlayer/scaffold";
359
+ import { VersionConflictError } from "@secondlayer/sdk";
360
+ import {
361
+ getTemplateById as getWorkflowTemplateById,
362
+ templates as workflowTemplates2
363
+ } from "@secondlayer/workflows/templates";
364
+ import { createPatch } from "diff";
365
+ import { z as z3 } from "zod/v4";
814
366
  function registerWorkflowTools(server) {
815
367
  defineTool(server, "workflows_list", "List all workflows. Returns summary fields only.", {}, async () => {
816
368
  const { workflows } = await getClient().workflows.list();
@@ -823,15 +375,155 @@ function registerWorkflowTools(server) {
823
375
  ]
824
376
  };
825
377
  });
826
- defineTool(server, "workflows_get", "Get full details of a workflow by name.", { name: z4.string().describe("Workflow name") }, async ({ name }) => {
378
+ defineTool(server, "workflows_get", "Get full details of a workflow by name.", { name: z3.string().describe("Workflow name") }, async ({ name }) => {
827
379
  const detail = await getClient().workflows.get(name);
828
380
  return {
829
381
  content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
830
382
  };
831
383
  });
384
+ defineTool(server, "workflows_get_definition", "Return the deployed TypeScript source of a workflow plus its stored version. Returns `sourceCode: null` + `readOnly: true` for workflows deployed before source capture.", { name: z3.string().describe("Workflow name") }, async ({ name }) => {
385
+ const source = await getClient().workflows.getSource(name);
386
+ return {
387
+ content: [{ type: "text", text: JSON.stringify(source, null, 2) }]
388
+ };
389
+ });
390
+ defineTool(server, "workflows_propose_edit", "Validate a proposed edit WITHOUT deploying. Fetches the current stored source, bundles the proposed source, computes a unified diff, and returns everything for review. Use this when you want to show the user a diff before committing — pair it with workflows_deploy(expectedVersion=...) to persist.", {
391
+ name: z3.string().describe("Workflow name"),
392
+ proposedCode: z3.string().describe("New TypeScript source — must compile and validate."),
393
+ expectedVersion: z3.string().regex(/^\d+\.\d+\.\d+$/).optional().describe("Version the proposer is editing from (for audit).")
394
+ }, async ({ name, proposedCode, expectedVersion }) => {
395
+ const current = await getClient().workflows.getSource(name);
396
+ if (current.sourceCode === null) {
397
+ return {
398
+ isError: true,
399
+ content: [
400
+ {
401
+ type: "text",
402
+ text: JSON.stringify({
403
+ error: "Workflow has no stored source. Redeploy via CLI first.",
404
+ readOnly: true,
405
+ version: current.version
406
+ }, null, 2)
407
+ }
408
+ ]
409
+ };
410
+ }
411
+ let bundleValid = false;
412
+ let validation;
413
+ let bundleSize = 0;
414
+ try {
415
+ const bundled = await bundleWorkflowCode(proposedCode);
416
+ bundleValid = true;
417
+ bundleSize = Buffer.byteLength(bundled.handlerCode, "utf8");
418
+ validation = {
419
+ name: bundled.name,
420
+ triggerType: bundled.trigger.type
421
+ };
422
+ } catch (err) {
423
+ validation = {
424
+ error: err instanceof Error ? err.message : String(err)
425
+ };
426
+ }
427
+ const diffText = createPatch(`${name}.ts`, current.sourceCode, proposedCode, `v${current.version}`, "proposed");
428
+ return {
429
+ content: [
430
+ {
431
+ type: "text",
432
+ text: JSON.stringify({
433
+ currentVersion: current.version,
434
+ expectedVersion,
435
+ currentSource: current.sourceCode,
436
+ proposedSource: proposedCode,
437
+ diffText,
438
+ bundleValid,
439
+ validation,
440
+ bundleSize
441
+ }, null, 2)
442
+ }
443
+ ]
444
+ };
445
+ });
446
+ defineTool(server, "workflows_tail_run", "Tail a workflow run via SSE and return a compacted log. Resolves as soon as the run completes, `limit` events are collected, or `timeoutMs` elapses (default 60s). MCP is not streaming-first — use this for short-lived follow-ups, not long tails.", {
447
+ name: z3.string().describe("Workflow name"),
448
+ runId: z3.string().describe("Run id"),
449
+ limit: z3.number().int().positive().max(200).optional().describe("Max step events to collect (default 50)"),
450
+ timeoutMs: z3.number().int().positive().max(5 * 60 * 1000).optional().describe("Hard timeout in ms (default 60000, max 300000)")
451
+ }, async ({ name, runId, limit, timeoutMs }) => {
452
+ const cap = limit ?? 50;
453
+ const deadline = timeoutMs ?? 60000;
454
+ const events = [];
455
+ let finalStatus = null;
456
+ let stoppedBy = "timeout";
457
+ const controller = new AbortController;
458
+ const timer = setTimeout(() => {
459
+ stoppedBy = "timeout";
460
+ controller.abort();
461
+ }, deadline);
462
+ try {
463
+ await getClient().workflows.streamRun(name, runId, (event) => {
464
+ if (event.type === "step") {
465
+ events.push(event.step);
466
+ if (events.length >= cap) {
467
+ stoppedBy = "limit";
468
+ controller.abort();
469
+ }
470
+ } else if (event.type === "done") {
471
+ finalStatus = event.done.status;
472
+ stoppedBy = "done";
473
+ }
474
+ }, controller.signal);
475
+ } catch (err) {
476
+ if (!(err instanceof Error) || err.name !== "AbortError") {
477
+ throw err;
478
+ }
479
+ } finally {
480
+ clearTimeout(timer);
481
+ }
482
+ return {
483
+ content: [
484
+ {
485
+ type: "text",
486
+ text: JSON.stringify({
487
+ runId,
488
+ finalStatus,
489
+ stoppedBy,
490
+ eventCount: events.length,
491
+ events
492
+ }, null, 2)
493
+ }
494
+ ]
495
+ };
496
+ });
497
+ defineTool(server, "workflows_pause_all", "Pause ALL active workflows for the authenticated account. Irreversible only by calling pause/resume per workflow. Returns the list of affected workflows.", {}, async () => {
498
+ const result = await getClient().workflows.pauseAll();
499
+ return {
500
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
501
+ };
502
+ });
503
+ defineTool(server, "workflows_cancel_run", "Cancel an in-flight workflow run. Marks the run as cancelled and removes any pending queue entry. No-ops if the run is already terminal.", { runId: z3.string().describe("Run id to cancel") }, async ({ runId }) => {
504
+ const result = await getClient().workflows.cancelRun(runId);
505
+ return {
506
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
507
+ };
508
+ });
509
+ defineTool(server, "workflows_rollback", "Roll a workflow back to a prior version. The restored handler is re-published as a NEW version (audit trail), so no history is lost. Pass toVersion to pick a specific bundle; omit to roll back to the immediate previous version on disk. Last 3 versions are retained.", {
510
+ name: z3.string().describe("Workflow name"),
511
+ toVersion: z3.string().regex(/^\d+\.\d+\.\d+$/).optional().describe("Target version to restore. Must be one of the retained bundles on disk.")
512
+ }, async ({ name, toVersion }) => {
513
+ const result = await getClient().workflows.rollback(name, toVersion);
514
+ return {
515
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
516
+ };
517
+ });
518
+ defineTool(server, "workflows_delete", "Delete a workflow permanently.", { name: z3.string().describe("Workflow name") }, async ({ name }) => {
519
+ await getClient().workflows.delete(name);
520
+ return {
521
+ content: [{ type: "text", text: `Deleted workflow "${name}"` }]
522
+ };
523
+ });
832
524
  defineTool(server, "workflows_trigger", "Trigger a workflow run. Optionally pass input as a JSON string.", {
833
- name: z4.string().describe("Workflow name"),
834
- input: z4.string().optional().describe("Input as JSON string")
525
+ name: z3.string().describe("Workflow name"),
526
+ input: z3.string().optional().describe("Input as JSON string")
835
527
  }, async ({ name, input }) => {
836
528
  const parsed = input ? JSON.parse(input) : undefined;
837
529
  const result = await getClient().workflows.trigger(name, parsed);
@@ -839,22 +531,138 @@ function registerWorkflowTools(server) {
839
531
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
840
532
  };
841
533
  });
842
- defineTool(server, "workflows_pause", "Pause a running workflow.", { name: z4.string().describe("Workflow name") }, async ({ name }) => {
534
+ defineTool(server, "workflows_pause", "Pause a running workflow.", { name: z3.string().describe("Workflow name") }, async ({ name }) => {
843
535
  await getClient().workflows.pause(name);
844
536
  return {
845
537
  content: [{ type: "text", text: `Paused workflow "${name}"` }]
846
538
  };
847
539
  });
848
- defineTool(server, "workflows_resume", "Resume a paused workflow.", { name: z4.string().describe("Workflow name") }, async ({ name }) => {
540
+ defineTool(server, "workflows_resume", "Resume a paused workflow.", { name: z3.string().describe("Workflow name") }, async ({ name }) => {
849
541
  await getClient().workflows.resume(name);
850
542
  return {
851
543
  content: [{ type: "text", text: `Resumed workflow "${name}"` }]
852
544
  };
853
545
  });
546
+ defineTool(server, "workflows_template_list", "List available workflow templates. Returns metadata only — use workflows_template_get for the full source.", {}, async () => {
547
+ return {
548
+ content: [
549
+ {
550
+ type: "text",
551
+ text: JSON.stringify(workflowTemplates2.map((t) => ({
552
+ id: t.id,
553
+ name: t.name,
554
+ description: t.description,
555
+ category: t.category,
556
+ trigger: t.trigger
557
+ })), null, 2)
558
+ }
559
+ ]
560
+ };
561
+ });
562
+ defineTool(server, "workflows_template_get", "Get a workflow template's full TypeScript source and prompt by id.", { id: z3.string().describe("Template id, e.g. 'whale-alert'") }, async ({ id }) => {
563
+ const template = getWorkflowTemplateById(id);
564
+ if (!template) {
565
+ return {
566
+ isError: true,
567
+ content: [
568
+ {
569
+ type: "text",
570
+ text: `Template "${id}" not found. Use workflows_template_list to see available templates.`
571
+ }
572
+ ]
573
+ };
574
+ }
575
+ return {
576
+ content: [
577
+ {
578
+ type: "text",
579
+ text: JSON.stringify(template, null, 2)
580
+ }
581
+ ]
582
+ };
583
+ });
584
+ defineTool(server, "workflows_scaffold", "Generate a compilable defineWorkflow() skeleton from a typed intent. Returns the TypeScript source; pass it to workflows_deploy to persist. Placeholders inside the source must be filled in before running a real workflow.", {
585
+ name: z3.string().regex(/^[a-z][a-z0-9-]*$/).describe("Workflow name (lowercase, hyphens)"),
586
+ trigger: z3.discriminatedUnion("type", [
587
+ z3.object({
588
+ type: z3.literal("event"),
589
+ filterType: z3.string().optional()
590
+ }),
591
+ z3.object({
592
+ type: z3.literal("schedule"),
593
+ cron: z3.string().min(1),
594
+ timezone: z3.string().optional()
595
+ }),
596
+ z3.object({ type: z3.literal("manual") })
597
+ ]).describe("Trigger shape"),
598
+ steps: z3.array(z3.enum(["run", "query", "ai", "deliver"])).describe("Ordered list of step kinds to include in the handler"),
599
+ deliveryTarget: z3.enum(["webhook", "slack", "email", "discord", "telegram"]).optional().describe("Delivery target used when steps includes `deliver`")
600
+ }, async ({ name, trigger, steps, deliveryTarget }) => {
601
+ const code = generateWorkflowCode({
602
+ name,
603
+ trigger,
604
+ steps,
605
+ deliveryTarget
606
+ });
607
+ return { content: [{ type: "text", text: code }] };
608
+ });
609
+ defineTool(server, "workflows_deploy", "Deploy a workflow from TypeScript source. Pass the full defineWorkflow() source — it will be bundled, validated, and deployed. Use expectedVersion for optimistic concurrency, or dryRun to validate without persisting.", {
610
+ code: z3.string().describe("TypeScript source code containing a defineWorkflow() call"),
611
+ expectedVersion: z3.string().regex(/^\d+\.\d+\.\d+$/).optional().describe("Stored version the client expects (major.minor.patch). Server returns 409 on mismatch."),
612
+ dryRun: z3.boolean().optional().describe("If true, validate and bundle only — do not persist.")
613
+ }, async ({ code, expectedVersion, dryRun }) => {
614
+ let bundled;
615
+ try {
616
+ bundled = await bundleWorkflowCode(code);
617
+ } catch (err) {
618
+ return {
619
+ isError: true,
620
+ content: [
621
+ {
622
+ type: "text",
623
+ text: err instanceof Error ? err.message : String(err)
624
+ }
625
+ ]
626
+ };
627
+ }
628
+ const base = {
629
+ name: bundled.name,
630
+ trigger: bundled.trigger,
631
+ handlerCode: bundled.handlerCode,
632
+ sourceCode: bundled.sourceCode,
633
+ retries: bundled.retries,
634
+ timeout: bundled.timeout,
635
+ expectedVersion
636
+ };
637
+ try {
638
+ const result = dryRun ? await getClient().workflows.deploy({ ...base, dryRun: true }) : await getClient().workflows.deploy(base);
639
+ return {
640
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
641
+ };
642
+ } catch (err) {
643
+ if (err instanceof VersionConflictError) {
644
+ return {
645
+ isError: true,
646
+ content: [
647
+ {
648
+ type: "text",
649
+ text: JSON.stringify({
650
+ error: err.message,
651
+ code: "VERSION_CONFLICT",
652
+ currentVersion: err.currentVersion,
653
+ expectedVersion: err.expectedVersion
654
+ }, null, 2)
655
+ }
656
+ ]
657
+ };
658
+ }
659
+ throw err;
660
+ }
661
+ });
854
662
  defineTool(server, "workflows_runs", "List runs for a workflow. Optionally filter by status and limit results.", {
855
- name: z4.string().describe("Workflow name"),
856
- status: z4.enum(["running", "completed", "failed", "cancelled"]).optional().describe("Filter by run status"),
857
- limit: z4.number().optional().describe("Max runs to return (default 20)")
663
+ name: z3.string().describe("Workflow name"),
664
+ status: z3.enum(["running", "completed", "failed", "cancelled"]).optional().describe("Filter by run status"),
665
+ limit: z3.number().optional().describe("Max runs to return (default 20)")
858
666
  }, async ({ name, status, limit }) => {
859
667
  const { runs } = await getClient().workflows.listRuns(name, {
860
668
  status,
@@ -875,14 +683,14 @@ function registerWorkflowTools(server) {
875
683
  import {
876
684
  getTemplateById,
877
685
  getTemplatesByCategory,
878
- templates as templates2
686
+ templates
879
687
  } from "@secondlayer/subgraphs/templates";
880
- import { z as z5 } from "zod/v4";
688
+ import { z as z4 } from "zod/v4";
881
689
  function registerTemplateTools(server) {
882
690
  defineTool(server, "templates_list", "List available subgraph templates. Returns metadata only — use templates_get for full code.", {
883
- category: z5.enum(["defi", "nft", "token", "infrastructure"]).optional().describe("Filter by category")
691
+ category: z4.enum(["defi", "nft", "token", "infrastructure"]).optional().describe("Filter by category")
884
692
  }, async ({ category }) => {
885
- const list = category ? getTemplatesByCategory(category) : templates2;
693
+ const list = category ? getTemplatesByCategory(category) : templates;
886
694
  return {
887
695
  content: [
888
696
  {
@@ -897,7 +705,7 @@ function registerTemplateTools(server) {
897
705
  ]
898
706
  };
899
707
  });
900
- defineTool(server, "templates_get", "Get a template's full code and prompt by ID.", { id: z5.string().describe("Template ID (e.g. 'dex-swaps')") }, async ({ id }) => {
708
+ defineTool(server, "templates_get", "Get a template's full code and prompt by ID.", { id: z4.string().describe("Template ID (e.g. 'dex-swaps')") }, async ({ id }) => {
901
709
  const template = getTemplateById(id);
902
710
  if (!template) {
903
711
  return {
@@ -938,7 +746,6 @@ function createServer() {
938
746
  });
939
747
  registerTemplateTools(server);
940
748
  registerScaffoldTools(server);
941
- registerStreamTools(server);
942
749
  registerSubgraphTools(server);
943
750
  registerAccountTools(server);
944
751
  registerWorkflowTools(server);
@@ -1032,5 +839,5 @@ httpServer.listen(port, () => {
1032
839
  console.error("Warning: SECONDLAYER_MCP_SECRET not set, authentication disabled");
1033
840
  });
1034
841
 
1035
- //# debugId=080385D15A6EE15164756E2164756E21
842
+ //# debugId=1386FA48A488D24664756E2164756E21
1036
843
  //# sourceMappingURL=bin-http.js.map