@secondlayer/mcp 3.6.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,6 +5,8 @@ import { fileURLToPath } from "node:url";
5
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
6
 
7
7
  // src/resources.ts
8
+ import { CHAIN_TRIGGER_FIELDS, DECODED_EVENT_TYPES } from "@secondlayer/shared";
9
+ import { TRAIT_STANDARDS } from "@secondlayer/stacks/clarity";
8
10
  import { TYPE_MAP } from "@secondlayer/subgraphs/schema";
9
11
 
10
12
  // src/lib/client.ts
@@ -17,10 +19,12 @@ function getClient() {
17
19
  if (!instance) {
18
20
  const apiKey = readApiKey();
19
21
  const baseUrl = process.env.SECONDLAYER_API_URL;
22
+ const dumpsBaseUrl = process.env.SL_STREAMS_DUMPS_URL;
20
23
  instance = new SecondLayer({
21
24
  ...apiKey ? { apiKey } : {},
22
25
  origin: "mcp",
23
- ...baseUrl ? { baseUrl } : {}
26
+ ...baseUrl ? { baseUrl } : {},
27
+ ...dumpsBaseUrl ? { dumpsBaseUrl } : {}
24
28
  });
25
29
  }
26
30
  return instance;
@@ -99,6 +103,11 @@ function defineTool(server, name, description, schema, handler) {
99
103
  }
100
104
 
101
105
  // src/resources.ts
106
+ var TRAIT_BLURBS = {
107
+ "sip-009": "Non-fungible token (NFT) standard",
108
+ "sip-010": "Fungible token standard",
109
+ "sip-013": "Semi-fungible token standard"
110
+ };
102
111
  var FILTERS_REFERENCE = [
103
112
  {
104
113
  type: "stx_transfer",
@@ -193,7 +202,7 @@ function buildCapabilities() {
193
202
  });
194
203
  return {
195
204
  products,
196
- discoverFirst: "Call datasets_list / contracts_find to learn what exists before querying."
205
+ discoverFirst: "Call datasets_list / index_discover / contracts_find to learn what exists (and which filters each surface accepts) before querying."
197
206
  };
198
207
  }
199
208
  var READ_AUTH_TIERS = {
@@ -252,6 +261,56 @@ function registerResources(server) {
252
261
  }
253
262
  ]
254
263
  }));
264
+ server.resource("traits", "secondlayer://traits", {
265
+ description: "SIP trait standards the platform can classify and scaffold against — the valid values for contracts_find `trait` and subgraph scaffold `--trait`."
266
+ }, async () => ({
267
+ contents: [
268
+ {
269
+ uri: "secondlayer://traits",
270
+ mimeType: "application/json",
271
+ text: JSON.stringify({
272
+ traits: TRAIT_STANDARDS.map((id) => ({
273
+ id,
274
+ description: TRAIT_BLURBS[id]
275
+ }))
276
+ }, null, 2)
277
+ }
278
+ ]
279
+ }));
280
+ server.resource("streams-filters", "secondlayer://streams-filters", {
281
+ description: "Streams firehose vocabulary — the decoded event types and the filter fields accepted by streams_events / streams_consume."
282
+ }, async () => ({
283
+ contents: [
284
+ {
285
+ uri: "secondlayer://streams-filters",
286
+ mimeType: "application/json",
287
+ text: JSON.stringify({
288
+ eventTypes: [...DECODED_EVENT_TYPES],
289
+ filters: {
290
+ types: "include only these event types (array)",
291
+ notTypes: "exclude these event types (array)",
292
+ contractId: "match a contract id",
293
+ sender: "match the sender principal",
294
+ recipient: "match the recipient principal",
295
+ assetIdentifier: "match the asset identifier (contract::asset)",
296
+ fromHeight: "start block height (inclusive)",
297
+ toHeight: "end block height (inclusive)"
298
+ }
299
+ }, null, 2)
300
+ }
301
+ ]
302
+ }));
303
+ server.resource("chain-triggers", "secondlayer://chain-triggers", {
304
+ description: "Chain-subscription trigger types and the filter fields each accepts (for subscriptions_create triggers)."
305
+ }, async () => ({
306
+ contents: [
307
+ {
308
+ uri: "secondlayer://chain-triggers",
309
+ mimeType: "application/json",
310
+ text: JSON.stringify(CHAIN_TRIGGER_FIELDS, null, 2)
311
+ }
312
+ ]
313
+ }));
255
314
  }
