@ldgr/mcp 0.1.2 → 0.1.3

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
@@ -1,122 +1,122 @@
1
- # @ldgr/mcp
2
-
3
- LDGR tools for Model Context Protocol clients. LDGR is an independent ledger with a single settlement authority. This package reads public ledger data and prepares unsigned requests for a person's local CLI wallet to review. MCP never signs or submits transactions.
4
-
5
- ## Hosted connection
6
-
7
- Configure a Streamable HTTP MCP connection to `https://mcp.ldgr.ltd/mcp`. Each ledger tool requires `network: "mainnet"` or `network: "testnet"`; asset-specific tools require the exact asset code. Native public IDs are `t1` on mainnet and `t0` on testnet; the signed wire code is `dng`. Mainnet native display is Ledra / LEDRA and testnet is Ledra Testnet / tLEDRA.
8
-
9
- A remote MCP connection cannot install software, access local files, unlock a wallet, or ask for recovery keys. `wallet_setup_instructions` returns commands for the user to run locally.
10
-
11
- ## Local CLI wallet and stdio
12
-
13
- Node.js 20.19 or newer is required. Install the package, then create or import a wallet in your own terminal:
14
-
15
- ```sh
16
- npm install --global @ldgr/mcp@0.1.2
17
- ldgr-mcp wallet create --testnet
18
- # Or: ldgr-mcp wallet import --testnet
19
- ldgr-mcp wallet list
20
- ```
21
-
22
- The package includes `@ldgr/cli`; a separate global CLI installation is unnecessary. Recovery keys and passphrases stay in local interactive prompts. Never paste them into chat, MCP arguments, command-line flags, or environment variables. Omit `--testnet` to create a mainnet wallet. `ldgr-mcp network show` displays the selected network; `ldgr-mcp network set mainnet` or `testnet` switches it using the CLI workflow.
23
-
24
- Without a global installation:
25
-
26
- ```sh
27
- npx --yes --package @ldgr/mcp@latest ldgr-mcp wallet create --testnet
28
- ```
29
-
30
- Configure a local MCP client:
31
-
32
- ```json
33
- {
34
- "mcpServers": {
35
- "ldgr": {
36
- "command": "npx",
37
- "args": ["--yes", "--package", "@ldgr/mcp@latest", "ldgr-mcp"]
38
- }
39
- }
40
- }
41
- ```
42
-
43
- For a custom wallet store, append `--wallet` and its absolute path. Server stdout contains JSON-RPC only; startup errors go to stderr. Local `wallet_info` returns only the selected address, public key, network, ledger ID and protection mode.
44
-
45
- The `network` argument to `wallet_info` checks the selected wallet; it does not
46
- select another wallet or change its network. `WALLET_NETWORK_MISMATCH` means you
47
- should request the selected network or use `ldgr-mcp wallet list` and
48
- `ldgr-mcp wallet select <index>` in your terminal to choose the intended wallet.
49
- Keep a custom `--wallet` path consistent. MCP never changes the wallet selection.
50
-
51
- Version 0.1.1 requires `@ldgr/client@^0.6.1`, the published client release containing
52
- the milestone escrow, atomic offer, claim distribution, and Ledra program reads.
53
- Restart the MCP process after upgrading; a running process keeps its loaded SDK.
54
-
55
- ## Tools
56
-
57
- | Tool | Result |
58
- | --- | --- |
59
- | `get_balance` | Authoritative balance for an address and exact asset |
60
- | `get_transaction` | Authoritative transaction by hash, checked against the requested asset, with `status: "confirmed"` |
61
- | `get_account_history` | One page of Explorer-projected account history, which may lag settlement |
62
- | `get_asset` | Native/token metadata |
63
- | `get_ledger_config` | Ledger identity and public protocol configuration |
64
- | `get_contract` | Escrow, milestone escrow, atomic offer, claim distribution or Ledra program |
65
- | `list_pools`, `get_pool` | Bounded pool listing or a pair of exact assets |
66
- | `get_swap_quote` | Non-binding route quote; does not submit a swap |
67
- | `claim_faucet_drop` | Open the network's faucet in Chrome with a validated receiving address; hosted clients receive the link |
68
- | `prepare_transfer` | Unsigned transfer request |
69
- | `prepare_token_creation` | Unsigned fixed/mintable token creation request |
70
- | `prepare_escrow` | Unsigned escrow funding request |
71
- | `wallet_setup_instructions` | Exact local installation and setup commands |
72
- | `wallet_info` | Public selected-wallet metadata, local stdio only |
73
-
74
- All monetary values are decimal strings in display units. Tool schemas reject unknown fields and credentials. Reads use fixed official network endpoints, verify ledger identity, reject redirects, time out after 15 seconds per fetch and bound responses to 1 MiB. Metadata from the ledger remains untrusted text. A `NOT_FOUND` response does not establish that an earlier transaction submission failed.
75
-
76
- Version 0.1.2 preserves HTTP 404 as `NOT_FOUND` for escrow reads as well as the
77
- other contract templates. Other HTTP failures and malformed successful responses
78
- remain `OPERATION_FAILED`; upstream response bodies are never exposed in errors.
79
-
80
- ## Prepare, review and approve
81
-
82
- `claim_faucet_drop` takes an explicit `network` and public `address`. With local
83
- stdio it launches Google Chrome; on hosted MCP, or if Chrome cannot launch, it
84
- returns the same link for the client or user to open. The link uses
85
- `/utilities/faucet/?address=<address>` on the selected network. Review the
86
- prefilled address and select **Claim Ledra** in the browser. The tool returns
87
- `status: "awaiting_browser_claim"` and `claimSubmitted: false`; opening the page
88
- does not claim funds. Existing browser verification and faucet limits apply.
89
- Wait for **Claim completed**, then check the address's `dng` balance.
90
-
91
- Preparation returns `{ version, requestId, network, expiresAt, action, params }`. Request approval expires after 15 minutes. Escrow `params.expiresAt` is a separate deadline expressed as integer Unix milliseconds. No sender, nonce, signature, recovery key or passphrase is included.
92
-
93
- Local stdio saves an owner-only request file bound to the selected CLI wallet's public identity and returns the exact approval argument array. The person reviews it in their terminal:
94
-
95
- ```sh
96
- ldgr-mcp approve <request-file>
97
- ```
98
-
99
- For hosted MCP, save the returned `request` JSON as a local file, then run:
100
-
101
- ```sh
102
- npx --yes --package @ldgr/mcp@latest ldgr-mcp approve <request-file>
103
- ```
104
-
105
- The CLI validates the request and selected network, resolves fresh asset precision, checks the ledger's current fees and nonces, and presents the transaction for explicit local approval. Do not run approval unattended. Prepared requests may become invalid as balances, fees, configuration, or deadlines change. Reconcile an uncertain submission before trying again.
106
-
107
- ## Embedding
108
-
109
- ```ts
110
- import { createLdgrMcpServer } from "@ldgr/mcp";
111
- const server = createLdgrMcpServer();
112
- await server.connect(yourTransport);
113
- ```
114
-
115
- The root export is browser/Worker compatible and does not import filesystem, process, CLI or signing adapters. It accepts optional `fetch` and `now` injection and explicit local hooks for hosts. Node-only stdio is a separate export:
116
-
117
- ```ts
118
- import { startLdgrMcpStdio } from "@ldgr/mcp/stdio";
119
- await startLdgrMcpStdio({ walletPath: "/absolute/path/wallet.json" });
120
- ```
121
-
122
- Build with `bun run build`, run tests with `bun test`, and check types with `bun run typecheck` from this package.
1
+ # @ldgr/mcp
2
+
3
+ LDGR tools for Model Context Protocol clients. LDGR is an independent ledger with a single settlement authority. This package reads public ledger data and prepares unsigned requests for a person's local CLI wallet to review. MCP never signs or submits transactions.
4
+
5
+ ## Hosted connection
6
+
7
+ Configure a Streamable HTTP MCP connection to `https://mcp.ldgr.ltd/mcp`. Each ledger tool requires `network: "mainnet"` or `network: "testnet"`; asset-specific tools require the exact asset code. Native public IDs are `t1` on mainnet and `t0` on testnet; the signed wire code is `dng`. Mainnet native display is Ledra / LEDRA and testnet is Ledra Testnet / tLEDRA.
8
+
9
+ A remote MCP connection cannot install software, access local files, unlock a wallet, or ask for recovery keys. `wallet_setup_instructions` returns commands for the user to run locally.
10
+
11
+ ## Local CLI wallet and stdio
12
+
13
+ Node.js 20.19 or newer is required. Install the package, then create or import a wallet in your own terminal:
14
+
15
+ ```sh
16
+ npm install --global @ldgr/mcp@0.1.3
17
+ ldgr-mcp wallet create --testnet
18
+ # Or: ldgr-mcp wallet import --testnet
19
+ ldgr-mcp wallet list
20
+ ```
21
+
22
+ From MCP 0.1.3, every local wallet operation runs `@ldgr/cli@latest` through npx with `--prefer-online`, including wallet metadata and unsigned request preparation. Node.js must include npm/npx. npm checks its registry and reuses downloaded packages from its cache; a separate global CLI installation is unnecessary. CLI updates apply on the next wallet operation. Public ledger reads do not launch the CLI. The CLI must be 0.5.1 or newer for the MCP bridge. Recovery keys and passphrases stay in local interactive prompts. Never paste them into chat, MCP arguments, command-line flags, or environment variables. Omit `--testnet` to create a mainnet wallet. `ldgr-mcp network show` displays the selected network; `ldgr-mcp network set mainnet` or `testnet` switches it using the CLI workflow.
23
+
24
+ Without a global installation:
25
+
26
+ ```sh
27
+ npx --yes --package @ldgr/mcp@latest ldgr-mcp wallet create --testnet
28
+ ```
29
+
30
+ Configure a local MCP client:
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "ldgr": {
36
+ "command": "npx",
37
+ "args": ["--yes", "--package", "@ldgr/mcp@latest", "ldgr-mcp"]
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ For a custom wallet store, append `--wallet` and its absolute path. Server stdout contains JSON-RPC only; startup errors go to stderr. Local `wallet_info` returns only the selected address, public key, network, ledger ID and protection mode.
44
+
45
+ The `network` argument to `wallet_info` checks the selected wallet; it does not
46
+ select another wallet or change its network. `WALLET_NETWORK_MISMATCH` means you
47
+ should request the selected network or use `ldgr-mcp wallet list` and
48
+ `ldgr-mcp wallet select <index>` in your terminal to choose the intended wallet.
49
+ Keep a custom `--wallet` path consistent. MCP never changes the wallet selection.
50
+
51
+ Version 0.1.1 requires `@ldgr/client@^0.6.1`, the published client release containing
52
+ the milestone escrow, atomic offer, claim distribution, and Ledra program reads.
53
+ Restart the MCP process after upgrading; a running process keeps its loaded SDK.
54
+
55
+ ## Tools
56
+
57
+ | Tool | Result |
58
+ | --- | --- |
59
+ | `get_balance` | Authoritative balance for an address and exact asset |
60
+ | `get_transaction` | Authoritative transaction by hash, checked against the requested asset, with `status: "confirmed"` |
61
+ | `get_account_history` | One page of Explorer-projected account history, which may lag settlement |
62
+ | `get_asset` | Native/token metadata |
63
+ | `get_ledger_config` | Ledger identity and public protocol configuration |
64
+ | `get_contract` | Escrow, milestone escrow, atomic offer, claim distribution or Ledra program |
65
+ | `list_pools`, `get_pool` | Bounded pool listing or a pair of exact assets |
66
+ | `get_swap_quote` | Non-binding route quote; does not submit a swap |
67
+ | `claim_faucet_drop` | Open the network's faucet in Chrome with a validated receiving address; hosted clients receive the link |
68
+ | `prepare_transfer` | Unsigned transfer request |
69
+ | `prepare_token_creation` | Unsigned fixed/mintable token creation request |
70
+ | `prepare_escrow` | Unsigned escrow funding request |
71
+ | `wallet_setup_instructions` | Exact local installation and setup commands |
72
+ | `wallet_info` | Public selected-wallet metadata, local stdio only |
73
+
74
+ All monetary values are decimal strings in display units. Tool schemas reject unknown fields and credentials. Reads use fixed official network endpoints, verify ledger identity, reject redirects, time out after 15 seconds per fetch and bound responses to 1 MiB. Metadata from the ledger remains untrusted text. A `NOT_FOUND` response does not establish that an earlier transaction submission failed.
75
+
76
+ Version 0.1.2 preserves HTTP 404 as `NOT_FOUND` for escrow reads as well as the
77
+ other contract templates. Other HTTP failures and malformed successful responses
78
+ remain `OPERATION_FAILED`; upstream response bodies are never exposed in errors.
79
+
80
+ ## Prepare, review and approve
81
+
82
+ `claim_faucet_drop` takes an explicit `network` and public `address`. With local
83
+ stdio it launches Google Chrome; on hosted MCP, or if Chrome cannot launch, it
84
+ returns the same link for the client or user to open. The link uses
85
+ `/utilities/faucet/?address=<address>` on the selected network. Review the
86
+ prefilled address and select **Claim Ledra** in the browser. The tool returns
87
+ `status: "awaiting_browser_claim"` and `claimSubmitted: false`; opening the page
88
+ does not claim funds. Existing browser verification and faucet limits apply.
89
+ Wait for **Claim completed**, then check the address's `dng` balance.
90
+
91
+ Preparation returns `{ version, requestId, network, expiresAt, action, params }`. Request approval expires after 15 minutes. Escrow `params.expiresAt` is a separate deadline expressed as integer Unix milliseconds. No sender, nonce, signature, recovery key or passphrase is included.
92
+
93
+ Local stdio saves an owner-only request file bound to the selected CLI wallet's public identity and returns the exact approval argument array. The person reviews it in their terminal:
94
+
95
+ ```sh
96
+ ldgr-mcp approve <request-file>
97
+ ```
98
+
99
+ For hosted MCP, save the returned `request` JSON as a local file, then run:
100
+
101
+ ```sh
102
+ npx --yes --package @ldgr/mcp@latest ldgr-mcp approve <request-file>
103
+ ```
104
+
105
+ The CLI validates the request and selected network, resolves fresh asset precision, checks the ledger's current fees and nonces, and presents the transaction for explicit local approval. Do not run approval unattended. Prepared requests may become invalid as balances, fees, configuration, or deadlines change. Reconcile an uncertain submission before trying again.
106
+
107
+ ## Embedding
108
+
109
+ ```ts
110
+ import { createLdgrMcpServer } from "@ldgr/mcp";
111
+ const server = createLdgrMcpServer();
112
+ await server.connect(yourTransport);
113
+ ```
114
+
115
+ The root export is browser/Worker compatible and does not import filesystem, process, CLI or signing adapters. It accepts optional `fetch` and `now` injection and explicit local hooks for hosts. Node-only stdio is a separate export:
116
+
117
+ ```ts
118
+ import { startLdgrMcpStdio } from "@ldgr/mcp/stdio";
119
+ await startLdgrMcpStdio({ walletPath: "/absolute/path/wallet.json" });
120
+ ```
121
+
122
+ Build with `bun run build`, run tests with `bun test`, and check types with `bun run typecheck` from this package.
package/dist/cli.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/cli.ts
4
- import { spawn as spawn2 } from "node:child_process";
5
- import { fileURLToPath } from "node:url";
3
+ // src/latest-cli.ts
4
+ import { spawn } from "node:child_process";
5
+ import { access, realpath } from "node:fs/promises";
6
+ import { delimiter, dirname, join } from "node:path";
6
7
 
7
8
  // src/commands.ts
8
9
  function walletCommandArgs(input) {
@@ -36,9 +37,94 @@ function walletCommandArgs(input) {
36
37
  return [...input];
37
38
  }
38
39
 
40
+ // src/latest-cli.ts
41
+ var FAILURE = "Latest LDGR CLI could not complete. Check npm connectivity and CLI availability.";
42
+ function npxArguments(envelope) {
43
+ return ["--yes", "--prefer-online", "--offline=false", "--package", "@ldgr/cli@latest", "--", "ldgr", "mcp", envelope];
44
+ }
45
+ async function resolveNpxEntrypoint() {
46
+ const directories = [dirname(process.execPath), ...(process.env.PATH ?? "").split(delimiter).filter(Boolean)];
47
+ const candidates = [];
48
+ const npmPath = process.env.npm_execpath;
49
+ if (npmPath && /[\\/]npm-cli\.js$/.test(npmPath))
50
+ candidates.push(join(dirname(npmPath), "npx-cli.js"));
51
+ for (const directory of directories) {
52
+ candidates.push(join(directory, "node_modules/npm/bin/npx-cli.js"), join(directory, "../lib/node_modules/npm/bin/npx-cli.js"));
53
+ try {
54
+ const target = await realpath(join(directory, "npx"));
55
+ if (target.endsWith("npx-cli.js"))
56
+ candidates.push(target);
57
+ } catch {}
58
+ }
59
+ for (const candidate of candidates) {
60
+ try {
61
+ await access(candidate);
62
+ return candidate;
63
+ } catch {}
64
+ }
65
+ throw new Error("npm/npx is required for the local LDGR CLI. Install Node.js with npm.");
66
+ }
67
+ var runNpx = async (args, options) => {
68
+ const entry = await resolveNpxEntrypoint();
69
+ return new Promise((resolve, reject) => {
70
+ const child = spawn(process.execPath, [entry, ...args], {
71
+ shell: false,
72
+ windowsHide: true,
73
+ stdio: options.interactive ? "inherit" : ["pipe", "pipe", "ignore"]
74
+ });
75
+ let stdout = "", bytes = 0;
76
+ child.once("error", () => reject(new Error(FAILURE)));
77
+ if (!options.interactive) {
78
+ child.stdout.setEncoding("utf8");
79
+ child.stdout.on("data", (chunk) => {
80
+ bytes += Buffer.byteLength(chunk);
81
+ if (bytes > 128 * 1024) {
82
+ child.kill();
83
+ reject(new Error(FAILURE));
84
+ } else
85
+ stdout += chunk;
86
+ });
87
+ child.stdin.on("error", () => reject(new Error(FAILURE)));
88
+ child.stdin.end(options.input);
89
+ }
90
+ child.once("close", (code) => resolve({ code: code ?? 1, stdout }));
91
+ });
92
+ };
93
+ function createLatestCliAdapter(run = runNpx) {
94
+ async function machine(envelope) {
95
+ const input = JSON.stringify(envelope);
96
+ if (Buffer.byteLength(input) > 128 * 1024)
97
+ throw new Error(FAILURE);
98
+ const result = await run(npxArguments("--stdin"), { interactive: false, input });
99
+ try {
100
+ const value = JSON.parse(result.stdout);
101
+ if (result.code !== 0 || !value || typeof value !== "object" || Array.isArray(value) || "error" in value)
102
+ throw new Error;
103
+ return value;
104
+ } catch {
105
+ throw new Error(FAILURE);
106
+ }
107
+ }
108
+ return {
109
+ walletInfo: (walletPath) => machine({ operation: "wallet-info", walletPath }),
110
+ async saveRequest(request, options) {
111
+ const value = await machine({ operation: "save-request", request, ...options });
112
+ if (typeof value.requestPath !== "string" || !Array.isArray(value.approvalArgs) || !value.approvalArgs.every((arg) => typeof arg === "string"))
113
+ throw new Error(FAILURE);
114
+ return { requestPath: value.requestPath, approvalArgs: walletCommandArgs(value.approvalArgs) };
115
+ },
116
+ async command(input) {
117
+ const args = walletCommandArgs(input);
118
+ const envelope = Buffer.from(JSON.stringify({ operation: "command", args })).toString("base64url");
119
+ if (envelope.length > 6000)
120
+ throw new Error("Wallet command arguments are too long");
121
+ return (await run(npxArguments(envelope), { interactive: true })).code;
122
+ }
123
+ };
124
+ }
125
+
39
126
  // src/stdio.ts
40
127
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
41
- import { getMcpWalletInfo, saveMcpRequest, validateMcpRequest } from "@ldgr/cli/mcp";
42
128
 
43
129
  // src/index.ts
44
130
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -163,7 +249,7 @@ class AssetMismatchError extends Error {
163
249
  class WalletNetworkMismatchError extends Error {
164
250
  }
165
251
  function createLdgrMcpServer(options = {}) {
166
- const server = new McpServer({ name: "ldgr", version: "0.1.2" }, { instructions: INSTRUCTIONS });
252
+ const server = new McpServer({ name: "ldgr", version: "0.1.3" }, { instructions: INSTRUCTIONS });
167
253
  const request = readTransport(options.fetch);
168
254
  const now = options.now ?? Date.now;
169
255
  const envelope = (network, source, data) => ({ network, source, data });
@@ -312,7 +398,7 @@ function createLdgrMcpServer(options = {}) {
312
398
  }, false);
313
399
  tool("wallet_setup_instructions", "Explain user-run CLI wallet installation, creation/import and MCP connection. This tool cannot install software or access local files.", { network: networkSchema }, async (args) => ({
314
400
  network: args.network,
315
- install: "npm install --global @ldgr/mcp@0.1.2",
401
+ install: "npm install --global @ldgr/mcp@0.1.3",
316
402
  node: ">=20.19.0",
317
403
  create: `ldgr-mcp wallet create${args.network === "testnet" ? " --testnet" : ""}`,
318
404
  import: `ldgr-mcp wallet import${args.network === "testnet" ? " --testnet" : ""}`,
@@ -337,10 +423,10 @@ function createLdgrMcpServer(options = {}) {
337
423
  }
338
424
 
339
425
  // src/browser.ts
340
- import { spawn } from "node:child_process";
426
+ import { spawn as spawn2 } from "node:child_process";
341
427
  import { win32 } from "node:path";
342
428
  var launchChrome = (command, args, waitForExit) => new Promise((resolve, reject) => {
343
- const child = spawn(command, [...args], {
429
+ const child = spawn2(command, [...args], {
344
430
  shell: false,
345
431
  stdio: "ignore",
346
432
  windowsHide: true,
@@ -376,13 +462,14 @@ async function openFaucetInBrowser(url, options = {}) {
376
462
 
377
463
  // src/stdio.ts
378
464
  async function startLdgrMcpStdio(options = {}) {
465
+ const cli = createLatestCliAdapter();
379
466
  const server = createLdgrMcpServer({
380
467
  fetch: options.fetch,
381
468
  now: options.now,
382
469
  local: {
383
470
  openBrowser: openFaucetInBrowser,
384
- walletInfo: () => getMcpWalletInfo(options.walletPath),
385
- saveRequest: (request) => saveMcpRequest(validateMcpRequest(request), { walletPath: options.walletPath, directory: options.requestDirectory })
471
+ walletInfo: () => cli.walletInfo(options.walletPath),
472
+ saveRequest: (request) => cli.saveRequest(request, { walletPath: options.walletPath, directory: options.requestDirectory })
386
473
  }
387
474
  });
388
475
  await server.connect(new StdioServerTransport);
@@ -390,7 +477,7 @@ async function startLdgrMcpStdio(options = {}) {
390
477
  }
391
478
 
392
479
  // src/cli.ts
393
- var HELP = `LDGR MCP 0.1.2
480
+ var HELP = `LDGR MCP 0.1.3
394
481
 
395
482
  ldgr-mcp Start local MCP over stdio
396
483
  ldgr-mcp --wallet <path> Start MCP with a selected wallet store
@@ -414,13 +501,7 @@ try {
414
501
  } else if (args.length === 0 || args.length === 2 && args[0] === "--wallet" && args[1] && !args[1].startsWith("-")) {
415
502
  await startLdgrMcpStdio({ walletPath: args[1] });
416
503
  } else {
417
- const forwarded = walletCommandArgs(args);
418
- const entry = fileURLToPath(import.meta.resolve("@ldgr/cli/cli"));
419
- process.exitCode = await new Promise((resolve, reject) => {
420
- const child = spawn2(process.execPath, [entry, ...forwarded], { stdio: "inherit", shell: false });
421
- child.once("error", reject);
422
- child.once("exit", (code) => resolve(code ?? 1));
423
- });
504
+ process.exitCode = await createLatestCliAdapter().command(args);
424
505
  }
425
506
  } catch {
426
507
  console.error("LDGR MCP could not complete. Check --help, the local wallet and request file. Recovery keys and passphrases belong only in local interactive prompts.");
package/dist/index.js CHANGED
@@ -121,7 +121,7 @@ class AssetMismatchError extends Error {
121
121
  class WalletNetworkMismatchError extends Error {
122
122
  }
123
123
  function createLdgrMcpServer(options = {}) {
124
- const server = new McpServer({ name: "ldgr", version: "0.1.2" }, { instructions: INSTRUCTIONS });
124
+ const server = new McpServer({ name: "ldgr", version: "0.1.3" }, { instructions: INSTRUCTIONS });
125
125
  const request = readTransport(options.fetch);
126
126
  const now = options.now ?? Date.now;
127
127
  const envelope = (network, source, data) => ({ network, source, data });
@@ -270,7 +270,7 @@ function createLdgrMcpServer(options = {}) {
270
270
  }, false);
271
271
  tool("wallet_setup_instructions", "Explain user-run CLI wallet installation, creation/import and MCP connection. This tool cannot install software or access local files.", { network: networkSchema }, async (args) => ({
272
272
  network: args.network,
273
- install: "npm install --global @ldgr/mcp@0.1.2",
273
+ install: "npm install --global @ldgr/mcp@0.1.3",
274
274
  node: ">=20.19.0",
275
275
  create: `ldgr-mcp wallet create${args.network === "testnet" ? " --testnet" : ""}`,
276
276
  import: `ldgr-mcp wallet import${args.network === "testnet" ? " --testnet" : ""}`,
@@ -0,0 +1,25 @@
1
+ import type { UnsignedMcpRequest } from "./requests";
2
+ type RunOptions = {
3
+ readonly interactive: boolean;
4
+ readonly input?: string;
5
+ };
6
+ type Runner = (args: readonly string[], options: RunOptions) => Promise<{
7
+ code: number;
8
+ stdout: string;
9
+ }>;
10
+ /** Only fixed arguments or a base64url envelope reach npm's internal command shell. */
11
+ export declare function npxArguments(envelope: string): string[];
12
+ /** Execute npm's JS entry directly: Windows .cmd launchers require a shell. */
13
+ export declare function resolveNpxEntrypoint(): Promise<string>;
14
+ export declare function createLatestCliAdapter(run?: Runner): {
15
+ walletInfo: (walletPath?: string) => Promise<unknown>;
16
+ saveRequest(request: UnsignedMcpRequest, options: {
17
+ walletPath?: string;
18
+ directory?: string;
19
+ }): Promise<{
20
+ requestPath: string;
21
+ approvalArgs: string[];
22
+ }>;
23
+ command(input: readonly string[]): Promise<number>;
24
+ };
25
+ export {};
package/dist/stdio.js CHANGED
@@ -1,6 +1,128 @@
1
1
  // src/stdio.ts
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
- import { getMcpWalletInfo, saveMcpRequest, validateMcpRequest } from "@ldgr/cli/mcp";
3
+
4
+ // src/latest-cli.ts
5
+ import { spawn } from "node:child_process";
6
+ import { access, realpath } from "node:fs/promises";
7
+ import { delimiter, dirname, join } from "node:path";
8
+
9
+ // src/commands.ts
10
+ function walletCommandArgs(input) {
11
+ if (input.some((value) => !value || /[\x00-\x1f\x7f]/.test(value)))
12
+ throw new TypeError("Invalid wallet arguments");
13
+ const positional = [];
14
+ let testnet = false, wallet = false;
15
+ for (let index = 0;index < input.length; index++) {
16
+ const value = input[index];
17
+ if (value === "--wallet") {
18
+ if (wallet || !input[index + 1] || input[index + 1].startsWith("-"))
19
+ throw new TypeError("Invalid wallet path");
20
+ wallet = true;
21
+ index++;
22
+ } else if (value === "--testnet") {
23
+ if (testnet)
24
+ throw new TypeError("Repeated network flag");
25
+ testnet = true;
26
+ } else if (value.startsWith("-"))
27
+ throw new TypeError("Unsupported wallet option");
28
+ else
29
+ positional.push(value);
30
+ }
31
+ const [first, second, third] = positional;
32
+ const creation = first === "wallet" && (second === "create" || second === "import");
33
+ if (testnet && !creation)
34
+ throw new TypeError("--testnet only applies to wallet create/import");
35
+ const valid = first === "wallet" && second === "create" && positional.length === 2 || first === "wallet" && second === "import" && (positional.length === 2 || positional.length === 3) || first === "wallet" && second === "list" && positional.length === 2 || first === "wallet" && second === "select" && positional.length === 3 && /^[0-9]+$/.test(third) || first === "approve" && positional.length === 2 || first === "address" && positional.length === 1 || first === "network" && second === "show" && positional.length === 2 || first === "network" && second === "set" && positional.length === 3 && (third === "mainnet" || third === "testnet");
36
+ if (!valid)
37
+ throw new TypeError("Unsupported wallet command");
38
+ return [...input];
39
+ }
40
+
41
+ // src/latest-cli.ts
42
+ var FAILURE = "Latest LDGR CLI could not complete. Check npm connectivity and CLI availability.";
43
+ function npxArguments(envelope) {
44
+ return ["--yes", "--prefer-online", "--offline=false", "--package", "@ldgr/cli@latest", "--", "ldgr", "mcp", envelope];
45
+ }
46
+ async function resolveNpxEntrypoint() {
47
+ const directories = [dirname(process.execPath), ...(process.env.PATH ?? "").split(delimiter).filter(Boolean)];
48
+ const candidates = [];
49
+ const npmPath = process.env.npm_execpath;
50
+ if (npmPath && /[\\/]npm-cli\.js$/.test(npmPath))
51
+ candidates.push(join(dirname(npmPath), "npx-cli.js"));
52
+ for (const directory of directories) {
53
+ candidates.push(join(directory, "node_modules/npm/bin/npx-cli.js"), join(directory, "../lib/node_modules/npm/bin/npx-cli.js"));
54
+ try {
55
+ const target = await realpath(join(directory, "npx"));
56
+ if (target.endsWith("npx-cli.js"))
57
+ candidates.push(target);
58
+ } catch {}
59
+ }
60
+ for (const candidate of candidates) {
61
+ try {
62
+ await access(candidate);
63
+ return candidate;
64
+ } catch {}
65
+ }
66
+ throw new Error("npm/npx is required for the local LDGR CLI. Install Node.js with npm.");
67
+ }
68
+ var runNpx = async (args, options) => {
69
+ const entry = await resolveNpxEntrypoint();
70
+ return new Promise((resolve, reject) => {
71
+ const child = spawn(process.execPath, [entry, ...args], {
72
+ shell: false,
73
+ windowsHide: true,
74
+ stdio: options.interactive ? "inherit" : ["pipe", "pipe", "ignore"]
75
+ });
76
+ let stdout = "", bytes = 0;
77
+ child.once("error", () => reject(new Error(FAILURE)));
78
+ if (!options.interactive) {
79
+ child.stdout.setEncoding("utf8");
80
+ child.stdout.on("data", (chunk) => {
81
+ bytes += Buffer.byteLength(chunk);
82
+ if (bytes > 128 * 1024) {
83
+ child.kill();
84
+ reject(new Error(FAILURE));
85
+ } else
86
+ stdout += chunk;
87
+ });
88
+ child.stdin.on("error", () => reject(new Error(FAILURE)));
89
+ child.stdin.end(options.input);
90
+ }
91
+ child.once("close", (code) => resolve({ code: code ?? 1, stdout }));
92
+ });
93
+ };
94
+ function createLatestCliAdapter(run = runNpx) {
95
+ async function machine(envelope) {
96
+ const input = JSON.stringify(envelope);
97
+ if (Buffer.byteLength(input) > 128 * 1024)
98
+ throw new Error(FAILURE);
99
+ const result = await run(npxArguments("--stdin"), { interactive: false, input });
100
+ try {
101
+ const value = JSON.parse(result.stdout);
102
+ if (result.code !== 0 || !value || typeof value !== "object" || Array.isArray(value) || "error" in value)
103
+ throw new Error;
104
+ return value;
105
+ } catch {
106
+ throw new Error(FAILURE);
107
+ }
108
+ }
109
+ return {
110
+ walletInfo: (walletPath) => machine({ operation: "wallet-info", walletPath }),
111
+ async saveRequest(request, options) {
112
+ const value = await machine({ operation: "save-request", request, ...options });
113
+ if (typeof value.requestPath !== "string" || !Array.isArray(value.approvalArgs) || !value.approvalArgs.every((arg) => typeof arg === "string"))
114
+ throw new Error(FAILURE);
115
+ return { requestPath: value.requestPath, approvalArgs: walletCommandArgs(value.approvalArgs) };
116
+ },
117
+ async command(input) {
118
+ const args = walletCommandArgs(input);
119
+ const envelope = Buffer.from(JSON.stringify({ operation: "command", args })).toString("base64url");
120
+ if (envelope.length > 6000)
121
+ throw new Error("Wallet command arguments are too long");
122
+ return (await run(npxArguments(envelope), { interactive: true })).code;
123
+ }
124
+ };
125
+ }
4
126
 
5
127
  // src/index.ts
6
128
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -125,7 +247,7 @@ class AssetMismatchError extends Error {
125
247
  class WalletNetworkMismatchError extends Error {
126
248
  }
127
249
  function createLdgrMcpServer(options = {}) {
128
- const server = new McpServer({ name: "ldgr", version: "0.1.2" }, { instructions: INSTRUCTIONS });
250
+ const server = new McpServer({ name: "ldgr", version: "0.1.3" }, { instructions: INSTRUCTIONS });
129
251
  const request = readTransport(options.fetch);
130
252
  const now = options.now ?? Date.now;
131
253
  const envelope = (network, source, data) => ({ network, source, data });
@@ -274,7 +396,7 @@ function createLdgrMcpServer(options = {}) {
274
396
  }, false);
275
397
  tool("wallet_setup_instructions", "Explain user-run CLI wallet installation, creation/import and MCP connection. This tool cannot install software or access local files.", { network: networkSchema }, async (args) => ({
276
398
  network: args.network,
277
- install: "npm install --global @ldgr/mcp@0.1.2",
399
+ install: "npm install --global @ldgr/mcp@0.1.3",
278
400
  node: ">=20.19.0",
279
401
  create: `ldgr-mcp wallet create${args.network === "testnet" ? " --testnet" : ""}`,
280
402
  import: `ldgr-mcp wallet import${args.network === "testnet" ? " --testnet" : ""}`,
@@ -299,10 +421,10 @@ function createLdgrMcpServer(options = {}) {
299
421
  }
300
422
 
301
423
  // src/browser.ts
302
- import { spawn } from "node:child_process";
424
+ import { spawn as spawn2 } from "node:child_process";
303
425
  import { win32 } from "node:path";
304
426
  var launchChrome = (command, args, waitForExit) => new Promise((resolve, reject) => {
305
- const child = spawn(command, [...args], {
427
+ const child = spawn2(command, [...args], {
306
428
  shell: false,
307
429
  stdio: "ignore",
308
430
  windowsHide: true,
@@ -338,13 +460,14 @@ async function openFaucetInBrowser(url, options = {}) {
338
460
 
339
461
  // src/stdio.ts
340
462
  async function startLdgrMcpStdio(options = {}) {
463
+ const cli = createLatestCliAdapter();
341
464
  const server = createLdgrMcpServer({
342
465
  fetch: options.fetch,
343
466
  now: options.now,
344
467
  local: {
345
468
  openBrowser: openFaucetInBrowser,
346
- walletInfo: () => getMcpWalletInfo(options.walletPath),
347
- saveRequest: (request) => saveMcpRequest(validateMcpRequest(request), { walletPath: options.walletPath, directory: options.requestDirectory })
469
+ walletInfo: () => cli.walletInfo(options.walletPath),
470
+ saveRequest: (request) => cli.saveRequest(request, { walletPath: options.walletPath, directory: options.requestDirectory })
348
471
  }
349
472
  });
350
473
  await server.connect(new StdioServerTransport);
package/package.json CHANGED
@@ -1,29 +1,28 @@
1
- {
2
- "name": "@ldgr/mcp",
3
- "version": "0.1.2",
4
- "type": "module",
5
- "types": "./dist/index.d.ts",
6
- "exports": {
7
- ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
8
- "./stdio": { "types": "./dist/stdio.d.ts", "import": "./dist/stdio.js" }
9
- },
10
- "bin": { "ldgr-mcp": "dist/cli.js" },
11
- "files": ["dist", "README.md"],
12
- "publishConfig": { "access": "public" },
13
- "engines": { "node": ">=20.19.0" },
14
- "dependencies": {
15
- "@ldgr/client": "^0.6.1",
16
- "@ldgr/cli": "^0.5.0",
17
- "@ldgr/protocol": "^0.5.0",
18
- "@modelcontextprotocol/sdk": "1.32.2",
19
- "zod": "^4.3.6"
20
- },
21
- "scripts": {
22
- "build": "bun build src/index.ts src/stdio.ts src/cli.ts --outdir dist --target node --format esm --packages external && tsrs -p tsconfig.build.json",
23
- "prepack": "bun run build",
24
- "start": "bun src/cli.ts",
25
- "test": "bun test",
26
- "typecheck": "tsrs --noEmit -p tsconfig.json && tsrs --noEmit -p test/tsconfig.json"
27
- },
28
- "devDependencies": { "tsrs": "0.20.0-ts7.1.0-beta" }
29
- }
1
+ {
2
+ "name": "@ldgr/mcp",
3
+ "version": "0.1.3",
4
+ "type": "module",
5
+ "types": "./dist/index.d.ts",
6
+ "exports": {
7
+ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
8
+ "./stdio": { "types": "./dist/stdio.d.ts", "import": "./dist/stdio.js" }
9
+ },
10
+ "bin": { "ldgr-mcp": "dist/cli.js" },
11
+ "files": ["dist", "README.md"],
12
+ "publishConfig": { "access": "public" },
13
+ "engines": { "node": ">=20.19.0" },
14
+ "dependencies": {
15
+ "@ldgr/client": "^0.6.1",
16
+ "@ldgr/protocol": "^0.5.0",
17
+ "@modelcontextprotocol/sdk": "1.32.2",
18
+ "zod": "^4.3.6"
19
+ },
20
+ "scripts": {
21
+ "build": "bun build src/index.ts src/stdio.ts src/cli.ts --outdir dist --target node --format esm --packages external && tsrs -p tsconfig.build.json",
22
+ "prepack": "bun run build",
23
+ "start": "bun src/cli.ts",
24
+ "test": "bun test",
25
+ "typecheck": "tsrs --noEmit -p tsconfig.json && tsrs --noEmit -p test/tsconfig.json"
26
+ },
27
+ "devDependencies": { "tsrs": "0.20.0-ts7.1.0-beta" }
28
+ }