cryptoiz-mcp 2.0.1 → 2.0.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +92 -156
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cryptoiz-mcp",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "CryptoIZ MCP Server — Solana DEX signals with x402 micropayments. Pay $0.01 USDC per call.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -1,25 +1,4 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * CryptoIZ MCP Server v2.0.0
4
- * Solana DEX signals with x402 micropayments — $0.01 USDC per call
5
- *
6
- * Setup di Claude Desktop:
7
- * {
8
- * "mcpServers": {
9
- * "cryptoiz": {
10
- * "command": "npx",
11
- * "args": ["cryptoiz-mcp"],
12
- * "env": {
13
- * "SVM_PRIVATE_KEY": "your-solana-private-key-base58"
14
- * }
15
- * }
16
- * }
17
- * }
18
- *
19
- * User harus punya USDC di wallet Solana mereka.
20
- * Setiap call = $0.01 USDC otomatis terkirim ke CryptoIZ.
21
- */
22
-
23
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
24
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
25
4
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
@@ -32,108 +11,117 @@ const SVM_PRIVATE_KEY = process.env.SVM_PRIVATE_KEY || '';
32
11
  const CRYPTOIZ_WALLET = new PublicKey('DsKmdkYx49Xc1WhqMUAztwhdYPTqieyC98VmnnJdgpXX');
33
12
  const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
34
13
  const SOL_RPC = 'https://api.mainnet-beta.solana.com';
35
- const AMOUNT = 10000; // 0.01 USDC
36
-
37
- // ─── Payment handler ─────────────────────────────────────────────────────────
14
+ const AMOUNT = 10000;
38
15
 
39
16
  async function payAndFetch(url) {
40
17
  if (!SVM_PRIVATE_KEY) {
41
- return {
42
- error: 'SVM_PRIVATE_KEY tidak ditemukan.',
43
- setup: 'Tambahkan SVM_PRIVATE_KEY (private key Solana kamu) ke config Claude Desktop.',
44
- help: 'cryptoiz.org/McpApiKey',
45
- };
18
+ return { error: 'SVM_PRIVATE_KEY tidak ditemukan.', setup: 'Tambahkan SVM_PRIVATE_KEY ke config Claude Desktop.', help: 'cryptoiz.org' };
46
19
  }
47
-
48
- // First request
49
20
  const first = await fetch(url);
50
21
  if (first.status !== 402) return first.json();
51
-
52
- // 402 received — pay USDC
53
22
  try {
54
23
  const keypair = Keypair.fromSecretKey(bs58.decode(SVM_PRIVATE_KEY));
55
24
  const connection = new Connection(SOL_RPC, 'confirmed');
56
-
57
- const fromAta = await getOrCreateAssociatedTokenAccount(
58
- connection, keypair, USDC_MINT, keypair.publicKey
59
- );
60
- const toAta = await getOrCreateAssociatedTokenAccount(
61
- connection, keypair, USDC_MINT, CRYPTOIZ_WALLET
62
- );
63
-
64
- const tx = new Transaction().add(
65
- createTransferInstruction(fromAta.address, toAta.address, keypair.publicKey, AMOUNT)
66
- );
67
-
25
+ const fromAta = await getOrCreateAssociatedTokenAccount(connection, keypair, USDC_MINT, keypair.publicKey);
26
+ const toAta = await getOrCreateAssociatedTokenAccount(connection, keypair, USDC_MINT, CRYPTOIZ_WALLET);
27
+ const tx = new Transaction().add(createTransferInstruction(fromAta.address, toAta.address, keypair.publicKey, AMOUNT));
68
28
  const signature = await connection.sendTransaction(tx, [keypair]);
69
29
  await connection.confirmTransaction(signature, 'confirmed');
70
-
71
- // Retry with payment proof
72
- const payment = Buffer.from(
73
- JSON.stringify({ signature, network: 'solana', x402Version: 1 })
74
- ).toString('base64');
75
-
30
+ const payment = Buffer.from(JSON.stringify({ signature, network: 'solana', x402Version: 1 })).toString('base64');
76
31
  const paidRes = await fetch(url, { headers: { 'x-payment': payment } });
77
32
  return paidRes.json();
78
-
79
33
  } catch (err) {
80
- return {
81
- error: `Payment gagal: ${err.message}`,
82
- hint: 'Pastikan wallet kamu punya USDC dan SOL untuk gas.',
83
- };
34
+ return { error: `Payment gagal: ${err.message}`, hint: 'Pastikan wallet punya USDC dan SOL untuk gas.' };
84
35
  }
85
36
  }
