@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 +122 -122
- package/dist/cli.js +99 -18
- package/dist/index.js +2 -2
- package/dist/latest-cli.d.ts +25 -0
- package/dist/stdio.js +130 -7
- package/package.json +28 -29
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.
|
|
17
|
-
ldgr-mcp wallet create --testnet
|
|
18
|
-
# Or: ldgr-mcp wallet import --testnet
|
|
19
|
-
ldgr-mcp wallet list
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
5
|
-
import {
|
|
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.
|
|
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.
|
|
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 =
|
|
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: () =>
|
|
385
|
-
saveRequest: (request) =>
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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 =
|
|
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: () =>
|
|
347
|
-
saveRequest: (request) =>
|
|
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.
|
|
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/
|
|
17
|
-
"@
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
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
|
+
}
|