cryptoiz-mcp 1.0.0 → 2.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.
Files changed (2) hide show
  1. package/package.json +7 -7
  2. package/src/index.js +180 -80
package/package.json CHANGED
@@ -1,16 +1,13 @@
1
1
  {
2
2
  "name": "cryptoiz-mcp",
3
- "version": "1.0.0",
4
- "description": "CryptoIZ MCP Server — Solana DEX Alpha Scanner, Divergence & Accumulation signals for Claude Desktop",
3
+ "version": "2.0.0",
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",
7
7
  "bin": {
8
8
  "cryptoiz-mcp": "src/index.js"
9
9
  },
10
- "scripts": {
11
- "start": "node src/index.js"
12
- },
13
- "keywords": ["cryptoiz", "solana", "defi", "mcp", "claude", "crypto", "alpha", "signals", "divergence", "accumulation"],
10
+ "keywords": ["cryptoiz", "solana", "defi", "mcp", "claude", "crypto", "alpha", "signals", "x402", "usdc"],
14
11
  "author": "CryptoIZ <cryptoiz.suport@gmail.com> (https://cryptoiz.org)",
15
12
  "license": "MIT",
16
13
  "repository": {
@@ -19,7 +16,10 @@
19
16
  },
20
17
  "homepage": "https://cryptoiz.org",
21
18
  "dependencies": {
22
- "@modelcontextprotocol/sdk": "^1.0.0"
19
+ "@modelcontextprotocol/sdk": "^1.0.0",
20
+ "@solana/web3.js": "^1.98.0",
21
+ "@solana/spl-token": "^0.4.9",
22
+ "bs58": "^6.0.0"
23
23
  },
24
24
  "engines": {
25
25
  "node": ">=18.0.0"
package/src/index.js CHANGED
@@ -1,149 +1,249 @@
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
+
2
23
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
24
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
25
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
26
+ import { Connection, Keypair, PublicKey, Transaction } from '@solana/web3.js';
27
+ import { getOrCreateAssociatedTokenAccount, createTransferInstruction } from '@solana/spl-token';
28
+ import bs58 from 'bs58';
29
+
30
+ const GATEWAY_URL = 'https://rehqwsypjnjirhuiapqh.supabase.co/functions/v1/mcp-x402-gateway';
31
+ const SVM_PRIVATE_KEY = process.env.SVM_PRIVATE_KEY || '';
32
+ const CRYPTOIZ_WALLET = new PublicKey('DsKmdkYx49Xc1WhqMUAztwhdYPTqieyC98VmnnJdgpXX');
33
+ const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
34
+ const SOL_RPC = 'https://api.mainnet-beta.solana.com';
35
+ const AMOUNT = 10000; // 0.01 USDC
36
+
37
+ // ─── Payment handler ─────────────────────────────────────────────────────────
38
+
39
+ async function payAndFetch(url) {
40
+ 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
+ };
46
+ }
47
+
48
+ // First request
49
+ const first = await fetch(url);
50
+ if (first.status !== 402) return first.json();
51
+
52
+ // 402 received — pay USDC
53
+ try {
54
+ const keypair = Keypair.fromSecretKey(bs58.decode(SVM_PRIVATE_KEY));
55
+ const connection = new Connection(SOL_RPC, 'confirmed');
5
56
 
6
- const BASE_URL = 'https://rehqwsypjnjirhuiapqh.supabase.co/functions/v1';
7
- const API_KEY = process.env.CRYPTOIZ_API_KEY || '';
8
-
9
- async function callApi(endpoint, params = {}) {
10
- const url = new URL(`${BASE_URL}/${endpoint}`);
11
- Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
12
- const headers = { 'Content-Type': 'application/json' };
13
- if (API_KEY) headers['x-api-key'] = API_KEY;
14
- const res = await fetch(url.toString(), { headers });
15
- const json = await res.json();
16
- if (!res.ok) throw new Error(json.error || `HTTP ${res.status}`);
17
- return json;
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
+
68
+ const signature = await connection.sendTransaction(tx, [keypair]);
69
+ 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
+
76
+ const paidRes = await fetch(url, { headers: { 'x-payment': payment } });
77
+ return paidRes.json();
78
+
79
+ } catch (err) {
80
+ return {
81
+ error: `Payment gagal: ${err.message}`,
82
+ hint: 'Pastikan wallet kamu punya USDC dan SOL untuk gas.',
83
+ };
84
+ }
18
85
  }
19
86
 
20
- function formatAlphaScanner(data) {
21
- if (!data.signals || data.signals.length === 0) return 'Tidak ada sinyal Alpha Scanner saat ini.';
87
+ // ─── Formatters ───────────────────────────────────────────────────────────────
88
+
89
+ function formatAlpha(data) {
90
+ if (data.error) return `ERROR: ${data.error}\n${data.setup || ''}`;
91
+ if (!data.signals?.length) return 'Tidak ada sinyal Alpha Scanner saat ini.';
22
92
  const lines = [
23
- `CRYPTOIZ ALPHA SCANNER`,
24
- `Tier: ${data.tier.toUpperCase()} | Total: ${data.total} | ${new Date(data.fetched_at).toUTCString()}`,
25
- data.note ? `INFO: ${data.note}` : '',
93
+ `CRYPTOIZ ALPHA SCANNER | ${data.fetched_at?.split('T')[0]}`,
94
+ `Total: ${data.total} sinyal | Powered by cryptoiz.org`,
26
95
  '',
27
- ...data.signals.map((s, i) => {
28
- if (data.tier === 'free') return `${i+1}. ${s.token} — Phase: ${s.phase} [${s.entry_class}]`;
29
- const whale = s.whale_delta > 0 ? `WHALE+${s.whale_delta}` : `WHALE${s.whale_delta}`;
30
- const dolph = s.dolphin_delta > 0 ? `DOLPHIN+${s.dolphin_delta}` : `DOLPHIN${s.dolphin_delta}`;
31
- return `${i+1}. ${s.token} — Score: ${s.alpha_score} | ${s.entry_class} | Phase: ${s.phase}\n ${whale} | ${dolph} | Regime: ${s.regime || 'N/A'}`;
32
- }).filter(Boolean),
33
- '', 'Source: CryptoIZ Alpha Entry Scanner | cryptoiz.org',
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
+ ),
99
+ '',
100
+ '📊 cryptoiz.org | @cryptoiz_IDN',
34
101
  ];
35
102
  return lines.join('\n');
36
103
  }
37
104
 
38
105
  function formatDivergence(data) {
39
- if (!data.signals || data.signals.length === 0) return `Tidak ada divergence signal (${data.timeframe}).`;
106
+ if (data.error) return `ERROR: ${data.error}\n${data.hint || ''}`;
107
+ if (!data.signals?.length) return `Tidak ada divergence signal (${data.timeframe}).`;
40
108
  const lines = [
41
- `CRYPTOIZ DIVERGENCE SCANNER — ${data.timeframe?.toUpperCase()}`,
42
- `Tier: ${data.tier.toUpperCase()} | Total: ${data.total} | ${new Date(data.fetched_at).toUTCString()}`,
109
+ `CRYPTOIZ DIVERGENCE — ${data.timeframe?.toUpperCase()}`,
110
+ `Total: ${data.total} | Powered by cryptoiz.org`,
43
111
  '',
44
112
  ...data.signals.map((s, i) => {
45
- const type = s.divergence_type === 'bullish' ? 'BULLISH' : s.divergence_type === 'bearish' ? 'BEARISH' : (s.divergence_type||'').toUpperCase();
46
- const score = s.divergence_score ? `Score: ${s.divergence_score.toFixed(1)}` : '';
47
- const conf = s.confidence_score ? `Conf: ${s.confidence_score.toFixed(1)}` : '';
48
- const mc = s.market_cap_usd ? `MC: $${(s.market_cap_usd/1000).toFixed(0)}K` : '';
49
- const chg = s.price_change_pct ? `${s.price_change_pct>0?'+':''}${s.price_change_pct.toFixed(1)}%` : '';
50
- return `${i+1}. ${s.token||s.symbol} [${type}]\n ${[score,conf,mc,chg].filter(Boolean).join(' | ')}`;
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`;
51
115
  }),
52
- '', 'Source: CryptoIZ Divergence Engine | cryptoiz.org',
116
+ '',
117
+ '📊 cryptoiz.org | @cryptoiz_IDN',
53
118
  ];
54
119
  return lines.join('\n');
55
120
  }
56
121
 
57
- function formatAccumulation(data) {
58
- if (!data.tokens || data.tokens.length === 0) return 'Tidak ada token dalam mode akumulasi saat ini.';
122
+ function formatAccum(data) {
123
+ if (data.error) return `ERROR: ${data.error}\n${data.hint || ''}`;
124
+ if (!data.tokens?.length) return 'Tidak ada token dalam fase akumulasi.';
59
125
  const lines = [
60
126
  `CRYPTOIZ AKUMULASI`,
61
- `Tier: ${data.tier.toUpperCase()} | Total: ${data.total} | ${new Date(data.fetched_at).toUTCString()}`,
62
- data.note ? `INFO: ${data.note}` : '',
127
+ `Total: ${data.total} | Powered by cryptoiz.org`,
63
128
  '',
64
- ...data.tokens.map((t, i) => {
65
- if (data.tier === 'free') return `${i+1}. ${t.token} ${t.strength} | ${t.accdist}`;
66
- const s = t.scores;
67
- return `${i+1}. ${t.token} — Composite: ${s.composite?.toFixed(1)||'N/A'} | ${t.strength}\n Structure: ${s.structure?.toFixed(1)||'-'} | AccDist: ${s.accdist?.toFixed(0)||'-'} | Holder: ${s.holder?.toFixed(1)||'-'} | Market: ${s.market?.toFixed(1)||'-'}`;
68
- }).filter(Boolean),
69
- '', 'Source: CryptoIZ Accumulation Dashboard | cryptoiz.org',
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
+ ),
132
+ '',
133
+ '📊 cryptoiz.org | @cryptoiz_IDN',
70
134
  ];
71
135
  return lines.join('\n');
72
136
  }
73
137
 
74
138
  function formatStatus() {
139
+ const hasKey = !!SVM_PRIVATE_KEY;
75
140
  return [
76
- `CryptoIZ MCP Server v1.1.0`, `Status : CONNECTED`,
77
- `Tier : ${API_KEY ? 'Basic/Pro (API key terdeteksi)' : 'Free (tidak ada API key)'}`,
78
- ``, `Tools tersedia:`,
79
- ` 1. get_alpha_scanner — Sinyal entry terbaik (Alpha Scanner)`,
80
- ` 2. get_divergence — Divergence bullish/bearish 4h atau 1d`,
81
- ` 3. get_accumulation Token fase akumulasi (Accum Dashboard)`,
82
- ` 4. get_status Info koneksi & tier`,
83
- ``, `Upgrade: cryptoiz.org/Pricing`,
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'}`,
144
+ ``,
145
+ `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`,
150
+ ``,
151
+ `Harga : $0.01 USDC per call (Solana)`,
152
+ `Wallet : DsKmdkYx49Xc1WhqMUAztwhdYPTqieyC98VmnnJdgpXX`,
153
+ `Info : cryptoiz.org | @cryptoiz_IDN`,
84
154
  ].join('\n');
85
155
  }
86
156
 
157
+ // ─── Tools definition ─────────────────────────────────────────────────────────
158
+
87
159
  const TOOLS = [
88
160
  {
89
161
  name: 'get_alpha_scanner',
90
- description: 'Sinyal dari CryptoIZ Alpha Entry Scanner token Solana dengan akumulasi terkuat berdasarkan alpha_score. Free: top 3. Basic/Pro: full list + detail.',
91
- inputSchema: { type: 'object', properties: {
92
- min_score: { type: 'number', description: 'Filter minimum alpha score (0-100).' },
93
- entry_class: { type: 'string', enum: ['ALPHA_BUILDING','ALPHA_EARLY','WATCHLIST_ONLY'], description: 'Filter berdasarkan entry class.' },
94
- }, required: [] },
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
+ },
95
170
  },
96
171
  {
97
172
  name: 'get_divergence',
98
- description: 'Sinyal divergence dari CryptoIZ — deteksi bullish/bearish divergence antara harga dan aktivitas whale/dolphin. Butuh API key Basic/Pro.',
99
- inputSchema: { type: 'object', properties: {
100
- timeframe: { type: 'string', enum: ['4h','1d'], description: 'Timeframe. Default: 4h.' },
101
- limit: { type: 'number', description: 'Max hasil (1-50). Default: 20.' },
102
- }, required: [] },
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
+ },
103
182
  },
104
183
  {
105
184
  name: 'get_accumulation',
106
- description: 'Token dalam fase akumulasi dari CryptoIZ Accumulation Dashboard menggunakan composite_score (Structure + AccDist + Holder + Market). Identik dengan tab Akumulasi di dashboard.',
107
- inputSchema: { type: 'object', properties: {
108
- min_composite: { type: 'number', description: 'Filter minimum composite score (0-100).' },
109
- }, required: [] },
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
+ },
110
193
  },
111
194
  {
112
195
  name: 'get_status',
113
- description: 'Cek status koneksi CryptoIZ MCP dan daftar tools tersedia.',
196
+ description: 'Cek status koneksi CryptoIZ MCP, payment setup, dan tools yang tersedia.',
114
197
  inputSchema: { type: 'object', properties: {}, required: [] },
115
198
  },
116
199
  ];
