@nataliapc/mcp-openmsx 1.1.5 → 1.1.13

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 (49) hide show
  1. package/README.md +41 -2
  2. package/dist/openmsx.js +9 -0
  3. package/dist/server.js +502 -327
  4. package/dist/utils.js +17 -0
  5. package/package.json +4 -1
  6. package/resources/audio/toc.json +31 -0
  7. package/resources/bios/Calling_BIOS_from_MSX-DOS.md +75 -0
  8. package/resources/bios/MSX2_SUBROM_BIOS_calls.md +734 -0
  9. package/resources/bios/MSX_BIOS_calls.md +1046 -0
  10. package/resources/bios/toc.json +24 -0
  11. package/resources/book--msx2-technical-handbook/Appendix1__BIOS_Listing.md +1464 -0
  12. package/resources/book--msx2-technical-handbook/Appendix2__Math-Pack.md +427 -0
  13. package/resources/book--msx2-technical-handbook/Appendix3__Bit_Block_Transfer.md +182 -0
  14. package/resources/book--msx2-technical-handbook/Appendix4__Work_Area_Listing.md +1637 -0
  15. package/resources/book--msx2-technical-handbook/Appendix5__VRAM_Map.md +145 -0
  16. package/resources/book--msx2-technical-handbook/Appendix6__IO_Map.md +128 -0
  17. package/resources/book--msx2-technical-handbook/Appendix8_10__Control_Codes_and_Escape_Sequences.md +76 -0
  18. package/resources/book--msx2-technical-handbook/Chapter1__MSX_System_Overview.md +402 -0
  19. package/resources/book--msx2-technical-handbook/Chapter2__BASIC.md +2148 -0
  20. package/resources/book--msx2-technical-handbook/Chapter3__MSX-DOS.md +2577 -0
  21. package/resources/book--msx2-technical-handbook/Chapter4a__VDP_and_Display_Screen.md +2052 -0
  22. package/resources/book--msx2-technical-handbook/Chapter4b__VDP_and_Display_Screen.md +3311 -0
  23. package/resources/book--msx2-technical-handbook/Chapter5a__Access_to_Peripherals_through_BIOS.md +2714 -0
  24. package/resources/book--msx2-technical-handbook/Chapter5b__Access_to_Peripherals_through_BIOS.md +1263 -0
  25. package/resources/book--msx2-technical-handbook/MSX_Kun_BASIC_Compiler.md +220 -0
  26. package/resources/book--msx2-technical-handbook/toc.json +82 -0
  27. package/resources/book--the-msx-red-book/the_msx_red_book.md +10349 -0
  28. package/resources/book--the-msx-red-book/toc.json +12 -0
  29. package/resources/msx-dos/MSX-DOS_2_Function_Specifications.md +1366 -0
  30. package/resources/msx-dos/MSX-DOS_2_Program_Interface_Specification.md +963 -0
  31. package/resources/msx-dos/toc.json +18 -0
  32. package/resources/msx-unapi/Ethernet_UNAPI_specification_1.1.md +369 -0
  33. package/resources/msx-unapi/Introduction_to_MSX-UNAPI.md +132 -0
  34. package/resources/msx-unapi/MSX_UNAPI_specification_1.1.md +679 -0
  35. package/resources/msx-unapi/TCP-IP_UNAPI_specification.md +2361 -0
  36. package/resources/msx-unapi/toc.json +27 -0
  37. package/resources/others/toc.json +11 -0
  38. package/resources/processors/Z80_R800_instruction_set.md +482 -0
  39. package/resources/processors/toc.json +24 -0
  40. package/resources/processors/z80-undocumented.tex +5617 -0
  41. package/resources/processors/z80_detailed_instruction_set.md +2025 -0
  42. package/resources/programming/toc.json +121 -0
  43. package/resources/system/MSX_IO_ports_overview.md +554 -0
  44. package/resources/system/toc.json +18 -0
  45. package/resources/video/V9938_Technical_Data_Book.md +3623 -0
  46. package/resources/video/V9958_Technical_Data_Book.md +417 -0
  47. package/resources/video/V9990_Programmers_Manual_Banzai.html +1582 -0
  48. package/resources/video/VDP_TMS9918A.txt +709 -0
  49. package/resources/video/toc.json +28 -0
package/dist/server.js CHANGED
@@ -6,11 +6,11 @@
6
6
  * through TCL commands via stdio.
7
7
  *
8
8
  * @package @nataliapc/mcp-openmsx
9
- * @version 1.1.4
9
+ * @version 1.1.13
10
10
  * @author Natalia Pujol Cremades (@nataliapc)
11
11
  * @license GPL2
12
12
  */
13
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
14
14
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
15
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
16
16
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
@@ -18,11 +18,13 @@ import { randomUUID } from "node:crypto";
18
18
  import { z } from "zod";
19
19
  import express from "express";
20
20
  import fs from "fs/promises";
21
+ import mime from "mime-types";
21
22
  import path from "path";
22
23
  import { openMSXInstance } from "./openmsx.js";
23
- import { encodeTypeText, isErrorResponse } from "./utils.js";
24
+ import { encodeTypeText, isErrorResponse, getResponseContent } from "./utils.js";
24
25
  // Version info for CLI
25
- const PACKAGE_VERSION = "1.1.4";
26
+ const PACKAGE_VERSION = "1.1.13";
27
+ const resourcesDir = path.join(path.dirname(new URL(import.meta.url).pathname), "../resources");
26
28
  // Defaults for openMSX paths
27
29
  var OPENMSX_EXECUTABLE = 'openmsx';
28
30
  var OPENMSX_SHARE_DIR = '/usr/share/openmsx';
@@ -35,31 +37,32 @@ var EXTENSIONS_DIR = `${OPENMSX_SHARE_DIR}/extensions`;
35
37
  // Tools available in the MCP server
36
38
  // https://modelcontextprotocol.io/docs/concepts/tools#tool-definition-structure
37
39
  //