86
37
 
87
38
  // ─── Formatters ───────────────────────────────────────────────────────────────
88
39
 
40
+ function fmt(n, dec=2) { return n != null ? parseFloat(n).toFixed(dec) : 'N/A'; }
41
+ function fmtMC(mc) { if (!mc) return 'N/A'; if (mc >= 1e6) return `$${(mc/1e6).toFixed(2)}M`; return `$${(mc/1000).toFixed(0)}K`; }
42
+ function fmtDelta(d) { return d > 0 ? `+${d}` : `${d}`; }
43
+ function fmtRisk(r) { return r > 0 ? `⚠️ ${fmt(r)}%` : '✅ 0%'; }
44
+
89
45
  function formatAlpha(data) {
90
- if (data.error) return `ERROR: ${data.error}\n${data.setup || ''}`;
46
+ if (data.error) return `❌ ERROR: ${data.error}\n${data.setup||''}`;
91
47
  if (!data.signals?.length) return 'Tidak ada sinyal Alpha Scanner saat ini.';
92
48
  const lines = [
93
- `CRYPTOIZ ALPHA SCANNER | ${data.fetched_at?.split('T')[0]}`,
94
- `Total: ${data.total} sinyal | Powered by cryptoiz.org`,
49
+ `╔═══ CRYPTOIZ ALPHA SCANNER ═══╗`,
50
+ `📅 ${data.fetched_at?.split('T')[0]} | Total: ${data.total} sinyal`,
51
+ `💰 $0.01 USDC | cryptoiz.org | @cryptoiz_IDN`,
52
+ `╚═══════════════════════════════╝`,
95
53
  '',
96
- ...data.signals.map((s, i) => {
97
- const mc = s.market_cap ? `MC: $${(s.market_cap/1000).toFixed(0)}K` : '';
98
- const price = s.price_usd ? `Price: $${parseFloat(s.price_usd).toFixed(6)}` : '';
99
- const addr = s.token_address ? `\n Address: ${s.token_address}` : '';
100
- return `${i+1}. ${s.name} — Score: ${s.alpha_score} | ${s.entry_class}\n Phase: ${s.phase_label} | Whale: ${s.whale_delta > 0 ? '+' : ''}${s.whale_delta} | Dolphin: ${s.dolphin_delta > 0 ? '+' : ''}${s.dolphin_delta} | ${mc} | ${price}${addr}`;
101
- }),
54
+ ...data.signals.map((s, i) => [
55
+ `${i+1}. ━━━ ${s.name} ━━━`,
56
+ ` 📊 Alpha Score : ${fmt(s.alpha_score)} | ${s.entry_class}`,
57
+ ` 📍 Phase : ${s.phase} (Conf: ${fmt(s.phase_confidence)}% | Age: ${s.phase_age_bars} bars)`,
58
+ ` 🌐 Regime : ${s.regime}`,
59
+ ` 🐋 Whale : ${fmtDelta(s.whale_delta)} (${fmt(s.whale_pct)}% supply)`,
60
+ ` 🐬 Dolphin : ${fmtDelta(s.dolphin_delta)} (${fmt(s.dolphin_pct)}% supply)`,
61
+ ` 🦐 Shrimp : ${fmtDelta(s.shrimp_delta)}`,
62
+ ` 📈 Sub-scores : Accum ${fmt(s.accumulation_score)} | Timing ${fmt(s.timing_score)} | Safety ${fmt(s.safety_score)}`,
63
+ ` ⚠️ Risks : Liq ${fmtRisk(s.liquidity_drain_risk)} | Trap ${fmtRisk(s.smart_money_trap_risk)}`,
64
+ ` 📉 Price Drop : ${s.price_drop_pct != null ? fmt(s.price_drop_pct)+'%' : 'N/A'}`,
65
+ ` 💰 Market Cap : ${fmtMC(s.market_cap_usd)}`,
66
+ ` 💵 Price : $${s.price_usd ? parseFloat(s.price_usd).toFixed(6) : 'N/A'}`,
67
+ ` 🔗 Address : ${s.token_address}`,
68
+ ].join('\n')),
102
69
  '',
103
- '📊 cryptoiz.org | @cryptoiz_IDN',
70
+ '📊 Data by CryptoIZ — cryptoiz.org | @cryptoiz_IDN',
104
71
  ];
105
72
  return lines.join('\n');
106
73
  }
107
74
 
