@secondlayer/mcp 0.2.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.d.ts +0 -0
- package/dist/bin-http.js +619 -0
- package/dist/bin-http.js.map +20 -0
- package/dist/bin.d.ts +0 -0
- package/dist/bin.js +551 -0
- package/dist/bin.js.map +20 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +544 -0
- package/dist/index.js.map +19 -0
- package/package.json +40 -0
package/dist/bin.js
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/bin.ts
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
|
|
6
|
+
// src/server.ts
|
|
7
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
|
|
9
|
+
// src/tools/templates.ts
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { templates, getTemplateById, getTemplatesByCategory } from "@secondlayer/subgraphs/templates";
|
|
12
|
+
|
|
13
|
+
// src/lib/tool.ts
|
|
14
|
+
function defineTool(server, name, description, schema, handler) {
|
|
15
|
+
server.tool(name, description, schema, handler);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/tools/templates.ts
|
|
19
|
+
function registerTemplateTools(server) {
|
|
20
|
+
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 }) => {
|
|
21
|
+
const list = category ? getTemplatesByCategory(category) : templates;
|
|
22
|
+
return {
|
|
23
|
+
content: [{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: JSON.stringify(list.map((t) => ({
|
|
26
|
+
id: t.id,
|
|
27
|
+
name: t.name,
|
|
28
|
+
description: t.description,
|
|
29
|
+
category: t.category
|
|
30
|
+
})), null, 2)
|
|
31
|
+
}]
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
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 }) => {
|
|
35
|
+
const template = getTemplateById(id);
|
|
36
|
+
if (!template) {
|
|
37
|
+
return { content: [{ type: "text", text: `Template "${id}" not found. Use templates_list to see available templates.` }], isError: true };
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
content: [{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: JSON.stringify({ id: template.id, name: template.name, description: template.description, category: template.category, code: template.code, prompt: template.prompt }, null, 2)
|
|
43
|
+
}]
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/tools/scaffold.ts
|
|
49
|
+
import { z as z2 } from "zod";
|
|
50
|
+
|
|
51
|
+
// src/lib/scaffold-generate.ts
|
|
52
|
+
function isAbiBuffer(t) {
|
|
53
|
+
return typeof t === "object" && t !== null && "buff" in t;
|
|
54
|
+
}
|
|
55
|
+
function isAbiStringAscii(t) {
|
|
56
|
+
return typeof t === "object" && t !== null && "string-ascii" in t;
|
|
57
|
+
}
|
|
58
|
+
function isAbiStringUtf8(t) {
|
|
59
|
+
return typeof t === "object" && t !== null && "string-utf8" in t;
|
|
60
|
+
}
|
|
61
|
+
function isAbiOptional(t) {
|
|
62
|
+
return typeof t === "object" && t !== null && "optional" in t;
|
|
63
|
+
}
|
|
64
|
+
function isAbiTuple(t) {
|
|
65
|
+
return typeof t === "object" && t !== null && "tuple" in t;
|
|
66
|
+
}
|
|
67
|
+
function isAbiList(t) {
|
|
68
|
+
return typeof t === "object" && t !== null && "list" in t;
|
|
69
|
+
}
|
|
70
|
+
function isAbiResponse(t) {
|
|
71
|
+
return typeof t === "object" && t !== null && "response" in t;
|
|
72
|
+
}
|
|
73
|
+
function mapType(abiType, nullable) {
|
|
74
|
+
if (typeof abiType === "string") {
|
|
75
|
+
switch (abiType) {
|
|
76
|
+
case "uint128":
|
|
77
|
+
return { type: "uint", nullable };
|
|
78
|
+
case "int128":
|
|
79
|
+
return { type: "int", nullable };
|
|
80
|
+
case "principal":
|
|
81
|
+
case "trait_reference":
|
|
82
|
+
return { type: "principal", nullable };
|
|
83
|
+
case "bool":
|
|
84
|
+
return { type: "boolean", nullable };
|
|
85
|
+
default: {
|
|
86
|
+
const s = abiType;
|
|
87
|
+
if (s.includes("uint"))
|
|
88
|
+
return { type: "uint", nullable };
|
|
89
|
+
if (s.includes("int"))
|
|
90
|
+
return { type: "int", nullable };
|
|
91
|
+
if (s.includes("string") || s.includes("ascii") || s.includes("utf8")) {
|
|
92
|
+
return { type: "text", nullable };
|
|
93
|
+
}
|
|
94
|
+
if (s.includes("buff"))
|
|
95
|
+
return { type: "text", nullable };
|
|
96
|
+
return { type: "jsonb", nullable };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (isAbiBuffer(abiType))
|
|
101
|
+
return { type: "text", nullable };
|
|
102
|
+
if (isAbiStringAscii(abiType) || isAbiStringUtf8(abiType)) {
|
|
103
|
+
return { type: "text", nullable };
|
|
104
|
+
}
|
|
105
|
+
if (isAbiOptional(abiType))
|
|
106
|
+
return mapType(abiType.optional, true);
|
|
107
|
+
if (isAbiList(abiType) || isAbiTuple(abiType))
|
|
108
|
+
return { type: "jsonb", nullable };
|
|
109
|
+
if (isAbiResponse(abiType)) {
|
|
110
|
+
return mapType(abiType.response.ok, nullable);
|
|
111
|
+
}
|
|
112
|
+
return { type: "jsonb", nullable };
|
|
113
|
+
}
|
|
114
|
+
function clarityTypeToSubgraphColumn(abiType) {
|
|
115
|
+
return mapType(abiType, false);
|
|
116
|
+
}
|
|
117
|
+
function toSnake(name) {
|
|
118
|
+
return name.replace(/-/g, "_");
|
|
119
|
+
}
|
|
120
|
+
function toCamel(name) {
|
|
121
|
+
return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
122
|
+
}
|
|
123
|
+
function buildColumns(args) {
|
|
124
|
+
if (args.length === 0)
|
|
125
|
+
return " _placeholder: { type: 'text' }";
|
|
126
|
+
return args.map((arg) => {
|
|
127
|
+
const mapped = clarityTypeToSubgraphColumn(arg.type);
|
|
128
|
+
const nullable = mapped.nullable ? ", nullable: true" : "";
|
|
129
|
+
return ` ${toSnake(arg.name)}: { type: '${mapped.type}'${nullable} }`;
|
|
130
|
+
}).join(`,
|
|
131
|
+
`);
|
|
132
|
+
}
|
|
133
|
+
function buildInsertCall(tableName, args) {
|
|
134
|
+
if (args.length === 0) {
|
|
135
|
+
return ` ctx.insert('${tableName}', {
|
|
136
|
+
sender: ctx.tx.sender,
|
|
137
|
+
});`;
|
|
138
|
+
}
|
|
139
|
+
const mappings = args.map((arg) => {
|
|
140
|
+
return ` ${toSnake(arg.name)}: event.${toCamel(arg.name)}`;
|
|
141
|
+
});
|
|
142
|
+
return ` ctx.insert('${tableName}', {
|
|
143
|
+
${mappings.join(`,
|
|
144
|
+
`)},
|
|
145
|
+
});`;
|
|
146
|
+
}
|
|
147
|
+
function generateSubgraphCode(contractId, functions, subgraphName, events) {
|
|
148
|
+
const contractParts = contractId.split(".");
|
|
149
|
+
const contractName = contractParts[contractParts.length - 1] ?? contractId;
|
|
150
|
+
const name = subgraphName ?? contractName;
|
|
151
|
+
const publicFunctions = functions.filter((f) => f.access === "public");
|
|
152
|
+
const hasEvents = events && events.length > 0;
|
|
153
|
+
if (publicFunctions.length === 0 && !hasEvents) {
|
|
154
|
+
return `// No public functions or events selected for ${contractId}`;
|
|
155
|
+
}
|
|
156
|
+
const tableDefs = [];
|
|
157
|
+
if (hasEvents) {
|
|
158
|
+
for (const ev of events) {
|
|
159
|
+
const tableName = toSnake(ev.name);
|
|
160
|
+
let columns;
|
|
161
|
+
if (isAbiTuple(ev.value)) {
|
|
162
|
+
columns = buildColumns(ev.value.tuple);
|
|
163
|
+
} else {
|
|
164
|
+
columns = ` value: { type: '${clarityTypeToSubgraphColumn(ev.value).type}' }`;
|
|
165
|
+
}
|
|
166
|
+
tableDefs.push(` ${tableName}: {
|
|
167
|
+
columns: {
|
|
168
|
+
${columns}
|
|
169
|
+
}
|
|
170
|
+
}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const fn of publicFunctions) {
|
|
174
|
+
const tableName = toSnake(fn.name);
|
|
175
|
+
const columns = buildColumns(fn.args);
|
|
176
|
+
tableDefs.push(` ${tableName}: {
|
|
177
|
+
columns: {
|
|
178
|
+
${columns}
|
|
179
|
+
}
|
|
180
|
+
}`);
|
|
181
|
+
}
|
|
182
|
+
const schemaBlock = tableDefs.join(`,
|
|
183
|
+
`);
|
|
184
|
+
const sourceEntries = [`{ contract: '${contractId}' }`];
|
|
185
|
+
const handlerEntries = [];
|
|
186
|
+
if (hasEvents) {
|
|
187
|
+
for (const ev of events) {
|
|
188
|
+
const tableName = toSnake(ev.name);
|
|
189
|
+
let insertCall;
|
|
190
|
+
if (isAbiTuple(ev.value)) {
|
|
191
|
+
insertCall = buildInsertCall(tableName, ev.value.tuple);
|
|
192
|
+
} else {
|
|
193
|
+
insertCall = ` ctx.insert('${tableName}', {
|
|
194
|
+
value: event.value,
|
|
195
|
+
});`;
|
|
196
|
+
}
|
|
197
|
+
handlerEntries.push(` '${contractId}::${ev.name}': async (event, ctx) => {
|
|
198
|
+
${insertCall}
|
|
199
|
+
}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
for (const fn of publicFunctions) {
|
|
203
|
+
const tableName = toSnake(fn.name);
|
|
204
|
+
const insertCall = buildInsertCall(tableName, fn.args);
|
|
205
|
+
handlerEntries.push(` '${contractId}::${fn.name}': async (event, ctx) => {
|
|
206
|
+
${insertCall}
|
|
207
|
+
}`);
|
|
208
|
+
}
|
|
209
|
+
const handlersBlock = handlerEntries.join(`,
|
|
210
|
+
|
|
211
|
+
`);
|
|
212
|
+
return `import { defineSubgraph } from '@secondlayer/subgraphs';
|
|
213
|
+
|
|
214
|
+
export default defineSubgraph({
|
|
215
|
+
name: '${name}',
|
|
216
|
+
sources: [${sourceEntries.join(", ")}],
|
|
217
|
+
schema: {
|
|
218
|
+
${schemaBlock}
|
|
219
|
+
},
|
|
220
|
+
handlers: {
|
|
221
|
+
${handlersBlock}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/tools/scaffold.ts
|
|
228
|
+
var API_BASE = process.env.SECONDLAYER_API_URL || "https://api.secondlayer.tools";
|
|
229
|
+
async function fetchAbi(contractId) {
|
|
230
|
+
const res = await fetch(`${API_BASE}/api/node/contracts/${contractId}/abi`);
|
|
231
|
+
if (!res.ok) {
|
|
232
|
+
if (res.status === 404)
|
|
233
|
+
throw new Error(`Contract not found: ${contractId}`);
|
|
234
|
+
throw new Error(`Failed to fetch ABI: HTTP ${res.status}`);
|
|
235
|
+
}
|
|
236
|
+
const abi = await res.json();
|
|
237
|
+
return {
|
|
238
|
+
functions: abi.functions ?? [],
|
|
239
|
+
maps: abi.maps ?? []
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function registerScaffoldTools(server) {
|
|
243
|
+
defineTool(server, "scaffold_from_contract", "Generate a subgraph scaffold from a deployed Stacks contract. Fetches the ABI automatically.", {
|
|
244
|
+
contractId: z2.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
|
|
245
|
+
subgraphName: z2.string().optional().describe("Override the subgraph name (defaults to contract name)")
|
|
246
|
+
}, async ({ contractId, subgraphName }) => {
|
|
247
|
+
const { functions, maps } = await fetchAbi(contractId);
|
|
248
|
+
const code = generateSubgraphCode(contractId, functions, subgraphName, maps);
|
|
249
|
+
return { content: [{ type: "text", text: code }] };
|
|
250
|
+
});
|
|
251
|
+
defineTool(server, "scaffold_from_abi", "Generate a subgraph scaffold from a provided ABI JSON. Use when you already have the ABI.", {
|
|
252
|
+
abi: z2.string().describe("ABI JSON string (the full contract ABI object)"),
|
|
253
|
+
contractId: z2.string().describe("Fully qualified contract ID"),
|
|
254
|
+
subgraphName: z2.string().optional().describe("Override the subgraph name")
|
|
255
|
+
}, async ({ abi, contractId, subgraphName }) => {
|
|
256
|
+
let parsed;
|
|
257
|
+
try {
|
|
258
|
+
parsed = JSON.parse(abi);
|
|
259
|
+
} catch {
|
|
260
|
+
return { content: [{ type: "text", text: "Invalid ABI JSON" }], isError: true };
|
|
261
|
+
}
|
|
262
|
+
const code = generateSubgraphCode(contractId, parsed.functions ?? [], subgraphName, parsed.maps ?? []);
|
|
263
|
+
return { content: [{ type: "text", text: code }] };
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/tools/streams.ts
|
|
268
|
+
import { z as z3 } from "zod";
|
|
269
|
+
|
|
270
|
+
// src/lib/client.ts
|
|
271
|
+
import { SecondLayer } from "@secondlayer/sdk";
|
|
272
|
+
var instance = null;
|
|
273
|
+
function getClient() {
|
|
274
|
+
if (!instance) {
|
|
275
|
+
const apiKey = process.env.SECONDLAYER_API_KEY;
|
|
276
|
+
if (!apiKey) {
|
|
277
|
+
throw new Error("SECONDLAYER_API_KEY environment variable is required. " + "Get your key at https://app.secondlayer.tools/settings/api-keys");
|
|
278
|
+
}
|
|
279
|
+
instance = new SecondLayer({ apiKey });
|
|
280
|
+
}
|
|
281
|
+
return instance;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/lib/format.ts
|
|
285
|
+
function formatStreamSummary(s) {
|
|
286
|
+
return {
|
|
287
|
+
id: s.id,
|
|
288
|
+
name: s.name,
|
|
289
|
+
status: s.status,
|
|
290
|
+
endpointUrl: s.endpointUrl,
|
|
291
|
+
totalDeliveries: s.totalDeliveries,
|
|
292
|
+
failedDeliveries: s.failedDeliveries
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function formatSubgraphSummary(s) {
|
|
296
|
+
return {
|
|
297
|
+
name: s.name,
|
|
298
|
+
status: s.status,
|
|
299
|
+
tables: Array.isArray(s.tables) ? s.tables : Object.keys(s.tables),
|
|
300
|
+
lastProcessedBlock: s.lastProcessedBlock
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function formatDeliverySummary(d) {
|
|
304
|
+
return {
|
|
305
|
+
id: d.id,
|
|
306
|
+
blockHeight: d.blockHeight,
|
|
307
|
+
status: d.status,
|
|
308
|
+
statusCode: d.statusCode,
|
|
309
|
+
attempts: d.attempts,
|
|
310
|
+
createdAt: d.createdAt
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function withCap(items, cap) {
|
|
314
|
+
return {
|
|
315
|
+
items: items.slice(0, cap),
|
|
316
|
+
truncated: items.length > cap,
|
|
317
|
+
total: items.length
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/tools/streams.ts
|
|
322
|
+
var FilterSchema = z3.discriminatedUnion("type", [
|
|
323
|
+
z3.object({ type: z3.literal("stx_transfer"), sender: z3.string().optional(), recipient: z3.string().optional(), minAmount: z3.number().optional(), maxAmount: z3.number().optional() }),
|
|
324
|
+
z3.object({ type: z3.literal("stx_mint"), recipient: z3.string().optional(), minAmount: z3.number().optional() }),
|
|
325
|
+
z3.object({ type: z3.literal("stx_burn"), sender: z3.string().optional(), minAmount: z3.number().optional() }),
|
|
326
|
+
z3.object({ type: z3.literal("stx_lock"), lockedAddress: z3.string().optional(), minAmount: z3.number().optional() }),
|
|
327
|
+
z3.object({ type: z3.literal("ft_transfer"), sender: z3.string().optional(), recipient: z3.string().optional(), assetIdentifier: z3.string().optional(), minAmount: z3.number().optional() }),
|
|
328
|
+
z3.object({ type: z3.literal("ft_mint"), recipient: z3.string().optional(), assetIdentifier: z3.string().optional(), minAmount: z3.number().optional() }),
|
|
329
|
+
z3.object({ type: z3.literal("ft_burn"), sender: z3.string().optional(), assetIdentifier: z3.string().optional(), minAmount: z3.number().optional() }),
|
|
330
|
+
z3.object({ type: z3.literal("nft_transfer"), sender: z3.string().optional(), recipient: z3.string().optional(), assetIdentifier: z3.string().optional(), tokenId: z3.string().optional() }),
|
|
331
|
+
z3.object({ type: z3.literal("nft_mint"), recipient: z3.string().optional(), assetIdentifier: z3.string().optional(), tokenId: z3.string().optional() }),
|
|
332
|
+
z3.object({ type: z3.literal("nft_burn"), sender: z3.string().optional(), assetIdentifier: z3.string().optional(), tokenId: z3.string().optional() }),
|
|
333
|
+
z3.object({ type: z3.literal("contract_call"), contractId: z3.string().optional(), functionName: z3.string().optional(), caller: z3.string().optional() }),
|
|
334
|
+
z3.object({ type: z3.literal("contract_deploy"), deployer: z3.string().optional(), contractName: z3.string().optional() }),
|
|
335
|
+
z3.object({ type: z3.literal("print_event"), contractId: z3.string().optional(), topic: z3.string().optional(), contains: z3.string().optional() })
|
|
336
|
+
]);
|
|
337
|
+
function registerStreamTools(server) {
|
|
338
|
+
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 }) => {
|
|
339
|
+
const { streams } = await getClient().streams.list(status ? { status } : undefined);
|
|
340
|
+
return {
|
|
341
|
+
content: [{ type: "text", text: JSON.stringify(streams.map(formatStreamSummary), null, 2) }]
|
|
342
|
+
};
|
|
343
|
+
});
|
|
344
|
+
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 }) => {
|
|
345
|
+
const stream = await getClient().streams.get(id);
|
|
346
|
+
return { content: [{ type: "text", text: JSON.stringify(stream, null, 2) }] };
|
|
347
|
+
});
|
|
348
|
+
defineTool(server, "streams_create", "Create a new webhook stream with filters.", {
|
|
349
|
+
name: z3.string().describe("Stream name"),
|
|
350
|
+
endpointUrl: z3.string().describe("Webhook endpoint URL"),
|
|
351
|
+
filters: z3.array(FilterSchema).min(1).describe("Event filters (at least one required)")
|
|
352
|
+
}, async ({ name, endpointUrl, filters }) => {
|
|
353
|
+
const result = await getClient().streams.create({ name, endpointUrl, filters });
|
|
354
|
+
return {
|
|
355
|
+
content: [{ type: "text", text: JSON.stringify({ id: result.stream.id, signingSecret: result.signingSecret }, null, 2) }]
|
|
356
|
+
};
|
|
357
|
+
});
|
|
358
|
+
defineTool(server, "streams_update", "Update a stream's name, endpoint, or filters.", {
|
|
359
|
+
id: z3.string().describe("Stream UUID or prefix"),
|
|
360
|
+
name: z3.string().optional().describe("New name"),
|
|
361
|
+
endpointUrl: z3.string().optional().describe("New endpoint URL"),
|
|
362
|
+
filters: z3.array(FilterSchema).min(1).optional().describe("New filters")
|
|
363
|
+
}, async ({ id, name, endpointUrl, filters }) => {
|
|
364
|
+
const data = {};
|
|
365
|
+
if (name !== undefined)
|
|
366
|
+
data.name = name;
|
|
367
|
+
if (endpointUrl !== undefined)
|
|
368
|
+
data.endpointUrl = endpointUrl;
|
|
369
|
+
if (filters !== undefined)
|
|
370
|
+
data.filters = filters;
|
|
371
|
+
const stream = await getClient().streams.update(id, data);
|
|
372
|
+
return { content: [{ type: "text", text: JSON.stringify(formatStreamSummary(stream), null, 2) }] };
|
|
373
|
+
});
|
|
374
|
+
defineTool(server, "streams_delete", "Delete a stream permanently.", { id: z3.string().describe("Stream UUID or prefix") }, async ({ id }) => {
|
|
375
|
+
await getClient().streams.delete(id);
|
|
376
|
+
return { content: [{ type: "text", text: `Stream ${id} deleted.` }] };
|
|
377
|
+
});
|
|
378
|
+
defineTool(server, "streams_toggle", "Enable or disable a stream.", {
|
|
379
|
+
id: z3.string().describe("Stream UUID or prefix"),
|
|
380
|
+
enabled: z3.boolean().describe("true to enable, false to disable")
|
|
381
|
+
}, async ({ id, enabled }) => {
|
|
382
|
+
const stream = enabled ? await getClient().streams.enable(id) : await getClient().streams.disable(id);
|
|
383
|
+
return { content: [{ type: "text", text: JSON.stringify({ id: stream.id, status: stream.status }, null, 2) }] };
|
|
384
|
+
});
|
|
385
|
+
defineTool(server, "streams_deliveries", "List recent deliveries for a stream (max 25).", {
|
|
386
|
+
id: z3.string().describe("Stream UUID or prefix"),
|
|
387
|
+
limit: z3.number().max(25).optional().describe("Max results (default 25)"),
|
|
388
|
+
status: z3.string().optional().describe("Filter by delivery status")
|
|
389
|
+
}, async ({ id, limit, status }) => {
|
|
390
|
+
const { deliveries } = await getClient().streams.listDeliveries(id, { limit: limit ?? 25, status });
|
|
391
|
+
const result = withCap(deliveries.map(formatDeliverySummary), 25);
|
|
392
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
393
|
+
});
|
|
394
|
+
defineTool(server, "streams_pause_all", "Pause all active streams. Without confirm: true, returns a preview of streams that would be paused.", {
|
|
395
|
+
confirm: z3.boolean().optional().describe("Set to true to execute. Omit or false for preview only.")
|
|
396
|
+
}, async ({ confirm }) => {
|
|
397
|
+
if (!confirm) {
|
|
398
|
+
const { streams } = await getClient().streams.list({ status: "active" });
|
|
399
|
+
const names = streams.map((s) => s.name);
|
|
400
|
+
return {
|
|
401
|
+
content: [{
|
|
402
|
+
type: "text",
|
|
403
|
+
text: JSON.stringify({ preview: true, count: names.length, streams: names }, null, 2)
|
|
404
|
+
}]
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
const result = await getClient().streams.pauseAll();
|
|
408
|
+
return {
|
|
409
|
+
content: [{
|
|
410
|
+
type: "text",
|
|
411
|
+
text: JSON.stringify({ paused: result.paused, streams: result.streams.map(formatStreamSummary) }, null, 2)
|
|
412
|
+
}]
|
|
413
|
+
};
|
|
414
|
+
});
|
|
415
|
+
defineTool(server, "streams_resume_all", "Resume all paused streams. Without confirm: true, returns a preview of streams that would be resumed.", {
|
|
416
|
+
confirm: z3.boolean().optional().describe("Set to true to execute. Omit or false for preview only.")
|
|
417
|
+
}, async ({ confirm }) => {
|
|
418
|
+
if (!confirm) {
|
|
419
|
+
const { streams } = await getClient().streams.list({ status: "paused" });
|
|
420
|
+
const names = streams.map((s) => s.name);
|
|
421
|
+
return {
|
|
422
|
+
content: [{
|
|
423
|
+
type: "text",
|
|
424
|
+
text: JSON.stringify({ preview: true, count: names.length, streams: names }, null, 2)
|
|
425
|
+
}]
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
const result = await getClient().streams.resumeAll();
|
|
429
|
+
return {
|
|
430
|
+
content: [{
|
|
431
|
+
type: "text",
|
|
432
|
+
text: JSON.stringify({ resumed: result.resumed, streams: result.streams.map(formatStreamSummary) }, null, 2)
|
|
433
|
+
}]
|
|
434
|
+
};
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/tools/subgraphs.ts
|
|
439
|
+
import { z as z4 } from "zod";
|
|
440
|
+
|
|
441
|
+
// src/lib/bundle.ts
|
|
442
|
+
import esbuild from "esbuild";
|
|
443
|
+
import { validateSubgraphDefinition } from "@secondlayer/subgraphs/validate";
|
|
444
|
+
import { sourceKey } from "@secondlayer/subgraphs";
|
|
445
|
+
async function bundleSubgraphCode(code) {
|
|
446
|
+
const result = await esbuild.build({
|
|
447
|
+
stdin: { contents: code, loader: "ts", resolveDir: process.cwd() },
|
|
448
|
+
bundle: true,
|
|
449
|
+
platform: "node",
|
|
450
|
+
format: "esm",
|
|
451
|
+
external: ["@secondlayer/subgraphs"],
|
|
452
|
+
write: false
|
|
453
|
+
});
|
|
454
|
+
const handlerCode = new TextDecoder().decode(result.outputFiles[0].contents);
|
|
455
|
+
const dataUri = `data:text/javascript;base64,${Buffer.from(handlerCode).toString("base64")}`;
|
|
456
|
+
const mod = await import(dataUri);
|
|
457
|
+
const def = mod.default ?? mod;
|
|
458
|
+
const validated = validateSubgraphDefinition(def);
|
|
459
|
+
return {
|
|
460
|
+
name: validated.name,
|
|
461
|
+
version: validated.version,
|
|
462
|
+
description: validated.description,
|
|
463
|
+
sources: validated.sources.map(sourceKey),
|
|
464
|
+
schema: validated.schema,
|
|
465
|
+
handlerCode
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/tools/subgraphs.ts
|
|
470
|
+
function registerSubgraphTools(server) {
|
|
471
|
+
defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only.", {}, async () => {
|
|
472
|
+
const { data } = await getClient().subgraphs.list();
|
|
473
|
+
return {
|
|
474
|
+
content: [{ type: "text", text: JSON.stringify(data.map(formatSubgraphSummary), null, 2) }]
|
|
475
|
+
};
|
|
476
|
+
});
|
|
477
|
+
defineTool(server, "subgraphs_get", "Get full details of a subgraph including schema, health, and table columns.", { name: z4.string().describe("Subgraph name") }, async ({ name }) => {
|
|
478
|
+
const detail = await getClient().subgraphs.get(name);
|
|
479
|
+
return { content: [{ type: "text", text: JSON.stringify(detail, null, 2) }] };
|
|
480
|
+
});
|
|
481
|
+
defineTool(server, "subgraphs_query", "Query rows from a subgraph table (max 50 rows).", {
|
|
482
|
+
name: z4.string().describe("Subgraph name"),
|
|
483
|
+
table: z4.string().describe("Table name"),
|
|
484
|
+
filters: z4.record(z4.string(), z4.string()).optional().describe('Column filters as key-value pairs (e.g. {"sender": "SP..."})'),
|
|
485
|
+
sort: z4.string().optional().describe("Column to sort by"),
|
|
486
|
+
order: z4.enum(["asc", "desc"]).optional().describe("Sort order"),
|
|
487
|
+
limit: z4.number().max(50).optional().describe("Max rows (default 50)"),
|
|
488
|
+
offset: z4.number().optional().describe("Offset for pagination")
|
|
489
|
+
}, async ({ name, table, filters, sort, order, limit, offset }) => {
|
|
490
|
+
const rows = await getClient().subgraphs.queryTable(name, table, {
|
|
491
|
+
filters,
|
|
492
|
+
sort,
|
|
493
|
+
order,
|
|
494
|
+
limit: limit ?? 50,
|
|
495
|
+
offset
|
|
496
|
+
});
|
|
497
|
+
const result = withCap(rows, 50);
|
|
498
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
499
|
+
});
|
|
500
|
+
defineTool(server, "subgraphs_reindex", "Reindex a subgraph from a specific block range.", {
|
|
501
|
+
name: z4.string().describe("Subgraph name"),
|
|
502
|
+
fromBlock: z4.number().optional().describe("Start block (defaults to beginning)"),
|
|
503
|
+
toBlock: z4.number().optional().describe("End block (defaults to latest)")
|
|
504
|
+
}, async ({ name, fromBlock, toBlock }) => {
|
|
505
|
+
const result = await getClient().subgraphs.reindex(name, { fromBlock, toBlock });
|
|
506
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
507
|
+
});
|
|
508
|
+
defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z4.string().describe("Subgraph name") }, async ({ name }) => {
|
|
509
|
+
const result = await getClient().subgraphs.delete(name);
|
|
510
|
+
return { content: [{ type: "text", text: result.message }] };
|
|
511
|
+
});
|
|
512
|
+
defineTool(server, "subgraphs_deploy", "Deploy a subgraph from TypeScript code. Pass the full defineSubgraph() source — it will be bundled, validated, and deployed.", {
|
|
513
|
+
code: z4.string().describe("TypeScript source code containing a defineSubgraph() call"),
|
|
514
|
+
reindex: z4.boolean().optional().describe("Force reindex on breaking schema change (drops and rebuilds all data)")
|
|
515
|
+
}, async ({ code, reindex }) => {
|
|
516
|
+
const bundled = await bundleSubgraphCode(code);
|
|
517
|
+
const result = await getClient().subgraphs.deploy({
|
|
518
|
+
name: bundled.name,
|
|
519
|
+
version: bundled.version,
|
|
520
|
+
description: bundled.description,
|
|
521
|
+
sources: bundled.sources,
|
|
522
|
+
schema: bundled.schema,
|
|
523
|
+
handlerCode: bundled.handlerCode,
|
|
524
|
+
reindex
|
|
525
|
+
});
|
|
526
|
+
return {
|
|
527
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
528
|
+
};
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// src/server.ts
|
|
533
|
+
function createServer() {
|
|
534
|
+
const server = new McpServer({
|
|
535
|
+
name: "secondlayer",
|
|
536
|
+
version: "0.1.0"
|
|
537
|
+
});
|
|
538
|
+
registerTemplateTools(server);
|
|
539
|
+
registerScaffoldTools(server);
|
|
540
|
+
registerStreamTools(server);
|
|
541
|
+
registerSubgraphTools(server);
|
|
542
|
+
return server;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/bin.ts
|
|
546
|
+
var server = createServer();
|
|
547
|
+
var transport = new StdioServerTransport;
|
|
548
|
+
await server.connect(transport);
|
|
549
|
+
|
|
550
|
+
//# debugId=6835CF0A8A97C86164756E2164756E21
|
|
551
|
+
//# sourceMappingURL=bin.js.map
|