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 +16 -0
- package/dist/index.js +1 -1
- package/dist/x402-signers.js +67 -28
- package/package.json +1 -1
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.
|
|
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
|
package/dist/x402-signers.js
CHANGED
|
@@ -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
|
-
|
|
21
|
-
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
import
|
|
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
|
-
|
|
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
|
|
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