mpp32-mcp-server 1.1.2 → 1.1.4

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/index.js +118 -27
  3. package/package.json +3 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,63 @@ 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.4] - 2026-05-11
8
+
9
+ ### Added
10
+
11
+ * **`get_mpp32_diagnostics` MCP tool.** Reports server version, API URL,
12
+ and per-variable detection state for `MPP32_AGENT_KEY`,
13
+ `MPP32_SOLANA_PRIVATE_KEY`, and `MPP32_PRIVATE_KEY` — without ever
14
+ echoing the secret. The single most common payment failure has been
15
+ "I set the key but the MCP server doesn't see it" (wrong
16
+ `claude_desktop_config.json` file, `env` block at the wrong level in
17
+ the JSON, typo, or stale process from an incomplete restart). This
18
+ tool turns that into a one-call diagnosis.
19
+ * **Per-variable startup banner lines.** The stderr banner now prints
20
+ one `[mpp32] MPP32_X: SET (fingerprint) / NOT SET` line per managed
21
+ env var, so users who can find their MCP log file can see the same
22
+ diagnosis without calling a tool.
23
+
24
+ ## [1.1.3] - 2026-05-11
25
+
26
+ ### Fixed
27
+
28
+ * **x402 payments actually work now.** `completeX402Payment` previously
29
+ dynamically imported `@solana/web3.js`, `bs58`, and `tweetnacl`, but
30
+ none of those packages were declared in `dependencies`. On a clean
31
+ `npx mpp32-mcp-server` install they were missing from `node_modules`,
32
+ the import threw, the catch block returned a misleading "check wallet
33
+ balance" message, and Claude paraphrased that as "no funded Solana
34
+ wallet configured" even when `MPP32_SOLANA_PRIVATE_KEY` was set.
35
+ * **Dropped `@solana/web3.js` entirely.** Adding it to `dependencies`
36
+ uncovered a second, deeper bug: its transitive `rpc-websockets`
37
+ bundle is CJS and `require()`s a now ESM-only `uuid`, which throws
38
+ `ERR_REQUIRE_ESM` on Node 20+ the first time the signer loads. We
39
+ only ever used `Keypair.fromSecretKey` and `publicKey.toBase58()`,
40
+ both of which are trivial Ed25519/base58 operations. Signing is now
41
+ done directly with `tweetnacl` + `bs58`, so the failure mode is gone
42
+ and the install footprint is ~30 MB smaller.
43
+ * **Properly declared signing dependencies.** `bs58` and `tweetnacl`
44
+ are now real `dependencies` rather than implicit assumptions.
45
+ * **Better error when a payment dependency genuinely fails to load.**
46
+ The catch around each dynamic import now tells the user the package
47
+ ships with `mpp32-mcp-server` and directs them to upgrade to
48
+ `@latest` instead of suggesting a manual `npm install` that won't
49
+ stick across `npx` runs.
50
+ * **32-byte seed Solana keys now accepted** in addition to 64-byte
51
+ expanded keys (previously `Keypair.fromSecretKey` rejected seeds).
52
+
53
+ ### Added
54
+
55
+ * **Catalog total surfaced in `list_mpp32_services`.** The federated
56
+ catalog has 4,500+ external services but the API returns at most 500
57
+ per call (default 100). The response now includes
58
+ `data.totalAvailable` and a `truncated`/`hint` pair so the model
59
+ knows results were paginated and how to drill in with `q`,
60
+ `category`, `source`, or `protocol`. The MCP client formats the
61
+ header as `Found N services (of M total available in catalog)` and
62
+ prints the hint when applicable.
63
+
7
64
  ## [1.1.2] - 2026-05-10
8
65
 
9
66
  ### 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.4";
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
@@ -173,8 +173,65 @@ function isHttpCallable(svc) {
173
173
  return false;
174
174
  return /^https?:\/\//.test(url);
175
175
  }
