@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/README.md
CHANGED
|
@@ -52,7 +52,7 @@ bunx -p @secondlayer/mcp secondlayer-mcp-http
|
|
|
52
52
|
|
|
53
53
|
| Domain | Tools |
|
|
54
54
|
| --- | --- |
|
|
55
|
-
| **Subgraphs** (
|
|
55
|
+
| **Subgraphs** (11) | `subgraphs_list`, `subgraphs_get`, `subgraphs_spec`, `subgraphs_query`, `subgraphs_aggregate`, `subgraphs_reindex`, `subgraphs_operation`, `subgraphs_delete`, `subgraphs_deploy`, `subgraphs_read_source`, `subgraphs_codegen` |
|
|
56
56
|
| **Subscriptions** (12) | `subscriptions_list`, `subscriptions_get`, `subscriptions_create`, `subscriptions_update`, `subscriptions_pause`, `subscriptions_resume`, `subscriptions_delete`, `subscriptions_rotate_secret`, `subscriptions_replay`, `subscriptions_recent_deliveries`, `subscriptions_dead`, `subscriptions_requeue_dead` |
|
|
57
57
|
| **Datasets** (2) | `datasets_list`, `datasets_query` |
|
|
58
58
|
| **Index** (4) | `index_ft_transfers`, `index_nft_transfers`, `index_events`, `index_contract_calls` |
|
|
@@ -82,6 +82,15 @@ subgraph (e.g. `[{ "type": "contract_call", "contractId": "SP....amm",
|
|
|
82
82
|
- Filter operators: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `like`
|
|
83
83
|
- Max limit: 200
|
|
84
84
|
|
|
85
|
+
### `subgraphs_aggregate`
|
|
86
|
+
|
|
87
|
+
Scalar aggregates over the same filtered set as `subgraphs_query`:
|
|
88
|
+
|
|
89
|
+
- `count` — `COUNT(*)` of matching rows
|
|
90
|
+
- `countDistinct` — columns to count distinct values of
|
|
91
|
+
- `sum` / `min` / `max` — numeric columns only (`uint`/`int` + system `_block_height`); returned as lossless strings
|
|
92
|
+
- No aggregate requested → returns the row count.
|
|
93
|
+
|
|
85
94
|
## Resources
|
|
86
95
|
|
|
87
96
|
| URI | Description |
|
package/dist/bin-http.js
CHANGED
|
@@ -12,6 +12,9 @@ import { dirname, join } from "node:path";
|
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
14
|
|
|
15
|
+
// src/resources.ts
|
|
16
|
+
import { TYPE_MAP } from "@secondlayer/subgraphs/schema";
|
|
17
|
+
|
|
15
18
|
// src/lib/client.ts
|
|
16
19
|
import { SecondLayer } from "@secondlayer/sdk";
|
|
17
20
|
var instance = null;
|
|
@@ -114,45 +117,44 @@ var FILTERS_REFERENCE = [
|
|
|
114
117
|
{ type: "stx_lock", fields: ["lockedAddress", "minAmount"] },
|
|
115
118
|
{
|
|
116
119
|
type: "ft_transfer",
|
|
117
|
-
fields: [
|
|
118
|
-
"sender",
|
|
119
|
-
"recipient",
|
|
120
|
-
"assetIdentifier",
|
|
121
|
-
"minAmount",
|
|
122
|
-
"maxAmount"
|
|
123
|
-
]
|
|
120
|
+
fields: ["sender", "recipient", "assetIdentifier", "minAmount", "trait"]
|
|
124
121
|
},
|
|
125
|
-
{ type: "ft_mint", fields: ["recipient", "assetIdentifier", "minAmount"] },
|
|
126
|
-
{ type: "ft_burn", fields: ["sender", "assetIdentifier", "minAmount"] },
|
|
127
122
|
{
|
|
128
|
-
type: "
|
|
129
|
-
fields: ["
|
|
123
|
+
type: "ft_mint",
|
|
124
|
+
fields: ["recipient", "assetIdentifier", "minAmount", "trait"]
|
|
130
125
|
},
|
|
131
|
-
{ type: "nft_mint", fields: ["recipient", "assetIdentifier", "tokenId"] },
|
|
132
|
-
{ type: "nft_burn", fields: ["sender", "assetIdentifier", "tokenId"] },
|
|
133
|
-
{ type: "contract_call", fields: ["contract", "function"] },
|
|
134
|
-
{ type: "contract_deploy", fields: ["contract"] },
|
|
135
|
-
{ type: "print_event", fields: ["contract", "event", "contains"] }
|
|
136
|
-
];
|
|
137
|
-
var COLUMN_TYPES = [
|
|
138
126
|
{
|
|
139
|
-
type: "
|
|
140
|
-
|
|
141
|
-
description: "Unsigned integer (Clarity uint)"
|
|
127
|
+
type: "ft_burn",
|
|
128
|
+
fields: ["sender", "assetIdentifier", "minAmount", "trait"]
|
|
142
129
|
},
|
|
143
130
|
{
|
|
144
|
-
type: "
|
|
145
|
-
|
|
146
|
-
description: "Signed integer (Clarity int)"
|
|
131
|
+
type: "nft_transfer",
|
|
132
|
+
fields: ["sender", "recipient", "assetIdentifier", "trait"]
|
|
147
133
|
},
|
|
148
|
-
{ type: "
|
|
134
|
+
{ type: "nft_mint", fields: ["recipient", "assetIdentifier", "trait"] },
|
|
135
|
+
{ type: "nft_burn", fields: ["sender", "assetIdentifier", "trait"] },
|
|
149
136
|
{
|
|
150
|
-
type: "
|
|
151
|
-
|
|
152
|
-
description: "Stacks address (standard or contract)"
|
|
137
|
+
type: "contract_call",
|
|
138
|
+
fields: ["contractId", "functionName", "caller", "trait", "abi"]
|
|
153
139
|
},
|
|
154
|
-
{ type: "
|
|
155
|
-
{ type: "
|
|
140
|
+
{ type: "contract_deploy", fields: ["deployer", "contractName"] },
|
|
141
|
+
{ type: "print_event", fields: ["contractId", "topic", "trait"] }
|
|
142
|
+
];
|
|
143
|
+
var COLUMN_TYPE_DESCRIPTIONS = {
|
|
144
|
+
text: "UTF-8 string",
|
|
145
|
+
uint: "Unsigned integer (Clarity uint) — NUMERIC for lossless 128-bit range",
|
|
146
|
+
int: "Signed integer (Clarity int) — NUMERIC for lossless 128-bit range",
|
|
147
|
+
principal: "Stacks address (standard or contract)",
|
|
148
|
+
boolean: "Boolean value",
|
|
149
|
+
timestamp: "Timestamp with time zone",
|
|
150
|
+
jsonb: "Arbitrary JSON data"
|
|
151
|
+
};
|
|
152
|
+
var COLUMN_TYPES = [
|
|
153
|
+
...Object.keys(TYPE_MAP).map((type) => ({
|
|
154
|
+
type,
|
|
155
|
+
sqlType: TYPE_MAP[type],
|
|
156
|
+
description: COLUMN_TYPE_DESCRIPTIONS[type]
|
|
157
|
+
})),
|
|
156
158
|
{
|
|
157
159
|
options: ["nullable", "indexed", "search"],
|
|
158
160
|
description: "Column options: nullable allows NULL, indexed creates a B-tree index, search enables full-text search"
|
|
@@ -325,6 +327,10 @@ function registerDatasetTools(server, clientProvider = getClient) {
|
|
|
325
327
|
|
|
326
328
|
// src/tools/index.ts
|
|
327
329
|
import { DECODED_EVENT_TYPES } from "@secondlayer/shared";
|
|
330
|
+
import {
|
|
331
|
+
generateIndexSchema,
|
|
332
|
+
INDEX_CODEGEN_TABLES
|
|
333
|
+
} from "@secondlayer/subgraphs";
|
|
328
334
|
import { z as z4 } from "zod/v4";
|
|
329
335
|
var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES;
|
|
330
336
|
var rangeFilters = {
|
|
@@ -400,6 +406,14 @@ function registerIndexTools(server, clientProvider = getClient) {
|
|
|
400
406
|
return tx ? jsonResponse(tx) : notFound(`No pending tx for ${txId}`);
|
|
401
407
|
});
|
|
402
408
|
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()));
|
|
409
|
+
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(", ")}.`, {
|
|
410
|
+
target: z4.enum(["prisma", "kysely", "drizzle", "json-schema"]).optional().describe("Output target (default kysely)"),
|
|
411
|
+
tables: z4.array(z4.string()).optional().describe("Subset of Index tables (default: all)"),
|
|
412
|
+
schemaName: z4.string().optional().describe("Postgres schema to qualify table names with")
|
|
413
|
+
}, async ({ target = "kysely", tables, schemaName }) => {
|
|
414
|
+
const out = generateIndexSchema(target, { tables, schemaName });
|
|
415
|
+
return { content: [{ type: "text", text: out }] };
|
|
416
|
+
});
|
|
403
417
|
}
|
|
404
418
|
|
|
405
419
|
// src/tools/scaffold.ts
|
|
@@ -478,6 +492,48 @@ function registerStreamsTools(server, clientProvider = getClient) {
|
|
|
478
492
|
cursor: z6.string().optional().describe("Opaque cursor from a prior response"),
|
|
479
493
|
limit: z6.number().optional().describe("Max events for this page")
|
|
480
494
|
}, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.events.list(params))));
|
|
495
|
+
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).", {
|
|
496
|
+
fromCursor: z6.string().optional().describe("Resume cursor from a prior consume (omit to start at genesis)"),
|
|
497
|
+
types: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to include"),
|
|
498
|
+
notTypes: z6.array(z6.enum(STREAMS_EVENT_TYPES)).optional().describe("Event types to exclude"),
|
|
499
|
+
contractId: z6.string().optional().describe("Filter by contract id"),
|
|
500
|
+
sender: z6.string().optional().describe("Filter by sender principal"),
|
|
501
|
+
recipient: z6.string().optional().describe("Filter by recipient principal"),
|
|
502
|
+
assetIdentifier: z6.string().optional().describe("Filter by asset identifier"),
|
|
503
|
+
batchSize: z6.number().optional().describe("Events per page (1–1000, default 100)"),
|
|
504
|
+
maxPages: z6.number().optional().describe("Max pages to walk this call (1–20, default 5)"),
|
|
505
|
+
finalizedOnly: z6.boolean().optional().describe("Only emit finalized events; skip reorg handling")
|
|
506
|
+
}, async (params) => withStreamsAuthHint(async () => {
|
|
507
|
+
const maxPages = Math.min(Math.max(params.maxPages ?? 5, 1), 20);
|
|
508
|
+
const batchSize = Math.min(Math.max(params.batchSize ?? 100, 1), 1000);
|
|
509
|
+
const events = [];
|
|
510
|
+
const reorgs = [];
|
|
511
|
+
const result = await clientProvider().streams.events.consume({
|
|
512
|
+
fromCursor: params.fromCursor ?? null,
|
|
513
|
+
mode: "bounded",
|
|
514
|
+
finalizedOnly: params.finalizedOnly,
|
|
515
|
+
types: params.types,
|
|
516
|
+
notTypes: params.notTypes,
|
|
517
|
+
contractId: params.contractId,
|
|
518
|
+
sender: params.sender,
|
|
519
|
+
recipient: params.recipient,
|
|
520
|
+
assetIdentifier: params.assetIdentifier,
|
|
521
|
+
batchSize,
|
|
522
|
+
maxPages,
|
|
523
|
+
onBatch: (batch) => {
|
|
524
|
+
events.push(...batch);
|
|
525
|
+
},
|
|
526
|
+
onReorg: (reorg) => {
|
|
527
|
+
reorgs.push(reorg);
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
return jsonResponse({
|
|
531
|
+
events,
|
|
532
|
+
reorgs,
|
|
533
|
+
cursor: result.cursor,
|
|
534
|
+
pages: result.pages
|
|
535
|
+
});
|
|
536
|
+
}));
|
|
481
537
|
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))));
|
|
482
538
|
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).", {
|
|
483
539
|
heightOrHash: z6.string().describe("Block height (digits) or block hash (0x… string)")
|
|
@@ -492,6 +548,11 @@ function registerStreamsTools(server, clientProvider = getClient) {
|
|
|
492
548
|
|
|
493
549
|
// src/tools/subgraphs.ts
|
|
494
550
|
import { bundleSubgraphCode } from "@secondlayer/bundler";
|
|
551
|
+
import {
|
|
552
|
+
generateDrizzleSchema,
|
|
553
|
+
generateKyselySchema,
|
|
554
|
+
generatePrismaSchema
|
|
555
|
+
} from "@secondlayer/subgraphs";
|
|
495
556
|
import { z as z7 } from "zod/v4";
|
|
496
557
|
function registerSubgraphTools(server, clientProvider = getClient) {
|
|
497
558
|
defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only.", {}, async () => {
|
|
@@ -568,6 +629,21 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
568
629
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
569
630
|
};
|
|
570
631
|
});
|
|
632
|
+
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.`, {
|
|
633
|
+
name: z7.string().describe("Subgraph name"),
|
|
634
|
+
table: z7.string().describe("Table name"),
|
|
635
|
+
filters: z7.record(z7.string(), z7.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
|
|
636
|
+
count: z7.boolean().optional().describe("Include COUNT(*) of matching rows"),
|
|
637
|
+
countDistinct: z7.array(z7.string()).optional().describe("Columns to count distinct values of"),
|
|
638
|
+
sum: z7.array(z7.string()).optional().describe("Numeric columns to sum (lossless string result)"),
|
|
639
|
+
min: z7.array(z7.string()).optional().describe("Numeric columns to take the minimum of"),
|
|
640
|
+
max: z7.array(z7.string()).optional().describe("Numeric columns to take the maximum of")
|
|
641
|
+
}, async ({ name, table, filters, count, countDistinct, sum, min, max }) => {
|
|
642
|
+
const result = await clientProvider().subgraphs.queryTableAggregate(name, table, { filters, count, countDistinct, sum, min, max });
|
|
643
|
+
return {
|
|
644
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
645
|
+
};
|
|
646
|
+
});
|
|
571
647
|
defineTool(server, "subgraphs_reindex", "Reindex a subgraph from a specific block range. Returns an operationId — poll subgraphs_operation to track progress to completion.", {
|
|
572
648
|
name: z7.string().describe("Subgraph name"),
|
|
573
649
|
fromBlock: z7.number().optional().describe("Start block (defaults to beginning)"),
|
|
@@ -619,6 +695,35 @@ function registerSubgraphTools(server, clientProvider = getClient) {
|
|
|
619
695
|
content: [{ type: "text", text: JSON.stringify(source, null, 2) }]
|
|
620
696
|
};
|
|
621
697
|
});
|
|
698
|
+
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.", {
|
|
699
|
+
code: z7.string().optional().describe("defineSubgraph() source (mutually exclusive with name)"),
|
|
700
|
+
name: z7.string().optional().describe("Deployed subgraph name (mutually exclusive with code)"),
|
|
701
|
+
target: z7.enum(["prisma", "drizzle", "kysely"]).optional().describe("ORM target (default prisma)"),
|
|
702
|
+
schemaName: z7.string().optional().describe("Postgres schema name (defaults to subgraph_<name>)")
|
|
703
|
+
}, async ({ code, name, target = "prisma", schemaName }) => {
|
|
704
|
+
if (code && name || !code && !name) {
|
|
705
|
+
throw new Error("Provide exactly one of `code` or `name`.");
|
|
706
|
+
}
|
|
707
|
+
let source = code;
|
|
708
|
+
if (name) {
|
|
709
|
+
const stored = await clientProvider().subgraphs.getSource(name);
|
|
710
|
+
if (!stored.sourceCode) {
|
|
711
|
+
throw new Error(`Subgraph "${name}" has no captured source (deployed before source capture). Redeploy it, or pass its source as \`code\`.`);
|
|
712
|
+
}
|
|
713
|
+
source = stored.sourceCode;
|
|
714
|
+
}
|
|
715
|
+
const bundled = await bundleSubgraphCode(source);
|
|
716
|
+
const def = {
|
|
717
|
+
name: bundled.name,
|
|
718
|
+
version: bundled.version,
|
|
719
|
+
description: bundled.description,
|
|
720
|
+
sources: bundled.sources,
|
|
721
|
+
schema: bundled.schema,
|
|
722
|
+
handlers: {}
|
|
723
|
+
};
|
|
724
|
+
const out = target === "drizzle" ? generateDrizzleSchema(def, { schemaName }) : target === "kysely" ? generateKyselySchema(def, { schemaName }) : generatePrismaSchema(def, { schemaName });
|
|
725
|
+
return { content: [{ type: "text", text: out }] };
|
|
726
|
+
});
|
|
622
727
|
}
|
|
623
728
|
|
|
624
729
|
// src/tools/subscriptions.ts
|
|
@@ -720,6 +825,12 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
|
|
|
720
825
|
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
721
826
|
};
|
|
722
827
|
});
|
|
828
|
+
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 }) => {
|
|
829
|
+
const res = await clientProvider().subscriptions.test(id);
|
|
830
|
+
return {
|
|
831
|
+
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
832
|
+
};
|
|
833
|
+
});
|
|
723
834
|
defineTool(server, "subscriptions_replay", "Replay a block range for a subscription. Replays run at 10% of batch capacity — use sparingly.", {
|
|
724
835
|
id: z8.string(),
|
|
725
836
|
fromBlock: z8.number().int().nonnegative(),
|
|
@@ -862,5 +973,5 @@ httpServer.listen(port, () => {
|
|
|
862
973
|
console.error("Warning: SECONDLAYER_MCP_SECRET not set, authentication disabled");
|
|
863
974
|
});
|
|
864
975
|
|
|
865
|
-
//# debugId=
|
|
976
|
+
//# debugId=4D9C7C1445EF7B1C64756E2164756E21
|
|
866
977
|
//# sourceMappingURL=bin-http.js.map
|