cryptoiz-mcp 2.0.0 → 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 -153
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cryptoiz-mcp",
3
- "version": "2.0.0",
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,105 +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
- `${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}`
98
- ),
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')),
99
69
  '',
100
- '📊 cryptoiz.org | @cryptoiz_IDN',
70
+ '📊 Data by CryptoIZ — cryptoiz.org | @cryptoiz_IDN',
101
71
  ];
102
72
  return lines.join('\n');
103
73
  }
104
74
 
105
75
  function formatDivergence(data) {
106
- if (data.error) return `ERROR: ${data.error}\n${data.hint || ''}`;
76
+ if (data.error) return `❌ ERROR: ${data.error}`;
107
77
  if (!data.signals?.length) return `Tidak ada divergence signal (${data.timeframe}).`;
108
78
  const lines = [
109
- `CRYPTOIZ DIVERGENCE ${data.timeframe?.toUpperCase()}`,
110
- `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
+ `╚════════════════════════════════════╝`,
111
83
  '',
112
84
  ...data.signals.map((s, i) => {
113
- const type = s.divergence_type === 'bullish' ? 'BULLISH 🟢' : s.divergence_type === 'bearish' ? 'BEARISH 🔴' : (s.divergence_type||'').toUpperCase();
114
- 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');
115
98
  }),
116
99
  '',
117
- '📊 cryptoiz.org | @cryptoiz_IDN',
100
+ '📊 Data by CryptoIZ — cryptoiz.org | @cryptoiz_IDN',
118
101
  ];
119
102
  return lines.join('\n');
120
103
  }
121
104
 
122
105
  function formatAccum(data) {
123
- if (data.error) return `ERROR: ${data.error}\n${data.hint || ''}`;
106
+ if (data.error) return `❌ ERROR: ${data.error}`;
124
107
  if (!data.tokens?.length) return 'Tidak ada token dalam fase akumulasi.';
125
108
  const lines = [
126
- `CRYPTOIZ AKUMULASI`,
127
- `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
+ `╚══════════════════════════╝`,
128
113
  '',
129
- ...data.tokens.map((t, i) =>
130
- `${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)||'-'}`
131
- ),
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')),
132
123
  '',
133
- '📊 cryptoiz.org | @cryptoiz_IDN',
124
+ '📊 Data by CryptoIZ — cryptoiz.org | @cryptoiz_IDN',
134
125
  ];
135
126
  return lines.join('\n');
136
127
  }
