@aicryptorisk/mcp-server 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/README.md +133 -0
- package/index.js +370 -0
- package/package.json +34 -0
- package/schema.sql +89 -0
- package/setup.js +98 -0
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# AI Crypto Risk MCP Server
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server that provides AI assistants with cryptocurrency risk analysis capabilities.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Token Risk Analysis**: Analyze any crypto token for scam indicators
|
|
8
|
+
- **Deep Research**: Request comprehensive 4-pillar research reports
|
|
9
|
+
- **Multi-Chain Support**: Ethereum, BSC, Polygon, Arbitrum, Base, Solana
|
|
10
|
+
- **Usage Metering**: Built-in API key authentication and usage tracking
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# From npm (when published)
|
|
16
|
+
npm install -g @aicryptorisk/mcp-server
|
|
17
|
+
|
|
18
|
+
# Or from source
|
|
19
|
+
cd mcp-server
|
|
20
|
+
npm install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
### For Claude Desktop
|
|
26
|
+
|
|
27
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on Mac or `%APPDATA%\Claude\claude_desktop_config.json` on Windows):
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"aicryptorisk": {
|
|
33
|
+
"command": "node",
|
|
34
|
+
"args": ["/path/to/mcp-server/index.js"],
|
|
35
|
+
"env": {
|
|
36
|
+
"AICRYPTORISK_API_KEY": "your-api-key-here"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### For Claude Code
|
|
44
|
+
|
|
45
|
+
Add to your project's `.claude/settings.json`:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"mcpServers": {
|
|
50
|
+
"aicryptorisk": {
|
|
51
|
+
"command": "node",
|
|
52
|
+
"args": ["./mcp-server/index.js"],
|
|
53
|
+
"env": {
|
|
54
|
+
"AICRYPTORISK_API_KEY": "your-api-key-here"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Available Tools
|
|
62
|
+
|
|
63
|
+
### `analyze_crypto_risk`
|
|
64
|
+
|
|
65
|
+
Analyze a token for scam indicators and risk factors.
|
|
66
|
+
|
|
67
|
+
**Input:**
|
|
68
|
+
- `contract_address` (required): Token contract address
|
|
69
|
+
- `chain` (optional): Blockchain network (default: ethereum)
|
|
70
|
+
- `coin_name` (optional): Name for better context
|
|
71
|
+
|
|
72
|
+
**Returns:**
|
|
73
|
+
- Risk score (0-100)
|
|
74
|
+
- Risk level (Low/Medium/High/Critical)
|
|
75
|
+
- Red flags and warnings
|
|
76
|
+
- Recommendations
|
|
77
|
+
|
|
78
|
+
### `request_deep_research`
|
|
79
|
+
|
|
80
|
+
Request comprehensive 4-pillar analysis (delivered via email).
|
|
81
|
+
|
|
82
|
+
**Input:**
|
|
83
|
+
- `contract_address` (required): Token contract address
|
|
84
|
+
- `chain` (required): Blockchain network
|
|
85
|
+
- `coin_name` (required): Token name
|
|
86
|
+
- `symbol` (optional): Token symbol
|
|
87
|
+
- `email` (required): Email for report delivery
|
|
88
|
+
|
|
89
|
+
### `get_supported_chains`
|
|
90
|
+
|
|
91
|
+
Get list of supported blockchain networks.
|
|
92
|
+
|
|
93
|
+
## Pricing Tiers
|
|
94
|
+
|
|
95
|
+
| Tier | Daily Limit | Price |
|
|
96
|
+
|------|-------------|-------|
|
|
97
|
+
| Free | 10 scans | $0 |
|
|
98
|
+
| Pro | 100 scans | $19/mo |
|
|
99
|
+
| Enterprise | Unlimited | Contact us |
|
|
100
|
+
|
|
101
|
+
Get your API key at: https://aicryptorisk.com/api-keys
|
|
102
|
+
|
|
103
|
+
## Example Usage
|
|
104
|
+
|
|
105
|
+
Once configured, you can ask Claude:
|
|
106
|
+
|
|
107
|
+
> "Analyze this token for scam risk: 0x1234...abcd on ethereum"
|
|
108
|
+
|
|
109
|
+
> "Research this new memecoin PEPE2 at 0xabc... on BSC and send the report to my@email.com"
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Install dependencies
|
|
115
|
+
npm install
|
|
116
|
+
|
|
117
|
+
# Run locally (dev mode, no auth required)
|
|
118
|
+
npm start
|
|
119
|
+
|
|
120
|
+
# With auth (production)
|
|
121
|
+
SUPABASE_URL=your-url \
|
|
122
|
+
SUPABASE_SERVICE_ROLE_KEY=your-key \
|
|
123
|
+
AICRYPTORISK_API_KEY=test-key \
|
|
124
|
+
npm start
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Database Setup
|
|
128
|
+
|
|
129
|
+
Run `schema.sql` in your Supabase SQL editor to create the required tables for API key management and usage tracking.
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { createClient } from "@supabase/supabase-js";
|
|
10
|
+
import crypto from "crypto";
|
|
11
|
+
|
|
12
|
+
// Configuration from environment
|
|
13
|
+
const SUPABASE_URL = process.env.SUPABASE_URL;
|
|
14
|
+
const SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
15
|
+
const API_KEY = process.env.AICRYPTORISK_API_KEY;
|
|
16
|
+
const API_BASE_URL = process.env.API_BASE_URL || "https://aicryptorisk.com";
|
|
17
|
+
|
|
18
|
+
// Initialize Supabase client for auth and usage tracking
|
|
19
|
+
const supabase = SUPABASE_URL && SUPABASE_SERVICE_ROLE_KEY
|
|
20
|
+
? createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)
|
|
21
|
+
: null;
|
|
22
|
+
|
|
23
|
+
// Supported chains
|
|
24
|
+
const SUPPORTED_CHAINS = {
|
|
25
|
+
ethereum: "1",
|
|
26
|
+
bsc: "56",
|
|
27
|
+
polygon: "137",
|
|
28
|
+
arbitrum: "42161",
|
|
29
|
+
base: "8453",
|
|
30
|
+
solana: "solana",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Validate API key and check usage limits
|
|
35
|
+
*/
|
|
36
|
+
async function validateApiKey(apiKey) {
|
|
37
|
+
if (!apiKey) {
|
|
38
|
+
return { valid: false, error: "API key required. Get one at https://aicryptorisk.com/api-keys" };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!supabase) {
|
|
42
|
+
// Local development mode - accept any key
|
|
43
|
+
console.error("[MCP] Warning: Supabase not configured, skipping auth");
|
|
44
|
+
return { valid: true, userId: "dev-mode", tier: "unlimited" };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// Check API key in database
|
|
49
|
+
const { data: keyData, error } = await supabase
|
|
50
|
+
.from("api_keys")
|
|
51
|
+
.select("id, user_id, tier, calls_today, daily_limit, is_active")
|
|
52
|
+
.eq("key_hash", hashApiKey(apiKey))
|
|
53
|
+
.single();
|
|
54
|
+
|
|
55
|
+
if (error || !keyData) {
|
|
56
|
+
return { valid: false, error: "Invalid API key" };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!keyData.is_active) {
|
|
60
|
+
return { valid: false, error: "API key is disabled" };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (keyData.daily_limit && keyData.calls_today >= keyData.daily_limit) {
|
|
64
|
+
return {
|
|
65
|
+
valid: false,
|
|
66
|
+
error: `Daily limit reached (${keyData.daily_limit} calls). Upgrade at https://aicryptorisk.com/pricing`
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
valid: true,
|
|
72
|
+
userId: keyData.user_id,
|
|
73
|
+
tier: keyData.tier,
|
|
74
|
+
keyId: keyData.id
|
|
75
|
+
};
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error("[MCP] Auth error:", err.message);
|
|
78
|
+
return { valid: false, error: "Authentication service unavailable" };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Hash API key for secure storage lookup (SHA-256)
|
|
84
|
+
*/
|
|
85
|
+
function hashApiKey(key) {
|
|
86
|
+
return crypto.createHash("sha256").update(key).digest("hex");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Track API usage for billing
|
|
91
|
+
*/
|
|
92
|
+
async function trackUsage(keyId, tool, success) {
|
|
93
|
+
if (!supabase || !keyId) return;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Increment daily counter
|
|
97
|
+
await supabase.rpc("increment_api_calls", { key_id: keyId });
|
|
98
|
+
|
|
99
|
+
// Log detailed usage
|
|
100
|
+
await supabase.from("api_usage_log").insert({
|
|
101
|
+
api_key_id: keyId,
|
|
102
|
+
tool_name: tool,
|
|
103
|
+
success,
|
|
104
|
+
timestamp: new Date().toISOString(),
|
|
105
|
+
});
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error("[MCP] Usage tracking error:", err.message);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Analyze a crypto token for risk indicators
|
|
113
|
+
*/
|
|
114
|
+
async function analyzeToken(contractAddress, chain, coinName) {
|
|
115
|
+
const chainId = SUPPORTED_CHAINS[chain.toLowerCase()] || chain;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
// Call our MCP-specific API endpoint (uses API key auth)
|
|
119
|
+
const response = await fetch(`${API_BASE_URL}/api/scam-likely/mcp`, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
"X-API-Key": API_KEY,
|
|
124
|
+
},
|
|
125
|
+
body: JSON.stringify({
|
|
126
|
+
contractAddress,
|
|
127
|
+
chainId,
|
|
128
|
+
coinName: coinName || undefined,
|
|
129
|
+
}),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
const error = await response.text();
|
|
134
|
+
throw new Error(`API error: ${response.status} - ${error}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return await response.json();
|
|
138
|
+
} catch (err) {
|
|
139
|
+
throw new Error(`Failed to analyze token: ${err.message}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Request deep research on a token (async, results sent via email)
|
|
145
|
+
*/
|
|
146
|
+
async function requestDeepResearch(contractAddress, chain, coinName, symbol, email) {
|
|
147
|
+
try {
|
|
148
|
+
const response = await fetch(`${API_BASE_URL}/api/scam-likely/research`, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
},
|
|
153
|
+
body: JSON.stringify({
|
|
154
|
+
contractAddress,
|
|
155
|
+
chain,
|
|
156
|
+
coinName,
|
|
157
|
+
symbol,
|
|
158
|
+
email,
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (!response.ok) {
|
|
163
|
+
const error = await response.text();
|
|
164
|
+
throw new Error(`Research API error: ${response.status} - ${error}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return await response.json();
|
|
168
|
+
} catch (err) {
|
|
169
|
+
throw new Error(`Failed to request research: ${err.message}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Create MCP server
|
|
174
|
+
const server = new Server(
|
|
175
|
+
{
|
|
176
|
+
name: "aicryptorisk",
|
|
177
|
+
version: "1.0.0",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
capabilities: {
|
|
181
|
+
tools: {},
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// List available tools
|
|
187
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
188
|
+
return {
|
|
189
|
+
tools: [
|
|
190
|
+
{
|
|
191
|
+
name: "analyze_crypto_risk",
|
|
192
|
+
description: `Analyze a cryptocurrency token for scam indicators and risk factors.
|
|
193
|
+
|
|
194
|
+
Returns a comprehensive risk analysis including:
|
|
195
|
+
- Overall risk score (0-100, higher = more risky)
|
|
196
|
+
- Risk level (Low/Medium/High/Critical)
|
|
197
|
+
- On-chain risk indicators (honeypot, ownership, liquidity)
|
|
198
|
+
- Specific red flags and warnings
|
|
199
|
+
- Actionable recommendations
|
|
200
|
+
|
|
201
|
+
Supports Ethereum, BSC, Polygon, Arbitrum, Base, and Solana.`,
|
|
202
|
+
inputSchema: {
|
|
203
|
+
type: "object",
|
|
204
|
+
properties: {
|
|
205
|
+
contract_address: {
|
|
206
|
+
type: "string",
|
|
207
|
+
description: "The token contract address to analyze",
|
|
208
|
+
},
|
|
209
|
+
chain: {
|
|
210
|
+
type: "string",
|
|
211
|
+
enum: ["ethereum", "bsc", "polygon", "arbitrum", "base", "solana"],
|
|
212
|
+
description: "The blockchain network (default: ethereum)",
|
|
213
|
+
},
|
|
214
|
+
coin_name: {
|
|
215
|
+
type: "string",
|
|
216
|
+
description: "Optional: Name of the coin for better context",
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
required: ["contract_address"],
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: "request_deep_research",
|
|
224
|
+
description: `Request comprehensive 4-pillar research on a cryptocurrency token.
|
|
225
|
+
|
|
226
|
+
This triggers an in-depth analysis covering:
|
|
227
|
+
1. On-Chain Analysis - Contract security, liquidity, holder distribution
|
|
228
|
+
2. Social Media Intelligence - Community sentiment, influencer activity
|
|
229
|
+
3. Institutional Interest - VC backing, exchange listings, partnerships
|
|
230
|
+
4. Off-Chain Intelligence - Team background, legal status, news
|
|
231
|
+
|
|
232
|
+
Results are delivered via email within 5-10 minutes.`,
|
|
233
|
+
inputSchema: {
|
|
234
|
+
type: "object",
|
|
235
|
+
properties: {
|
|
236
|
+
contract_address: {
|
|
237
|
+
type: "string",
|
|
238
|
+
description: "The token contract address to research",
|
|
239
|
+
},
|
|
240
|
+
chain: {
|
|
241
|
+
type: "string",
|
|
242
|
+
enum: ["ethereum", "bsc", "polygon", "arbitrum", "base", "solana"],
|
|
243
|
+
description: "The blockchain network",
|
|
244
|
+
},
|
|
245
|
+
coin_name: {
|
|
246
|
+
type: "string",
|
|
247
|
+
description: "Name of the coin",
|
|
248
|
+
},
|
|
249
|
+
symbol: {
|
|
250
|
+
type: "string",
|
|
251
|
+
description: "Token symbol (e.g., ETH, BTC)",
|
|
252
|
+
},
|
|
253
|
+
email: {
|
|
254
|
+
type: "string",
|
|
255
|
+
description: "Email address to receive the research report",
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
required: ["contract_address", "chain", "coin_name", "email"],
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "get_supported_chains",
|
|
263
|
+
description: "Get list of supported blockchain networks for token analysis",
|
|
264
|
+
inputSchema: {
|
|
265
|
+
type: "object",
|
|
266
|
+
properties: {},
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
};
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Handle tool calls
|
|
274
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
275
|
+
const { name, arguments: args } = request.params;
|
|
276
|
+
|
|
277
|
+
// Validate API key
|
|
278
|
+
const auth = await validateApiKey(API_KEY);
|
|
279
|
+
if (!auth.valid) {
|
|
280
|
+
return {
|
|
281
|
+
content: [
|
|
282
|
+
{
|
|
283
|
+
type: "text",
|
|
284
|
+
text: JSON.stringify({ error: auth.error }, null, 2),
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
isError: true,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
let result;
|
|
293
|
+
|
|
294
|
+
switch (name) {
|
|
295
|
+
case "analyze_crypto_risk": {
|
|
296
|
+
const { contract_address, chain = "ethereum", coin_name } = args;
|
|
297
|
+
|
|
298
|
+
if (!contract_address) {
|
|
299
|
+
throw new Error("contract_address is required");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
result = await analyzeToken(contract_address, chain, coin_name);
|
|
303
|
+
await trackUsage(auth.keyId, "analyze_crypto_risk", true);
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
case "request_deep_research": {
|
|
308
|
+
const { contract_address, chain, coin_name, symbol, email } = args;
|
|
309
|
+
|
|
310
|
+
if (!contract_address || !chain || !coin_name || !email) {
|
|
311
|
+
throw new Error("contract_address, chain, coin_name, and email are required");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
result = await requestDeepResearch(contract_address, chain, coin_name, symbol, email);
|
|
315
|
+
await trackUsage(auth.keyId, "request_deep_research", true);
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
case "get_supported_chains": {
|
|
320
|
+
result = {
|
|
321
|
+
chains: Object.keys(SUPPORTED_CHAINS),
|
|
322
|
+
details: {
|
|
323
|
+
ethereum: { chainId: "1", name: "Ethereum Mainnet" },
|
|
324
|
+
bsc: { chainId: "56", name: "BNB Smart Chain" },
|
|
325
|
+
polygon: { chainId: "137", name: "Polygon" },
|
|
326
|
+
arbitrum: { chainId: "42161", name: "Arbitrum One" },
|
|
327
|
+
base: { chainId: "8453", name: "Base" },
|
|
328
|
+
solana: { chainId: "solana", name: "Solana" },
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
default:
|
|
335
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
content: [
|
|
340
|
+
{
|
|
341
|
+
type: "text",
|
|
342
|
+
text: JSON.stringify(result, null, 2),
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
};
|
|
346
|
+
} catch (error) {
|
|
347
|
+
await trackUsage(auth.keyId, name, false);
|
|
348
|
+
return {
|
|
349
|
+
content: [
|
|
350
|
+
{
|
|
351
|
+
type: "text",
|
|
352
|
+
text: JSON.stringify({ error: error.message }, null, 2),
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
isError: true,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Start the server
|
|
361
|
+
async function main() {
|
|
362
|
+
const transport = new StdioServerTransport();
|
|
363
|
+
await server.connect(transport);
|
|
364
|
+
console.error("[MCP] AI Crypto Risk server running");
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
main().catch((error) => {
|
|
368
|
+
console.error("[MCP] Fatal error:", error);
|
|
369
|
+
process.exit(1);
|
|
370
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aicryptorisk/mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP Server for AI Crypto Risk Analysis - Analyze tokens for scam indicators",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"aicryptorisk-mcp": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js",
|
|
12
|
+
"setup": "node setup.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
16
|
+
"@supabase/supabase-js": "^2.58.0"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"crypto",
|
|
21
|
+
"risk",
|
|
22
|
+
"scam",
|
|
23
|
+
"analysis",
|
|
24
|
+
"ai",
|
|
25
|
+
"claude"
|
|
26
|
+
],
|
|
27
|
+
"author": "AICryptoRisk",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/djangamane/montecrypto"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://aicryptorisk.com"
|
|
34
|
+
}
|
package/schema.sql
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
-- API Keys table for MCP server authentication
|
|
2
|
+
-- Run this in your Supabase SQL editor
|
|
3
|
+
|
|
4
|
+
-- API Keys table
|
|
5
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
6
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
7
|
+
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
8
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
9
|
+
key_prefix TEXT NOT NULL, -- First 8 chars for display (e.g., "acr_1a2b...")
|
|
10
|
+
name TEXT NOT NULL DEFAULT 'Default',
|
|
11
|
+
tier TEXT NOT NULL DEFAULT 'free', -- free, pro, enterprise
|
|
12
|
+
daily_limit INTEGER DEFAULT 10, -- null = unlimited
|
|
13
|
+
calls_today INTEGER DEFAULT 0,
|
|
14
|
+
calls_total INTEGER DEFAULT 0,
|
|
15
|
+
is_active BOOLEAN DEFAULT true,
|
|
16
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
17
|
+
last_used_at TIMESTAMPTZ,
|
|
18
|
+
last_reset_at DATE DEFAULT CURRENT_DATE
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
-- Usage log for billing and analytics
|
|
22
|
+
CREATE TABLE IF NOT EXISTS api_usage_log (
|
|
23
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
24
|
+
api_key_id UUID REFERENCES api_keys(id) ON DELETE SET NULL,
|
|
25
|
+
tool_name TEXT NOT NULL,
|
|
26
|
+
success BOOLEAN NOT NULL,
|
|
27
|
+
timestamp TIMESTAMPTZ DEFAULT NOW(),
|
|
28
|
+
metadata JSONB DEFAULT '{}'
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
-- Index for fast lookups
|
|
32
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash);
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_user ON api_keys(user_id);
|
|
34
|
+
CREATE INDEX IF NOT EXISTS idx_usage_log_key ON api_usage_log(api_key_id);
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_usage_log_timestamp ON api_usage_log(timestamp);
|
|
36
|
+
|
|
37
|
+
-- Function to increment API calls (atomic)
|
|
38
|
+
CREATE OR REPLACE FUNCTION increment_api_calls(key_id UUID)
|
|
39
|
+
RETURNS void AS $$
|
|
40
|
+
BEGIN
|
|
41
|
+
UPDATE api_keys
|
|
42
|
+
SET
|
|
43
|
+
calls_today = CASE
|
|
44
|
+
WHEN last_reset_at < CURRENT_DATE THEN 1
|
|
45
|
+
ELSE calls_today + 1
|
|
46
|
+
END,
|
|
47
|
+
calls_total = calls_total + 1,
|
|
48
|
+
last_used_at = NOW(),
|
|
49
|
+
last_reset_at = CURRENT_DATE
|
|
50
|
+
WHERE id = key_id;
|
|
51
|
+
END;
|
|
52
|
+
$$ LANGUAGE plpgsql;
|
|
53
|
+
|
|
54
|
+
-- RLS Policies
|
|
55
|
+
ALTER TABLE api_keys ENABLE ROW LEVEL SECURITY;
|
|
56
|
+
ALTER TABLE api_usage_log ENABLE ROW LEVEL SECURITY;
|
|
57
|
+
|
|
58
|
+
-- Users can only see their own API keys
|
|
59
|
+
CREATE POLICY "Users can view own api_keys" ON api_keys
|
|
60
|
+
FOR SELECT USING (auth.uid() = user_id);
|
|
61
|
+
|
|
62
|
+
CREATE POLICY "Users can insert own api_keys" ON api_keys
|
|
63
|
+
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
64
|
+
|
|
65
|
+
CREATE POLICY "Users can update own api_keys" ON api_keys
|
|
66
|
+
FOR UPDATE USING (auth.uid() = user_id);
|
|
67
|
+
|
|
68
|
+
CREATE POLICY "Users can delete own api_keys" ON api_keys
|
|
69
|
+
FOR DELETE USING (auth.uid() = user_id);
|
|
70
|
+
|
|
71
|
+
-- Users can view their own usage
|
|
72
|
+
CREATE POLICY "Users can view own usage" ON api_usage_log
|
|
73
|
+
FOR SELECT USING (
|
|
74
|
+
api_key_id IN (SELECT id FROM api_keys WHERE user_id = auth.uid())
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
-- Service role can do everything (for the MCP server)
|
|
78
|
+
CREATE POLICY "Service role full access api_keys" ON api_keys
|
|
79
|
+
FOR ALL USING (auth.role() = 'service_role');
|
|
80
|
+
|
|
81
|
+
CREATE POLICY "Service role full access usage" ON api_usage_log
|
|
82
|
+
FOR ALL USING (auth.role() = 'service_role');
|
|
83
|
+
|
|
84
|
+
-- Tier configurations (for reference)
|
|
85
|
+
COMMENT ON TABLE api_keys IS 'Tier limits:
|
|
86
|
+
- free: 10 calls/day
|
|
87
|
+
- pro: 100 calls/day
|
|
88
|
+
- enterprise: unlimited (null daily_limit)
|
|
89
|
+
';
|
package/setup.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Server Setup Helper
|
|
5
|
+
* Helps users configure Claude Desktop to use the AI Crypto Risk MCP server
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import readline from "readline";
|
|
12
|
+
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
|
|
15
|
+
const rl = readline.createInterface({
|
|
16
|
+
input: process.stdin,
|
|
17
|
+
output: process.stdout,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
function prompt(question) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
rl.question(question, resolve);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Detect platform and config path
|
|
27
|
+
function getClaudeConfigPath() {
|
|
28
|
+
const platform = process.platform;
|
|
29
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
30
|
+
|
|
31
|
+
if (platform === "darwin") {
|
|
32
|
+
return path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
33
|
+
} else if (platform === "win32") {
|
|
34
|
+
return path.join(process.env.APPDATA, "Claude", "claude_desktop_config.json");
|
|
35
|
+
} else {
|
|
36
|
+
return path.join(home, ".config", "claude", "claude_desktop_config.json");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function main() {
|
|
41
|
+
console.log("\nš AI Crypto Risk MCP Server Setup\n");
|
|
42
|
+
console.log("This will configure Claude Desktop to use the crypto risk analysis tools.\n");
|
|
43
|
+
|
|
44
|
+
// Get API key
|
|
45
|
+
const apiKey = await prompt("Enter your API key (get one at https://aicryptorisk.com/api-keys): ");
|
|
46
|
+
|
|
47
|
+
if (!apiKey || !apiKey.startsWith("acr_")) {
|
|
48
|
+
console.log("\nā Invalid API key. Keys should start with 'acr_'");
|
|
49
|
+
rl.close();
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const configPath = getClaudeConfigPath();
|
|
54
|
+
const serverPath = path.resolve(__dirname, "index.js");
|
|
55
|
+
|
|
56
|
+
// Read existing config or create new
|
|
57
|
+
let config = { mcpServers: {} };
|
|
58
|
+
try {
|
|
59
|
+
if (fs.existsSync(configPath)) {
|
|
60
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
61
|
+
config.mcpServers = config.mcpServers || {};
|
|
62
|
+
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.log("Creating new Claude config...");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Add our server
|
|
68
|
+
config.mcpServers.aicryptorisk = {
|
|
69
|
+
command: "node",
|
|
70
|
+
args: [serverPath],
|
|
71
|
+
env: {
|
|
72
|
+
AICRYPTORISK_API_KEY: apiKey,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Ensure directory exists
|
|
77
|
+
const configDir = path.dirname(configPath);
|
|
78
|
+
if (!fs.existsSync(configDir)) {
|
|
79
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Write config
|
|
83
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
84
|
+
|
|
85
|
+
console.log(`\nā
Configuration saved to: ${configPath}`);
|
|
86
|
+
console.log("\nš Next steps:");
|
|
87
|
+
console.log(" 1. Restart Claude Desktop");
|
|
88
|
+
console.log(" 2. You can now ask Claude to analyze crypto tokens!");
|
|
89
|
+
console.log("\nš” Try: \"Analyze this token for scam risk: 0x...\"");
|
|
90
|
+
|
|
91
|
+
rl.close();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
main().catch((err) => {
|
|
95
|
+
console.error("Setup failed:", err.message);
|
|
96
|
+
rl.close();
|
|
97
|
+
process.exit(1);
|
|
98
|
+
});
|