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