38
- function registerAllTools(server) {
39
- server.tool(
40
+ async function registerAllTools(server) {
41
+ server.registerTool(
40
42
  // Name of the tool (used to call it)
41
- "emu_control",
42
- // Description of the tool (what it does)
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
- `,
56
- // Schema for the tool (input validation)
57
- {
58
- command: z.enum(["launch", "close", "powerOn", "powerOff", "reset", "getEmulatorSpeed", "setEmulatorSpeed", "machineList", "extensionList", "wait"]),
59
- machine: z.string().min(1).max(100).optional(),
60
- extensions: z.array(z.string().min(1).max(100)).optional(),
61
- emuspeed: z.number().min(1).max(10000).optional().default(100),
62
- seconds: z.number().min(1).max(10).optional().default(2), // Seconds to wait
43
+ "emu_control", {
44
+ title: "Emulator control tools",
45
+ // Description of the tool (what it does)
46
+ description: "Tools to control an openMSX emulator.",
47
+ // Schema for the tool (input validation)
48
+ inputSchema: {
49
+ command: z.enum(["launch", "close", "powerOn", "powerOff", "reset", "getEmulatorSpeed", "setEmulatorSpeed", "machineList", "extensionList", "wait"]).describe(`Available commands:
50
+ '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. " +
51
+ 'close': closes the openMSX emulator.
52
+ 'powerOn': powers on the openMSX emulator.
53
+ 'powerOff': powers off the openMSX emulator.
54
+ 'reset': resets the current machine.
55
+ 'getEmulatorSpeed': gets the current emulator speed as a percentage, default is 100.
56
+ 'setEmulatorSpeed <emuspeed>': sets the emulator speed as a percentage, valid values are 1-10000, default is 100.
57
+ 'machineList': gets a list of all available MSX machines that can be emulated with openMSX.
58
+ 'extensionList': gets a list of all available MSX extensions that can be used with openMSX.
59
+ 'wait <seconds>': performs a wait for the specified number of seconds, default is 3.
60
+ `),
61
+ machine: z.string().min(1).max(100).optional().describe("Machine name to launch; valid names can be obtained using [machineList]. Used by [launch]."),
62
+ extensions: z.array(z.string().min(1).max(100)).optional().describe("List of extensions to use; valid extensions can be obtained using [extensionList]. Used by [launch]."),
63
+ emuspeed: z.number().min(1).max(10000).optional().default(100).describe("Emulator speed as a percentage (1-10000); default is 100. Used by [setEmulatorSpeed]."),
64
+ seconds: z.number().min(1).max(10).optional().default(3).describe("Number of seconds to wait; default is 3. Used by [wait]."),
65
+ },
63
66
  },
64
67
  // Handler for the tool (function to be executed when the tool is called)
65
68
  async ({ command, machine, extensions, emuspeed, seconds }) => {
@@ -110,28 +113,29 @@ function registerAllTools(server) {
110
113
  result
111
114
  ]);
112
115
  });
113
- server.tool(
116
+ server.registerTool(
114
117
  // Name of the tool (used to call it)
115
- "emu_media",
116
- // Description of the tool (what it does)
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
- `,
128
- // Schema for the tool (input validation)
129
- {
130
- command: z.enum(["tapeInsert", "tapeRewind", "tapeEject", "romInsert", "romEject", "diskInsert", "diskInsertFolder", "diskEject"]),
131
- tapefile: z.string().min(1).max(200).optional(), // Tape file to insert
132
- romfile: z.string().min(1).max(200).optional(), // ROM file to insert
133
- diskfile: z.string().min(1).max(200).optional(), // Disk file to insert
134
- diskfolder: z.string().min(1).max(200).optional(), // Disk folder to insert
118
+ "emu_media", {
119
+ title: "Emulator media tools",
120
+ // Description of the tool (what it does)
121
+ description: "Manage tapes, rom cartridges, and floppy disks.",
122
+ // Schema for the tool (input validation)
123
+ inputSchema: {
124
+ command: z.enum(["tapeInsert", "tapeRewind", "tapeEject", "romInsert", "romEject", "diskInsert", "diskInsertFolder", "diskEject"]).describe(`Available commands:
125
+ 'tapeInsert <tapefile>': insert a valid tape file (*.cas, *.wav, *.tsx).
126
+ 'tapeRewind': rewind the current tape.
127
+ 'tapeEject': remove tape from virtual cassette player.
128
+ 'romInsert <romfile>': insert a valid ROM cartridge file (*.rom) at cartridge slot A.
129
+ 'romEject': remove the current ROM cartridge from cartridge slot A.
130
+ 'diskInsert <diskfile>': insert a valid disk file (*.dsk) in floppy disk A.
131
+ 'diskInsertFolder <diskfolder>': use a host folder as a floppy disk A root directory.
132
+ 'diskEject': remove the current disk from floppy disk A.
133
+ `),
134
+ tapefile: z.string().min(1).max(200).optional().describe("Absolute Tape filename to insert. Used by [tapeInsert]"),
135
+ romfile: z.string().min(1).max(200).optional().describe("Absolute ROM filename to insert. Used by [romInsert]"),
136
+ diskfile: z.string().min(1).max(200).optional().describe("Absolute Disk filename to insert. Used by [diskInsert]"),
137
+ diskfolder: z.string().min(1).max(200).optional().describe("Absolute Disk folder filename to insert. Used by [diskInsertFolder]"),
138
+ },
135
139
  },
136
140
  // Handler for the tool (function to be executed when the tool is called)
137
141
  async ({ command, tapefile, romfile, diskfile, diskfolder }) => {
@@ -172,19 +176,20 @@ function registerAllTools(server) {
172
176
  response
173
177
  ]);
174
178
  });
175
- server.tool(
179
+ server.registerTool(
176
180
  // Name of the tool (used to call it)
177
- "emu_info",
178
- // Description of the tool (what it does)
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
- `,
185
- // Schema for the tool (input validation)
186
- {
187
- command: z.enum(["getStatus", "getSlotsMap", "getIOPortsMap"]),
181
+ "emu_info", {
182
+ title: "Emulator info tools",
183
+ // Description of the tool (what it does)
184
+ description: "Obtain informacion about the current emulated machine.",
185
+ // Schema for the tool (input validation)
186
+ inputSchema: {
187
+ command: z.enum(["getStatus", "getSlotsMap", "getIOPortsMap"]).describe(`Available commands:
188
+ 'getStatus': returns the status of the openMSX emulator.
189
+ 'getSlotsMap': shows what devices/ROM/RAM are inserted into which slots.
190
+ 'getIOPortsMap': shows an overview about the I/O mapped devices.
191
+ `),
192
+ },
188
193
  },
189
194
  // Handler for the tool (function to be executed when the tool is called)
190
195
  async ({ command }) => {
@@ -210,24 +215,25 @@ function registerAllTools(server) {
210
215
  response
211
216
  ]);
212
217
  });
213
- server.tool(
218
+ server.registerTool(
214
219
  // Name of the tool (used to call it)
215
- "emu_vdp",
216
- // Description of the tool (what it does)
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
- `,
226
- // Schema for the tool (input validation)
227
- {
228
- command: z.enum(["getPalette", "getRegisters", "getRegisterValue", "setRegisterValue", "screenGetMode", "screenGetFullText"]),
229
- register: z.number().min(0).max(31).optional(), // Register to read/write
230
- value: z.string().regex(/^0x[0-9a-fA-F]{2}$/).optional(),
220
+ "emu_vdp", {
221
+ title: "VDP tools",
222
+ // Description of the tool (what it does)
223
+ description: "Manage the VDP (Video Display Processor).",
224
+ // Schema for the tool (input validation)
225
+ inputSchema: {
226
+ command: z.enum(["getPalette", "getRegisters", "getRegisterValue", "setRegisterValue", "screenGetMode", "screenGetFullText"]).describe(`Available commands:
227
+ 'getPalette': returns the current V9938/V9958 color palette in RGB333 format.
228
+ 'getRegisters': returns all VDP register values.
229
+ 'getRegisterValue <register>': returns the value of a specific VDP register (0-31) in decimal format.
230
+ 'setRegisterValue <register> <value>': sets a hexadecimal value to a specific VDP register (0-31).
231
+ 'screenGetMode': returns the current screen mode (0-12) as a number, which matches the BASIC SCREEN command.
232
+ '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.
233
+ `),
234
+ register: z.number().min(0).max(31).optional().describe("VDP register number (0-31) to read/write. Used by [getRegisterValue, setRegisterValue]"),
235
+ value: z.string().regex(/^0x[0-9a-fA-F]{2}$/).optional().describe("2 hexadecimal digits for a VDP register value (e.g. 0x1f). Used by [setRegisterValue]"),
236
+ },
231
237
  },
232
238
  // Handler for the tool (function to be executed when the tool is called)
233
239
  async ({ command, register, value }) => {
@@ -263,26 +269,27 @@ function registerAllTools(server) {
263
269
  response
264
270
  ]);
265
271
  });
266
- server.tool(
272
+ server.registerTool(
267
273
  // Name of the tool (used to call it)
268
- "debug_run",
269
- // Description of the tool (what it does)
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
- `,
282
- // Schema for the tool (input validation)
283
- {
284
- command: z.enum(["break", "isBreaked", "continue", "stepIn", "stepOut", "stepOver", "stepBack", "runTo"]),
285
- address: z.string().regex(/^0x[0-9a-fA-F]{4}$/).optional()
274
+ "debug_run", {
275
+ title: "CPU Runtime Debugger tools",
276
+ // Description of the tool (what it does)
277
+ description: "Control execution (break, continue, step).",
278
+ // Schema for the tool (input validation)
279
+ inputSchema: {
280
+ command: z.enum(["break", "isBreaked", "continue", "stepIn", "stepOut", "stepOver", "stepBack", "runTo"]).describe(`Available commands:
281
+ 'break': to break CPU (pause emulation) at current execution position.
282
+ 'isBreaked': to check if the CPU is currently in break state (1) or not (0).
283
+ 'continue': to continue execution after break.
284
+ 'stepIn': to execute one CPU instruction, go into subroutines.
285
+ 'stepOver': to execute one CPU instruction, but don't go into subroutines.
286
+ 'stepOut': to step out of the current subroutine.
287
+ 'stepBack': to step one instruction back in time.
288
+ 'runTo <address>': to run the CPU until it reaches the specified address.
289
+ **Important Note**: Addresses and values are in hexadecimal format (e.g. 0x0000).
290
+ `),
291
+ address: z.string().regex(/^0x[0-9a-fA-F]{4}$/).optional().describe("4 hexadecimal digits for a memory address (e.g. 0x4af3). Used by [runTo]"),
292
+ },
286
293
  },
287
294
  // Handler for the tool (function to be executed when the tool is called)
288
295
  async ({ command, address }) => {
@@ -322,27 +329,29 @@ function registerAllTools(server) {
322
329
  response
323
330
  ]);
324
331
  });
325
- server.tool(
332
+ server.registerTool(
326
333
  // Name of the tool (used to call it)
327
- "debug_cpu",
328
- // Description of the tool (what it does)
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
- `,
339
- // Schema for the tool (input validation)
340
- {
341
- command: z.enum(["getCpuRegisters", "getRegister", "setRegister", "getStackPile", "disassemble", "getActiveCpu"]),
342
- register: z.enum(["pc", "sp", "ix", "iy", "af", "bc", "de", "hl", "ixh", "ixl", "iyh", "iyl", "a", "f", "b", "c", "d", "e", "h", "l", "i", "r", "im", "iff"]).optional(),
343
- address: z.string().regex(/^0x[0-9a-fA-F]{4}$/), // 4 hex digits for MSX memory address
344
- value: z.string().regex(/^0x[0-9a-fA-F]{2-4}$/).optional(), // 2-4 hex digits for byte value for writeByte command
345
- size: z.number().min(1).max(50).optional().default(8), // Number of bytes for disassemble command
334
+ "debug_cpu", {
335
+ title: "CPU tools",
336
+ // Description of the tool (what it does)
337
+ description: "Read/write CPU registers, CPU info, Stack pile, and Disassemble code from memory.",
338
+ // Schema for the tool (input validation)
339
+ inputSchema: {
340
+ command: z.enum(["getCpuRegisters", "getRegister", "setRegister", "getStackPile", "disassemble", "getActiveCpu"]).describe(`Available commands:
341
+ 'getCpuRegisters': to get an overview of all the CPU registers.
342
+ '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).
343
+ '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).
344
+ 'getStackPile': to get an overview of the CPU stack.
345
+ 'disassemble [address] [size]': to print disassembled instructions at the address parameter location or PC register if empty.
346
+ 'getActiveCpu': to return the active cpu: z80 or r800.
347
+ "**Important Note**: Addresses and values are in hexadecimal format (e.g. 0xd2 0x3af2)."
348
+ `),
349
+ register: z.enum(["pc", "sp", "ix", "iy", "af", "bc", "de", "hl", "ixh", "ixl", "iyh", "iyl", "a", "f", "b", "c", "d", "e", "h", "l", "i", "r", "im", "iff"]).optional()
350
+ .describe("CPU register to read/write. Used by [getRegister, setRegister]"),
351
+ address: z.string().regex(/^0x[0-9a-fA-F]{4}$/).optional().describe("4 hexadecimal digits for a memory address (e.g. 0x4af3). Used by [disassemble]"),
352
+ value: z.string().regex(/^0x[0-9a-fA-F]{2,4}$/).optional().describe("2-4 hexadecimal digits for a byte value (e.g. 0xa5 or 0xa5b1). Used by [setRegister]"),
353
+ size: z.number().min(8).max(50).optional().describe("Number of bytes to disassemble. Used by [disassemble]"),
354
+ },
346
355
  },
347
356
  // Handler for the tool (function to be executed when the tool is called)
348
357
  async ({ command, address, register, value, size }) => {
@@ -376,27 +385,28 @@ function registerAllTools(server) {
376
385
  response
377
386
  ]);
378
387
  });
379
- server.tool(
388
+ server.registerTool(
380
389
  // Name of the tool (used to call it)
381
- "debug_memory",
382
- // Description of the tool (what it does)
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
- `,
393
- // Schema for the tool (input validation)
394
- {
395
- command: z.enum(["selectedSlots", "getBlock", "readByte", "readWord", "writeByte", "writeWord"]),
396
- address: z.string().regex(/^0x[0-9a-fA-F]{4}$/).optional(), // 4 hex digits for MSX memory address
397
- lines: z.number().min(1).max(50).optional().default(8), // Number of lines for getBlock command
398
- value8: z.string().regex(/^0x[0-9a-fA-F]{2}$/).optional(), // 2 hex digits for byte value for writeByte command
399
- value16: z.string().regex(/^0x[0-9a-fA-F]{4}$/).optional(), // 4 hex digits for byte value for writeByte command
390
+ "debug_memory", {
391
+ title: "Memory tools",
392
+ // Description of the tool (what it does)
393
+ description: "Slots info, and Read/write from/to memory in the openMSX emulator.",
394
+ // Schema for the tool (input validation)
395
+ inputSchema: {
396
+ command: z.enum(["selectedSlots", "getBlock", "readByte", "readWord", "writeByte", "writeWord"]).describe(`Available commands:
397
+ 'selectedSlots': to get a list of the currently selected memory slots.
398
+ 'getBlock <address> [lines]': to read a block of memory from the specified address.
399
+ 'readByte <address>': to read a BYTE from the specified address.
400
+ 'readWord <address>': to read a WORD from the specified address.
401
+ 'writeByte <address> <value8>': to write a BYTE to the specified address.
402
+ 'writeWord <address> <value16>': to write a WORD to the specified address.
403
+ **Important Note**: Addresses and values are in hexadecimal format (e.g. 0x0000).
404
+ `),
405
+ address: z.string().regex(/^0x[0-9a-fA-F]{4}$/).optional().describe("4 hexadecimal digits for a memory address (e.g. 0x4af3). Used by [getBlock, readByte, writeByte, readWord, writeWord]"),
406
+ lines: z.number().min(1).max(50).optional().default(8).describe("Number of lines to obtain. Used by [getBlock]"),
407
+ value8: z.string().regex(/^0x[0-9a-fA-F]{2}$/).optional().describe("2 hexadecimal digits for a byte value (e.g. 0xa5). Used by [writeByte]"),
408
+ value16: z.string().regex(/^0x[0-9a-fA-F]{4}$/).optional().describe("4 hexadecimal digits for a word value (e.g. 0xa5b1). Used by [writeWord]"),
409
+ },
400
410
  },
401
411
  // Handler for the tool (function to be executed when the tool is called)
402
412
  async ({ command, address, lines, value8, value16 }) => {
@@ -430,23 +440,24 @@ function registerAllTools(server) {
430
440
  response
431
441
  ]);
432
442
  });
433
- server.tool(
443
+ server.registerTool(
434
444
  // Name of the tool (used to call it)
435
- "debug_vram",
436
- // Description of the tool (what it does)
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
- `,
444
- // Schema for the tool (input validation)
445
- {
446
- command: z.enum(["getBlock", "readByte", "writeByte"]),
447
- address: z.string().regex(/^0x[0-9a-fA-F]{4}$/).optional(), // 4 hex digits for MSX memory address
448
- lines: z.number().min(1).max(50).optional().default(8), // Number of lines for getBlock command
449
- value8: z.string().regex(/^0x[0-9a-fA-F]{2}$/).optional(), // 2 hex digits for byte value for writeByte command
445
+ "debug_vram", {
446
+ title: "VRAM tools",
447
+ // Description of the tool (what it does)
448
+ description: "Read or write from/to VRAM video memory from the openMSX emulator.",
449
+ // Schema for the tool (input validation)
450
+ inputSchema: {
451
+ command: z.enum(["getBlock", "readByte", "writeByte"]).describe(`Available commands:
452
+ 'getBlock <address> [lines]': to read a block of VRAM memory from the specified address.
453
+ 'readByte <address>': to read a BYTE from the specified VRAM address.
454
+ 'writeByte <address> <value8>': to write a BYTE to the specified VRAM address.
455
+ **Important Note**: Addresses and values are in hexadecimal format (e.g. 0x0000).
456
+ `),
457
+ address: z.string().regex(/^0x[0-9a-fA-F]{5}$/).optional().describe("5 hexadecimal digits for a VRAM address (e.g. 0x04af3). Used by [getBlock, readByte, writeByte]"),
458
+ lines: z.number().min(1).max(50).optional().default(8).describe("Number of lines to obtain. Used by [getBlock]"),
459
+ value8: z.string().regex(/^0x[0-9a-fA-F]{2}$/).optional().describe("2 hexadecimal digits for a byte value (e.g. 0xa5). Used by [writeByte]"),
460
+ },
450
461
  },
451
462
  // Handler for the tool (function to be executed when the tool is called)
452
463
  async ({ command, address, lines, value8 }) => {
@@ -471,23 +482,24 @@ function registerAllTools(server) {
471
482
  response
472
483
  ]);
473
484
  });
474
- server.tool(
485
+ server.registerTool(
475
486
  // Name of the tool (used to call it)
476
- "debug_breakpoints",
477
- // Description of the tool (what it does)
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
- `,
486
- // Schema for the tool (input validation)
487
- {
488
- command: z.enum(["create", "remove", "list"]),
489
- address: z.string().regex(/^0x[0-9a-fA-F]{4}$/).optional(), // 4 hex digits for MSX memory address
490
- bpname: z.string().min(3).max(10).optional(), // breakpoint name (e.g. bp#1)
487
+ "debug_breakpoints", {
488
+ title: "Breakpoints tools",
489
+ // Description of the tool (what it does)
490
+ description: "Create, remove, and list breakpoints.",
491
+ // Schema for the tool (input validation)
492
+ inputSchema: {
493
+ command: z.enum(["create", "remove", "list"]).describe(`Available commands:
494
+ 'create <address>': create a breakpoint at a specified address, and returns its name.
495
+ 'remove <bpname>': remove a breakpoint by name (e.g. bp#1).
496
+ 'list': enumerate the active breakpoints.
497
+ "**Important Note**: Addresses and values are in hexadecimal format (e.g. 0x4af3).
498
+ "**Important Note**: The memory addresses of functions and variables can be previously obtained from *.sym or *.map files.
499
+ `),
500
+ address: z.string().regex(/^0x[0-9a-fA-F]{4}$/).optional().describe("4 hexadecimal digits for a memory address (e.g. 0x4af3). Used by [create]"),
501
+ bpname: z.string().min(3).max(10).optional().describe("Breakpoint name (e.g. bp#1). Used by [remove]"),
502
+ },
491
503
  },
492
504
  // Handler for the tool (function to be executed when the tool is called)
493
505
  async ({ command, address, bpname }) => {
@@ -512,21 +524,22 @@ function registerAllTools(server) {
512
524
  response
513
525
  ]);
514
526
  });
515
- server.tool(
527
+ server.registerTool(
516
528
  // Name of the tool (used to call it)
517
- "emu_savestates",
518
- // Description of the tool (what it does)
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
- `,
526
- // Schema for the tool (input validation)
527
- {
528
- command: z.enum(["load", "save", "list"]),
529
- name: z.string().min(1).max(20).optional(), // breakpoint name (e.g. bp#1)
529
+ "emu_savestates", {
530
+ title: "Save states tools",
531
+ // Description of the tool (what it does)
532
+ description: "Load, save, and list savestates.",
533
+ // Schema for the tool (input validation)
534
+ inputSchema: {
535
+ command: z.enum(["load", "save", "list"]).describe(`Available commands:
536
+ 'load <name>': restores a previously created savestate.
537
+ 'save <name>': creates a snapshot of the currently emulated MSX machine specifying a name for the savestate.
538
+ 'list': returns the names of all previously created savestates, separated by spaces.
539
+ **Important Note**: names with spaces are enclosed in {}.
540
+ `),
541
+ name: z.string().min(1).max(20).optional().describe("Name of the savestate to load/save. Used by [load, save]"),
542
+ },
530
543
  },
531
544
  // Handler for the tool (function to be executed when the tool is called)
532
545
  async ({ command, name }) => {
@@ -556,32 +569,35 @@ function registerAllTools(server) {
556
569
  response
557
570
  ]);
558
571
  });
559
- server.tool(
572
+ server.registerTool(
560
573
  // Name of the tool (used to call it)
561
- "emu_replay",
562
- // Description of the tool (what it does)
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
- `,
576
- // Schema for the tool (input validation)
577
- {
578
- command: z.enum(["start", "stop", "status", "goBack", "absoluteGoto", "truncate", "saveReplay", "loadReplay"]),
579
- seconds: z.number().min(1).max(60).optional(), // Seconds to go back
580
- time: z.string().regex(/^\d+$/).optional(), // Time in seconds to go to
581
- filename: z.string().min(1).max(200).optional(), // Filename to save/load replay
574
+ "emu_replay", {
575
+ title: "Replay tools",
576
+ // Description of the tool (what it does)
577
+ description: "When replay is enabled (the default) the emulator collect data while emulating, which enables you to go back and forward in the emulated MSX time.",
578
+ // Schema for the tool (input validation)
579
+ inputSchema: {
580
+ command: z.enum(["start", "stop", "status", "goBack", "absoluteGoto", "truncate", "advanceFrame", "reverseFrame", "saveReplay", "loadReplay"]).describe(`Available commands:
581
+ 'start': starts the replay mode (enabled by default when emulator is launched).
582
+ 'stop': stops the replay mode.
583
+ 'status': gives information about the replay feature and the data that is collected.
584
+ 'goBack <seconds>': go back specified seconds (1-60) in the timeline, you cannot go back to a time before the time the replay started.
585
+ '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.
586
+ 'truncate': stop replaying and wipe all the future replay data after now.
587
+ 'advanceFrame' [frames]: advances a number of frames in the replay timeline, useful to advance the timeline while debugging.
588
+ 'reverseFrame' [frames]: reverses a number of frames in the replay timeline, useful to reverse the timeline while debugging.
589
+ 'saveReplay [filename]': saves the current replay data to a file (extension .omr), filename is returned in the response.
590
+ 'loadReplay <filename>': loads a previously saved replay file (extension .omr), starts replaying from the begin, and starts replay mode.
591
+ **Important Note**: consider do a #debug_run 'break' to maintain the timeline before a 'goBack' or 'absoluteGoto'.
592
+ `),
593
+ seconds: z.number().min(1).max(60).optional().describe("Seconds to go back. Used by [goBack]"),
594
+ time: z.string().regex(/^\d+$/).optional().describe("Absolute time in seconds to go to. Used by [absoluteGoto]"),
595
+ frames: z.number().min(1).max(1000).optional().default(1).describe("Number of frames to advance/reverse. Used by [advanceFrame, reverseFrame]"),
596
+ filename: z.string().min(1).max(200).optional().describe("Filename to save/load replay. Used by [saveReplay, loadReplay]"),
597
+ },
582
598
  },
583
599
  // Handler for the tool (function to be executed when the tool is called)
584
- async ({ command, seconds, time, filename }) => {
600
+ async ({ command, seconds, time, frames, filename }) => {
585
601
  let tclCommand;
586
602
  switch (command) {
587
603
  case "start":
@@ -602,6 +618,12 @@ function registerAllTools(server) {
602
618
  case "truncate":
603
619
  tclCommand = "reverse truncatereplay";
604
620
  break;
621
+ case "advanceFrame":
622
+ tclCommand = `advance_frame ${frames}`;
623
+ break;
624
+ case "reverseFrame":
625
+ tclCommand = `reverse_frame ${frames}`;
626
+ break;
605
627
  case "saveReplay":
606
628
  if (filename)
607
629
  filename = `"${OPENMSX_REPLAYS_DIR}${filename}"`;
@@ -622,20 +644,21 @@ function registerAllTools(server) {
622
644
  response
623
645
  ]);
624
646
  });
625
- server.tool(
647
+ server.registerTool(
626
648
  // Name of the tool (used to call it)
627
- "emu_keyboard",
628
- // Description of the tool (what it does)
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
- `,
635
- // Schema for the tool (input validation)
636
- {
637
- command: z.enum(["sendText"]),
638
- text: z.string().min(1).max(200).optional().default(''), // Key to send to the emulator
649
+ "emu_keyboard", {
650
+ title: "Keyboard tools",
651
+ // Description of the tool (what it does)
652
+ description: "Send a text to the openMSX emulator.",
653
+ // Schema for the tool (input validation)
654
+ inputSchema: {
655
+ command: z.enum(["sendText"]).describe(`Available commands:
656
+ 'sendText <text>': type a string in the emulated MSX, this command automatically press and release keys in the MSX keyboard matrix.
657
+ **Important Note**: each 'text' sent is limited to 200 characters, and the 'text' is sent as if it was typed in the MSX keyboard.
658
+ **Important Note**: escape keys that needs it as Return key (use \\r), double quotes (use \\\"), etc...
659
+ `),
660
+ text: z.string().min(1).max(200).optional().default('').describe("Text to send to the emulator via emulated keyboard"),
661
+ },
639
662
  },
640
663
  // Handler for the tool (function to be executed when the tool is called)
641
664
  async ({ command, text }) => {
@@ -654,18 +677,19 @@ function registerAllTools(server) {
654
677
  response
655
678
  ]);
656
679
  });
657
- server.tool(
680
+ server.registerTool(
658
681
  // Name of the tool (used to call it)
659
- "screen_shot",
660
- // Description of the tool (what it does)
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
- `,
666
- // Schema for the tool (input validation)
667
- {
668
- command: z.enum(["as_image", "to_file"]),
682
+ "screen_shot", {
683
+ title: "Screenshot tools",
684
+ // Description of the tool (what it does)
685
+ description: "Take a screenshot of the openMSX emulator screen.",
686
+ // Schema for the tool (input validation)
687
+ inputSchema: {
688
+ command: z.enum(["as_image", "to_file"]).describe(`Available commands:
689
+ 'as_image': take a screenshot and the image is returned in the response.
690
+ 'to_file': take a screenshot and save it to a file, the file name is returned in the response.
691
+ `),
692
+ },
669
693
  },
670
694
  // Handler for the tool (function to be executed when the tool is called)
671
695
  async ({ command }) => {
@@ -674,7 +698,7 @@ function registerAllTools(server) {
674
698
  switch (command) {
675
699
  case "as_image":
676
700
  try {
677
- // Check if the response is a file path using fstat
701
+ // Check if the response is a file path
678
702
  if (!response || !response.startsWith(OPENMSX_SCREENSHOT_DIR) || !response.endsWith('.png')) {
679
703
  throw new Error(`Invalid screenshot "${response}"`);
680
704
  }
@@ -710,16 +734,18 @@ function registerAllTools(server) {
710
734
  `Error: Unknown screen_shot command "${command}".`
711
735
  ]);
712
736
  });
713
- server.tool(
737
+ server.registerTool(
714
738
  // Name of the tool (used to call it)
715
- "screen_dump",
716
- // Description of the tool (what it does)
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
- `,
720
- // Schema for the tool (input validation)
721
- {
722
- scrbasename: z.string().min(1).max(100).default("screendump"),
739
+ "screen_dump", {
740
+ title: "Screen dump tools",
741
+ // Description of the tool (what it does)
742
+ description: `Take a screendump of the openMSX emulator screen as SC?.
743
+ The parameter scrbasename is the name of the filename (without path) to save the screendump, default is 'screendump'.
744
+ `,
745
+ // Schema for the tool (input validation)
746
+ inputSchema: {
747
+ scrbasename: z.string().min(1).max(100).default("screendump").describe("Screendump filename (without path or extension) to save the screendump; default is 'screendump'"),
748
+ },
723
749
  },
724
750
  // Handler for the tool (function to be executed when the tool is called)
725
751
  async ({ scrbasename }) => {
@@ -730,93 +756,105 @@ function registerAllTools(server) {
730
756
  response
731
757
  ]);
732
758
  });
733
- server.tool(
759
+ server.registerTool(
734
760
  // 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(),
761
+ "basic_programming", {
762
+ title: "BASIC programming tools",
763
+ // Description of the tool (what it does)
764
+ description: "Helper tool for developing BASIC programs.",
765
+ // Schema for the tool (input validation)
766
+ inputSchema: {
767
+ command: z.enum(["isBasicAvailable", "newProgram", "runProgram", "setProgram", "getFullProgram", "getFullProgramAdvanced", "listProgramLines", "deleteProgramLines"]).describe(`Available commands:
768
+ 'isBasicAvailable': checks if the current machine is ready to manage BASIC programs (true), or not (false).
769
+ 'newProgram': clears the current BASIC program.
770
+ 'setProgram <program>': sets a full BASIC program or updates part of the current BASIC program with the specified string.
771
+ 'runProgram': runs the current BASIC program.
772
+ 'getFullProgram': retrieves the current BASIC program as plain text; very useful in text screen modes.
773
+ 'getFullProgramAdvanced': retrieves the current BASIC program along with the RAM address where each line is stored.
774
+ 'listProgramLines <startLine> [endLine]': lists the selected range of lines from the current BASIC program on the emulator screen.
775
+ 'deleteProgramLines <startLine> [endLine]': deletes a specific range of lines from the current BASIC program; if endLine is not specified, only the startLine is deleted.
776
+ **Important Note**: if error 'not in BASIC mode' then use the command 'isBasicAvailable' to wait for a ready state.
777
+ **Important Note**: prioritize these tools for developing BASIC programs, as they are more efficient than using the 'sendText' tool.
778
+ **Important Note**: all program lines must end with a carriage return (\\r) to be processed correctly, including the last line.
779
+ **Important Note**: if you have questions about MSX BASIC, use the resources provided by this MCP server.
780
+ `),
781
+ program: z.string().min(1).max(10000).optional().describe("Basic program to set; use only \\r for line endings, even the last one. Used by [setProgram]"),
782
+ startLine: z.number().min(0).max(9999).optional().describe("Start line number to list/delete BASIC program lines. Used by [listProgramLines, deleteProgramLines]"),
783
+ endLine: z.number().min(0).max(9999).optional().describe("End line number to list/delete BASIC program lines. Used by [listProgramLines, deleteProgramLines]"),
784
+ },
755
785
  },
756
786
  // Handler for the tool (function to be executed when the tool is called)
757
787
  async ({ command, program, startLine, endLine }) => {
758
788
  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
789
  let tclCommand = undefined;
760
790
  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.';
791
+ const inBasic = await openMSXInstance.emu_isInBasic();
792
+ if (command !== "isBasicAvailable" && !inBasic) {
793
+ response = 'Error: The current MSX machine is not in BASIC mode.';
794
+ }
795
+ else
796
+ switch (command) {
797
+ case "isBasicAvailable":
798
+ response = inBasic.toString();
775
799
  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.';
800
+ case "newProgram":
801
+ response = await openMSXInstance.sendCommand(CTRL_L_TEMPLATE.replace('%s', encodeTypeText('new\r')));
802
+ if (response.startsWith('after#'))
803
+ response = '';
782
804
  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;
805
+ case "runProgram":
806
+ response = await openMSXInstance.sendCommand(CTRL_L_TEMPLATE.replace('%s', encodeTypeText('run\r')));
807
+ if (response.startsWith('after#'))
808
+ response = '';
788
809
  break;
789
- }
790
- // Set speed to fast for program input
791
- if (isErrorResponse(response = await openMSXInstance.sendCommand('set speed 10000')))
810
+ case "deleteProgramLines":
811
+ if (startLine === undefined) {
812
+ response = 'Error: No startLine number provided to delete BASIC program lines.';
813
+ break;
814
+ }
815
+ response = await openMSXInstance.sendCommand(CTRL_L_TEMPLATE.replace('%s', encodeTypeText(`delete ${startLine}-${endLine || startLine}\r`)));
792
816
  break;
793
- // Clear the screen and type the program
794
- if (isErrorResponse(response = await openMSXInstance.sendCommand(`type_via_keybuf "${encodeTypeText(program)}"`)))
817
+ case "setProgram":
818
+ if (!program) {
819
+ response = 'Error: no BASIC program provided to set.';
820
+ break;
821
+ }
822
+ if (program.includes('\n') || program.includes('\\n')) {
823
+ response = 'Error: you cannot use \\n for line endings, use only \\r instead.';
824
+ break;
825
+ }
826
+ // Escape '$' characters if '(' is the next character and is not escaped yet (openMSX variable substitutions)
827
+ program = program.replace(/([^\\])(\$\()/g, '$1\\$2');
828
+ // Get current speed to restore it later
829
+ let speed = '100';
830
+ if (isErrorResponse(speed = await openMSXInstance.sendCommand('set speed'))) {
831
+ response = speed;
832
+ break;
833
+ }
834
+ // Set speed to fast, type de program, wait to end, and restore speed
835
+ if (isErrorResponse(response = await openMSXInstance.sendCommand(`set speed 10000 ; type_via_keybuf "${encodeTypeText(program)}" ; after idle 20 { set speed ${speed} }`)))
836
+ break;
837
+ // Success response
838
+ response = '';
795
839
  break;
796
- // Restore the original speed
797
- if (isErrorResponse(response = await openMSXInstance.sendCommand(`set speed ${speed}`)))
840
+ case "getFullProgram":
841
+ // Source: https://www.msx.org/forum/msx-talk/openmsx/export-basic-listing#comment-407392
842
+ tclCommand = 'regsub -all -line {^[0-9a-f]x[0-9a-f]{4} > } [ listing ] ""';
798
843
  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.';
844
+ case "getFullProgramAdvanced":
845
+ tclCommand = "listing";
812
846
  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
- }
847
+ case "listProgramLines":
848
+ if (startLine === undefined) {
849
+ response = 'Error: No start line provided to list BASIC program lines.';
850
+ break;
851
+ }
852
+ tclCommand = `type_via_keybuf \"${encodeTypeText(`list ${startLine}-${endLine || startLine}\r`)}\"`;
853
+ break;
854
+ default:
855
+ response = `Error: Unknown command "${command}".`;
856
+ break;
857
+ }
820
858
  if (response === undefined && tclCommand) {
821
859
  response = await openMSXInstance.sendCommand(tclCommand);
822
860
  }
@@ -824,17 +862,148 @@ function registerAllTools(server) {
824
862
  response !== undefined ? response : `Error: No response for command "${command}".`
825
863
  ]);
826
864
  });
865
+ // ============================================================================
866
+ // MSX Documentation resources
867
+ const resdocs = (await listResourcesDirectory()).sort();
868
+ for (let index = 0; index < resdocs.length; index++) {
869
+ const sectionName = resdocs[index];
870
+ const tocFile = path.join(resourcesDir, `${sectionName}/toc.json`);
871
+ const tocContent = JSON.parse(await fs.readFile(tocFile, 'utf8'));
872
+ tocContent.toc.forEach((item, itemIndex) => {
873
+ const itemName = item.uri.split('/').pop() || '';
874
+ server.registerResource(
875
+ // Name of the resource (used to call it)
876
+ `msxdocs_${sectionName}_${itemName}`,
877
+ // Resource URI template
878
+ item.uri,
879
+ // Metadata for the resource
880
+ {
881
+ title: item.title || `MSX Documentation '${sectionName}' - ${itemName}`,
882
+ description: item.description || `Documentation for MSX resource '${sectionName}' - ${itemName}`,
883
+ mimeType: item.mimeType || 'text/markdown',
884
+ },
885
+ // Handler for the resource (function to be executed when the resource is called)
886
+ async (uri) => {
887
+ let resourceContent;
888
+ let mimeType;
889
+ if (uri.href.startsWith('http://') || uri.href.startsWith('https://')) {
890
+ // Fetch the resource from the URL
891
+ try {
892
+ resourceContent = await fetch(uri.href).then(response => {
893
+ mimeType = response.headers.get('content-type') || 'text/plain';
894
+ return response.text();
895
+ }) || 'Error downloading resource content';
896
+ }
897
+ catch (error) {
898
+ // Throw exception (MCP protocol requirement)
899
+ throw new Error(`Error fetching resource from ${uri.href}: ${error instanceof Error ? error.message : String(error)}`);
900
+ }
901
+ }
902
+ else {
903
+ // Read the resource from the local MCP server resources directory
904
+ try {
905
+ let resourceFile;
906
+ [mimeType, resourceFile] = await addFileExtension(path.join(resourcesDir, `${sectionName}/${itemName}`));
907
+ resourceContent = await fs.readFile(resourceFile, 'utf8');
908
+ }
909
+ catch (error) {
910
+ // Throw exception (MCP protocol requirement)
911
+ throw new Error(`Error reading resource ${sectionName}/${item.uri}: ${error instanceof Error ? error.message : String(error)}`);
912
+ }
913
+ }
914
+ return {
915
+ contents: [{
916
+ uri: uri.href,
917
+ text: resourceContent,
918
+ mimeType: mimeType || 'text/plain',
919
+ }],
920
+ };
921
+ });
922
+ });
923
+ }
924
+ ;
925
+ server.resource("msxdocs_msxorg_wiki", new ResourceTemplate("msxdocs://msxorg_wiki/{section}", {
926
+ list: undefined,
927
+ complete: {
928
+ section: (value) => [
929
+ "ABS()", "AND", "ASC()", "ATN()", "AUTO", "BASE()", "BEEP", "BIN$()", "BLOAD", "BSAVE", "CALL", "CALL ADJUST", "CALL PAUSE", "CALL PCMPLAY", "CALL PCMREC",
930
+ "CDBL()", "CHR$()", "CINT()", "CIRCLE", "CLEAR", "CLOAD", "CLOAD?", "CLOSE", "CLS", "COLOR", "COLOR=", "COLOR", "COLOR", "CONT", "COPY", "COPY", "COS()", "CSAVE",
931
+ "CSNG()", "CSRLIN", "DATA", "DEFDBL", "DEF FN", "DEFINT", "DEFSNG", "DEFSTR", "DEF USR", "DELETE", "DIM", "DRAW", "ELSE", "END", "EOF()", "EQV", "ERASE", "ERL", "ERR",
932
+ "ERROR", "EXP()", "FIX()", "FN MSX1", "FOR...NEXT", "FRE()", "GET DATE", "GET TIME", "GOSUB", "GOTO", "HEX$()", "IF...GOTO...ELSE", "IF...THEN...ELSE",
933
+ "IMP", "INKEY$", "INP()", "INPUT", "INPUT$()", "INSTR()", "INT()", "INTERVAL", "KEY", "KEY()", "LEFT$()", "LEN()", "LET", "LINE", "LINE INPUT", "LIST",
934
+ "LLIST", "LOAD", "LOCATE", "LOG()", "LPOS()", "LPRINT", "MAXFILES", "MERGE", "MID$()", "MOD", "MOTOR", "NEW", "NOT", "OCT$()", "ON...GOSUB", "ON...GOTO",
935
+ "ON ERROR GOTO", "ON INTERVAL GOSUB", "ON KEY GOSUB", "ON SPRITE GOSUB", "ON STOP GOSUB", "ON STRIG GOSUB", "OPEN", "OR", "OUT", "PAD()", "PAINT", "PDL()",
936
+ "PEEK()", "PLAY", "PLAY()", "POINT", "POKE", "POS()", "PRESET", "PRINT", "PSET", "PUT KANJI", "PUT SPRITE", "READ", "REM", "RENUM", "RESTORE", "RESUME",
937
+ "RETURN", "RIGHT$()", "RND()", "RUN", "SAVE", "SCREEN", "SET ADJUST", "SET BEEP", "SET DATE", "SET PAGE", "SET PASSWORD", "SET PROMPT", "SET SCREEN",
938
+ "SET SCROLL", "SET TIME", "SET TITLE", "SET VIDEO", "SGN()", "SIN()", "SOUND", "SPACE$()", "SPC()", "SPRITE", "SPRITE$()", "SQR()", "STICK()", "STOP",
939
+ "STR$()", "STRIG()", "STRING$()", "SWAP", "TAB()", "TAN()", "TIME", "TROFF", "TRON", "USR()", "VAL()", "VARPTR()", "VDP()", "VPEEK()", "VPOKE", "WAIT",
940
+ "WIDTH", "XOR"
941
+ ],
942
+ },
943
+ }), {
944
+ title: "BASIC MSX Documentation",
945
+ description: "Documentation about all the standard MSX-BASIC instructions.",
946
+ mimeType: "text/html",
947
+ }, async (uri, variables) => {
948
+ const section = variables.section.replace(/ /g, '_').replace(/\?/g, '%3F').replace(/=/g, '%3D');
949
+ const url = `https://www.msx.org/wiki/${section}`;
950
+ let resourceContent;
951
+ let mimeType;
952
+ try {
953
+ resourceContent = await fetch(url).then(response => {
954
+ mimeType = response.headers.get('content-type') || 'text/plain';
955
+ return response.text();
956
+ }) || 'Error downloading resource content';
957
+ // Remove script, style, and link tags from the content
958
+ resourceContent = resourceContent.replace(/<script\b[^>]*>[\s\S]*?<\/script>|<style\b[^>]*>[\s\S]*?<\/style>|<link\b[^>]*\/?>/gi, '');
959
+ }
960
+ catch (error) {
961
+ // Throw exception (MCP protocol requirement)
962
+ throw new Error(`Error fetching resource "${uri}" from "${url}": ${error instanceof Error ? error.message : String(error)}`);
963
+ }
964
+ return {
965
+ contents: [{
966
+ uri: uri.href,
967
+ text: resourceContent,
968
+ mimeType: mimeType || 'text/plain',
969
+ }],
970
+ };
971
+ });
972
+ }
973
+ async function addFileExtension(filePath) {
974
+ // Get directory and filename
975
+ const directory = path.dirname(filePath);
976
+ const filename = path.basename(filePath);
977
+ try {
978
+ // Get all files in directory that start with our filename
979
+ const files = await fs.readdir(directory);
980
+ const matchingFiles = files.filter(file => file.startsWith(filename));
981
+ if (matchingFiles.length > 0) {
982
+ const fileFound = path.join(directory, matchingFiles[0]);
983
+ return [
984
+ mime.lookup(fileFound) || 'text/plain',
985
+ fileFound
986
+ ];
987
+ }
988
+ }
989
+ catch (error) {
990
+ console.error('Error reading directory:', error);
991
+ }
992
+ // Return original if no matches found
993
+ return ['text/plain', filePath];
827
994
  }
828
- function getResponseContent(response, isError = false) {
829
- // Check if any response line starts with "Error:" to automatically set isError to true
830
- const hasError = isError || response.some(line => isErrorResponse(line));
831
- return {
832
- content: response.map(line => ({
833
- type: "text",
834
- text: line == '' ? "Ok" : line,
835
- })),
836
- isError: hasError
837
- };
995
+ async function listResourcesDirectory() {
996
+ try {
997
+ const directories = await fs.readdir(resourcesDir, { withFileTypes: true });
998
+ const folderNames = directories
999
+ .filter(dirent => dirent.isDirectory())
1000
+ .map(dirent => dirent.name);
1001
+ return folderNames;
1002
+ }
1003
+ catch (error) {
1004
+ console.error("Error reading resources directory:", error);
1005
+ return [];
1006
+ }
838
1007
  }
839
1008
  // ============================================================================
840
1009
  // Cleanup handlers for graceful shutdown of MCP server
@@ -933,7 +1102,7 @@ async function startHttpServer() {
933
1102
  }
934
1103
  };
935
1104
  // Create a new server instance for this session
936
- const httpServer = createServerInstance();
1105
+ const httpServer = await createServerInstance();
937
1106
  await httpServer.connect(transport);
938
1107
  }
939
1108
  else {
@@ -961,14 +1130,20 @@ async function startHttpServer() {
961
1130
  console.log(`MCP Server listening on port ${port}`);
962
1131
  });
963
1132
  }
964
- function createServerInstance() {
1133
+ async function createServerInstance() {
965
1134
  // Create a new server instance (you might want to extract server creation logic)
966
1135
  const newServer = new McpServer({
967
1136
  name: "mcp-openmsx",
968
1137
  version: PACKAGE_VERSION,
969
1138
  });
970
1139
  // Re-register all tools (you might want to extract this to a separate function)
971
- registerAllTools(newServer);
1140
+ try {
1141
+ await registerAllTools(newServer);
1142
+ }
1143
+ catch (error) {
1144
+ console.error("Error registering tools/resources:", error);
1145
+ throw error;
1146
+ }
972
1147
  return newServer;
973
1148
  }
974
1149
  // ============================================================================
@@ -1012,7 +1187,7 @@ async function main() {
1012
1187
  else {
1013
1188
  // Default to stdio
1014
1189
  const transport = new StdioServerTransport();
1015
- await createServerInstance().connect(transport);
1190
+ (await createServerInstance()).connect(transport);
1016
1191
  }
1017
1192
  }
1018
1193
  main().catch((error) => {