@fundtracer/mcp 1.0.0 → 1.0.1

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/fundtracer-mcp.js +8 -386
  2. package/package.json +1 -1
package/fundtracer-mcp.js CHANGED
@@ -1,12 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
- }) : x)(function(x) {
7
- if (typeof require !== "undefined") return require.apply(this, arguments);
8
- throw Error('Dynamic require of "' + x + '" is not supported');
9
- });
10
4
  var __esm = (fn, res) => function __init() {
11
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
6
  };
@@ -15,133 +9,6 @@ var __export = (target, all) => {
15
9
  __defProp(target, name, { get: all[name], enumerable: true });
16
10
  };
17
11
 
18
- // src/firebase.ts
19
- var firebase_exports = {};
20
- __export(firebase_exports, {
21
- admin: () => admin,
22
- getAuth: () => getAuth,
23
- getFirestore: () => getFirestore,
24
- getUserByAddress: () => getUserByAddress,
25
- initializeFirebase: () => initializeFirebase,
26
- updateUserTier: () => updateUserTier
27
- });
28
- import admin from "firebase-admin";
29
- import fs from "fs";
30
- function initializeFirebase() {
31
- if (firebaseInitialized) {
32
- console.log("[Firebase] Already initialized (skipping)");
33
- return;
34
- }
35
- try {
36
- if (admin.apps.length > 0) {
37
- console.log("[Firebase] Already initialized by Firebase auto-discovery");
38
- firebaseInitialized = true;
39
- return;
40
- }
41
- } catch (e) {
42
- }
43
- console.log("[Firebase] Starting initialization...");
44
- const base64ServiceAccount = process.env.FIREBASE_SERVICE_ACCOUNT_BASE64;
45
- if (base64ServiceAccount) {
46
- try {
47
- console.log("[Firebase] Found base64 service account, decoding...");
48
- const decoded = Buffer.from(base64ServiceAccount, "base64").toString("utf-8");
49
- const serviceAccount = JSON.parse(decoded);
50
- admin.initializeApp({
51
- credential: admin.credential.cert(serviceAccount)
52
- });
53
- firebaseInitialized = true;
54
- console.log("[Firebase] \u2705 Initialized from base64 service account");
55
- return;
56
- } catch (error) {
57
- console.error("[Firebase] Failed to decode base64 service account:", error);
58
- }
59
- }
60
- const credentialsPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
61
- console.log("[Firebase] Checking credentials file path:", credentialsPath || "(not set)");
62
- if (credentialsPath && fs.existsSync(credentialsPath)) {
63
- try {
64
- const serviceAccount = JSON.parse(fs.readFileSync(credentialsPath, "utf8"));
65
- admin.initializeApp({
66
- credential: admin.credential.cert(serviceAccount)
67
- });
68
- firebaseInitialized = true;
69
- console.log("[Firebase] \u2705 Initialized from credentials file");
70
- return;
71
- } catch (error) {
72
- console.error("[Firebase] Failed to load credentials file:", error);
73
- }
74
- }
75
- const projectId = process.env.FIREBASE_PROJECT_ID;
76
- const clientEmail = process.env.FIREBASE_CLIENT_EMAIL;
77
- let privateKey = process.env.FIREBASE_PRIVATE_KEY;
78
- console.log("[Firebase] Checking env vars...");
79
- console.log("[Firebase] Project ID:", projectId ? "\u2713 set" : "\u2717 missing");
80
- console.log("[Firebase] Client Email:", clientEmail ? "\u2713 set" : "\u2717 missing");
81
- console.log("[Firebase] Private Key:", privateKey ? `\u2713 set (${privateKey.length} chars)` : "\u2717 missing");
82
- if (privateKey && !privateKey.includes("-----BEGIN")) {
83
- try {
84
- console.log("[Firebase] Decoding base64 private key...");
85
- privateKey = Buffer.from(privateKey, "base64").toString("utf-8");
86
- } catch (e) {
87
- console.warn("[Firebase] Failed to decode base64 private key, using as-is");
88
- }
89
- }
90
- privateKey = privateKey?.replace(/\\n/g, "\n");
91
- console.log("[Firebase] Private key starts with BEGIN:", privateKey?.includes("-----BEGIN PRIVATE KEY-----"));
92
- if (!projectId || !clientEmail || !privateKey) {
93
- const errorMsg = "[Firebase] FATAL: Credentials not configured! Set FIREBASE_SERVICE_ACCOUNT_BASE64 (preferred) or FIREBASE_PROJECT_ID/CLIENT_EMAIL/PRIVATE_KEY.";
94
- console.error(errorMsg);
95
- throw new Error(errorMsg);
96
- }
97
- try {
98
- admin.initializeApp({
99
- credential: admin.credential.cert({
100
- projectId,
101
- clientEmail,
102
- privateKey
103
- })
104
- });
105
- firebaseInitialized = true;
106
- console.log("Firebase Admin SDK initialized");
107
- } catch (error) {
108
- console.error("Failed to initialize Firebase:", error);
109
- }
110
- }
111
- function getAuth() {
112
- return admin.auth();
113
- }
114
- function getFirestore() {
115
- return admin.firestore();
116
- }
117
- async function getUserByAddress(address) {
118
- const db = getFirestore();
119
- const userDoc = await db.collection("users").doc(address.toLowerCase()).get();
120
- if (!userDoc.exists) {
121
- return null;
122
- }
123
- return {
124
- address: userDoc.id,
125
- ...userDoc.data()
126
- };
127
- }
128
- async function updateUserTier(address, tier, expiresAt) {
129
- const db = getFirestore();
130
- const userRef = db.collection("users").doc(address.toLowerCase());
131
- await userRef.set({
132
- tier,
133
- tierExpiresAt: admin.firestore.Timestamp.fromDate(expiresAt),
134
- updatedAt: admin.firestore.FieldValue.serverTimestamp()
135
- }, { merge: true });
136
- console.log(`[Payment] Updated user ${address} to ${tier} tier, expires ${expiresAt.toISOString()}`);
137
- }
138
- var firebaseInitialized;
139
- var init_firebase = __esm({
140
- "src/firebase.ts"() {
141
- firebaseInitialized = false;
142
- }
143
- });
144
-
145
12
  // src/mcp/tools.ts
