@nataliapc/mcp-openmsx 1.1.4 → 1.1.5
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 +8 -2
- package/dist/openmsx.js +11 -1
- package/dist/server.js +237 -104
- package/dist/utils.js +26 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ This project creates a bridge between modern AI-assisted development (e.g. GitHu
|
|
|
12
12
|
|
|
13
13
|
- **Emulator Control**: Launch, configure, manage openMSX instances, and replay timelines.
|
|
14
14
|
- **Media Management**: Handle ROM cartridges, floppy disks, and cassette tapes.
|
|
15
|
+
- **BASIC Programming Support**: Tools to facilitate BASIC language programming and development.
|
|
15
16
|
- **Debugging Tools**: Full CPU debugging with breakpoints, memory inspection, and step execution.
|
|
16
17
|
- **Video Control**: VDP register manipulation and screen capture.
|
|
17
18
|
- **Memory Operations**: Read/write RAM, VRAM, and I/O port access.
|
|
@@ -43,11 +44,14 @@ The MCP server translates high-level commands from your Copilot AI into `TCL` co
|
|
|
43
44
|
|
|
44
45
|
### Emulator Control Tools
|
|
45
46
|
- `emu_control`: Controls an openMSX emulator: _`launch`, `close`, `powerOn`, `powerOff`, `reset`, `getEmulatorSpeed`, `setEmulatorSpeed`, `machineList`, `extensionList`, `wait`_.
|
|
46
|
-
- `emu_replay`: Controls emulation timeline: _`start`, `
|
|
47
|
+
- `emu_replay`: Controls emulation timeline: _`start`, `stop`, `status`, `goBack`, `absoluteGoto`, `truncate`, `saveReplay`, `loadReplay`_.
|
|
47
48
|
- `emu_info`: Obtain informacion about the current emulated machine: _`getStatus`, `getSlotsMap`, `getIOPortsMap`_.
|
|
48
49
|
- `emu_media`: Manage ROM, disk, and tape media: _`tapeInsert`, `tapeRewind`, `tapeEject`, `romInsert`, `romEject`, `diskInsert`, `diskInsertFolder`, `diskEject`_.
|
|
49
50
|
- `emu_vdp`: Manage VDP (Video Display Processor): _`getPalette`, `getRegisters`, `getRegisterValue`, `setRegisterValue`, `screenGetMode`, `screenGetFullText`_.
|
|
50
51
|
|
|
52
|
+
### Programming Tools
|
|
53
|
+
- `basic_programming`: BASIC tools: _`newProgram`, `runProgram`, `setProgram`, `getFullProgram`, `getFullProgramAdvanced`, `listProgramLines`, `deleteProgramLines`_.
|
|
54
|
+
|
|
51
55
|
### Debugging Tools
|
|
52
56
|
- `debug_run`: Control execution: _`break`, `isBreaked`, `continue`, `stepIn`, `stepOut`, `stepOver`, `stepBack`, `runTo`_.
|
|
53
57
|
- `debug_cpu`: Read/write CPU registers, CPU info, Stack pile, and Disassemble code: _`getCpuRegisters`, `getRegister`, `setRegister`, `getStackPile`, `disassemble`, `getActiveCpu`_.
|
|
@@ -59,7 +63,7 @@ The MCP server translates high-level commands from your Copilot AI into `TCL` co
|
|
|
59
63
|
- `emu_keyboard`: Send text input to emulator: _`sendText`_.
|
|
60
64
|
- `emu_savestates`: Save and restore machine states: _`load`, `save`, `list`_.
|
|
61
65
|
- `screen_shot`: Capture emulator screen: _`as_image`, `to_file`_.
|
|
62
|
-
- `screen_dump`: Export screen data as BASIC BSAVE.
|
|
66
|
+
- `screen_dump`: Export screen data as BASIC BSAVE instruction.
|
|
63
67
|
|
|
64
68
|
|
|
65
69
|
## 🚀 Quick Start
|
|
@@ -131,6 +135,7 @@ Add to your `claude_desktop_config.json`:
|
|
|
131
135
|
| `OPENMSX_SHARE_DIR` | Directory containing openMSX data files (machines, extensions, etc.) | System dependent | `/home/myuser/.openmsx/share` |
|
|
132
136
|
| `OPENMSX_SCREENSHOT_DIR` | Directory where screenshots will be saved | Default for openmsx | `/myproject/screenshots` |
|
|
133
137
|
| `OPENMSX_SCREENDUMP_DIR` | Directory where screen dumps will be saved | Default for openmsx | `/myproject/screendumps` |
|
|
138
|
+
| `OPENMSX_REPLAYS_DIR` | Directory where replay files will be saved | Default for openmsx | `/myproject/replays` |
|
|
134
139
|
| `MCP_TRANSPORT` | Transport mode (`stdio` or `http`) | `stdio` | `http` |
|
|
135
140
|
| `MCP_HTTP_PORT` | Port number for HTTP transport mode | `3000` | `8080` |
|
|
136
141
|
|
|
@@ -154,6 +159,7 @@ export OPENMSX_EXECUTABLE="openmsx"
|
|
|
154
159
|
export OPENMSX_SHARE_DIR="/usr/share/openmsx"
|
|
155
160
|
export OPENMSX_SCREENSHOT_DIR="/my_project/screenshots"
|
|
156
161
|
export OPENMSX_SCREENDUMP_DIR="/my_project/screendumps"
|
|
162
|
+
export OPENMSX_REPLAYS_DIR="/my_project/replays"
|
|
157
163
|
export MCP_HTTP_PORT=3000
|
|
158
164
|
```
|
|
159
165
|
|
package/dist/openmsx.js
CHANGED
|
@@ -12,6 +12,7 @@ import path from 'path';
|
|
|
12
12
|
* OpenMSX class for controlling the openMSX emulator via TCL commands over TCP socket
|
|
13
13
|
*/
|
|
14
14
|
export class OpenMSX {
|
|
15
|
+
lastMachine = null;
|
|
15
16
|
process = null;
|
|
16
17
|
isConnected = false;
|
|
17
18
|
/**
|
|
@@ -34,13 +35,14 @@ export class OpenMSX {
|
|
|
34
35
|
try {
|
|
35
36
|
// Check if emulator is already running
|
|
36
37
|
if (this.process && !this.process.killed) {
|
|
37
|
-
safeResolve(
|
|
38
|
+
safeResolve(`Error: openMSX emulator instance is already running (currrent machine: ${this.lastMachine}). Close it first before launching a new one.`);
|
|
38
39
|
return;
|
|
39
40
|
}
|
|
40
41
|
// Build command line arguments
|
|
41
42
|
const args = ['-control', 'stdio'];
|
|
42
43
|
// Add machine parameter if specified
|
|
43
44
|
if (machine) {
|
|
45
|
+
this.lastMachine = machine; // Store last machine for future reference
|
|
44
46
|
args.push('-machine', machine);
|
|
45
47
|
}
|
|
46
48
|
// Add extensions if specified
|
|
@@ -86,6 +88,8 @@ export class OpenMSX {
|
|
|
86
88
|
if (!resolved) {
|
|
87
89
|
try {
|
|
88
90
|
this.writeData('<openmsx-control>\n');
|
|
91
|
+
// Set save settings on exit off
|
|
92
|
+
this.sendCommand('set save_settings_on_exit off');
|
|
89
93
|
// Set renderer to SDL
|
|
90
94
|
this.sendCommand('set renderer SDLGL-PP');
|
|
91
95
|
// set machine on
|
|
@@ -148,6 +152,7 @@ export class OpenMSX {
|
|
|
148
152
|
return;
|
|
149
153
|
}
|
|
150
154
|
this.process.on('exit', () => {
|
|
155
|
+
this.lastMachine = null; // Clear last machine on exit
|
|
151
156
|
this.isConnected = false;
|
|
152
157
|
this.process = null;
|
|
153
158
|
resolve("Ok: Emulator process closed successfully");
|
|
@@ -187,10 +192,15 @@ export class OpenMSX {
|
|
|
187
192
|
return response;
|
|
188
193
|
}
|
|
189
194
|
// Parse machine_info output into key-value pairs
|
|
195
|
+
const skipInfo = ['issubslotted', 'input_port', 'slot', 'isexternalslot', 'output_port'];
|
|
190
196
|
const parameters = response.trim().split(' ');
|
|
191
197
|
const machineInfo = {};
|
|
192
198
|
for (const param of parameters) {
|
|
193
199
|
const trimmedLine = param.trim();
|
|
200
|
+
// Skip certain parameters that are not useful
|
|
201
|
+
if (skipInfo.includes(trimmedLine)) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
194
204
|
if (trimmedLine) {
|
|
195
205
|
const value = await this.sendCommand(`machine_info ${trimmedLine}`);
|
|
196
206
|
machineInfo[trimmedLine] = value.trim();
|
package/dist/server.js
CHANGED
|
@@ -20,11 +20,13 @@ import express from "express";
|
|
|
20
20
|
import fs from "fs/promises";
|
|
21
21
|
import path from "path";
|
|
22
22
|
import { openMSXInstance } from "./openmsx.js";
|
|
23
|
+
import { encodeTypeText, isErrorResponse } from "./utils.js";
|
|
23
24
|
// Version info for CLI
|
|
24
25
|
const PACKAGE_VERSION = "1.1.4";
|
|
25
26
|
// Defaults for openMSX paths
|
|
26
27
|
var OPENMSX_EXECUTABLE = 'openmsx';
|
|
27
28
|
var OPENMSX_SHARE_DIR = '/usr/share/openmsx';
|
|
29
|
+
var OPENMSX_REPLAYS_DIR = '';
|
|
28
30
|
var OPENMSX_SCREENSHOT_DIR = '';
|
|
29
31
|
var OPENMSX_SCREENDUMP_DIR = '';
|
|
30
32
|
var MACHINES_DIR = `${OPENMSX_SHARE_DIR}/machines`;
|
|
@@ -38,17 +40,19 @@ function registerAllTools(server) {
|
|
|
38
40
|
// Name of the tool (used to call it)
|
|
39
41
|
"emu_control",
|
|
40
42
|
// Description of the tool (what it does)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
43
|
+
`Controls an openMSX emulator.
|
|
44
|
+
Commands:
|
|
45
|
+
'launch [machine] [extensions]': opens a powered-on openMSX emulator; you must wait some time waiting the machine is fully booted; machine and extensions parameters can be specified so use 'machineList' and 'extensionList' commands to obtain valid values. " +
|
|
46
|
+
'close': closes the openMSX emulator.
|
|
47
|
+
'powerOn': powers on the openMSX emulator.
|
|
48
|
+
'powerOff': powers off the openMSX emulator.
|
|
49
|
+
'reset': resets the current machine.
|
|
50
|
+
'getEmulatorSpeed': gets the current emulator speed as a percentage, default is 100.
|
|
51
|
+
'setEmulatorSpeed <emuspeed>': sets the emulator speed as a percentage, valid values are 1-10000, default is 100.
|
|
52
|
+
'machineList': gets a list of all available MSX machines that can be emulated with openMSX.
|
|
53
|
+
'extensionList': gets a list of all available MSX extensions that can be used with openMSX.
|
|
54
|
+
'wait <seconds>': performs a wait for the specified number of seconds, default is 2.
|
|
55
|
+
`,
|
|
52
56
|
// Schema for the tool (input validation)
|
|
53
57
|
{
|
|
54
58
|
command: z.enum(["launch", "close", "powerOn", "powerOff", "reset", "getEmulatorSpeed", "setEmulatorSpeed", "machineList", "extensionList", "wait"]),
|
|
@@ -110,15 +114,17 @@ function registerAllTools(server) {
|
|
|
110
114
|
// Name of the tool (used to call it)
|
|
111
115
|
"emu_media",
|
|
112
116
|
// Description of the tool (what it does)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
`Manage tapes, rom cartridges, and floppy disks.
|
|
118
|
+
Commands:
|
|
119
|
+
'tapeInsert <tapefile>': insert a valid tape file (*.cas, *.wav, *.tsx).
|
|
120
|
+
'tapeRewind': rewind the current tape.
|
|
121
|
+
'tapeEject': remove tape from virtual cassette player.
|
|
122
|
+
'romInsert <romfile>': insert a valid ROM cartridge file (*.rom) at cartridge slot A.
|
|
123
|
+
'romEject': remove the current ROM cartridge from cartridge slot A.
|
|
124
|
+
'diskInsert <diskfile>': insert a valid disk file (*.dsk) in floppy disk A.
|
|
125
|
+
'diskInsertFolder <diskfolder>': use a host folder as a floppy disk A root directory.
|
|
126
|
+
'diskEject': remove the current disk from floppy disk A.
|
|
127
|
+
`,
|
|
122
128
|
// Schema for the tool (input validation)
|
|
123
129
|
{
|
|
124
130
|
command: z.enum(["tapeInsert", "tapeRewind", "tapeEject", "romInsert", "romEject", "diskInsert", "diskInsertFolder", "diskEject"]),
|
|
@@ -170,10 +176,12 @@ function registerAllTools(server) {
|
|
|
170
176
|
// Name of the tool (used to call it)
|
|
171
177
|
"emu_info",
|
|
172
178
|
// Description of the tool (what it does)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
179
|
+
`Obtain informacion about the current emulated machine.
|
|
180
|
+
Commands:
|
|
181
|
+
'getStatus': returns the status of the openMSX emulator.
|
|
182
|
+
'getSlotsMap': shows what devices/ROM/RAM are inserted into which slots.
|
|
183
|
+
'getIOPortsMap': shows an overview about the I/O mapped devices.
|
|
184
|
+
`,
|
|
177
185
|
// Schema for the tool (input validation)
|
|
178
186
|
{
|
|
179
187
|
command: z.enum(["getStatus", "getSlotsMap", "getIOPortsMap"]),
|
|
@@ -206,13 +214,15 @@ function registerAllTools(server) {
|
|
|
206
214
|
// Name of the tool (used to call it)
|
|
207
215
|
"emu_vdp",
|
|
208
216
|
// Description of the tool (what it does)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
217
|
+
`Manage the VDP (Video Display Processor).
|
|
218
|
+
Commands:
|
|
219
|
+
'getPalette': returns the current V9938/V9958 color palette in RGB333 format.
|
|
220
|
+
'getRegisters': returns all VDP register values.
|
|
221
|
+
'getRegisterValue <register>': returns the value of a specific VDP register (0-31) in decimal format.
|
|
222
|
+
'setRegisterValue <register> <value>': sets a hexadecimal value to a specific VDP register (0-31).
|
|
223
|
+
'screenGetMode': returns the current screen mode (0-12) as a number, which matches the BASIC SCREEN command.
|
|
224
|
+
'screenGetFullText': returns the full content of an MSX text screen (screen 0 or 1) as a string; PRIORITIZE this command to view screen content in text modes.
|
|
225
|
+
`,
|
|
216
226
|
// Schema for the tool (input validation)
|
|
217
227
|
{
|
|
218
228
|
command: z.enum(["getPalette", "getRegisters", "getRegisterValue", "setRegisterValue", "screenGetMode", "screenGetFullText"]),
|
|
@@ -240,7 +250,7 @@ function registerAllTools(server) {
|
|
|
240
250
|
break;
|
|
241
251
|
case "screenGetFullText":
|
|
242
252
|
const response = await openMSXInstance.sendCommand('get_screen');
|
|
243
|
-
return response
|
|
253
|
+
return isErrorResponse(response) ?
|
|
244
254
|
getResponseContent([response]) :
|
|
245
255
|
getResponseContent(["The screen text is:", response]);
|
|
246
256
|
default:
|
|
@@ -257,16 +267,18 @@ function registerAllTools(server) {
|
|
|
257
267
|
// Name of the tool (used to call it)
|
|
258
268
|
"debug_run",
|
|
259
269
|
// Description of the tool (what it does)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
+
`Control execution (break, continue, step).
|
|
271
|
+
Commands:
|
|
272
|
+
'break': to break CPU at current execution position.
|
|
273
|
+
'isBreaked': to check if the CPU is currently in break state (1) or not (0).
|
|
274
|
+
'continue': to continue execution after break.
|
|
275
|
+
'stepIn': to execute one CPU instruction, go into subroutines.
|
|
276
|
+
'stepOver': to execute one CPU instruction, but don't go into subroutines.
|
|
277
|
+
'stepOut': to step out of the current subroutine.
|
|
278
|
+
'stepBack': to step one instruction back in time.
|
|
279
|
+
'runTo <address>': to run the CPU until it reaches the specified address.
|
|
280
|
+
**Important Note**: Addresses and values are in hexadecimal format (e.g. 0x0000).
|
|
281
|
+
`,
|
|
270
282
|
// Schema for the tool (input validation)
|
|
271
283
|
{
|
|
272
284
|
command: z.enum(["break", "isBreaked", "continue", "stepIn", "stepOut", "stepOver", "stepBack", "runTo"]),
|
|
@@ -314,14 +326,16 @@ function registerAllTools(server) {
|
|
|
314
326
|
// Name of the tool (used to call it)
|
|
315
327
|
"debug_cpu",
|
|
316
328
|
// Description of the tool (what it does)
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
329
|
+
`Read/write CPU registers, CPU info, Stack pile, and Disassemble code from memory.
|
|
330
|
+
Commands:
|
|
331
|
+
'getCpuRegisters': to get an overview of all the CPU registers.
|
|
332
|
+
'getRegister <register>': to get the decimal value of a specific CPU register (pc, sp, ix, iy, af, bc, de, hl, ixh, ixl, iyh, iyl, a, f, b, c, d, e, h, l, i, r, im, iff).
|
|
333
|
+
'setRegister <register> <value>': to set the value of a specific CPU register (pc, sp, ix, iy, af, bc, de, hl, ixh, ixl, iyh, iyl, a, f, b, c, d, e, h, l, i, r, im, iff).
|
|
334
|
+
'getStackPile': to get an overview of the CPU stack.
|
|
335
|
+
'disassemble [address] [size]': to print disassembled instructions at the address parameter location or PC register if empty.
|
|
336
|
+
'getActiveCpu': to return the active cpu: z80 or r800.
|
|
337
|
+
"**Important Note**: Addresses and values are in hexadecimal format (e.g. 0x0000)."
|
|
338
|
+
`,
|
|
325
339
|
// Schema for the tool (input validation)
|
|
326
340
|
{
|
|
327
341
|
command: z.enum(["getCpuRegisters", "getRegister", "setRegister", "getStackPile", "disassemble", "getActiveCpu"]),
|
|
@@ -338,10 +352,10 @@ function registerAllTools(server) {
|
|
|
338
352
|
tclCommand = "cpuregs";
|
|
339
353
|
break;
|
|
340
354
|
case "getRegister":
|
|
341
|
-
tclCommand =
|
|
355
|
+
tclCommand = `reg ${register}`;
|
|
342
356
|
break;
|
|
343
357
|
case "setRegister":
|
|
344
|
-
tclCommand =
|
|
358
|
+
tclCommand = `reg ${register} ${value}`;
|
|
345
359
|
break;
|
|
346
360
|
case "getStackPile":
|
|
347
361
|
tclCommand = "stack";
|
|
@@ -366,18 +380,19 @@ function registerAllTools(server) {
|
|
|
366
380
|
// Name of the tool (used to call it)
|
|
367
381
|
"debug_memory",
|
|
368
382
|
// Description of the tool (what it does)
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
383
|
+
`Slots info, and Read/write from/to memory in the openMSX emulator.
|
|
384
|
+
Commands:
|
|
385
|
+
'selectedSlots': to get a list of the currently selected memory slots.
|
|
386
|
+
'getBlock <address> [lines]': to read a block of memory from the specified address.
|
|
387
|
+
'readByte <address>': to read a BYTE from the specified address.
|
|
388
|
+
'readWord <address>': to read a WORD from the specified address.
|
|
389
|
+
'writeByte <address> <value8>': to write a BYTE to the specified address.
|
|
390
|
+
'writeWord <address> <value16>': to write a WORD to the specified address.
|
|
391
|
+
**Important Note**: Addresses and values are in hexadecimal format (e.g. 0x0000).
|
|
392
|
+
`,
|
|
378
393
|
// Schema for the tool (input validation)
|
|
379
394
|
{
|
|
380
|
-
command: z.enum(["selectedSlots", "getBlock", "readByte", "readWord", "writeByte", "writeWord"
|
|
395
|
+
command: z.enum(["selectedSlots", "getBlock", "readByte", "readWord", "writeByte", "writeWord"]),
|
|
381
396
|
address: z.string().regex(/^0x[0-9a-fA-F]{4}$/).optional(), // 4 hex digits for MSX memory address
|
|
382
397
|
lines: z.number().min(1).max(50).optional().default(8), // Number of lines for getBlock command
|
|
383
398
|
value8: z.string().regex(/^0x[0-9a-fA-F]{2}$/).optional(), // 2 hex digits for byte value for writeByte command
|
|
@@ -405,9 +420,6 @@ function registerAllTools(server) {
|
|
|
405
420
|
case "writeWord":
|
|
406
421
|
tclCommand = `poke16 ${address} ${value16}`;
|
|
407
422
|
break;
|
|
408
|
-
case "advanced_basic_listing":
|
|
409
|
-
tclCommand = "listing";
|
|
410
|
-
break;
|
|
411
423
|
default:
|
|
412
424
|
return getResponseContent([
|
|
413
425
|
`Error: Unknown memory command "${command}".`
|
|
@@ -422,11 +434,13 @@ function registerAllTools(server) {
|
|
|
422
434
|
// Name of the tool (used to call it)
|
|
423
435
|
"debug_vram",
|
|
424
436
|
// Description of the tool (what it does)
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
437
|
+
`Read or write from/to VRAM video memory in the openMSX emulator.
|
|
438
|
+
Commands:
|
|
439
|
+
'getBlock <address> [lines]': to read a block of VRAM memory from the specified address.
|
|
440
|
+
'readByte <address>': to read a BYTE from the specified VRAM address.
|
|
441
|
+
'writeByte <address> <value8>': to write a BYTE to the specified VRAM address.
|
|
442
|
+
**Important Note**: Addresses and values are in hexadecimal format (e.g. 0x0000).
|
|
443
|
+
`,
|
|
430
444
|
// Schema for the tool (input validation)
|
|
431
445
|
{
|
|
432
446
|
command: z.enum(["getBlock", "readByte", "writeByte"]),
|
|
@@ -461,12 +475,14 @@ function registerAllTools(server) {
|
|
|
461
475
|
// Name of the tool (used to call it)
|
|
462
476
|
"debug_breakpoints",
|
|
463
477
|
// Description of the tool (what it does)
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
478
|
+
`Create, remove, and list breakpoints.
|
|
479
|
+
Commands:
|
|
480
|
+
'create <address>': create a breakpoint at a specified address, and returns its name.
|
|
481
|
+
'remove <bpname>': remove a breakpoint by name (e.g. bp#1).
|
|
482
|
+
'list': enumerate the active breakpoints.
|
|
483
|
+
"**Important Note**: Addresses and values are in hexadecimal format (e.g. 0x0000).
|
|
484
|
+
"**Important Note**: The memory addresses of functions and variables can be previously obtained from *.sym or *.map files.
|
|
485
|
+
`,
|
|
470
486
|
// Schema for the tool (input validation)
|
|
471
487
|
{
|
|
472
488
|
command: z.enum(["create", "remove", "list"]),
|
|
@@ -500,11 +516,13 @@ function registerAllTools(server) {
|
|
|
500
516
|
// Name of the tool (used to call it)
|
|
501
517
|
"emu_savestates",
|
|
502
518
|
// Description of the tool (what it does)
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
519
|
+
`Load, save, and list savestates.
|
|
520
|
+
Commands:
|
|
521
|
+
'load <name>': restores a previously created savestate.
|
|
522
|
+
'save <name>': creates a snapshot of the currently emulated MSX machine specifying a name for the savestate.
|
|
523
|
+
'list': returns the names of all previously created savestates, separated by spaces.
|
|
524
|
+
**Important Note**: names with spaces are enclosed in {}.
|
|
525
|
+
`,
|
|
508
526
|
// Schema for the tool (input validation)
|
|
509
527
|
{
|
|
510
528
|
command: z.enum(["load", "save", "list"]),
|
|
@@ -542,15 +560,19 @@ function registerAllTools(server) {
|
|
|
542
560
|
// Name of the tool (used to call it)
|
|
543
561
|
"emu_replay",
|
|
544
562
|
// Description of the tool (what it does)
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
563
|
+
`When replay is enabled (the default) the emulator collect data while emulating,
|
|
564
|
+
which enables you to go back and forward in MSX time.
|
|
565
|
+
Commands:
|
|
566
|
+
'start': starts the replay mode (enabled by default when emulator is launched).
|
|
567
|
+
'stop': stops the replay mode.
|
|
568
|
+
'status': gives information about the replay feature and the data that is collected.
|
|
569
|
+
'goBack <seconds>': go back specified seconds (1-60) in the timeline, you cannot go back to a time before the time the replay started.
|
|
570
|
+
'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.
|
|
571
|
+
'truncate': stop replaying and wipe all the future replay data after now.
|
|
572
|
+
'saveReplay [filename]': saves the current replay data to a file (extension .omr), filename is returned in the response.
|
|
573
|
+
'loadReplay <filename>': loads a previously saved replay file (extension .omr), starts replaying from the begin, and starts replay mode.
|
|
574
|
+
**Important Note**: consider do a #debug_run 'break' to maintain the timeline before a 'goBack' or 'absoluteGoto'.
|
|
575
|
+
`,
|
|
554
576
|
// Schema for the tool (input validation)
|
|
555
577
|
{
|
|
556
578
|
command: z.enum(["start", "stop", "status", "goBack", "absoluteGoto", "truncate", "saveReplay", "loadReplay"]),
|
|
@@ -582,12 +604,12 @@ function registerAllTools(server) {
|
|
|
582
604
|
break;
|
|
583
605
|
case "saveReplay":
|
|
584
606
|
if (filename)
|
|
585
|
-
filename = `"${filename}"`;
|
|
607
|
+
filename = `"${OPENMSX_REPLAYS_DIR}${filename}"`;
|
|
586
608
|
tclCommand = `reverse savereplay ${filename || ''}`;
|
|
587
609
|
break;
|
|
588
610
|
case "loadReplay":
|
|
589
611
|
if (filename)
|
|
590
|
-
filename = `"${filename}"`;
|
|
612
|
+
filename = `"${OPENMSX_REPLAYS_DIR}${filename}"`;
|
|
591
613
|
tclCommand = `reverse loadreplay ${filename}`;
|
|
592
614
|
break;
|
|
593
615
|
default:
|
|
@@ -604,10 +626,12 @@ function registerAllTools(server) {
|
|
|
604
626
|
// Name of the tool (used to call it)
|
|
605
627
|
"emu_keyboard",
|
|
606
628
|
// Description of the tool (what it does)
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
629
|
+
`Send a text to the openMSX emulator.
|
|
630
|
+
Commands:
|
|
631
|
+
'sendText <text>': type a string in the emulated MSX, this command automatically press and release keys in the MSX keyboard matrix.
|
|
632
|
+
**Important Note**: each 'text' sent is limited to 200 characters, and the 'text' is sent as if it was typed in the MSX keyboard.
|
|
633
|
+
**Important Note**: escape keys that needs it as Return key (use \\r), double quotes (use \\\"), etc...
|
|
634
|
+
`,
|
|
611
635
|
// Schema for the tool (input validation)
|
|
612
636
|
{
|
|
613
637
|
command: z.enum(["sendText"]),
|
|
@@ -618,7 +642,7 @@ function registerAllTools(server) {
|
|
|
618
642
|
let tclCommand;
|
|
619
643
|
switch (command) {
|
|
620
644
|
case "sendText":
|
|
621
|
-
tclCommand = `type "${text}"`;
|
|
645
|
+
tclCommand = `type "${encodeTypeText(text)}"`;
|
|
622
646
|
break;
|
|
623
647
|
default:
|
|
624
648
|
return getResponseContent([
|
|
@@ -634,9 +658,11 @@ function registerAllTools(server) {
|
|
|
634
658
|
// Name of the tool (used to call it)
|
|
635
659
|
"screen_shot",
|
|
636
660
|
// Description of the tool (what it does)
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
661
|
+
`Take a screenshot of the openMSX emulator screen.
|
|
662
|
+
Commands:
|
|
663
|
+
'as_image': take a screenshot and the image is returned in the response.
|
|
664
|
+
'to_file': take a screenshot and save it to a file, the file name is returned in the response.
|
|
665
|
+
`,
|
|
640
666
|
// Schema for the tool (input validation)
|
|
641
667
|
{
|
|
642
668
|
command: z.enum(["as_image", "to_file"]),
|
|
@@ -648,8 +674,16 @@ function registerAllTools(server) {
|
|
|
648
674
|
switch (command) {
|
|
649
675
|
case "as_image":
|
|
650
676
|
try {
|
|
677
|
+
// Check if the response is a file path using fstat
|
|
678
|
+
if (!response || !response.startsWith(OPENMSX_SCREENSHOT_DIR) || !response.endsWith('.png')) {
|
|
679
|
+
throw new Error(`Invalid screenshot "${response}"`);
|
|
680
|
+
}
|
|
681
|
+
// Read the screenshot file
|
|
651
682
|
const imageBuffer = await fs.readFile(response);
|
|
652
683
|
const base64image = imageBuffer.toString('base64');
|
|
684
|
+
// Remove the file after reading it
|
|
685
|
+
await fs.unlink(response);
|
|
686
|
+
// Return the image in the response
|
|
653
687
|
return {
|
|
654
688
|
content: [{
|
|
655
689
|
type: "text",
|
|
@@ -669,7 +703,7 @@ function registerAllTools(server) {
|
|
|
669
703
|
}
|
|
670
704
|
case "to_file":
|
|
671
705
|
return getResponseContent([
|
|
672
|
-
response
|
|
706
|
+
isErrorResponse(response) ? response : 'Screenshot taken in file: ' + response
|
|
673
707
|
]);
|
|
674
708
|
}
|
|
675
709
|
return getResponseContent([
|
|
@@ -680,7 +714,9 @@ function registerAllTools(server) {
|
|
|
680
714
|
// Name of the tool (used to call it)
|
|
681
715
|
"screen_dump",
|
|
682
716
|
// Description of the tool (what it does)
|
|
683
|
-
|
|
717
|
+
`Take a screendump of the openMSX emulator screen as SC?.
|
|
718
|
+
The parameter scrbasename is the name of the filename (without path) to save the screendump, default is 'screendump'.
|
|
719
|
+
`,
|
|
684
720
|
// Schema for the tool (input validation)
|
|
685
721
|
{
|
|
686
722
|
scrbasename: z.string().min(1).max(100).default("screendump"),
|
|
@@ -690,14 +726,108 @@ function registerAllTools(server) {
|
|
|
690
726
|
const openmsxCommand = `save_msx_screen "${OPENMSX_SCREENDUMP_DIR + scrbasename}"`;
|
|
691
727
|
const response = await openMSXInstance.sendCommand(openmsxCommand);
|
|
692
728
|
return getResponseContent([
|
|
693
|
-
response
|
|
729
|
+
isErrorResponse(response) ? 'Fail:' : 'Screendump file saved as:',
|
|
694
730
|
response
|
|
695
731
|
]);
|
|
696
732
|
});
|
|
733
|
+
server.tool(
|
|
734
|
+
// Name of the tool (used to call it)
|
|
735
|
+
"basic_programming",
|
|
736
|
+
// Description of the tool (what it does)
|
|
737
|
+
`Tool helper to develop BASIC programs.
|
|
738
|
+
Commands:
|
|
739
|
+
'newProgram': clears the current BASIC program.
|
|
740
|
+
'setProgram <program>': sets/updates the current BASIC program to the specified string.
|
|
741
|
+
'runProgram': runs the current BASIC program.
|
|
742
|
+
'getFullProgram': returns the current BASIC program as a plain text string.
|
|
743
|
+
'getFullProgramAdvanced': returns the current BASIC program with the ram address where each line is coded.
|
|
744
|
+
'listProgramLines <startLine> [endLine]': lists at the emulator screen the selected range lines of the current BASIC program.
|
|
745
|
+
'deleteProgramLines <startLine> [endline]': deletes a specific line range from the current BASIC program, if endline is not specified, only the startLine is deleted.
|
|
746
|
+
**Important Note**: priorize this tools to develop BASIC programs, it's more efficient than using the 'sendText' tool.
|
|
747
|
+
**Important Note**: all the program lines must be ended with a carriage return (\\r) to be correctly processed, even the last one.
|
|
748
|
+
`,
|
|
749
|
+
// Schema for the tool (input validation)
|
|
750
|
+
{
|
|
751
|
+
command: z.enum(["newProgram", "runProgram", "setProgram", "getFullProgram", "getFullProgramAdvanced", "listProgramLines", "deleteProgramLines"]),
|
|
752
|
+
program: z.string().min(1).max(10000).optional(), // BASIC program to set
|
|
753
|
+
startLine: z.number().min(0).max(9999).optional(),
|
|
754
|
+
endLine: z.number().min(0).max(9999).optional(),
|
|
755
|
+
},
|
|
756
|
+
// Handler for the tool (function to be executed when the tool is called)
|
|
757
|
+
async ({ command, program, startLine, endLine }) => {
|
|
758
|
+
const CTRL_L_TEMPLATE = 'keymatrixdown 6 2 ; keymatrixdown 4 2 ; after time 0.1 { keymatrixup 6 2 ; keymatrixup 4 2 ; type_via_keybuf "%s" }';
|
|
759
|
+
let tclCommand = undefined;
|
|
760
|
+
let response = undefined;
|
|
761
|
+
switch (command) {
|
|
762
|
+
case "newProgram":
|
|
763
|
+
response = await openMSXInstance.sendCommand(CTRL_L_TEMPLATE.replace('%s', encodeTypeText('new\r')));
|
|
764
|
+
if (response.startsWith('after#'))
|
|
765
|
+
response = '';
|
|
766
|
+
break;
|
|
767
|
+
case "runProgram":
|
|
768
|
+
response = await openMSXInstance.sendCommand(CTRL_L_TEMPLATE.replace('%s', encodeTypeText('run\r')));
|
|
769
|
+
if (response.startsWith('after#'))
|
|
770
|
+
response = '';
|
|
771
|
+
break;
|
|
772
|
+
case "deleteProgramLines":
|
|
773
|
+
if (startLine === undefined) {
|
|
774
|
+
response = 'Error: No startLine number provided to delete BASIC program lines.';
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
response = await openMSXInstance.sendCommand(CTRL_L_TEMPLATE.replace('%s', encodeTypeText(`delete ${startLine}-${endLine || startLine}\r`)));
|
|
778
|
+
break;
|
|
779
|
+
case "setProgram":
|
|
780
|
+
if (!program) {
|
|
781
|
+
response = 'Error: No BASIC program provided to set.';
|
|
782
|
+
break;
|
|
783
|
+
}
|
|
784
|
+
// Get current speed to restore it later
|
|
785
|
+
let speed = '100';
|
|
786
|
+
if (isErrorResponse(speed = await openMSXInstance.sendCommand('set speed'))) {
|
|
787
|
+
response = speed;
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
// Set speed to fast for program input
|
|
791
|
+
if (isErrorResponse(response = await openMSXInstance.sendCommand('set speed 10000')))
|
|
792
|
+
break;
|
|
793
|
+
// Clear the screen and type the program
|
|
794
|
+
if (isErrorResponse(response = await openMSXInstance.sendCommand(`type_via_keybuf "${encodeTypeText(program)}"`)))
|
|
795
|
+
break;
|
|
796
|
+
// Restore the original speed
|
|
797
|
+
if (isErrorResponse(response = await openMSXInstance.sendCommand(`set speed ${speed}`)))
|
|
798
|
+
break;
|
|
799
|
+
// Success response
|
|
800
|
+
response = '';
|
|
801
|
+
break;
|
|
802
|
+
case "getFullProgram":
|
|
803
|
+
// Source: https://www.msx.org/forum/msx-talk/openmsx/export-basic-listing#comment-407392
|
|
804
|
+
tclCommand = 'regsub -all -line {^[0-9a-f]x[0-9a-f]{4} > } [ listing ] ""';
|
|
805
|
+
break;
|
|
806
|
+
case "getFullProgramAdvanced":
|
|
807
|
+
tclCommand = "listing";
|
|
808
|
+
break;
|
|
809
|
+
case "listProgramLines":
|
|
810
|
+
if (startLine === undefined) {
|
|
811
|
+
response = 'Error: No start line provided to list BASIC program lines.';
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
tclCommand = `type_via_keybuf \"${encodeTypeText(`list ${startLine}-${endLine || startLine}\r`)}\"`;
|
|
815
|
+
break;
|
|
816
|
+
default:
|
|
817
|
+
response = `Error: Unknown command "${command}".`;
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
820
|
+
if (response === undefined && tclCommand) {
|
|
821
|
+
response = await openMSXInstance.sendCommand(tclCommand);
|
|
822
|
+
}
|
|
823
|
+
return getResponseContent([
|
|
824
|
+
response !== undefined ? response : `Error: No response for command "${command}".`
|
|
825
|
+
]);
|
|
826
|
+
});
|
|
697
827
|
}
|
|
698
828
|
function getResponseContent(response, isError = false) {
|
|
699
829
|
// Check if any response line starts with "Error:" to automatically set isError to true
|
|
700
|
-
const hasError = isError || response.some(line => line
|
|
830
|
+
const hasError = isError || response.some(line => isErrorResponse(line));
|
|
701
831
|
return {
|
|
702
832
|
content: response.map(line => ({
|
|
703
833
|
type: "text",
|
|
@@ -865,6 +995,9 @@ async function main() {
|
|
|
865
995
|
if (process.env.OPENMSX_SCREENDUMP_DIR && process.env.OPENMSX_SCREENDUMP_DIR !== '') {
|
|
866
996
|
OPENMSX_SCREENDUMP_DIR = process.env.OPENMSX_SCREENDUMP_DIR + path.sep;
|
|
867
997
|
}
|
|
998
|
+
if (process.env.OPENMSX_REPLAYS_DIR && process.env.OPENMSX_REPLAYS_DIR !== '') {
|
|
999
|
+
OPENMSX_REPLAYS_DIR = process.env.OPENMSX_REPLAYS_DIR + path.sep;
|
|
1000
|
+
}
|
|
868
1001
|
if (process.env.OPENMSX_SHARE_DIR) {
|
|
869
1002
|
OPENMSX_SHARE_DIR = process.env.OPENMSX_SHARE_DIR + path.sep;
|
|
870
1003
|
MACHINES_DIR = `${OPENMSX_SHARE_DIR}machines`;
|
package/dist/utils.js
CHANGED
|
@@ -78,3 +78,29 @@ export function encodeHtmlEntities(text) {
|
|
|
78
78
|
return char;
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Encode a string for BASIC program input, escaping special characters
|
|
83
|
+
* @param text - String to encode
|
|
84
|
+
* @returns string - Encoded string with special characters escaped
|
|
85
|
+
*/
|
|
86
|
+
export function encodeTypeText(text) {
|
|
87
|
+
const replacementMap = {
|
|
88
|
+
'\r': '\\r',
|
|
89
|
+
'\t': '\\t',
|
|
90
|
+
'"': '\\"',
|
|
91
|
+
};
|
|
92
|
+
return text.replace(/[\r\t"]/g, (char) => {
|
|
93
|
+
if (replacementMap[char]) {
|
|
94
|
+
return replacementMap[char];
|
|
95
|
+
}
|
|
96
|
+
return char;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if a response is an error response
|
|
101
|
+
* @param response - The response string to check
|
|
102
|
+
* @returns boolean - True if the response indicates an error, false otherwise
|
|
103
|
+
*/
|
|
104
|
+
export function isErrorResponse(response) {
|
|
105
|
+
return response.startsWith('Error:') || response.startsWith('error:');
|
|
106
|
+
}
|