@secondlayer/mcp 0.3.2 → 0.3.3

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
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/bin-http.ts
4
- import { createServer as createHttpServer } from "node:http";
4
+ import {
5
+ createServer as createHttpServer
6
+ } from "node:http";
5
7
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
6
8
 
7
9
  // src/server.ts
@@ -10,9 +12,85 @@ import { dirname, join } from "node:path";
10
12
  import { fileURLToPath } from "node:url";
11
13
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
14
 
13
- // src/tools/templates.ts
15
+ // src/resources.ts
16
+ import { templates } from "@secondlayer/subgraphs/templates";
17
+
18
+ // src/tools/streams.ts
14
19
  import { z } from "zod/v4";
15
- import { templates, getTemplateById, getTemplatesByCategory } from "@secondlayer/subgraphs/templates";
20
+
21
+ // src/lib/client.ts
22
+ import { SecondLayer } from "@secondlayer/sdk";
23
+ var instance = null;
24
+ function getClient() {
25
+ if (!instance) {
26
+ const apiKey = process.env.SECONDLAYER_API_KEY;
27
+ if (!apiKey) {
28
+ throw new Error("SECONDLAYER_API_KEY environment variable is required. " + "Get your key at https://app.secondlayer.tools/settings/api-keys");
29
+ }
30
+ instance = new SecondLayer({ apiKey });
31
+ }
32
+ return instance;
33
+ }
34
+ async function apiRequest(method, path, body) {
35
+ const apiKey = process.env.SECONDLAYER_API_KEY;
36
+ if (!apiKey)
37
+ throw new Error("SECONDLAYER_API_KEY required");
38
+ const baseUrl = process.env.SECONDLAYER_API_URL || "https://api.secondlayer.tools";
39
+ const res = await fetch(`${baseUrl}${path}`, {
40
+ method,
41
+ headers: {
42
+ "Content-Type": "application/json",
43
+ Authorization: `Bearer ${apiKey}`
44
+ },
45
+ body: body ? JSON.stringify(body) : undefined
46
+ });
47
+ if (!res.ok) {
48
+ const text = await res.text().catch(() => "");
49
+ throw Object.assign(new Error(text || `HTTP ${res.status}`), {
50
+ status: res.status
51
+ });
52
+ }
53
+ if (res.status === 204)
54
+ return;
55
+ return res.json();
56
+ }
57
+
58
+ // 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
+ function formatSubgraphSummary(s) {
70
+ return {
71
+ name: s.name,
72
+ status: s.status,
73
+ tables: Array.isArray(s.tables) ? s.tables : Object.keys(s.tables),
74
+ lastProcessedBlock: s.lastProcessedBlock
75
+ };
76
+ }
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
+ function withCap(items, cap) {
88
+ return {
89
+ items: items.slice(0, cap),
90
+ truncated: items.length > cap,
91
+ total: items.length
92
+ };
93
+ }
16
94
 
17
95
  // src/lib/tool.ts
