@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-http.js CHANGED
@@ -13,6 +13,8 @@ import { fileURLToPath } from "node:url";
13
13
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
14
 
15
15
  // src/resources.ts
16
+ import { CHAIN_TRIGGER_FIELDS, DECODED_EVENT_TYPES } from "@secondlayer/shared";
17
+ import { TRAIT_STANDARDS } from "@secondlayer/stacks/clarity";
16
18
  import { TYPE_MAP } from "@secondlayer/subgraphs/schema";
17
19
 
18
20
  // src/lib/client.ts
@@ -25,10 +27,12 @@ function getClient() {
25
27
  if (!instance) {
26
28
  const apiKey = readApiKey();
27
29
  const baseUrl = process.env.SECONDLAYER_API_URL;
30
+ const dumpsBaseUrl = process.env.SL_STREAMS_DUMPS_URL;
28
31
  instance = new SecondLayer({
29
32
  ...apiKey ? { apiKey } : {},
30
33
  origin: "mcp",
31
- ...baseUrl ? { baseUrl } : {}
34
+ ...baseUrl ? { baseUrl } : {},
35
+ ...dumpsBaseUrl ? { dumpsBaseUrl } : {}
32
36
  });
33
37
  }
34
38
  return instance;
@@ -107,6 +111,11 @@ function defineTool(server, name, description, schema, handler) {
107
111
  }
108
112
 
109
113
  // src/resources.ts
114
+ var TRAIT_BLURBS = {
115
+ "sip-009": "Non-fungible token (NFT) standard",
116
+ "sip-010": "Fungible token standard",
117
+ "sip-013": "Semi-fungible token standard"
118
+ };
110
119
  var FILTERS_REFERENCE = [
111
120
  {
112
121
  type: "stx_transfer",
@@ -201,7 +210,7 @@ function buildCapabilities() {
201
210
  });
202
211
  return {
203
212
  products,
204
- discoverFirst: "Call datasets_list / contracts_find to learn what exists before querying."
213
+ discoverFirst: "Call datasets_list / index_discover / contracts_find to learn what exists (and which filters each surface accepts) before querying."
205
214
  };
206
215
  }
207
216
  var READ_AUTH_TIERS = {
@@ -260,6 +269,56 @@ function registerResources(server) {
260
269
  }
261
270
  ]
262
271
  }));
272
+ server.resource("traits", "secondlayer://traits", {
273
+ description: "SIP trait standards the platform can classify and scaffold against — the valid values for contracts_find `trait` and subgraph scaffold `--trait`."
274
+ }, async () => ({
275
+ contents: [
276
+ {
277
+ uri: "secondlayer://traits",
278
+ mimeType: "application/json",
279
+ text: JSON.stringify({
280
+ traits: TRAIT_STANDARDS.map((id) => ({
281
+ id,
282
+ description: TRAIT_BLURBS[id]
283
+ }))
284
+ }, null, 2)
285
+ }
286
+ ]
287
+ }));
288
+ server.resource("streams-filters", "secondlayer://streams-filters", {
289
+ description: "Streams firehose vocabulary — the decoded event types and the filter fields accepted by streams_events / streams_consume."
290
+ }, async () => ({
291
+ contents: [
292
+ {
293
+ uri: "secondlayer://streams-filters",
294
+ mimeType: "application/json",
295
+ text: JSON.stringify({
296
+ eventTypes: [...DECODED_EVENT_TYPES],
297
+ filters: {
298
+ types: "include only these event types (array)",
299
+ notTypes: "exclude these event types (array)",
300
+ contractId: "match a contract id",
301
+ sender: "match the sender principal",
302
+ recipient: "match the recipient principal",
303
+ assetIdentifier: "match the asset identifier (contract::asset)",
304
+ fromHeight: "start block height (inclusive)",
305
+ toHeight: "end block height (inclusive)"
306
+ }
307
+ }, null, 2)
308
+ }
309
+ ]
310
+ }));
311
+ server.resource("chain-triggers", "secondlayer://chain-triggers", {
312
+ description: "Chain-subscription trigger types and the filter fields each accepts (for subscriptions_create triggers)."
313
+ }, async () => ({
314
+ contents: [
315
+ {
316
+ uri: "secondlayer://chain-triggers",
317
+ mimeType: "application/json",
318
+ text: JSON.stringify(CHAIN_TRIGGER_FIELDS, null, 2)
319
+ }
320
+ ]
321
+ }));
263
322
  }
