@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/bin.js CHANGED
@@ -10,6 +10,8 @@ import { fileURLToPath } from "node:url";
10
10
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
11
 
12
12
  // src/resources.ts
13
+ import { CHAIN_TRIGGER_FIELDS, DECODED_EVENT_TYPES } from "@secondlayer/shared";
14
+ import { TRAIT_STANDARDS } from "@secondlayer/stacks/clarity";
13
15
  import { TYPE_MAP } from "@secondlayer/subgraphs/schema";
14
16
 
15
17
  // src/lib/client.ts
@@ -22,10 +24,12 @@ function getClient() {
22
24
  if (!instance) {
23
25
  const apiKey = readApiKey();
24
26
  const baseUrl = process.env.SECONDLAYER_API_URL;
27
+ const dumpsBaseUrl = process.env.SL_STREAMS_DUMPS_URL;
25
28
  instance = new SecondLayer({
26
29
  ...apiKey ? { apiKey } : {},
27
30
  origin: "mcp",
28
- ...baseUrl ? { baseUrl } : {}
31
+ ...baseUrl ? { baseUrl } : {},
32
+ ...dumpsBaseUrl ? { dumpsBaseUrl } : {}
29
33
  });
30
34
  }
31
35
  return instance;
@@ -104,6 +108,11 @@ function defineTool(server, name, description, schema, handler) {
104
108
  }
105
109
 
106
110
  // src/resources.ts
111
+ var TRAIT_BLURBS = {
112
+ "sip-009": "Non-fungible token (NFT) standard",
113
+ "sip-010": "Fungible token standard",
114
+ "sip-013": "Semi-fungible token standard"
115
+ };
107
116
  var FILTERS_REFERENCE = [
108
117
  {
109
118
  type: "stx_transfer",
@@ -198,7 +207,7 @@ function buildCapabilities() {
198
207
  });
199
208
  return {
200
209
  products,
201
- discoverFirst: "Call datasets_list / contracts_find to learn what exists before querying."
210
+ discoverFirst: "Call datasets_list / index_discover / contracts_find to learn what exists (and which filters each surface accepts) before querying."
202
211
  };
203
212
  }
204
213
  var READ_AUTH_TIERS = {
@@ -257,6 +266,56 @@ function registerResources(server) {
257
266
  }
258
267
  ]
259
268
  }));
269
+ server.resource("traits", "secondlayer://traits", {
270
+ description: "SIP trait standards the platform can classify and scaffold against — the valid values for contracts_find `trait` and subgraph scaffold `--trait`."
271
+ }, async () => ({
272
+ contents: [
273
+ {
274
+ uri: "secondlayer://traits",
275
+ mimeType: "application/json",
276
+ text: JSON.stringify({
277
+ traits: TRAIT_STANDARDS.map((id) => ({
278
+ id,
279
+ description: TRAIT_BLURBS[id]
280
+ }))
281
+ }, null, 2)
282
+ }
283
+ ]
284
+ }));
285
+ server.resource("streams-filters", "secondlayer://streams-filters", {
286
+ description: "Streams firehose vocabulary — the decoded event types and the filter fields accepted by streams_events / streams_consume."
287
+ }, async () => ({
288
+ contents: [
289
+ {
290
+ uri: "secondlayer://streams-filters",
291
+ mimeType: "application/json",
292
+ text: JSON.stringify({
293
+ eventTypes: [...DECODED_EVENT_TYPES],
294
+ filters: {
295
+ types: "include only these event types (array)",
296
+ notTypes: "exclude these event types (array)",
297
+ contractId: "match a contract id",
298
+ sender: "match the sender principal",
299
+ recipient: "match the recipient principal",
300
+ assetIdentifier: "match the asset identifier (contract::asset)",
301
+ fromHeight: "start block height (inclusive)",
302
+ toHeight: "end block height (inclusive)"
303
+ }
304
+ }, null, 2)
305
+ }
306
+ ]
307
+ }));
308
+ server.resource("chain-triggers", "secondlayer://chain-triggers", {
309
+ description: "Chain-subscription trigger types and the filter fields each accepts (for subscriptions_create triggers)."
310
+ }, async () => ({
311
+ contents: [
312
+ {
313
+ uri: "secondlayer://chain-triggers",
314
+ mimeType: "application/json",
315
+ text: JSON.stringify(CHAIN_TRIGGER_FIELDS, null, 2)
316
+ }
317
+ ]
318
+ }));
260
319
  }
