mpp32-mcp-server 1.1.2 → 1.1.3

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,46 @@ 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.1.3] - 2026-05-11
8
+
9
+ ### Fixed
10
+
11
+ * **x402 payments actually work now.** `completeX402Payment` previously
12
+ dynamically imported `@solana/web3.js`, `bs58`, and `tweetnacl`, but
13
+ none of those packages were declared in `dependencies`. On a clean
14
+ `npx mpp32-mcp-server` install they were missing from `node_modules`,
15
+ the import threw, the catch block returned a misleading "check wallet
16
+ balance" message, and Claude paraphrased that as "no funded Solana
17
+ wallet configured" even when `MPP32_SOLANA_PRIVATE_KEY` was set.
18
+ * **Dropped `@solana/web3.js` entirely.** Adding it to `dependencies`
19
+ uncovered a second, deeper bug: its transitive `rpc-websockets`
20
+ bundle is CJS and `require()`s a now ESM-only `uuid`, which throws
21
+ `ERR_REQUIRE_ESM` on Node 20+ the first time the signer loads. We
22
+ only ever used `Keypair.fromSecretKey` and `publicKey.toBase58()`,
23
+ both of which are trivial Ed25519/base58 operations. Signing is now
24
+ done directly with `tweetnacl` + `bs58`, so the failure mode is gone
25
+ and the install footprint is ~30 MB smaller.
26
+ * **Properly declared signing dependencies.** `bs58` and `tweetnacl`
27
+ are now real `dependencies` rather than implicit assumptions.
28
+ * **Better error when a payment dependency genuinely fails to load.**
29
+ The catch around each dynamic import now tells the user the package
30
+ ships with `mpp32-mcp-server` and directs them to upgrade to
31
+ `@latest` instead of suggesting a manual `npm install` that won't
32
+ stick across `npx` runs.
33
+ * **32-byte seed Solana keys now accepted** in addition to 64-byte
34
+ expanded keys (previously `Keypair.fromSecretKey` rejected seeds).
35
+
36
+ ### Added
37
+
38
+ * **Catalog total surfaced in `list_mpp32_services`.** The federated
39
+ catalog has 4,500+ external services but the API returns at most 500
40
+ per call (default 100). The response now includes
41
+ `data.totalAvailable` and a `truncated`/`hint` pair so the model
42
+ knows results were paginated and how to drill in with `q`,
43
+ `category`, `source`, or `protocol`. The MCP client formats the
44
+ header as `Found N services (of M total available in catalog)` and
45
+ prints the hint when applicable.
46
+
7
47
  ## [1.1.2] - 2026-05-10
8
48
 
9
49
  ### Fixed
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  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
- const SERVER_VERSION = "1.1.2";
5
+ const SERVER_VERSION = "1.1.3";
6
6
  // ── Env loading: trim and sanitize aggressively ─────────────────────────────
7
7
  // Copy-paste from Claude Desktop / Cursor / Windsurf JSON config UIs frequently
8
8
  // adds trailing \n, \r, NBSP, BOM, or wraps the value in literal quotes. Any
@@ -174,7 +174,7 @@ function isHttpCallable(svc) {
174
174
  return /^https?:\/\//.test(url);
175
175
  }
176
176
  // ── Tool 1: list_mpp32_services ─────────────────────────────────────────────
