@nataliapc/mcp-openmsx 1.0.2 → 1.1.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.
- package/README.md +11 -10
- package/dist/openmsx.js +18 -4
- package/dist/server.js +169 -201
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,13 +12,13 @@ Currently, the MCP server requires Linux to run. It has not been tested on Windo
|
|
|
12
12
|
|
|
13
13
|
This project creates a bridge between modern AI-assisted development (e.g. GitHub Copilot, Claude Desktop) and retro computing (MSX systems) by providing:
|
|
14
14
|
|
|
15
|
-
- **Emulator Control**: Launch, configure,
|
|
16
|
-
- **Media Management**: Handle ROM cartridges, floppy disks, and cassette tapes
|
|
17
|
-
- **Debugging Tools**: Full CPU debugging with breakpoints, memory inspection, and step execution
|
|
18
|
-
- **Video Control**: VDP register manipulation and screen capture
|
|
19
|
-
- **Memory Operations**: Read/write RAM, VRAM, and I/O port access
|
|
20
|
-
- **Automation**: Keyboard input simulation and savestate management
|
|
21
|
-
- **Dual Transport**: Support for both stdio and HTTP transports
|
|
15
|
+
- **Emulator Control**: Launch, configure, manage openMSX instances, and replay timelines.
|
|
16
|
+
- **Media Management**: Handle ROM cartridges, floppy disks, and cassette tapes.
|
|
17
|
+
- **Debugging Tools**: Full CPU debugging with breakpoints, memory inspection, and step execution.
|
|
18
|
+
- **Video Control**: VDP register manipulation and screen capture.
|
|
19
|
+
- **Memory Operations**: Read/write RAM, VRAM, and I/O port access.
|
|
20
|
+
- **Automation**: Keyboard input simulation and savestate management.
|
|
21
|
+
- **Dual Transport**: Support for both stdio and HTTP transports.
|
|
22
22
|
|
|
23
23
|
## 🏗️ Architecture
|
|
24
24
|
|
|
@@ -44,7 +44,8 @@ The MCP server translates high-level commands from your Copilot AI into `TCL` co
|
|
|
44
44
|
## 🛠️ Available MCP Tools
|
|
45
45
|
|
|
46
46
|
### Emulator Control Tools
|
|
47
|
-
- `emu_control`: Controls an openMSX emulator: _`launch`, `close`, `powerOn`, `powerOff`, `reset`, `getEmulatorSpeed`, `setEmulatorSpeed`, `machineList`, `extensionList`_.
|
|
47
|
+
- `emu_control`: Controls an openMSX emulator: _`launch`, `close`, `powerOn`, `powerOff`, `reset`, `getEmulatorSpeed`, `setEmulatorSpeed`, `machineList`, `extensionList`, `wait`_.
|
|
48
|
+
- `emu_replay`: Controls emulation timeline: _`start`, `strop`, `status`, `goBack`, `absoluteGoto`, `truncate`, `saveReplay`, `loadReplay`_.
|
|
48
49
|
- `emu_info`: Obtain informacion about the current emulated machine: _`getStatus`, `getSlotsMap`, `getIOPortsMap`_.
|
|
49
50
|
- `emu_media`: Manage ROM, disk, and tape media: _`tapeInsert`, `tapeRewind`, `tapeEject`, `romInsert`, `romEject`, `diskInsert`, `diskInsertFolder`, `diskEject`_.
|
|
50
51
|
- `emu_vdp`: Manage VDP (Video Display Processor): _`getPalette`, `getRegisters`, `getRegisterValue`, `setRegisterValue`, `screenGetMode`, `screenGetFullText`_.
|
|
@@ -127,8 +128,8 @@ Add to your `claude_desktop_config.json`:
|
|
|
127
128
|
|----------|-------------|---------------|---------|
|
|
128
129
|
| `OPENMSX_EXECUTABLE` | Path or command to the openMSX executable | `openmsx` | `/usr/local/bin/openmsx` |
|
|
129
130
|
| `OPENMSX_SHARE_DIR` | Directory containing openMSX data files (machines, extensions, etc.) | System dependent | `/home/myuser/.openmsx/share` |
|
|
130
|
-
| `OPENMSX_SCREENSHOT_DIR` | Directory where screenshots will be saved |
|
|
131
|
-
| `OPENMSX_SCREENDUMP_DIR` | Directory where screen dumps will be saved |
|
|
131
|
+
| `OPENMSX_SCREENSHOT_DIR` | Directory where screenshots will be saved | Default for openmsx | `/myproject/screenshots` |
|
|
132
|
+
| `OPENMSX_SCREENDUMP_DIR` | Directory where screen dumps will be saved | Default for openmsx | `/myproject/screendumps` |
|
|
132
133
|
| `MCP_TRANSPORT` | Transport mode (`stdio` or `http`) | `stdio` | `http` |
|
|
133
134
|
| `MCP_HTTP_PORT` | Port number for HTTP transport mode | `3000` | `8080` |
|
|
134
135
|
|
package/dist/openmsx.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import fs from "fs/promises";
|
|
8
8
|
import { extractDescriptionFromXML, decodeHtmlEntities } from "./utils.js";
|
|
9
9
|
import { spawn } from 'child_process';
|
|
10
|
+
import path from 'path';
|
|
10
11
|
/**
|
|
11
12
|
* OpenMSX class for controlling the openMSX emulator via TCL commands over TCP socket
|
|
12
13
|
*/
|
|
@@ -89,8 +90,21 @@ export class OpenMSX {
|
|
|
89
90
|
this.sendCommand('set renderer SDLGL-PP');
|
|
90
91
|
// set machine on
|
|
91
92
|
this.sendCommand('set power on');
|
|
93
|
+
// start reverse replay mode
|
|
94
|
+
this.sendCommand('reverse start');
|
|
92
95
|
// Return success message
|
|
93
|
-
|
|
96
|
+
let result = 'Ok: openMSX emulator launched successfully';
|
|
97
|
+
if (machine) {
|
|
98
|
+
result += ` with machine "${machine}"`;
|
|
99
|
+
}
|
|
100
|
+
if (extensions && extensions.length > 0) {
|
|
101
|
+
if (machine) {
|
|
102
|
+
result += ' and';
|
|
103
|
+
}
|
|
104
|
+
result += ` with extensions: "${extensions.join('", "')}"`;
|
|
105
|
+
}
|
|
106
|
+
result += ', is powered on, and replay mode is started.';
|
|
107
|
+
safeResolve(result);
|
|
94
108
|
}
|
|
95
109
|
catch (error) {
|
|
96
110
|
safeResolve(`Error: Failed to send control commands - ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
@@ -179,7 +193,7 @@ export class OpenMSX {
|
|
|
179
193
|
const trimmedLine = param.trim();
|
|
180
194
|
if (trimmedLine) {
|
|
181
195
|
const value = await this.sendCommand(`machine_info ${trimmedLine}`);
|
|
182
|
-
machineInfo[trimmedLine] = value;
|
|
196
|
+
machineInfo[trimmedLine] = value.trim();
|
|
183
197
|
}
|
|
184
198
|
}
|
|
185
199
|
return JSON.stringify(machineInfo, null, 2);
|
|
@@ -205,7 +219,7 @@ export class OpenMSX {
|
|
|
205
219
|
.map(async (file) => {
|
|
206
220
|
return {
|
|
207
221
|
name: file.replace('.xml', ''),
|
|
208
|
-
description: await extractDescriptionFromXML(
|
|
222
|
+
description: await extractDescriptionFromXML(path.join(machinesDirectory, file))
|
|
209
223
|
};
|
|
210
224
|
}));
|
|
211
225
|
}
|
|
@@ -232,7 +246,7 @@ export class OpenMSX {
|
|
|
232
246
|
.map(async (file) => {
|
|
233
247
|
return {
|
|
234
248
|
name: file.replace('.xml', ''),
|
|
235
|
-
description: await extractDescriptionFromXML(
|
|
249
|
+
description: await extractDescriptionFromXML(path.join(extensionDirectory, file))
|
|
236
250
|
};
|
|
237
251
|
}));
|
|
238
252
|
}
|
package/dist/server.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* through TCL commands via stdio.
|
|
7
7
|
*
|
|
8
8
|
* @package @nataliapc/mcp-openmsx
|
|
9
|
-
* @version 1.
|
|
9
|
+
* @version 1.1.2
|
|
10
10
|
* @author Natalia Pujol Cremades (@nataliapc)
|
|
11
11
|
* @license GPL2
|
|
12
12
|
*/
|
|
@@ -21,7 +21,7 @@ import fs from "fs/promises";
|
|
|
21
21
|
import path from "path";
|
|
22
22
|
import { openMSXInstance } from "./openmsx.js";
|
|
23
23
|
// Version info for CLI
|
|
24
|
-
const PACKAGE_VERSION = "1.
|
|
24
|
+
const PACKAGE_VERSION = "1.1.2";
|
|
25
25
|
// Defaults for openMSX paths
|
|
26
26
|
var OPENMSX_EXECUTABLE = 'openmsx';
|
|
27
27
|
var OPENMSX_SHARE_DIR = '/usr/share/openmsx';
|
|
@@ -39,41 +39,30 @@ function registerAllTools(server) {
|
|
|
39
39
|
"emu_control",
|
|
40
40
|
// Description of the tool (what it does)
|
|
41
41
|
"Controls an openMSX emulator. Commands: " +
|
|
42
|
-
"'launch [machine] [extensions]': opens a powered
|
|
42
|
+
"'launch [machine] [extensions]': opens a powered-on openMSX emulator; machine and extensions parameters can be specified; use 'machineList' and 'extensionList' tools to obtain valid values. " +
|
|
43
43
|
"'close': closes the openMSX emulator. " +
|
|
44
44
|
"'powerOn': powers on the openMSX emulator. " +
|
|
45
45
|
"'powerOff': powers off the openMSX emulator. " +
|
|
46
46
|
"'reset': resets the current machine. " +
|
|
47
|
-
"'getEmulatorSpeed':
|
|
48
|
-
"'setEmulatorSpeed <emuspeed>':
|
|
49
|
-
"'machineList':
|
|
50
|
-
"'extensionList':
|
|
47
|
+
"'getEmulatorSpeed': gets the current emulator speed as a percentage, default is 100. " +
|
|
48
|
+
"'setEmulatorSpeed <emuspeed>': sets the emulator speed as a percentage, valid values are 1-10000, default is 100. " +
|
|
49
|
+
"'machineList': gets a list of all available MSX machines that can be emulated with openMSX. " +
|
|
50
|
+
"'extensionList': gets a list of all available MSX extensions that can be used with openMSX. " +
|
|
51
|
+
"'wait <seconds>': performs a wait for the specified number of seconds, default is 2. ",
|
|
51
52
|
// Schema for the tool (input validation)
|
|
52
53
|
{
|
|
53
|
-
command: z.enum(["launch", "close", "powerOn", "powerOff", "reset", "getEmulatorSpeed", "setEmulatorSpeed", "machineList", "extensionList"]),
|
|
54
|
+
command: z.enum(["launch", "close", "powerOn", "powerOff", "reset", "getEmulatorSpeed", "setEmulatorSpeed", "machineList", "extensionList", "wait"]),
|
|
54
55
|
machine: z.string().min(1).max(100).optional(),
|
|
55
56
|
extensions: z.array(z.string().min(1).max(100)).optional(),
|
|
56
57
|
emuspeed: z.number().min(1).max(10000).optional().default(100),
|
|
58
|
+
seconds: z.number().min(1).max(10).optional().default(2), // Seconds to wait
|
|
57
59
|
},
|
|
58
60
|
// Handler for the tool (function to be executed when the tool is called)
|
|
59
|
-
async ({ command, machine, extensions, emuspeed }) => {
|
|
61
|
+
async ({ command, machine, extensions, emuspeed, seconds }) => {
|
|
60
62
|
let result = "Error";
|
|
61
63
|
switch (command) {
|
|
62
64
|
case "launch":
|
|
63
65
|
result = await openMSXInstance.emu_launch(OPENMSX_EXECUTABLE, machine || "", extensions || []);
|
|
64
|
-
// Check if launch was successful
|
|
65
|
-
if (result === "Ok") {
|
|
66
|
-
result = "openMSX emulator launched";
|
|
67
|
-
if (machine) {
|
|
68
|
-
result += ` with machine "${machine}"`;
|
|
69
|
-
}
|
|
70
|
-
if (extensions && extensions.length > 0) {
|
|
71
|
-
if (machine) {
|
|
72
|
-
result += ' and ';
|
|
73
|
-
}
|
|
74
|
-
result += ` with extensions: ${extensions.join(', ')}`;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
66
|
break;
|
|
78
67
|
case "close":
|
|
79
68
|
result = await openMSXInstance.emu_close();
|
|
@@ -104,17 +93,18 @@ function registerAllTools(server) {
|
|
|
104
93
|
case "extensionList":
|
|
105
94
|
result = await openMSXInstance.getExtensionList(EXTENSIONS_DIR);
|
|
106
95
|
break;
|
|
96
|
+
case "wait":
|
|
97
|
+
await new Promise(resolve => setTimeout(resolve, seconds * 1000));
|
|
98
|
+
result = `Waited for ${seconds} seconds.`;
|
|
99
|
+
break;
|
|
107
100
|
default:
|
|
108
101
|
result = `Error: Unknown command "${command}".`;
|
|
109
102
|
break;
|
|
110
103
|
}
|
|
111
104
|
// Return result with proper format for MCP
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
text: result === '' ? 'Ok' : result,
|
|
116
|
-
}],
|
|
117
|
-
};
|
|
105
|
+
return getResponseContent([
|
|
106
|
+
result
|
|
107
|
+
]);
|
|
118
108
|
});
|
|
119
109
|
server.tool(
|
|
120
110
|
// Name of the tool (used to call it)
|
|
@@ -166,21 +156,15 @@ function registerAllTools(server) {
|
|
|
166
156
|
tclCommand = "diska eject";
|
|
167
157
|
break;
|
|
168
158
|
default:
|
|
169
|
-
return
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
text: `Error: Unknown emulator media command "${command}".`,
|
|
173
|
-
}],
|
|
174
|
-
};
|
|
159
|
+
return getResponseContent([
|
|
160
|
+
`Error: Unknown emulator media command "${command}".`
|
|
161
|
+
]);
|
|
175
162
|
}
|
|
176
163
|
const response = await openMSXInstance.sendCommand(tclCommand);
|
|
177
164
|
// Return the response from openMSX
|
|
178
|
-
return
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
text: response === '' ? 'Ok' : response,
|
|
182
|
-
}],
|
|
183
|
-
};
|
|
165
|
+
return getResponseContent([
|
|
166
|
+
response
|
|
167
|
+
]);
|
|
184
168
|
});
|
|
185
169
|
server.tool(
|
|
186
170
|
// Name of the tool (used to call it)
|
|
@@ -199,12 +183,9 @@ function registerAllTools(server) {
|
|
|
199
183
|
let tclCommand;
|
|
200
184
|
switch (command) {
|
|
201
185
|
case "getStatus":
|
|
202
|
-
return
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
text: await openMSXInstance.emu_status(),
|
|
206
|
-
}],
|
|
207
|
-
};
|
|
186
|
+
return getResponseContent([
|
|
187
|
+
await openMSXInstance.emu_status()
|
|
188
|
+
]);
|
|
208
189
|
case "getSlotsMap":
|
|
209
190
|
tclCommand = "slotmap";
|
|
210
191
|
break;
|
|
@@ -212,20 +193,14 @@ function registerAllTools(server) {
|
|
|
212
193
|
tclCommand = "iomap";
|
|
213
194
|
break;
|
|
214
195
|
default:
|
|
215
|
-
return
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
text: `Error: Unknown emulator info command "${command}".`,
|
|
219
|
-
}],
|
|
220
|
-
};
|
|
196
|
+
return getResponseContent([
|
|
197
|
+
`Error: Unknown emulator info command "${command}".`
|
|
198
|
+
]);
|
|
221
199
|
}
|
|
222
200
|
const response = await openMSXInstance.sendCommand(tclCommand);
|
|
223
|
-
return
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
text: response,
|
|
227
|
-
}],
|
|
228
|
-
};
|
|
201
|
+
return getResponseContent([
|
|
202
|
+
response
|
|
203
|
+
]);
|
|
229
204
|
});
|
|
230
205
|
server.tool(
|
|
231
206
|
// Name of the tool (used to call it)
|
|
@@ -265,35 +240,18 @@ function registerAllTools(server) {
|
|
|
265
240
|
break;
|
|
266
241
|
case "screenGetFullText":
|
|
267
242
|
const response = await openMSXInstance.sendCommand('get_screen');
|
|
268
|
-
return response.startsWith('Error:') ?
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
text: response,
|
|
272
|
-
}]
|
|
273
|
-
} : {
|
|
274
|
-
content: [{
|
|
275
|
-
type: "text",
|
|
276
|
-
text: "The screen text is:",
|
|
277
|
-
}, {
|
|
278
|
-
type: "text",
|
|
279
|
-
text: response,
|
|
280
|
-
}],
|
|
281
|
-
};
|
|
243
|
+
return response.startsWith('Error:') ?
|
|
244
|
+
getResponseContent([response]) :
|
|
245
|
+
getResponseContent(["The screen text is:", response]);
|
|
282
246
|
default:
|
|
283
|
-
return
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
text: `Error: Unknown emulator vdp command "${command}".`,
|
|
287
|
-
}],
|
|
288
|
-
};
|
|
247
|
+
return getResponseContent([
|
|
248
|
+
`Error: Unknown emulator vdp command "${command}".`
|
|
249
|
+
]);
|
|
289
250
|
}
|
|
290
251
|
const response = await openMSXInstance.sendCommand(tclCommand);
|
|
291
|
-
return
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
text: response === "" ? "Ok" : response,
|
|
295
|
-
}],
|
|
296
|
-
};
|
|
252
|
+
return getResponseContent([
|
|
253
|
+
response
|
|
254
|
+
]);
|
|
297
255
|
});
|
|
298
256
|
server.tool(
|
|
299
257
|
// Name of the tool (used to call it)
|
|
@@ -343,20 +301,14 @@ function registerAllTools(server) {
|
|
|
343
301
|
tclCommand = `run_to ${address}`;
|
|
344
302
|
break;
|
|
345
303
|
default:
|
|
346
|
-
return
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
text: `Error: Unknown debug command "${command}".`,
|
|
350
|
-
}],
|
|
351
|
-
};
|
|
304
|
+
return getResponseContent([
|
|
305
|
+
`Error: Unknown debug command "${command}".`
|
|
306
|
+
]);
|
|
352
307
|
}
|
|
353
308
|
const response = await openMSXInstance.sendCommand(tclCommand);
|
|
354
|
-
return
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
text: response === '' ? 'Ok' : response,
|
|
358
|
-
}],
|
|
359
|
-
};
|
|
309
|
+
return getResponseContent([
|
|
310
|
+
response
|
|
311
|
+
]);
|
|
360
312
|
});
|
|
361
313
|
server.tool(
|
|
362
314
|
// Name of the tool (used to call it)
|
|
@@ -401,20 +353,14 @@ function registerAllTools(server) {
|
|
|
401
353
|
tclCommand = "get_active_cpu";
|
|
402
354
|
break;
|
|
403
355
|
default:
|
|
404
|
-
return
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
text: `Error: Unknown memory command "${command}".`,
|
|
408
|
-
}],
|
|
409
|
-
};
|
|
356
|
+
return getResponseContent([
|
|
357
|
+
`Error: Unknown memory command "${command}".`
|
|
358
|
+
]);
|
|
410
359
|
}
|
|
411
360
|
const response = await openMSXInstance.sendCommand(tclCommand);
|
|
412
|
-
return
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
text: response === '' ? 'Ok' : response,
|
|
416
|
-
}],
|
|
417
|
-
};
|
|
361
|
+
return getResponseContent([
|
|
362
|
+
response
|
|
363
|
+
]);
|
|
418
364
|
});
|
|
419
365
|
server.tool(
|
|
420
366
|
// Name of the tool (used to call it)
|
|
@@ -463,20 +409,14 @@ function registerAllTools(server) {
|
|
|
463
409
|
tclCommand = "listing";
|
|
464
410
|
break;
|
|
465
411
|
default:
|
|
466
|
-
return
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
text: `Error: Unknown memory command "${command}".`,
|
|
470
|
-
}],
|
|
471
|
-
};
|
|
412
|
+
return getResponseContent([
|
|
413
|
+
`Error: Unknown memory command "${command}".`
|
|
414
|
+
]);
|
|
472
415
|
}
|
|
473
416
|
const response = await openMSXInstance.sendCommand(tclCommand);
|
|
474
|
-
return
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
text: response === '' ? 'Ok' : response,
|
|
478
|
-
}],
|
|
479
|
-
};
|
|
417
|
+
return getResponseContent([
|
|
418
|
+
response
|
|
419
|
+
]);
|
|
480
420
|
});
|
|
481
421
|
server.tool(
|
|
482
422
|
// Name of the tool (used to call it)
|
|
@@ -508,20 +448,14 @@ function registerAllTools(server) {
|
|
|
508
448
|
tclCommand = `vpoke ${address} ${value8}`;
|
|
509
449
|
break;
|
|
510
450
|
default:
|
|
511
|
-
return
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
text: `Error: Unknown video memory command "${command}".`,
|
|
515
|
-
}],
|
|
516
|
-
};
|
|
451
|
+
return getResponseContent([
|
|
452
|
+
`Error: Unknown video memory command "${command}".`
|
|
453
|
+
]);
|
|
517
454
|
}
|
|
518
455
|
const response = await openMSXInstance.sendCommand(tclCommand);
|
|
519
|
-
return
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
text: response === '' ? 'Ok' : response,
|
|
523
|
-
}],
|
|
524
|
-
};
|
|
456
|
+
return getResponseContent([
|
|
457
|
+
response
|
|
458
|
+
]);
|
|
525
459
|
});
|
|
526
460
|
server.tool(
|
|
527
461
|
// Name of the tool (used to call it)
|
|
@@ -553,20 +487,14 @@ function registerAllTools(server) {
|
|
|
553
487
|
tclCommand = 'debug list_bp';
|
|
554
488
|
break;
|
|
555
489
|
default:
|
|
556
|
-
return
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
text: `Error: Unknown breakpoint command "${command}".`,
|
|
560
|
-
}],
|
|
561
|
-
};
|
|
490
|
+
return getResponseContent([
|
|
491
|
+
`Error: Unknown breakpoint command "${command}".`
|
|
492
|
+
]);
|
|
562
493
|
}
|
|
563
494
|
const response = await openMSXInstance.sendCommand(tclCommand);
|
|
564
|
-
return
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
text: response,
|
|
568
|
-
}],
|
|
569
|
-
};
|
|
495
|
+
return getResponseContent([
|
|
496
|
+
response
|
|
497
|
+
]);
|
|
570
498
|
});
|
|
571
499
|
server.tool(
|
|
572
500
|
// Name of the tool (used to call it)
|
|
@@ -600,23 +528,77 @@ function registerAllTools(server) {
|
|
|
600
528
|
tclCommand = 'list_savestates';
|
|
601
529
|
break;
|
|
602
530
|
default:
|
|
603
|
-
return
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
text: `Error: Unknown savestate command "${command}".`,
|
|
607
|
-
}],
|
|
608
|
-
};
|
|
531
|
+
return getResponseContent([
|
|
532
|
+
`Error: Unknown savestate command "${command}".`
|
|
533
|
+
]);
|
|
609
534
|
}
|
|
610
535
|
const response = await openMSXInstance.sendCommand(tclCommand);
|
|
611
|
-
return
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
536
|
+
return getResponseContent([
|
|
537
|
+
textResponse,
|
|
538
|
+
response
|
|
539
|
+
]);
|
|
540
|
+
});
|
|
541
|
+
server.tool(
|
|
542
|
+
// Name of the tool (used to call it)
|
|
543
|
+
"emu_replay",
|
|
544
|
+
// Description of the tool (what it does)
|
|
545
|
+
"When replay is enabled (the default) the emulator collect data while emulating, which enables you to go back and forward in MSX time; consider do a 'pause' to maintain the the timeline before a 'goBack' or 'absoluteGoto'. Commands: " +
|
|
546
|
+
"'start': starts the replay mode (enabled by default when emulator is launched). " +
|
|
547
|
+
"'stop': stops the replay mode. " +
|
|
548
|
+
"'status': gives information about the replay feature and the data that is collected. " +
|
|
549
|
+
"'goBack <seconds>': go back specified seconds (1-60) in the timeline, you cannot go back to a time before the time the replay started. " +
|
|
550
|
+
"'absoluteGoto <time>': go to the indicated absolute time in seconds in the MSX timeline, if time is before replay started it will jump to the time when is started. " +
|
|
551
|
+
"'truncate': stop replaying and wipe all the future replay data after now. " +
|
|
552
|
+
"'saveReplay [filename]': saves the current replay data to a file (extension .omr), filename is returned in the response. " +
|
|
553
|
+
"'loadReplay <filename>': loads a previously saved replay file (extension .omr), starts replaying from the begin, and starts replay mode.",
|
|
554
|
+
// Schema for the tool (input validation)
|
|
555
|
+
{
|
|
556
|
+
command: z.enum(["start", "stop", "status", "goBack", "absoluteGoto", "truncate", "saveReplay", "loadReplay"]),
|
|
557
|
+
seconds: z.number().min(1).max(60).optional(), // Seconds to go back
|
|
558
|
+
time: z.string().regex(/^\d+$/).optional(), // Time in seconds to go to
|
|
559
|
+
filename: z.string().min(1).max(200).optional(), // Filename to save/load replay
|
|
560
|
+
},
|
|
561
|
+
// Handler for the tool (function to be executed when the tool is called)
|
|
562
|
+
async ({ command, seconds, time, filename }) => {
|
|
563
|
+
let tclCommand;
|
|
564
|
+
switch (command) {
|
|
565
|
+
case "start":
|
|
566
|
+
tclCommand = "reverse start";
|
|
567
|
+
break;
|
|
568
|
+
case "stop":
|
|
569
|
+
tclCommand = "reverse stop";
|
|
570
|
+
break;
|
|
571
|
+
case "status":
|
|
572
|
+
tclCommand = "reverse status";
|
|
573
|
+
break;
|
|
574
|
+
case "goBack":
|
|
575
|
+
tclCommand = `reverse goback ${seconds}`;
|
|
576
|
+
break;
|
|
577
|
+
case "absoluteGoto":
|
|
578
|
+
tclCommand = `reverse goto ${time}`;
|
|
579
|
+
break;
|
|
580
|
+
case "truncate":
|
|
581
|
+
tclCommand = "reverse truncatereplay";
|
|
582
|
+
break;
|
|
583
|
+
case "saveReplay":
|
|
584
|
+
if (filename)
|
|
585
|
+
filename = `"${filename}"`;
|
|
586
|
+
tclCommand = `reverse savereplay ${filename || ''}`;
|
|
587
|
+
break;
|
|
588
|
+
case "loadReplay":
|
|
589
|
+
if (filename)
|
|
590
|
+
filename = `"${filename}"`;
|
|
591
|
+
tclCommand = `reverse loadreplay ${filename}`;
|
|
592
|
+
break;
|
|
593
|
+
default:
|
|
594
|
+
return getResponseContent([
|
|
595
|
+
`Error: Unknown replay command "${command}".`
|
|
596
|
+
]);
|
|
597
|
+
}
|
|
598
|
+
const response = await openMSXInstance.sendCommand(tclCommand);
|
|
599
|
+
return getResponseContent([
|
|
600
|
+
response
|
|
601
|
+
]);
|
|
620
602
|
});
|
|
621
603
|
server.tool(
|
|
622
604
|
// Name of the tool (used to call it)
|
|
@@ -637,20 +619,14 @@ function registerAllTools(server) {
|
|
|
637
619
|
tclCommand = `type "${text}"`;
|
|
638
620
|
break;
|
|
639
621
|
default:
|
|
640
|
-
return
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
text: `Error: Unknown keyboard command "${command}".`,
|
|
644
|
-
}],
|
|
645
|
-
};
|
|
622
|
+
return getResponseContent([
|
|
623
|
+
`Error: Unknown keyboard command "${command}".`
|
|
624
|
+
]);
|
|
646
625
|
}
|
|
647
626
|
const response = await openMSXInstance.sendCommand(tclCommand);
|
|
648
|
-
return
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
text: response === '' ? 'Ok' : response,
|
|
652
|
-
}],
|
|
653
|
-
};
|
|
627
|
+
return getResponseContent([
|
|
628
|
+
response
|
|
629
|
+
]);
|
|
654
630
|
});
|
|
655
631
|
server.tool(
|
|
656
632
|
// Name of the tool (used to call it)
|
|
@@ -684,30 +660,19 @@ function registerAllTools(server) {
|
|
|
684
660
|
};
|
|
685
661
|
}
|
|
686
662
|
catch (error) {
|
|
687
|
-
return
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}, {
|
|
692
|
-
type: "text",
|
|
693
|
-
text: error instanceof Error ? error.message : String(error),
|
|
694
|
-
}],
|
|
695
|
-
};
|
|
663
|
+
return getResponseContent([
|
|
664
|
+
'Error creating screenshot: ' + response,
|
|
665
|
+
error instanceof Error ? error.message : String(error)
|
|
666
|
+
]);
|
|
696
667
|
}
|
|
697
668
|
case "to_file":
|
|
698
|
-
return
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
text: response.startsWith('Error:') ? response : 'Screenshot taken in file: ' + response,
|
|
702
|
-
}],
|
|
703
|
-
};
|
|
669
|
+
return getResponseContent([
|
|
670
|
+
response.startsWith('Error:') ? response : 'Screenshot taken in file: ' + response
|
|
671
|
+
]);
|
|
704
672
|
}
|
|
705
|
-
return
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
text: `Error: Unknown screen_shot command "${command}".`,
|
|
709
|
-
}],
|
|
710
|
-
};
|
|
673
|
+
return getResponseContent([
|
|
674
|
+
`Error: Unknown screen_shot command "${command}".`
|
|
675
|
+
]);
|
|
711
676
|
});
|
|
712
677
|
server.tool(
|
|
713
678
|
// Name of the tool (used to call it)
|
|
@@ -722,17 +687,20 @@ function registerAllTools(server) {
|
|
|
722
687
|
async ({ scrbasename }) => {
|
|
723
688
|
const openmsxCommand = `save_msx_screen "${OPENMSX_SCREENDUMP_DIR + scrbasename}"`;
|
|
724
689
|
const response = await openMSXInstance.sendCommand(openmsxCommand);
|
|
725
|
-
return
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
}, {
|
|
730
|
-
type: "text",
|
|
731
|
-
text: response,
|
|
732
|
-
}],
|
|
733
|
-
};
|
|
690
|
+
return getResponseContent([
|
|
691
|
+
response.startsWith('Error:') ? 'Fail:' : 'Screendump file saved as:',
|
|
692
|
+
response
|
|
693
|
+
]);
|
|
734
694
|
});
|
|
735
695
|
}
|
|
696
|
+
function getResponseContent(response) {
|
|
697
|
+
return {
|
|
698
|
+
content: response.map(line => ({
|
|
699
|
+
type: "text",
|
|
700
|
+
text: line == '' ? "Ok" : line,
|
|
701
|
+
})),
|
|
702
|
+
};
|
|
703
|
+
}
|
|
736
704
|
// ============================================================================
|
|
737
705
|
// Cleanup handlers for graceful shutdown of MCP server
|
|
738
706
|
// Ensure openMSX emulator is closed when MCP server stops
|