@secondlayer/mcp 6.0.1 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/dist/bin-http.js +226 -140
- package/dist/bin-http.js.map +15 -14
- package/dist/bin.js +226 -140
- package/dist/bin.js.map +15 -14
- package/dist/index.js +226 -140
- package/dist/index.js.map +15 -14
- package/package.json +4 -4
package/dist/bin-http.js
CHANGED
|
@@ -19,10 +19,10 @@ import { filterFieldsByType } from "@secondlayer/subgraphs";
|
|
|
19
19
|
import { TYPE_MAP } from "@secondlayer/subgraphs/schema";
|
|
20
20
|
|
|
21
21
|
// src/lib/client.ts
|
|
22
|
-
import { SecondLayer } from "@secondlayer/sdk";
|
|
22
|
+
import { SecondLayer, resolveApiKey } from "@secondlayer/sdk";
|
|
23
23
|
var instance = null;
|
|
24
24
|
function readApiKey() {
|
|
25
|
-
return
|
|
25
|
+
return resolveApiKey();
|
|
26
26
|
}
|
|
27
27
|
function getClient() {
|
|
28
28
|
if (!instance) {
|
|
@@ -38,17 +38,35 @@ function getClient() {
|
|
|
38
38
|
}
|
|
39
39
|
return instance;
|
|
40
40
|
}
|
|
41
|
+
var keyHint = " — set INSTANCE_TOKEN from `sl init` for writes (SL_API_KEY is a legacy alias)";
|
|
42
|
+
async function apiRequest(method, path, body) {
|
|
43
|
+
const apiKey = readApiKey();
|
|
44
|
+
const baseUrl = process.env.SECONDLAYER_API_URL || process.env.SL_API_URL || "http://127.0.0.1:3800";
|
|
45
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
46
|
+
method,
|
|
47
|
+
headers: {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
50
|
+
},
|
|
51
|
+
body: body ? JSON.stringify(body) : undefined
|
|
52
|
+
});
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
const text = await res.text().catch(() => "");
|
|
55
|
+
const needsKey = !apiKey && (res.status === 401 || res.status === 403);
|
|
56
|
+
throw Object.assign(new Error((text || `HTTP ${res.status}`) + (needsKey ? keyHint : "")), { status: res.status });
|
|
57
|
+
}
|
|
58
|
+
if (res.status === 204)
|
|
59
|
+
return;
|
|
60
|
+
return res.json();
|
|
61
|
+
}
|
|
41
62
|
|
|
42
63
|
// src/lib/format.ts
|
|
43
64
|
function formatSubgraphSummary(s) {
|
|
44
|
-
const apiUrl = process.env.SECONDLAYER_API_URL || process.env.SL_API_URL || "http://127.0.0.1:3800";
|
|
45
65
|
return {
|
|
46
66
|
name: s.name,
|
|
47
67
|
status: s.status,
|
|
48
68
|
tables: Array.isArray(s.tables) ? s.tables : Object.keys(s.tables),
|
|
49
|
-
lastProcessedBlock: s.lastProcessedBlock
|
|
50
|
-
...s.visibility ? { visibility: s.visibility } : {},
|
|
51
|
-
...s.visibility === "public" ? { publicUrl: `${apiUrl}/v1/subgraphs/${s.name}` } : {}
|
|
69
|
+
lastProcessedBlock: s.lastProcessedBlock
|
|
52
70
|
};
|
|
53
71
|
}
|
|
54
72
|
function withCap(items, cap) {
|
|
@@ -76,7 +94,7 @@ var registeredToolNames = new Set;
|
|
|
76
94
|
function getRegisteredToolNames() {
|
|
77
95
|
return [...registeredToolNames];
|
|
78
96
|
}
|
|
79
|
-
function defineTool(server, name, description, schema, handler
|
|
97
|
+
function defineTool(server, name, description, schema, handler) {
|
|
80
98
|
const wrappedHandler = async (args) => {
|
|
81
99
|
try {
|
|
82
100
|
return await handler(args);
|
|
@@ -97,9 +115,6 @@ function defineTool(server, name, description, schema, handler, options = {}) {
|
|
|
97
115
|
};
|
|
98
116
|
registeredToolNames.add(name);
|
|
99
117
|
server.tool(name, description, schema, wrappedHandler);
|
|
100
|
-
for (const alias of options.aliases ?? []) {
|
|
101
|
-
server.tool(alias, `Deprecated alias for \`${name}\`. ${description}`, schema, wrappedHandler);
|
|
102
|
-
}
|
|
103
118
|
}
|
|
104
119
|
|
|
105
120
|
// src/resources.ts
|
|
@@ -131,12 +146,11 @@ var COLUMN_TYPES = [
|
|
|
131
146
|
];
|
|
132
147
|
var PRODUCT_BLURBS = {
|
|
133
148
|
index: "decoded events, transfers, contract calls, blocks, transactions",
|
|
134
|
-
streams: "
|
|
149
|
+
streams: "raw chain event firehose — cursor-paginated reads, tip/canonical/reorg checks, bulk parquet dumps",
|
|
135
150
|
contracts: "trait-based contract discovery and ABIs",
|
|
136
|
-
subgraphs: "author/deploy/query custom indexes",
|
|
151
|
+
subgraphs: "author/scaffold/deploy/query custom indexes",
|
|
137
152
|
subscriptions: "webhook delivery on subgraph rows or raw chain events",
|
|
138
153
|
account: "identity and self-provisioned API keys",
|
|
139
|
-
scaffold: "generate deploy-ready subgraph code from a deployed contract",
|
|
140
154
|
codegen: "ORM schemas for the tables you read"
|
|
141
155
|
};
|
|
142
156
|
var PRODUCT_ORDER = [
|
|
@@ -146,7 +160,6 @@ var PRODUCT_ORDER = [
|
|
|
146
160
|
"subgraphs",
|
|
147
161
|
"subscriptions",
|
|
148
162
|
"account",
|
|
149
|
-
"scaffold",
|
|
150
163
|
"codegen"
|
|
151
164
|
];
|
|
152
165
|
function buildCapabilities() {
|
|
@@ -172,17 +185,17 @@ function buildCapabilities() {
|
|
|
172
185
|
};
|
|
173
186
|
}
|
|
174
187
|
var READ_AUTH_TIERS = {
|
|
175
|
-
index: "
|
|
176
|
-
streams: "API
|
|
177
|
-
subgraphs: "
|
|
188
|
+
index: "keyless while the API is only reachable on loopback; the instance token is required once it is published past loopback",
|
|
189
|
+
streams: "keyless while the API is only reachable on loopback; the instance token is required once it is published past loopback",
|
|
190
|
+
subgraphs: "reads at /v1/subgraphs/<name>/<table> ({ rows, next_cursor, tip } cursor envelope) follow the same loopback rule as the other read planes; writes always require the instance token"
|
|
178
191
|
};
|
|
179
192
|
async function buildContext(deps = { clientProvider: getClient }) {
|
|
180
|
-
const unavailable = "unavailable: set
|
|
193
|
+
const unavailable = "unavailable: set INSTANCE_TOKEN";
|
|
181
194
|
const orNull = (v) => v == null ? unavailable : v;
|
|
182
195
|
const snap = await deps.clientProvider().context().catch(() => null);
|
|
183
196
|
return {
|
|
184
197
|
authState: {
|
|
185
|
-
apiKeySet: Boolean(
|
|
198
|
+
apiKeySet: Boolean(readApiKey())
|
|
186
199
|
},
|
|
187
200
|
whatExists: {
|
|
188
201
|
account: orNull(snap?.account),
|
|
@@ -255,18 +268,31 @@ function registerResources(server) {
|
|
|
255
268
|
}));
|
|
256
269
|
}
|
|
257
270
|
|
|
271
|
+
// src/tools/account.ts
|
|
272
|
+
import { z } from "zod";
|
|
273
|
+
function registerAccountTools(server) {
|
|
274
|
+
defineTool(server, "account_whoami", "Show the authenticated account's email.", {}, async () => {
|
|
275
|
+
const result = await apiRequest("GET", "/api/accounts/me");
|
|
276
|
+
return jsonResponse(result);
|
|
277
|
+
});
|
|
278
|
+
defineTool(server, "account_create_key", "Mint a scoped streams/index read key. Only the metered archive deployment can do this — a self-hosted instance has no account system and returns 404, so on your own instance use the INSTANCE_TOKEN from `secondlayer init` instead. The returned `key` is shown ONCE.", {
|
|
279
|
+
product: z.enum(["streams", "index"]).optional().describe("Key scope (default streams)"),
|
|
280
|
+
name: z.string().optional().describe("Optional label for the key")
|
|
281
|
+
}, async ({ product, name }) => jsonResponse(await apiRequest("POST", "/api/keys", { product, name })));
|
|
282
|
+
}
|
|
283
|
+
|
|
258
284
|
// src/tools/codegen.ts
|
|
259
285
|
import {
|
|
260
286
|
generateIndexSchema
|
|
261
287
|
} from "@secondlayer/subgraphs";
|
|
262
|
-
import { z } from "zod";
|
|
288
|
+
import { z as z2 } from "zod";
|
|
263
289
|
var TARGETS = ["prisma", "kysely", "drizzle", "json-schema"];
|
|
264
290
|
function registerCodegenTools(server) {
|
|
265
291
|
defineTool(server, "codegen_index_schema", "Generate an ORM schema for the Index tables (blocks, transactions, decoded_events, …) — the same output as `sl codegen index`. Returns the file contents as text; write it wherever your project keeps its schema. Use `tables` to emit only what you read.", {
|
|
266
|
-
target:
|
|
267
|
-
tables:
|
|
268
|
-
schemaName:
|
|
269
|
-
datasourceEnv:
|
|
292
|
+
target: z2.enum(TARGETS).optional().describe("Output flavor (default kysely)"),
|
|
293
|
+
tables: z2.array(z2.string()).optional().describe("Restrict to these Index tables (default: all)"),
|
|
294
|
+
schemaName: z2.string().optional().describe("Postgres schema to qualify table names with"),
|
|
295
|
+
datasourceEnv: z2.string().optional().describe("Prisma only: datasource url env var (default DATABASE_URL)")
|
|
270
296
|
}, ({ target, tables, schemaName, datasourceEnv }) => textResponse(generateIndexSchema(target ?? "kysely", {
|
|
271
297
|
tables,
|
|
272
298
|
schemaName,
|
|
@@ -275,17 +301,17 @@ function registerCodegenTools(server) {
|
|
|
275
301
|
}
|
|
276
302
|
|
|
277
303
|
// src/tools/contracts.ts
|
|
278
|
-
import { z as
|
|
304
|
+
import { z as z3 } from "zod";
|
|
279
305
|
function registerContractTools(server, clientProvider = getClient) {
|
|
280
306
|
defineTool(server, "contracts_find", 'Discover deployed Stacks contracts conforming to a trait (e.g. "sip-010", "sip-009", "sip-013"). The discovery endpoint for "which contracts implement X". Reads are public.', {
|
|
281
|
-
trait:
|
|
282
|
-
conformance:
|
|
283
|
-
include:
|
|
284
|
-
limit:
|
|
285
|
-
cursor:
|
|
307
|
+
trait: z3.string().describe('Required. Trait to match (e.g. "sip-010").'),
|
|
308
|
+
conformance: z3.enum(["declared", "inferred", "any"]).optional().describe("declared = parsed from source, inferred = ABI shape-match, any = either (default)"),
|
|
309
|
+
include: z3.literal("abi").optional().describe(`Set to "abi" to include each contract's full ABI`),
|
|
310
|
+
limit: z3.number().optional().describe("Page size, 1–500 (default 100)"),
|
|
311
|
+
cursor: z3.string().optional().describe("Opaque cursor from a prior response's next_cursor")
|
|
286
312
|
}, async (params) => jsonResponse(await clientProvider().contracts.list(params)));
|
|
287
|
-
defineTool(server, "contracts_get_abi", "Fetch a single deployed contract's ABI from the registry (prod-safe). Returns the contract's metadata + full ABI (functions, maps, variables, fungible/non-fungible tokens). Returns not_found if the contract isn't in the registry. Use it to inspect a contract before
|
|
288
|
-
contractId:
|
|
313
|
+
defineTool(server, "contracts_get_abi", "Fetch a single deployed contract's ABI from the registry (prod-safe). Returns the contract's metadata + full ABI (functions, maps, variables, fungible/non-fungible tokens). Returns not_found if the contract isn't in the registry. Use it to inspect a contract before subgraphs_scaffold.", {
|
|
314
|
+
contractId: z3.string().describe("Fully qualified contract id (e.g. SP….amm-pool-v2-01)")
|
|
289
315
|
}, async ({ contractId }) => {
|
|
290
316
|
const contract = await clientProvider().contracts.get(contractId, {
|
|
291
317
|
includeAbi: true
|
|
@@ -297,19 +323,19 @@ function registerContractTools(server, clientProvider = getClient) {
|
|
|
297
323
|
message: `Contract not in registry: ${contractId}`
|
|
298
324
|
}
|
|
299
325
|
}, true);
|
|
300
|
-
}
|
|
326
|
+
});
|
|
301
327
|
}
|
|
302
328
|
|
|
303
329
|
// src/tools/index.ts
|
|
304
330
|
import { DECODED_EVENT_TYPES } from "@secondlayer/shared";
|
|
305
|
-
import { z as
|
|
331
|
+
import { z as z4 } from "zod";
|
|
306
332
|
var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES;
|
|
307
333
|
var rangeFilters = {
|
|
308
|
-
contractId:
|
|
309
|
-
fromHeight:
|
|
310
|
-
toHeight:
|
|
311
|
-
cursor:
|
|
312
|
-
limit:
|
|
334
|
+
contractId: z4.string().optional().describe("Filter by contract id"),
|
|
335
|
+
fromHeight: z4.number().optional().describe("Start block height (inclusive)"),
|
|
336
|
+
toHeight: z4.number().optional().describe("End block height (inclusive)"),
|
|
337
|
+
cursor: z4.string().optional().describe("Opaque cursor from a prior response's next_cursor"),
|
|
338
|
+
limit: z4.number().optional().describe("Max rows for this page")
|
|
313
339
|
};
|
|
314
340
|
var heightFilters = {
|
|
315
341
|
fromHeight: rangeFilters.fromHeight,
|
|
@@ -318,54 +344,54 @@ var heightFilters = {
|
|
|
318
344
|
limit: rangeFilters.limit
|
|
319
345
|
};
|
|
320
346
|
function registerIndexTools(server, clientProvider = getClient) {
|
|
321
|
-
defineTool(server, "index_ft_transfers", "List decoded SIP-010 fungible-token transfers from
|
|
347
|
+
defineTool(server, "index_ft_transfers", "List decoded SIP-010 fungible-token transfers from Index. Reads are keyless.", {
|
|
322
348
|
...rangeFilters,
|
|
323
|
-
sender:
|
|
324
|
-
recipient:
|
|
325
|
-
assetIdentifier:
|
|
349
|
+
sender: z4.string().optional().describe("Filter by sender principal"),
|
|
350
|
+
recipient: z4.string().optional().describe("Filter by recipient principal"),
|
|
351
|
+
assetIdentifier: z4.string().optional().describe("Filter by asset identifier (contract::asset), e.g. SP…sbtc-token::sbtc-token")
|
|
326
352
|
}, async (params) => jsonResponse(await clientProvider().index.ftTransfers.list(params)));
|
|
327
|
-
defineTool(server, "index_nft_transfers", "List decoded SIP-009 non-fungible-token transfers from
|
|
353
|
+
defineTool(server, "index_nft_transfers", "List decoded SIP-009 non-fungible-token transfers from Index. Reads are keyless.", {
|
|
328
354
|
...rangeFilters,
|
|
329
|
-
sender:
|
|
330
|
-
recipient:
|
|
331
|
-
assetIdentifier:
|
|
355
|
+
sender: z4.string().optional().describe("Filter by sender principal"),
|
|
356
|
+
recipient: z4.string().optional().describe("Filter by recipient principal"),
|
|
357
|
+
assetIdentifier: z4.string().optional().describe("Filter by asset identifier (contract::asset)")
|
|
332
358
|
}, async (params) => jsonResponse(await clientProvider().index.nftTransfers.list(params)));
|
|
333
359
|
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.", {
|
|
334
|
-
eventType:
|
|
360
|
+
eventType: z4.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
|
|
335
361
|
...rangeFilters,
|
|
336
|
-
sender:
|
|
337
|
-
recipient:
|
|
338
|
-
assetIdentifier:
|
|
339
|
-
trait:
|
|
340
|
-
txContext:
|
|
362
|
+
sender: z4.string().optional().describe("Filter by sender principal"),
|
|
363
|
+
recipient: z4.string().optional().describe("Filter by recipient principal"),
|
|
364
|
+
assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable"),
|
|
365
|
+
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."),
|
|
366
|
+
txContext: z4.boolean().optional().describe("Join the submitting transaction into each event (tx_sender, tx_type, tx_status, tx_contract_id, tx_function_name). For print events it's the only source of the submitting sender.")
|
|
341
367
|
}, async (params) => jsonResponse(await clientProvider().index.events.list(params)));
|
|
342
368
|
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.", {
|
|
343
369
|
...rangeFilters,
|
|
344
|
-
functionName:
|
|
345
|
-
sender:
|
|
346
|
-
trait:
|
|
370
|
+
functionName: z4.string().optional().describe("Filter by called function name"),
|
|
371
|
+
sender: z4.string().optional().describe("Filter by caller principal"),
|
|
372
|
+
trait: z4.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
|
|
347
373
|
}, async (params) => jsonResponse(await clientProvider().index.contractCalls.list(params)));
|
|
348
|
-
defineTool(server, "index_blocks", "List decoded blocks from
|
|
349
|
-
defineTool(server, "index_transactions", "List decoded transactions from
|
|
374
|
+
defineTool(server, "index_blocks", "List decoded blocks from Index. Reads are keyless.", { ...heightFilters }, async (params) => jsonResponse(await clientProvider().index.blocks.list(params)));
|
|
375
|
+
defineTool(server, "index_transactions", "List decoded transactions from Index. Filter by type, sender, or contract. Reads are keyless.", {
|
|
350
376
|
...rangeFilters,
|
|
351
|
-
type:
|
|
352
|
-
sender:
|
|
377
|
+
type: z4.string().optional().describe("Filter by transaction type"),
|
|
378
|
+
sender: z4.string().optional().describe("Filter by sender principal")
|
|
353
379
|
}, async (params) => jsonResponse(await clientProvider().index.transactions.list(params)));
|
|
354
380
|
defineTool(server, "index_print_schema", "Empirical per-topic print payload schemas for a contract, inferred from sampled on-chain events: each topic's fields with observed Clarity types, decoded TS types, subgraph column types, and presence rates. Use before writing print_event subgraph handlers to learn what's on e.data. Anonymous read.", {
|
|
355
|
-
contractId:
|
|
381
|
+
contractId: z4.string().describe("Contract id (SP….name) whose print events to profile")
|
|
356
382
|
}, async (params) => jsonResponse(await clientProvider().index.printSchema(params.contractId)));
|
|
357
383
|
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()));
|
|
358
384
|
defineTool(server, "batch_query", "Run up to 10 public /v1 reads in one round trip (POST /v1/batch). Each item keeps its own auth/quota/pay-per-call semantics; results return in order with per-item status. Paths must start with /v1/index, /v1/subgraphs, /v1/streams, or /v1/contracts.", {
|
|
359
|
-
requests:
|
|
360
|
-
path:
|
|
361
|
-
params:
|
|
385
|
+
requests: z4.array(z4.object({
|
|
386
|
+
path: z4.string().describe("Public /v1 read path"),
|
|
387
|
+
params: z4.record(z4.string(), z4.string()).optional()
|
|
362
388
|
})).min(1).max(10).describe("Read descriptors, executed concurrently")
|
|
363
389
|
}, async (params) => jsonResponse(await clientProvider().batch(params.requests)));
|
|
364
390
|
}
|
|
365
391
|
|
|
366
392
|
// src/tools/scaffold.ts
|
|
367
393
|
import { generateSubgraphCode } from "@secondlayer/scaffold";
|
|
368
|
-
import { z as
|
|
394
|
+
import { z as z5 } from "zod";
|
|
369
395
|
async function fetchAbi(clientProvider, contractId) {
|
|
370
396
|
const contract = await clientProvider().contracts.get(contractId, {
|
|
371
397
|
includeAbi: true
|
|
@@ -379,9 +405,9 @@ async function fetchAbi(clientProvider, contractId) {
|
|
|
379
405
|
return { functions: abi.functions ?? [] };
|
|
380
406
|
}
|
|
381
407
|
function registerScaffoldTools(server, clientProvider = getClient) {
|
|
382
|
-
defineTool(server, "
|
|
383
|
-
contractId:
|
|
384
|
-
subgraphName:
|
|
408
|
+
defineTool(server, "subgraphs_scaffold", "Generate a subgraph scaffold from a deployed Stacks contract. Fetches the ABI automatically. Returns TypeScript source — review it, then pass it to subgraphs_deploy.", {
|
|
409
|
+
contractId: z5.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
|
|
410
|
+
subgraphName: z5.string().optional().describe("Override the subgraph name (defaults to contract name)")
|
|
385
411
|
}, async ({ contractId, subgraphName }) => {
|
|
386
412
|
const { functions } = await fetchAbi(clientProvider, contractId);
|
|
387
413
|
const code = generateSubgraphCode(contractId, functions, subgraphName);
|
|
@@ -390,32 +416,75 @@ function registerScaffoldTools(server, clientProvider = getClient) {
|
|
|
390
416
|
}
|
|
391
417
|
|
|
392
418
|
// src/tools/streams.ts
|
|
419
|
+
import { STREAMS_EVENT_TYPES } from "@secondlayer/shared";
|
|
420
|
+
import { z as z6 } from "zod";
|
|
421
|
+
var filterValue = z6.union([z6.string(), z6.array(z6.string())]);
|
|
393
422
|
function registerStreamsTools(server, clientProvider = getClient) {
|
|
394
|
-
defineTool(server, "
|
|
423
|
+
defineTool(server, "streams_tip", "Current canonical chain tip as Streams sees it: block height/hash, burn height, finalized_height, lag_seconds, and the oldest seekable height/cursor for this key's retention window. Call it before a cursor walk to know where the stream ends, and after an ingest to check lag.", {}, async () => jsonResponse(await clientProvider().streams.tip()));
|
|
424
|
+
defineTool(server, "streams_events", "List raw chain events from Streams — ONE page per call, newest filters applied server-side. Returns { events, next_cursor, tip, reorgs }. To follow the chain live, poll this tool with `cursor` set to the previous response's `next_cursor` (input is exclusive, so no duplicates); there is no open-stream tool because a tool call cannot hold a connection. ALWAYS narrow with types/contractId/sender/recipient — an unfiltered page is the full firehose.", {
|
|
425
|
+
cursor: z6.string().optional().describe("Resume strictly after this cursor (`<block_height>:<event_index>`, e.g. 951475:3) — pass a prior response's next_cursor"),
|
|
426
|
+
fromHeight: z6.number().int().nonnegative().optional().describe("Start block height (inclusive)"),
|
|
427
|
+
toHeight: z6.number().int().nonnegative().optional().describe("End block height (inclusive)"),
|
|
428
|
+
types: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
|
|
429
|
+
notTypes: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude (applied after types)"),
|
|
430
|
+
contractId: filterValue.optional().describe("Contract id, or a list of contract ids to OR together"),
|
|
431
|
+
sender: filterValue.optional().describe("Sender principal, or a list to OR together"),
|
|
432
|
+
recipient: filterValue.optional().describe("Recipient principal, or a list to OR together"),
|
|
433
|
+
assetIdentifier: z6.string().optional().describe("Asset identifier (contract::asset), e.g. SP…sbtc-token::sbtc-token"),
|
|
434
|
+
limit: z6.number().int().min(1).max(1000).optional().describe("Page size, 1–1000 (default 100)")
|
|
435
|
+
}, async (params) => jsonResponse(await clientProvider().streams.events.list(params)));
|
|
436
|
+
defineTool(server, "streams_events_by_tx", "List every event emitted by a single transaction, in emission order. Use it to explain one transaction end to end after finding it in streams_events or the Index.", {
|
|
437
|
+
txId: z6.string().describe("Transaction id (0x-prefixed)")
|
|
438
|
+
}, async ({ txId }) => jsonResponse(await clientProvider().streams.events.byTxId(txId)));
|
|
439
|
+
defineTool(server, "streams_block_events", "List every event in one block, addressed by block height or block hash. Use it to audit a block a consumer flagged, or to diff what a reorg replaced.", {
|
|
440
|
+
heightOrHash: z6.union([z6.number().int().nonnegative(), z6.string()]).describe("Block height (number) or block hash (0x-prefixed string)")
|
|
441
|
+
}, async ({ heightOrHash }) => jsonResponse(await clientProvider().streams.blocks.events(heightOrHash)));
|
|
442
|
+
defineTool(server, "streams_canonical", "Get the canonical block at a height: hash, burn block hash, parent, and whether the height is still canonical. Use it to confirm a height you already consumed was not reorged away before you trust derived state.", {
|
|
443
|
+
height: z6.number().int().nonnegative().describe("Block height")
|
|
444
|
+
}, async ({ height }) => jsonResponse(await clientProvider().streams.canonical(height)));
|
|
445
|
+
defineTool(server, "streams_reorgs", "List reorgs detected since a point in time, newest last, with the new canonical tip for each. Returns { reorgs, next_since } — resume with next_since. Call it when a consumer's derived state disagrees with streams_canonical, or on a schedule to know which heights to reprocess.", {
|
|
446
|
+
since: z6.string().describe("Required. ISO 8601 timestamp, or a `next_since` resume token from a prior response"),
|
|
447
|
+
limit: z6.number().int().positive().optional().describe("Max reorgs to return")
|
|
448
|
+
}, async ({ since, limit }) => jsonResponse(await clientProvider().streams.reorgs.list({
|
|
449
|
+
since,
|
|
450
|
+
...limit !== undefined ? { limit } : {}
|
|
451
|
+
})));
|
|
452
|
+
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). For live reads use streams_events (cursor-paginated) and streams_tip.", {}, async () => jsonResponse(await clientProvider().streams.dumps.list()));
|
|
395
453
|
}
|
|
396
454
|
|
|
397
455
|
// src/tools/subgraphs.ts
|
|
398
456
|
import { bundleSubgraphCode } from "@secondlayer/bundler";
|
|
399
|
-
import { z as
|
|
457
|
+
import { z as z7 } from "zod";
|
|
400
458
|
function registerSubgraphTools(server, clientProvider = getClient) {
|
|
401
|
-
defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only
|
|
459
|
+
defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only; read rows with subgraphs_query.", {}, async () => {
|
|
402
460
|
const { data } = await clientProvider().subgraphs.list();
|
|
403
461
|
return jsonResponse(data.map(formatSubgraphSummary));
|
|
404
462
|
});
|
|
405
|
-
defineTool(server, "
|
|
406
|
-
const detail = await clientProvider().subgraphs.
|
|
463
|
+
defineTool(server, "subgraphs_status", "Get full details of a subgraph including schema, health, and table columns.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
|
|
464
|
+
const detail = await clientProvider().subgraphs.status(name);
|
|
407
465
|
return jsonResponse(detail);
|
|
408
466
|
});
|
|
409
|
-
defineTool(server, "
|
|
410
|
-
name:
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
467
|
+
defineTool(server, "subgraphs_spec", "Fetch a subgraph's self-describing spec — the tables, columns, filters, and read URLs a consumer needs, without guessing. `agent` (default) returns the compact JSON schema built for tool use, `openapi` the OpenAPI document for the subgraph's read routes, `markdown` human-readable docs. Read it before writing queries against an unfamiliar subgraph.", {
|
|
468
|
+
name: z7.string().describe("Subgraph name"),
|
|
469
|
+
format: z7.enum(["agent", "openapi", "markdown"]).optional().describe("agent = JSON schema for tool use (default), openapi = OpenAPI JSON, markdown = docs.md")
|
|
470
|
+
}, async ({ name, format }) => {
|
|
471
|
+
const client = clientProvider();
|
|
472
|
+
if (format === "openapi")
|
|
473
|
+
return jsonResponse(await client.subgraphs.openapi(name));
|
|
474
|
+
if (format === "markdown")
|
|
475
|
+
return textResponse(await client.subgraphs.markdown(name));
|
|
476
|
+
return jsonResponse(await client.subgraphs.schema(name));
|
|
477
|
+
});
|
|
478
|
+
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 — when tailing with a `fields` list, include "_id" in it or the next poll filter cannot be formed. Fetch one row by id with {"_id": "<id>"}. The same rows are readable over HTTP at GET /v1/subgraphs/<name>/<table> — { rows, next_cursor, tip } envelope, resume with ?cursor=<next_cursor> + _order=asc|desc (no _offset/_sort on /v1); hand that URL to third parties.', {
|
|
479
|
+
name: z7.string().describe("Subgraph name"),
|
|
480
|
+
table: z7.string().describe("Table name"),
|
|
481
|
+
filters: z7.record(z7.string(), z7.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
|
|
482
|
+
sort: z7.string().optional().describe("Column to sort by"),
|
|
483
|
+
order: z7.enum(["asc", "desc"]).optional().describe("Sort order"),
|
|
484
|
+
limit: z7.number().max(200).optional().describe("Max rows (default 50, max 200)"),
|
|
485
|
+
offset: z7.number().optional().describe("Offset for pagination"),
|
|
486
|
+
fields: z7.string().optional().describe('Comma-separated column list to return (e.g. "sender,amount")'),
|
|
487
|
+
count: z7.boolean().optional().describe("If true, return row count instead of rows")
|
|
419
488
|
}, async ({
|
|
420
489
|
name,
|
|
421
490
|
table,
|
|
@@ -442,16 +511,16 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
442
511
|
const result = withCap(rows, Math.min(limit ?? 50, 200));
|
|
443
512
|
return jsonResponse(result);
|
|
444
513
|
});
|
|
445
|
-
defineTool(server, "subgraphs_reindex", "Reindex a subgraph: drops and rebuilds the whole subgraph from its start block to chain tip. Takes no block range — use subgraphs_backfill to process a specific range. Returns an operationId — check
|
|
446
|
-
name:
|
|
514
|
+
defineTool(server, "subgraphs_reindex", "Reindex a subgraph: drops and rebuilds the whole subgraph from its start block to chain tip. Takes no block range — use subgraphs_backfill to process a specific range. Returns an operationId — check subgraphs_status (health) or the REST operations endpoint to track progress to completion.", {
|
|
515
|
+
name: z7.string().describe("Subgraph name")
|
|
447
516
|
}, async ({ name }) => {
|
|
448
517
|
const result = await clientProvider().subgraphs.reindex(name);
|
|
449
518
|
return jsonResponse(result);
|
|
450
519
|
});
|
|
451
|
-
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 — check
|
|
452
|
-
name:
|
|
453
|
-
fromBlock:
|
|
454
|
-
toBlock:
|
|
520
|
+
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 — check subgraphs_status (health) or the REST operations endpoint to track progress.", {
|
|
521
|
+
name: z7.string().describe("Subgraph name"),
|
|
522
|
+
fromBlock: z7.number().int().nonnegative().describe("Start block (inclusive)"),
|
|
523
|
+
toBlock: z7.number().int().nonnegative().describe("End block (inclusive)")
|
|
455
524
|
}, async ({ name, fromBlock, toBlock }) => {
|
|
456
525
|
const result = await clientProvider().subgraphs.backfill(name, {
|
|
457
526
|
fromBlock,
|
|
@@ -459,15 +528,15 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
459
528
|
});
|
|
460
529
|
return jsonResponse(result);
|
|
461
530
|
});
|
|
462
|
-
defineTool(server, "subgraphs_stop", "Cancel an in-flight reindex or backfill operation for a subgraph. Returns the stop request status; check
|
|
531
|
+
defineTool(server, "subgraphs_stop", "Cancel an in-flight reindex or backfill operation for a subgraph. Returns the stop request status; check subgraphs_status (health) or the REST operations endpoint to confirm it reaches a terminal state.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
|
|
463
532
|
const result = await clientProvider().subgraphs.stop(name);
|
|
464
533
|
return jsonResponse(result);
|
|
465
534
|
});
|
|
466
535
|
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.", {
|
|
467
|
-
name:
|
|
468
|
-
limit:
|
|
469
|
-
offset:
|
|
470
|
-
resolved:
|
|
536
|
+
name: z7.string().describe("Subgraph name"),
|
|
537
|
+
limit: z7.number().int().positive().optional().describe("Max gaps to return"),
|
|
538
|
+
offset: z7.number().int().nonnegative().optional().describe("Pagination offset"),
|
|
539
|
+
resolved: z7.boolean().optional().describe("Filter by resolved state (omit for unresolved only)")
|
|
471
540
|
}, async ({ name, limit, offset, resolved }) => {
|
|
472
541
|
const result = await clientProvider().subgraphs.gaps(name, {
|
|
473
542
|
limit,
|
|
@@ -476,14 +545,21 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
476
545
|
});
|
|
477
546
|
return jsonResponse(result);
|
|
478
547
|
});
|
|
479
|
-
defineTool(server, "
|
|
548
|
+
defineTool(server, "subgraphs_operations", "Operation history for a subgraph (deploy/reindex/backfill/stop), newest first — status, block range, progress, and error. THIS IS THE VERIFY CALL: after subgraphs_deploy, subgraphs_reindex, subgraphs_backfill, or subgraphs_stop, poll it with the returned operationId until `status` is terminal (completed/failed/cancelled) before reporting success. Omit operationId for the recent history.", {
|
|
549
|
+
name: z7.string().describe("Subgraph name"),
|
|
550
|
+
operationId: z7.string().optional().describe("Fetch one operation by id (the operationId returned by reindex/backfill/stop); omit for recent history")
|
|
551
|
+
}, async ({ name, operationId }) => {
|
|
552
|
+
const client = clientProvider();
|
|
553
|
+
return jsonResponse(operationId ? await client.subgraphs.getOperation(name, operationId) : await client.subgraphs.operations(name));
|
|
554
|
+
});
|
|
555
|
+
defineTool(server, "subgraphs_delete", "Delete a subgraph permanently.", { name: z7.string().describe("Subgraph name") }, async ({ name }) => {
|
|
480
556
|
const result = await clientProvider().subgraphs.delete(name);
|
|
481
557
|
return textResponse(result.message);
|
|
482
558
|
});
|
|
483
559
|
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. Call `subgraphs_reindex` separately if you need a forced reindex.", {
|
|
484
|
-
code:
|
|
485
|
-
startBlock:
|
|
486
|
-
dryRun:
|
|
560
|
+
code: z7.string().describe("TypeScript source code containing a defineSubgraph() call"),
|
|
561
|
+
startBlock: z7.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy"),
|
|
562
|
+
dryRun: z7.boolean().optional().describe("Validate and preview the deploy (schema/DDL) without writing changes")
|
|
487
563
|
}, async ({ code, startBlock, dryRun }) => {
|
|
488
564
|
const bundled = await bundleSubgraphCode(code);
|
|
489
565
|
const result = await clientProvider().subgraphs.deploy({
|
|
@@ -503,38 +579,38 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
503
579
|
|
|
504
580
|
// src/tools/subscriptions.ts
|
|
505
581
|
import { CHAIN_TRIGGER_TYPES } from "@secondlayer/shared";
|
|
506
|
-
import { z as
|
|
582
|
+
import { z as z8 } from "zod";
|
|
507
583
|
function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
508
584
|
defineTool(server, "subscriptions_list", "List all subscriptions for the current account. Returns summary fields (no secrets).", {}, async () => {
|
|
509
585
|
const { data } = await clientProvider().subscriptions.list();
|
|
510
586
|
return jsonResponse(data);
|
|
511
587
|
});
|
|
512
|
-
defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id:
|
|
588
|
+
defineTool(server, "subscriptions_get", "Get full detail for a subscription (filter, auth, retry config, circuit state).", { id: z8.string().describe("Subscription id") }, async ({ id }) => {
|
|
513
589
|
const detail = await clientProvider().subscriptions.get(id);
|
|
514
590
|
return jsonResponse(detail);
|
|
515
591
|
});
|
|
516
592
|
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.", {
|
|
517
|
-
name:
|
|
518
|
-
subgraphName:
|
|
519
|
-
tableName:
|
|
520
|
-
triggers:
|
|
521
|
-
type:
|
|
522
|
-
contractId:
|
|
523
|
-
functionName:
|
|
524
|
-
caller:
|
|
525
|
-
sender:
|
|
526
|
-
recipient:
|
|
527
|
-
assetIdentifier:
|
|
528
|
-
deployer:
|
|
529
|
-
contractName:
|
|
530
|
-
topic:
|
|
531
|
-
lockedAddress:
|
|
532
|
-
trait:
|
|
533
|
-
minAmount:
|
|
534
|
-
maxAmount:
|
|
593
|
+
name: z8.string().describe("Human-readable name, unique per account"),
|
|
594
|
+
subgraphName: z8.string().optional().describe("Subgraph to subscribe to (subgraph subscription)"),
|
|
595
|
+
tableName: z8.string().optional().describe("Table within the subgraph (subgraph subscription)"),
|
|
596
|
+
triggers: z8.array(z8.object({
|
|
597
|
+
type: z8.enum(CHAIN_TRIGGER_TYPES),
|
|
598
|
+
contractId: z8.string().optional(),
|
|
599
|
+
functionName: z8.string().optional(),
|
|
600
|
+
caller: z8.string().optional(),
|
|
601
|
+
sender: z8.string().optional(),
|
|
602
|
+
recipient: z8.string().optional(),
|
|
603
|
+
assetIdentifier: z8.string().optional(),
|
|
604
|
+
deployer: z8.string().optional(),
|
|
605
|
+
contractName: z8.string().optional(),
|
|
606
|
+
topic: z8.string().optional(),
|
|
607
|
+
lockedAddress: z8.string().optional(),
|
|
608
|
+
trait: z8.string().optional(),
|
|
609
|
+
minAmount: z8.union([z8.string(), z8.number()]).optional(),
|
|
610
|
+
maxAmount: z8.union([z8.string(), z8.number()]).optional()
|
|
535
611
|
})).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."),
|
|
536
|
-
url:
|
|
537
|
-
format:
|
|
612
|
+
url: z8.string().describe("Webhook URL"),
|
|
613
|
+
format: z8.enum([
|
|
538
614
|
"standard-webhooks",
|
|
539
615
|
"inngest",
|
|
540
616
|
"trigger",
|
|
@@ -542,20 +618,20 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
|
542
618
|
"cloudevents",
|
|
543
619
|
"raw"
|
|
544
620
|
]).optional().describe("Wire format (default standard-webhooks)"),
|
|
545
|
-
runtime:
|
|
546
|
-
filter:
|
|
547
|
-
authConfig:
|
|
621
|
+
runtime: z8.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
|
|
622
|
+
filter: z8.record(z8.string(), z8.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}'),
|
|
623
|
+
authConfig: z8.record(z8.string(), z8.unknown()).optional().describe('Receiver auth sent with each delivery, e.g. {"type": "bearer", "token": "..."}')
|
|
548
624
|
}, async (input) => {
|
|
549
625
|
const res = await clientProvider().subscriptions.create(input);
|
|
550
626
|
return jsonResponse(res);
|
|
551
627
|
});
|
|
552
628
|
defineTool(server, "subscriptions_update", "Patch a subscription (name, url, filter, authConfig, format, runtime, retry, timeout, concurrency).", {
|
|
553
|
-
id:
|
|
554
|
-
name:
|
|
555
|
-
url:
|
|
556
|
-
filter:
|
|
557
|
-
authConfig:
|
|
558
|
-
format:
|
|
629
|
+
id: z8.string(),
|
|
630
|
+
name: z8.string().optional().describe("Rename the subscription"),
|
|
631
|
+
url: z8.string().optional(),
|
|
632
|
+
filter: z8.record(z8.string(), z8.unknown()).optional(),
|
|
633
|
+
authConfig: z8.record(z8.string(), z8.unknown()).optional().describe("Receiver auth sent with each delivery (bearer/etc.)"),
|
|
634
|
+
format: z8.enum([
|
|
559
635
|
"standard-webhooks",
|
|
560
636
|
"inngest",
|
|
561
637
|
"trigger",
|
|
@@ -563,27 +639,36 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
|
563
639
|
"cloudevents",
|
|
564
640
|
"raw"
|
|
565
641
|
]).optional(),
|
|
566
|
-
runtime:
|
|
567
|
-
maxRetries:
|
|
568
|
-
timeoutMs:
|
|
569
|
-
concurrency:
|
|
642
|
+
runtime: z8.enum(["inngest", "trigger", "cloudflare", "node"]).nullable().optional(),
|
|
643
|
+
maxRetries: z8.number().int().min(0).optional(),
|
|
644
|
+
timeoutMs: z8.number().int().min(100).optional(),
|
|
645
|
+
concurrency: z8.number().int().min(1).optional()
|
|
570
646
|
}, async ({ id, ...patch }) => {
|
|
571
647
|
const res = await clientProvider().subscriptions.update(id, patch);
|
|
572
648
|
return jsonResponse(res);
|
|
573
649
|
});
|
|
574
|
-
defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id:
|
|
650
|
+
defineTool(server, "subscriptions_delete", "Delete a subscription. Pending outbox rows are cascade-deleted.", { id: z8.string() }, async ({ id }) => {
|
|
575
651
|
const res = await clientProvider().subscriptions.delete(id);
|
|
576
652
|
return jsonResponse(res);
|
|
577
653
|
});
|
|
578
|
-
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. Returns {ok, statusCode, error, durationMs, deliveryId}.", { id:
|
|
654
|
+
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. Returns {ok, statusCode, error, durationMs, deliveryId}.", { id: z8.string().describe("Subscription id") }, async ({ id }) => {
|
|
579
655
|
const res = await clientProvider().subscriptions.test(id);
|
|
580
656
|
return jsonResponse(res);
|
|
581
657
|
});
|
|
658
|
+
defineTool(server, "subscriptions_pause", "Pause a subscription: stops delivery attempts and stops queueing new ones. Nothing is deleted — resume picks the subscription back up from the tip. Verify with subscriptions_get (status).", { id: z8.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.pause(id)));
|
|
659
|
+
defineTool(server, "subscriptions_resume", "Resume a paused subscription (also clears a tripped circuit breaker). Deliveries restart from the current tip — use subscriptions_replay for the blocks missed while paused. Verify with subscriptions_get (status).", { id: z8.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.resume(id)));
|
|
660
|
+
defineTool(server, "subscriptions_rotate_secret", "Rotate a subscription's webhook signing secret. Returns the NEW `signingSecret` ONCE — forward it to the user; deliveries signed with the old secret stop verifying as soon as this returns, so the receiver must be updated.", { id: z8.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.rotateSecret(id)));
|
|
661
|
+
defineTool(server, "subscriptions_deliveries", "List recent delivery attempts for a subscription — status code, attempt count, error, duration, timestamps. THIS IS THE VERIFY CALL: after subscriptions_create, subscriptions_test, or subscriptions_replay, read it to confirm the webhook actually landed (and to see the receiver's response when it didn't) instead of trusting the enqueue result.", { id: z8.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.deliveries(id)));
|
|
662
|
+
defineTool(server, "subscriptions_dead", "List a subscription's dead-letter queue — deliveries that exhausted their retries, with the outbox id, payload, and last error. Diagnose the receiver first (subscriptions_get for the URL/auth, subscriptions_test for a live probe), then requeue rows with subscriptions_requeue.", { id: z8.string().describe("Subscription id") }, async ({ id }) => jsonResponse(await clientProvider().subscriptions.dead(id)));
|
|
663
|
+
defineTool(server, "subscriptions_requeue", "Requeue ONE dead-lettered delivery for another attempt, by its outbox id from subscriptions_dead. Fix the receiver first — a requeue against a still-broken endpoint just dies again. Verify with subscriptions_deliveries.", {
|
|
664
|
+
id: z8.string().describe("Subscription id"),
|
|
665
|
+
outboxId: z8.string().describe("Outbox id of the dead delivery (from subscriptions_dead)")
|
|
666
|
+
}, async ({ id, outboxId }) => jsonResponse(await clientProvider().subscriptions.requeue(id, outboxId)));
|
|
582
667
|
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.", {
|
|
583
|
-
id:
|
|
584
|
-
fromBlock:
|
|
585
|
-
toBlock:
|
|
586
|
-
force:
|
|
668
|
+
id: z8.string(),
|
|
669
|
+
fromBlock: z8.number().int().nonnegative(),
|
|
670
|
+
toBlock: z8.number().int().nonnegative(),
|
|
671
|
+
force: z8.string().optional().describe("Idempotency suffix to force a duplicate replay of the range")
|
|
587
672
|
}, async ({ id, fromBlock, toBlock, force }) => {
|
|
588
673
|
const res = await clientProvider().subscriptions.replay(id, {
|
|
589
674
|
fromBlock,
|
|
@@ -609,6 +694,7 @@ function createServer() {
|
|
|
609
694
|
registerStreamsTools(server);
|
|
610
695
|
registerContractTools(server);
|
|
611
696
|
registerCodegenTools(server);
|
|
697
|
+
registerAccountTools(server);
|
|
612
698
|
registerResources(server);
|
|
613
699
|
return server;
|
|
614
700
|
}
|
|
@@ -699,5 +785,5 @@ httpServer.listen(port, () => {
|
|
|
699
785
|
console.error("Warning: SECONDLAYER_MCP_SECRET not set, authentication disabled");
|
|
700
786
|
});
|
|
701
787
|
|
|
702
|
-
//# debugId=
|
|
788
|
+
//# debugId=D1D3A96804FBA78564756E2164756E21
|
|
703
789
|
//# sourceMappingURL=bin-http.js.map
|