@reverse-craft/ai-tools 1.0.0 → 1.0.2

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 (34) hide show
  1. package/README.md +204 -77
  2. package/dist/__tests__/batchProcessing.property.test.d.ts +10 -0
  3. package/dist/__tests__/batchProcessing.property.test.d.ts.map +1 -0
  4. package/dist/__tests__/errorHandling.property.test.d.ts +11 -0
  5. package/dist/__tests__/errorHandling.property.test.d.ts.map +1 -0
  6. package/dist/__tests__/llmConfig.property.test.d.ts +48 -0
  7. package/dist/__tests__/llmConfig.property.test.d.ts.map +1 -0
  8. package/dist/__tests__/mergeResults.property.test.d.ts +12 -0
  9. package/dist/__tests__/mergeResults.property.test.d.ts.map +1 -0
  10. package/dist/__tests__/tokenizer.property.test.d.ts +20 -0
  11. package/dist/__tests__/tokenizer.property.test.d.ts.map +1 -0
  12. package/dist/index.d.ts +8 -3
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/jsvmpDetector.d.ts +70 -5
  15. package/dist/jsvmpDetector.d.ts.map +1 -1
  16. package/dist/llmConfig.d.ts +36 -1
  17. package/dist/llmConfig.d.ts.map +1 -1
  18. package/dist/server.d.ts +2 -0
  19. package/dist/server.d.ts.map +1 -0
  20. package/dist/server.js +614 -0
  21. package/dist/server.js.map +7 -0
  22. package/dist/tokenizer.d.ts +23 -0
  23. package/dist/tokenizer.d.ts.map +1 -0
  24. package/dist/tools/ToolDefinition.d.ts +24 -0
  25. package/dist/tools/ToolDefinition.d.ts.map +1 -0
  26. package/dist/tools/findJsvmpDispatcher.d.ts +41 -0
  27. package/dist/tools/findJsvmpDispatcher.d.ts.map +1 -0
  28. package/dist/tools/findJsvmpDispatcherTool.d.ts +29 -0
  29. package/dist/tools/findJsvmpDispatcherTool.d.ts.map +1 -0
  30. package/dist/tools/findJsvmpDispatcherTool.test.d.ts +7 -0
  31. package/dist/tools/findJsvmpDispatcherTool.test.d.ts.map +1 -0
  32. package/dist/tools/index.d.ts +20 -0
  33. package/dist/tools/index.d.ts.map +1 -0
  34. package/package.json +19 -11
