@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 CHANGED
@@ -52,7 +52,7 @@ bunx -p @secondlayer/mcp secondlayer-mcp-http
52
52
 
53
53
  | Domain | Tools |
54
54
  | --- | --- |
55
- | **Subgraphs** (7) | `subgraphs_list`, `subgraphs_get`, `subgraphs_query`, `subgraphs_reindex`, `subgraphs_delete`, `subgraphs_deploy`, `subgraphs_read_source` |
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: "nft_transfer",
129
- fields: ["sender", "recipient", "assetIdentifier", "tokenId"]
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: "uint",
140
- sqlType: "bigint",
141
- description: "Unsigned integer (Clarity uint)"
127
+ type: "ft_burn",
128
+ fields: ["sender", "assetIdentifier", "minAmount", "trait"]
142
129
  },
143
130
  {
144
- type: "int",
145
- sqlType: "bigint",
146
- description: "Signed integer (Clarity int)"
131
+ type: "nft_transfer",
132
+ fields: ["sender", "recipient", "assetIdentifier", "trait"]
147
133
  },
148
- { type: "text", sqlType: "text", description: "UTF-8 string" },
134
+ { type: "nft_mint", fields: ["recipient", "assetIdentifier", "trait"] },
135
+ { type: "nft_burn", fields: ["sender", "assetIdentifier", "trait"] },
149
136
  {
150
- type: "principal",
151
- sqlType: "text",
152
- description: "Stacks address (standard or contract)"
137
+ type: "contract_call",
138
+ fields: ["contractId", "functionName", "caller", "trait", "abi"]
153
139
  },
154
- { type: "bool", sqlType: "boolean", description: "Boolean value" },
155
- { type: "json", sqlType: "jsonb", description: "Arbitrary JSON data" },
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=587F4DDF9A78B13764756E2164756E21
976
+ //# debugId=4D9C7C1445EF7B1C64756E2164756E21
866
977
  //# sourceMappingURL=bin-http.js.map