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});