@secondlayer/mcp 3.7.0 → 3.9.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
@@ -10,7 +10,7 @@ bun add @secondlayer/mcp
10
10
 
11
11
  ## Auth
12
12
 
13
- Most reads are public — subgraph reads, `datasets_*`, `index_*`, and `contracts_find` work with no key. **`streams_*` requires an `SL_API_KEY`** (and the Index tools reject free-tier keys — Build+ for keyed access). Writes (deploy, reindex, delete, subscriptions) and account tools need a key: create one (prefixed `sk-sl_`) in the platform console at https://secondlayer.tools/platform/api-keys and set it as `SL_API_KEY`. Read `secondlayer://context` first — it reports auth state and read-auth tiers.
13
+ Most reads are public — `datasets_*`, `index_*`, and `contracts_find` work with no key. Subgraph tools need an `SL_API_KEY`; separately, **public** subgraphs are anon-readable over HTTP at `GET /v1/subgraphs/<name>/<table>` (`{ rows, next_cursor, tip }` cursor envelope), while private ones need the owning account's key (anon → 404). **`streams_*` requires an `SL_API_KEY`** (and the Index tools reject free-tier keys — Build+ for keyed access). Writes (deploy, publish/unpublish, reindex, delete, subscriptions) and account tools need a key: create one (prefixed `sk-sl_`) in the platform console at https://secondlayer.tools/platform/api-keys and set it as `SL_API_KEY`. Read `secondlayer://context` first — it reports auth state and read-auth tiers.
14
14
 
15
15
  ## Quick Start — Stdio (IDE)
16
16
 
@@ -52,7 +52,7 @@ bunx -p @secondlayer/mcp secondlayer-mcp-http
52
52
 
53
53
  | Domain | Tools |
