mpp32-mcp-server 1.2.1 → 1.2.2
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 +25 -0
- package/dist/index.js +1 -1
- package/dist/x402-signers.d.ts +2 -2
- package/dist/x402-signers.js +74 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ 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.2] - 2026-05-11
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
* **x402 v2 envelope (the whole third-party catalog).** Every third-party
|
|
12
|
+
x402 provider we tested — Venice (`api.venice.ai`), Exa (`api.exa.ai`),
|
|
13
|
+
Firecrawl, OpenAI's x402 gateway, etc. — ships the **v2** challenge
|
|
14
|
+
shape: `{x402Version: 2, accepts: [{scheme, network, asset, payTo,
|
|
15
|
+
amount, ...}, ...]}`. 1.2.0 and 1.2.1 only understood **v1**
|
|
16
|
+
(`{scheme, network, asset, payTo, maxAmountRequired, ...}` at the top
|
|
17
|
+
level), so every third-party call failed with "missing network field"
|
|
18
|
+
before we ever signed anything. The signer now:
|
|
19
|
+
- Detects v1 vs v2 by the presence of `accepts: [...]`.
|
|
20
|
+
- Picks the first `accepts[]` entry it can pay (prefers EVM when
|
|
21
|
+
`MPP32_PRIVATE_KEY` is set, falls back to SVM when only the Solana
|
|
22
|
+
key is set, falls back to the first entry so the per-network error
|
|
23
|
+
surfaces precisely).
|
|
24
|
+
- Maps v2's `amount` to v1's `maxAmountRequired` internally, so the
|
|
25
|
+
same signing code handles both.
|
|
26
|
+
- Echoes the challenge's `x402Version` back in the outgoing envelope
|
|
27
|
+
so servers that key off it (Venice's facilitator does) accept the
|
|
28
|
+
response. v1 challenges still get v1 back; v2 challenges get v2.
|
|
29
|
+
This unblocks paid access to the entire ~4,500-entry federated
|
|
30
|
+
catalog, which is overwhelmingly v2.
|
|
31
|
+
|
|
7
32
|
## [1.2.1] - 2026-05-11
|
|
8
33
|
|
|
9
34
|
### Fixed
|
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.2";
|
|
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.d.ts
CHANGED
|
@@ -25,8 +25,8 @@ export interface X402PaymentEnvelope {
|
|
|
25
25
|
}
|
|
26
26
|
export declare function isSvmNetwork(network: string): boolean;
|
|
27
27
|
export declare function isEvmNetwork(network: string): boolean;
|
|
28
|
-
export declare function signX402PaymentSvm(requirements: X402PaymentRequirements, rawKey: string, rpcUrlOverride?: string): Promise<string>;
|
|
29
|
-
export declare function signX402PaymentEvm(requirements: X402PaymentRequirements, rawKey: string): Promise<string>;
|
|
28
|
+
export declare function signX402PaymentSvm(requirements: X402PaymentRequirements, rawKey: string, rpcUrlOverride?: string, echoedVersion?: number): Promise<string>;
|
|
29
|
+
export declare function signX402PaymentEvm(requirements: X402PaymentRequirements, rawKey: string, echoedVersion?: number): Promise<string>;
|
|
30
30
|
export interface SignX402Args {
|
|
31
31
|
paymentRequiredHeader: string;
|
|
32
32
|
solanaKey?: string;
|
package/dist/x402-signers.js
CHANGED
|
@@ -111,7 +111,7 @@ async function buildSolanaSigner(rawKey, deps) {
|
|
|
111
111
|
}
|
|
112
112
|
// ── SVM signer ──────────────────────────────────────────────────────────────
|
|
113
113
|
const DEFAULT_SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
|
114
|
-
export async function signX402PaymentSvm(requirements, rawKey, rpcUrlOverride) {
|
|
114
|
+
export async function signX402PaymentSvm(requirements, rawKey, rpcUrlOverride, echoedVersion = 1) {
|
|
115
115
|
if (requirements.scheme !== "exact") {
|
|
116
116
|
throw new Error(`SVM x402 scheme "${requirements.scheme}" not implemented; only "exact" is supported.`);
|
|
117
117
|
}
|
|
@@ -165,7 +165,7 @@ export async function signX402PaymentSvm(requirements, rawKey, rpcUrlOverride) {
|
|
|
165
165
|
const partiallySigned = await deps.partiallySignTransactionMessageWithSigners(message);
|
|
166
166
|
const base64Tx = deps.getBase64EncodedWireTransaction(partiallySigned);
|
|
167
167
|
const envelope = {
|
|
168
|
-
x402Version:
|
|
168
|
+
x402Version: echoedVersion,
|
|
169
169
|
scheme: "exact",
|
|
170
170
|
network: requirements.network,
|
|
171
171
|
payload: { transaction: base64Tx },
|
|
@@ -179,7 +179,7 @@ function randomHex32() {
|
|
|
179
179
|
buf[i] = Math.floor(Math.random() * 256);
|
|
180
180
|
return ("0x" + buf.toString("hex"));
|
|
181
181
|
}
|
|
182
|
-
export async function signX402PaymentEvm(requirements, rawKey) {
|
|
182
|
+
export async function signX402PaymentEvm(requirements, rawKey, echoedVersion = 1) {
|
|
183
183
|
if (requirements.scheme !== "exact") {
|
|
184
184
|
throw new Error(`EVM x402 scheme "${requirements.scheme}" not implemented; only "exact" is supported.`);
|
|
185
185
|
}
|
|
@@ -238,7 +238,7 @@ export async function signX402PaymentEvm(requirements, rawKey) {
|
|
|
238
238
|
message: messageObj,
|
|
239
239
|
});
|
|
240
240
|
const envelope = {
|
|
241
|
-
x402Version:
|
|
241
|
+
x402Version: echoedVersion,
|
|
242
242
|
scheme: "exact",
|
|
243
243
|
network: requirements.network,
|
|
244
244
|
payload: {
|
|
@@ -255,29 +255,93 @@ export async function signX402PaymentEvm(requirements, rawKey) {
|
|
|
255
255
|
};
|
|
256
256
|
return Buffer.from(JSON.stringify(envelope)).toString("base64");
|
|
257
257
|
}
|
|
258
|
+
function isV2Challenge(decoded) {
|
|
259
|
+
return (!!decoded &&
|
|
260
|
+
typeof decoded === "object" &&
|
|
261
|
+
Array.isArray(decoded.accepts));
|
|
262
|
+
}
|
|
263
|
+
// Normalize a v2 `accepts[i]` to our internal requirements shape. v2 uses
|
|
264
|
+
// `amount`, v1 uses `maxAmountRequired` — we map both into the same field so
|
|
265
|
+
// the downstream signers don't have to know which version produced the input.
|
|
266
|
+
function normalizeRequirements(raw) {
|
|
267
|
+
const amount = raw.maxAmountRequired ?? raw.amount ?? "";
|
|
268
|
+
return {
|
|
269
|
+
scheme: String(raw.scheme ?? "exact"),
|
|
270
|
+
network: String(raw.network ?? ""),
|
|
271
|
+
maxAmountRequired: amount,
|
|
272
|
+
resource: String(raw.resource ?? ""),
|
|
273
|
+
description: raw.description,
|
|
274
|
+
mimeType: raw.mimeType,
|
|
275
|
+
payTo: String(raw.payTo ?? ""),
|
|
276
|
+
maxTimeoutSeconds: raw.maxTimeoutSeconds,
|
|
277
|
+
asset: String(raw.asset ?? ""),
|
|
278
|
+
outputSchema: raw.outputSchema,
|
|
279
|
+
extra: raw.extra,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
// Pick the first entry in `accepts` we can actually sign. Preference order:
|
|
283
|
+
// 1. EVM entries when an EVM key is available (Base is the dominant chain).
|
|
284
|
+
// 2. SVM entries when a Solana key is available.
|
|
285
|
+
// If no entry matches the keys we hold, fall back to the first entry and let
|
|
286
|
+
// the per-network signer throw a precise "you need MPP32_X_PRIVATE_KEY" error.
|
|
287
|
+
function pickRequirements(accepts, haveSvm, haveEvm) {
|
|
288
|
+
if (accepts.length === 0) {
|
|
289
|
+
throw new Error("x402 v2 challenge has empty `accepts` array — nothing to pay.");
|
|
290
|
+
}
|
|
291
|
+
if (haveEvm) {
|
|
292
|
+
const evm = accepts.find((a) => isEvmNetwork(a.network));
|
|
293
|
+
if (evm)
|
|
294
|
+
return evm;
|
|
295
|
+
}
|
|
296
|
+
if (haveSvm) {
|
|
297
|
+
const svm = accepts.find((a) => isSvmNetwork(a.network));
|
|
298
|
+
if (svm)
|
|
299
|
+
return svm;
|
|
300
|
+
}
|
|
301
|
+
return accepts[0];
|
|
302
|
+
}
|
|
258
303
|
export async function signX402Payment(args) {
|
|
259
|
-
let
|
|
304
|
+
let decoded;
|
|
260
305
|
try {
|
|
261
306
|
const json = Buffer.from(args.paymentRequiredHeader, "base64").toString("utf-8");
|
|
262
|
-
|
|
307
|
+
decoded = JSON.parse(json);
|
|
263
308
|
}
|
|
264
309
|
catch (err) {
|
|
265
310
|
throw new Error(`Could not decode Payment-Required header as base64 JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
266
311
|
}
|
|
312
|
+
// Resolve v1 vs v2. v2's `x402Version` field tells the server which envelope
|
|
313
|
+
// shape to expect back — we mirror whichever the challenge used.
|
|
314
|
+
let requirements;
|
|
315
|
+
let echoedVersion;
|
|
316
|
+
if (isV2Challenge(decoded)) {
|
|
317
|
+
const accepts = decoded.accepts
|
|
318
|
+
.filter((a) => !!a && typeof a === "object")
|
|
319
|
+
.map(normalizeRequirements);
|
|
320
|
+
requirements = pickRequirements(accepts, !!args.solanaKey, !!args.evmKey);
|
|
321
|
+
echoedVersion = decoded.x402Version || 2;
|
|
322
|
+
}
|
|
323
|
+
else if (decoded && typeof decoded === "object") {
|
|
324
|
+
requirements = normalizeRequirements(decoded);
|
|
325
|
+
echoedVersion = decoded.x402Version || 1;
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
throw new Error("Decoded Payment-Required is not a JSON object.");
|
|
329
|
+
}
|
|
267
330
|
if (!requirements.network)
|
|
268
331
|
throw new Error("x402 payment requirements missing 'network'");
|
|
269
332
|
if (!requirements.asset)
|
|
270
333
|
throw new Error("x402 payment requirements missing 'asset'");
|
|
271
334
|
if (!requirements.payTo)
|
|
272
335
|
throw new Error("x402 payment requirements missing 'payTo'");
|
|
273
|
-
if (!requirements.maxAmountRequired)
|
|
274
|
-
throw new Error("x402 payment requirements missing 'maxAmountRequired'");
|
|
336
|
+
if (!requirements.maxAmountRequired) {
|
|
337
|
+
throw new Error("x402 payment requirements missing 'maxAmountRequired'/'amount'");
|
|
338
|
+
}
|
|
275
339
|
if (isSvmNetwork(requirements.network)) {
|
|
276
340
|
if (!args.solanaKey) {
|
|
277
341
|
throw new Error(`Provider requires SVM payment on ${requirements.network}, but MPP32_SOLANA_PRIVATE_KEY is not configured. ` +
|
|
278
342
|
`Set it in your MCP config to enable USDC-on-Solana payments.`);
|
|
279
343
|
}
|
|
280
|
-
const header = await signX402PaymentSvm(requirements, args.solanaKey, args.solanaRpcUrl);
|
|
344
|
+
const header = await signX402PaymentSvm(requirements, args.solanaKey, args.solanaRpcUrl, echoedVersion);
|
|
281
345
|
return {
|
|
282
346
|
xPaymentHeader: header,
|
|
283
347
|
network: requirements.network,
|
|
@@ -290,7 +354,7 @@ export async function signX402Payment(args) {
|
|
|
290
354
|
throw new Error(`Provider requires EVM payment on ${requirements.network}, but MPP32_PRIVATE_KEY is not configured. ` +
|
|
291
355
|
`Set it in your MCP config to enable USDC-on-Base payments.`);
|
|
292
356
|
}
|
|
293
|
-
const header = await signX402PaymentEvm(requirements, args.evmKey);
|
|
357
|
+
const header = await signX402PaymentEvm(requirements, args.evmKey, echoedVersion);
|
|
294
358
|
return {
|
|
295
359
|
xPaymentHeader: header,
|
|
296
360
|
network: requirements.network,
|
package/package.json
CHANGED