18
96
  function defineTool(server, name, description, schema, handler) {
@@ -24,7 +102,12 @@ function defineTool(server, name, description, schema, handler) {
24
102
  const status = err instanceof Error && "status" in err ? err.status : 0;
25
103
  const type = status === 401 ? "unauthorized" : status === 404 ? "not_found" : status === 429 ? "rate_limited" : status >= 500 ? "server_error" : "error";
26
104
  return {
27
- content: [{ type: "text", text: JSON.stringify({ error: { type, status, message } }) }],
105
+ content: [
106
+ {
107
+ type: "text",
108
+ text: JSON.stringify({ error: { type, status, message } })
109
+ }
110
+ ],
28
111
  isError: true
29
112
  };
30
113
  }
@@ -32,32 +115,330 @@ function defineTool(server, name, description, schema, handler) {
32
115
  server.tool(name, description, schema, wrappedHandler);
33
116
  }
34
117
 
35
- // src/tools/templates.ts
36
- function registerTemplateTools(server) {
37
- defineTool(server, "templates_list", "List available subgraph templates. Returns metadata only — use templates_get for full code.", { category: z.enum(["defi", "nft", "token", "infrastructure"]).optional().describe("Filter by category") }, async ({ category }) => {
38
- const list = category ? getTemplatesByCategory(category) : templates;
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);
39
203
  return {
40
- content: [{
41
- type: "text",
42
- text: JSON.stringify(list.map((t) => ({
43
- id: t.id,
44
- name: t.name,
45
- description: t.description,
46
- category: t.category
47
- })), null, 2)
48
- }]
204
+ content: [
205
+ {
206
+ type: "text",
207
+ text: JSON.stringify(streams.map(formatStreamSummary), null, 2)
208
+ }
209
+ ]
49
210
  };
50
211
  });
51
- defineTool(server, "templates_get", "Get a template's full code and prompt by ID.", { id: z.string().describe("Template ID (e.g. 'dex-swaps')") }, async ({ id }) => {
52
- const template = getTemplateById(id);
53
- if (!template) {
54
- return { content: [{ type: "text", text: `Template "${id}" not found. Use templates_list to see available templates.` }], isError: true };
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
+ };
55
338
  }
339
+ const result = await getClient().streams.resumeAll();
56
340
  return {
57
- content: [{
58
- type: "text",
59
- text: JSON.stringify({ id: template.id, name: template.name, description: template.description, category: template.category, code: template.code, prompt: template.prompt }, null, 2)
60
- }]
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
+ // src/tools/account.ts
437
+ function registerAccountTools(server) {
438
+ defineTool(server, "account_whoami", "Show the authenticated account's email and plan.", {}, async () => {
439
+ const result = await apiRequest("GET", "/api/accounts/me");
440
+ return {
441
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
61
442
  };
62
443
  });
63
444
  }
@@ -276,221 +657,23 @@ function registerScaffoldTools(server) {
276
657
  try {
277
658
  parsed = JSON.parse(abi);
278
659
  } catch {
279
- return { content: [{ type: "text", text: "Invalid ABI JSON" }], isError: true };
280
- }
281
- const code = generateSubgraphCode(contractId, parsed.functions ?? [], subgraphName, parsed.maps ?? []);
282
- return { content: [{ type: "text", text: code }] };
283
- });
284
- }
285
-
286
- // src/tools/streams.ts
287
- import { z as z3 } from "zod/v4";
288
-
289
- // src/lib/client.ts
290
- import { SecondLayer } from "@secondlayer/sdk";
291
- var instance = null;
292
- function getClient() {
293
- if (!instance) {
294
- const apiKey = process.env.SECONDLAYER_API_KEY;
295
- if (!apiKey) {
296
- throw new Error("SECONDLAYER_API_KEY environment variable is required. " + "Get your key at https://app.secondlayer.tools/settings/api-keys");
297
- }
298
- instance = new SecondLayer({ apiKey });
299
- }
300
- return instance;
301
- }
302
- async function apiRequest(method, path, body) {
303
- const apiKey = process.env.SECONDLAYER_API_KEY;
304
- if (!apiKey)
305
- throw new Error("SECONDLAYER_API_KEY required");
306
- const baseUrl = process.env.SECONDLAYER_API_URL || "https://api.secondlayer.tools";
307
- const res = await fetch(`${baseUrl}${path}`, {
308
- method,
309
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
310
- body: body ? JSON.stringify(body) : undefined
311
- });
312
- if (!res.ok) {
313
- const text = await res.text().catch(() => "");
314
- throw Object.assign(new Error(text || `HTTP ${res.status}`), { status: res.status });
315
- }
316
- if (res.status === 204)
317
- return;
318
- return res.json();
319
- }
320
-
321
- // src/lib/format.ts
322
- function formatStreamSummary(s) {
323
- return {
324
- id: s.id,
325
- name: s.name,
326
- status: s.status,
327
- endpointUrl: s.endpointUrl,
328
- totalDeliveries: s.totalDeliveries,
329
- failedDeliveries: s.failedDeliveries
330
- };
331
- }
332
- function formatSubgraphSummary(s) {
333
- return {
334
- name: s.name,
335
- status: s.status,
336
- tables: Array.isArray(s.tables) ? s.tables : Object.keys(s.tables),
337
- lastProcessedBlock: s.lastProcessedBlock
338
- };
339
- }
340
- function formatDeliverySummary(d) {
341
- return {
342
- id: d.id,
343
- blockHeight: d.blockHeight,
344
- status: d.status,
345
- statusCode: d.statusCode,
346
- attempts: d.attempts,
347
- createdAt: d.createdAt
348
- };
349
- }
350
- function withCap(items, cap) {
351
- return {
352
- items: items.slice(0, cap),
353
- truncated: items.length > cap,
354
- total: items.length
355
- };
356
- }
357
-
358
- // src/tools/streams.ts
359
- var FilterSchema = z3.discriminatedUnion("type", [
360
- z3.object({ type: z3.literal("stx_transfer"), sender: z3.string().optional(), recipient: z3.string().optional(), minAmount: z3.number().optional(), maxAmount: z3.number().optional() }),
361
- z3.object({ type: z3.literal("stx_mint"), recipient: z3.string().optional(), minAmount: z3.number().optional() }),
362
- z3.object({ type: z3.literal("stx_burn"), sender: z3.string().optional(), minAmount: z3.number().optional() }),
363
- z3.object({ type: z3.literal("stx_lock"), lockedAddress: z3.string().optional(), minAmount: z3.number().optional() }),
364
- z3.object({ type: z3.literal("ft_transfer"), sender: z3.string().optional(), recipient: z3.string().optional(), assetIdentifier: z3.string().optional(), minAmount: z3.number().optional() }),
365
- z3.object({ type: z3.literal("ft_mint"), recipient: z3.string().optional(), assetIdentifier: z3.string().optional(), minAmount: z3.number().optional() }),
366
- z3.object({ type: z3.literal("ft_burn"), sender: z3.string().optional(), assetIdentifier: z3.string().optional(), minAmount: z3.number().optional() }),
367
- z3.object({ type: z3.literal("nft_transfer"), sender: z3.string().optional(), recipient: z3.string().optional(), assetIdentifier: z3.string().optional(), tokenId: z3.string().optional() }),
368
- z3.object({ type: z3.literal("nft_mint"), recipient: z3.string().optional(), assetIdentifier: z3.string().optional(), tokenId: z3.string().optional() }),
369
- z3.object({ type: z3.literal("nft_burn"), sender: z3.string().optional(), assetIdentifier: z3.string().optional(), tokenId: z3.string().optional() }),
370
- z3.object({ type: z3.literal("contract_call"), contractId: z3.string().optional(), functionName: z3.string().optional(), caller: z3.string().optional() }),
371
- z3.object({ type: z3.literal("contract_deploy"), deployer: z3.string().optional(), contractName: z3.string().optional() }),
372
- z3.object({ type: z3.literal("print_event"), contractId: z3.string().optional(), topic: z3.string().optional(), contains: z3.string().optional() })
373
- ]);
374
- function registerStreamTools(server) {
375
- defineTool(server, "streams_list", "List all webhook streams. Returns summary fields only.", { status: z3.enum(["active", "inactive", "paused", "failed"]).optional().describe("Filter by status") }, async ({ status }) => {
376
- const { streams } = await getClient().streams.list(status ? { status } : undefined);
377
- return {
378
- content: [{ type: "text", text: JSON.stringify(streams.map(formatStreamSummary), null, 2) }]
379
- };
380
- });
381
- defineTool(server, "streams_get", "Get full details of a stream by ID (accepts UUID prefix).", { id: z3.string().describe("Stream UUID or prefix") }, async ({ id }) => {
382
- const stream = await getClient().streams.get(id);
383
- return { content: [{ type: "text", text: JSON.stringify(stream, null, 2) }] };
384
- });
385
- defineTool(server, "streams_create", "Create a new webhook stream with filters.", {
386
- name: z3.string().describe("Stream name"),
387
- endpointUrl: z3.string().describe("Webhook endpoint URL"),
388
- filters: z3.array(FilterSchema).min(1).describe("Event filters (at least one required)")
389
- }, async ({ name, endpointUrl, filters }) => {
390
- const result = await getClient().streams.create({ name, endpointUrl, filters });
391
- return {
392
- content: [{ type: "text", text: JSON.stringify({ id: result.stream.id, signingSecret: result.signingSecret }, null, 2) }]
393
- };
394
- });
395
- defineTool(server, "streams_update", "Update a stream's name, endpoint, or filters.", {
396
- id: z3.string().describe("Stream UUID or prefix"),
397
- name: z3.string().optional().describe("New name"),
398
- endpointUrl: z3.string().optional().describe("New endpoint URL"),
399
- filters: z3.array(FilterSchema).min(1).optional().describe("New filters")
400
- }, async ({ id, name, endpointUrl, filters }) => {
401
- const data = {};
402
- if (name !== undefined)
403
- data.name = name;
404
- if (endpointUrl !== undefined)
405
- data.endpointUrl = endpointUrl;
406
- if (filters !== undefined)
407
- data.filters = filters;
408
- const stream = await getClient().streams.update(id, data);
409
- return { content: [{ type: "text", text: JSON.stringify(formatStreamSummary(stream), null, 2) }] };
410
- });
411
- defineTool(server, "streams_delete", "Delete a stream permanently.", { id: z3.string().describe("Stream UUID or prefix") }, async ({ id }) => {
412
- await getClient().streams.delete(id);
413
- return { content: [{ type: "text", text: `Stream ${id} deleted.` }] };
414
- });
415
- defineTool(server, "streams_toggle", "Enable or disable a stream.", {
416
- id: z3.string().describe("Stream UUID or prefix"),
417
- enabled: z3.boolean().describe("true to enable, false to disable")
418
- }, async ({ id, enabled }) => {
419
- const stream = enabled ? await getClient().streams.enable(id) : await getClient().streams.disable(id);
420
- return { content: [{ type: "text", text: JSON.stringify({ id: stream.id, status: stream.status }, null, 2) }] };
421
- });
422
- defineTool(server, "streams_deliveries", "List recent deliveries for a stream (max 25).", {
423
- id: z3.string().describe("Stream UUID or prefix"),
424
- limit: z3.number().max(25).optional().describe("Max results (default 25)"),
425
- status: z3.string().optional().describe("Filter by delivery status")
426
- }, async ({ id, limit, status }) => {
427
- const { deliveries } = await getClient().streams.listDeliveries(id, { limit: limit ?? 25, status });
428
- const result = withCap(deliveries.map(formatDeliverySummary), 25);
429
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
430
- });
431
- defineTool(server, "streams_pause_all", "Pause all active streams. Without confirm: true, returns a preview of streams that would be paused.", {
432
- confirm: z3.boolean().optional().describe("Set to true to execute. Omit or false for preview only.")
433
- }, async ({ confirm }) => {
434
- if (!confirm) {
435
- const { streams } = await getClient().streams.list({ status: "active" });
436
- const names = streams.map((s) => s.name);
437
- return {
438
- content: [{
439
- type: "text",
440
- text: JSON.stringify({ preview: true, count: names.length, streams: names }, null, 2)
441
- }]
442
- };
443
- }
444
- const result = await getClient().streams.pauseAll();
445
- return {
446
- content: [{
447
- type: "text",
448
- text: JSON.stringify({ paused: result.paused, streams: result.streams.map(formatStreamSummary) }, null, 2)
449
- }]
450
- };
451
- });
452
- defineTool(server, "streams_resume_all", "Resume all paused streams. Without confirm: true, returns a preview of streams that would be resumed.", {
453
- confirm: z3.boolean().optional().describe("Set to true to execute. Omit or false for preview only.")
454
- }, async ({ confirm }) => {
455
- if (!confirm) {
456
- const { streams } = await getClient().streams.list({ status: "paused" });
457
- const names = streams.map((s) => s.name);
458
660
  return {
459
- content: [{
460
- type: "text",
461
- text: JSON.stringify({ preview: true, count: names.length, streams: names }, null, 2)
462
- }]
661
+ content: [{ type: "text", text: "Invalid ABI JSON" }],
662
+ isError: true
463
663
  };
464
664
  }
465
- const result = await getClient().streams.resumeAll();
466
- return {
467
- content: [{
468
- type: "text",
469
- text: JSON.stringify({ resumed: result.resumed, streams: result.streams.map(formatStreamSummary) }, null, 2)
470
- }]
471
- };
472
- });
473
- defineTool(server, "streams_replay", "Replay blocks through a stream, re-delivering events for a block range.", {
474
- id: z3.string().describe("Stream UUID or prefix"),
475
- fromBlock: z3.number().describe("Start block height (inclusive)"),
476
- toBlock: z3.number().describe("End block height (inclusive)")
477
- }, async ({ id, fromBlock, toBlock }) => {
478
- const result = await apiRequest("POST", `/api/streams/${id}/replay`, { fromBlock, toBlock });
479
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
480
- });
481
- defineTool(server, "streams_rotate_secret", "Rotate the signing secret for a stream. Returns the new secret.", { id: z3.string().describe("Stream UUID or prefix") }, async ({ id }) => {
482
- const result = await getClient().streams.rotateSecret(id);
483
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
665
+ const code = generateSubgraphCode(contractId, parsed.functions ?? [], subgraphName, parsed.maps ?? []);
666
+ return { content: [{ type: "text", text: code }] };
484
667
  });
485
668
  }
486
669
 
487
670
  // src/tools/subgraphs.ts
488
- import { z as z4 } from "zod/v4";
671
+ import { z as z3 } from "zod/v4";
489
672
 
490
673
  // src/lib/bundle.ts
491
- import esbuild from "esbuild";
492
- import { validateSubgraphDefinition } from "@secondlayer/subgraphs/validate";
493
674
  import { sourceKey } from "@secondlayer/subgraphs";
675
+ import { validateSubgraphDefinition } from "@secondlayer/subgraphs/validate";
676
+ import esbuild from "esbuild";
494
677
  async function bundleSubgraphCode(code) {
495
678
  let result;
496
679
  try {
@@ -535,27 +718,46 @@ function registerSubgraphTools(server) {
535
718
  defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only.", {}, async () => {
536
719
  const { data } = await getClient().subgraphs.list();
537
720
  return {
538
- content: [{ type: "text", text: JSON.stringify(data.map(formatSubgraphSummary), null, 2) }]
721
+ content: [
722
+ {
723
+ type: "text",
724
+ text: JSON.stringify(data.map(formatSubgraphSummary), null, 2)
725
+ }
726
+ ]
539
727
  };
540
728
  });
541
- defineTool(server, "subgraphs_get", "Get full details of a subgraph including schema, health, and table columns.", { name: z4.string().describe("Subgraph name") }, async ({ name }) => {
729
+ defineTool(server, "subgraphs_get", "Get full details of a subgraph including schema, health, and table columns.", { name: z3.string().describe("Subgraph name") }, async ({ name }) => {
542
730
  const detail = await getClient().subgraphs.get(name);
543
- return { content: [{ type: "text", text: JSON.stringify(detail, null, 2) }] };
731
+ return {
732
+ content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
733
+ };
544
734
  });
545
735
  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.', {
546
- name: z4.string().describe("Subgraph name"),
547
- table: z4.string().describe("Table name"),
548
- filters: z4.record(z4.string(), z4.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
549
- sort: z4.string().optional().describe("Column to sort by"),
550
- order: z4.enum(["asc", "desc"]).optional().describe("Sort order"),
551
- limit: z4.number().max(200).optional().describe("Max rows (default 50, max 200)"),
552
- offset: z4.number().optional().describe("Offset for pagination"),
553
- fields: z4.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
554
- count: z4.boolean().optional().describe("If true, return row count instead of rows")
555
- }, async ({ name, table, filters, sort, order, limit, offset, fields, count }) => {
736
+ name: z3.string().describe("Subgraph name"),
737
+ table: z3.string().describe("Table name"),
738
+ filters: z3.record(z3.string(), z3.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
739
+ sort: z3.string().optional().describe("Column to sort by"),
740
+ order: z3.enum(["asc", "desc"]).optional().describe("Sort order"),
741
+ limit: z3.number().max(200).optional().describe("Max rows (default 50, max 200)"),
742
+ offset: z3.number().optional().describe("Offset for pagination"),
743
+ fields: z3.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
744
+ count: z3.boolean().optional().describe("If true, return row count instead of rows")
745
+ }, async ({
746
+ name,
747
+ table,
748
+ filters,
749
+ sort,
750
+ order,
751
+ limit,
752
+ offset,
753
+ fields,
754
+ count
755
+ }) => {
556
756
  if (count) {
557
757
  const result2 = await getClient().subgraphs.queryTableCount(name, table, { filters, sort, order });
558
- return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
758
+ return {
759
+ content: [{ type: "text", text: JSON.stringify(result2, null, 2) }]
760
+ };
559
761
  }
560
762
  const rows = await getClient().subgraphs.queryTable(name, table, {
561
763
  filters,
@@ -567,23 +769,30 @@ function registerSubgraphTools(server) {
567
769
  });
568
770
  const cap = limit ?? 50;
569
771
  const result = withCap(rows, cap > 200 ? 200 : cap);
570
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
772
+ return {
773
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
774
+ };
571
775
  });
572
776
  defineTool(server, "subgraphs_reindex", "Reindex a subgraph from a specific block range.", {
573
- name: z4.string().describe("Subgraph name"),
574
- fromBlock: z4.number().optional().describe("Start block (defaults to beginning)"),
575
- toBlock: z4.number().optional().describe("End block (defaults to latest)")
777
+ name: z3.string().describe("Subgraph name"),
778
+ fromBlock: z3.number().optional().describe("Start block (defaults to beginning)"),
779
+ toBlock: z3.number().optional().describe("End block (defaults to latest)")
576
780
  }, async ({ name, fromBlock, toBlock }) => {
577
- const result = await getClient().subgraphs.reindex(name, { fromBlock, toBlock });
578
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
781
+ const result = await getClient().subgraphs.reindex(name, {
782
+ fromBlock,
783
+ toBlock
784
+ });
785
+ return {
786
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
787
+ };
579
788
  });
580
- defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z4.string().describe("Subgraph name") }, async ({ name }) => {
789
+ defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z3.string().describe("Subgraph name") }, async ({ name }) => {
581
790
  const result = await getClient().subgraphs.delete(name);
582
791
  return { content: [{ type: "text", text: result.message }] };
583
792
  });
584
793
  defineTool(server, "subgraphs_deploy", "Deploy a subgraph from TypeScript code. Pass the full defineSubgraph() source — it will be bundled, validated, and deployed.", {
585
- code: z4.string().describe("TypeScript source code containing a defineSubgraph() call"),
586
- reindex: z4.boolean().optional().describe("Force reindex on breaking schema change (drops and rebuilds all data)")
794
+ code: z3.string().describe("TypeScript source code containing a defineSubgraph() call"),
795
+ reindex: z3.boolean().optional().describe("Force reindex on breaking schema change (drops and rebuilds all data)")
587
796
  }, async ({ code, reindex }) => {
588
797
  const bundled = await bundleSubgraphCode(code);
589
798
  const result = await getClient().subgraphs.deploy({
@@ -601,54 +810,61 @@ function registerSubgraphTools(server) {
601
810
  });
602
811
  }
603
812
 
604
- // src/tools/account.ts
605
- function registerAccountTools(server) {
606
- defineTool(server, "account_whoami", "Show the authenticated account's email and plan.", {}, async () => {
607
- const result = await apiRequest("GET", "/api/accounts/me");
608
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
813
+ // src/tools/templates.ts
814
+ import {
815
+ getTemplateById,
816
+ getTemplatesByCategory,
817
+ templates as templates2
818
+ } from "@secondlayer/subgraphs/templates";
819
+ import { z as z4 } from "zod/v4";
820
+ function registerTemplateTools(server) {
821
+ defineTool(server, "templates_list", "List available subgraph templates. Returns metadata only — use templates_get for full code.", {
822
+ category: z4.enum(["defi", "nft", "token", "infrastructure"]).optional().describe("Filter by category")
823
+ }, async ({ category }) => {
824
+ const list = category ? getTemplatesByCategory(category) : templates2;
825
+ return {
826
+ content: [
827
+ {
828
+ type: "text",
829
+ text: JSON.stringify(list.map((t) => ({
830
+ id: t.id,
831
+ name: t.name,
832
+ description: t.description,
833
+ category: t.category
834
+ })), null, 2)
835
+ }
836
+ ]
837
+ };
838
+ });
839
+ 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 }) => {
840
+ const template = getTemplateById(id);
841
+ if (!template) {
842
+ return {
843
+ content: [
844
+ {
845
+ type: "text",
846
+ text: `Template "${id}" not found. Use templates_list to see available templates.`
847
+ }
848
+ ],
849
+ isError: true
850
+ };
851
+ }
852
+ return {
853
+ content: [
854
+ {
855
+ type: "text",
856
+ text: JSON.stringify({
857
+ id: template.id,
858
+ name: template.name,
859
+ description: template.description,
860
+ category: template.category,
861
+ code: template.code,
862
+ prompt: template.prompt
863
+ }, null, 2)
864
+ }
865
+ ]
866
+ };
609
867
  });
610
- }
611
-
612
- // src/resources.ts
613
- import { templates as templates2 } from "@secondlayer/subgraphs/templates";
614
- var FILTERS_REFERENCE = FilterSchema.options.map((opt) => ({
615
- type: opt.shape.type.def.values[0],
616
- fields: Object.keys(opt.shape).filter((k) => k !== "type")
617
- }));
618
- var COLUMN_TYPES = [
619
- { type: "uint", sqlType: "bigint", description: "Unsigned integer (Clarity uint)" },
620
- { type: "int", sqlType: "bigint", description: "Signed integer (Clarity int)" },
621
- { type: "text", sqlType: "text", description: "UTF-8 string" },
622
- { type: "principal", sqlType: "text", description: "Stacks address (standard or contract)" },
623
- { type: "bool", sqlType: "boolean", description: "Boolean value" },
624
- { type: "json", sqlType: "jsonb", description: "Arbitrary JSON data" },
625
- {
626
- options: ["nullable", "indexed", "search"],
627
- description: "Column options: nullable allows NULL, indexed creates a B-tree index, search enables full-text search"
628
- }
629
- ];
630
- function registerResources(server) {
631
- server.resource("filters", "secondlayer://filters", { description: "Stream filter types and their available fields" }, async () => ({
632
- contents: [{
633
- uri: "secondlayer://filters",
634
- mimeType: "application/json",
635
- text: JSON.stringify(FILTERS_REFERENCE, null, 2)
636
- }]
637
- }));
638
- server.resource("column-types", "secondlayer://column-types", { description: "Subgraph column types, SQL mappings, and options" }, async () => ({
639
- contents: [{
640
- uri: "secondlayer://column-types",
641
- mimeType: "application/json",
642
- text: JSON.stringify(COLUMN_TYPES, null, 2)
643
- }]
644
- }));
645
- server.resource("templates", "secondlayer://templates", { description: "Available subgraph templates with descriptions and categories" }, async () => ({
646
- contents: [{
647
- uri: "secondlayer://templates",
648
- mimeType: "application/json",
649
- text: JSON.stringify(templates2.map(({ id, name, description, category }) => ({ id, name, description, category })), null, 2)
650
- }]
651
- }));
652
868
  }
653
869
 
654
870
  // src/server.ts
@@ -669,7 +885,7 @@ function createServer() {
669
885
  }
670
886
 
671
887
  // src/bin-http.ts
672
- var port = parseInt(process.env.SECONDLAYER_MCP_PORT || "3100");
888
+ var port = Number.parseInt(process.env.SECONDLAYER_MCP_PORT || "3100");
673
889
  var secret = process.env.SECONDLAYER_MCP_SECRET;
674
890
  var sessions = new Map;
675
891
  function authenticate(req) {
@@ -754,5 +970,5 @@ httpServer.listen(port, () => {
754
970
  console.error("Warning: SECONDLAYER_MCP_SECRET not set, authentication disabled");
755
971
  });
756
972
 
757
- //# debugId=CA12403A062D1D3964756E2164756E21
973
+ //# debugId=404AC9A659F64E0F64756E2164756E21
758
974
  //# sourceMappingURL=bin-http.js.map