146
13
  var tools_exports = {};
147
14
  __export(tools_exports, {
@@ -3395,251 +3262,6 @@ var init_handlers = __esm({
3395
3262
  }
3396
3263
  });
3397
3264
 
3398
- // src/models/apiKey.ts
3399
- var apiKey_exports = {};
3400
- __export(apiKey_exports, {
3401
- API_SCOPES: () => API_SCOPES,
3402
- TIER_DEFAULT_SCOPES: () => TIER_DEFAULT_SCOPES,
3403
- TIER_LIMITS: () => TIER_LIMITS,
3404
- checkRateLimit: () => checkRateLimit,
3405
- createAPIKey: () => createAPIKey,
3406
- deactivateAPIKey: () => deactivateAPIKey,
3407
- generateAPIKey: () => generateAPIKey,
3408
- generateMcpKey: () => generateMcpKey,
3409
- getAPIKeyById: () => getAPIKeyById,
3410
- getUserAPIKeys: () => getUserAPIKeys,
3411
- hasScope: () => hasScope,
3412
- hashAPIKey: () => hashAPIKey,
3413
- incrementAPIKeyUsage: () => incrementAPIKeyUsage,
3414
- resetDailyUsageIfNeeded: () => resetDailyUsageIfNeeded,
3415
- updateAPIKeyTier: () => updateAPIKeyTier,
3416
- validateAPIKey: () => validateAPIKey
3417
- });
3418
- import { FieldValue } from "firebase-admin/firestore";
3419
- function generateAPIKey(type = "live") {
3420
- const prefix = type === "live" ? "ft_live_" : type === "mcp" ? "ft_mcp_" : "ft_test_";
3421
- const randomPart = generateSecureRandom(32);
3422
- return `${prefix}${randomPart}`;
3423
- }
3424
- function generateMcpKey() {
3425
- return generateAPIKey("mcp");
3426
- }
3427
- function generateSecureRandom(length) {
3428
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
3429
- const array = new Uint8Array(length);
3430
- __require("crypto").randomFillSync(array);
3431
- return Array.from(array, (byte) => chars[byte % chars.length]).join("");
3432
- }
3433
- function hashAPIKey(key) {
3434
- return __require("crypto").createHash("sha256").update(key).digest("hex");
3435
- }
3436
- async function createAPIKey(userId, options) {
3437
- const db = getFirestore();
3438
- const keyCollection = db.collection("apiKeys");
3439
- const tier = options.tier || "free";
3440
- const limits = TIER_LIMITS[tier];
3441
- const existingKeys = await keyCollection.where("userId", "==", userId).where("isActive", "==", true).get();
3442
- const currentKeyCount = existingKeys.size;
3443
- if (limits.maxKeys !== Infinity && currentKeyCount >= limits.maxKeys) {
3444
- throw new Error(`Maximum API keys (${limits.maxKeys}) reached for ${tier} tier. Upgrade to create more keys.`);
3445
- }
3446
- const keyType = options.keyType || "live";
3447
- const rawKey = generateAPIKey(keyType);
3448
- const keyHash = hashAPIKey(rawKey);
3449
- const keyPrefix = rawKey.split("_").slice(0, 2).join("_");
3450
- const scopes = options.scopes || TIER_DEFAULT_SCOPES[tier];
3451
- const now = Date.now();
3452
- const dailyReset = getDailyResetTimestamp();
3453
- const apiKey = {
3454
- id: keyCollection.doc().id,
3455
- userId,
3456
- keyHash,
3457
- keyPrefix,
3458
- keyType,
3459
- name: options.name,
3460
- scopes,
3461
- tier,
3462
- dailyUsage: 0,
3463
- dailyUsageReset: dailyReset,
3464
- lastUsed: now,
3465
- createdAt: now,
3466
- isActive: true,
3467
- expiresAt: options.expiresAt,
3468
- testnetOnly: options.testnetOnly || false
3469
- };
3470
- await keyCollection.doc(apiKey.id).set(apiKey);
3471
- return { key: apiKey, rawKey };
3472
- }
3473
- async function validateAPIKey(rawKey) {
3474
- const db = getFirestore();
3475
- const keyCollection = db.collection("apiKeys");
3476
- const snapshot = await keyCollection.where("isActive", "==", true).get();
3477
- const keyHash = hashAPIKey(rawKey);
3478
- for (const doc of snapshot.docs) {
3479
- const keyData = doc.data();
3480
- if (keyData.keyHash === keyHash) {
3481
- if (keyData.expiresAt && keyData.expiresAt < Date.now()) {
3482
- return null;
3483
- }
3484
- return keyData;
3485
- }
3486
- }
3487
- return null;
3488
- }
3489
- async function getAPIKeyById(keyId) {
3490
- const db = getFirestore();
3491
- const doc = await db.collection("apiKeys").doc(keyId).get();
3492
- return doc.exists ? doc.data() : null;
3493
- }
3494
- async function getUserAPIKeys(userId) {
3495
- const db = getFirestore();
3496
- const snapshot = await db.collection("apiKeys").where("userId", "==", userId).orderBy("createdAt", "desc").get();
3497
- return snapshot.docs.map((doc) => doc.data());
3498
- }
3499
- async function incrementAPIKeyUsage(userId, rawKey) {
3500
- console.log(`[INCREMENT] Starting - userId: ${userId}, rawKey: ${rawKey.substring(0, 15)}...`);
3501
- const db = getFirestore();
3502
- const now = Date.now();
3503
- try {
3504
- const topLevelRef = db.collection("apiKeys").doc(rawKey);
3505
- await topLevelRef.update({
3506
- lastUsed: now,
3507
- requests: FieldValue.increment(1)
3508
- });
3509
- console.log(`[INCREMENT] Updated top-level apiKeys/${rawKey.substring(0, 15)}...`);
3510
- } catch (err2) {
3511
- console.error(`[INCREMENT] Failed to update top-level apiKeys doc:`, err2);
3512
- }
3513
- try {
3514
- const subSnapshot = await db.collection("users").doc(userId).collection("apiKeys").where("key", "==", rawKey).limit(1).get();
3515
- if (!subSnapshot.empty) {
3516
- const subDoc = subSnapshot.docs[0];
3517
- await subDoc.ref.update({
3518
- lastUsed: now,
3519
- requests: FieldValue.increment(1)
3520
- });
3521
- console.log(`[INCREMENT] Updated subcollection doc ${subDoc.id} for key`);
3522
- } else {
3523
- console.warn(`[INCREMENT] No subcollection doc found for key ${rawKey.substring(0, 15)}...`);
3524
- }
3525
- } catch (err2) {
3526
- console.error(`[INCREMENT] Failed to update subcollection doc:`, err2);
3527
- }
3528
- try {
3529
- const topLevelDoc = await db.collection("apiKeys").doc(rawKey).get();
3530
- if (topLevelDoc.exists) {
3531
- const dailyReset = getDailyResetTimestamp();
3532
- await topLevelDoc.ref.update({
3533
- dailyUsage: FieldValue.increment(1),
3534
- dailyUsageReset: dailyReset
3535
- });
3536
- }
3537
- } catch (err2) {
3538
- console.error(`[INCREMENT] Failed to update daily usage:`, err2);
3539
- }
3540
- console.log(`[INCREMENT] Usage tracking complete for key: ${rawKey.substring(0, 15)}...`);
3541
- }
3542
- async function resetDailyUsageIfNeeded(key) {
3543
- if (key.dailyUsageReset < Date.now()) {
3544
- const db = getFirestore();
3545
- await db.collection("apiKeys").doc(key.id).update({
3546
- dailyUsage: 0,
3547
- dailyUsageReset: getDailyResetTimestamp()
3548
- });
3549
- }
3550
- }
3551
- async function checkRateLimit(key) {
3552
- const limits = TIER_LIMITS[key.tier];
3553
- const now = Date.now();
3554
- await resetDailyUsageIfNeeded(key);
3555
- return {
3556
- limit: limits.daily,
3557
- remaining: Math.max(0, limits.daily - key.dailyUsage),
3558
- reset: key.dailyUsageReset,
3559
- tier: key.tier
3560
- };
3561
- }
3562
- async function deactivateAPIKey(keyId, userId) {
3563
- const db = getFirestore();
3564
- const keyRef = db.collection("apiKeys").doc(keyId);
3565
- const doc = await keyRef.get();
3566
- if (!doc.exists || doc.data()?.userId !== userId) {
3567
- return false;
3568
- }
3569
- await keyRef.update({ isActive: false });
3570
- return true;
3571
- }
3572
- function hasScope(key, requiredScope) {
3573
- if (key.scopes.includes(API_SCOPES.ADMIN)) {
3574
- return true;
3575
- }
3576
- return key.scopes.includes(requiredScope);
3577
- }
3578
- function getDailyResetTimestamp() {
3579
- const now = /* @__PURE__ */ new Date();
3580
- const tomorrow = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1));
3581
- return tomorrow.getTime();
3582
- }
3583
- async function updateAPIKeyTier(keyId, updates) {
3584
- const db = getFirestore();
3585
- await db.collection("apiKeys").doc(keyId).update({
3586
- ...updates.tier && { tier: updates.tier },
3587
- ...updates.scopes && { scopes: updates.scopes },
3588
- ...updates.expiresAt !== void 0 && { expiresAt: updates.expiresAt }
3589
- });
3590
- }
3591
- var API_SCOPES, TIER_LIMITS, TIER_DEFAULT_SCOPES;
3592
- var init_apiKey = __esm({
3593
- "src/models/apiKey.ts"() {
3594
- init_firebase();
3595
- API_SCOPES = {
3596
- READ_ADDRESS: "read:address",
3597
- READ_TRANSACTIONS: "read:transactions",
3598
- READ_GRAPH: "read:graph",
3599
- WRITE_ALERTS: "write:alerts",
3600
- WRITE_WEBHOOKS: "write:webhooks",
3601
- ADMIN: "admin",
3602
- MCP: "mcp"
3603
- };
3604
- TIER_LIMITS = {
3605
- free: {
3606
- daily: 1e3,
3607
- perMinute: 100,
3608
- burst: 200,
3609
- maxKeys: 2,
3610
- endpoints: ["address", "transactions", "tokens", "risk"],
3611
- features: ["basic_address_info", "transaction_history"]
3612
- },
3613
- pro: {
3614
- daily: 1e4,
3615
- perMinute: 200,
3616
- burst: 400,
3617
- maxKeys: 10,
3618
- endpoints: ["address", "transactions", "tokens", "risk", "graph", "sources", "destinations", "analyze", "entities"],
3619
- features: ["full_graph_analysis", "async_analysis", "entity_detection"]
3620
- },
3621
- enterprise: {
3622
- daily: 1e5,
3623
- perMinute: 300,
3624
- burst: 1e3,
3625
- maxKeys: Infinity,
3626
- endpoints: ["*"],
3627
- features: ["*", "webhooks", "alerts", "websocket", "priority_support"]
3628
- }
3629
- };
3630
- TIER_DEFAULT_SCOPES = {
3631
- free: [API_SCOPES.READ_ADDRESS, API_SCOPES.READ_TRANSACTIONS],
3632
- pro: [
3633
- API_SCOPES.READ_ADDRESS,
3634
- API_SCOPES.READ_TRANSACTIONS,
3635
- API_SCOPES.READ_GRAPH,
3636
- API_SCOPES.WRITE_ALERTS
3637
- ],
3638
- enterprise: [API_SCOPES.ADMIN]
3639
- };
3640
- }
3641
- });
3642
-
3643
3265
  // src/mcp/mcpAuth.ts