108
75
  function formatDivergence(data) {
109
- if (data.error) return `ERROR: ${data.error}\n${data.hint || ''}`;
76
+ if (data.error) return `❌ ERROR: ${data.error}`;
110
77
  if (!data.signals?.length) return `Tidak ada divergence signal (${data.timeframe}).`;
111
78
  const lines = [
112
- `CRYPTOIZ DIVERGENCE ${data.timeframe?.toUpperCase()}`,
113
- `Total: ${data.total} | Powered by cryptoiz.org`,
79
+ `╔═══ CRYPTOIZ DIVERGENCE SCANNER ═══╗`,
80
+ `📅 ${data.timeframe?.toUpperCase()} | Total: ${data.total}`,
81
+ `💰 $0.01 USDC | cryptoiz.org | @cryptoiz_IDN`,
82
+ `╚════════════════════════════════════╝`,
114
83
  '',
115
84
  ...data.signals.map((s, i) => {
116
- const type = s.divergence_type === 'bullish' ? 'BULLISH 🟢' : s.divergence_type === 'bearish' ? 'BEARISH 🔴' : (s.divergence_type||'').toUpperCase();
117
- return `${i+1}. ${s.name||s.symbol} [${type}]\n Score: ${s.divergence_score?.toFixed(1)||'-'} | MC: $${((s.market_cap||0)/1000).toFixed(0)}K`;
85
+ const type = s.divergence_type === 'bullish' ? '🟢 BULLISH' : s.divergence_type === 'bearish' ? '🔴 BEARISH' : (s.divergence_type||'').toUpperCase();
86
+ return [
87
+ `${i+1}. ━━━ ${s.name||s.symbol} ━━━`,
88
+ ` 📊 Divergence : ${type} (Score: ${fmt(s.divergence_score)})`,
89
+ ` 🎯 Confidence : ${fmt(s.confidence_score)}%`,
90
+ ` 📍 Phase : ${s.phase_label}`,
91
+ ` 🐋 Whale : ${fmtDelta(s.whale_delta)} | 🐬 Dolphin: ${fmtDelta(s.dolphin_delta)}`,
92
+ ` 💰 Market Cap : ${fmtMC(s.market_cap_usd)}`,
93
+ ` 💵 Price : $${s.price_usd ? parseFloat(s.price_usd).toFixed(6) : 'N/A'}`,
94
+ ` 📉 Change : ${s.price_change_pct ? fmt(s.price_change_pct)+'%' : 'N/A'}`,
95
+ ` 🔗 Address : ${s.token_address}`,
96
+ ` 🕐 Detected : ${s.detected_at ? new Date(s.detected_at).toUTCString() : 'N/A'}`,
97
+ ].join('\n');
118
98
  }),
119
99
  '',
120
- '📊 cryptoiz.org | @cryptoiz_IDN',
100
+ '📊 Data by CryptoIZ — cryptoiz.org | @cryptoiz_IDN',
121
101
  ];
122
102
  return lines.join('\n');
123
103
  }
124
104
 
125
105
  function formatAccum(data) {
126
- if (data.error) return `ERROR: ${data.error}\n${data.hint || ''}`;
106
+ if (data.error) return `❌ ERROR: ${data.error}`;
127
107
  if (!data.tokens?.length) return 'Tidak ada token dalam fase akumulasi.';
128
108
  const lines = [
129
- `CRYPTOIZ AKUMULASI`,
130
- `Total: ${data.total} | Powered by cryptoiz.org`,
109
+ `╔═══ CRYPTOIZ AKUMULASI ═══╗`,
110
+ `📅 ${data.fetched_at?.split('T')[0]} | Total: ${data.total}`,
111
+ `💰 $0.01 USDC | cryptoiz.org | @cryptoiz_IDN`,
112
+ `╚══════════════════════════╝`,
131
113
  '',
132
- ...data.tokens.map((t, i) =>
133
- `${i+1}. ${t.name} — Composite: ${t.composite_score?.toFixed(1)||'N/A'} | ${t.strength_label}\n AccDist: ${t.accdist_score?.toFixed(0)||'-'} | Holder: ${t.holder_score?.toFixed(1)||'-'} | Market: ${t.market_score?.toFixed(1)||'-'}`
134
- ),
114
+ ...data.tokens.map((t, i) => [
115
+ `${i+1}. ━━━ ${t.name} ━━━`,
116
+ ` 📊 Composite : ${fmt(t.composite_score)} | ${t.strength_label}`,
117
+ ` 🔄 AccDist : ${t.accdist_label} (Score: ${fmt(t.accdist_score)})`,
118
+ ` 📈 Structure : ${fmt(t.structure_score)}`,
119
+ ` 👥 Holder : ${fmt(t.holder_score)}`,
120
+ ` 📉 Market : ${fmt(t.market_score)}`,
121
+ ` 🔗 Address : ${t.token_address}`,
122
+ ].join('\n')),
135
123
  '',
136
- '📊 cryptoiz.org | @cryptoiz_IDN',
124
+ '📊 Data by CryptoIZ — cryptoiz.org | @cryptoiz_IDN',
137
125
  ];
138
126
  return lines.join('\n');
139
127
  }
