@secondlayer/mcp 3.3.1 → 3.5.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/README.md +10 -1
- package/dist/bin-http.js +142 -31
- package/dist/bin-http.js.map +8 -8
- package/dist/bin.js +142 -31
- package/dist/bin.js.map +8 -8
- package/dist/index.js +142 -31
- package/dist/index.js.map +8 -8
- package/package.json +4 -3
package/dist/bin.js
CHANGED
|
@@ -9,6 +9,9 @@ import { dirname, join } from "node:path";
|
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
11
|
|
|
12
|
+
// src/resources.ts
|
|
13
|
+
import { TYPE_MAP } from "@secondlayer/subgraphs/schema";
|
|
14
|
+
|
|
12
15
|
// src/lib/client.ts
|
|
13
16
|
import { SecondLayer } from "@secondlayer/sdk";
|
|
14
17
|
var instance = null;
|
|
@@ -111,45 +114,44 @@ var FILTERS_REFERENCE = [
|
|
|
111
114
|
{ type: "stx_lock", fields: ["lockedAddress", "minAmount"] },
|
|
112
115
|
{
|
|
113
116
|
type: "ft_transfer",
|
|
114
|
-
fields: [
|
|
115
|
-
"sender",
|
|
116
|
-
"recipient",
|
|
117
|
-
"assetIdentifier",
|
|
118
|
-
"minAmount",
|
|
119
|
-
"maxAmount"
|
|
120
|
-
]
|
|
117
|
+
fields: ["sender", "recipient", "assetIdentifier", "minAmount", "trait"]
|
|
121
118
|
},
|
|
122
|
-
{ type: "ft_mint", fields: ["recipient", "assetIdentifier", "minAmount"] },
|
|
123
|
-
{ type: "ft_burn", fields: ["sender", "assetIdentifier", "minAmount"] },
|
|
124
119
|
{
|
|
125
|
-
type: "
|
|
126
|
-
fields: ["
|
|
120
|
+
type: "ft_mint",
|
|
121
|
+
fields: ["recipient", "assetIdentifier", "minAmount", "trait"]
|
|
127
122
|
},
|
|
128
|
-
{ type: "nft_mint", fields: ["recipient", "assetIdentifier", "tokenId"] },
|
|
129
|
-
{ type: "nft_burn", fields: ["sender", "assetIdentifier", "tokenId"] },
|
|
130
|
-
{ type: "contract_call", fields: ["contract", "function"] },
|
|
131
|
-
{ type: "contract_deploy", fields: ["contract"] },
|
|
132
|
-
{ type: "print_event", fields: ["contract", "event", "contains"] }
|
|
133
|
-
];
|
|
134
|
-
var COLUMN_TYPES = [
|
|
135
123
|
{
|
|
136
|
-
type: "
|
|
137
|
-
|
|
138
|
-
description: "Unsigned integer (Clarity uint)"
|
|
124
|
+
type: "ft_burn",
|
|
125
|
+
fields: ["sender", "assetIdentifier", "minAmount", "trait"]
|
|
139
126
|
},
|
|
140
127
|
{
|
|
141
|
-
type: "
|
|
142
|
-
|
|
143
|
-
description: "Signed integer (Clarity int)"
|
|
128
|
+
type: "nft_transfer",
|
|
129
|
+
fields: ["sender", "recipient", "assetIdentifier", "trait"]
|
|
144
130
|
},
|
|
145
|
-
{ type: "
|
|
131
|
+
{ type: "nft_mint", fields: ["recipient", "assetIdentifier", "trait"] },
|
|
132
|
+
{ type: "nft_burn", fields: ["sender", "assetIdentifier", "trait"] },
|
|
146
133
|
{
|
|
147
|
-
type: "
|
|
148
|
-
|
|
149
|
-
description: "Stacks address (standard or contract)"
|
|
134
|
+
type: "contract_call",
|
|
135
|
+
fields: ["contractId", "functionName", "caller", "trait", "abi"]
|
|
150
136
|
},
|
|
151
|
-
{ type: "
|
|
152
|
-
{ type: "
|
|
137
|
+
{ type: "contract_deploy", fields: ["deployer", "contractName"] },
|
|
138
|
+
{ type: "print_event", fields: ["contractId", "topic", "trait"] }
|
|
139
|
+
];
|
|
140
|
+
var COLUMN_TYPE_DESCRIPTIONS = {
|
|
141
|
+
text: "UTF-8 string",
|
|
142
|
+
uint: "Unsigned integer (Clarity uint) — NUMERIC for lossless 128-bit range",
|
|
143
|
+
int: "Signed integer (Clarity int) — NUMERIC for lossless 128-bit range",
|
|
144
|
+
principal: "Stacks address (standard or contract)",
|
|
145
|
+
boolean: "Boolean value",
|
|
146
|
+
timestamp: "Timestamp with time zone",
|
|
147
|
+
jsonb: "Arbitrary JSON data"
|
|
148
|
+
};
|
|
149
|
+
var COLUMN_TYPES = [
|
|
150
|
+
...Object.keys(TYPE_MAP).map((type) => ({
|
|
151
|
+
type,
|
|
152
|
+
sqlType: TYPE_MAP[type],
|
|
153
|
+
description: COLUMN_TYPE_DESCRIPTIONS[type]
|
|
154
|
+
})),
|
|
153
155
|
{
|
|
154
156
|
options: ["nullable", "indexed", "search"],
|
|
155
157
|
description: "Column options: nullable allows NULL, indexed creates a B-tree index, search enables full-text search"
|
|
@@ -322,6 +324,10 @@ function registerDatasetTools(server, clientProvider = getClient) {
|
|
|
322
324
|
|
|
323
325
|
// src/tools/index.ts
|
|
324
326
|
import { DECODED_EVENT_TYPES } from "@secondlayer/shared";
|
|
327
|
+
import {
|
|
328
|
+
generateIndexSchema,
|
|
329
|
+
INDEX_CODEGEN_TABLES
|
|
330
|
+
} from "@secondlayer/subgraphs";
|
|
325
331
|
import { z as z4 } from "zod/v4";
|
|
326
332
|
var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES;
|
|
327
333
|
var rangeFilters = {
|
|
@@ -397,6 +403,14 @@ function registerIndexTools(server, clientProvider = getClient) {
|
|
|
397
403
|
return tx ? jsonResponse(tx) : notFound(`No pending tx for ${txId}`);
|
|
398
404
|
});
|
|
399
405
|
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()));
|
|
406
|
+
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(", ")}.`, {
|
|
407
|
+
target: z4.enum(["prisma", "kysely", "drizzle", "json-schema"]).optional().describe("Output target (default kysely)"),
|
|
408
|
+
tables: z4.array(z4.string()).optional().describe("Subset of Index tables (default: all)"),
|
|
409
|
+
schemaName: z4.string().optional().describe("Postgres schema to qualify table names with")
|
|
410
|
+
}, async ({ target = "kysely", tables, schemaName }) => {
|
|
411
|
+
const out = generateIndexSchema(target, { tables, schemaName });
|
|
412
|
+
return { content: [{ type: "text", text: out }] };
|
|
413
|
+
});
|
|
400
414
|
}
|
|
401
415
|
|
|
402
416
|
// src/tools/scaffold.ts
|
|
@@ -475,6 +489,48 @@ function registerStreamsTools(server, clientProvider = getClient) {
|
|
|
475
489
|
cursor: z6.string().optional().describe("Opaque cursor from a prior response"),
|
|
476
490
|
limit: z6.number().optional().describe("Max events for this page")
|
|
477
491
|
}, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.events.list(params))));
|
|
492
|
+
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).", {
|
|
493
|
+
fromCursor: z6.string().optional().describe("Resume cursor from a prior consume (omit to start at genesis)"),
|
|
494
|
+
types: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
|
|
495
|
+
notTypes: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude"),
|
|
496
|
+
contractId: z6.string().optional().describe("Filter by contract id"),
|
|
497
|
+
sender: z6.string().optional().describe("Filter by sender principal"),
|
|
498
|
+
recipient: z6.string().optional().describe("Filter by recipient principal"),
|
|
499
|
+
assetIdentifier: z6.string().optional().describe("Filter by asset identifier"),
|
|
500
|
+
batchSize: z6.number().optional().describe("Events per page (1–1000, default 100)"),
|
|
501
|
+
maxPages: z6.number().optional().describe("Max pages to walk this call (1–20, default 5)"),
|
|
502
|
+
finalizedOnly: z6.boolean().optional().describe("Only emit finalized events; skip reorg handling")
|
|
503
|
+
}, async (params) => withStreamsAuthHint(async () => {
|
|
504
|
+
const maxPages = Math.min(Math.max(params.maxPages ?? 5, 1), 20);
|
|
505
|
+
const batchSize = Math.min(Math.max(params.batchSize ?? 100, 1), 1000);
|
|
506
|
+
const events = [];
|
|
507
|
+
const reorgs = [];
|
|
508
|
+
const result = await clientProvider().streams.events.consume({
|
|
509
|
+
fromCursor: params.fromCursor ?? null,
|
|
510
|
+
mode: "bounded",
|
|
511
|
+
finalizedOnly: params.finalizedOnly,
|
|
512
|
+
types: params.types,
|
|
513
|
+
notTypes: params.notTypes,
|
|
514
|
+
contractId: params.contractId,
|
|
515
|
+
sender: params.sender,
|
|
516
|
+
recipient: params.recipient,
|
|
517
|
+
assetIdentifier: params.assetIdentifier,
|
|
518
|
+
batchSize,
|
|
519
|
+
maxPages,
|
|
520
|
+
onBatch: (batch) => {
|
|
521
|
+
events.push(...batch);
|
|
522
|
+
},
|
|
523
|
+
onReorg: (reorg) => {
|
|
524
|
+
reorgs.push(reorg);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
return jsonResponse({
|
|
528
|
+
events,
|
|
529
|
+
reorgs,
|
|
530
|
+
cursor: result.cursor,
|
|
531
|
+
pages: result.pages
|
|
532
|
+
});
|
|
533
|
+
}));
|
|
478
534
|
defineTool(server, "streams_event_by_txid", "List all Streams events emitted by a single transaction. Streams requires an API key (SL_API_KEY).", { txId: z6.string().describe("Transaction id (0x… hash)") }, async ({ txId }) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.events.byTxId(txId))));
|
|
479
535
|
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).", {
|
|
480
536
|
heightOrHash: z6.string().describe("Block height (digits) or block hash (0x… string)")
|
|
@@ -489,6 +545,11 @@ function registerStreamsTools(server, clientProvider = getClient) {
|
|
|
489
545
|
|
|
490
546
|
// src/tools/subgraphs.ts
|
|
491
547
|
import { bundleSubgraphCode } from "@secondlayer/bundler";
|
|
548
|
+
import {
|
|
549
|
+
generateDrizzleSchema,
|
|
550
|
+
generateKyselySchema,
|
|
551
|
+
generatePrismaSchema
|
|
552
|
+
} from "@secondlayer/subgraphs";
|
|
492
553
|
import { z as z7 } from "zod/v4";
|
|
493
554
|
function registerSubgraphTools(server, clientProvider = getClient) {
|
|
494
555
|
defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only.", {}, async () => {
|
|
@@ -565,6 +626,21 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
565
626
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
566
627
|
};
|
|
567
628
|
});
|
|
629
|
+
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.`, {
|
|
630
|
+
name: z7.string().describe("Subgraph name"),
|
|
631
|
+
table: z7.string().describe("Table name"),
|
|
632
|
+
filters: z7.record(z7.string(), z7.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
|
|
633
|
+
count: z7.boolean().optional().describe("Include COUNT(*) of matching rows"),
|
|
634
|
+
countDistinct: z7.array(z7.string()).optional().describe("Columns to count distinct values of"),
|
|
635
|
+
sum: z7.array(z7.string()).optional().describe("Numeric columns to sum (lossless string result)"),
|
|
636
|
+
min: z7.array(z7.string()).optional().describe("Numeric columns to take the minimum of"),
|
|
637
|
+
max: z7.array(z7.string()).optional().describe("Numeric columns to take the maximum of")
|
|
638
|
+
}, async ({ name, table, filters, count, countDistinct, sum, min, max }) => {
|
|
639
|
+
const result = await clientProvider().subgraphs.queryTableAggregate(name, table, { filters, count, countDistinct, sum, min, max });
|
|
640
|
+
return {
|
|
641
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
642
|
+
};
|
|
643
|
+
});
|
|
568
644
|
defineTool(server, "subgraphs_reindex", "Reindex a subgraph from a specific block range. Returns an operationId — poll subgraphs_operation to track progress to completion.", {
|
|
569
645
|
name: z7.string().describe("Subgraph name"),
|
|
570
646
|
fromBlock: z7.number().optional().describe("Start block (defaults to beginning)"),
|
|
@@ -616,6 +692,35 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
616
692
|
content: [{ type: "text", text: JSON.stringify(source, null, 2) }]
|
|
617
693
|
};
|
|
618
694
|
});
|
|
695
|
+
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.", {
|
|
696
|
+
code: z7.string().optional().describe("defineSubgraph() source (mutually exclusive with name)"),
|
|
697
|
+
name: z7.string().optional().describe("Deployed subgraph name (mutually exclusive with code)"),
|
|
698
|
+
target: z7.enum(["prisma", "drizzle", "kysely"]).optional().describe("ORM target (default prisma)"),
|
|
699
|
+
schemaName: z7.string().optional().describe("Postgres schema name (defaults to subgraph_<name>)")
|
|
700
|
+
}, async ({ code, name, target = "prisma", schemaName }) => {
|
|
701
|
+
if (code && name || !code && !name) {
|
|
702
|
+
throw new Error("Provide exactly one of `code` or `name`.");
|
|
703
|
+
}
|
|
704
|
+
let source = code;
|
|
705
|
+
if (name) {
|
|
706
|
+
const stored = await clientProvider().subgraphs.getSource(name);
|
|
707
|
+
if (!stored.sourceCode) {
|
|
708
|
+
throw new Error(`Subgraph "${name}" has no captured source (deployed before source capture). Redeploy it, or pass its source as \`code\`.`);
|
|
709
|
+
}
|
|
710
|
+
source = stored.sourceCode;
|
|
711
|
+
}
|
|
712
|
+
const bundled = await bundleSubgraphCode(source);
|
|
713
|
+
const def = {
|
|
714
|
+
name: bundled.name,
|
|
715
|
+
version: bundled.version,
|
|
716
|
+
description: bundled.description,
|
|
717
|
+
sources: bundled.sources,
|
|
718
|
+
schema: bundled.schema,
|
|
719
|
+
handlers: {}
|
|
720
|
+
};
|
|
721
|
+
const out = target === "drizzle" ? generateDrizzleSchema(def, { schemaName }) : target === "kysely" ? generateKyselySchema(def, { schemaName }) : generatePrismaSchema(def, { schemaName });
|
|
722
|
+
return { content: [{ type: "text", text: out }] };
|
|
723
|
+
});
|
|
619
724
|
}
|
|
620
725
|
|
|
621
726
|
// src/tools/subscriptions.ts
|
|
@@ -717,6 +822,12 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
|
717
822
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
718
823
|
};
|
|
719
824
|
});
|
|
825
|
+
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: z8.string().describe("Subscription id") }, async ({ id }) => {
|
|
826
|
+
const res = await clientProvider().subscriptions.test(id);
|
|
827
|
+
return {
|
|
828
|
+
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
829
|
+
};
|
|
830
|
+
});
|
|
720
831
|
defineTool(server, "subscriptions_replay", "Replay a block range for a subscription. Replays run at 10% of batch capacity — use sparingly.", {
|
|
721
832
|
id: z8.string(),
|
|
722
833
|
fromBlock: z8.number().int().nonnegative(),
|
|
@@ -778,5 +889,5 @@ var server = createServer();
|
|
|
778
889
|
var transport = new StdioServerTransport;
|
|
779
890
|
await server.connect(transport);
|
|
780
891
|
|
|
781
|
-
//# debugId=
|
|
892
|
+
//# debugId=43FD93326BB3318D64756E2164756E21
|
|
782
893
|
//# sourceMappingURL=bin.js.map
|