mpp32-mcp-server 1.2.0 → 1.2.1

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/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to `mpp32-mcp-server` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
5
5
  project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.2.1] - 2026-05-11
8
+
9
+ ### Fixed
10
+
11
+ * **"Server disconnected" on startup under Claude Desktop's bundled Node.**
12
+ 1.2.0 imported `@solana/kit` (and `@solana-program/*`) at the top of the
13
+ payment-signer module. Those packages declare `engines.node: ">=20.18.0"`
14
+ and use Node-20-only WebCrypto Ed25519 APIs at load time. Claude Desktop
15
+ ships a bundled Node that on many installs is still 18.x, so the import
16
+ threw before the MCP server could answer the initialize handshake — the
17
+ process exited and Claude Desktop reported only "Server disconnected"
18
+ with no further diagnostics. All Solana and EVM crypto deps are now
19
+ loaded lazily inside the signer functions. The server boots on any Node
20
+ that supports MCP; only a `solana:*` payment attempt fails on too-old
21
+ Node, and now with a clear actionable error.
22
+
7
23
  ## [1.2.0] - 2026-05-11
8
24
 
9
25
  This release makes x402 payments actually work end-to-end. Prior versions
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
5
  import { signX402Payment } from "./x402-signers.js";
6
- const SERVER_VERSION = "1.2.0";
6
+ const SERVER_VERSION = "1.2.1";
7
7
  // ── Env loading: trim and sanitize aggressively ─────────────────────────────
8
8
  // Copy-paste from Claude Desktop / Cursor / Windsurf JSON config UIs frequently
9
9
  // adds trailing \n, \r, NBSP, BOM, or wraps the value in literal quotes. Any
@@ -17,12 +17,50 @@
17
17
  // In both cases the outer envelope is
18
18
  // { x402Version: 1, scheme: "exact", network, payload: <scheme payload> }
19
19
  // base64-encoded into the `X-Payment` HTTP header.
20
- import { address, createKeyPairSignerFromBytes, createSolanaRpc, createTransactionMessage, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, partiallySignTransactionMessageWithSigners, getBase64EncodedWireTransaction, pipe, } from "@solana/kit";
21
- import { getTransferCheckedInstruction, findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS, } from "@solana-program/token";
22
- import { getSetComputeUnitLimitInstruction, getSetComputeUnitPriceInstruction, } from "@solana-program/compute-budget";
23
- import { privateKeyToAccount } from "viem/accounts";
24
- import bs58 from "bs58";
25
- import nacl from "tweetnacl";
20
+ async function loadSvmDeps() {
21
+ const [kit, tokenProgram, computeBudgetProgram, bs58Mod, naclMod] = await Promise.all([
22
+ import("@solana/kit"),
23
+ import("@solana-program/token"),
24
+ import("@solana-program/compute-budget"),
25
+ import("bs58"),
26
+ import("tweetnacl"),
27
+ ]).catch((err) => {
28
+ const msg = err instanceof Error ? err.message : String(err);
29
+ throw new Error(`Could not load Solana signing libraries: ${msg}. ` +
30
+ `Solana x402 payments require Node 20.18 or newer (Claude Desktop's bundled Node may be older). ` +
31
+ `Upgrade Node, or run mpp32-mcp-server under a system Node 20+ via your MCP config's "command".`);
32
+ });
33
+ return {
34
+ address: kit.address,
35
+ createKeyPairSignerFromBytes: kit.createKeyPairSignerFromBytes,
36
+ createSolanaRpc: kit.createSolanaRpc,
37
+ createTransactionMessage: kit.createTransactionMessage,
38
+ setTransactionMessageFeePayer: kit.setTransactionMessageFeePayer,
39
+ setTransactionMessageLifetimeUsingBlockhash: kit.setTransactionMessageLifetimeUsingBlockhash,
40
+ appendTransactionMessageInstructions: kit.appendTransactionMessageInstructions,
41
+ partiallySignTransactionMessageWithSigners: kit.partiallySignTransactionMessageWithSigners,
42
+ getBase64EncodedWireTransaction: kit.getBase64EncodedWireTransaction,
43
+ pipe: kit.pipe,
44
+ getTransferCheckedInstruction: tokenProgram.getTransferCheckedInstruction,
45
+ findAssociatedTokenPda: tokenProgram.findAssociatedTokenPda,
46
+ TOKEN_PROGRAM_ADDRESS: tokenProgram.TOKEN_PROGRAM_ADDRESS,
47
+ getSetComputeUnitLimitInstruction: computeBudgetProgram.getSetComputeUnitLimitInstruction,
48
+ getSetComputeUnitPriceInstruction: computeBudgetProgram.getSetComputeUnitPriceInstruction,
49
+ bs58: bs58Mod.default ?? bs58Mod,
50
+ nacl: naclMod.default ?? naclMod,
51
+ };
52
+ }
53
+ async function loadEvmDeps() {
54
+ try {
55
+ const viemAccounts = await import("viem/accounts");
56
+ return { privateKeyToAccount: viemAccounts.privateKeyToAccount };
57
+ }
58
+ catch (err) {
59
+ const msg = err instanceof Error ? err.message : String(err);
60
+ throw new Error(`Could not load EVM signing libraries: ${msg}. ` +
61
+ `Base / Ethereum x402 payments require Node 18 or newer. Upgrade Node and retry.`);
62
+ }
63
+ }
26
64
  // ── Network classification ──────────────────────────────────────────────────
