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.
- package/CHANGELOG.md +57 -0
- package/dist/index.js +118 -27
- 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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
]
|
|
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
|
|
1041
|
+
let tweetnacl;
|
|
975
1042
|
try {
|
|
976
|
-
const pkg = "
|
|
977
|
-
|
|
1043
|
+
const pkg = "tweetnacl";
|
|
1044
|
+
tweetnacl = await import(pkg);
|
|
978
1045
|
}
|
|
979
|
-
catch {
|
|
980
|
-
throw new Error(
|
|
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
|
|
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
|
-
|
|
1068
|
+
rawKey = new Uint8Array(JSON.parse(solanaPrivateKey));
|
|
987
1069
|
}
|
|
988
1070
|
else if (/^[0-9a-fA-F]+$/.test(solanaPrivateKey) && solanaPrivateKey.length % 2 === 0) {
|
|
989
|
-
|
|
1071
|
+
rawKey = new Uint8Array(Buffer.from(solanaPrivateKey, "hex"));
|
|
990
1072
|
}
|
|
991
1073
|
else {
|
|
992
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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": {
|