256
315
 
257
316
  // src/tools/account.ts
@@ -276,7 +335,7 @@ function registerAccountTools(server) {
276
335
  const result = await apiRequest("PATCH", "/api/accounts/me", body);
277
336
  return jsonResponse(result);
278
337
  });
279
- defineTool(server, "account_billing", "Show the account's plan and subscription/billing status. Requires an API key.", {}, async () => {
338
+ defineTool(server, "account_billing", "Show the account's plan and subscription/billing status. Requires an API key. (Plan upgrade / Stripe portal / checkout are deliberately not agent tools — they are session-only human-payment flows; use account_set_caps to bound spend.)", {}, async () => {
280
339
  const result = await apiRequest("GET", "/api/billing/status");
281
340
  return jsonResponse(result);
282
341
  });
@@ -339,13 +398,13 @@ function registerDatasetTools(server, clientProvider = getClient) {
339
398
  }
340
399
 
341
400
  // src/tools/index.ts
342
- import { DECODED_EVENT_TYPES } from "@secondlayer/shared";
401
+ import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
343
402
  import {
344
- generateIndexSchema,
345
- INDEX_CODEGEN_TABLES
403
+ INDEX_CODEGEN_TABLES,
404
+ generateIndexSchema
346
405
  } from "@secondlayer/subgraphs";
347
406
  import { z as z4 } from "zod/v4";
348
- var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES;
407
+ var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES2;
349
408
  var rangeFilters = {
350
409
  contractId: z4.string().optional().describe("Filter by contract id"),
351
410
  fromHeight: z4.number().optional().describe("Start block height (inclusive)"),
@@ -372,17 +431,19 @@ function registerIndexTools(server, clientProvider = getClient) {
372
431
  recipient: z4.string().optional().describe("Filter by recipient principal"),
373
432
  assetIdentifier: z4.string().optional().describe("Filter by asset identifier (contract::asset)")
374
433
  }, async (params) => jsonResponse(await clientProvider().index.nftTransfers.list(params)));
375
- defineTool(server, "index_events", "List decoded chain events from the Index by event type. Use this for event types without a dedicated tool (stx_*, ft_mint/burn, nft_mint/burn, print). For ft/nft transfers prefer index_ft_transfers / index_nft_transfers.", {
434
+ defineTool(server, "index_events", "List decoded chain events from the Index by event type. Use this for event types without a dedicated tool (stx_*, ft_mint/burn, nft_mint/burn, print), and for trait-scoped queries: set `trait` (e.g. sip-010) to match all contracts conforming to a standard — pair with contracts_find to discover traits. For ft/nft transfers without a trait prefer index_ft_transfers / index_nft_transfers.", {
376
435
  eventType: z4.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
377
436
  ...rangeFilters,
378
437
  sender: z4.string().optional().describe("Filter by sender principal"),
379
438
  recipient: z4.string().optional().describe("Filter by recipient principal"),
380
- assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable")
439
+ assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable"),
440
+ trait: z4.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId; contract-keyed event types only.")
381
441
  }, async (params) => jsonResponse(await clientProvider().index.events.list(params)));
382
- defineTool(server, "index_contract_calls", "List decoded contract calls from the Index (function name, args, result). Note: contract-call cursors are a SEPARATE keyspace from event cursors — they are not interchangeable.", {
442
+ defineTool(server, "index_contract_calls", "List decoded contract calls from the Index (function name, args, result). Set `trait` (e.g. sip-010) to match calls to all contracts conforming to a standard. Note: contract-call cursors are a SEPARATE keyspace from event cursors — they are not interchangeable.", {
383
443
  ...rangeFilters,
384
444
  functionName: z4.string().optional().describe("Filter by called function name"),
385
- sender: z4.string().optional().describe("Filter by caller principal")
445
+ sender: z4.string().optional().describe("Filter by caller principal"),
446
+ trait: z4.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
386
447
  }, async (params) => jsonResponse(await clientProvider().index.contractCalls.list(params)));
387
448
  defineTool(server, "index_canonical", "List the canonical Stacks block sequence from the Index (height + hash). Anonymous reads allowed (free-tier keys rejected).", { ...heightFilters }, async (params) => jsonResponse(await clientProvider().index.canonical.list(params)));
388
449
  defineTool(server, "index_blocks", "List decoded blocks from the Index. Anonymous reads allowed (free-tier keys rejected).", { ...heightFilters }, async (params) => jsonResponse(await clientProvider().index.blocks.list(params)));
@@ -418,6 +479,7 @@ function registerIndexTools(server, clientProvider = getClient) {
418
479
  const tx = await clientProvider().index.mempool.get(txId);
419
480
  return tx ? jsonResponse(tx) : notFound(`No pending tx for ${txId}`);
420
481
  });
482
+ defineTool(server, "index_discover", "Discover the Index vocabulary: every event type and its columns, allowed/equality filters, and required-non-null fields (and which types accept `trait`). Read this before building Index queries instead of guessing filters. Anonymous read.", {}, async () => jsonResponse(await clientProvider().index.discover()));
421
483
  defineTool(server, "index_usage", "Your own Index consumption (decoded events today + this month) and tier limits. Requires a Build+ API key (anonymous reads can't report usage).", {}, async () => jsonResponse(await clientProvider().index.usage()));
422
484
  defineTool(server, "index_codegen", `Generate a typed schema (Prisma, Kysely, Drizzle, or JSON-Schema) for the public Index domain tables so they can be mirrored into a BYO database with full types. Returns the schema as text. Tables: ${INDEX_CODEGEN_TABLES.join(", ")}.`, {
423
485
  target: z4.enum(["prisma", "kysely", "drizzle", "json-schema"]).optional().describe("Output target (default kysely)"),
@@ -512,9 +574,9 @@ function registerScaffoldTools(server) {
512
574
 
513
575
  // src/tools/streams.ts
514
576
  import { AuthError } from "@secondlayer/sdk";
515
- import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
577
+ import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES3 } from "@secondlayer/shared";
516
578
  import { z as z7 } from "zod/v4";
517
- var STREAMS_EVENT_TYPES = DECODED_EVENT_TYPES2;
579
+ var STREAMS_EVENT_TYPES = DECODED_EVENT_TYPES3;
518
580
  async function withStreamsAuthHint(fn) {
519
581
  try {
520
582
  return await fn();
@@ -591,6 +653,7 @@ function registerStreamsTools(server, clientProvider = getClient) {
591
653
  }, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.reorgs.list(params))));
592
654
  defineTool(server, "streams_canonical", "Get the canonical block at a given height from Streams (height + hashes + is_canonical). Streams requires an API key (SL_API_KEY).", { height: z7.number().describe("Block height") }, async ({ height }) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.canonical(height))));
593
655
  defineTool(server, "streams_usage", "Your own Streams consumption (events today + this month) and tier limits (rate limit, retention). Streams requires an API key (SL_API_KEY).", {}, async () => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.usage())));
656
+ defineTool(server, "streams_dumps", "List the Streams bulk parquet dumps manifest — coverage range, latest_finalized_cursor, and per-file metadata (block range, row count, size, sha256, signed URL). This is the cold backfill path for downloading all raw data; fetch the file URLs directly (e.g. with DuckDB). Requires the dumps base URL to be configured (SL_STREAMS_DUMPS_URL).", {}, async () => jsonResponse(await clientProvider().streams.dumps.list()));
594
657
  }
595
658
 
596
659
  // src/tools/subgraphs.ts
@@ -636,7 +699,7 @@ function registerSubgraphTools(server, clientProvider = getClient) {
636
699
  ]
637
700
  };