3644
3266
  var mcpAuth_exports = {};
3645
3267
  __export(mcpAuth_exports, {
@@ -3657,8 +3279,8 @@ async function validateMcpApiKey(rawKey) {
3657
3279
  return validateViaHttp(rawKey);
3658
3280
  }
3659
3281
  async function validateWithFirestore(rawKey) {
3660
- const { getFirestore: getFirestore2 } = await Promise.resolve().then(() => (init_firebase(), firebase_exports));
3661
- const db = getFirestore2();
3282
+ const { getFirestore } = await import("../firebase.js");
3283
+ const db = getFirestore();
3662
3284
  if (!db) return null;
3663
3285
  const keyDoc = await db.collection("apiKeys").doc(rawKey).get();
3664
3286
  if (keyDoc.exists) {
@@ -3677,8 +3299,8 @@ async function validateWithFirestore(rawKey) {
3677
3299
  apiKeyPrefix: rawKey.substring(0, 15)
3678
3300
  };
3679
3301
  }
3680
- const { hashAPIKey: hashAPIKey2 } = await Promise.resolve().then(() => (init_apiKey(), apiKey_exports));
3681
- const keyHash = hashAPIKey2(rawKey);
3302
+ const { hashAPIKey } = await import("../models/apiKey.js");
3303
+ const keyHash = hashAPIKey(rawKey);
3682
3304
  const snapshot = await db.collection("apiKeys").where("isActive", "==", true).get();
3683
3305
  for (const doc of snapshot.docs) {
3684
3306
  const data = doc.data();
@@ -3723,8 +3345,8 @@ async function validateViaHttp(rawKey) {
3723
3345
  }
3724
3346
  async function trackUsage(userId, rawKey) {
3725
3347
  try {
3726
- const { incrementAPIKeyUsage: incrementAPIKeyUsage2 } = await Promise.resolve().then(() => (init_apiKey(), apiKey_exports));
3727
- await incrementAPIKeyUsage2(userId, rawKey);
3348
+ const { incrementAPIKeyUsage } = await import("../models/apiKey.js");
3349
+ await incrementAPIKeyUsage(userId, rawKey);
3728
3350
  } catch {
3729
3351
  }
3730
3352
  }
@@ -3757,8 +3379,8 @@ dotenv.config();
3757
3379
  async function main() {
3758
3380
  let firebaseAvailable = false;
3759
3381
  try {
3760
- const { initializeFirebase: initializeFirebase2 } = await Promise.resolve().then(() => (init_firebase(), firebase_exports));
3761
- initializeFirebase2();
3382
+ const { initializeFirebase } = await import("../firebase.js");
3383
+ initializeFirebase();
3762
3384
  firebaseAvailable = true;
3763
3385
  console.error("[MCP] Firebase initialized");
3764
3386
  } catch (err2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fundtracer/mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "FundTracer MCP Server — blockchain analysis for AI assistants (Claude Desktop, Claude Code, Cursor, etc.)",
5
5
  "type": "module",
6
6
  "main": "fundtracer-mcp.js",