117
200
 
201
+ // ─── Server ───────────────────────────────────────────────────────────────────
202
+
118
203
  const server = new Server(
119
- { name: 'cryptoiz-mcp', version: '1.1.0' },
204
+ { name: 'cryptoiz-mcp', version: '2.0.0' },
120
205
  { capabilities: { tools: {} } }
121
206
  );
122
207
 
123
208
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
209
+
124
210
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
125
211
  const { name, arguments: args } = request.params;
212
+
126
213
  try {
127
- if (name === 'get_status') return { content: [{ type: 'text', text: formatStatus() }] };
214
+ if (name === 'get_status') {
215
+ return { content: [{ type: 'text', text: formatStatus() }] };
216
+ }
217
+
128
218
  if (name === 'get_alpha_scanner') {
129
- const data = await callApi('mcp-accum-signals');
130
- if (args?.min_score && data.tier !== 'free' && data.signals) { data.signals = data.signals.filter(s => s.alpha_score >= args.min_score); data.total = data.signals.length; }
131
- if (args?.entry_class && data.tier !== 'free' && data.signals) { data.signals = data.signals.filter(s => s.entry_class === args.entry_class); data.total = data.signals.length; }
132
- return { content: [{ type: 'text', text: formatAlphaScanner(data) }] };
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
+ }
225
+ return { content: [{ type: 'text', text: formatAlpha(data) }] };
133
226
  }
227
+
134
228
  if (name === 'get_divergence') {
135
- const params = {};
136
- if (args?.timeframe) params.tf = args.timeframe;
137
- if (args?.limit) params.limit = String(args.limit);
138
- const data = await callApi('mcp-divergence-signals', params);
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}`);
139
233
  return { content: [{ type: 'text', text: formatDivergence(data) }] };
140
234
  }
235
+
141
236
  if (name === 'get_accumulation') {
142
- const data = await callApi('mcp-accumulation');
143
- if (args?.min_composite && data.tier !== 'free' && data.tokens) { data.tokens = data.tokens.filter(t => t.scores?.composite >= args.min_composite); data.total = data.tokens.length; }
144
- return { content: [{ type: 'text', text: formatAccumulation(data) }] };
237
+ 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
+ }
242
+ return { content: [{ type: 'text', text: formatAccum(data) }] };
145
243
  }
244
+
146
245
  return { content: [{ type: 'text', text: `Tool tidak dikenal: ${name}` }], isError: true };
246
+
147
247
  } catch (err) {
148
248
  return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
149
249
  }