@@ -1 +1 @@
1
- {"version":3,"file":"llmConfig.d.ts","sourceRoot":"","sources":["../src/llmConfig.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,SAAS,GAAG,IAAI,CAiB/C;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACtD;AAoDD;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CAyD5D"}
1
+ {"version":3,"file":"llmConfig.d.ts","sourceRoot":"","sources":["../src/llmConfig.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAExC;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,WAAW,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAIpE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,WAAW,EAAE;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,CAgBA,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,WAAW,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,IAAI,CAM9E;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,SAAS,GAAG,IAAI,CAoC/C;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACtD;AA6DD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,aAAa,CAwBpE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CAyB5D"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":""}
package/dist/server.js ADDED
@@ -0,0 +1,614 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/server.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z as z2 } from "zod";
7
+
8
+ // src/tools/findJsvmpDispatcherTool.ts
9
+ import { z } from "zod";
10
+
11
+ // src/tools/ToolDefinition.ts
12
+ function defineTool(definition) {
13
+ return definition;
14
+ }
15
+
16
+ // src/jsvmpDetector.ts
17
+ import { SourceMapConsumer } from "source-map-js";
18
+ import { ensureBeautified, truncateCodeHighPerf } from "@reverse-craft/smart-fs";
19
+ import { existsSync } from "fs";
20
+
21
+ // src/llmConfig.ts
22
+ import { generateText } from "ai";
23
+ import { createOpenAI } from "@ai-sdk/openai";
24
+ import { createAnthropic } from "@ai-sdk/anthropic";
25
+ import { createGoogleGenerativeAI } from "@ai-sdk/google";
26
+ var PROVIDER_DEFAULTS = {
27
+ openai: { model: "gpt-4o-mini" },
28
+ anthropic: { model: "claude-sonnet-4-20250514" },
29
+ google: { model: "gemini-2.0-flash" }
30
+ };
31
+ var PROVIDER_ENV_KEYS = {
32
+ openai: {
33
+ apiKey: "OPENAI_API_KEY",
34
+ model: "OPENAI_MODEL",
35
+ baseUrl: "OPENAI_BASE_URL"
36
+ },
37
+ anthropic: {
38
+ apiKey: "ANTHROPIC_API_KEY",
39
+ model: "ANTHROPIC_MODEL",
40
+ baseUrl: "ANTHROPIC_BASE_URL"
41
+ },
42
+ google: {
43
+ apiKey: "GOOGLE_API_KEY",
44
+ model: "GOOGLE_MODEL",
45
+ baseUrl: "GOOGLE_BASE_URL"
46
+ }
47
+ };
48
+ function validateProvider(value) {
49
+ if (value === void 0) return null;
50
+ if (value === "openai" || value === "anthropic" || value === "google") {
51
+ return value;
52
+ }
53
+ return null;
54
+ }
55
+ function getLLMConfig() {
56
+ const providerEnv = process.env.LLM_PROVIDER?.toLowerCase();
57
+ const provider = validateProvider(providerEnv);
58
+ if (provider === null && providerEnv !== void 0) {
59
+ console.warn(`Invalid LLM_PROVIDER: ${providerEnv}. Valid values: openai, anthropic, google`);
60
+ return null;
61
+ }
62
+ const effectiveProvider = provider ?? "openai";
63
+ const envKeys = PROVIDER_ENV_KEYS[effectiveProvider];
64
+ const apiKey = process.env[envKeys.apiKey];
65
+ if (!apiKey) {
66
+ return null;
67
+ }
68
+ const model = process.env.LLM_MODEL || process.env[envKeys.model] || PROVIDER_DEFAULTS[effectiveProvider].model;
69
+ const baseUrl = process.env.LLM_BASE_URL || process.env[envKeys.baseUrl];
70
+ return {
71
+ provider: effectiveProvider,
72
+ apiKey,
73
+ model,
74
+ baseUrl
75
+ };
76
+ }
77
+ function buildJSVMPSystemPrompt() {
78
+ return `You are a Senior JavaScript Reverse Engineer and De-obfuscation Expert. Your specialty is analyzing **JSVMP (JavaScript Virtual Machine Protection)**.
79
+
80
+ **Context: What is JSVMP?**
81
+ JSVMP is a protection technique where original JavaScript code is compiled into custom **bytecode** and executed by a custom **interpreter** (virtual machine) written in JavaScript.
82
+
83
+ Key components of JSVMP code include:
84
+ 1. **The Virtual Stack:** A central array used to store operands and results (e.g., \`stack[pointer++]\` or \`v[p--]\`).
85
+ 2. **The Dispatcher:** A control flow structure inside a loop that decides which instruction to execute next based on the current bytecode (opcode).
86
+ * *Common variants:* A massive \`switch\` statement, a deeply nested \`if-else\` chain (binary search style), or a function array mapping (\`handlers[opcode]()\`).
87
+ 3. **The Bytecode:** A large string or array of integers representing the program logic.
88
+
89
+ **Task:**
90
+ Analyze the provided JavaScript code snippet to identify regions that match JSVMP structural patterns.
91
+
92
+ **Input Data Format:**
93
+ The code is provided in a simplified format: \`LineNo SourceLoc Code\`.
94
+ * **Example:** \`10 L234:56 var x = stack[p++];\`
95
+ * **Instruction:** Focus on the **LineNo** (1st column) and **Code** (3rd column onwards). Ignore the \`SourceLoc\` (middle column).
96
+
97
+ **Detection Rules & Confidence Levels:**
98
+ Please assign confidence based on the following criteria:
99
+
100
+ * **Ultra High:**
101
+ * A combination of a **Main Loop** + **Dispatcher** + **Stack Operations** appears in the same block.
102
+ * *Example:* A \`while(true)\` loop containing a huge \`if-else\` chain where branches perform \`stack[p++]\` operations.
103
+
104
+ * **High:**
105
+ * Distinct **Dispatcher** structures found (e.g., a \`switch\` with >20 cases, or an \`if-else\` chain nested >10 levels deep checking integer values).
106
+ * Large arrays containing only function definitions (Instruction Handlers).
107
+
108
+ * **Medium:**
109
+ * Isolated **Stack Operations** (e.g., \`v2[p2] = v2[p2 - 1]\`) without visible dispatchers nearby.
110
+ * Suspicious \`while\` loops iterating over a string/array.
111
+
112
+ * **Low:**
113
+ * Generic obfuscation patterns (short variable names, comma operators) that *might* be part of a VM but lack specific structural proof.
114
+
115
+ **Output Format:**
116
+ Return **ONLY valid JSON**. No markdown wrapper, no conversational text.
117
+
118
+ **JSON Schema:**
119
+ {
120
+ "summary": "Brief analysis of the code structure in chinese, shortly",
121
+ "regions": [
122
+ {
123
+ "start": <start_line>,
124
+ "end": <end_line>,
125
+ "type": "<If-Else Dispatcher | Switch Dispatcher | Instruction Array | Stack Operation>",
126
+ "confidence": "<ultra_high | high | medium | low>",
127
+ "description": "<Why you flagged this. Mention specific variables like 'v2', 'p2' or structures. in chinese, shortly>"
128
+ }
129
+ ]
130
+ }`;
131
+ }
132
+ function createProviderModel(config) {
133
+ switch (config.provider) {
134
+ case "openai": {
135
+ const openai = createOpenAI({
136
+ apiKey: config.apiKey,
137
+ baseURL: config.baseUrl
138
+ });
139
+ return openai(config.model);
140
+ }
141
+ case "anthropic": {
142
+ const anthropic = createAnthropic({
143
+ apiKey: config.apiKey,
144
+ baseURL: config.baseUrl
145
+ });
146
+ return anthropic(config.model);
147
+ }
148
+ case "google": {
149
+ const google = createGoogleGenerativeAI({
150
+ apiKey: config.apiKey,
151
+ baseURL: config.baseUrl
152
+ });
153
+ return google(config.model);
154
+ }
155
+ }
156
+ }
157
+ function createLLMClient(config) {
158
+ const model = createProviderModel(config);
159
+ return {
160
+ async analyzeJSVMP(formattedCode) {
161
+ const systemPrompt = buildJSVMPSystemPrompt();
162
+ try {
163
+ const result = await generateText({
164
+ model,
165
+ system: systemPrompt,
166
+ prompt: `\u8BF7\u5206\u6790\u4EE5\u4E0B\u4EE3\u7801\uFF0C\u8BC6\u522B JSVMP \u4FDD\u62A4\u7ED3\u6784\uFF1A
167
+
168
+ ${formattedCode}`,
169
+ temperature: 0.1
170
+ });
171
+ return result.text;
172
+ } catch (error) {
173
+ const providerName = config.provider.charAt(0).toUpperCase() + config.provider.slice(1);
174
+ if (error instanceof Error) {
175
+ throw new Error(`${providerName} LLM \u8BF7\u6C42\u5931\u8D25: ${error.message}`);
176
+ }
177
+ throw new Error(`${providerName} LLM \u8BF7\u6C42\u5931\u8D25: ${String(error)}`);
178
+ }
179
+ }
180
+ };
181
+ }
182
+
183
+ // src/tokenizer.ts
184
+ import { encoding_for_model } from "tiktoken";
185
+ var DEFAULT_MODEL = "gpt-4o";
186
+ function countTokens(text, model) {
187
+ const enc = encoding_for_model(model ?? DEFAULT_MODEL);
188
+ try {
189
+ const tokens = enc.encode(text);
190
+ return tokens.length;
191
+ } finally {
192
+ enc.free();
193
+ }
194
+ }
195
+ function splitByTokenLimit(lines, maxTokens, model) {
196
+ if (lines.length === 0) {
197
+ return [];
198
+ }
199
+ if (maxTokens <= 0) {
200
+ throw new Error("maxTokens must be a positive number");
201
+ }
202
+ const batches = [];
203
+ let currentBatch = [];
204
+ let currentTokenCount = 0;
205
+ const enc = encoding_for_model(model ?? DEFAULT_MODEL);
206
+ try {
207
+ for (const line of lines) {
208
+ const lineWithNewline = line + "\n";
209
+ const lineTokens = enc.encode(lineWithNewline).length;
210
+ if (lineTokens > maxTokens) {
211
+ if (currentBatch.length > 0) {
212
+ batches.push(currentBatch);
213
+ currentBatch = [];
214
+ currentTokenCount = 0;
215
+ }
216
+ batches.push([line]);
217
+ continue;
218
+ }
219
+ if (currentTokenCount + lineTokens > maxTokens && currentBatch.length > 0) {
220
+ batches.push(currentBatch);
221
+ currentBatch = [];
222
+ currentTokenCount = 0;
223
+ }
224
+ currentBatch.push(line);
225
+ currentTokenCount += lineTokens;
226
+ }
227
+ if (currentBatch.length > 0) {
228
+ batches.push(currentBatch);
229
+ }
230
+ return batches;
231
+ } finally {
232
+ enc.free();
233
+ }
234
+ }
235
+
236
+ // src/jsvmpDetector.ts
237
+ function formatSourcePosition(line, column) {
238
+ if (line !== null && column !== null) {
239
+ return `L${line}:${column}`;
240
+ }
241
+ return "";
242
+ }
243
+ function formatCodeLine(lineNumber, sourcePos, code) {
244
+ const lineNumStr = String(lineNumber).padStart(5, " ");
245
+ const srcPosPadded = sourcePos ? sourcePos.padEnd(10, " ") : " ";
246
+ return `${lineNumStr} ${srcPosPadded} ${code}`;
247
+ }
248
+ async function formatEntireFile(filePath, charLimit = 300) {
249
+ const beautifyResult = await ensureBeautified(filePath);
250
+ const { code, rawMap } = beautifyResult;
251
+ const truncatedCode = truncateCodeHighPerf(code, charLimit);
252
+ const codeLines = truncatedCode.split("\n");
253
+ const totalLines = codeLines.length;
254
+ const formattedLines = [];
255
+ let consumer = null;
256
+ if (rawMap && rawMap.sources && rawMap.names && rawMap.mappings) {
257
+ consumer = new SourceMapConsumer({
258
+ version: String(rawMap.version),
259
+ sources: rawMap.sources,
260
+ names: rawMap.names,
261
+ mappings: rawMap.mappings,
262
+ file: rawMap.file,
263
+ sourceRoot: rawMap.sourceRoot
264
+ });
265
+ }
266
+ for (let lineNum = 1; lineNum <= totalLines; lineNum++) {
267
+ const lineIndex = lineNum - 1;
268
+ const lineContent = codeLines[lineIndex] ?? "";
269
+ let sourcePos = "";
270
+ if (consumer) {
271
+ const originalPos = consumer.originalPositionFor({
272
+ line: lineNum,
273
+ column: 0
274
+ });
275
+ sourcePos = formatSourcePosition(originalPos.line, originalPos.column);
276
+ }
277
+ formattedLines.push(formatCodeLine(lineNum, sourcePos, lineContent));
278
+ }
279
+ return {
280
+ lines: formattedLines,
281
+ totalLines
282
+ };
283
+ }
284
+ function extractLineNumber(formattedLine) {
285
+ const lineNumStr = formattedLine.substring(0, 5).trim();
286
+ return parseInt(lineNumStr, 10);
287
+ }
288
+ function createBatches(formattedLines, maxTokensPerBatch) {
289
+ if (formattedLines.length === 0) {
290
+ return [];
291
+ }
292
+ const lineBatches = splitByTokenLimit(formattedLines, maxTokensPerBatch);
293
+ const batches = [];
294
+ for (const batchLines of lineBatches) {
295
+ if (batchLines.length === 0) continue;
296
+ const startLine = extractLineNumber(batchLines[0]);
297
+ const endLine = extractLineNumber(batchLines[batchLines.length - 1]);
298
+ const content = batchLines.join("\n");
299
+ const tokenCount = countTokens(content);
300
+ batches.push({
301
+ startLine,
302
+ endLine,
303
+ content,
304
+ tokenCount
305
+ });
306
+ }
307
+ return batches;
308
+ }
309
+ var VALID_DETECTION_TYPES = [
310
+ "If-Else Dispatcher",
311
+ "Switch Dispatcher",
312
+ "Instruction Array",
313
+ "Stack Operation"
314
+ ];
315
+ var VALID_CONFIDENCE_LEVELS = [
316
+ "ultra_high",
317
+ "high",
318
+ "medium",
319
+ "low"
320
+ ];
321
+ function isValidDetectionType(value) {
322
+ return VALID_DETECTION_TYPES.includes(value);
323
+ }
324
+ function isValidConfidenceLevel(value) {
325
+ return VALID_CONFIDENCE_LEVELS.includes(value);
326
+ }
327
+ function parseDetectionResult(jsonString) {
328
+ let parsed;
329
+ try {
330
+ parsed = JSON.parse(jsonString);
331
+ } catch (error) {
332
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790 LLM \u54CD\u5E94: ${error instanceof Error ? error.message : String(error)}`);
333
+ }
334
+ if (typeof parsed !== "object" || parsed === null) {
335
+ throw new Error("LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0C\u671F\u671B\u5BF9\u8C61\u7C7B\u578B");
336
+ }
337
+ const obj = parsed;
338
+ if (typeof obj.summary !== "string") {
339
+ throw new Error("LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0C\u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5: summary");
340
+ }
341
+ if (!Array.isArray(obj.regions)) {
342
+ throw new Error("LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0C\u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5: regions");
343
+ }
344
+ const validatedRegions = [];
345
+ for (let i = 0; i < obj.regions.length; i++) {
346
+ const region = obj.regions[i];
347
+ if (typeof region !== "object" || region === null) {
348
+ throw new Error(`LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}] \u4E0D\u662F\u5BF9\u8C61`);
349
+ }
350
+ if (typeof region.start !== "number") {
351
+ throw new Error(`LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}] \u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5: start`);
352
+ }
353
+ if (typeof region.end !== "number") {
354
+ throw new Error(`LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}] \u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5: end`);
355
+ }
356
+ if (typeof region.type !== "string") {
357
+ throw new Error(`LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}] \u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5: type`);
358
+ }
359
+ if (typeof region.confidence !== "string") {
360
+ throw new Error(`LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}] \u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5: confidence`);
361
+ }
362
+ if (typeof region.description !== "string") {
363
+ throw new Error(`LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}] \u7F3A\u5C11\u5FC5\u9700\u5B57\u6BB5: description`);
364
+ }
365
+ if (!isValidDetectionType(region.type)) {
366
+ throw new Error(
367
+ `LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}].type \u503C\u65E0\u6548: "${region.type}". \u6709\u6548\u503C: ${VALID_DETECTION_TYPES.join(", ")}`
368
+ );
369
+ }
370
+ if (!isValidConfidenceLevel(region.confidence)) {
371
+ throw new Error(
372
+ `LLM \u54CD\u5E94\u683C\u5F0F\u65E0\u6548\uFF0Cregions[${i}].confidence \u503C\u65E0\u6548: "${region.confidence}". \u6709\u6548\u503C: ${VALID_CONFIDENCE_LEVELS.join(", ")}`
373
+ );
374
+ }
375
+ validatedRegions.push({
376
+ start: region.start,
377
+ end: region.end,
378
+ type: region.type,
379
+ confidence: region.confidence,
380
+ description: region.description
381
+ });
382
+ }
383
+ return {
384
+ summary: obj.summary,
385
+ regions: validatedRegions
386
+ };
387
+ }
388
+ function formatDetectionResultOutput(result, filePath, totalLines, batchCount) {
389
+ const lines = [];
390
+ lines.push("=== JSVMP Dispatcher Detection Result ===");
391
+ lines.push(`File: ${filePath} (${totalLines} lines, ${batchCount} batch${batchCount > 1 ? "es" : ""})`);
392
+ lines.push("");
393
+ lines.push(`Summary: ${result.summary}`);
394
+ lines.push("");
395
+ if (result.regions.length > 0) {
396
+ lines.push("Detected Regions:");
397
+ for (const region of result.regions) {
398
+ lines.push(`[${region.confidence}] Lines ${region.start}-${region.end}: ${region.type}`);
399
+ lines.push(` ${region.description}`);
400
+ lines.push("");
401
+ }
402
+ } else {
403
+ lines.push("No JSVMP dispatcher patterns detected.");
404
+ }
405
+ return lines.join("\n");
406
+ }
407
+ function mergeDetectionResults(results) {
408
+ if (results.length === 0) {
409
+ return { summary: "", regions: [] };
410
+ }
411
+ if (results.length === 1) {
412
+ const sortedRegions = [...results[0].regions].sort((a, b) => a.start - b.start);
413
+ return { summary: results[0].summary, regions: sortedRegions };
414
+ }
415
+ const summaries = results.map((r, i) => `[Batch ${i + 1}] ${r.summary}`);
416
+ const combinedSummary = summaries.join("\n");
417
+ const allRegions = [];
418
+ for (const result of results) {
419
+ allRegions.push(...result.regions);
420
+ }
421
+ allRegions.sort((a, b) => a.start - b.start);
422
+ const confidenceOrder = {
423
+ "ultra_high": 4,
424
+ "high": 3,
425
+ "medium": 2,
426
+ "low": 1
427
+ };
428
+ const deduplicatedRegions = [];
429
+ for (const region of allRegions) {
430
+ let overlappingIndex = -1;
431
+ for (let i = 0; i < deduplicatedRegions.length; i++) {
432
+ const existing = deduplicatedRegions[i];
433
+ if (region.start <= existing.end && region.end >= existing.start) {
434
+ overlappingIndex = i;
435
+ break;
436
+ }
437
+ }
438
+ if (overlappingIndex === -1) {
439
+ deduplicatedRegions.push(region);
440
+ } else {
441
+ const existing = deduplicatedRegions[overlappingIndex];
442
+ if (confidenceOrder[region.confidence] > confidenceOrder[existing.confidence]) {
443
+ deduplicatedRegions[overlappingIndex] = region;
444
+ }
445
+ }
446
+ }
447
+ return {
448
+ summary: combinedSummary,
449
+ regions: deduplicatedRegions
450
+ };
451
+ }
452
+ async function processBatch(client, batch) {
453
+ const llmResponse = await client.analyzeJSVMP(batch.content);
454
+ return parseDetectionResult(llmResponse);
455
+ }
456
+ async function processBatchesWithErrorHandling(client, batches) {
457
+ const results = [];
458
+ const errors = [];
459
+ for (let i = 0; i < batches.length; i++) {
460
+ const batch = batches[i];
461
+ try {
462
+ const result = await processBatch(client, batch);
463
+ results.push(result);
464
+ } catch (error) {
465
+ const errorMsg = `Batch ${i + 1} (lines ${batch.startLine}-${batch.endLine}) failed: ${error instanceof Error ? error.message : String(error)}`;
466
+ errors.push(errorMsg);
467
+ }
468
+ }
469
+ return { results, errors };
470
+ }
471
+ async function findJsvmpDispatcher(filePath, options) {
472
+ const charLimit = options?.charLimit ?? 300;
473
+ const maxTokensPerBatch = options?.maxTokensPerBatch ?? 8e3;
474
+ const config = getLLMConfig();
475
+ if (!config) {
476
+ return {
477
+ success: false,
478
+ filePath,
479
+ totalLines: 0,
480
+ batchCount: 0,
481
+ error: "\u672A\u914D\u7F6E LLM\u3002\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF OPENAI_API_KEY \u4EE5\u542F\u7528 JSVMP dispatcher \u68C0\u6D4B\u529F\u80FD\u3002"
482
+ };
483
+ }
484
+ if (!existsSync(filePath)) {
485
+ return {
486
+ success: false,
487
+ filePath,
488
+ totalLines: 0,
489
+ batchCount: 0,
490
+ error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`
491
+ };
492
+ }
493
+ try {
494
+ const formattedCode = await formatEntireFile(filePath, charLimit);
495
+ const totalLines = formattedCode.totalLines;
496
+ const batches = createBatches(formattedCode.lines, maxTokensPerBatch);
497
+ const batchCount = batches.length;
498
+ const client = createLLMClient(config);
499
+ const { results, errors } = await processBatchesWithErrorHandling(client, batches);
500
+ if (results.length === 0) {
501
+ return {
502
+ success: false,
503
+ filePath,
504
+ totalLines,
505
+ batchCount,
506
+ error: `\u6240\u6709\u6279\u6B21\u5904\u7406\u5931\u8D25: ${errors.join("; ")}`,
507
+ partialErrors: errors
508
+ };
509
+ }
510
+ const mergedResult = mergeDetectionResults(results);
511
+ const formattedOutput = formatDetectionResultOutput(mergedResult, filePath, totalLines, batchCount);
512
+ return {
513
+ success: true,
514
+ filePath,
515
+ totalLines,
516
+ batchCount,
517
+ result: mergedResult,
518
+ formattedOutput,
519
+ partialErrors: errors.length > 0 ? errors : void 0
520
+ };
521
+ } catch (error) {
522
+ return {
523
+ success: false,
524
+ filePath,
525
+ totalLines: 0,
526
+ batchCount: 0,
527
+ error: error instanceof Error ? error.message : String(error)
528
+ };
529
+ }
530
+ }
531
+
532
+ // src/tools/findJsvmpDispatcherTool.ts
533
+ var FindJsvmpDispatcherInputSchema = {
534
+ filePath: z.string().describe("Path to the JavaScript file to analyze"),
535
+ charLimit: z.number().int().positive().optional().describe("Character limit for string truncation (default: 300)"),
536
+ maxTokensPerBatch: z.number().int().positive().optional().describe("Maximum tokens per batch for LLM analysis (default: 200000)")
537
+ };
538
+ var findJsvmpDispatcherTool = defineTool({
539
+ name: "find_jsvmp_dispatcher",
540
+ description: `Detect JSVMP (JavaScript Virtual Machine Protection) patterns in code using LLM analysis.
541
+
542
+ JSVMP is a code protection technique that converts JavaScript to bytecode executed by a virtual machine. This tool identifies:
543
+ - If-Else Dispatchers: Nested if-else chains for instruction dispatch
544
+ - Switch Dispatchers: Large switch statements (>20 cases) for opcode handling
545
+ - Instruction Arrays: Arrays storing bytecode instructions
546
+ - Stack Operations: Virtual stack push/pop patterns
547
+
548
+ Automatically splits large files into batches based on token limits and merges results.
549
+
550
+ Returns detection results with confidence levels (ultra_high, high, medium, low) and detailed descriptions.
551
+
552
+ Requires OPENAI_API_KEY environment variable. Optional: OPENAI_BASE_URL, OPENAI_MODEL.`,
553
+ schema: FindJsvmpDispatcherInputSchema,
554
+ handler: async (params) => {
555
+ const { filePath, charLimit, maxTokensPerBatch } = params;
556
+ const result = await findJsvmpDispatcher(filePath, {
557
+ charLimit: charLimit ?? 300,
558
+ maxTokensPerBatch: maxTokensPerBatch ?? 2e5
559
+ });
560
+ if (!result.success) {
561
+ throw new Error(result.error ?? "Detection failed");
562
+ }
563
+ return result.formattedOutput ?? "No output generated";
564
+ }
565
+ });
566
+
567
+ // src/tools/index.ts
568
+ var tools = [
569
+ findJsvmpDispatcherTool
570
+ ];
571
+
572
+ // src/server.ts
573
+ var server = new McpServer({
574
+ name: "ai-tools-mcp",
575
+ version: "1.0.0"
576
+ });
577
+ function registerTool(tool) {
578
+ const zodSchema = z2.object(tool.schema);
579
+ server.registerTool(
580
+ tool.name,
581
+ {
582
+ description: tool.description,
583
+ inputSchema: tool.schema
584
+ },
585
+ async (params, _extra) => {
586
+ try {
587
+ const validatedParams = zodSchema.parse(params);
588
+ const result = await tool.handler(validatedParams);
589
+ return {
590
+ content: [{ type: "text", text: result }]
591
+ };
592
+ } catch (error) {
593
+ const message = error instanceof Error ? error.message : String(error);
594
+ return {
595
+ content: [{ type: "text", text: `Error: ${message}` }],
596
+ isError: true
597
+ };
598
+ }
599
+ }
600
+ );
601
+ }
602
+ for (const tool of tools) {
603
+ registerTool(tool);
604
+ }
605
+ async function main() {
606
+ const transport = new StdioServerTransport();
607
+ await server.connect(transport);
608
+ console.error("AI Tools MCP Server running on stdio");
609
+ }
610
+ main().catch((error) => {
611
+ console.error("Fatal error:", error);
612
+ process.exit(1);
613
+ });
614
+ //# sourceMappingURL=server.js.map