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 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.1";
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
@@ -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;
@@ -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: 1,
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: 1,
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 requirements;
304
+ let decoded;
260
305
  try {
261
306
  const json = Buffer.from(args.paymentRequiredHeader, "base64").toString("utf-8");
262
- requirements = JSON.parse(json);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mpp32-mcp-server",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
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",