261
320
 
262
321
  // src/tools/account.ts
@@ -281,7 +340,7 @@ function registerAccountTools(server) {
281
340
  const result = await apiRequest("PATCH", "/api/accounts/me", body);
282
341
  return jsonResponse(result);
283
342
  });
284
- defineTool(server, "account_billing", "Show the account's plan and subscription/billing status. Requires an API key.", {}, async () => {
343
+ 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 () => {
285
344
  const result = await apiRequest("GET", "/api/billing/status");
286
345
  return jsonResponse(result);
287
346
  });
@@ -344,13 +403,13 @@ function registerDatasetTools(server, clientProvider = getClient) {
344
403
  }
345
404
 
346
405
  // src/tools/index.ts
347
- import { DECODED_EVENT_TYPES } from "@secondlayer/shared";
406
+ import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
348
407
  import {
349
- generateIndexSchema,
350
- INDEX_CODEGEN_TABLES
408
+ INDEX_CODEGEN_TABLES,
409
+ generateIndexSchema
351
410
  } from "@secondlayer/subgraphs";
352
411
  import { z as z4 } from "zod/v4";
353
- var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES;
412
+ var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES2;
354
413
  var rangeFilters = {
355
414
  contractId: z4.string().optional().describe("Filter by contract id"),
356
415
  fromHeight: z4.number().optional().describe("Start block height (inclusive)"),
@@ -377,17 +436,19 @@ function registerIndexTools(server, clientProvider = getClient) {
377
436
  recipient: z4.string().optional().describe("Filter by recipient principal"),
378
437
  assetIdentifier: z4.string().optional().describe("Filter by asset identifier (contract::asset)")
379
438
  }, async (params) => jsonResponse(await clientProvider().index.nftTransfers.list(params)));
380
- 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.", {
439
+ 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.", {
381
440
  eventType: z4.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
382
441
  ...rangeFilters,
383
442
  sender: z4.string().optional().describe("Filter by sender principal"),
384
443
  recipient: z4.string().optional().describe("Filter by recipient principal"),
385
- assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable")
444
+ assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable"),
445
+ 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.")
386
446
  }, async (params) => jsonResponse(await clientProvider().index.events.list(params)));
387
- 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.", {
447
+ 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.", {
388
448
  ...rangeFilters,
389
449
  functionName: z4.string().optional().describe("Filter by called function name"),
390
- sender: z4.string().optional().describe("Filter by caller principal")
450
+ sender: z4.string().optional().describe("Filter by caller principal"),
451
+ trait: z4.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
391
452
  }, async (params) => jsonResponse(await clientProvider().index.contractCalls.list(params)));
392
453
  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)));
393
454
  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)));
@@ -423,6 +484,7 @@ function registerIndexTools(server, clientProvider = getClient) {
423
484
  const tx = await clientProvider().index.mempool.get(txId);
424
485
  return tx ? jsonResponse(tx) : notFound(`No pending tx for ${txId}`);
425
486
  });
487
+ 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()));
426
488
  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()));