264
323
 
265
324
  // src/tools/account.ts
@@ -284,7 +343,7 @@ function registerAccountTools(server) {
284
343
  const result = await apiRequest("PATCH", "/api/accounts/me", body);
285
344
  return jsonResponse(result);
286
345
  });
287
- defineTool(server, "account_billing", "Show the account's plan and subscription/billing status. Requires an API key.", {}, async () => {
346
+ 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 () => {
288
347
  const result = await apiRequest("GET", "/api/billing/status");
289
348
  return jsonResponse(result);
290
349
  });
@@ -347,13 +406,13 @@ function registerDatasetTools(server, clientProvider = getClient) {
347
406
  }
348
407
 
349
408
  // src/tools/index.ts
350
- import { DECODED_EVENT_TYPES } from "@secondlayer/shared";
409
+ import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
351
410
  import {
352
- generateIndexSchema,
353
- INDEX_CODEGEN_TABLES
411
+ INDEX_CODEGEN_TABLES,
412
+ generateIndexSchema
354
413
  } from "@secondlayer/subgraphs";
355
414
  import { z as z4 } from "zod/v4";
356
- var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES;
415
+ var INDEX_EVENT_TYPES = DECODED_EVENT_TYPES2;
357
416
  var rangeFilters = {
358
417
  contractId: z4.string().optional().describe("Filter by contract id"),
359
418
  fromHeight: z4.number().optional().describe("Start block height (inclusive)"),
@@ -380,17 +439,19 @@ function registerIndexTools(server, clientProvider = getClient) {
380
439
  recipient: z4.string().optional().describe("Filter by recipient principal"),
381
440
  assetIdentifier: z4.string().optional().describe("Filter by asset identifier (contract::asset)")
382
441
  }, async (params) => jsonResponse(await clientProvider().index.nftTransfers.list(params)));
383
- 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.", {
442
+ 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.", {
384
443
  eventType: z4.enum(INDEX_EVENT_TYPES).describe("Required. Decoded event type to list."),
385
444
  ...rangeFilters,
386
445
  sender: z4.string().optional().describe("Filter by sender principal"),
387
446
  recipient: z4.string().optional().describe("Filter by recipient principal"),
388
- assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable")
447
+ assetIdentifier: z4.string().optional().describe("Filter by asset identifier where applicable"),
448
+ 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.")
389
449
  }, async (params) => jsonResponse(await clientProvider().index.events.list(params)));
390
- 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.", {
450
+ 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.", {
391
451
  ...rangeFilters,
392
452
  functionName: z4.string().optional().describe("Filter by called function name"),
393
- sender: z4.string().optional().describe("Filter by caller principal")
453
+ sender: z4.string().optional().describe("Filter by caller principal"),
454
+ trait: z4.string().optional().describe("Match contracts conforming to a trait/standard (e.g. sip-010). Mutually exclusive with contractId.")
394
455
  }, async (params) => jsonResponse(await clientProvider().index.contractCalls.list(params)));
395
456
  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)));
396
457
  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)));
@@ -426,6 +487,7 @@ function registerIndexTools(server, clientProvider = getClient) {
426
487
  const tx = await clientProvider().index.mempool.get(txId);
427
488
  return tx ? jsonResponse(tx) : notFound(`No pending tx for ${txId}`);
428
489
  });
490
+ 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()));
429
491
  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()));