54
54
  | --- | --- |
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` |
55
+ | **Subgraphs** (16) | `subgraphs_list`, `subgraphs_get`, `subgraphs_spec`, `subgraphs_query`, `subgraphs_aggregate`, `subgraphs_reindex`, `subgraphs_operation`, `subgraphs_backfill`, `subgraphs_stop`, `subgraphs_gaps`, `subgraphs_delete`, `subgraphs_deploy`, `subgraphs_publish`, `subgraphs_unpublish`, `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` |
@@ -75,6 +75,10 @@ a webhook on raw chain events (contract / event / function / trait) with no
75
75
  subgraph (e.g. `[{ "type": "contract_call", "contractId": "SP....amm",
76
76
  "functionName": "swap-*" }]`).
77
77
 
78
+ ### Subgraph visibility
79
+
80
+ `subgraphs_deploy` takes a `visibility` param (`public` | `private`; defaults: managed → public, BYO `databaseUrl` → private). Flip later with `subgraphs_publish` / `subgraphs_unpublish` — publishing claims the name in the single global public namespace (409 `PUBLIC_NAME_TAKEN` if claimed). Public subgraphs are anon-readable at `GET /v1/subgraphs/<name>/<table>`.
81
+
78
82
  ### `subgraphs_query` enhancements
79
83
 
80
84
  - `fields` — comma-separated column projection (e.g. `"sender,amount_x"`)
package/dist/bin-http.js CHANGED
@@ -61,11 +61,14 @@ async function apiRequest(method, path, body) {
61
61
 
62
62
  // src/lib/format.ts
63
63
  function formatSubgraphSummary(s) {
64
+ const apiUrl = process.env.SECONDLAYER_API_URL || "https://api.secondlayer.tools";
64
65
  return {
65
66
  name: s.name,
66
67
  status: s.status,
67
68
  tables: Array.isArray(s.tables) ? s.tables : Object.keys(s.tables),
68
- lastProcessedBlock: s.lastProcessedBlock
69
+ lastProcessedBlock: s.lastProcessedBlock,
70
+ ...s.visibility ? { visibility: s.visibility } : {},
71
+ ...s.visibility === "public" ? { publicUrl: `${apiUrl}/v1/subgraphs/${s.name}` } : {}
69
72
  };
70
73
  }
71
74
  function withCap(items, cap) {
@@ -217,7 +220,7 @@ var READ_AUTH_TIERS = {
217
220
  datasets: "open — no API key required",
218
221
  index: "anonymous reads allowed; free-tier API keys are rejected (Build+ required)",
219
222
  streams: "API key required (SL_API_KEY) — keyless calls return 401",
220
- subgraphs: "reads public during open beta; writes require an API key"
223
+ subgraphs: "public subgraphs anon-readable at /v1/subgraphs/<name>/<table> ({ rows, next_cursor, tip } cursor envelope); private subgraphs need the owning account's API key (anon → 404); writes require an API key"
221
224
  };
222
225
  async function buildContext(deps = { clientProvider: getClient }) {
223
226
  const unavailable = "unavailable: set SL_API_KEY";
@@ -371,6 +374,7 @@ function registerAccountTools(server) {
371
374
  }
372
375
 
373
376
  // src/tools/contracts.ts
377
+ import { generateContractInterface } from "@secondlayer/scaffold";
374
378
  import { z as z2 } from "zod/v4";
375
379
  function registerContractTools(server, clientProvider = getClient) {
376
380
  defineTool(server, "contracts_find", 'Discover deployed Stacks contracts conforming to a trait (e.g. "sip-010", "sip-009", "sip-013"). The discovery endpoint for "which contracts implement X". Reads are public.', {
@@ -380,6 +384,47 @@ function registerContractTools(server, clientProvider = getClient) {
380
384
  limit: z2.number().optional().describe("Page size, 1–500 (default 100)"),
381
385
  cursor: z2.string().optional().describe("Opaque cursor from a prior response's next_cursor")
382
386
  }, async (params) => jsonResponse(await clientProvider().contracts.list(params)));
387
+ defineTool(server, "get_contract_abi", "Fetch a single deployed contract's ABI from the registry (prod-safe). Returns the contract's metadata + full ABI (functions, maps, variables, fungible/non-fungible tokens). Returns not_found if the contract isn't in the registry. Feed the ABI into scaffold_from_abi or generate a typed client.", {
388
+ contractId: z2.string().describe("Fully qualified contract id (e.g. SP….amm-pool-v2-01)")
389
+ }, async ({ contractId }) => {
390
+ const contract = await clientProvider().contracts.get(contractId, {
391
+ includeAbi: true
392
+ });
393
+ return contract ? jsonResponse(contract) : jsonResponse({
394
+ error: {
395
+ type: "not_found",
396
+ status: 404,
397
+ message: `Contract not in registry: ${contractId}`
398
+ }
399
+ }, true);
400
+ });
401
+ defineTool(server, "generate_contract_interface", "Generate a typed TypeScript contract-client interface (typed methods + map/var/constant readers) from a deployed contract's ABI (fetched from the registry). Returns not_found if the contract isn't in the registry.", {
402
+ contractId: z2.string().describe("Fully qualified contract id (e.g. SP….amm-pool-v2-01)")
403
+ }, async ({ contractId }) => {
404
+ const c = await clientProvider().contracts.get(contractId, {
405
+ includeAbi: true
406
+ });
407
+ if (!c || !c.abi) {
408
+ return jsonResponse({
409
+ error: {
410
+ type: "not_found",
411
+ status: 404,
412
+ message: `Contract not in registry: ${contractId}`
413
+ }
414
+ }, true);
415
+ }
416
+ const [address, contractName] = contractId.split(".");
417
+ const name = (contractName ?? contractId).replace(/[^a-zA-Z0-9]/g, "_");
418
+ const code = generateContractInterface([
419
+ {
420
+ name,
421
+ address,
422
+ contractName: contractName ?? "",
423
+ abi: c.abi
424
+ }
425
+ ]);
426
+ return { content: [{ type: "text", text: code }] };
427
+ });
383
428
  }
384
429
 
385
430
  // src/tools/datasets.ts
@@ -470,6 +515,10 @@ function registerIndexTools(server, clientProvider = getClient) {
470
515
  const tx = await clientProvider().index.transactions.get(txId);
471
516
  return tx ? jsonResponse(tx) : notFound(`No transaction for ${txId}`);
472
517
  });
518
+ defineTool(server, "index_transaction_proof", "Fetch a transaction's inclusion proof (raw tx + signed Nakamoto block header + merkle path) so it can be verified trustlessly client-side with the SDK's verifyTransactionProof. Returns not_found if the tx/block is unknown; a 503 means the proof can't be assembled on this deployment right now (PROOF_TX_SET_INCOMPLETE, or PROOF_NODE_UNAVAILABLE when the stacks-node header source isn't configured).", { txId: z4.string().describe("Transaction id (0x… hash)") }, async ({ txId }) => {
519
+ const proof = await clientProvider().index.transactions.getProof(txId);
520
+ return proof ? jsonResponse(proof) : notFound(`No proof available for ${txId}`);
521
+ });
473
522
  defineTool(server, "index_stacking", "List decoded PoX-4 stacking actions from the Index (stack-stx, delegate-stx, etc.). Anonymous reads allowed (free-tier keys rejected).", {
474
523
  ...heightFilters,
475
524
  functionName: z4.string().optional().describe("Filter by PoX function name"),
@@ -534,25 +583,32 @@ function registerProjectTools(server, clientProvider = getClient) {
534
583
  }
535
584
 
536
585
  // src/tools/scaffold.ts
537
- import { generateSubgraphCode } from "@secondlayer/scaffold";
586
+ import {
587
+ generateSubgraphCode,
588
+ generateTraitSubgraph
589
+ } from "@secondlayer/scaffold";
590
+ import { TRAIT_STANDARDS as TRAIT_STANDARDS2 } from "@secondlayer/stacks/clarity";
538
591
  import { z as z6 } from "zod/v4";
539
- var API_BASE = process.env.SECONDLAYER_API_URL || "https://api.secondlayer.tools";
540
592
  async function fetchAbi(contractId) {
541
- const res = await fetch(`${API_BASE}/api/node/contracts/${contractId}/abi`, {
542
- signal: AbortSignal.timeout(1e4)
593
+ const contract = await getClient().contracts.get(contractId, {
594
+ includeAbi: true
543
595
  });
544
- if (!res.ok) {
545
- if (res.status === 404)
546
- throw new Error(`Contract not found: ${contractId}`);
547
- throw new Error(`Failed to fetch ABI: HTTP ${res.status}`);
596
+ if (!contract)
597
+ throw new Error(`Contract not found: ${contractId}`);
598
+ const abi = contract.abi;
599
+ if (!abi) {
600
+ throw new Error(`No ABI available for ${contractId} (abi_status: ${contract.abi_status})`);
548
601
  }
549
- const abi = await res.json();
550
- return {
551
- functions: abi.functions ?? [],
552
- maps: abi.maps ?? []
553
- };
602
+ return { functions: abi.functions ?? [], maps: abi.maps ?? [] };
554
603
  }
555
604
  function registerScaffoldTools(server) {
605
+ defineTool(server, "scaffold_from_trait", "Generate a deploy-ready subgraph scaffold that indexes EVERY contract conforming to a SIP trait (no specific contract needed) — sip-009 → an nft_transfer source, sip-010/sip-013 → ft_transfer. Use scaffold_from_contract/abi instead when targeting one contract. See the secondlayer://traits resource for valid trait ids.", {
606
+ trait: z6.enum(TRAIT_STANDARDS2).describe("SIP standard to index (sip-009 | sip-010 | sip-013)"),
607
+ subgraphName: z6.string().optional().describe("Override the subgraph name (defaults to <trait>-transfers)")
608
+ }, async ({ trait, subgraphName }) => {
609
+ const code = generateTraitSubgraph({ trait, name: subgraphName });
610
+ return { content: [{ type: "text", text: code }] };
611
+ });
556
612
  defineTool(server, "scaffold_from_contract", "Generate a subgraph scaffold from a deployed Stacks contract. Fetches the ABI automatically.", {
557
613
  contractId: z6.string().describe("Fully qualified contract ID (e.g. SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01)"),
558
614
  subgraphName: z6.string().optional().describe("Override the subgraph name (defaults to contract name)")
@@ -674,7 +730,7 @@ import {
674
730
  } from "@secondlayer/subgraphs";
675
731
  import { z as z8 } from "zod/v4";
676
732
  function registerSubgraphTools(server, clientProvider = getClient) {
677
- defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only.", {}, async () => {
733
+ defineTool(server, "subgraphs_list", "List all deployed subgraphs. Returns summary fields only, including visibility — public subgraphs are anon-readable at /v1/subgraphs/<name>.", {}, async () => {
678
734
  const { data } = await clientProvider().subgraphs.list();
679
735
  return {
680
736
  content: [
@@ -707,7 +763,7 @@ function registerSubgraphTools(server, clientProvider = getClient) {
707
763
  ]
708
764
  };
709
765
  });
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>"}.', {
766
+ 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>"}. Public subgraphs are also keyless over HTTP at GET /v1/subgraphs/<name>/<table> — { rows, next_cursor, tip } envelope, resume with ?cursor=<next_cursor> + _order=asc|desc (no _offset/_sort on /v1); hand that URL to third parties.', {
711
767
  name: z8.string().describe("Subgraph name"),
712
768
  table: z8.string().describe("Table name"),
713
769
  filters: z8.record(z8.string(), z8.string()).optional().describe('Column filters — plain values or with operators (e.g. {"amount.gte": "1000", "sender": "SP..."})'),
@@ -823,12 +879,13 @@ function registerSubgraphTools(server, clientProvider = getClient) {
823
879
  const result = await clientProvider().subgraphs.delete(name);
824
880
  return { content: [{ type: "text", text: result.message }] };
825
881
  });
826
- defineTool(server, "subgraphs_deploy", "Deploy a subgraph from TypeScript code. Pass the full defineSubgraph() source — it will be bundled, validated, and deployed. Optional startBlock overrides the source definition for this deploy. Set dryRun to validate and preview the schema/DDL without writing anything. Set databaseUrl to deploy to your own Postgres (BYO data plane) — the server verifies the connection first; with dryRun it returns the DDL + grant script. A breaking BYO schema change is refused and returns a migration plan (drop + rebuild DDL) instead of deploying. Call `subgraphs_reindex` separately if you need a forced reindex.", {
882
+ defineTool(server, "subgraphs_deploy", "Deploy a subgraph from TypeScript code. Pass the full defineSubgraph() source — it will be bundled, validated, and deployed. Optional startBlock overrides the source definition for this deploy. Set dryRun to validate and preview the schema/DDL without writing anything. Set databaseUrl to deploy to your own Postgres (BYO data plane) — the server verifies the connection first; with dryRun it returns the DDL + grant script. A breaking BYO schema change is refused and returns a migration plan (drop + rebuild DDL) instead of deploying. Visibility defaults: managed deploys are public (anon-readable at /v1/subgraphs/<name>, name claimed in the global public namespace), BYO deploys are private. Call `subgraphs_reindex` separately if you need a forced reindex.", {
827
883
  code: z8.string().describe("TypeScript source code containing a defineSubgraph() call"),
828
884
  startBlock: z8.number().int().nonnegative().optional().describe("Override the definition startBlock for this deploy"),
829
885
  databaseUrl: z8.string().optional().describe("BYO data plane: Postgres connection string to host the subgraph's schema and rows in your own database"),
830
- dryRun: z8.boolean().optional().describe("Validate and preview the deploy (schema/DDL, BYO connection) without writing changes")
831
- }, async ({ code, startBlock, databaseUrl, dryRun }) => {
886
+ dryRun: z8.boolean().optional().describe("Validate and preview the deploy (schema/DDL, BYO connection) without writing changes"),
887
+ visibility: z8.enum(["public", "private"]).optional().describe("Read visibility: public = anon /v1 reads + global name claim; private = owning account's key only. Defaults: managed → public, BYO → private.")
888
+ }, async ({ code, startBlock, databaseUrl, dryRun, visibility }) => {
832
889
  const bundled = await bundleSubgraphCode(code);
833
890
  try {
834
891
  const result = await clientProvider().subgraphs.deploy({
@@ -841,7 +898,8 @@ function registerSubgraphTools(server, clientProvider = getClient) {
841
898
  sourceCode: code,
842
899
  ...startBlock !== undefined ? { startBlock } : {},
843
900
  ...databaseUrl !== undefined ? { databaseUrl } : {},
844
- ...dryRun !== undefined ? { dryRun } : {}
901
+ ...dryRun !== undefined ? { dryRun } : {},
902
+ ...visibility !== undefined ? { visibility } : {}
845
903
  });
846
904
  return {
847
905
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
@@ -860,6 +918,18 @@ function registerSubgraphTools(server, clientProvider = getClient) {
860
918
  throw err;
861
919
  }
862
920
  });
921
+ defineTool(server, "subgraphs_publish", "Make a subgraph publicly readable at /v1/subgraphs/<name> — anyone (or any agent) can read it without a key. Claims the name in the global public namespace; fails with PUBLIC_NAME_TAKEN if another account holds it.", { name: z8.string().describe("Subgraph name") }, async ({ name }) => {
922
+ const result = await clientProvider().subgraphs.publish(name);
923
+ return {
924
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
925
+ };
926
+ });
927
+ defineTool(server, "subgraphs_unpublish", "Make a subgraph private again — /v1 reads then require the owning account's bearer key, and the global public name claim is released.", { name: z8.string().describe("Subgraph name") }, async ({ name }) => {
928
+ const result = await clientProvider().subgraphs.unpublish(name);
929
+ return {
930
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
931
+ };
932
+ });
863
933
  defineTool(server, "subgraphs_read_source", "Fetch the deployed TypeScript source of a subgraph (plus its stored version). Returns a readOnly payload for subgraphs deployed before source capture — in that case the caller should redeploy via CLI before editing.", { name: z8.string().describe("Subgraph name") }, async ({ name }) => {
864
934
  const source = await clientProvider().subgraphs.getSource(name);
865
935
  return {
@@ -1150,5 +1220,5 @@ httpServer.listen(port, () => {
1150
1220
  console.error("Warning: SECONDLAYER_MCP_SECRET not set, authentication disabled");
1151
1221
  });
1152
1222
 
1153
- //# debugId=330454369B037F3564756E2164756E21
1223
+ //# debugId=38C3E42509C42A3264756E2164756E21
1154
1224
  //# sourceMappingURL=bin-http.js.map