@rackspay/wallet-mcp 1.0.0
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/build/index.js +451 -0
- package/package.json +39 -0
package/build/index.js
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* RACKS Wallet MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Crypto-native trading and betting infrastructure for AI agents.
|
|
6
|
+
* Enables Claude Desktop (and any MCP client) to:
|
|
7
|
+
* - Check on-chain USDC balances across Arbitrum, Polygon, Solana, HyperCore
|
|
8
|
+
* - Place perp/spot orders on Hyperliquid with builder code revenue
|
|
9
|
+
* - Place prediction market bets on Polymarket
|
|
10
|
+
* - Monitor open positions, orders, and PnL
|
|
11
|
+
*
|
|
12
|
+
* Usage (Claude Desktop):
|
|
13
|
+
* {
|
|
14
|
+
* "mcpServers": {
|
|
15
|
+
* "racks-wallet": {
|
|
16
|
+
* "command": "npx",
|
|
17
|
+
* "args": ["@rackswallet/mcp", "--api-key", "rk_live_..."]
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* @see https://wallet.rackspay.com
|
|
23
|
+
* @see https://modelcontextprotocol.io
|
|
24
|
+
*/
|
|
25
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
26
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// CONFIGURATION
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Parse --api-key from CLI args (preferred) or fall back to env var
|
|
32
|
+
const argv = process.argv.slice(2);
|
|
33
|
+
const apiKeyFlagIdx = argv.indexOf("--api-key");
|
|
34
|
+
const apiKeyFromFlag = apiKeyFlagIdx !== -1 ? argv[apiKeyFlagIdx + 1] : undefined;
|
|
35
|
+
const CONFIG = {
|
|
36
|
+
apiUrl: process.env.RACKS_API_URL ?? "https://api.wallet.rackspay.com",
|
|
37
|
+
apiKey: apiKeyFromFlag ?? process.env.RACKS_API_KEY ?? "",
|
|
38
|
+
timeout: 15_000,
|
|
39
|
+
};
|
|
40
|
+
if (!CONFIG.apiKey) {
|
|
41
|
+
console.error("ERROR: RACKS API key is required.\n\n" +
|
|
42
|
+
"Provide it via the --api-key flag:\n" +
|
|
43
|
+
' npx @rackswallet/mcp --api-key "rk_live_..."\n\n' +
|
|
44
|
+
"Or via the RACKS_API_KEY environment variable.\n\n" +
|
|
45
|
+
"Get your agent API key at https://wallet.rackspay.com/agents");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
async function request(method, path, body) {
|
|
49
|
+
const url = `${CONFIG.apiUrl}${path}`;
|
|
50
|
+
try {
|
|
51
|
+
const res = await fetch(url, {
|
|
52
|
+
method,
|
|
53
|
+
headers: {
|
|
54
|
+
"X-Agent-API-Key": CONFIG.apiKey,
|
|
55
|
+
"Content-Type": "application/json",
|
|
56
|
+
},
|
|
57
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
58
|
+
signal: AbortSignal.timeout(CONFIG.timeout),
|
|
59
|
+
});
|
|
60
|
+
const data = (await res.json().catch(() => ({})));
|
|
61
|
+
return { ok: res.ok, status: res.status, data };
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
65
|
+
return { ok: false, status: 0, data: { error: "Request timed out" } };
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
status: 0,
|
|
70
|
+
data: { error: err instanceof Error ? err.message : "Network error" },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// ── Shared error response helper ──────────────────────────────────────────────
|
|
75
|
+
function errorResult(message, code) {
|
|
76
|
+
return {
|
|
77
|
+
content: [
|
|
78
|
+
{
|
|
79
|
+
type: "text",
|
|
80
|
+
text: JSON.stringify({ success: false, error: message, errorCode: code }, null, 2),
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
isError: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function apiError(res, fallback) {
|
|
87
|
+
const msg = typeof res.data?.detail === "string"
|
|
88
|
+
? res.data.detail
|
|
89
|
+
: fallback;
|
|
90
|
+
const codes = {
|
|
91
|
+
401: "UNAUTHORIZED",
|
|
92
|
+
403: "FORBIDDEN",
|
|
93
|
+
404: "NOT_FOUND",
|
|
94
|
+
422: "VALIDATION_ERROR",
|
|
95
|
+
502: "UPSTREAM_ERROR",
|
|
96
|
+
};
|
|
97
|
+
return errorResult(msg, codes[res.status] ?? "API_ERROR");
|
|
98
|
+
}
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// MCP SERVER
|
|
101
|
+
// ============================================================================
|
|
102
|
+
const server = new McpServer({
|
|
103
|
+
name: "racks-wallet",
|
|
104
|
+
version: "1.0.0",
|
|
105
|
+
});
|
|
106
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
107
|
+
// Tool: racks_get_status
|
|
108
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
109
|
+
server.tool("racks_get_status", "Check this agent's current status: name, wallet addresses, USDC balances across all chains, " +
|
|
110
|
+
"enabled capabilities (trading, betting, card issuance), and spending limits. " +
|
|
111
|
+
"Call this first to understand what this agent can do.", {}, async () => {
|
|
112
|
+
const res = await request("GET", "/api/v1/agent/status");
|
|
113
|
+
if (!res.ok)
|
|
114
|
+
return apiError(res, "Failed to fetch agent status");
|
|
115
|
+
const d = res.data;
|
|
116
|
+
return {
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: "text",
|
|
120
|
+
text: JSON.stringify({
|
|
121
|
+
success: true,
|
|
122
|
+
agent: {
|
|
123
|
+
name: d.name,
|
|
124
|
+
type: d.type,
|
|
125
|
+
status: d.status,
|
|
126
|
+
wallet_address: d.wallet_address,
|
|
127
|
+
wallet_status: d.wallet_status,
|
|
128
|
+
balance: d.balance,
|
|
129
|
+
capabilities: d.capabilities,
|
|
130
|
+
monthly_spend_limit: d.monthly_spend_limit,
|
|
131
|
+
daily_spend_limit: d.daily_spend_limit,
|
|
132
|
+
},
|
|
133
|
+
}, null, 2),
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
139
|
+
// Tool: racks_get_wallet_balance
|
|
140
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
141
|
+
server.tool("racks_get_wallet_balance", "Return this agent's USDC balance on every chain: Arbitrum (primary), Polygon (Polymarket), " +
|
|
142
|
+
"Solana, and HyperCore (funds deposited into Hyperliquid). " +
|
|
143
|
+
"Use this before trading or betting to verify sufficient funds.", {}, async () => {
|
|
144
|
+
const res = await request("GET", "/api/v1/agent/wallet/balance");
|
|
145
|
+
if (!res.ok)
|
|
146
|
+
return apiError(res, "Failed to fetch wallet balance");
|
|
147
|
+
const d = res.data;
|
|
148
|
+
return {
|
|
149
|
+
content: [
|
|
150
|
+
{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: JSON.stringify({
|
|
153
|
+
success: true,
|
|
154
|
+
balance: {
|
|
155
|
+
arbitrum_usdc: d.arbitrum_usdc,
|
|
156
|
+
polygon_usdc: d.polygon_usdc,
|
|
157
|
+
hypercore_usdc: d.hypercore_usdc,
|
|
158
|
+
total_usdc: d.total_usdc,
|
|
159
|
+
},
|
|
160
|
+
note: "HyperCore balance is USDC already deposited into Hyperliquid for trading.",
|
|
161
|
+
}, null, 2),
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
167
|
+
// Tool: racks_get_trade_account
|
|
168
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
169
|
+
server.tool("racks_get_trade_account", "Fetch this agent's full Hyperliquid HyperCore account state: account value, margin, " +
|
|
170
|
+
"withdrawable balance, all open positions (with unrealized PnL), and all open orders. " +
|
|
171
|
+
"Requires trading to be enabled on this agent.", {}, async () => {
|
|
172
|
+
const res = await request("GET", "/api/v1/agent/trade/account");
|
|
173
|
+
if (!res.ok)
|
|
174
|
+
return apiError(res, "Failed to fetch Hyperliquid account");
|
|
175
|
+
return {
|
|
176
|
+
content: [
|
|
177
|
+
{
|
|
178
|
+
type: "text",
|
|
179
|
+
text: JSON.stringify({ success: true, account: res.data }, null, 2),
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
185
|
+
// Tool: racks_get_positions
|
|
186
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
187
|
+
server.tool("racks_get_positions", "List all open perpetual positions on Hyperliquid for this agent. " +
|
|
188
|
+
"Each position shows coin, side (long/short), size, entry price, mark price, " +
|
|
189
|
+
"liquidation price, unrealized PnL, and leverage. " +
|
|
190
|
+
"Requires trading to be enabled.", {}, async () => {
|
|
191
|
+
const res = await request("GET", "/api/v1/agent/trade/positions");
|
|
192
|
+
if (!res.ok)
|
|
193
|
+
return apiError(res, "Failed to fetch positions");
|
|
194
|
+
const positions = Array.isArray(res.data) ? res.data : [];
|
|
195
|
+
return {
|
|
196
|
+
content: [
|
|
197
|
+
{
|
|
198
|
+
type: "text",
|
|
199
|
+
text: JSON.stringify({
|
|
200
|
+
success: true,
|
|
201
|
+
position_count: positions.length,
|
|
202
|
+
positions,
|
|
203
|
+
}, null, 2),
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
209
|
+
// Tool: racks_place_order
|
|
210
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
211
|
+
server.tool("racks_place_order", "Place a perpetual futures order on Hyperliquid. The platform's builder code is attached " +
|
|
212
|
+
"automatically. Supports limit and market orders. " +
|
|
213
|
+
"IMPORTANT: always call racks_get_wallet_balance first to verify HyperCore balance. " +
|
|
214
|
+
"Requires trading to be enabled on this agent.", {
|
|
215
|
+
coin: z
|
|
216
|
+
.string()
|
|
217
|
+
.describe('Trading pair coin symbol, e.g. "BTC", "ETH", "SOL"'),
|
|
218
|
+
side: z
|
|
219
|
+
.enum(["buy", "sell"])
|
|
220
|
+
.describe('"buy" opens or adds to a long; "sell" opens or adds to a short'),
|
|
221
|
+
size: z
|
|
222
|
+
.number()
|
|
223
|
+
.positive()
|
|
224
|
+
.describe("Position size in units of the coin (e.g. 0.01 for 0.01 BTC)"),
|
|
225
|
+
price: z
|
|
226
|
+
.number()
|
|
227
|
+
.positive()
|
|
228
|
+
.optional()
|
|
229
|
+
.describe("Limit price in USD. Omit for a market order"),
|
|
230
|
+
order_type: z
|
|
231
|
+
.enum(["limit", "market"])
|
|
232
|
+
.optional()
|
|
233
|
+
.default("limit")
|
|
234
|
+
.describe('Order type. Defaults to "limit"'),
|
|
235
|
+
time_in_force: z
|
|
236
|
+
.enum(["Gtc", "Ioc", "Alo"])
|
|
237
|
+
.optional()
|
|
238
|
+
.default("Gtc")
|
|
239
|
+
.describe('Time in force: "Gtc" (good till cancel), "Ioc" (immediate or cancel), "Alo" (add liquidity only)'),
|
|
240
|
+
reduce_only: z
|
|
241
|
+
.boolean()
|
|
242
|
+
.optional()
|
|
243
|
+
.default(false)
|
|
244
|
+
.describe("If true, the order can only reduce an existing position"),
|
|
245
|
+
intent: z
|
|
246
|
+
.string()
|
|
247
|
+
.optional()
|
|
248
|
+
.describe('Human-readable reason for this trade, e.g. "Going long ETH based on bullish breakout"'),
|
|
249
|
+
}, async ({ coin, side, size, price, order_type, time_in_force, reduce_only, intent }) => {
|
|
250
|
+
const res = await request("POST", "/api/v1/agent/trade/order", {
|
|
251
|
+
coin,
|
|
252
|
+
side,
|
|
253
|
+
size,
|
|
254
|
+
price,
|
|
255
|
+
order_type,
|
|
256
|
+
time_in_force,
|
|
257
|
+
reduce_only,
|
|
258
|
+
intent,
|
|
259
|
+
});
|
|
260
|
+
if (!res.ok)
|
|
261
|
+
return apiError(res, "Failed to place order");
|
|
262
|
+
const d = res.data;
|
|
263
|
+
return {
|
|
264
|
+
content: [
|
|
265
|
+
{
|
|
266
|
+
type: "text",
|
|
267
|
+
text: JSON.stringify({
|
|
268
|
+
success: true,
|
|
269
|
+
status: d.status,
|
|
270
|
+
order: {
|
|
271
|
+
coin,
|
|
272
|
+
side,
|
|
273
|
+
size,
|
|
274
|
+
price: price ?? "market",
|
|
275
|
+
order_type,
|
|
276
|
+
},
|
|
277
|
+
hl_response: d.hl_response,
|
|
278
|
+
note: "Builder code attached. Monitor position via racks_get_positions.",
|
|
279
|
+
}, null, 2),
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
285
|
+
// Tool: racks_cancel_order
|
|
286
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
287
|
+
server.tool("racks_cancel_order", "Cancel an open order on Hyperliquid by order ID or client order ID (cloid). " +
|
|
288
|
+
"Use racks_get_trade_account to find open order IDs.", {
|
|
289
|
+
coin: z
|
|
290
|
+
.string()
|
|
291
|
+
.describe("Coin symbol of the order to cancel, e.g. \"BTC\""),
|
|
292
|
+
order_id: z
|
|
293
|
+
.number()
|
|
294
|
+
.optional()
|
|
295
|
+
.describe("Hyperliquid numeric order ID (use this or cloid)"),
|
|
296
|
+
cloid: z
|
|
297
|
+
.string()
|
|
298
|
+
.optional()
|
|
299
|
+
.describe("Client-supplied order ID (use this or order_id)"),
|
|
300
|
+
}, async ({ coin, order_id, cloid }) => {
|
|
301
|
+
if (!order_id && !cloid) {
|
|
302
|
+
return errorResult("Provide either order_id or cloid", "MISSING_PARAM");
|
|
303
|
+
}
|
|
304
|
+
const res = await request("DELETE", "/api/v1/agent/trade/order", {
|
|
305
|
+
coin,
|
|
306
|
+
order_id,
|
|
307
|
+
cloid,
|
|
308
|
+
});
|
|
309
|
+
if (!res.ok)
|
|
310
|
+
return apiError(res, "Failed to cancel order");
|
|
311
|
+
return {
|
|
312
|
+
content: [
|
|
313
|
+
{
|
|
314
|
+
type: "text",
|
|
315
|
+
text: JSON.stringify({ success: true, cancelled: { coin, order_id, cloid }, hl_response: res.data.hl_response }, null, 2),
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
};
|
|
319
|
+
});
|
|
320
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
321
|
+
// Tool: racks_get_markets
|
|
322
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
323
|
+
server.tool("racks_get_markets", "Browse active Polymarket prediction markets. Returns market questions, outcomes, " +
|
|
324
|
+
"current prices (0–1 scale = probability), and token IDs needed for placing bets. " +
|
|
325
|
+
"Use the next_cursor value to paginate through more markets.", {
|
|
326
|
+
next_cursor: z
|
|
327
|
+
.string()
|
|
328
|
+
.optional()
|
|
329
|
+
.default("MA==")
|
|
330
|
+
.describe('Pagination cursor (use the next_cursor from a previous call, or omit for the first page)'),
|
|
331
|
+
limit: z
|
|
332
|
+
.number()
|
|
333
|
+
.int()
|
|
334
|
+
.min(1)
|
|
335
|
+
.max(50)
|
|
336
|
+
.optional()
|
|
337
|
+
.default(10)
|
|
338
|
+
.describe("Number of markets to return (default 10, max 50)"),
|
|
339
|
+
}, async ({ next_cursor, limit }) => {
|
|
340
|
+
const res = await request("GET", `/api/v1/agent/predict/markets?next_cursor=${encodeURIComponent(next_cursor ?? "MA==")}&limit=${limit}`);
|
|
341
|
+
if (!res.ok)
|
|
342
|
+
return apiError(res, "Failed to fetch Polymarket markets");
|
|
343
|
+
const d = res.data;
|
|
344
|
+
return {
|
|
345
|
+
content: [
|
|
346
|
+
{
|
|
347
|
+
type: "text",
|
|
348
|
+
text: JSON.stringify({
|
|
349
|
+
success: true,
|
|
350
|
+
markets: d.data,
|
|
351
|
+
next_cursor: d.next_cursor,
|
|
352
|
+
note: "Prices are probabilities on a 0–1 scale. Use token_id when placing bets.",
|
|
353
|
+
}, null, 2),
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
};
|
|
357
|
+
});
|
|
358
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
359
|
+
// Tool: racks_place_bet
|
|
360
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
361
|
+
server.tool("racks_place_bet", "Place a prediction market bet on Polymarket via the CLOB. " +
|
|
362
|
+
"First browse markets with racks_get_markets to find the token_id for the outcome you want. " +
|
|
363
|
+
"IMPORTANT: ensure the agent holds USDC on Polygon before betting. " +
|
|
364
|
+
"Requires betting to be enabled and Polymarket API key to be initialised on this agent.", {
|
|
365
|
+
token_id: z
|
|
366
|
+
.string()
|
|
367
|
+
.describe("Polymarket outcome token ID (from racks_get_markets). Each Yes/No outcome has its own token ID"),
|
|
368
|
+
side: z
|
|
369
|
+
.enum(["buy", "sell"])
|
|
370
|
+
.describe('"buy" to open a position; "sell" to close/reduce an existing position'),
|
|
371
|
+
size_usdc: z
|
|
372
|
+
.number()
|
|
373
|
+
.positive()
|
|
374
|
+
.describe("Amount in USDC to spend on this bet (e.g. 10 = $10 USDC)"),
|
|
375
|
+
price: z
|
|
376
|
+
.number()
|
|
377
|
+
.min(0.01)
|
|
378
|
+
.max(0.99)
|
|
379
|
+
.describe("Limit price as a probability between 0.01 and 0.99 (e.g. 0.65 = 65% chance)"),
|
|
380
|
+
intent: z
|
|
381
|
+
.string()
|
|
382
|
+
.optional()
|
|
383
|
+
.describe('Human-readable rationale, e.g. "Betting Yes on Bitcoin ETF approval before Q3"'),
|
|
384
|
+
}, async ({ token_id, side, size_usdc, price, intent }) => {
|
|
385
|
+
const res = await request("POST", "/api/v1/agent/predict/bet", {
|
|
386
|
+
token_id,
|
|
387
|
+
side,
|
|
388
|
+
size_usdc,
|
|
389
|
+
price,
|
|
390
|
+
intent,
|
|
391
|
+
});
|
|
392
|
+
if (!res.ok) {
|
|
393
|
+
const d = res.data;
|
|
394
|
+
// Special-case uninitialized API key
|
|
395
|
+
if (typeof d?.detail === "string" && d.detail.includes("API key not initialised")) {
|
|
396
|
+
return errorResult("Polymarket API key not initialised. Go to wallet.rackspay.com/agents and click 'Init API Key' for this agent.", "POLY_NOT_INITIALIZED");
|
|
397
|
+
}
|
|
398
|
+
return apiError(res, "Failed to place bet");
|
|
399
|
+
}
|
|
400
|
+
const d = res.data;
|
|
401
|
+
return {
|
|
402
|
+
content: [
|
|
403
|
+
{
|
|
404
|
+
type: "text",
|
|
405
|
+
text: JSON.stringify({
|
|
406
|
+
success: true,
|
|
407
|
+
status: d.status,
|
|
408
|
+
order_id: d.order_id,
|
|
409
|
+
bet: { token_id, side, size_usdc, price },
|
|
410
|
+
note: "Monitor your open positions via racks_get_bet_positions.",
|
|
411
|
+
}, null, 2),
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
};
|
|
415
|
+
});
|
|
416
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
417
|
+
// Tool: racks_get_bet_positions
|
|
418
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
419
|
+
server.tool("racks_get_bet_positions", "List all open Polymarket positions for this agent: market question, outcome, " +
|
|
420
|
+
"shares held, average entry price, current price, market value, and unrealized PnL. " +
|
|
421
|
+
"Requires betting to be enabled.", {}, async () => {
|
|
422
|
+
const res = await request("GET", "/api/v1/agent/predict/positions");
|
|
423
|
+
if (!res.ok)
|
|
424
|
+
return apiError(res, "Failed to fetch Polymarket positions");
|
|
425
|
+
const positions = Array.isArray(res.data) ? res.data : [];
|
|
426
|
+
return {
|
|
427
|
+
content: [
|
|
428
|
+
{
|
|
429
|
+
type: "text",
|
|
430
|
+
text: JSON.stringify({
|
|
431
|
+
success: true,
|
|
432
|
+
position_count: positions.length,
|
|
433
|
+
positions,
|
|
434
|
+
}, null, 2),
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
};
|
|
438
|
+
});
|
|
439
|
+
// ============================================================================
|
|
440
|
+
// SERVER STARTUP
|
|
441
|
+
// ============================================================================
|
|
442
|
+
async function main() {
|
|
443
|
+
const transport = new StdioServerTransport();
|
|
444
|
+
await server.connect(transport);
|
|
445
|
+
// Print to stderr so it doesn't pollute stdio MCP protocol messages
|
|
446
|
+
console.error(`RACKS Wallet MCP Server v1.0.0 running (key: ${CONFIG.apiKey.slice(0, 12)}…)`);
|
|
447
|
+
}
|
|
448
|
+
main().catch((err) => {
|
|
449
|
+
console.error("Fatal error:", err);
|
|
450
|
+
process.exit(1);
|
|
451
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rackspay/wallet-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "RACKS Wallet MCP — crypto trading and betting infrastructure for AI agents. Trade perps on Hyperliquid, bet on Polymarket, and check on-chain balances via Claude Desktop or any MCP client.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"racks-wallet-mcp": "./build/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc && chmod 755 build/index.js",
|
|
11
|
+
"dev": "tsx src/index.ts",
|
|
12
|
+
"start": "node build/index.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"build",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"racks",
|
|
21
|
+
"ai-agents",
|
|
22
|
+
"crypto",
|
|
23
|
+
"hyperliquid",
|
|
24
|
+
"polymarket",
|
|
25
|
+
"trading",
|
|
26
|
+
"defi"
|
|
27
|
+
],
|
|
28
|
+
"author": "Racks AI, Inc.",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
32
|
+
"zod": "^3.24.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.0.0",
|
|
36
|
+
"tsx": "^4.19.0",
|
|
37
|
+
"typescript": "^5.7.0"
|
|
38
|
+
}
|
|
39
|
+
}
|