pion-mcp 0.4.1 → 0.5.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.
Files changed (3) hide show
  1. package/README.md +42 -2
  2. package/dist/index.js +46 -18
  3. package/package.json +4 -3
package/README.md CHANGED
@@ -3,6 +3,8 @@
3
3
  **Model Context Protocol server for Pi Network** — connect AI agents
4
4
  (Claude, Cursor, and any MCP-compatible client) to Pi Network chain data.
5
5
 
6
+ [![pion-mcp MCP server](https://glama.ai/mcp/servers/jleeblack/pion-mcp/badges/card.svg)](https://glama.ai/mcp/servers/jleeblack/pion-mcp)
7
+
6
8
  > ⚠️ **Reads: both networks. Payments: testnet only.** The chain tools query Pi
7
9
  > Mainnet or Pi Testnet, selected with `PION_NETWORK`. `send_payment` moves real
8
10
  > funds, must be explicitly armed, and *cannot* be armed on mainnet — Pi
@@ -97,6 +99,35 @@ a payment. The tool reports exactly which step failed, whether funds left the
97
99
  wallet, and the payment id needed to clean up. A blind retry could pay twice,
98
100
  so it refuses to guess.
99
101
 
102
+ ## Requirements
103
+
104
+ **Node.js 22.12.0 or newer.**
105
+
106
+ This floor is higher than earlier releases advertised, and correcting it is
107
+ the reason 0.5.0 is a minor rather than a patch. Through 0.4.2 `package.json`
108
+ declared `>=18.17`, which was never true: the `@stellar/stellar-sdk` 16.x that
109
+ 0.4.x pinned already declared `>=22.0.0` of its own, so Node 18 and 20 were
110
+ outside what the dependency supported the whole time. Nothing surfaced it,
111
+ because our own field is the one npm checks an install against — a package
112
+ cannot be warned about a floor it is itself misreporting.
113
+
114
+ 0.5.0 moves to `@stellar/stellar-sdk` 17.x, whose floor is `>=22.12.0` (its
115
+ CommonJS build requires ESM-only dependencies, and `require(esm)` is only
116
+ unflagged from 22.12.0), and sets our declared floor to match it honestly.
117
+
118
+ If you are on Node 18 or 20, what this actually means:
119
+
120
+ - The chain read tools and `verify_user` never load the Stellar SDK — it is
121
+ imported lazily, inside the payment handler — so those paths are unlikely
122
+ to be affected in practice.
123
+ - **`send_payment` is the part that genuinely needs 22.12.0.** It is also the
124
+ only part that moves funds, which is why the floor is stated here rather
125
+ than left to fail somewhere expensive.
126
+
127
+ Upgrading Node is the supported fix. Pinning 0.4.2 preserves the old declared
128
+ floor but not a working payment path — that release depends on an SDK that
129
+ did not support your runtime either.
130
+
100
131
  ## Usage
101
132
 
102
133
  MCP clients can run it straight from npm — no install step:
@@ -130,8 +161,8 @@ claude mcp add pion -- node /absolute/path/to/pion-mcp/dist/index.js
130
161
 
131
162
  | Variable | Default | Purpose |
132
163
  |---|---|---|
133
- | `PION_NETWORK` | `testnet` | Which chain the read tools query — `testnet` or `mainnet` |
134
- | `PION_HORIZON_URL` | derived from `PION_NETWORK` | Horizon base URL, overriding the above |
164
+ | `PION_NETWORK` | `testnet` | Which chain the read tools query — `testnet` or `mainnet`. Mainnet is echoed as **REAL VALUE** in the startup banner |
165
+ | `PION_HORIZON_URL` | derived from `PION_NETWORK` | Override for the Horizon base URL. Optional. If set alongside `PION_NETWORK` the two must name the same chain — a contradiction is a startup error, not a silent winner |
135
166
  | `PION_PLATFORM_URL` | `https://api.minepi.com` | Platform API base URL |
136
167
  | `PION_ENABLE_PAYMENTS` | unset (off) | Arms `send_payment` — see Tier C above |
137
168
  | `PION_MAX_PAYMENT_PI` | unset | Required per-payment ceiling when armed |
@@ -159,6 +190,7 @@ npm run smoke # end-to-end against live testnet
159
190
  npm run smoke:mainnet # the same checks against live mainnet
160
191
  npm run crossnet # proves the two chains are actually distinguished
161
192
  npm run arming # Tier C guards and spend cap (no credentials needed)
193
+ npm run signing # golden-XDR check on the A2U signing path (offline)
162
194
  ```
163
195
 
164
196
  `npm run smoke` spawns the server over stdio as a real MCP client, discovers a
@@ -182,6 +214,14 @@ a result. It uses a freshly generated, never-funded keypair. The one live call
182
214
  it makes is a deliberately-rejected create against the Pi API, which proves the
183
215
  first failure stage end to end.
184
216
 
217
+ `npm run signing` rebuilds the exact transaction `send_payment` signs, with
218
+ every input pinned, and compares the envelope and hash against bytes recorded
219
+ in the file. It is offline and cannot spend. Its job is dependency bumps: the
220
+ other suites all stub the network, so none of them can tell you whether an SDK
221
+ upgrade changed what you put on the wire. Run it on any `@stellar/stellar-sdk`
222
+ change — a failure means the bytes moved, and the constants should not be
223
+ refreshed until you know why.
224
+
185
225
  ## Known gaps
186
226
 
187
227
  - **`verify_user` success path — confirmed** against a live token. Returns
package/dist/index.js CHANGED
@@ -50,8 +50,11 @@ if (process.argv.includes("--help") || process.argv.includes("-h")) {
50
50
  " send_payment (only when explicitly armed — see below)",
51
51
  "",
52
52
  "Environment:",
53
- " PION_NETWORK testnet (default) or mainnet — which chain the read tools query",
54
- " PION_HORIZON_URL Horizon base URL, overriding PION_NETWORK's default",
53
+ " PION_NETWORK testnet (default) or mainnet — which chain the read tools",
54
+ " query. mainnet is echoed as REAL VALUE in the startup banner.",
55
+ " PION_HORIZON_URL Override for the Horizon base URL. Optional; derived from",
56
+ " PION_NETWORK when unset. If both are set they must name the",
57
+ " same chain — a contradiction is a startup error, not a guess.",
55
58
  " PION_PLATFORM_URL Platform API base URL (default: https://api.minepi.com)",
56
59
  "",
57
60
  "Reads work on both Pi chains. Payments do not: Pi restricts App-to-User",
@@ -72,29 +75,54 @@ if (NETWORK_ERROR) {
72
75
  console.error(`pion-mcp: ${NETWORK_ERROR.message}`);
73
76
  process.exit(1);
74
77
  }
75
- const server = new McpServer({ name: "pion-mcp", version: VERSION }, {
76
- instructions: `Pion exposes read-only Pi Network data. get_wallet_balance, get_account_payments, ` +
77
- `and query_transaction are public ledger reads from Horizon at ${HORIZON_URL} ` +
78
- `(${NETWORK.label}), needing no credentials. This server is reading ` +
79
- `${NETWORK.label} — every result repeats it in its "network" field, and the two Pi ` +
80
- `chains are separate ledgers, so an address funded on one does not exist on the ` +
81
- `other. Amounts are decimal strings; Pi itself is ` +
82
- 'reported as the asset "PI", custom tokens as "CODE:ISSUER", and liquidity-pool ' +
83
- 'shares as "pool:ID". verify_user is different: it checks a user access token ' +
84
- `against the Pi Platform API at ${PLATFORM_URL} and requires the caller to supply ` +
85
- "that token. No tool here can send payments, sign anything, or spend from a wallet.",
86
- });
87
- registerGetWalletBalance(server, NETWORK);
88
- registerGetAccountPayments(server, NETWORK);
89
- registerQueryTransaction(server, NETWORK);
90
- registerVerifyUser(server);
91
78
  // Tier C is registered only when fully armed. A disarmed server does not
92
79
  // advertise a payment tool at all, so an agent cannot try to spend and cannot
93
80
  // be talked into thinking it might succeed.
94
81
  //
95
82
  // Passed the resolved network, not the URL: arming turns on what chain this
96
83
  // *is*, and a string containing "testnet" is not the same claim.
84
+ //
85
+ // Resolved BEFORE the server is constructed because the instructions below
86
+ // state whether a spend tool exists, and that claim has to be built from the
87
+ // same answer that decides whether one is registered. Until 0.4.2 the check
88
+ // ran afterwards and the instructions said "no tool here can send payments"
89
+ // unconditionally — false on an armed server, which is the one configuration
90
+ // where being wrong about it costs money.
97
91
  const payments = checkPaymentsArming(NETWORK);
92
+ /**
93
+ * Server instructions — the text a client places ahead of the tool catalog.
94
+ *
95
+ * Every session pays for this in context, so it carries only what changes a
96
+ * decision an agent is about to make, and nothing recoverable from a tool
97
+ * description it will read anyway.
98
+ *
99
+ * The cross-chain sentence is the reason this exists. It previously said an
100
+ * address funded on one chain "does not exist on the other" — measured false
101
+ * on 2026-08-14 (docs/FINDINGS.md finding 5): one address held 2.06 Pi on
102
+ * mainnet and 32.29938 Pi on testnet simultaneously. That wording invited
103
+ * exactly the wrong inference, that a wrong-chain read fails loudly. It does
104
+ * not; it returns a plausible number. An agent needs the true version before
105
+ * it reports a figure, not after.
106
+ */
107
+ const instructions = `Pion reads Pi Network chain data. get_wallet_balance, get_account_payments and ` +
108
+ `query_transaction are public Horizon reads at ${HORIZON_URL}, needing no ` +
109
+ `credentials. verify_user checks a caller-supplied user access token against the ` +
110
+ `Pi Platform API at ${PLATFORM_URL}. ` +
111
+ `This server reads ${NETWORK.label}, and every result carries a "network" field — ` +
112
+ `check it before trusting a figure. The two Pi chains are separate ledgers sharing ` +
113
+ `one address format, and the same address can hold different balances on each: a ` +
114
+ `wrong-chain read returns a plausible wrong number, not an error. Testnet Pi has no ` +
115
+ `monetary value. Amounts are decimal strings; Pi is "PI", tokens "CODE:ISSUER", ` +
116
+ `pool shares "pool:ID". ` +
117
+ (payments.armed
118
+ ? `send_payment is ARMED and can spend up to ${payments.config.maxAmountPi} Pi per ` +
119
+ `call from the app wallet on ${NETWORK.label}; every other tool is read-only.`
120
+ : "No tool here can send payments, sign anything, or spend from a wallet.");
121
+ const server = new McpServer({ name: "pion-mcp", version: VERSION }, { instructions });
122
+ registerGetWalletBalance(server, NETWORK);
123
+ registerGetAccountPayments(server, NETWORK);
124
+ registerQueryTransaction(server, NETWORK);
125
+ registerVerifyUser(server);
98
126
  if (payments.armed) {
99
127
  registerSendPayment(server, payments.config);
100
128
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pion-mcp",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Pion — Model Context Protocol (MCP) server for Pi Network. Read-only chain queries against Pi Mainnet and Testnet; payments are testnet-only.",
5
5
  "keywords": [
6
6
  "mcp",
@@ -26,7 +26,7 @@
26
26
  "dist"
27
27
  ],
28
28
  "engines": {
29
- "node": ">=18.17"
29
+ "node": ">=22.12.0"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "tsc",
@@ -38,6 +38,7 @@
38
38
  "crossnet": "node scripts/crossnet-test.mjs",
39
39
  "arming": "node scripts/arming-test.mjs",
40
40
  "u2a": "node scripts/u2a-test.mjs",
41
+ "signing": "node scripts/signing-test.mjs",
41
42
  "incomplete": "node scripts/incomplete.mjs",
42
43
  "probe:a2u": "node scripts/probe-a2u.mjs",
43
44
  "diagnose:a2u": "node scripts/diagnose-a2u.mjs",
@@ -53,7 +54,7 @@
53
54
  },
54
55
  "dependencies": {
55
56
  "@modelcontextprotocol/sdk": "^1.30.0",
56
- "@stellar/stellar-sdk": "^16.2.0",
57
+ "@stellar/stellar-sdk": "^17.0.0",
57
58
  "zod": "^4.4.3"
58
59
  },
59
60
  "devDependencies": {