rwagenthub-mcp 1.0.8 → 1.0.9

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 (2) hide show
  1. package/index.js +231 -131
  2. package/package.json +5 -5
package/index.js CHANGED
@@ -1,131 +1,231 @@
1
- #!/usr/bin/env node
2
- import "dotenv/config";
3
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
- import { createWalletClient, http } from "viem";
6
- import { privateKeyToAccount } from "viem/accounts";
7
- import { base } from "viem/chains";
8
- import { wrapFetchWithPayment } from "@x402/fetch";
9
- import { x402Client } from "@x402/core/client";
10
- import { registerExactEvmScheme } from "@x402/evm/exact/client";
11
- import { z } from "zod";
12
-
13
- const GATEWAY_URL = "https://agents-production-73c1.up.railway.app";
14
-
15
- // ── Payment setup ──────────────────────────────────────────────────────────────
16
- if (!process.env.MCP_WALLET_PRIVATE_KEY) {
17
- console.error("[mcp] ERROR: MCP_WALLET_PRIVATE_KEY not set");
18
- console.error("[mcp] Add it to your MCP client config under env.MCP_WALLET_PRIVATE_KEY");
19
- process.exit(1);
20
- }
21
-
22
- const account = privateKeyToAccount(process.env.MCP_WALLET_PRIVATE_KEY);
23
- const walletClient = createWalletClient({ account, chain: base, transport: http() });
24
- const signer = Object.assign(walletClient, { address: account.address });
25
-
26
- const client = new x402Client();
27
- registerExactEvmScheme(client, { signer });
28
- const pay = wrapFetchWithPayment(fetch, client);
29
-
30
- async function callGateway(api, inputs) {
31
- const res = await pay(`${GATEWAY_URL}/v1/call`, {
32
- method: "POST",
33
- headers: { "Content-Type": "application/json" },
34
- body: JSON.stringify({ api, inputs }),
35
- });
36
- if (res.status === 402) {
37
- const body = await res.json().catch(() => ({}));
38
- const reason = body?.details ?? body?.error?.invalidReason ?? body?.error ?? "payment_failed";
39
- throw new Error(`payment_failed:${reason}`);
40
- }
41
- return res.json();
42
- }
43
-
44
- // ── Type mapper: JSON Schema property → Zod ───────────────────────────────────
45
- function toZod(propName, prop, requiredList) {
46
- let schema;
47
- if (prop.type === "number") {
48
- schema = z.number();
49
- } else if (prop.type === "boolean") {
50
- schema = z.boolean();
51
- } else if (prop.oneOf) {
52
- schema = z.union([z.string(), z.array(z.string())]);
53
- } else {
54
- schema = z.string();
55
- }
56
-
57
- if (prop.description) schema = schema.describe(prop.description);
58
- const isRequired = requiredList?.includes(propName);
59
- if (!isRequired) schema = schema.optional();
60
- if (prop.default !== undefined) schema = schema.default(prop.default);
61
- return schema;
62
- }
63
-
64
- // ── Main: fetch schema from gateway, register tools, start server ──────────────
65
- async function main() {
66
- // Fetch live schema + prices from the gateway
67
- const res = await fetch(`${GATEWAY_URL}/v1/schema`);
68
- if (!res.ok) {
69
- console.error(`[mcp] Failed to fetch schema from gateway: ${res.status} ${res.statusText}`);
70
- process.exit(1);
71
- }
72
- const { apis } = await res.json();
73
-
74
- const server = new McpServer({ name: "agenthub", version: "1.0.0" });
75
-
76
- for (const api of apis) {
77
- const { properties = {}, required: requiredList = [] } = api.inputSchema ?? {};
78
- const zodShape = {};
79
- for (const [fieldName, prop] of Object.entries(properties)) {
80
- zodShape[fieldName] = toZod(fieldName, prop, requiredList);
81
- }
82
-
83
- server.tool(
84
- api.name,
85
- `${api.description} Costs $${(api.price_usd ?? 0.01).toFixed(2)} USDC per call via x402 on Base.`,
86
- zodShape,
87
- async (inputs) => {
88
- try {
89
- const result = await callGateway(api.name, inputs);
90
-
91
- if (!result.success) {
92
- return {
93
- content: [{ type: "text", text: `Error: ${result.error}${result.message ? ` — ${result.message}` : ""}${result.hint ? `\n${result.hint}` : ""}` }],
94
- isError: true,
95
- };
96
- }
97
-
98
- return {
99
- content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
100
- };
101
- } catch (err) {
102
- const msg = err.message ?? "";
103
- const isPaymentError =
104
- msg.startsWith("payment_failed") ||
105
- msg.toLowerCase().includes("insufficient funds") ||
106
- msg.toLowerCase().includes("insufficient_funds") ||
107
- msg.toLowerCase().includes("failed to create payment payload");
108
- if (isPaymentError) {
109
- return {
110
- content: [{ type: "text", text: `Payment failed: insufficient USDC balance on Base.\nWallet: ${account.address}\n\nTop up your wallet with USDC on Base and try again.` }],
111
- isError: true,
112
- };
113
- }
114
- return {
115
- content: [{ type: "text", text: `Gateway error: ${err.message}` }],
116
- isError: true,
117
- };
118
- }
119
- }
120
- );
121
- }
122
-
123
- const transport = new StdioServerTransport();
124
- await server.connect(transport);
125
- console.error(`[mcp] AgentHub MCP server running — ${apis.length} tools available`);
126
- }
127
-
128
- main().catch((err) => {
129
- console.error("[mcp] Fatal:", err);
130
- process.exit(1);
131
- });
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { createWalletClient, http } from "viem";
6
+ import { privateKeyToAccount } from "viem/accounts";
7
+ import { base } from "viem/chains";
8
+ import { wrapFetchWithPayment } from "@x402/fetch";
9
+ import { x402Client } from "@x402/core/client";
10
+ import { registerExactEvmScheme } from "@x402/evm/exact/client";
11
+ import { z } from "zod";
12
+
13
+ const GATEWAY_URL = "https://agents-production-73c1.up.railway.app";
14
+
15
+ // ── Payment setup ──────────────────────────────────────────────────────────────
16
+ if (!process.env.MCP_WALLET_PRIVATE_KEY) {
17
+ console.error("[mcp] ERROR: MCP_WALLET_PRIVATE_KEY not set");
18
+ console.error("[mcp] Add it to your MCP client config under env.MCP_WALLET_PRIVATE_KEY");
19
+ process.exit(1);
20
+ }
21
+
22
+ const account = privateKeyToAccount(process.env.MCP_WALLET_PRIVATE_KEY);
23
+ const walletClient = createWalletClient({ account, chain: base, transport: http() });
24
+ const signer = Object.assign(walletClient, { address: account.address });
25
+
26
+ const client = new x402Client();
27
+ registerExactEvmScheme(client, { signer });
28
+ const pay = wrapFetchWithPayment(fetch, client);
29
+
30
+ // ── Balance mode (optional) ────────────────────────────────────────────────────
31
+ // Set MCP_BALANCE_TOKEN to use prepaid balance instead of paying gas per call.
32
+ // Top up via the `topup_balance` tool registered below.
33
+ let balanceToken = process.env.MCP_BALANCE_TOKEN ?? null;
34
+
35
+ async function callGateway(api, inputs) {
36
+ // Balance mode: use token, skip x402
37
+ if (balanceToken) {
38
+ const res = await fetch(`${GATEWAY_URL}/v1/call`, {
39
+ method: "POST",
40
+ headers: {
41
+ "Content-Type": "application/json",
42
+ "Authorization": `Bearer ${balanceToken}`,
43
+ },
44
+ body: JSON.stringify({ api, inputs }),
45
+ });
46
+
47
+ if (res.status === 402) {
48
+ const body = await res.json().catch(() => ({}));
49
+ // insufficient_balance fall through to x402 pay-per-call
50
+ if (body?.error === "insufficient_balance") {
51
+ console.error(`[mcp] Balance insufficient ($${body.balance_usd} < $${body.required_usd}). Falling back to x402.`);
52
+ return callGatewayX402(api, inputs);
53
+ }
54
+ throw new Error(`token_error:${body?.error ?? "unknown"}`);
55
+ }
56
+ return res.json();
57
+ }
58
+
59
+ return callGatewayX402(api, inputs);
60
+ }
61
+
62
+ async function callGatewayX402(api, inputs) {
63
+ const res = await pay(`${GATEWAY_URL}/v1/call`, {
64
+ method: "POST",
65
+ headers: { "Content-Type": "application/json" },
66
+ body: JSON.stringify({ api, inputs }),
67
+ });
68
+ if (res.status === 402) {
69
+ const body = await res.json().catch(() => ({}));
70
+ const reason = body?.details ?? body?.error?.invalidReason ?? body?.error ?? "payment_failed";
71
+ throw new Error(`payment_failed:${reason}`);
72
+ }
73
+ return res.json();
74
+ }
75
+
76
+ async function topupGateway(amount_usd) {
77
+ const res = await pay(`${GATEWAY_URL}/v1/topup`, {
78
+ method: "POST",
79
+ headers: { "Content-Type": "application/json" },
80
+ body: JSON.stringify({ amount_usd, token: balanceToken ?? undefined }),
81
+ });
82
+ if (res.status === 402) {
83
+ const body = await res.json().catch(() => ({}));
84
+ const reason = body?.details ?? body?.error ?? "payment_failed";
85
+ throw new Error(`payment_failed:${reason}`);
86
+ }
87
+ return res.json();
88
+ }
89
+
90
+ async function getBalance() {
91
+ if (!balanceToken) return { error: "no_token", message: "No balance token set. Use topup_balance first." };
92
+ const res = await fetch(`${GATEWAY_URL}/v1/user/balance`, {
93
+ headers: { "Authorization": `Bearer ${balanceToken}` },
94
+ });
95
+ return res.json();
96
+ }
97
+
98
+ // ── Type mapper: JSON Schema property → Zod ───────────────────────────────────
99
+ function toZod(propName, prop, requiredList) {
100
+ let schema;
101
+ if (prop.type === "number") {
102
+ schema = z.number();
103
+ } else if (prop.type === "boolean") {
104
+ schema = z.boolean();
105
+ } else if (prop.oneOf) {
106
+ schema = z.union([z.string(), z.array(z.string())]);
107
+ } else {
108
+ schema = z.string();
109
+ }
110
+
111
+ if (prop.description) schema = schema.describe(prop.description);
112
+ const isRequired = requiredList?.includes(propName);
113
+ if (!isRequired) schema = schema.optional();
114
+ if (prop.default !== undefined) schema = schema.default(prop.default);
115
+ return schema;
116
+ }
117
+
118
+ // ── Main: fetch schema from gateway, register tools, start server ──────────────
119
+ async function main() {
120
+ // Fetch live schema + prices from the gateway
121
+ const res = await fetch(`${GATEWAY_URL}/v1/schema`);
122
+ if (!res.ok) {
123
+ console.error(`[mcp] Failed to fetch schema from gateway: ${res.status} ${res.statusText}`);
124
+ process.exit(1);
125
+ }
126
+ const { apis } = await res.json();
127
+
128
+ const server = new McpServer({ name: "agenthub", version: "1.0.0" });
129
+
130
+ // ── Balance management tools ───────────────────────────────────────────────
131
+ server.tool(
132
+ "topup_balance",
133
+ `Top up your AgentHub prepaid balance with USDC via x402 (one on-chain payment). Valid amounts: 1, 2, 5, 10, 20, 50 USD. Returns a token for gas-free subsequent calls. If you already have a token (MCP_BALANCE_TOKEN), it will be recharged instead of creating a new one.`,
134
+ { amount_usd: z.number().describe("Amount in USD to top up. Must be one of: 1, 2, 5, 10, 20, 50") },
135
+ async ({ amount_usd }) => {
136
+ try {
137
+ const result = await topupGateway(amount_usd);
138
+ if (!result.success) {
139
+ return { content: [{ type: "text", text: `Topup failed: ${result.error ?? "unknown"}` }], isError: true };
140
+ }
141
+ // Store token for this session
142
+ balanceToken = result.token;
143
+ return {
144
+ content: [{ type: "text", text: `Balance topped up successfully!\n\nToken: ${result.token}\nBalance: $${result.balance_usd} USDC\nWallet: ${result.wallet}\n\nSave your token:\n MCP_BALANCE_TOKEN=${result.token}\n\nAll subsequent API calls in this session will use the prepaid balance (no gas per call).` }],
145
+ };
146
+ } catch (err) {
147
+ const msg = err.message ?? "";
148
+ const isPaymentError = msg.startsWith("payment_failed") || msg.toLowerCase().includes("insufficient funds");
149
+ if (isPaymentError) {
150
+ return {
151
+ content: [{ type: "text", text: `Payment failed: insufficient USDC on Base.\nWallet: ${account.address}\n\nTop up your wallet with USDC on Base and try again.` }],
152
+ isError: true,
153
+ };
154
+ }
155
+ return { content: [{ type: "text", text: `Topup error: ${err.message}` }], isError: true };
156
+ }
157
+ }
158
+ );
159
+
160
+ server.tool(
161
+ "get_balance",
162
+ "Check your current AgentHub prepaid balance. Requires MCP_BALANCE_TOKEN env var or a previous topup_balance call.",
163
+ {},
164
+ async () => {
165
+ const result = await getBalance();
166
+ if (result.error) {
167
+ return { content: [{ type: "text", text: `${result.error}: ${result.message ?? ""}` }], isError: true };
168
+ }
169
+ return {
170
+ content: [{ type: "text", text: `AgentHub Balance\n\nToken: ${result.token}\nWallet: ${result.wallet}\nAvailable: $${result.balance_usd} USDC\nTotal topped up: $${result.topup_total} USDC\nTotal spent: $${result.spend_total} USDC` }],
171
+ };
172
+ }
173
+ );
174
+
175
+ // ── API tools (dynamic from gateway schema) ────────────────────────────────
176
+ for (const api of apis) {
177
+ const { properties = {}, required: requiredList = [] } = api.inputSchema ?? {};
178
+ const zodShape = {};
179
+ for (const [fieldName, prop] of Object.entries(properties)) {
180
+ zodShape[fieldName] = toZod(fieldName, prop, requiredList);
181
+ }
182
+
183
+ server.tool(
184
+ api.name,
185
+ `${api.description} Costs $${(api.price_usd ?? 0.01).toFixed(2)} USDC per call via x402 on Base.`,
186
+ zodShape,
187
+ async (inputs) => {
188
+ try {
189
+ const result = await callGateway(api.name, inputs);
190
+
191
+ if (!result.success) {
192
+ return {
193
+ content: [{ type: "text", text: `Error: ${result.error}${result.message ? ` — ${result.message}` : ""}${result.hint ? `\n${result.hint}` : ""}` }],
194
+ isError: true,
195
+ };
196
+ }
197
+
198
+ return {
199
+ content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
200
+ };
201
+ } catch (err) {
202
+ const msg = err.message ?? "";
203
+ const isPaymentError =
204
+ msg.startsWith("payment_failed") ||
205
+ msg.toLowerCase().includes("insufficient funds") ||
206
+ msg.toLowerCase().includes("insufficient_funds") ||
207
+ msg.toLowerCase().includes("failed to create payment payload");
208
+ if (isPaymentError) {
209
+ return {
210
+ content: [{ type: "text", text: `Payment failed: insufficient USDC balance on Base.\nWallet: ${account.address}\n\nTop up your wallet with USDC on Base and try again.` }],
211
+ isError: true,
212
+ };
213
+ }
214
+ return {
215
+ content: [{ type: "text", text: `Gateway error: ${err.message}` }],
216
+ isError: true,
217
+ };
218
+ }
219
+ }
220
+ );
221
+ }
222
+
223
+ const transport = new StdioServerTransport();
224
+ await server.connect(transport);
225
+ console.error(`[mcp] AgentHub MCP server running — ${apis.length} tools available`);
226
+ }
227
+
228
+ main().catch((err) => {
229
+ console.error("[mcp] Fatal:", err);
230
+ process.exit(1);
231
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rwagenthub-mcp",
3
- "version": "1.0.8",
4
- "description": "MCP server for AgentHub — 34 AI APIs (flights, weather, crypto, stocks, DeFi, web scraping, geocoding, IP reputation, blockchain data...) paid via x402 USDC on Base",
3
+ "version": "1.0.9",
4
+ "description": "MCP server for AgentHub — 32 AI APIs (flights, weather, crypto, stocks, DeFi, web scraping, geocoding, IP reputation, blockchain data...) paid via x402 USDC on Base",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "rwagenthub-mcp": "./index.js"
@@ -35,9 +35,9 @@
35
35
  "license": "MIT",
36
36
  "dependencies": {
37
37
  "@modelcontextprotocol/sdk": "^1.27.1",
38
- "@x402/core": "^2.3.1",
39
- "@x402/evm": "^2.3.1",
40
- "@x402/fetch": "^2.3.0",
38
+ "@x402/core": "^2.6.0",
39
+ "@x402/evm": "^2.6.0",
40
+ "@x402/fetch": "^2.6.0",
41
41
  "dotenv": "^16.4.0",
42
42
  "viem": "^2.46.2",
43
43
  "zod": "^3.25.76"