batman
  1#!/usr/bin/env node
  2
  3/**
  4 * Simple test script to demonstrate JA4 MCP Server resources functionality
  5 * Tests the server by importing and calling methods directly
  6 */
  7
  8import fs from "fs/promises";
  9import path from "path";
 10
 11// Import the database class from the main server file
 12class JA4Database {
 13  constructor() {
 14    this.database = null;
 15    this.cacheFile = path.join(
 16      process.env.HOME || process.env.USERPROFILE || "/tmp",
 17      ".cache",
 18      "ja4-mcp",
 19      "ja4db.json",
 20    );
 21  }
 22
 23  async loadDatabase() {
 24    try {
 25      const data = await fs.readFile(this.cacheFile, "utf8");
 26      this.database = JSON.parse(data);
 27      return true;
 28    } catch (error) {
 29      console.log("Database not cached, would download in real scenario");
 30      // For testing, create a minimal mock database
 31      this.database = [
 32        {
 33          JA4: "t13d1516h2_8daaf6152771_b0da82dd1658",
 34          JA4S: "t130200_1301_a56c586c4def",
 35          JA4H: "ge11nn05enus_9c178b129f5c_cd8aa1d32be0",
 36          Application: "Chrome",
 37          "Operating System": "Windows 10",
 38          Verified: "Yes",
 39        },
 40        {
 41          JA4: "t13d1715h2_55b375c5d22e_06de65f6a064",
 42          JA4S: "t130200_1301_5c23875c0a2b",
 43          Application: "Firefox",
 44          "Operating System": "macOS",
 45          Verified: "Yes",
 46        },
 47      ];
 48      return true;
 49    }
 50  }
 51
 52  async getStatistics() {
 53    if (!this.database) {
 54      await this.loadDatabase();
 55    }
 56
 57    const stats = {
 58      total_records: this.database.length,
 59      ja4_count: this.database.filter((r) => r.JA4).length,
 60      ja4s_count: this.database.filter((r) => r.JA4S).length,
 61      ja4h_count: this.database.filter((r) => r.JA4H).length,
 62      verified_count: this.database.filter((r) => r.Verified === "Yes").length,
 63      applications: new Set(this.database.map((r) => r.Application).filter((a) => a)).size,
 64      operating_systems: new Set(this.database.map((r) => r["Operating System"]).filter((os) => os)).size,
 65    };
 66
 67    return stats;
 68  }
 69}
 70
 71// Mock resource handlers
 72class ResourceHandler {
 73  constructor() {
 74    this.db = new JA4Database();
 75  }
 76
 77  async listResources() {
 78    const resources = [
 79      {
 80        uri: "ja4-database://fingerprints",
 81        name: "JA4 Fingerprints Database",
 82        title: "Complete JA4 Fingerprints Database",
 83        description: "Complete database of known JA4 fingerprints with applications and OS information",
 84        mimeType: "application/json",
 85        annotations: { audience: ["user", "assistant"], priority: 0.9 },
 86      },
 87      {
 88        uri: "ja4-database://applications",
 89        name: "Applications Database",
 90        title: "Applications with JA4 Fingerprints",
 91        description: "List of applications and their associated JA4 fingerprints",
 92        mimeType: "application/json",
 93        annotations: { audience: ["user", "assistant"], priority: 0.8 },
 94      },
 95      {
 96        uri: "ja4-database://statistics",
 97        name: "Database Statistics",
 98        title: "JA4 Database Statistics",
 99        description: "Statistics about the JA4 fingerprints database",
100        mimeType: "application/json",
101        annotations: { audience: ["user", "assistant"], priority: 0.7 },
102      },
103      {
104        uri: "ja4-docs://protocols",
105        name: "Protocol Reference",
106        title: "JA4 Protocol Codes Reference",
107        description: "Reference documentation for JA4 protocol codes (t, q, d)",
108        mimeType: "application/json",
109        annotations: { audience: ["user", "assistant"], priority: 0.9 },
110      },
111    ];
112
113    return { resources };
114  }
115
116  async readResource(uri) {
117    if (uri.startsWith("ja4-database://")) {
118      await this.db.loadDatabase();
119      const resourceType = uri.replace("ja4-database://", "");
120
121      switch (resourceType) {
122        case "fingerprints":
123          return {
124            contents: [
125              {
126                uri,
127                mimeType: "application/json",
128                text: JSON.stringify(this.db.database, null, 2),
129              },
130            ],
131          };
132
133        case "applications":
134          const applications = {};
135          if (this.db.database && this.db.database.length > 0) {
136            this.db.database.forEach((record) => {
137              if (record.Application && record.Application !== "") {
138                if (!applications[record.Application]) {
139                  applications[record.Application] = [];
140                }
141                applications[record.Application].push({
142                  ja4: record.JA4,
143                  ja4s: record.JA4S,
144                  ja4h: record.JA4H,
145                  os: record["Operating System"],
146                  verified: record.Verified === "Yes",
147                });
148              }
149            });
150          }
151          return {
152            contents: [
153              {
154                uri,
155                mimeType: "application/json",
156                text: JSON.stringify(applications, null, 2),
157              },
158            ],
159          };
160
161        case "statistics":
162          const stats = await this.db.getStatistics();
163          return {
164            contents: [
165              {
166                uri,
167                mimeType: "application/json",
168                text: JSON.stringify(stats, null, 2),
169              },
170            ],
171          };
172
173        default:
174          throw new Error(`Unknown database resource: ${resourceType}`);
175      }
176    } else if (uri.startsWith("ja4-docs://")) {
177      const docType = uri.replace("ja4-docs://", "");
178
179      switch (docType) {
180        case "protocols":
181          const protocols = {
182            t: { code: "t", description: "TCP", full_name: "Transmission Control Protocol" },
183            q: { code: "q", description: "QUIC", full_name: "Quick UDP Internet Connections" },
184            d: { code: "d", description: "DTLS", full_name: "Datagram Transport Layer Security" },
185          };
186          return {
187            contents: [
188              {
189                uri,
190                mimeType: "application/json",
191                text: JSON.stringify(protocols, null, 2),
192              },
193            ],
194          };
195
196        case "tls-versions":
197          const tlsVersions = {
198            10: { version: "TLS 1.0", description: "Legacy TLS version", security: "Deprecated" },
199            11: { version: "TLS 1.1", description: "Legacy TLS version", security: "Deprecated" },
200            12: { version: "TLS 1.2", description: "Current standard TLS version", security: "Secure" },
201            13: { version: "TLS 1.3", description: "Latest TLS version", security: "Most Secure" },
202          };
203          return {
204            contents: [
205              {
206                uri,
207                mimeType: "application/json",
208                text: JSON.stringify(tlsVersions, null, 2),
209              },
210            ],
211          };
212
213        default:
214          throw new Error(`Unknown documentation resource: ${docType}`);
215      }
216    } else {
217      throw new Error(`Unknown resource scheme: ${uri}`);
218    }
219  }
220}
221
222async function testResources() {
223  console.log("=== JA4 MCP Server Resources Test ===\n");
224
225  const handler = new ResourceHandler();
226
227  try {
228    // Test 1: List available resources
229    console.log("1. Testing resources/list...");
230    const resourcesList = await handler.listResources();
231    console.log(`   Found ${resourcesList.resources.length} resources:`);
232    resourcesList.resources.forEach((resource, index) => {
233      console.log(`   ${index + 1}. ${resource.name}`);
234      console.log(`      URI: ${resource.uri}`);
235      console.log(`      Description: ${resource.description}`);
236      console.log(`      Priority: ${resource.annotations.priority}\n`);
237    });
238
239    // Test 2: Read database statistics
240    console.log("2. Testing database statistics resource...");
241    try {
242      const statsResponse = await handler.readResource("ja4-database://statistics");
243      const stats = JSON.parse(statsResponse.contents[0].text);
244      console.log("   Database Statistics:");
245      console.log(`   - Total records: ${stats.total_records}`);
246      console.log(`   - JA4 fingerprints: ${stats.ja4_count}`);
247      console.log(`   - Verified records: ${stats.verified_count}`);
248      console.log(`   - Applications: ${stats.applications}`);
249      console.log(`   - Operating systems: ${stats.operating_systems}\n`);
250    } catch (error) {
251      console.log(`   Error reading statistics: ${error.message}\n`);
252    }
253
254    // Test 3: Read protocol documentation
255    console.log("3. Testing protocol documentation resource...");
256    try {
257      const protocolsResponse = await handler.readResource("ja4-docs://protocols");
258      const protocols = JSON.parse(protocolsResponse.contents[0].text);
259      console.log("   Protocol Reference:");
260      Object.entries(protocols).forEach(([code, info]) => {
261        console.log(`   - ${code}: ${info.full_name} (${info.description})`);
262      });
263      console.log();
264    } catch (error) {
265      console.log(`   Error reading protocols: ${error.message}\n`);
266    }
267
268    // Test 4: Read applications database
269    console.log("4. Testing applications database resource...");
270    try {
271      const appsResponse = await handler.readResource("ja4-database://applications");
272      const applications = JSON.parse(appsResponse.contents[0].text);
273      const appNames = Object.keys(applications);
274      console.log(`   Applications Database (${appNames.length} applications found):`);
275
276      appNames.forEach((appName) => {
277        const fingerprints = applications[appName];
278        console.log(`   - ${appName}: ${fingerprints.length} fingerprint(s)`);
279        if (fingerprints.length > 0) {
280          console.log(`     Example JA4: ${fingerprints[0].ja4}`);
281          console.log(`     OS: ${fingerprints[0].os}`);
282          console.log(`     Verified: ${fingerprints[0].verified}`);
283        }
284      });
285      console.log();
286    } catch (error) {
287      console.log(`   Error reading applications: ${error.message}\n`);
288    }
289
290    // Test 5: Read fingerprints database
291    console.log("5. Testing complete fingerprints database resource...");
292    try {
293      const fingerprintsResponse = await handler.readResource("ja4-database://fingerprints");
294      const fingerprints = JSON.parse(fingerprintsResponse.contents[0].text);
295      console.log(`   Complete fingerprints database: ${fingerprints.length} records`);
296      if (fingerprints.length > 0) {
297        console.log("   Sample record:");
298        const sample = fingerprints[0];
299        console.log(`   - JA4: ${sample.JA4}`);
300        console.log(`   - JA4S: ${sample.JA4S}`);
301        console.log(`   - Application: ${sample.Application}`);
302        console.log(`   - OS: ${sample["Operating System"]}`);
303        console.log(`   - Verified: ${sample.Verified}`);
304      }
305      console.log();
306    } catch (error) {
307      console.log(`   Error reading fingerprints: ${error.message}\n`);
308    }
309
310    // Test 6: Test error handling
311    console.log("6. Testing error handling with invalid resource...");
312    try {
313      await handler.readResource("invalid://nonexistent");
314      console.log("   ERROR: Expected exception but got success");
315    } catch (error) {
316      console.log(`   SUCCESS: Correctly handled error: ${error.message}`);
317    }
318    console.log();
319
320    console.log("=== All tests completed successfully! ===");
321  } catch (error) {
322    console.error("Test failed:", error.message);
323    console.error("Stack trace:", error.stack);
324  }
325}
326
327// Run the test
328testResources().catch((error) => {
329  console.error("Unhandled error:", error);
330  process.exit(1);
331});