427
489
  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(", ")}.`, {
428
490
  target: z4.enum(["prisma", "kysely", "drizzle", "json-schema"]).optional().describe("Output target (default kysely)"),
@@ -517,9 +579,9 @@ function registerScaffoldTools(server) {
517
579
 
518
580
  // src/tools/streams.ts
519
581
  import { AuthError } from "@secondlayer/sdk";
520
- import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
582
+ import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES3 } from "@secondlayer/shared";
521
583
  import { z as z7 } from "zod/v4";
522
- var STREAMS_EVENT_TYPES = DECODED_EVENT_TYPES2;
584
+ var STREAMS_EVENT_TYPES = DECODED_EVENT_TYPES3;
523
585
  async function withStreamsAuthHint(fn) {
524
586
  try {
525
587
  return await fn();
@@ -596,6 +658,7 @@ function registerStreamsTools(server, clientProvider = getClient) {
596
658
  }, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.reorgs.list(params))));
597
659
  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))));
598
660
  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())));
661
+ 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()));
599
662
  }
600
663
 
601
664
  // src/tools/subgraphs.ts
@@ -641,7 +704,7 @@ function registerSubgraphTools(server, clientProvider = getClient) {
641
704
  ]
642
705
  };
643
706
  });
644
- 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.', {
707
+ 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>"}.', {
645
708
  name: z8.string().describe("Subgraph name"),
646
709
  table: z8.string().describe("Table name"),
647
710
  filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
@@ -866,7 +929,7 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
866
929
  trait: z9.string().optional(),
867
930
  minAmount: z9.union([z9.string(), z9.number()]).optional(),
868
931
  maxAmount: z9.union([z9.string(), z9.number()]).optional()
869
- })).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."),
932
+ })).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."),
870
933
  url: z9.string().describe("Webhook URL"),
871
934
  format: z9.enum([
872
935
  "standard-webhooks",
@@ -877,17 +940,20 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
877
940
  "raw"
878
941
  ]).optional().describe("Wire format (default standard-webhooks)"),
879
942
  runtime: z9.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
880
- filter: z9.record(z9.string(), z9.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}')
943
+ filter: z9.record(z9.string(), z9.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}'),
944
+ authConfig: z9.record(z9.string(), z9.unknown()).optional().describe('Receiver auth sent with each delivery, e.g. {"type": "bearer", "token": "..."}')
881
945
  }, async (input) => {
882
946
  const res = await clientProvider().subscriptions.create(input);
883
947
  return {
884
948
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
885
949
  };
886
950
  });
887
- defineTool(server, "subscriptions_update", "Patch a subscription (url, filter, format, runtime, retry, timeout, concurrency).", {
951
+ defineTool(server, "subscriptions_update", "Patch a subscription (name, url, filter, authConfig, format, runtime, retry, timeout, concurrency).", {
888
952
  id: z9.string(),
953
+ name: z9.string().optional().describe("Rename the subscription"),
889
954
  url: z9.string().optional(),
890
955
  filter: z9.record(z9.string(), z9.unknown()).optional(),
956
+ authConfig: z9.record(z9.string(), z9.unknown()).optional().describe("Receiver auth sent with each delivery (bearer/etc.)"),
891
957
  format: z9.enum([
892
958
  "standard-webhooks",
893
959
  "inngest",
@@ -936,14 +1002,16 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
936
1002
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
937
1003
  };
938
1004
  });
939
- defineTool(server, "subscriptions_replay", "Replay a block range for a subscription. Replays run at 10% of batch capacity — use sparingly.", {
1005
+ 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.", {
940
1006
  id: z9.string(),
941
1007
  fromBlock: z9.number().int().nonnegative(),
942
- toBlock: z9.number().int().nonnegative()
943
- }, async ({ id, fromBlock, toBlock }) => {
1008
+ toBlock: z9.number().int().nonnegative(),
1009
+ force: z9.string().optional().describe("Idempotency suffix to force a duplicate replay of the range")
1010
+ }, async ({ id, fromBlock, toBlock, force }) => {
944
1011
  const res = await clientProvider().subscriptions.replay(id, {
945
1012
  fromBlock,
946
- toBlock
1013
+ toBlock,
1014
+ ...force !== undefined ? { force } : {}
947
1015
  });
948
1016
  return {
949
1017
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
@@ -998,5 +1066,5 @@ var server = createServer();
998
1066
  var transport = new StdioServerTransport;
999
1067
  await server.connect(transport);
1000
1068
 
1001
- //# debugId=95B17975F7D570CF64756E2164756E21
1069
+ //# debugId=A418D04474BCA8ED64756E2164756E21
1002
1070
  //# sourceMappingURL=bin.js.map