176
+ // ── Tool 0: get_mpp32_diagnostics ───────────────────────────────────────────
177
+ // Lets the user (and Claude) see exactly what the MCP process detected at
178
+ // startup. The single most common failure mode is "I set the env var but it
179
+ // didn't reach the server" — wrong claude_desktop_config.json file edited,
180
+ // `env` block at the wrong level, typo in the variable name, stale process
181
+ // from an incomplete restart. This tool answers all of those without
182
+ // asking the user to dig through MCP log files.
183
+ function describeEnvVarStatus(name, value) {
184
+ const raw = process.env[name];
185
+ if (raw === undefined)
186
+ return `${name}: NOT SET (variable absent from MCP process env)`;
187
+ if (raw.length === 0)
188
+ return `${name}: EMPTY (set but blank)`;
189
+ if (value === undefined) {
190
+ return `${name}: REJECTED (raw length ${raw.length}, but failed validation — check startup log for reason)`;
191
+ }
192
+ // Show a short, non-secret fingerprint so the user can confirm it's the
193
+ // right value without us exfiltrating the key.
194
+ const fingerprint = value.length <= 12
195
+ ? `${value.length} chars`
196
+ : `${value.slice(0, 6)}…${value.slice(-4)} (${value.length} chars)`;
197
+ return `${name}: SET (${fingerprint})`;
198
+ }
199
+ server.tool("get_mpp32_diagnostics", "Report what the mpp32-mcp-server detected at startup: version, API URL, and which env vars (MPP32_AGENT_KEY, MPP32_SOLANA_PRIVATE_KEY, MPP32_PRIVATE_KEY) were loaded into this process. Use this FIRST if payments fail with 'no key configured' even though you set one in claude_desktop_config.json — it confirms whether your env vars actually reached the MCP process or got dropped by a typo / wrong file / stale restart.", {}, async () => {
200
+ const lines = [
201
+ `**mpp32-mcp-server diagnostics**`,
202
+ ``,
203
+ `Version: ${SERVER_VERSION}`,
204
+ `API URL: ${API_URL}`,
205
+ `Timeout: ${TIMEOUT_MS}ms`,
206
+ `Node: ${process.version} on ${process.platform}/${process.arch}`,
207
+ ``,
208
+ `**Environment variable detection** (values are fingerprinted, never returned in full):`,
209
+ ``,
210
+ describeEnvVarStatus("MPP32_AGENT_KEY", AGENT_KEY),
211
+ describeEnvVarStatus("MPP32_SOLANA_PRIVATE_KEY", SOLANA_PRIVATE_KEY),
212
+ describeEnvVarStatus("MPP32_PRIVATE_KEY", PRIVATE_KEY),
213
+ ``,
214
+ `**Capabilities:**`,
215
+ `- Catalog browsing: yes (always available)`,
216
+ `- Federated service execution: ${AGENT_KEY ? "yes" : "no — set MPP32_AGENT_KEY"}`,
217
+ `- x402 (USDC on Solana) payment: ${SOLANA_PRIVATE_KEY ? "yes" : "no — set MPP32_SOLANA_PRIVATE_KEY"}`,
218
+ `- Tempo (pathUSD on Eth L2) payment: ${PRIVATE_KEY ? "yes" : "no — set MPP32_PRIVATE_KEY"}`,
219
+ ``,
220
+ `If a variable shows NOT SET but you put it in claude_desktop_config.json:`,
221
+ `1. Confirm the file path Claude Desktop actually reads from:`,
222
+ ` - macOS: ~/Library/Application Support/Claude/claude_desktop_config.json`,
223
+ ` - Windows: %APPDATA%\\Claude\\claude_desktop_config.json`,
224
+ `2. The 'env' block must sit INSIDE the server entry, alongside 'command' and 'args' — not at the top level.`,
225
+ `3. Fully quit Claude Desktop (Cmd+Q on Mac, right-click tray → Quit on Windows). Closing the window is not enough.`,
226
+ `4. Re-open Claude Desktop. The new MCP process inherits the env from the JSON.`,
227
+ `5. Call get_mpp32_diagnostics again — if it still shows NOT SET, the JSON did not load (check for a syntax error).`,
228
+ ];
229
+ return {
230
+ content: [{ type: "text", text: lines.join("\n") }],
231
+ };
232
+ });
176
233
  // ── 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.", {
234
+ 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
235
  category: z
