@secondlayer/mcp 3.5.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin-http.js +325 -148
- package/dist/bin-http.js.map +13 -12
- package/dist/bin.js +325 -148
- package/dist/bin.js.map +13 -12
- package/dist/index.js +325 -148
- package/dist/index.js.map +13 -12
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
6
|
|
|
7
7
|
// src/resources.ts
|
|
8
|
+
import { CHAIN_TRIGGER_FIELDS, DECODED_EVENT_TYPES } from "@secondlayer/shared";
|
|
9
|
+
import { TRAIT_STANDARDS } from "@secondlayer/stacks/clarity";
|
|
8
10
|
import { TYPE_MAP } from "@secondlayer/subgraphs/schema";
|
|
9
11
|
|
|
10
12
|
// src/lib/client.ts
|
|
@@ -17,10 +19,12 @@ function getClient() {
|
|
|
17
19
|
if (!instance) {
|
|
18
20
|
const apiKey = readApiKey();
|
|
19
21
|
const baseUrl = process.env.SECONDLAYER_API_URL;
|
|
22
|
+
const dumpsBaseUrl = process.env.SL_STREAMS_DUMPS_URL;
|
|
20
23
|
instance = new SecondLayer({
|
|
21
24
|
...apiKey ? { apiKey } : {},
|
|
22
25
|
origin: "mcp",
|
|
23
|
-
...baseUrl ? { baseUrl } : {}
|
|
26
|
+
...baseUrl ? { baseUrl } : {},
|
|
27
|
+
...dumpsBaseUrl ? { dumpsBaseUrl } : {}
|
|
24
28
|
});
|
|
25
29
|
}
|
|
26
30
|
return instance;
|
|
@@ -99,6 +103,11 @@ function defineTool(server, name, description, schema, handler) {
|
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
// src/resources.ts
|
|
106
|
+
var TRAIT_BLURBS = {
|
|
107
|
+
"sip-009": "Non-fungible token (NFT) standard",
|
|
108
|
+
"sip-010": "Fungible token standard",
|
|
109
|
+
"sip-013": "Semi-fungible token standard"
|
|
110
|
+
};
|
|
102
111
|
var FILTERS_REFERENCE = [
|
|
103
112
|
{
|
|
104
113
|
type: "stx_transfer",
|
|
@@ -159,7 +168,8 @@ var PRODUCT_BLURBS = {
|
|
|
159
168
|
contracts: "trait-based contract discovery",
|
|
160
169
|
subgraphs: "author/deploy/query custom indexes",
|
|
161
170
|
subscriptions: "webhook delivery on subgraph rows or raw chain events",
|
|
162
|
-
account: "identity, plan, billing, and API keys",
|
|
171
|
+
account: "identity, plan, billing, usage, spend caps, and API keys",
|
|
172
|
+
project: "create/manage projects and view their team",
|
|
163
173
|
scaffold: "generate typed contract clients from a deployment or ABI"
|
|
164
174
|
};
|
|
165
175
|
var PRODUCT_ORDER = [
|
|
@@ -170,6 +180,7 @@ var PRODUCT_ORDER = [
|
|
|
170
180
|
"subgraphs",
|
|
171
181
|
"subscriptions",
|
|
172
182
|
"account",
|
|
183
|
+
"project",
|
|
173
184
|
"scaffold"
|
|
174
185
|
];
|
|
175
186
|
function buildCapabilities() {
|
|
@@ -191,7 +202,7 @@ function buildCapabilities() {
|
|
|
191
202
|
});
|
|
192
203
|
return {
|
|
193
204
|
products,
|
|
194
|
-
discoverFirst: "Call datasets_list / contracts_find to learn what exists before querying."
|
|
205
|
+
discoverFirst: "Call datasets_list / index_discover / contracts_find to learn what exists (and which filters each surface accepts) before querying."
|
|
195
206
|
};
|
|
196
207
|
}
|
|
197
208
|
var READ_AUTH_TIERS = {
|
|
@@ -212,6 +223,8 @@ async function buildContext(deps = { clientProvider: getClient }) {
|
|
|
212
223
|
indexTip: orNull(snap?.indexTip),
|
|
213
224
|
subgraphs: snap?.subgraphs ? snap.subgraphs.map(formatSubgraphSummary) : unavailable,
|
|
214
225
|
subscriptions: orNull(snap?.subscriptions),
|
|
226
|
+
projects: orNull(snap?.projects),
|
|
227
|
+
apiKeys: orNull(snap?.apiKeys),
|
|
215
228
|
activeOperations: orNull(snap?.activeOperations)
|
|
216
229
|
},
|
|
217
230
|
whatYouCanDo: buildCapabilities(),
|
|
@@ -248,6 +261,56 @@ function registerResources(server) {
|
|
|
248
261
|
}
|
|
249
262
|
]
|
|
250
263
|
}));
|
|
264
|
+
server.resource("traits", "secondlayer://traits", {
|
|
265
|
+
description: "SIP trait standards the platform can classify and scaffold against — the valid values for contracts_find `trait` and subgraph scaffold `--trait`."
|
|
266
|
+
}, async () => ({
|
|
267
|
+
contents: [
|
|
268
|
+
{
|
|
269
|
+
uri: "secondlayer://traits",
|
|
270
|
+
mimeType: "application/json",
|
|
271
|
+
text: JSON.stringify({
|
|
272
|
+
traits: TRAIT_STANDARDS.map((id) => ({
|
|
273
|
+
id,
|
|
274
|
+
description: TRAIT_BLURBS[id]
|
|
275
|
+
}))
|
|
276
|
+
}, null, 2)
|
|
277
|
+
}
|
|
278
|
+
]
|
|
279
|
+
}));
|
|
280
|
+
server.resource("streams-filters", "secondlayer://streams-filters", {
|
|
281
|
+
description: "Streams firehose vocabulary — the decoded event types and the filter fields accepted by streams_events / streams_consume."
|
|
282
|
+
}, async () => ({
|
|
283
|
+
contents: [
|
|
284
|
+
{
|
|
285
|
+
uri: "secondlayer://streams-filters",
|
|
286
|
+
mimeType: "application/json",
|
|
287
|
+
text: JSON.stringify({
|
|
288
|
+
eventTypes: [...DECODED_EVENT_TYPES],
|
|
289
|
+
filters: {
|
|
290
|
+
types: "include only these event types (array)",
|
|
291
|
+
notTypes: "exclude these event types (array)",
|
|
292
|
+
contractId: "match a contract id",
|
|
293
|
+
sender: "match the sender principal",
|
|
294
|
+
recipient: "match the recipient principal",
|
|
295
|
+
assetIdentifier: "match the asset identifier (contract::asset)",
|
|
296
|
+
fromHeight: "start block height (inclusive)",
|
|
297
|
+
toHeight: "end block height (inclusive)"
|
|
298
|
+
}
|
|
299
|
+
}, null, 2)
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
}));
|
|
303
|
+
server.resource("chain-triggers", "secondlayer://chain-triggers", {
|
|
304
|
+
description: "Chain-subscription trigger types and the filter fields each accepts (for subscriptions_create triggers)."
|
|
305
|
+
}, async () => ({
|
|
306
|
+
contents: [
|
|
307
|
+
{
|
|
308
|
+
uri: "secondlayer://chain-triggers",
|
|
309
|
+
mimeType: "application/json",
|
|
310
|
+
text: JSON.stringify(CHAIN_TRIGGER_FIELDS, null, 2)
|
|
311
|
+
}
|
|
312
|
+
]
|
|
313
|
+
}));
|
|
251
314
|
}
|
|
252
315
|
|
|
253
316
|
// src/tools/account.ts
|
|
@@ -272,7 +335,7 @@ function registerAccountTools(server) {
|
|
|
272
335
|
const result = await apiRequest("PATCH", "/api/accounts/me", body);
|
|
273
336
|
return jsonResponse(result);
|
|
274
337
|
});
|
|
275
|
-
defineTool(server, "account_billing", "Show the account's plan and subscription/billing status. Requires an API key.", {}, async () => {
|
|
338
|
+
defineTool(server, "account_billing", "Show the account's plan and subscription/billing status. Requires an API key. (Plan upgrade / Stripe portal / checkout are deliberately not agent tools — they are session-only human-payment flows; use account_set_caps to bound spend.)", {}, async () => {
|
|
276
339
|
const result = await apiRequest("GET", "/api/billing/status");
|
|
277
340
|
return jsonResponse(result);
|
|
278
341
|
});
|
|
@@ -280,6 +343,23 @@ function registerAccountTools(server) {
|
|
|
280
343
|
product: z.enum(["streams", "index"]).optional().describe("Key scope (default streams)"),
|
|
281
344
|
name: z.string().optional().describe("Optional label for the key")
|
|
282
345
|
}, async ({ product, name }) => jsonResponse(await getClient().apiKeys.create({ product, name })));
|
|
346
|
+
defineTool(server, "account_list_keys", "List the account's API keys (metadata only — prefix, name, status, product, tier, last used; never the plaintext). Requires an account-level (owner) API key.", {}, async () => jsonResponse(await getClient().apiKeys.list()));
|
|
347
|
+
defineTool(server, "account_revoke_key", "Revoke an API key by id. Requests using that key stop working immediately. Requires an account-level (owner) API key.", { id: z.string().describe("Key id from account_list_keys") }, async ({ id }) => jsonResponse(await getClient().apiKeys.revoke(id)));
|
|
348
|
+
defineTool(server, "account_usage", "Show the current billing period's usage snapshot: spend (with cap/projection), compute, storage, and per-project breakdown. Requires an API key.", {}, async () => jsonResponse(await apiRequest("GET", "/api/accounts/usage")));
|
|
349
|
+
defineTool(server, "account_get_caps", "Show the account's spend caps and alert threshold. Requires an account-level (owner) API key.", {}, async () => jsonResponse(await apiRequest("GET", "/api/billing/caps")));
|
|
350
|
+
defineTool(server, "account_set_caps", "Set the account's spend caps and alert threshold (no payment — just bounds spend). Requires an account-level (owner) API key.", {
|
|
351
|
+
monthlyCapCents: z.number().int().nonnegative().optional().describe("Total monthly spend cap in cents"),
|
|
352
|
+
computeCapCents: z.number().int().nonnegative().optional().describe("Compute spend cap in cents"),
|
|
353
|
+
storageCapCents: z.number().int().nonnegative().optional().describe("Storage spend cap in cents"),
|
|
354
|
+
alertThresholdPct: z.number().int().min(1).max(100).optional().describe("Percent of cap that triggers an alert (1-100)")
|
|
355
|
+
}, async (caps) => {
|
|
356
|
+
const body = {};
|
|
357
|
+
for (const [k, v] of Object.entries(caps)) {
|
|
358
|
+
if (v !== undefined)
|
|
359
|
+
body[k] = v;
|
|
360
|
+
}
|
|
361
|
+
return jsonResponse(await apiRequest("PATCH", "/api/billing/caps", body));
|
|
362
|
+
});
|
|
283
363
|
}
|
|
284
364
|
|
|
285
365
|
// src/tools/contracts.ts
|
|
@@ -301,7 +381,7 @@ function registerDatasetTools(server, clientProvider = getClient) {
|
|
|
301
381
|
const catalog = await clientProvider().datasets.listDatasets();
|
|
302
382
|
return jsonResponse(catalog);
|
|
303
383
|
});
|
|
304
|
-
defineTool(server, "datasets_query", 'Query
|
|
384
|
+
defineTool(server, "datasets_query", 'Query ANY Foundation Dataset by slug — every family returned by datasets_list is queryable, including bespoke ones (bns/resolve with {"fqn":"alice.btc"}, bns/names, bns/namespaces, network-health/summary) and any dataset added later. Accepts family ("sbtc-events") or path ("sbtc/events") slug forms. Filters are passed through as documented query params (e.g. {"sender": "SP...", "from_block": "150000"}). Returns { rows, next_cursor, tip }; single-record datasets like bns/resolve return 0-or-1 rows (empty = not found). Call datasets_list first to discover slugs and their filters.', {
|
|
305
385
|
slug: z3.string().describe("Dataset slug from datasets_list (e.g. stx-transfers)"),
|
|
306
386
|
filters: z3.record(z3.string(), z3.string()).optional().describe("Documented per-dataset query params (snake_case values)"),
|
|
307
387
|
limit: z3.number().optional().describe("Max rows for this page"),
|
|
@@ -312,19 +392,19 @@ function registerDatasetTools(server, clientProvider = getClient) {
|
|
|
312
392
|
params.limit = limit;
|
|
313
393
|
if (cursor !== undefined)
|
|
314
394
|
params.cursor = cursor;
|
|
315
|
-
const result = await clientProvider().datasets.
|
|
395
|
+
const result = await clientProvider().datasets.get(slug, params);
|
|
316
396
|
return jsonResponse(result);
|
|
317
397
|
});
|
|
318
398
|
}
|
|
319
399
|
|
|
320
400
|
// src/tools/index.ts
|
|
321
|
-
import { DECODED_EVENT_TYPES } from "@secondlayer/shared";
|
|
401
|
+
import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
|
|
322
402
|
import {
|
|
323
|
-
|
|
324
|
-
|
|
403
|
+
INDEX_CODEGEN_TABLES,
|
|
404
|
+
generateIndexSchema
|
|
325
405
|
} from "@secondlayer/subgraphs";
|
|
326
406
|
import { z as z4 } from "zod/v4";
|
|
327
|
-
var INDEX_EVENT_TYPES =
|
|
407
|
+
var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES2;
|
|
328
408
|
var rangeFilters = {
|
|
329
409
|
contractId: z4.string().optional().describe("Filter by contract id"),
|
|
330
410
|
fromHeight: z4.number().optional().describe("Start block height (inclusive)"),
|
|
@@ -351,17 +431,19 @@ function registerIndexTools(server, clientProvider = getClient) {
|
|
|
351
431
|
recipient: z4.string().optional().describe("Filter by recipient principal"),
|
|
352
432
|
assetIdentifier: z4.string().optional().describe("Filter by asset identifier (contract::asset)")
|
|
353
433
|
}, async (params) => jsonResponse(await clientProvider().index.nftTransfers.list(params)));
|
|
354
|
-
defineTool(server, "index_events", "List decoded chain events from the Index by event type. Use this for event types without a dedicated tool (stx_*, ft_mint/burn, nft_mint/burn, print). For ft/nft transfers prefer index_ft_transfers / index_nft_transfers.", {
|
|
434
|
+
defineTool(server, "index_events", "List decoded chain events from the Index by event type. Use this for event types without a dedicated tool (stx_*, ft_mint/burn, nft_mint/burn, print), and for trait-scoped queries: set `trait` (e.g. sip-010) to match all contracts conforming to a standard — pair with contracts_find to discover traits. For ft/nft transfers without a trait prefer index_ft_transfers / index_nft_transfers.", {
|
|
355
435
|
eventType: z4.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
|
|
356
436
|
...rangeFilters,
|
|
357
437
|
sender: z4.string().optional().describe("Filter by sender principal"),
|
|
358
438
|
recipient: z4.string().optional().describe("Filter by recipient principal"),
|
|
359
|
-
assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable")
|
|
439
|
+
assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable"),
|
|
440
|
+
trait: z4.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId; contract-keyed event types only.")
|
|
360
441
|
}, async (params) => jsonResponse(await clientProvider().index.events.list(params)));
|
|
361
|
-
defineTool(server, "index_contract_calls", "List decoded contract calls from the Index (function name, args, result). Note: contract-call cursors are a SEPARATE keyspace from event cursors — they are not interchangeable.", {
|
|
442
|
+
defineTool(server, "index_contract_calls", "List decoded contract calls from the Index (function name, args, result). Set `trait` (e.g. sip-010) to match calls to all contracts conforming to a standard. Note: contract-call cursors are a SEPARATE keyspace from event cursors — they are not interchangeable.", {
|
|
362
443
|
...rangeFilters,
|
|
363
444
|
functionName: z4.string().optional().describe("Filter by called function name"),
|
|
364
|
-
sender: z4.string().optional().describe("Filter by caller principal")
|
|
445
|
+
sender: z4.string().optional().describe("Filter by caller principal"),
|
|
446
|
+
trait: z4.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
|
|
365
447
|
}, async (params) => jsonResponse(await clientProvider().index.contractCalls.list(params)));
|
|
366
448
|
defineTool(server, "index_canonical", "List the canonical Stacks block sequence from the Index (height + hash). Anonymous reads allowed (free-tier keys rejected).", { ...heightFilters }, async (params) => jsonResponse(await clientProvider().index.canonical.list(params)));
|
|
367
449
|
defineTool(server, "index_blocks", "List decoded blocks from the Index. Anonymous reads allowed (free-tier keys rejected).", { ...heightFilters }, async (params) => jsonResponse(await clientProvider().index.blocks.list(params)));
|
|
@@ -397,6 +479,7 @@ function registerIndexTools(server, clientProvider = getClient) {
|
|
|
397
479
|
const tx = await clientProvider().index.mempool.get(txId);
|
|
398
480
|
return tx ? jsonResponse(tx) : notFound(`No pending tx for ${txId}`);
|
|
399
481
|
});
|
|
482
|
+
defineTool(server, "index_discover", "Discover the Index vocabulary: every event type and its columns, allowed/equality filters, and required-non-null fields (and which types accept `trait`). Read this before building Index queries instead of guessing filters. Anonymous read.", {}, async () => jsonResponse(await clientProvider().index.discover()));
|
|
400
483
|
defineTool(server, "index_usage", "Your own Index consumption (decoded events today + this month) and tier limits. Requires a Build+ API key (anonymous reads can't report usage).", {}, async () => jsonResponse(await clientProvider().index.usage()));
|
|
401
484
|
defineTool(server, "index_codegen", `Generate a typed schema (Prisma, Kysely, Drizzle, or JSON-Schema) for the public Index domain tables so they can be mirrored into a BYO database with full types. Returns the schema as text. Tables: ${INDEX_CODEGEN_TABLES.join(", ")}.`, {
|
|
402
485
|
target: z4.enum(["prisma", "kysely", "drizzle", "json-schema"]).optional().describe("Output target (default kysely)"),
|
|
@@ -408,9 +491,43 @@ function registerIndexTools(server, clientProvider = getClient) {
|
|
|
408
491
|
});
|
|
409
492
|
}
|
|
410
493
|
|
|
494
|
+
// src/tools/projects.ts
|
|
495
|
+
import { z as z5 } from "zod/v4";
|
|
496
|
+
function registerProjectTools(server, clientProvider = getClient) {
|
|
497
|
+
defineTool(server, "project_list", "List the account's projects. Requires an account-level (owner) API key.", {}, async () => jsonResponse(await clientProvider().projects.list()));
|
|
498
|
+
defineTool(server, "project_get", "Get a single project by slug.", { slug: z5.string().describe("Project slug") }, async ({ slug }) => jsonResponse(await clientProvider().projects.get(slug)));
|
|
499
|
+
defineTool(server, "project_create", "Create a project. The slug is derived from the name when omitted. Requires an account-level (owner) API key.", {
|
|
500
|
+
name: z5.string().describe("Project name"),
|
|
501
|
+
slug: z5.string().optional().describe("URL slug (auto-derived from name if omitted)"),
|
|
502
|
+
network: z5.string().optional().describe("Network (e.g. mainnet, testnet)"),
|
|
503
|
+
nodeRpc: z5.string().optional().describe("Custom node RPC URL")
|
|
504
|
+
}, async ({ name, slug, network, nodeRpc }) => jsonResponse(await clientProvider().projects.create({
|
|
505
|
+
name,
|
|
506
|
+
slug,
|
|
507
|
+
network,
|
|
508
|
+
nodeRpc
|
|
509
|
+
})));
|
|
510
|
+
defineTool(server, "project_update", "Update a project. `slug` selects the project; `newSlug` renames it. Requires an account-level (owner) API key.", {
|
|
511
|
+
slug: z5.string().describe("Slug of the project to update"),
|
|
512
|
+
newSlug: z5.string().optional().describe("New slug (renames the project)"),
|
|
513
|
+
name: z5.string().optional().describe("New display name"),
|
|
514
|
+
network: z5.string().optional().describe("Network (e.g. mainnet, testnet)"),
|
|
515
|
+
nodeRpc: z5.string().optional().describe("Custom node RPC URL"),
|
|
516
|
+
settings: z5.record(z5.string(), z5.unknown()).optional().describe("Arbitrary project settings to merge")
|
|
517
|
+
}, async ({ slug, newSlug, name, network, nodeRpc, settings }) => jsonResponse(await clientProvider().projects.update(slug, {
|
|
518
|
+
...newSlug !== undefined ? { slug: newSlug } : {},
|
|
519
|
+
...name !== undefined ? { name } : {},
|
|
520
|
+
...network !== undefined ? { network } : {},
|
|
521
|
+
...nodeRpc !== undefined ? { nodeRpc } : {},
|
|
522
|
+
...settings !== undefined ? { settings } : {}
|
|
523
|
+
})));
|
|
524
|
+
defineTool(server, "project_delete", "Delete a project permanently. The account's last remaining project cannot be deleted.", { slug: z5.string().describe("Project slug") }, async ({ slug }) => jsonResponse(await clientProvider().projects.delete(slug)));
|
|
525
|
+
defineTool(server, "project_team_list", "List a project's team members and pending invitations.", { slug: z5.string().describe("Project slug") }, async ({ slug }) => jsonResponse(await clientProvider().projects.team(slug)));
|
|
526
|
+
}
|
|
527
|
+
|
|
411
528
|
// src/tools/scaffold.ts
|
|
412
529
|
import { generateSubgraphCode } from "@secondlayer/scaffold";
|
|
413
|
-
import { z as
|
|
530
|
+
import { z as z6 } from "zod/v4";
|
|
414
531
|
var API_BASE = process.env.SECONDLAYER_API_URL || "https://api.secondlayer.tools";
|
|
415
532
|
async function fetchAbi(contractId) {
|
|
416
533
|
const res = await fetch(`${API_BASE}/api/node/contracts/${contractId}/abi`, {
|
|
@@ -429,17 +546,17 @@ async function fetchAbi(contractId) {
|
|
|
429
546
|
}
|
|
430
547
|
function registerScaffoldTools(server) {
|
|
431
548
|
defineTool(server, "scaffold_from_contract", "Generate a subgraph scaffold from a deployed Stacks contract. Fetches the ABI automatically.", {
|
|
432
|
-
contractId:
|
|
433
|
-
subgraphName:
|
|
549
|
+
contractId: z6.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
|
|
550
|
+
subgraphName: z6.string().optional().describe("Override the subgraph name (defaults to contract name)")
|
|
434
551
|
}, async ({ contractId, subgraphName }) => {
|
|
435
552
|
const { functions, maps } = await fetchAbi(contractId);
|
|
436
553
|
const code = generateSubgraphCode(contractId, functions, subgraphName, maps);
|
|
437
554
|
return { content: [{ type: "text", text: code }] };
|
|
438
555
|
});
|
|
439
556
|
defineTool(server, "scaffold_from_abi", "Generate a subgraph scaffold from a provided ABI JSON. Use when you already have the ABI.", {
|
|
440
|
-
abi:
|
|
441
|
-
contractId:
|
|
442
|
-
subgraphName:
|
|
557
|
+
abi: z6.string().describe("ABI JSON string (the full contract ABI object)"),
|
|
558
|
+
contractId: z6.string().describe("Fully qualified contract ID"),
|
|
559
|
+
subgraphName: z6.string().optional().describe("Override the subgraph name")
|
|
443
560
|
}, async ({ abi, contractId, subgraphName }) => {
|
|
444
561
|
let parsed;
|
|
445
562
|
try {
|
|
@@ -457,9 +574,9 @@ function registerScaffoldTools(server) {
|
|
|
457
574
|
|
|
458
575
|
// src/tools/streams.ts
|
|
459
576
|
import { AuthError } from "@secondlayer/sdk";
|
|
460
|
-
import { DECODED_EVENT_TYPES as
|
|
461
|
-
import { z as
|
|
462
|
-
var STREAMS_EVENT_TYPES =
|
|
577
|
+
import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES3 } from "@secondlayer/shared";
|
|
578
|
+
import { z as z7 } from "zod/v4";
|
|
579
|
+
var STREAMS_EVENT_TYPES = DECODED_EVENT_TYPES3;
|
|
463
580
|
async function withStreamsAuthHint(fn) {
|
|
464
581
|
try {
|
|
465
582
|
return await fn();
|
|
@@ -473,28 +590,28 @@ async function withStreamsAuthHint(fn) {
|
|
|
473
590
|
function registerStreamsTools(server, clientProvider = getClient) {
|
|
474
591
|
defineTool(server, "streams_tip", "Get the current Streams chain tip (latest processed block + lag). Streams requires an API key (SL_API_KEY).", {}, async () => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.tip())));
|
|
475
592
|
defineTool(server, "streams_events", "List raw chain events from the Streams firehose. Streams requires an API key (SL_API_KEY). Filter by event types, principals, contract, asset, or block range; page with cursor.", {
|
|
476
|
-
types:
|
|
477
|
-
notTypes:
|
|
478
|
-
contractId:
|
|
479
|
-
sender:
|
|
480
|
-
recipient:
|
|
481
|
-
assetIdentifier:
|
|
482
|
-
fromHeight:
|
|
483
|
-
toHeight:
|
|
484
|
-
cursor:
|
|
485
|
-
limit:
|
|
593
|
+
types: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
|
|
594
|
+
notTypes: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude (applied after types)"),
|
|
595
|
+
contractId: z7.string().optional().describe("Filter by contract id"),
|
|
596
|
+
sender: z7.string().optional().describe("Filter by sender principal"),
|
|
597
|
+
recipient: z7.string().optional().describe("Filter by recipient principal"),
|
|
598
|
+
assetIdentifier: z7.string().optional().describe("Filter by asset identifier"),
|
|
599
|
+
fromHeight: z7.number().optional().describe("Start block height (inclusive)"),
|
|
600
|
+
toHeight: z7.number().optional().describe("End block height (inclusive)"),
|
|
601
|
+
cursor: z7.string().optional().describe("Opaque cursor from a prior response"),
|
|
602
|
+
limit: z7.number().optional().describe("Max events for this page")
|
|
486
603
|
}, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.events.list(params))));
|
|
487
604
|
defineTool(server, "streams_consume", "Consume a bounded, reorg-aware run of Streams events from a cursor. Walks up to maxPages pages and returns the events, any reorgs observed, and a resume `cursor` to pass back as fromCursor next call — the agent-native consume/resume primitive. Streams requires an API key (SL_API_KEY).", {
|
|
488
|
-
fromCursor:
|
|
489
|
-
types:
|
|
490
|
-
notTypes:
|
|
491
|
-
contractId:
|
|
492
|
-
sender:
|
|
493
|
-
recipient:
|
|
494
|
-
assetIdentifier:
|
|
495
|
-
batchSize:
|
|
496
|
-
maxPages:
|
|
497
|
-
finalizedOnly:
|
|
605
|
+
fromCursor: z7.string().optional().describe("Resume cursor from a prior consume (omit to start at genesis)"),
|
|
606
|
+
types: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
|
|
607
|
+
notTypes: z7.array(z7.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude"),
|
|
608
|
+
contractId: z7.string().optional().describe("Filter by contract id"),
|
|
609
|
+
sender: z7.string().optional().describe("Filter by sender principal"),
|
|
610
|
+
recipient: z7.string().optional().describe("Filter by recipient principal"),
|
|
611
|
+
assetIdentifier: z7.string().optional().describe("Filter by asset identifier"),
|
|
612
|
+
batchSize: z7.number().optional().describe("Events per page (1–1000, default 100)"),
|
|
613
|
+
maxPages: z7.number().optional().describe("Max pages to walk this call (1–20, default 5)"),
|
|
614
|
+
finalizedOnly: z7.boolean().optional().describe("Only emit finalized events; skip reorg handling")
|
|
498
615
|
}, async (params) => withStreamsAuthHint(async () => {
|
|
499
616
|
const maxPages = Math.min(Math.max(params.maxPages ?? 5, 1), 20);
|
|
500
617
|
const batchSize = Math.min(Math.max(params.batchSize ?? 100, 1), 1000);
|
|
@@ -526,26 +643,28 @@ function registerStreamsTools(server, clientProvider = getClient) {
|
|
|
526
643
|
pages: result.pages
|
|
527
644
|
});
|
|
528
645
|
}));
|
|
529
|
-
defineTool(server, "streams_event_by_txid", "List all Streams events emitted by a single transaction. Streams requires an API key (SL_API_KEY).", { txId:
|
|
646
|
+
defineTool(server, "streams_event_by_txid", "List all Streams events emitted by a single transaction. Streams requires an API key (SL_API_KEY).", { txId: z7.string().describe("Transaction id (0x… hash)") }, async ({ txId }) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.events.byTxId(txId))));
|
|
530
647
|
defineTool(server, "streams_block_events", "List all Streams events in a single block, by height or block hash. Streams requires an API key (SL_API_KEY).", {
|
|
531
|
-
heightOrHash:
|
|
648
|
+
heightOrHash: z7.string().describe("Block height (digits) or block hash (0x… string)")
|
|
532
649
|
}, async ({ heightOrHash }) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.blocks.events(/^\d+$/.test(heightOrHash) ? Number(heightOrHash) : heightOrHash))));
|
|
533
650
|
defineTool(server, "streams_reorgs", "List chain reorgs observed by Streams since a cursor. Streams requires an API key (SL_API_KEY).", {
|
|
534
|
-
since:
|
|
535
|
-
limit:
|
|
651
|
+
since: z7.string().describe("Cursor to list reorgs since (block:index or ISO timestamp)"),
|
|
652
|
+
limit: z7.number().optional().describe("Max reorgs to return")
|
|
536
653
|
}, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.reorgs.list(params))));
|
|
537
|
-
defineTool(server, "streams_canonical", "Get the canonical block at a given height from Streams (height + hashes + is_canonical). Streams requires an API key (SL_API_KEY).", { height:
|
|
654
|
+
defineTool(server, "streams_canonical", "Get the canonical block at a given height from Streams (height + hashes + is_canonical). Streams requires an API key (SL_API_KEY).", { height: z7.number().describe("Block height") }, async ({ height }) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.canonical(height))));
|
|
538
655
|
defineTool(server, "streams_usage", "Your own Streams consumption (events today + this month) and tier limits (rate limit, retention). Streams requires an API key (SL_API_KEY).", {}, async () => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.usage())));
|
|
656
|
+
defineTool(server, "streams_dumps", "List the Streams bulk parquet dumps manifest — coverage range, latest_finalized_cursor, and per-file metadata (block range, row count, size, sha256, signed URL). This is the cold backfill path for downloading all raw data; fetch the file URLs directly (e.g. with DuckDB). Requires the dumps base URL to be configured (SL_STREAMS_DUMPS_URL).", {}, async () => jsonResponse(await clientProvider().streams.dumps.list()));
|
|
539
657
|
}
|
|
540
658
|
|
|
541
659
|
// src/tools/subgraphs.ts
|
|
542
660
|
import { bundleSubgraphCode } from "@secondlayer/bundler";
|
|
661
|
+
import { ByoBreakingChangeError } from "@secondlayer/sdk";
|
|
543
662
|
import {
|
|
544
663
|
generateDrizzleSchema,
|
|
545
664
|
generateKyselySchema,
|
|
546
665
|
generatePrismaSchema
|
|
547
666
|
} from "@secondlayer/subgraphs";
|
|
548
|
-
import { z as
|
|
667
|
+
import { z as z8 } from "zod/v4";
|
|
549
668
|
function registerSubgraphTools(server, clientProvider = getClient) {
|
|
550
669
|
defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only.", {}, async () => {
|
|
551
670
|
const { data } = await clientProvider().subgraphs.list();
|
|
@@ -558,16 +677,16 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
558
677
|
]
|
|
559
678
|
};
|
|
560
679
|
});
|
|
561
|
-
defineTool(server, "subgraphs_get", "Get full details of a subgraph including schema, health, and table columns.", { name:
|
|
680
|
+
defineTool(server, "subgraphs_get", "Get full details of a subgraph including schema, health, and table columns.", { name: z8.string().describe("Subgraph name") }, async ({ name }) => {
|
|
562
681
|
const detail = await clientProvider().subgraphs.get(name);
|
|
563
682
|
return {
|
|
564
683
|
content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
|
|
565
684
|
};
|
|
566
685
|
});
|
|
567
686
|
defineTool(server, "subgraphs_spec", "Get generated API documentation for a subgraph. Defaults to compact agent schema; supports OpenAPI JSON and Markdown.", {
|
|
568
|
-
name:
|
|
569
|
-
format:
|
|
570
|
-
serverUrl:
|
|
687
|
+
name: z8.string().describe("Subgraph name"),
|
|
688
|
+
format: z8.enum(["agent", "openapi", "markdown"]).optional().describe("Spec format to return. Defaults to agent."),
|
|
689
|
+
serverUrl: z8.string().optional().describe("Override the server URL embedded in generated docs.")
|
|
571
690
|
}, async ({ name, format = "agent", serverUrl }) => {
|
|
572
691
|
const options = serverUrl ? { serverUrl } : undefined;
|
|
573
692
|
const spec = format === "openapi" ? await clientProvider().subgraphs.openapi(name, options) : format === "markdown" ? await clientProvider().subgraphs.markdown(name, options) : await clientProvider().subgraphs.schema(name, options);
|
|
@@ -580,16 +699,16 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
580
699
|
]
|
|
581
700
|
};
|
|
582
701
|
});
|
|
583
|
-
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.', {
|
|
584
|
-
name:
|
|
585
|
-
table:
|
|
586
|
-
filters:
|
|
587
|
-
sort:
|
|
588
|
-
order:
|
|
589
|
-
limit:
|
|
590
|
-
offset:
|
|
591
|
-
fields:
|
|
592
|
-
count:
|
|
702
|
+
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. To TAIL new rows (no streaming over MCP): sort=_id, order=desc for the latest, then poll forward with the filter {"_id.gt": "<last _id seen>"}, order=asc. Fetch one row by id with {"_id": "<id>"}.', {
|
|
703
|
+
name: z8.string().describe("Subgraph name"),
|
|
704
|
+
table: z8.string().describe("Table name"),
|
|
705
|
+
filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
|
|
706
|
+
sort: z8.string().optional().describe("Column to sort by"),
|
|
707
|
+
order: z8.enum(["asc", "desc"]).optional().describe("Sort order"),
|
|
708
|
+
limit: z8.number().max(200).optional().describe("Max rows (default 50, max 200)"),
|
|
709
|
+
offset: z8.number().optional().describe("Offset for pagination"),
|
|
710
|
+
fields: z8.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
|
|
711
|
+
count: z8.boolean().optional().describe("If true, return row count instead of rows")
|
|
593
712
|
}, async ({
|
|
594
713
|
name,
|
|
595
714
|
table,
|
|
@@ -622,14 +741,14 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
622
741
|
};
|
|
623
742
|
});
|
|
624
743
|
defineTool(server, "subgraphs_aggregate", `Compute scalar aggregates over a subgraph table's filtered rows. Supports count, countDistinct, and sum/min/max (numeric columns only — uint/int plus the system _block_height). Filters use the same grammar as subgraphs_query (e.g. {"amount.gte": "1000"}). sum/min/max come back as lossless strings; counts as numbers. With no aggregate requested, returns the row count.`, {
|
|
625
|
-
name:
|
|
626
|
-
table:
|
|
627
|
-
filters:
|
|
628
|
-
count:
|
|
629
|
-
countDistinct:
|
|
630
|
-
sum:
|
|
631
|
-
min:
|
|
632
|
-
max:
|
|
744
|
+
name: z8.string().describe("Subgraph name"),
|
|
745
|
+
table: z8.string().describe("Table name"),
|
|
746
|
+
filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
|
|
747
|
+
count: z8.boolean().optional().describe("Include COUNT(*) of matching rows"),
|
|
748
|
+
countDistinct: z8.array(z8.string()).optional().describe("Columns to count distinct values of"),
|
|
749
|
+
sum: z8.array(z8.string()).optional().describe("Numeric columns to sum (lossless string result)"),
|
|
750
|
+
min: z8.array(z8.string()).optional().describe("Numeric columns to take the minimum of"),
|
|
751
|
+
max: z8.array(z8.string()).optional().describe("Numeric columns to take the maximum of")
|
|
633
752
|
}, async ({ name, table, filters, count, countDistinct, sum, min, max }) => {
|
|
634
753
|
const result = await clientProvider().subgraphs.queryTableAggregate(name, table, { filters, count, countDistinct, sum, min, max });
|
|
635
754
|
return {
|
|
@@ -637,9 +756,9 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
637
756
|
};
|
|
638
757
|
});
|
|
639
758
|
defineTool(server, "subgraphs_reindex", "Reindex a subgraph from a specific block range. Returns an operationId — poll subgraphs_operation to track progress to completion.", {
|
|
640
|
-
name:
|
|
641
|
-
fromBlock:
|
|
642
|
-
toBlock:
|
|
759
|
+
name: z8.string().describe("Subgraph name"),
|
|
760
|
+
fromBlock: z8.number().optional().describe("Start block (defaults to beginning)"),
|
|
761
|
+
toBlock: z8.number().optional().describe("End block (defaults to latest)")
|
|
643
762
|
}, async ({ name, fromBlock, toBlock }) => {
|
|
644
763
|
const result = await clientProvider().subgraphs.reindex(name, {
|
|
645
764
|
fromBlock,
|
|
@@ -650,48 +769,100 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
650
769
|
};
|
|
651
770
|
});
|
|
652
771
|
defineTool(server, "subgraphs_operation", "Check reindex/backfill progress. With operationId, returns that operation's status (poll until status is completed/failed/cancelled); without it, lists recent operations for the subgraph.", {
|
|
653
|
-
name:
|
|
654
|
-
operationId:
|
|
772
|
+
name: z8.string().describe("Subgraph name"),
|
|
773
|
+
operationId: z8.string().optional().describe("Operation id from reindex/backfill/stop; omit to list recent operations")
|
|
655
774
|
}, async ({ name, operationId }) => {
|
|
656
775
|
const result = operationId ? await clientProvider().subgraphs.getOperation(name, operationId) : await clientProvider().subgraphs.operations(name);
|
|
657
776
|
return {
|
|
658
777
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
659
778
|
};
|
|
660
779
|
});
|
|
661
|
-
defineTool(server, "
|
|
662
|
-
|
|
663
|
-
|
|
780
|
+
defineTool(server, "subgraphs_backfill", "Backfill a subgraph over a block range. Non-destructive forward fill (does not drop existing data) — unlike subgraphs_reindex, and the only data-fill path for BYO subgraphs (reindex is blocked there). Both blocks required. Returns an operationId — poll subgraphs_operation to track progress.", {
|
|
781
|
+
name: z8.string().describe("Subgraph name"),
|
|
782
|
+
fromBlock: z8.number().int().nonnegative().describe("Start block (inclusive)"),
|
|
783
|
+
toBlock: z8.number().int().nonnegative().describe("End block (inclusive)")
|
|
784
|
+
}, async ({ name, fromBlock, toBlock }) => {
|
|
785
|
+
const result = await clientProvider().subgraphs.backfill(name, {
|
|
786
|
+
fromBlock,
|
|
787
|
+
toBlock
|
|
788
|
+
});
|
|
789
|
+
return {
|
|
790
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
791
|
+
};
|
|
664
792
|
});
|
|
665
|
-
defineTool(server, "
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
793
|
+
defineTool(server, "subgraphs_stop", "Cancel an in-flight reindex or backfill operation for a subgraph. Returns the stop request status; poll subgraphs_operation to confirm it reaches a terminal state.", { name: z8.string().describe("Subgraph name") }, async ({ name }) => {
|
|
794
|
+
const result = await clientProvider().subgraphs.stop(name);
|
|
795
|
+
return {
|
|
796
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
797
|
+
};
|
|
798
|
+
});
|
|
799
|
+
defineTool(server, "subgraphs_gaps", "List indexing gaps (missing block ranges) for a subgraph. Each gap reports start/end/size, reason, and detected/resolved timestamps. Feed an unresolved gap's range into subgraphs_backfill to fill it. Defaults to unresolved gaps.", {
|
|
800
|
+
name: z8.string().describe("Subgraph name"),
|
|
801
|
+
limit: z8.number().int().positive().optional().describe("Max gaps to return"),
|
|
802
|
+
offset: z8.number().int().nonnegative().optional().describe("Pagination offset"),
|
|
803
|
+
resolved: z8.boolean().optional().describe("Filter by resolved state (omit for unresolved only)")
|
|
804
|
+
}, async ({ name, limit, offset, resolved }) => {
|
|
805
|
+
const result = await clientProvider().subgraphs.gaps(name, {
|
|
806
|
+
limit,
|
|
807
|
+
offset,
|
|
808
|
+
resolved
|
|
679
809
|
});
|
|
680
810
|
return {
|
|
681
811
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
682
812
|
};
|
|
683
813
|
});
|
|
684
|
-
defineTool(server, "
|
|
814
|
+
defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z8.string().describe("Subgraph name") }, async ({ name }) => {
|
|
815
|
+
const result = await clientProvider().subgraphs.delete(name);
|
|
816
|
+
return { content: [{ type: "text", text: result.message }] };
|
|
817
|
+
});
|
|
818
|
+
defineTool(server, "subgraphs_deploy", "Deploy a subgraph from TypeScript code. Pass the full defineSubgraph() source — it will be bundled, validated, and deployed. Optional startBlock overrides the source definition for this deploy. Set dryRun to validate and preview the schema/DDL without writing anything. Set databaseUrl to deploy to your own Postgres (BYO data plane) — the server verifies the connection first; with dryRun it returns the DDL + grant script. A breaking BYO schema change is refused and returns a migration plan (drop + rebuild DDL) instead of deploying. Call `subgraphs_reindex` separately if you need a forced reindex.", {
|
|
819
|
+
code: z8.string().describe("TypeScript source code containing a defineSubgraph() call"),
|
|
820
|
+
startBlock: z8.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy"),
|
|
821
|
+
databaseUrl: z8.string().optional().describe("BYO data plane: Postgres connection string to host the subgraph's schema and rows in your own database"),
|
|
822
|
+
dryRun: z8.boolean().optional().describe("Validate and preview the deploy (schema/DDL, BYO connection) without writing changes")
|
|
823
|
+
}, async ({ code, startBlock, databaseUrl, dryRun }) => {
|
|
824
|
+
const bundled = await bundleSubgraphCode(code);
|
|
825
|
+
try {
|
|
826
|
+
const result = await clientProvider().subgraphs.deploy({
|
|
827
|
+
name: bundled.name,
|
|
828
|
+
version: bundled.version,
|
|
829
|
+
description: bundled.description,
|
|
830
|
+
sources: bundled.sources,
|
|
831
|
+
schema: bundled.schema,
|
|
832
|
+
handlerCode: bundled.handlerCode,
|
|
833
|
+
sourceCode: code,
|
|
834
|
+
...startBlock !== undefined ? { startBlock } : {},
|
|
835
|
+
...databaseUrl !== undefined ? { databaseUrl } : {},
|
|
836
|
+
...dryRun !== undefined ? { dryRun } : {}
|
|
837
|
+
});
|
|
838
|
+
return {
|
|
839
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
840
|
+
};
|
|
841
|
+
} catch (err) {
|
|
842
|
+
if (err instanceof ByoBreakingChangeError) {
|
|
843
|
+
return {
|
|
844
|
+
content: [
|
|
845
|
+
{
|
|
846
|
+
type: "text",
|
|
847
|
+
text: JSON.stringify({ byoBreakingChange: true, ...err.details }, null, 2)
|
|
848
|
+
}
|
|
849
|
+
]
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
throw err;
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
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: z8.string().describe("Subgraph name") }, async ({ name }) => {
|
|
685
856
|
const source = await clientProvider().subgraphs.getSource(name);
|
|
686
857
|
return {
|
|
687
858
|
content: [{ type: "text", text: JSON.stringify(source, null, 2) }]
|
|
688
859
|
};
|
|
689
860
|
});
|
|
690
861
|
defineTool(server, "subgraphs_codegen", "Generate a typed ORM schema (Prisma, Drizzle, or Kysely) for a subgraph's tables so they can be queried from a BYO database. Pass either `code` (defineSubgraph source) or `name` (a deployed subgraph — its captured source is used). Returns the schema as text.", {
|
|
691
|
-
code:
|
|
692
|
-
name:
|
|
693
|
-
target:
|
|
694
|
-
schemaName:
|
|
862
|
+
code: z8.string().optional().describe("defineSubgraph() source (mutually exclusive with name)"),
|
|
863
|
+
name: z8.string().optional().describe("Deployed subgraph name (mutually exclusive with code)"),
|
|
864
|
+
target: z8.enum(["prisma", "drizzle", "kysely"]).optional().describe("ORM target (default prisma)"),
|
|
865
|
+
schemaName: z8.string().optional().describe("Postgres schema name (defaults to subgraph_<name>)")
|
|
695
866
|
}, async ({ code, name, target = "prisma", schemaName }) => {
|
|
696
867
|
if (code && name || !code && !name) {
|
|
697
868
|
throw new Error("Provide exactly one of `code` or `name`.");
|
|
@@ -720,7 +891,7 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
720
891
|
|
|
721
892
|
// src/tools/subscriptions.ts
|
|
722
893
|
import { CHAIN_TRIGGER_TYPES } from "@secondlayer/shared";
|
|
723
|
-
import { z as
|
|
894
|
+
import { z as z9 } from "zod/v4";
|
|
724
895
|
function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
725
896
|
defineTool(server, "subscriptions_list", "List all subscriptions for the current account. Returns summary fields (no secrets).", {}, async () => {
|
|
726
897
|
const { data } = await clientProvider().subscriptions.list();
|
|
@@ -728,34 +899,34 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
|
728
899
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
729
900
|
};
|
|
730
901
|
});
|
|
731
|
-
defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id:
|
|
902
|
+
defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id: z9.string().describe("Subscription id") }, async ({ id }) => {
|
|
732
903
|
const detail = await clientProvider().subscriptions.get(id);
|
|
733
904
|
return {
|
|
734
905
|
content: [{ type: "text", text: JSON.stringify(detail, null, 2) }]
|
|
735
906
|
};
|
|
736
907
|
});
|
|
737
908
|
defineTool(server, "subscriptions_create", "Create a subscription. Two kinds (mutually exclusive): a SUBGRAPH subscription fires on a subgraph table's rows (set subgraphName + tableName + optional filter); a CHAIN subscription fires on raw chain events with no subgraph (set triggers). Returns `signingSecret` ONCE — forward it to the user so they can wire it into their receiver.", {
|
|
738
|
-
name:
|
|
739
|
-
subgraphName:
|
|
740
|
-
tableName:
|
|
741
|
-
triggers:
|
|
742
|
-
type:
|
|
743
|
-
contractId:
|
|
744
|
-
functionName:
|
|
745
|
-
caller:
|
|
746
|
-
sender:
|
|
747
|
-
recipient:
|
|
748
|
-
assetIdentifier:
|
|
749
|
-
deployer:
|
|
750
|
-
contractName:
|
|
751
|
-
topic:
|
|
752
|
-
lockedAddress:
|
|
753
|
-
trait:
|
|
754
|
-
minAmount:
|
|
755
|
-
maxAmount:
|
|
756
|
-
})).optional().describe("Chain triggers (chain subscription) — provide INSTEAD of subgraphName/tableName. Each targets a raw chain event/tx; string fields accept `*` wildcards, `trait` scopes to a SIP/trait. Forward-looking: starts at chain tip, no backfill."),
|
|
757
|
-
url:
|
|
758
|
-
format:
|
|
909
|
+
name: z9.string().describe("Human-readable name, unique per account"),
|
|
910
|
+
subgraphName: z9.string().optional().describe("Subgraph to subscribe to (subgraph subscription)"),
|
|
911
|
+
tableName: z9.string().optional().describe("Table within the subgraph (subgraph subscription)"),
|
|
912
|
+
triggers: z9.array(z9.object({
|
|
913
|
+
type: z9.enum(CHAIN_TRIGGER_TYPES),
|
|
914
|
+
contractId: z9.string().optional(),
|
|
915
|
+
functionName: z9.string().optional(),
|
|
916
|
+
caller: z9.string().optional(),
|
|
917
|
+
sender: z9.string().optional(),
|
|
918
|
+
recipient: z9.string().optional(),
|
|
919
|
+
assetIdentifier: z9.string().optional(),
|
|
920
|
+
deployer: z9.string().optional(),
|
|
921
|
+
contractName: z9.string().optional(),
|
|
922
|
+
topic: z9.string().optional(),
|
|
923
|
+
lockedAddress: z9.string().optional(),
|
|
924
|
+
trait: z9.string().optional(),
|
|
925
|
+
minAmount: z9.union([z9.string(), z9.number()]).optional(),
|
|
926
|
+
maxAmount: z9.union([z9.string(), z9.number()]).optional()
|
|
927
|
+
})).optional().describe("Chain triggers (chain subscription) — provide INSTEAD of subgraphName/tableName. Each targets a raw chain event/tx; string fields accept `*` wildcards, `trait` scopes to a SIP/trait. Per-type accepted fields: see the secondlayer://chain-triggers resource. Forward-looking: starts at chain tip, no backfill."),
|
|
928
|
+
url: z9.string().describe("Webhook URL"),
|
|
929
|
+
format: z9.enum([
|
|
759
930
|
"standard-webhooks",
|
|
760
931
|
"inngest",
|
|
761
932
|
"trigger",
|
|
@@ -763,19 +934,22 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
|
763
934
|
"cloudevents",
|
|
764
935
|
"raw"
|
|
765
936
|
]).optional().describe("Wire format (default standard-webhooks)"),
|
|
766
|
-
runtime:
|
|
767
|
-
filter:
|
|
937
|
+
runtime: z9.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
|
|
938
|
+
filter: z9.record(z9.string(), z9.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}'),
|
|
939
|
+
authConfig: z9.record(z9.string(), z9.unknown()).optional().describe('Receiver auth sent with each delivery, e.g. {"type": "bearer", "token": "..."}')
|
|
768
940
|
}, async (input) => {
|
|
769
941
|
const res = await clientProvider().subscriptions.create(input);
|
|
770
942
|
return {
|
|
771
943
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
772
944
|
};
|
|
773
945
|
});
|
|
774
|
-
defineTool(server, "subscriptions_update", "Patch a subscription (url, filter, format, runtime, retry, timeout, concurrency).", {
|
|
775
|
-
id:
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
946
|
+
defineTool(server, "subscriptions_update", "Patch a subscription (name, url, filter, authConfig, format, runtime, retry, timeout, concurrency).", {
|
|
947
|
+
id: z9.string(),
|
|
948
|
+
name: z9.string().optional().describe("Rename the subscription"),
|
|
949
|
+
url: z9.string().optional(),
|
|
950
|
+
filter: z9.record(z9.string(), z9.unknown()).optional(),
|
|
951
|
+
authConfig: z9.record(z9.string(), z9.unknown()).optional().describe("Receiver auth sent with each delivery (bearer/etc.)"),
|
|
952
|
+
format: z9.enum([
|
|
779
953
|
"standard-webhooks",
|
|
780
954
|
"inngest",
|
|
781
955
|
"trigger",
|
|
@@ -783,75 +957,77 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
|
783
957
|
"cloudevents",
|
|
784
958
|
"raw"
|
|
785
959
|
]).optional(),
|
|
786
|
-
runtime:
|
|
787
|
-
maxRetries:
|
|
788
|
-
timeoutMs:
|
|
789
|
-
concurrency:
|
|
960
|
+
runtime: z9.enum(["inngest", "trigger", "cloudflare", "node"]).nullable().optional(),
|
|
961
|
+
maxRetries: z9.number().int().min(0).optional(),
|
|
962
|
+
timeoutMs: z9.number().int().min(100).optional(),
|
|
963
|
+
concurrency: z9.number().int().min(1).optional()
|
|
790
964
|
}, async ({ id, ...patch }) => {
|
|
791
965
|
const res = await clientProvider().subscriptions.update(id, patch);
|
|
792
966
|
return {
|
|
793
967
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
794
968
|
};
|
|
795
969
|
});
|
|
796
|
-
defineTool(server, "subscriptions_pause", "Pause a subscription. Pending rows remain queued until resumed.", { id:
|
|
970
|
+
defineTool(server, "subscriptions_pause", "Pause a subscription. Pending rows remain queued until resumed.", { id: z9.string() }, async ({ id }) => {
|
|
797
971
|
const res = await clientProvider().subscriptions.pause(id);
|
|
798
972
|
return {
|
|
799
973
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
800
974
|
};
|
|
801
975
|
});
|
|
802
|
-
defineTool(server, "subscriptions_resume", "Resume a paused or circuit-open subscription and reset circuit failures.", { id:
|
|
976
|
+
defineTool(server, "subscriptions_resume", "Resume a paused or circuit-open subscription and reset circuit failures.", { id: z9.string() }, async ({ id }) => {
|
|
803
977
|
const res = await clientProvider().subscriptions.resume(id);
|
|
804
978
|
return {
|
|
805
979
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
806
980
|
};
|
|
807
981
|
});
|
|
808
|
-
defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id:
|
|
982
|
+
defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id: z9.string() }, async ({ id }) => {
|
|
809
983
|
const res = await clientProvider().subscriptions.delete(id);
|
|
810
984
|
return {
|
|
811
985
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
812
986
|
};
|
|
813
987
|
});
|
|
814
|
-
defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription signing secret. Returns the new plaintext secret once.", { id:
|
|
988
|
+
defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription signing secret. Returns the new plaintext secret once.", { id: z9.string() }, async ({ id }) => {
|
|
815
989
|
const res = await clientProvider().subscriptions.rotateSecret(id);
|
|
816
990
|
return {
|
|
817
991
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
818
992
|
};
|
|
819
993
|
});
|
|
820
|
-
defineTool(server, "subscriptions_test", "Send a one-off test webhook to a subscription's URL (built for its format, SSRF-guarded). Logged as a delivery row — check subscriptions_recent_deliveries. Returns {ok, statusCode, error, durationMs, deliveryId}.", { id:
|
|
994
|
+
defineTool(server, "subscriptions_test", "Send a one-off test webhook to a subscription's URL (built for its format, SSRF-guarded). Logged as a delivery row — check subscriptions_recent_deliveries. Returns {ok, statusCode, error, durationMs, deliveryId}.", { id: z9.string().describe("Subscription id") }, async ({ id }) => {
|
|
821
995
|
const res = await clientProvider().subscriptions.test(id);
|
|
822
996
|
return {
|
|
823
997
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
824
998
|
};
|
|
825
999
|
});
|
|
826
|
-
defineTool(server, "subscriptions_replay", "Replay a block range for a subscription. Replays run at 10% of batch capacity — use sparingly.", {
|
|
827
|
-
id:
|
|
828
|
-
fromBlock:
|
|
829
|
-
toBlock:
|
|
830
|
-
|
|
1000
|
+
defineTool(server, "subscriptions_replay", "Replay a block range for a subscription. Replays run at 10% of batch capacity — use sparingly. Pass `force` (a short idempotency suffix) to re-run a range that was already replayed.", {
|
|
1001
|
+
id: z9.string(),
|
|
1002
|
+
fromBlock: z9.number().int().nonnegative(),
|
|
1003
|
+
toBlock: z9.number().int().nonnegative(),
|
|
1004
|
+
force: z9.string().optional().describe("Idempotency suffix to force a duplicate replay of the range")
|
|
1005
|
+
}, async ({ id, fromBlock, toBlock, force }) => {
|
|
831
1006
|
const res = await clientProvider().subscriptions.replay(id, {
|
|
832
1007
|
fromBlock,
|
|
833
|
-
toBlock
|
|
1008
|
+
toBlock,
|
|
1009
|
+
...force !== undefined ? { force } : {}
|
|
834
1010
|
});
|
|
835
1011
|
return {
|
|
836
1012
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
837
1013
|
};
|
|
838
1014
|
});
|
|
839
|
-
defineTool(server, "subscriptions_dead", "Return the last 100 dead-letter outbox rows for a subscription.", { id:
|
|
1015
|
+
defineTool(server, "subscriptions_dead", "Return the last 100 dead-letter outbox rows for a subscription.", { id: z9.string() }, async ({ id }) => {
|
|
840
1016
|
const { data } = await clientProvider().subscriptions.dead(id);
|
|
841
1017
|
return {
|
|
842
1018
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
843
1019
|
};
|
|
844
1020
|
});
|
|
845
1021
|
defineTool(server, "subscriptions_requeue_dead", "Requeue one dead-letter outbox row for delivery retry.", {
|
|
846
|
-
id:
|
|
847
|
-
outboxId:
|
|
1022
|
+
id: z9.string(),
|
|
1023
|
+
outboxId: z9.string()
|
|
848
1024
|
}, async ({ id, outboxId }) => {
|
|
849
1025
|
const res = await clientProvider().subscriptions.requeueDead(id, outboxId);
|
|
850
1026
|
return {
|
|
851
1027
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
852
1028
|
};
|
|
853
1029
|
});
|
|
854
|
-
defineTool(server, "subscriptions_recent_deliveries", "Return the last 100 delivery attempts (attempt #, status code, duration, truncated response).", { id:
|
|
1030
|
+
defineTool(server, "subscriptions_recent_deliveries", "Return the last 100 delivery attempts (attempt #, status code, duration, truncated response).", { id: z9.string() }, async ({ id }) => {
|
|
855
1031
|
const { data } = await clientProvider().subscriptions.recentDeliveries(id);
|
|
856
1032
|
return {
|
|
857
1033
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
@@ -871,6 +1047,7 @@ function createServer() {
|
|
|
871
1047
|
registerSubgraphTools(server);
|
|
872
1048
|
registerSubscriptionTools(server);
|
|
873
1049
|
registerAccountTools(server);
|
|
1050
|
+
registerProjectTools(server);
|
|
874
1051
|
registerDatasetTools(server);
|
|
875
1052
|
registerIndexTools(server);
|
|
876
1053
|
registerStreamsTools(server);
|
|
@@ -882,5 +1059,5 @@ export {
|
|
|
882
1059
|
createServer
|
|
883
1060
|
};
|
|
884
1061
|
|
|
885
|
-
//# debugId=
|
|
1062
|
+
//# debugId=B30584194FF8002264756E2164756E21
|
|
886
1063
|
//# sourceMappingURL=index.js.map
|