@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 +6 -2
- package/dist/bin-http.js +92 -22
- package/dist/bin-http.js.map +8 -8
- package/dist/bin.js +92 -22
- package/dist/bin.js.map +8 -8
- package/dist/index.js +92 -22
- package/dist/index.js.map +8 -8
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ bun add @secondlayer/mcp
|
|
|
10
10
|
|
|
11
11
|
## Auth
|
|
12
12
|
|
|
13
|
-
Most reads are public —
|
|
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** (
|
|
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: "
|
|
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 {
|
|
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
|
|
542
|
-
|
|
593
|
+
const contract = await getClient().contracts.get(contractId, {
|
|
594
|
+
includeAbi: true
|
|
543
595
|
});
|
|
544
|
-
if (!
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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=
|
|
1223
|
+
//# debugId=38C3E42509C42A3264756E2164756E21
|
|
1154
1224
|
//# sourceMappingURL=bin-http.js.map
|