638
701
  });
639
- 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.', {
702
+ defineTool(server, "subgraphs_query", 'Query rows from a subgraph table (max 200 rows). Filters support operators: "amount.gte": "1000", "sender.neq": "SP...", "name.like": "%token%". Available operators: eq, neq, gt, gte, lt, lte, like. To TAIL new rows (no streaming over MCP): sort=_id, order=desc for the latest, then poll forward with the filter {"_id.gt": "<last _id seen>"}, order=asc. Fetch one row by id with {"_id": "<id>"}.', {
640
703
  name: z8.string().describe("Subgraph name"),
641
704
  table: z8.string().describe("Table name"),
642
705
  filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
@@ -861,7 +924,7 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
861
924
  trait: z9.string().optional(),
862
925
  minAmount: z9.union([z9.string(), z9.number()]).optional(),
863
926
  maxAmount: z9.union([z9.string(), z9.number()]).optional()
864
- })).optional().describe("Chain triggers (chain subscription) — provide INSTEAD of subgraphName/tableName. Each targets a raw chain event/tx; string fields accept `*` wildcards, `trait` scopes to a SIP/trait. Forward-looking: starts at chain tip, no backfill."),
927
+ })).optional().describe("Chain triggers (chain subscription) — provide INSTEAD of subgraphName/tableName. Each targets a raw chain event/tx; string fields accept `*` wildcards, `trait` scopes to a SIP/trait. Per-type accepted fields: see the secondlayer://chain-triggers resource. Forward-looking: starts at chain tip, no backfill."),
865
928
  url: z9.string().describe("Webhook URL"),
