cheatengine 5.8.14 → 5.8.15
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/bin/cheatengine +23 -67
- package/ce_mcp_server.js +367 -0
- package/package.json +9 -7
- package/src/base.js +235 -0
- package/src/pipe-client.js +316 -0
- package/src/tool-registry.js +887 -0
- package/ce_mcp_server.py +0 -2509
package/bin/cheatengine
CHANGED
|
@@ -1,89 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Cheat Engine MCP Server -
|
|
4
|
-
*
|
|
3
|
+
* Cheat Engine MCP Server - CLI Entry Point
|
|
4
|
+
* Works with: npm install -g, npx, and local installs
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const { spawn, execSync } = require('child_process');
|
|
8
7
|
const path = require('path');
|
|
9
8
|
const fs = require('fs');
|
|
10
9
|
|
|
11
|
-
//
|
|
10
|
+
// Debug mode
|
|
12
11
|
const DEBUG = process.env.DEBUG === '1';
|
|
13
12
|
function log(...args) {
|
|
14
13
|
if (DEBUG) console.error('[cheatengine]', ...args);
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
log('Starting cheatengine
|
|
16
|
+
log('Starting cheatengine MCP server...');
|
|
18
17
|
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
// 方法1: 通过 package.json 定位
|
|
22
|
-
try {
|
|
23
|
-
const pkgPath = require.resolve('cheatengine/package.json');
|
|
24
|
-
return path.dirname(pkgPath);
|
|
25
|
-
} catch (e) {
|
|
26
|
-
// 方法2: 通过当前文件相对路径
|
|
27
|
-
return path.join(__dirname, '..');
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const packageRoot = findPackageRoot();
|
|
18
|
+
// Get package root directory (__dirname is bin/ in the package)
|
|
19
|
+
const packageRoot = path.join(__dirname, '..');
|
|
32
20
|
log('Package root:', packageRoot);
|
|
33
21
|
|
|
34
|
-
const
|
|
35
|
-
log('
|
|
22
|
+
const serverScript = path.join(packageRoot, 'ce_mcp_server.js');
|
|
23
|
+
log('Server script:', serverScript);
|
|
36
24
|
|
|
37
|
-
//
|
|
38
|
-
if (!fs.existsSync(
|
|
39
|
-
console.error('[cheatengine]
|
|
40
|
-
console.error('
|
|
41
|
-
console.error('包根目录:', packageRoot);
|
|
25
|
+
// Verify script exists
|
|
26
|
+
if (!fs.existsSync(serverScript)) {
|
|
27
|
+
console.error('[cheatengine] Error: Cannot find ce_mcp_server.js');
|
|
28
|
+
console.error('Expected at:', serverScript);
|
|
42
29
|
process.exit(1);
|
|
43
30
|
}
|
|
44
31
|
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// Windows: 尝试 python, python3, py
|
|
49
|
-
for (const cmd of ['python', 'python3', 'py']) {
|
|
50
|
-
try {
|
|
51
|
-
execSync(`${cmd} --version`, { stdio: 'ignore' });
|
|
52
|
-
pythonCmd = cmd;
|
|
53
|
-
log('Found Python:', cmd);
|
|
54
|
-
break;
|
|
55
|
-
} catch (e) {
|
|
56
|
-
// 尝试下一个
|
|
57
|
-
}
|
|
58
|
-
}
|
|
32
|
+
// Ensure stdin/stdout are in proper mode for MCP
|
|
33
|
+
if (process.stdin.isTTY) {
|
|
34
|
+
process.stdin.setRawMode(false);
|
|
59
35
|
}
|
|
60
36
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
log('Python process spawned, PID:', proc.pid);
|
|
69
|
-
|
|
70
|
-
// 透传stdin/stdout/stderr
|
|
71
|
-
process.stdin.pipe(proc.stdin);
|
|
72
|
-
proc.stdout.pipe(process.stdout);
|
|
73
|
-
proc.stderr.pipe(process.stderr);
|
|
74
|
-
|
|
75
|
-
proc.on('exit', (code, signal) => {
|
|
76
|
-
log('Python process exited, code:', code, 'signal:', signal);
|
|
77
|
-
process.exit(code ?? 0);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
proc.on('error', (err) => {
|
|
81
|
-
console.error('[cheatengine] 启动失败:', err.message);
|
|
82
|
-
console.error('请确保已安装Python和pywin32: pip install pywin32');
|
|
37
|
+
// Run the server
|
|
38
|
+
log('Loading MCP server...');
|
|
39
|
+
try {
|
|
40
|
+
require(serverScript);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.error('[cheatengine] Failed to start:', err.message);
|
|
43
|
+
console.error(err.stack);
|
|
83
44
|
process.exit(1);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// 如果3秒内没有退出,说明启动成功
|
|
87
|
-
setTimeout(() => {
|
|
88
|
-
log('Python process still running after 3s');
|
|
89
|
-
}, 3000);
|
|
45
|
+
}
|
package/ce_mcp_server.js
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// -*- coding: utf-8 -*-
|
|
3
|
+
/**
|
|
4
|
+
* Cheat Engine MCP Server - Main Server Implementation
|
|
5
|
+
* JavaScript/Node.js version
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { Config, TimeoutManager, MetricsCollector, log } = require('./src/base');
|
|
9
|
+
const { PipeClient } = require('./src/pipe-client');
|
|
10
|
+
const { ToolRegistry } = require('./src/tool-registry');
|
|
11
|
+
|
|
12
|
+
// ============ MCP Server ============
|
|
13
|
+
class CEMCPServer {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.pipeClient = new PipeClient();
|
|
16
|
+
this.pipeClient.startBackgroundReconnect();
|
|
17
|
+
this.toolRegistry = new ToolRegistry();
|
|
18
|
+
this.timeoutManager = new TimeoutManager();
|
|
19
|
+
this.metricsCollector = new MetricsCollector();
|
|
20
|
+
this.requestCount = 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async _diagnoseConnection() {
|
|
24
|
+
const checks = [];
|
|
25
|
+
const suggestions = [];
|
|
26
|
+
let allPassed = true;
|
|
27
|
+
|
|
28
|
+
// Check 1: Pipe existence
|
|
29
|
+
let pipeExists = false;
|
|
30
|
+
try {
|
|
31
|
+
const fs = require('fs');
|
|
32
|
+
// Try to check if pipe exists (Windows specific)
|
|
33
|
+
const testClient = new PipeClient();
|
|
34
|
+
const connected = await testClient.connect(false, 1000);
|
|
35
|
+
if (connected) {
|
|
36
|
+
pipeExists = true;
|
|
37
|
+
testClient.stop();
|
|
38
|
+
checks.push({
|
|
39
|
+
name: 'pipe_exists',
|
|
40
|
+
passed: true,
|
|
41
|
+
message: `Pipe '${Config.PIPE_NAME}' available`,
|
|
42
|
+
});
|
|
43
|
+
} else {
|
|
44
|
+
throw new Error('Connection failed');
|
|
45
|
+
}
|
|
46
|
+
} catch (err) {
|
|
47
|
+
allPassed = false;
|
|
48
|
+
checks.push({
|
|
49
|
+
name: 'pipe_exists',
|
|
50
|
+
passed: false,
|
|
51
|
+
message: 'Pipe not found - CE bridge not running',
|
|
52
|
+
});
|
|
53
|
+
suggestions.push(
|
|
54
|
+
'1. Open Cheat Engine',
|
|
55
|
+
"2. Table -> Show Cheat Table Lua Script",
|
|
56
|
+
'3. Paste ce_mcp_bridge.lua content and Execute',
|
|
57
|
+
"4. Look for '[CheatEngine-MCP] Bridge started' message"
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check 2: CE process
|
|
62
|
+
try {
|
|
63
|
+
const { execSync } = require('child_process');
|
|
64
|
+
const result = execSync('tasklist /FI "IMAGENAME eq cheatengine*" /FO CSV /NH', {
|
|
65
|
+
encoding: 'utf-8',
|
|
66
|
+
timeout: 5000
|
|
67
|
+
});
|
|
68
|
+
const ceRunning = result.toLowerCase().includes('cheatengine');
|
|
69
|
+
if (ceRunning) {
|
|
70
|
+
checks.push({
|
|
71
|
+
name: 'ce_process',
|
|
72
|
+
passed: true,
|
|
73
|
+
message: 'Cheat Engine is running',
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
allPassed = false;
|
|
77
|
+
checks.push({
|
|
78
|
+
name: 'ce_process',
|
|
79
|
+
passed: false,
|
|
80
|
+
message: 'Cheat Engine not found',
|
|
81
|
+
});
|
|
82
|
+
suggestions.unshift('Start Cheat Engine first!');
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
checks.push({
|
|
86
|
+
name: 'ce_process',
|
|
87
|
+
passed: null,
|
|
88
|
+
message: `Could not check: ${err.message}`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check 3: Custom pipe name
|
|
93
|
+
const customPipe = process.env.CE_MCP_PIPE_NAME;
|
|
94
|
+
if (customPipe) {
|
|
95
|
+
checks.push({
|
|
96
|
+
name: 'custom_pipe',
|
|
97
|
+
passed: null,
|
|
98
|
+
message: `Using custom pipe: ${customPipe}`,
|
|
99
|
+
});
|
|
100
|
+
suggestions.push(`Ensure CE Lua also uses pipe name: ${customPipe}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
status: (allPassed && pipeExists) ? 'ok' : 'failed',
|
|
105
|
+
pipe_name: Config.PIPE_NAME,
|
|
106
|
+
checks,
|
|
107
|
+
suggestions: suggestions.length > 0 ? suggestions : ['Connection should work - try again'],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async executeTool(name, args) {
|
|
112
|
+
const luaCmd = this.toolRegistry.getLuaCommand(name);
|
|
113
|
+
if (!luaCmd) {
|
|
114
|
+
return { error: `Unknown tool: ${name}` };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const timeoutSeconds = this.timeoutManager.getTimeout(name);
|
|
118
|
+
const timeoutMs = timeoutSeconds * 1000;
|
|
119
|
+
const startTime = Date.now();
|
|
120
|
+
|
|
121
|
+
const response = await this.pipeClient.sendReceive(
|
|
122
|
+
{ command: luaCmd, params: args },
|
|
123
|
+
timeoutMs,
|
|
124
|
+
name
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const duration = (Date.now() - startTime) / 1000;
|
|
128
|
+
const isError = response.error !== undefined;
|
|
129
|
+
this.metricsCollector.recordCall(name, duration, isError);
|
|
130
|
+
|
|
131
|
+
if (isError) {
|
|
132
|
+
// For ce_ping, return diagnostic info on connection failure
|
|
133
|
+
if (name === 'ce_ping') {
|
|
134
|
+
const diag = await this._diagnoseConnection();
|
|
135
|
+
return { error: response.error, diagnostic: diag };
|
|
136
|
+
}
|
|
137
|
+
if (response.timeout_info) {
|
|
138
|
+
return {
|
|
139
|
+
error: response.error,
|
|
140
|
+
timeout_info: response.timeout_info,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return { error: response.error };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const result = response.result || response;
|
|
147
|
+
|
|
148
|
+
// Attach health and server metrics to ce_ping response
|
|
149
|
+
if (name === 'ce_ping') {
|
|
150
|
+
const healthMetrics = this.pipeClient.getHealthMetrics();
|
|
151
|
+
const serverMetrics = this.metricsCollector.getSummary();
|
|
152
|
+
if (typeof result === 'object' && result !== null) {
|
|
153
|
+
result.connection_health = healthMetrics;
|
|
154
|
+
result.server_metrics = serverMetrics;
|
|
155
|
+
} else {
|
|
156
|
+
return {
|
|
157
|
+
result,
|
|
158
|
+
connection_health: healthMetrics,
|
|
159
|
+
server_metrics: serverMetrics,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
handleRequest(req) {
|
|
168
|
+
const method = req.method || '';
|
|
169
|
+
const reqId = req.id;
|
|
170
|
+
const params = req.params || {};
|
|
171
|
+
|
|
172
|
+
if (method === 'initialize') {
|
|
173
|
+
return this._handleInitialize(reqId);
|
|
174
|
+
} else if (method === 'notifications/initialized') {
|
|
175
|
+
return null;
|
|
176
|
+
} else if (method === 'tools/list') {
|
|
177
|
+
return this._handleToolsList(reqId);
|
|
178
|
+
} else if (method === 'tools/call') {
|
|
179
|
+
return this._handleToolsCall(reqId, params);
|
|
180
|
+
} else {
|
|
181
|
+
return this._errorResponse(reqId, -32601, `Method not found: ${method}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_handleInitialize(reqId) {
|
|
186
|
+
return {
|
|
187
|
+
jsonrpc: '2.0',
|
|
188
|
+
id: reqId,
|
|
189
|
+
result: {
|
|
190
|
+
protocolVersion: '2024-11-05',
|
|
191
|
+
capabilities: { tools: {} },
|
|
192
|
+
serverInfo: { name: 'cheatengine-mcp-bridge', version: '5.8.14' },
|
|
193
|
+
instructions: (
|
|
194
|
+
'# Cheat Engine MCP - AI Usage Guide\n\n' +
|
|
195
|
+
'## Tool Selection Decision Tree\n\n' +
|
|
196
|
+
'### Q: Do I know the address?\n' +
|
|
197
|
+
'- NO, but can observe value changes -> ce_scan_new workflow (value hunting)\n' +
|
|
198
|
+
'- YES, need stable pointer -> ce_find_pointer_path (auto) or manual F5+value_scan\n' +
|
|
199
|
+
'- YES, want to read/write -> ce_read_memory / ce_write_memory\n\n' +
|
|
200
|
+
'### Q: What am I searching for?\n' +
|
|
201
|
+
'- Game values (health, gold) -> ce_scan_new (NOT ce_value_scan!)\n' +
|
|
202
|
+
'- Code signatures -> ce_aob_scan (supports ?? wildcards)\n' +
|
|
203
|
+
'- Pointer storage locations -> ce_value_scan (after getting register value from F5)\n\n' +
|
|
204
|
+
'### Q: How to analyze code?\n' +
|
|
205
|
+
'- View execution flow -> ce_break_and_trace (dynamic, real execution)\n' +
|
|
206
|
+
'- Safely understand algorithm -> ce_symbolic_trace (static, no execution)\n' +
|
|
207
|
+
'- Function structure -> ce_build_cfg + ce_detect_patterns\n\n' +
|
|
208
|
+
'## Workflow Templates\n\n' +
|
|
209
|
+
'### 1. Value Hunting (find unknown address)\n' +
|
|
210
|
+
"1. ce_attach_process(target='game.exe')\n" +
|
|
211
|
+
"2. ce_scan_new(value='100', type='dword') -> returns session_id\n" +
|
|
212
|
+
'3. [Take damage in game, health becomes 95]\n' +
|
|
213
|
+
"4. ce_scan_next(session_id, value='95', scan_type='exact') -> count decreases\n" +
|
|
214
|
+
'5. Repeat steps 3-4 until count < 10\n' +
|
|
215
|
+
'6. ce_scan_results(session_id) -> get candidate addresses\n' +
|
|
216
|
+
"7. ce_read_memory(address, type='dword') -> verify correct address\n" +
|
|
217
|
+
'8. ce_scan_close(session_id) -> release resources (max 5 concurrent!)\n\n' +
|
|
218
|
+
'### 2. Pointer Tracing (make address stable)\n' +
|
|
219
|
+
"Method A (auto, try first): ce_find_pointer_path(address='0x12345678')\n" +
|
|
220
|
+
'Method B (manual, if A fails):\n' +
|
|
221
|
+
"1. ce_find_what_accesses(address='0x12345678', duration_ms=10000)\n" +
|
|
222
|
+
'2. Get register value from result (e.g., RBX=0x255D5E758)\n' +
|
|
223
|
+
"3. ce_value_scan(value='0x255D5E758', type='qword')\n" +
|
|
224
|
+
'4. Find result with isStatic=true -> that\'s your base pointer!\n\n' +
|
|
225
|
+
'### 3. Function Monitoring (non-blocking)\n' +
|
|
226
|
+
"1. ce_hook_function(address='game.exe+12345', name='damageHook')\n" +
|
|
227
|
+
'2. [Let game run, trigger events]\n' +
|
|
228
|
+
"3. ce_get_hook_log(name='damageHook') -> captured args (x64: RCX,RDX,R8,R9)\n" +
|
|
229
|
+
"4. ce_unhook_function(name='damageHook') -> cleanup when done\n\n" +
|
|
230
|
+
'## Tool Comparison (Correct vs Wrong)\n' +
|
|
231
|
+
'| Purpose | Correct | Wrong |\n' +
|
|
232
|
+
'| Find game values | ce_scan_new | ce_value_scan |\n' +
|
|
233
|
+
'| Find code signature | ce_aob_scan | ce_scan_new |\n' +
|
|
234
|
+
'| Batch read memory | ce_read_memory_batch | multiple ce_read_memory |\n' +
|
|
235
|
+
'| Monitor function | ce_hook_function | ce_break_and_get_regs |\n\n' +
|
|
236
|
+
'## System Limits\n' +
|
|
237
|
+
'- Scan sessions: max 5 (auto-expire after 5 min inactivity)\n' +
|
|
238
|
+
'- Hardware breakpoints: max 4 (shared by F5/F6/trace)\n' +
|
|
239
|
+
'- Hook buffer: 64 records (circular, oldest overwritten when full)\n' +
|
|
240
|
+
'- Pointer depth: max 10 levels\n' +
|
|
241
|
+
'- Message size: 10MB per request/response\n\n' +
|
|
242
|
+
'## Common Errors\n' +
|
|
243
|
+
"- 'Process not attached' -> call ce_attach_process first\n" +
|
|
244
|
+
"- 'Too many scan sessions' -> ce_scan_list() then ce_scan_close()\n" +
|
|
245
|
+
"- 'Hardware breakpoint limit' -> ce_cleanup() to clear all\n" +
|
|
246
|
+
"- 'Hook name already exists' -> ce_unhook_function(name) first"
|
|
247
|
+
),
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
_handleToolsList(reqId) {
|
|
253
|
+
return {
|
|
254
|
+
jsonrpc: '2.0',
|
|
255
|
+
id: reqId,
|
|
256
|
+
result: { tools: this.toolRegistry.getAllSchemas() },
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async _handleToolsCall(reqId, params) {
|
|
261
|
+
const toolName = params.name || '';
|
|
262
|
+
const toolArgs = params.arguments || {};
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const result = await this.executeTool(toolName, toolArgs);
|
|
266
|
+
|
|
267
|
+
// Check if error: has error field, success=false, and no partial results
|
|
268
|
+
let isError = false;
|
|
269
|
+
if (typeof result === 'object' && result !== null && result.error) {
|
|
270
|
+
const hasPartialResult = ['chain', 'path', 'partialCENotation', 'finalAddress'].some(k => k in result);
|
|
271
|
+
isError = result.success === false && !hasPartialResult;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const text = isError
|
|
275
|
+
? `Error: ${result.error}`
|
|
276
|
+
: JSON.stringify(result, null, 2);
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
jsonrpc: '2.0',
|
|
280
|
+
id: reqId,
|
|
281
|
+
result: {
|
|
282
|
+
content: [{ type: 'text', text }],
|
|
283
|
+
isError: isError,
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
} catch (err) {
|
|
287
|
+
return this._errorResponse(reqId, -32603, err.message);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
_errorResponse(reqId, code, message) {
|
|
292
|
+
return {
|
|
293
|
+
jsonrpc: '2.0',
|
|
294
|
+
id: reqId,
|
|
295
|
+
error: { code, message },
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async run() {
|
|
300
|
+
log.info('Cheat Engine MCP Bridge Started. Waiting for input...');
|
|
301
|
+
log.info(`Node.js version: ${process.version}`);
|
|
302
|
+
|
|
303
|
+
const readline = require('readline');
|
|
304
|
+
const rl = readline.createInterface({
|
|
305
|
+
input: process.stdin,
|
|
306
|
+
output: process.stdout,
|
|
307
|
+
terminal: false,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
rl.on('line', async (line) => {
|
|
311
|
+
if (!line.trim()) return;
|
|
312
|
+
|
|
313
|
+
this.requestCount++;
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
const request = JSON.parse(line);
|
|
317
|
+
const response = this.handleRequest(request);
|
|
318
|
+
|
|
319
|
+
if (response) {
|
|
320
|
+
// Handle async tool calls
|
|
321
|
+
if (response instanceof Promise) {
|
|
322
|
+
const result = await response;
|
|
323
|
+
process.stdout.write(JSON.stringify(result) + '\n');
|
|
324
|
+
} else {
|
|
325
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} catch (err) {
|
|
329
|
+
if (err instanceof SyntaxError) {
|
|
330
|
+
log.error(`Failed to decode JSON: ${err.message}`);
|
|
331
|
+
} else {
|
|
332
|
+
log.error(`Critical Error (request #${this.requestCount}): ${err.stack}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
rl.on('close', () => {
|
|
338
|
+
log.info('Received end of input');
|
|
339
|
+
this.pipeClient.stop();
|
|
340
|
+
log.info(`Server Stopped (processed ${this.requestCount} requests)`);
|
|
341
|
+
process.exit(0);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Handle graceful shutdown
|
|
345
|
+
process.on('SIGINT', () => {
|
|
346
|
+
log.info('Received interrupt signal');
|
|
347
|
+
this.pipeClient.stop();
|
|
348
|
+
log.info(`Server Stopped (processed ${this.requestCount} requests)`);
|
|
349
|
+
process.exit(0);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
process.on('SIGTERM', () => {
|
|
353
|
+
log.info('Received terminate signal');
|
|
354
|
+
this.pipeClient.stop();
|
|
355
|
+
log.info(`Server Stopped (processed ${this.requestCount} requests)`);
|
|
356
|
+
process.exit(0);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ============ Entry Point ============
|
|
362
|
+
function main() {
|
|
363
|
+
const server = new CEMCPServer();
|
|
364
|
+
server.run();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cheatengine",
|
|
3
|
-
"version": "5.8.
|
|
3
|
+
"version": "5.8.15",
|
|
4
4
|
"description": "Cheat Engine MCP Server - AI-assisted reverse engineering bridge",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "ce_mcp_server.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cheatengine": "bin/cheatengine"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"start": "node
|
|
10
|
+
"start": "node ce_mcp_server.js",
|
|
11
|
+
"test": "node ce_mcp_server.js"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=14.0.0"
|
|
11
15
|
},
|
|
12
16
|
"keywords": [
|
|
13
17
|
"mcp",
|
|
@@ -19,12 +23,10 @@
|
|
|
19
23
|
],
|
|
20
24
|
"author": "",
|
|
21
25
|
"license": "MIT",
|
|
22
|
-
"engines": {
|
|
23
|
-
"node": ">=14.0.0"
|
|
24
|
-
},
|
|
25
26
|
"files": [
|
|
26
27
|
"bin/",
|
|
27
|
-
"
|
|
28
|
+
"src/",
|
|
29
|
+
"ce_mcp_server.js",
|
|
28
30
|
"README.md",
|
|
29
31
|
"README_CN.md"
|
|
30
32
|
]
|