@@ -138,112 +129,60 @@ function formatAccum(data) {
138
129
  function formatStatus() {
139
130
  const hasKey = !!SVM_PRIVATE_KEY;
140
131
  return [
141
- `CryptoIZ MCP Server v2.0.0`,
142
- `Status : CONNECTED`,
143
- `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
+ `╚═══════════════════════════════════╝`,
144
137
  ``,
145
138
  `Tools tersedia:`,
146
- ` 1. get_alpha_scanner — Alpha entry signals terkuat`,
147
- ` 2. get_divergence — Divergence bullish/bearish`,
148
- ` 3. get_accumulation — Token fase akumulasi`,
149
- ` 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`,
150
143
  ``,
151
- `Harga : $0.01 USDC per call (Solana)`,
152
- `Wallet : DsKmdkYx49Xc1WhqMUAztwhdYPTqieyC98VmnnJdgpXX`,
153
- `Info : cryptoiz.org | @cryptoiz_IDN`,
144
+ `💰 $0.01 USDC per call via Solana`,
145
+ `🌐 cryptoiz.org | @cryptoiz_IDN`,
154
146
  ].join('\n');
155
147
  }
156
148
 
157
- // ─── Tools definition ─────────────────────────────────────────────────────────
158
-
159
149
  const TOOLS = [
160
- {
161
- name: 'get_alpha_scanner',
162
- description: 'Ambil sinyal token Solana terkuat hari ini dari CryptoIZ Alpha Scanner. Biaya: $0.01 USDC per call via Solana.',
163
- inputSchema: {
164
- type: 'object',
165
- properties: {
166
- min_score: { type: 'number', description: 'Minimum alpha score (0-100).' },
167
- },
168
- required: [],
169
- },
170
- },
171
- {
172
- name: 'get_divergence',
173
- description: 'Ambil divergence signals dari CryptoIZ — deteksi bullish/bearish divergence whale/dolphin di Solana. Biaya: $0.01 USDC.',
174
- inputSchema: {
175
- type: 'object',
176
- properties: {
177
- timeframe: { type: 'string', enum: ['4h', '1d'], description: 'Timeframe. Default: 4h.' },
178
- limit: { type: 'number', description: 'Max results (1-50). Default: 20.' },
179
- },
180
- required: [],
181
- },
182
- },
183
- {
184
- name: 'get_accumulation',
185
- description: 'Ambil token dalam fase akumulasi dari CryptoIZ Accumulation Dashboard. Biaya: $0.01 USDC.',
186
- inputSchema: {
187
- type: 'object',
188
- properties: {
189
- min_composite: { type: 'number', description: 'Minimum composite score (0-100).' },
190
- },
191
- required: [],
192
- },
193
- },
194
- {
195
- name: 'get_status',
196
- description: 'Cek status koneksi CryptoIZ MCP, payment setup, dan tools yang tersedia.',
197
- inputSchema: { type: 'object', properties: {}, required: [] },
198
- },
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: [] } },
199
154
  ];
200
155
 
201
- // ─── Server ───────────────────────────────────────────────────────────────────
202
-
203
- const server = new Server(
204
- { name: 'cryptoiz-mcp', version: '2.0.0' },
205
- { capabilities: { tools: {} } }
206
- );
207
-
156
+ const server = new Server({ name: 'cryptoiz-mcp', version: '2.0.2' }, { capabilities: { tools: {} } });
208
157
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
209
158
 
210
159
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
211
160
  const { name, arguments: args } = request.params;
212
-
213
161
  try {
214
- if (name === 'get_status') {
215
- return { content: [{ type: 'text', text: formatStatus() }] };
216
- }
162
+ if (name === 'get_status') return { content: [{ type: 'text', text: formatStatus() }] };
217
163
 
218
164
  if (name === 'get_alpha_scanner') {
219
- const url = `${GATEWAY_URL}?tool=get_alpha_scanner`;
220
- const data = await payAndFetch(url);
221
- if (args?.min_score && data.signals) {
222
- data.signals = data.signals.filter(s => s.alpha_score >= args.min_score);
223
- data.total = data.signals.length;
224
- }
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; }
225
168
  return { content: [{ type: 'text', text: formatAlpha(data) }] };
226
169
  }
227
170
 
228
171
  if (name === 'get_divergence') {
229
- const params = new URLSearchParams({ tool: 'get_divergence' });
230
- if (args?.timeframe) params.set('tf', args.timeframe);
231
- if (args?.limit) params.set('limit', String(args.limit));
232
- 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}`);
233
176
  return { content: [{ type: 'text', text: formatDivergence(data) }] };
234
177
  }
235
178
 
236
179
  if (name === 'get_accumulation') {
237
180
  const data = await payAndFetch(`${GATEWAY_URL}?tool=get_accumulation`);
238
- if (args?.min_composite && data.tokens) {
239
- data.tokens = data.tokens.filter(t => t.composite_score >= args.min_composite);
240
- data.total = data.tokens.length;
241
- }
181
+ if (args?.min_composite && data.tokens) { data.tokens = data.tokens.filter(t => t.composite_score >= args.min_composite); data.total = data.tokens.length; }
242
182
  return { content: [{ type: 'text', text: formatAccum(data) }] };
243
183
  }
244
184
 
245
185
  return { content: [{ type: 'text', text: `Tool tidak dikenal: ${name}` }], isError: true };
246
-
247
186
  } catch (err) {
248
187
  return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
249
188
  }