866
929
  format: z9.enum([
867
930
  "standard-webhooks",
@@ -872,17 +935,20 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
872
935
  "raw"
873
936
  ]).optional().describe("Wire format (default standard-webhooks)"),
874
937
  runtime: z9.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
875
- filter: z9.record(z9.string(), z9.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}')
938
+ filter: z9.record(z9.string(), z9.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}'),
939
+ authConfig: z9.record(z9.string(), z9.unknown()).optional().describe('Receiver auth sent with each delivery, e.g. {"type": "bearer", "token": "..."}')
876
940
  }, async (input) => {
877
941
  const res = await clientProvider().subscriptions.create(input);
878
942
  return {
879
943
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
880
944
  };
881
945
  });
882
- defineTool(server, "subscriptions_update", "Patch a subscription (url, filter, format, runtime, retry, timeout, concurrency).", {
946
+ defineTool(server, "subscriptions_update", "Patch a subscription (name, url, filter, authConfig, format, runtime, retry, timeout, concurrency).", {
883
947
  id: z9.string(),
948
+ name: z9.string().optional().describe("Rename the subscription"),
884
949
  url: z9.string().optional(),
885
950
  filter: z9.record(z9.string(), z9.unknown()).optional(),
951
+ authConfig: z9.record(z9.string(), z9.unknown()).optional().describe("Receiver auth sent with each delivery (bearer/etc.)"),
886
952
  format: z9.enum([
887
953
  "standard-webhooks",
888
954
  "inngest",
@@ -931,14 +997,16 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
931
997
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
932
998
  };
933
999
  });
934
- defineTool(server, "subscriptions_replay", "Replay a block range for a subscription. Replays run at 10% of batch capacity — use sparingly.", {
1000
+ defineTool(server, "subscriptions_replay", "Replay a block range for a subscription. Replays run at 10% of batch capacity — use sparingly. Pass `force` (a short idempotency suffix) to re-run a range that was already replayed.", {
935
1001
  id: z9.string(),
936
1002
  fromBlock: z9.number().int().nonnegative(),
937
- toBlock: z9.number().int().nonnegative()
938
- }, async ({ id, fromBlock, toBlock }) => {
1003
+ toBlock: z9.number().int().nonnegative(),
1004
+ force: z9.string().optional().describe("Idempotency suffix to force a duplicate replay of the range")
1005
+ }, async ({ id, fromBlock, toBlock, force }) => {
939
1006
  const res = await clientProvider().subscriptions.replay(id, {
940
1007
  fromBlock,
941
- toBlock
1008
+ toBlock,
1009
+ ...force !== undefined ? { force } : {}
942
1010
  });
943
1011
  return {
944
1012
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
@@ -991,5 +1059,5 @@ export {
991
1059
  createServer
992
1060
  };
993
1061
 
994
- //# debugId=ADEBB38A46A46AF464756E2164756E21
1062
+ //# debugId=B30584194FF8002264756E2164756E21
995
1063
  //# sourceMappingURL=index.js.map