430
492
  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(", ")}.`, {
431
493
  target: z4.enum(["prisma", "kysely", "drizzle", "json-schema"]).optional().describe("Output target (default kysely)"),
@@ -520,9 +582,9 @@ function registerScaffoldTools(server) {
520
582
 
521
583
  // src/tools/streams.ts
522
584
  import { AuthError } from "@secondlayer/sdk";
523
- import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES2 } from "@secondlayer/shared";
585
+ import { DECODED_EVENT_TYPES as DECODED_EVENT_TYPES3 } from "@secondlayer/shared";
524
586
  import { z as z7 } from "zod/v4";
525
- var STREAMS_EVENT_TYPES = DECODED_EVENT_TYPES2;
587
+ var STREAMS_EVENT_TYPES = DECODED_EVENT_TYPES3;
526
588
  async function withStreamsAuthHint(fn) {
527
589
  try {
528
590
  return await fn();
@@ -599,6 +661,7 @@ function registerStreamsTools(server, clientProvider = getClient) {
599
661
  }, async (params) => withStreamsAuthHint(async () => jsonResponse(await clientProvider().streams.reorgs.list(params))));
600
662
  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))));
601
663
  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())));
664
+ 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()));
602
665
  }
603
666
 
604
667
  // src/tools/subgraphs.ts
@@ -644,7 +707,7 @@ function registerSubgraphTools(server, clientProvider = getClient) {
644
707
  ]
645
708
  };
646
709
  });
647
- 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.', {
710
+ 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>"}.', {
648
711
  name: z8.string().describe("Subgraph name"),
649
712
  table: z8.string().describe("Table name"),
650
713
  filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
@@ -869,7 +932,7 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
869
932
  trait: z9.string().optional(),
870
933
  minAmount: z9.union([z9.string(), z9.number()]).optional(),
871
934
  maxAmount: z9.union([z9.string(), z9.number()]).optional()
872
- })).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."),
935
+ })).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."),
873
936
  url: z9.string().describe("Webhook URL"),
874
937
  format: z9.enum([
875
938
  "standard-webhooks",
@@ -880,17 +943,20 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
880
943
  "raw"
881
944
  ]).optional().describe("Wire format (default standard-webhooks)"),
882
945
  runtime: z9.enum(["inngest", "trigger", "cloudflare", "node"]).optional().describe("Receiver runtime label (display only)"),
883
- filter: z9.record(z9.string(), z9.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}')
946
+ filter: z9.record(z9.string(), z9.unknown()).optional().describe('Scalar filter DSL, e.g. {"amount": {"gte": 100}, "sender": "SP..."}'),
947
+ authConfig: z9.record(z9.string(), z9.unknown()).optional().describe('Receiver auth sent with each delivery, e.g. {"type": "bearer", "token": "..."}')
884
948
  }, async (input) => {
885
949
  const res = await clientProvider().subscriptions.create(input);
886
950
  return {
887
951
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
888
952
  };
889
953
  });
890
- defineTool(server, "subscriptions_update", "Patch a subscription (url, filter, format, runtime, retry, timeout, concurrency).", {
954
+ defineTool(server, "subscriptions_update", "Patch a subscription (name, url, filter, authConfig, format, runtime, retry, timeout, concurrency).", {
891
955
  id: z9.string(),
956
+ name: z9.string().optional().describe("Rename the subscription"),
892
957
  url: z9.string().optional(),
893
958
  filter: z9.record(z9.string(), z9.unknown()).optional(),
959
+ authConfig: z9.record(z9.string(), z9.unknown()).optional().describe("Receiver auth sent with each delivery (bearer/etc.)"),
894
960
  format: z9.enum([
895
961
  "standard-webhooks",
896
962
  "inngest",
@@ -939,14 +1005,16 @@ function registerSubscriptionTools(server, clientProvider = getClient) {
939
1005
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
940
1006
  };
941
1007
  });
942
- defineTool(server, "subscriptions_replay", "Replay a block range for a subscription. Replays run at 10% of batch capacity — use sparingly.", {
1008
+ 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.", {
943
1009
  id: z9.string(),
944
1010
  fromBlock: z9.number().int().nonnegative(),
945
- toBlock: z9.number().int().nonnegative()
946
- }, async ({ id, fromBlock, toBlock }) => {
1011
+ toBlock: z9.number().int().nonnegative(),
1012
+ force: z9.string().optional().describe("Idempotency suffix to force a duplicate replay of the range")
1013
+ }, async ({ id, fromBlock, toBlock, force }) => {
947
1014
  const res = await clientProvider().subscriptions.replay(id, {
948
1015
  fromBlock,
949
- toBlock
1016
+ toBlock,
1017
+ ...force !== undefined ? { force } : {}
950
1018
  });
951
1019
  return {
952
1020
  content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
@@ -1082,5 +1150,5 @@ httpServer.listen(port, () => {
1082
1150
  console.error("Warning: SECONDLAYER_MCP_SECRET not set, authentication disabled");
1083
1151
  });
1084
1152
 
1085
- //# debugId=DC56651C5FD27C3364756E2164756E21
1153
+ //# debugId=330454369B037F3564756E2164756E21
1086
1154
  //# sourceMappingURL=bin-http.js.map