179
236
  .string()
180
237
  .optional()
@@ -251,17 +308,24 @@ server.tool("list_mpp32_services", "Browse the MPP32 federated catalog of machin
251
308
  .join("\n");
252
309
  });
253
310
  const counts = json.data.counts;
311
+ const totalAvailable = json.data.totalAvailable;
254
312
  const callableCount = services.filter(isHttpCallable).length;
313
+ const sourcesLine = totalAvailable
314
+ ? `**Sources:** ${counts.native} native + ${counts.external} external (of ${totalAvailable.combined} total available in catalog). **Callable through this MCP:** ${callableCount}.`
315
+ : `**Sources:** ${counts.native} native + ${counts.external} external. **Callable through this MCP:** ${callableCount}.`;
255
316
  const header = [
256
317
  `# MPP32 Federated Catalog — ${services.length} result${services.length !== 1 ? "s" : ""}`,
257
318
  ``,
258
- `**Sources:** ${counts.native} native + ${counts.external} external. **Callable through this MCP:** ${callableCount}.`,
319
+ sourcesLine,
320
+ json.data.truncated && json.data.hint ? `\n> ⚠️ ${json.data.hint}` : ``,
259
321
  ``,
260
322
  AGENT_KEY
261
323
  ? `Calls through \`call_mpp32_endpoint\` are tracked in your dashboard at ${API_URL}/agent-console (your X-Agent-Key is set).`
262
324
  : `**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
325
  ``,
264
- ].join("\n");
326
+ ]
327
+ .filter((l) => l !== ``)
328
+ .join("\n");
265
329
  return {
266
330
  content: [{ type: "text", text: header + "\n" + lines.join("\n\n") }],
267
331
  };
@@ -970,40 +1034,70 @@ async function completeX402Payment(paymentRequiredHeader, solanaPrivateKey) {
970
1034
  catch {
971
1035
  throw new Error("Could not decode Payment-Required header");
972
1036
  }
1037
+ // We sign x402 challenges with raw Ed25519 (Solana keys are Ed25519). No
1038
+ // @solana/web3.js needed — it pulls in rpc-websockets which has a
1039
+ // CJS/ESM uuid incompat on Node 20+ that breaks every paid call.
973
1040
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
974
- let solanaWeb3;
1041
+ let tweetnacl;
975
1042
  try {
976
- const pkg = "@solana/web3.js";
977
- solanaWeb3 = await import(pkg);
1043
+ const pkg = "tweetnacl";
1044
+ tweetnacl = await import(pkg);
978
1045
  }
979
- catch {
980
- throw new Error("x402 payment requires @solana/web3.js: npm install @solana/web3.js");
1046
+ catch (err) {
1047
+ throw new Error(`x402 signing requires tweetnacl, which ships with mpp32-mcp-server. ` +
1048
+ `If you're seeing this on a clean npx install, upgrade to mpp32-mcp-server@latest. ` +
1049
+ `Underlying error: ${err instanceof Error ? err.message : String(err)}`);
981
1050
  }
982
1051
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
983
- let keypair;
1052
+ let bs58;
1053
+ try {
1054
+ const pkg = "bs58";
1055
+ bs58 = await import(pkg);
1056
+ }
1057
+ catch (err) {
1058
+ throw new Error(`x402 signing requires bs58, which ships with mpp32-mcp-server. ` +
1059
+ `Upgrade to mpp32-mcp-server@latest. ` +
1060
+ `Underlying error: ${err instanceof Error ? err.message : String(err)}`);
1061
+ }
1062
+ const bs58Decode = bs58.default?.decode ?? bs58.decode;
1063
+ const bs58Encode = bs58.default?.encode ?? bs58.encode;
1064
+ const naclSign = tweetnacl.default?.sign ?? tweetnacl.sign;
1065
+ let rawKey;
984
1066
  try {
985
1067
  if (solanaPrivateKey.startsWith("[")) {
986
- keypair = solanaWeb3.Keypair.fromSecretKey(new Uint8Array(JSON.parse(solanaPrivateKey)));
1068
+ rawKey = new Uint8Array(JSON.parse(solanaPrivateKey));
987
1069
  }
988
1070
  else if (/^[0-9a-fA-F]+$/.test(solanaPrivateKey) && solanaPrivateKey.length % 2 === 0) {
989
- keypair = solanaWeb3.Keypair.fromSecretKey(new Uint8Array(Buffer.from(solanaPrivateKey, "hex")));
1071
+ rawKey = new Uint8Array(Buffer.from(solanaPrivateKey, "hex"));
990
1072
  }
991
1073
  else {
992
- const bs58Pkg = "bs58";
993
- const bs58 = await import(bs58Pkg);
994
- keypair = solanaWeb3.Keypair.fromSecretKey(bs58.default.decode(solanaPrivateKey));
1074
+ rawKey = bs58Decode(solanaPrivateKey);
995
1075
  }
996
1076
  }
