crossfin-mcp 1.7.1 → 1.8.2
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/README.md +7 -1
- package/dist/index.js +114 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CrossFin MCP Server
|
|
2
2
|
|
|
3
|
-
MCP server for CrossFin —
|
|
3
|
+
MCP server for CrossFin — 16 tools for service discovery, local ledger, routing engine, and paid API execution.
|
|
4
4
|
|
|
5
5
|
## Install (npm)
|
|
6
6
|
|
|
@@ -37,6 +37,12 @@ crossfin-mcp
|
|
|
37
37
|
- `get_analytics`
|
|
38
38
|
- `get_guide`
|
|
39
39
|
|
|
40
|
+
### Routing engine
|
|
41
|
+
|
|
42
|
+
- `find_optimal_route` — find optimal crypto transfer route across 6 exchanges (paid via x402, requires `EVM_PRIVATE_KEY`)
|
|
43
|
+
- `list_exchange_fees` — compare trading and withdrawal fees across exchanges
|
|
44
|
+
- `compare_exchange_prices` — compare live prices for a coin across Korean exchanges
|
|
45
|
+
|
|
40
46
|
### Paid execution
|
|
41
47
|
|
|
42
48
|
- `call_paid_service` — call any CrossFin paid endpoint with automatic x402 USDC payment on Base (requires `EVM_PRIVATE_KEY`)
|
package/dist/index.js
CHANGED
|
@@ -5,11 +5,15 @@ import { z } from 'zod/v4';
|
|
|
5
5
|
import { x402Client, wrapFetchWithPayment, x402HTTPClient } from '@x402/fetch';
|
|
6
6
|
import { registerExactEvmScheme } from '@x402/evm/exact/client';
|
|
7
7
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
8
9
|
import { createWallet, defaultLedgerPath, getBalance, listTransactions, setBudget, transfer, } from './ledgerStore.js';
|
|
9
10
|
const LEDGER_PATH = process.env.CROSSFIN_LEDGER_PATH?.trim() || defaultLedgerPath();
|
|
10
11
|
const API_BASE = (process.env.CROSSFIN_API_URL?.trim() || 'https://crossfin.dev').replace(/\/$/, '');
|
|
11
12
|
const EVM_PRIVATE_KEY = process.env.EVM_PRIVATE_KEY?.trim() ?? '';
|
|
12
13
|
const API_ORIGIN = new URL(API_BASE).origin;
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const pkg = require('../package.json');
|
|
16
|
+
const MCP_VERSION = typeof pkg.version === 'string' && pkg.version.trim() ? pkg.version.trim() : '0.0.0';
|
|
13
17
|
function ensureCrossfinPaidUrl(raw) {
|
|
14
18
|
let url;
|
|
15
19
|
try {
|
|
@@ -53,7 +57,7 @@ function basescanLink(networkId, txHash) {
|
|
|
53
57
|
return `https://basescan.org/tx/${txHash}`;
|
|
54
58
|
return null;
|
|
55
59
|
}
|
|
56
|
-
const server = new McpServer({ name: 'crossfin', version:
|
|
60
|
+
const server = new McpServer({ name: 'crossfin', version: MCP_VERSION });
|
|
57
61
|
const railSchema = z.enum(['manual', 'kakaopay', 'toss', 'stripe', 'x402']);
|
|
58
62
|
async function apiFetch(path) {
|
|
59
63
|
const res = await fetch(`${API_BASE}${path}`);
|
|
@@ -394,5 +398,114 @@ server.registerTool('call_paid_service', {
|
|
|
394
398
|
};
|
|
395
399
|
}
|
|
396
400
|
});
|
|
401
|
+
/* ── Routing Engine Tools ── */
|
|
402
|
+
server.registerTool('find_optimal_route', {
|
|
403
|
+
title: 'Find optimal route',
|
|
404
|
+
description: 'Find the cheapest/fastest path to move money across Asian exchanges. ' +
|
|
405
|
+
'Example: KRW on Bithumb → USDC on Binance. ' +
|
|
406
|
+
'Supports 6 exchanges (Bithumb, Upbit, Coinone, Korbit, GoPax, Binance) and 12 bridge coins. ' +
|
|
407
|
+
'Paid tool: calls /api/premium/route/find ($0.10) via x402 (requires EVM_PRIVATE_KEY).',
|
|
408
|
+
inputSchema: z.object({
|
|
409
|
+
from: z
|
|
410
|
+
.string()
|
|
411
|
+
.describe('Source in exchange:currency format (e.g. "bithumb:KRW", "upbit:KRW")'),
|
|
412
|
+
to: z
|
|
413
|
+
.string()
|
|
414
|
+
.describe('Destination in exchange:currency format (e.g. "binance:USDC", "binance:BTC")'),
|
|
415
|
+
amount: z.number().describe('Amount in source currency (e.g. 1000000 for ₩1,000,000)'),
|
|
416
|
+
strategy: z
|
|
417
|
+
.enum(['cheapest', 'fastest', 'balanced'])
|
|
418
|
+
.optional()
|
|
419
|
+
.describe('Routing strategy: cheapest (default), fastest, or balanced'),
|
|
420
|
+
}),
|
|
421
|
+
}, async ({ from, to, amount, strategy }) => {
|
|
422
|
+
if (!paidFetch || !httpClient) {
|
|
423
|
+
return {
|
|
424
|
+
content: [{ type: 'text', text: 'EVM_PRIVATE_KEY not configured — cannot call paid routing endpoint' }],
|
|
425
|
+
isError: true,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
const qs = new URLSearchParams({
|
|
430
|
+
from,
|
|
431
|
+
to,
|
|
432
|
+
amount: String(amount),
|
|
433
|
+
});
|
|
434
|
+
if (strategy)
|
|
435
|
+
qs.set('strategy', strategy);
|
|
436
|
+
const targetUrl = ensureCrossfinPaidUrl(`${API_BASE}/api/premium/route/find?${qs.toString()}`);
|
|
437
|
+
const res = await paidFetch(targetUrl, { method: 'GET' });
|
|
438
|
+
const body = await res.text();
|
|
439
|
+
let data;
|
|
440
|
+
try {
|
|
441
|
+
data = JSON.parse(body);
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
data = body;
|
|
445
|
+
}
|
|
446
|
+
const settle = httpClient.getPaymentSettleResponse((name) => res.headers.get(name));
|
|
447
|
+
const txHash = settle?.transaction;
|
|
448
|
+
const networkId = settle?.network;
|
|
449
|
+
const scanLink = basescanLink(networkId, txHash);
|
|
450
|
+
const result = {
|
|
451
|
+
status: res.status,
|
|
452
|
+
payer: payerAddress,
|
|
453
|
+
paid: !!settle,
|
|
454
|
+
txHash: txHash ?? null,
|
|
455
|
+
basescan: scanLink,
|
|
456
|
+
data,
|
|
457
|
+
};
|
|
458
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
459
|
+
}
|
|
460
|
+
catch (e) {
|
|
461
|
+
return {
|
|
462
|
+
content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : 'Unknown error'}` }],
|
|
463
|
+
isError: true,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
server.registerTool('list_exchange_fees', {
|
|
468
|
+
title: 'List exchange fees',
|
|
469
|
+
description: 'Show trading fees, withdrawal fees, and transfer times for all supported exchanges ' +
|
|
470
|
+
'(Bithumb, Upbit, Coinone, Korbit, GoPax, Binance)',
|
|
471
|
+
inputSchema: z.object({}),
|
|
472
|
+
}, async (_params) => {
|
|
473
|
+
try {
|
|
474
|
+
const data = await apiFetch('/api/route/fees');
|
|
475
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
476
|
+
}
|
|
477
|
+
catch (e) {
|
|
478
|
+
return {
|
|
479
|
+
content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : 'Unknown error'}` }],
|
|
480
|
+
isError: true,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
server.registerTool('compare_exchange_prices', {
|
|
485
|
+
title: 'Compare exchange prices',
|
|
486
|
+
description: 'Compare real-time prices for a coin across all Korean exchanges + Binance. ' +
|
|
487
|
+
'Shows price, premium, and arbitrage opportunities.',
|
|
488
|
+
inputSchema: z.object({
|
|
489
|
+
coin: z
|
|
490
|
+
.string()
|
|
491
|
+
.optional()
|
|
492
|
+
.describe('Coin symbol to compare (e.g. "BTC", "XRP"). Omit for all supported coins.'),
|
|
493
|
+
}),
|
|
494
|
+
}, async ({ coin }) => {
|
|
495
|
+
try {
|
|
496
|
+
const qs = new URLSearchParams();
|
|
497
|
+
if (coin?.trim())
|
|
498
|
+
qs.set('coin', coin.trim().toUpperCase());
|
|
499
|
+
const path = qs.size ? `/api/route/pairs?${qs.toString()}` : '/api/route/pairs';
|
|
500
|
+
const data = await apiFetch(path);
|
|
501
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
502
|
+
}
|
|
503
|
+
catch (e) {
|
|
504
|
+
return {
|
|
505
|
+
content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : 'Unknown error'}` }],
|
|
506
|
+
isError: true,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
});
|
|
397
510
|
const transport = new StdioServerTransport();
|
|
398
511
|
await server.connect(transport);
|