@@ -141,112 +129,60 @@ function formatAccum(data) {
141
129
  function formatStatus() {
142
130
  const hasKey = !!SVM_PRIVATE_KEY;
143
131
  return [
144
- `CryptoIZ MCP Server v2.0.0`,
145
- `Status : CONNECTED`,
146
- `Payment : ${hasKey ? 'READY — x402 Solana USDC ($0.01/call)' : 'NOT SET — tambahkan SVM_PRIVATE_KEY'}`,
132
+ `╔═══ CryptoIZ MCP Server v2.0.2 ═══╗`,
133
+ ` Status : CONNECTED`,
134
+ ` Payment : ${hasKey ? 'READY — x402 Solana USDC ($0.01/call)' : 'NOT SET — tambahkan SVM_PRIVATE_KEY'}`,
135
+ ` Wallet : DsKmdkYx49Xc1WhqMUAztwhdYPTqieyC98VmnnJdgpXX`,
136
+ `╚═══════════════════════════════════╝`,
147
137
  ``,
148
138
  `Tools tersedia:`,
149
- ` 1. get_alpha_scanner — Alpha entry signals terkuat`,
150
- ` 2. get_divergence — Divergence bullish/bearish`,
151
- ` 3. get_accumulation — Token fase akumulasi`,
152
- ` 4. get_status — Info ini`,
139
+ ` 1. get_alpha_scanner — Alpha entry signals + address + MC`,
140
+ ` 2. get_divergence — Divergence bullish/bearish + address`,
141
+ ` 3. get_accumulation — Token fase akumulasi + address`,
142
+ ` 4. get_status — Info server ini`,
153
143
  ``,
154
- `Harga : $0.01 USDC per call (Solana)`,
155
- `Wallet : DsKmdkYx49Xc1WhqMUAztwhdYPTqieyC98VmnnJdgpXX`,
156
- `Info : cryptoiz.org | @cryptoiz_IDN`,
144
+ `💰 $0.01 USDC per call via Solana`,
145
+ `🌐 cryptoiz.org | @cryptoiz_IDN`,
157
146
  ].join('\n');
158
147
  }
159
148
 
160
- // ─── Tools definition ─────────────────────────────────────────────────────────
161
-
162
149
  const TOOLS = [
163
- {
164
- name: 'get_alpha_scanner',
165
- description: 'Ambil sinyal token Solana terkuat hari ini dari CryptoIZ Alpha Scanner. Biaya: $0.01 USDC per call via Solana.',
166
- inputSchema: {
167
- type: 'object',
168
- properties: {
169
- min_score: { type: 'number', description: 'Minimum alpha score (0-100).' },
170
- },
171
- required: [],
172
- },
173
- },
174
- {
175
- name: 'get_divergence',
176
- description: 'Ambil divergence signals dari CryptoIZ — deteksi bullish/bearish divergence whale/dolphin di Solana. Biaya: $0.01 USDC.',
177
- inputSchema: {
178
- type: 'object',
179
- properties: {
180
- timeframe: { type: 'string', enum: ['4h', '1d'], description: 'Timeframe. Default: 4h.' },
181
- limit: { type: 'number', description: 'Max results (1-50). Default: 20.' },
182
- },
183
- required: [],
184
- },
185
- },
186
- {
187
- name: 'get_accumulation',
188
- description: 'Ambil token dalam fase akumulasi dari CryptoIZ Accumulation Dashboard. Biaya: $0.01 USDC.',
189
- inputSchema: {
190
- type: 'object',
191
- properties: {
192
- min_composite: { type: 'number', description: 'Minimum composite score (0-100).' },
193
- },
194
- required: [],
195
- },
196
- },
197
- {
198
- name: 'get_status',
199
- description: 'Cek status koneksi CryptoIZ MCP, payment setup, dan tools yang tersedia.',
200
- inputSchema: { type: 'object', properties: {}, required: [] },
201
- },
150
+ { name: 'get_alpha_scanner', description: 'Sinyal token Solana terkuat dari CryptoIZ Alpha Scanner — lengkap dengan address, MC, price, holder signals, sub-scores, risks. $0.01 USDC/call.', inputSchema: { type: 'object', properties: { min_score: { type: 'number', description: 'Minimum alpha score (0-100).' }, entry_class: { type: 'string', enum: ['ALPHA_EARLY','ALPHA_BUILDING','WATCHLIST_ONLY'] } }, required: [] } },
151
+ { name: 'get_divergence', description: 'Divergence signals dari CryptoIZ — bullish/bearish dengan address, MC, confidence score. $0.01 USDC/call.', inputSchema: { type: 'object', properties: { timeframe: { type: 'string', enum: ['4h','1d'] }, limit: { type: 'number' } }, required: [] } },
152
+ { name: 'get_accumulation', description: 'Token fase akumulasi dari CryptoIZ Accumulation Dashboard composite score, holder, market score + address. $0.01 USDC/call.', inputSchema: { type: 'object', properties: { min_composite: { type: 'number' } }, required: [] } },
153
+ { name: 'get_status', description: 'Status koneksi CryptoIZ MCP dan info payment.', inputSchema: { type: 'object', properties: {}, required: [] } },
202
154
  ];
203
155
 
204
- // ─── Server ───────────────────────────────────────────────────────────────────
205
-
206
- const server = new Server(
207
- { name: 'cryptoiz-mcp', version: '2.0.1' },
208
- { capabilities: { tools: {} } }
209
- );
210
-
156
+ const server = new Server({ name: 'cryptoiz-mcp', version: '2.0.2' }, { capabilities: { tools: {} } });
211
157
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
212
158
 
213
159
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
214
160
  const { name, arguments: args } = request.params;
215
-
216
161
  try {
217
- if (name === 'get_status') {
218
- return { content: [{ type: 'text', text: formatStatus() }] };
219
- }
162
+ if (name === 'get_status') return { content: [{ type: 'text', text: formatStatus() }] };
220
163
 
221
164
  if (name === 'get_alpha_scanner') {
222
- const url = `${GATEWAY_URL}?tool=get_alpha_scanner`;
223
- const data = await payAndFetch(url);
224
- if (args?.min_score && data.signals) {
225
- data.signals = data.signals.filter(s => s.alpha_score >= args.min_score);
226
- data.total = data.signals.length;
227
- }
165
+ const data = await payAndFetch(`${GATEWAY_URL}?tool=get_alpha_scanner`);
166
+ if (args?.min_score && data.signals) { data.signals = data.signals.filter(s => s.alpha_score >= args.min_score); data.total = data.signals.length; }
167
+ if (args?.entry_class && data.signals) { data.signals = data.signals.filter(s => s.entry_class === args.entry_class); data.total = data.signals.length; }
228
168
  return { content: [{ type: 'text', text: formatAlpha(data) }] };
229
169
  }
230
170
 
231
171
  if (name === 'get_divergence') {
232
- const params = new URLSearchParams({ tool: 'get_divergence' });
233
- if (args?.timeframe) params.set('tf', args.timeframe);
234
- if (args?.limit) params.set('limit', String(args.limit));
235
- const data = await payAndFetch(`${GATEWAY_URL}?${params}`);
172
+ const p = new URLSearchParams({ tool: 'get_divergence' });
173
+ if (args?.timeframe) p.set('tf', args.timeframe);
174
+ if (args?.limit) p.set('limit', String(args.limit));
175
+ const data = await payAndFetch(`${GATEWAY_URL}?${p}`);
236
176
  return { content: [{ type: 'text', text: formatDivergence(data) }] };
237
177
  }
238
178
 
239
179
  if (name === 'get_accumulation') {
240
180
  const data = await payAndFetch(`${GATEWAY_URL}?tool=get_accumulation`);
241
- if (args?.min_composite && data.tokens) {
242
- data.tokens = data.tokens.filter(t => t.composite_score >= args.min_composite);
243
- data.total = data.tokens.length;
244
- }
181
+ if (args?.min_composite && data.tokens) { data.tokens = data.tokens.filter(t => t.composite_score >= args.min_composite); data.total = data.tokens.length; }
245
182
  return { content: [{ type: 'text', text: formatAccum(data) }] };
246
183
  }
247
184
 
248
185
  return { content: [{ type: 'text', text: `Tool tidak dikenal: ${name}` }], isError: true };
249
-
250
186
  } catch (err) {
251
187
  return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
252
188
  }