997
1077
  catch (err) {
998
1078
  throw new Error(`Could not decode Solana private key: ${err instanceof Error ? err.message : String(err)}`);
999
1079
  }
1080
+ let secretKey;
1081
+ let publicKey;
1082
+ if (rawKey.length === 64) {
1083
+ secretKey = rawKey;
1084
+ publicKey = rawKey.slice(32);
1085
+ }
1086
+ else if (rawKey.length === 32) {
1087
+ const kp = naclSign.keyPair.fromSeed(rawKey);
1088
+ secretKey = kp.secretKey;
1089
+ publicKey = kp.publicKey;
1090
+ }
1091
+ else {
1092
+ throw new Error(`Solana private key must be a 32-byte seed or 64-byte expanded key; got ${rawKey.length} bytes.`);
1093
+ }
1000
1094
  const payload = {
1001
1095
  x402Version: 1,
1002
1096
  scheme: requirements.scheme ?? "exact",
1003
1097
  network: requirements.network ?? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
1004
1098
  payload: {
1005
1099
  signature: "",
1006
- from: keypair.publicKey.toBase58(),
1100
+ from: bs58Encode(publicKey),
1007
1101
  amount: requirements.maxAmountRequired,
1008
1102
  asset: requirements.asset,
1009
1103
  payTo: requirements.payTo,
@@ -1012,17 +1106,7 @@ async function completeX402Payment(paymentRequiredHeader, solanaPrivateKey) {
1012
1106
  };
1013
1107
  const message = JSON.stringify(payload.payload);
1014
1108
  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);
1109
+ const signed = naclSign.detached(messageBytes, secretKey);
1026
1110
  payload.payload.signature = Buffer.from(signed).toString("base64");
1027
1111
  return Buffer.from(JSON.stringify(payload)).toString("base64");
1028
1112
  }
@@ -1038,6 +1122,13 @@ async function main() {
1038
1122
  .filter(Boolean)
1039
1123
  .join(", ") || "no keys (catalog-only legacy mode)";
1040
1124
  console.error(`[mpp32] MCP server v${SERVER_VERSION} on stdio. API ${API_URL}. Configured: ${features}. Timeout ${TIMEOUT_MS}ms.`);
1125
+ // Per-variable status so a user staring at this log can immediately see
1126
+ // whether their env vars made it through. Values are fingerprinted.
1127
+ const fp = (v) => !v ? "NOT SET" : v.length <= 12 ? `SET (${v.length}c)` : `SET (${v.slice(0, 6)}…${v.slice(-4)}, ${v.length}c)`;
1128
+ console.error(`[mpp32] MPP32_AGENT_KEY: ${fp(AGENT_KEY)}`);
1129
+ console.error(`[mpp32] MPP32_SOLANA_PRIVATE_KEY: ${fp(SOLANA_PRIVATE_KEY)}`);
1130
+ console.error(`[mpp32] MPP32_PRIVATE_KEY: ${fp(PRIVATE_KEY)}`);
1131
+ console.error(`[mpp32] If a key shows NOT SET but you set it in claude_desktop_config.json, call the get_mpp32_diagnostics tool for help, or fully quit Claude Desktop and reopen.`);
1041
1132
  }
1042
1133
  main().catch((err) => {
1043
1134
  console.error("Fatal:", err);
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.4",
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": {