177
- server.tool("list_mpp32_services", "Browse the MPP32 federated catalog of machine-payable APIs and data services. Includes native MPP32 services (callable end-to-end through this MCP), the x402 Bazaar (USDC on Solana), curated free APIs (DexScreener, Jupiter, CoinGecko health, httpbin, etc.), and the public MCP Registry (npx-installable servers; listing-only). Each result indicates whether it is callable through `call_mpp32_endpoint` or listing-only. Use the `category`, `q`, or `source` filters to narrow down.", {
177
+ server.tool("list_mpp32_services", "Browse the MPP32 federated catalog of 4,500+ machine-payable APIs and data services. Includes native MPP32 services (callable end-to-end through this MCP), the x402 Bazaar (USDC on Solana), curated free APIs (DexScreener, Jupiter, CoinGecko health, httpbin, etc.), and the public MCP Registry (npx-installable servers; listing-only). Each result indicates whether it is callable through `call_mpp32_endpoint` or listing-only. The catalog is large (~4,500 entries) — by default a single call returns up to 100 results and the response will tell you the true total and whether the page was truncated. Use `q` (free-text search), `category`, `source`, or `protocol` to narrow down, or raise `limit` (max 500) for broader pages.", {
178
178
  category: z
179
179
  .string()
180
180
  .optional()
@@ -251,17 +251,24 @@ server.tool("list_mpp32_services", "Browse the MPP32 federated catalog of machin
251
251
  .join("\n");
252
252
  });
253
253
  const counts = json.data.counts;
254
+ const totalAvailable = json.data.totalAvailable;
254
255
  const callableCount = services.filter(isHttpCallable).length;
256
+ const sourcesLine = totalAvailable
257
+ ? `**Sources:** ${counts.native} native + ${counts.external} external (of ${totalAvailable.combined} total available in catalog). **Callable through this MCP:** ${callableCount}.`
258
+ : `**Sources:** ${counts.native} native + ${counts.external} external. **Callable through this MCP:** ${callableCount}.`;
255
259
  const header = [
256
260
  `# MPP32 Federated Catalog — ${services.length} result${services.length !== 1 ? "s" : ""}`,
257
261
  ``,
258
- `**Sources:** ${counts.native} native + ${counts.external} external. **Callable through this MCP:** ${callableCount}.`,
262
+ sourcesLine,
263
+ json.data.truncated && json.data.hint ? `\n> ⚠️ ${json.data.hint}` : ``,
259
264
  ``,
260
265
  AGENT_KEY
261
266
  ? `Calls through \`call_mpp32_endpoint\` are tracked in your dashboard at ${API_URL}/agent-console (your X-Agent-Key is set).`
262
267
  : `**Tip:** set \`MPP32_AGENT_KEY\` in your MCP config to track usage at ${API_URL}/agent-console. Get a key at ${API_URL}/agent-console.`,
263
268
  ``,
264
- ].join("\n");
269
+ ]
270
+ .filter((l) => l !== ``)
271
+ .join("\n");
265
272
  return {
266
273
  content: [{ type: "text", text: header + "\n" + lines.join("\n\n") }],
267
274
  };
@@ -970,40 +977,70 @@ async function completeX402Payment(paymentRequiredHeader, solanaPrivateKey) {
970
977
  catch {
971
978
  throw new Error("Could not decode Payment-Required header");
972
979
  }
980
+ // We sign x402 challenges with raw Ed25519 (Solana keys are Ed25519). No
981
+ // @solana/web3.js needed — it pulls in rpc-websockets which has a
982
+ // CJS/ESM uuid incompat on Node 20+ that breaks every paid call.
973
983
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
974
- let solanaWeb3;
984
+ let tweetnacl;
975
985
  try {
976
- const pkg = "@solana/web3.js";
977
- solanaWeb3 = await import(pkg);
986
+ const pkg = "tweetnacl";
987
+ tweetnacl = await import(pkg);
978
988
  }
979
- catch {
980
- throw new Error("x402 payment requires @solana/web3.js: npm install @solana/web3.js");
989
+ catch (err) {
990
+ throw new Error(`x402 signing requires tweetnacl, which ships with mpp32-mcp-server. ` +
991
+ `If you're seeing this on a clean npx install, upgrade to mpp32-mcp-server@latest. ` +
992
+ `Underlying error: ${err instanceof Error ? err.message : String(err)}`);
981
993
  }
982
994
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
983
- let keypair;
995
+ let bs58;
996
+ try {
997
+ const pkg = "bs58";
998
+ bs58 = await import(pkg);
999
+ }
1000
+ catch (err) {
1001
+ throw new Error(`x402 signing requires bs58, which ships with mpp32-mcp-server. ` +
1002
+ `Upgrade to mpp32-mcp-server@latest. ` +
1003
+ `Underlying error: ${err instanceof Error ? err.message : String(err)}`);
1004
+ }
1005
+ const bs58Decode = bs58.default?.decode ?? bs58.decode;
1006
+ const bs58Encode = bs58.default?.encode ?? bs58.encode;
1007
+ const naclSign = tweetnacl.default?.sign ?? tweetnacl.sign;
1008
+ let rawKey;
984
1009
  try {
985
1010
  if (solanaPrivateKey.startsWith("[")) {
986
- keypair = solanaWeb3.Keypair.fromSecretKey(new Uint8Array(JSON.parse(solanaPrivateKey)));
1011
+ rawKey = new Uint8Array(JSON.parse(solanaPrivateKey));
987
1012
  }
988
1013
  else if (/^[0-9a-fA-F]+$/.test(solanaPrivateKey) && solanaPrivateKey.length % 2 === 0) {
989
- keypair = solanaWeb3.Keypair.fromSecretKey(new Uint8Array(Buffer.from(solanaPrivateKey, "hex")));
1014
+ rawKey = new Uint8Array(Buffer.from(solanaPrivateKey, "hex"));
990
1015
  }
991
1016
  else {
992
- const bs58Pkg = "bs58";
993
- const bs58 = await import(bs58Pkg);
994
- keypair = solanaWeb3.Keypair.fromSecretKey(bs58.default.decode(solanaPrivateKey));
1017
+ rawKey = bs58Decode(solanaPrivateKey);
995
1018
  }
996
1019
  }
997
1020
  catch (err) {
998
1021
  throw new Error(`Could not decode Solana private key: ${err instanceof Error ? err.message : String(err)}`);
999
1022
  }
1023
+ let secretKey;
1024
+ let publicKey;
1025
+ if (rawKey.length === 64) {
1026
+ secretKey = rawKey;
1027
+ publicKey = rawKey.slice(32);
1028
+ }
1029
+ else if (rawKey.length === 32) {
1030
+ const kp = naclSign.keyPair.fromSeed(rawKey);
1031
+ secretKey = kp.secretKey;
1032
+ publicKey = kp.publicKey;
1033
+ }
1034
+ else {
1035
+ throw new Error(`Solana private key must be a 32-byte seed or 64-byte expanded key; got ${rawKey.length} bytes.`);
1036
+ }
1000
1037
  const payload = {
1001
1038
  x402Version: 1,
1002
1039
  scheme: requirements.scheme ?? "exact",
1003
1040
  network: requirements.network ?? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
1004
1041
  payload: {
1005
1042
  signature: "",
1006
- from: keypair.publicKey.toBase58(),
1043
+ from: bs58Encode(publicKey),
1007
1044
  amount: requirements.maxAmountRequired,
1008
1045
  asset: requirements.asset,
1009
1046
  payTo: requirements.payTo,
@@ -1012,17 +1049,7 @@ async function completeX402Payment(paymentRequiredHeader, solanaPrivateKey) {
1012
1049
  };
1013
1050
  const message = JSON.stringify(payload.payload);
1014
1051
  const messageBytes = new TextEncoder().encode(message);
1015
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1016
- let tweetnacl;
1017
- try {
1018
- const pkg = "tweetnacl";
1019
- tweetnacl = await import(pkg);
1020
- }
1021
- catch {
1022
- throw new Error("x402 signing requires tweetnacl: npm install tweetnacl");
1023
- }
1024
- const naclSign = tweetnacl.default?.sign ?? tweetnacl.sign;
1025
- const signed = naclSign.detached(messageBytes, keypair.secretKey);
1052
+ const signed = naclSign.detached(messageBytes, secretKey);
1026
1053
  payload.payload.signature = Buffer.from(signed).toString("base64");
1027
1054
  return Buffer.from(JSON.stringify(payload)).toString("base64");
1028
1055
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mpp32-mcp-server",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
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",
@@ -69,6 +69,8 @@
69
69
  },
70
70
  "dependencies": {
71
71
  "@modelcontextprotocol/sdk": "^1.12.0",
72
+ "bs58": "^6.0.0",
73
+ "tweetnacl": "^1.0.3",
72
74
  "zod": "^3.23.0"
73
75
  },
74
76
  "peerDependencies": {