27
65
  const SOLANA_MAINNET = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
28
66
  const SOLANA_DEVNET = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
@@ -46,8 +84,7 @@ function chainSpecFor(network) {
46
84
  }
47
85
  throw new Error(`Unsupported EVM network "${network}". x402 EVM payments currently support: base, base-sepolia, ethereum.`);
48
86
  }
49
- // ── Key decoding ────────────────────────────────────────────────────────────
50
- function decodeSolanaSecret(raw) {
87
+ function decodeSolanaSecret(raw, deps) {
51
88
  if (raw.startsWith("[")) {
52
89
  const arr = JSON.parse(raw);
53
90
  if (!Array.isArray(arr))
@@ -57,20 +94,20 @@ function decodeSolanaSecret(raw) {
57
94
  if (/^[0-9a-fA-F]+$/.test(raw) && raw.length % 2 === 0) {
58
95
  return new Uint8Array(Buffer.from(raw, "hex"));
59
96
  }
60
- return bs58.decode(raw);
97
+ return deps.bs58.decode(raw);
61
98
  }
62
- async function buildSolanaSigner(rawKey) {
63
- let bytes = decodeSolanaSecret(rawKey);
99
+ async function buildSolanaSigner(rawKey, deps) {
100
+ let bytes = decodeSolanaSecret(rawKey, deps);
64
101
  if (bytes.length === 32) {
65
102
  // 32-byte seed — kit's createKeyPairSignerFromBytes wants the 64-byte
66
103
  // expanded key. Derive via tweetnacl.
67
- const kp = nacl.sign.keyPair.fromSeed(bytes);
104
+ const kp = deps.nacl.sign.keyPair.fromSeed(bytes);
68
105
  bytes = kp.secretKey;
69
106
  }
70
107
  else if (bytes.length !== 64) {
71
108
  throw new Error(`Solana private key must be a 32-byte seed or a 64-byte expanded key; got ${bytes.length} bytes.`);
72
109
  }
73
- return await createKeyPairSignerFromBytes(bytes);
110
+ return await deps.createKeyPairSignerFromBytes(bytes);
74
111
  }
75
112
  // ── SVM signer ──────────────────────────────────────────────────────────────
76
113
  const DEFAULT_SOLANA_RPC = "https://api.mainnet-beta.solana.com";
@@ -86,33 +123,34 @@ export async function signX402PaymentSvm(requirements, rawKey, rpcUrlOverride) {
86
123
  const amount = BigInt(requirements.maxAmountRequired);
87
124
  if (amount <= 0n)
88
125
  throw new Error(`Invalid maxAmountRequired: ${requirements.maxAmountRequired}`);
89
- const signer = await buildSolanaSigner(rawKey);
126
+ const deps = await loadSvmDeps();
127
+ const signer = await buildSolanaSigner(rawKey, deps);
90
128
  const payerAddress = signer.address;
91
- const mintAddress = address(requirements.asset);
92
- const recipientAddress = address(requirements.payTo);
93
- const feePayerAddress = address(requirements.extra.feePayer);
129
+ const mintAddress = deps.address(requirements.asset);
130
+ const recipientAddress = deps.address(requirements.payTo);
131
+ const feePayerAddress = deps.address(requirements.extra.feePayer);
94
132
  // Derive both sides' associated token accounts (classic SPL Token program).
95
133
  const [sourceAtaTuple, destinationAtaTuple] = await Promise.all([
96
- findAssociatedTokenPda({
134
+ deps.findAssociatedTokenPda({
97
135
  owner: payerAddress,
98
136
  mint: mintAddress,
99
- tokenProgram: TOKEN_PROGRAM_ADDRESS,
137
+ tokenProgram: deps.TOKEN_PROGRAM_ADDRESS,
100
138
  }),
101
- findAssociatedTokenPda({
139
+ deps.findAssociatedTokenPda({
102
140
  owner: recipientAddress,
103
141
  mint: mintAddress,
104
- tokenProgram: TOKEN_PROGRAM_ADDRESS,
142
+ tokenProgram: deps.TOKEN_PROGRAM_ADDRESS,
105
143
  }),
106
144
  ]);
107
145
  const sourceAta = sourceAtaTuple[0];
108
146
  const destinationAta = destinationAtaTuple[0];
109
147
  const rpcUrl = rpcUrlOverride && rpcUrlOverride.length > 0 ? rpcUrlOverride : DEFAULT_SOLANA_RPC;
110
- const rpc = createSolanaRpc(rpcUrl);
148
+ const rpc = deps.createSolanaRpc(rpcUrl);
111
149
  const { value: latestBlockhash } = await rpc.getLatestBlockhash({ commitment: "confirmed" }).send();
112
150
  const instructions = [
113
- getSetComputeUnitLimitInstruction({ units: 150_000 }),
114
- getSetComputeUnitPriceInstruction({ microLamports: 1000n }),
115
- getTransferCheckedInstruction({
151
+ deps.getSetComputeUnitLimitInstruction({ units: 150_000 }),
152
+ deps.getSetComputeUnitPriceInstruction({ microLamports: 1000n }),
153
+ deps.getTransferCheckedInstruction({
116
154
  source: sourceAta,
117
155
  mint: mintAddress,
118
156
  destination: destinationAta,
@@ -121,11 +159,11 @@ export async function signX402PaymentSvm(requirements, rawKey, rpcUrlOverride) {
121
159
  decimals,
122
160
  }),
123
161
  ];
124
- const message = pipe(createTransactionMessage({ version: 0 }), (m) => setTransactionMessageFeePayer(feePayerAddress, m), (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), (m) => appendTransactionMessageInstructions(instructions, m));
162
+ const message = deps.pipe(deps.createTransactionMessage({ version: 0 }), (m) => deps.setTransactionMessageFeePayer(feePayerAddress, m), (m) => deps.setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), (m) => deps.appendTransactionMessageInstructions(instructions, m));
125
163
  // Partially sign — fills the payer's signature slot, leaves the fee payer's
126
164
  // slot empty for the facilitator to fill in at /settle time.
127
- const partiallySigned = await partiallySignTransactionMessageWithSigners(message);
128
- const base64Tx = getBase64EncodedWireTransaction(partiallySigned);
165
+ const partiallySigned = await deps.partiallySignTransactionMessageWithSigners(message);
166
+ const base64Tx = deps.getBase64EncodedWireTransaction(partiallySigned);
129
167
  const envelope = {
130
168
  x402Version: 1,
131
169
  scheme: "exact",
@@ -163,6 +201,7 @@ export async function signX402PaymentEvm(requirements, rawKey) {
163
201
  if (!/^0x[0-9a-fA-F]{64}$/.test(keyHex)) {
164
202
  throw new Error("MPP32_PRIVATE_KEY must be a 64-character hex EVM private key (0x-prefixed or bare).");
165
203
  }
204
+ const { privateKeyToAccount } = await loadEvmDeps();
166
205
  const account = privateKeyToAccount(keyHex);
167
206
  const now = Math.floor(Date.now() / 1000);
168
207
  const validAfter = BigInt(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mpp32-mcp-server",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "mcpName": "io.github.MPP32/mpp32-mcp-server",
5
5
  "description": "Payment layer for AI agents. One MCP, five protocols, thousands of paid APIs your agent can call.",
6
6
  "type": "module",