@nataliapc/mcp-openmsx 1.1.0 → 1.1.3

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 CHANGED
@@ -6,8 +6,6 @@ A [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) s
6
6
 
7
7
  This server provides comprehensive tools for MSX software development, testing, and automation through standardized MCP protocols.
8
8
 
9
- Currently, the MCP server requires Linux to run. It has not been tested on Windows or macOS, although it will likely work on the latter as well.
10
-
11
9
  ## 🎯 Project Overview
12
10
 
13
11
  This project creates a bridge between modern AI-assisted development (e.g. GitHub Copilot, Claude Desktop) and retro computing (MSX systems) by providing:
@@ -66,6 +64,8 @@ The MCP server translates high-level commands from your Copilot AI into `TCL` co
66
64
 
67
65
  ## 🚀 Quick Start
68
66
 
67
+ You can use this MCP server in this basic way with the [precompiled NPM package](https://www.npmjs.com/package/@nataliapc/mcp-openmsx). You may need to have `nodejs` installed for this to work.
68
+
69
69
  ### 🟢 Basic Usage with VSCode
70
70
 
71
71
  * Install [Github Copilot extension](https://code.visualstudio.com/docs/copilot/overview)
@@ -86,7 +86,8 @@ The MCP server translates high-level commands from your Copilot AI into `TCL` co
86
86
  }
87
87
  }
88
88
  ```
89
- **Note:** Environment variables are optional.
89
+
90
+ **Note:** Environment variables are optional. Customize them as you need.
90
91
 
91
92
  ### Streamed HTTP mode (more advanced)
92
93
 
@@ -136,6 +137,8 @@ Add to your `claude_desktop_config.json`:
136
137
 
137
138
  ## 🧑‍💻 Advanced Manual Usage
138
139
 
140
+ Currently, the MCP server requires Linux to be compiled. It has not been tested on Windows or macOS, although it will likely work on the latter as well.
141
+
139
142
  ### Manual installation
140
143
 
141
144
  ```bash
package/dist/openmsx.js CHANGED
@@ -7,6 +7,7 @@
7
7
  import fs from "fs/promises";
8
8
  import { extractDescriptionFromXML, decodeHtmlEntities } from "./utils.js";
9
9
  import { spawn } from 'child_process';
10
+ import path from 'path';
10
11
  /**
11
12
  * OpenMSX class for controlling the openMSX emulator via TCL commands over TCP socket
12
13
  */
@@ -143,7 +144,7 @@ export class OpenMSX {
143
144
  async emu_close() {
144
145
  return new Promise((resolve) => {
145
146
  if (!this.process) {
146
- resolve("No emulator process running");
147
+ resolve("Error: No emulator process running");
147
148
  return;
148
149
  }
149
150
  this.process.on('exit', () => {
@@ -152,7 +153,7 @@ export class OpenMSX {
152
153
  resolve("Ok: Emulator process closed successfully");
153
154
  });
154
155
  this.process.on('error', (error) => {
155
- resolve(`Error closing emulator: ${error.message}`);
156
+ resolve(`Error: error closing emulator: ${error.message}`);
156
157
  });
157
158
  // Try graceful shutdown first
158
159
  if (this.isConnected) {
@@ -183,7 +184,7 @@ export class OpenMSX {
183
184
  try {
184
185
  const response = await this.sendCommand('machine_info');
185
186
  if (response.startsWith('Error:')) {
186
- return JSON.stringify({ error: response });
187
+ return response;
187
188
  }
188
189
  // Parse machine_info output into key-value pairs
189
190
  const parameters = response.trim().split(' ');
@@ -198,9 +199,7 @@ export class OpenMSX {
198
199
  return JSON.stringify(machineInfo, null, 2);
199
200
  }
200
201
  catch (error) {
201
- return JSON.stringify({
202
- error: `Failed to get machine status: ${error instanceof Error ? error.message : 'Unknown error'}`
203
- });
202
+ return `Error: Failed to get machine status - ${error instanceof Error ? error.message : 'Unknown error'}`;
204
203
  }
205
204
  }
206
205
  /**
@@ -210,7 +209,7 @@ export class OpenMSX {
210
209
  async getMachineList(machinesDirectory) {
211
210
  // Read the machines directory
212
211
  let machines = [];
213
- let machinesList = "No machines found.";
212
+ let machinesList = "Error: No machines found.";
214
213
  try {
215
214
  const allFiles = await fs.readdir(machinesDirectory);
216
215
  machines = await Promise.all(allFiles
@@ -218,17 +217,17 @@ export class OpenMSX {
218
217
  .map(async (file) => {
219
218
  return {
220
219
  name: file.replace('.xml', ''),
221
- description: await extractDescriptionFromXML(`${machinesDirectory}+path.sep+${file}`)
220
+ description: await extractDescriptionFromXML(path.join(machinesDirectory, file))
222
221
  };
223
222
  }));
223
+ if (machines.length !== 0) {
224
+ machinesList = JSON.stringify(machines, null, 2);
225
+ }
226
+ return machinesList;
224
227
  }
225
228
  catch (error) {
226
- machinesList = 'Error reading machines directory: ' + error;
227
- }
228
- if (machines.length !== 0) {
229
- machinesList = JSON.stringify(machines, null, 2);
229
+ return `Error: error reading machines directory - ${error instanceof Error ? error.message : error}`;
230
230
  }
231
- return machinesList;
232
231
  }
233
232
  /**
234
233
  * Get the list of extensions available in the openMSX emulator
@@ -237,7 +236,7 @@ export class OpenMSX {
237
236
  async getExtensionList(extensionDirectory) {
238
237
  // Read the extensions directory
239
238
  let extensions = [];
240
- let extensionsList = "No extensions found.";
239
+ let extensionsList = "Error: No extensions found.";
241
240
  try {
242
241
  const allFiles = await fs.readdir(extensionDirectory);
243
242
  extensions = await Promise.all(allFiles
@@ -245,17 +244,17 @@ export class OpenMSX {
245
244
  .map(async (file) => {
246
245
  return {
247
246
  name: file.replace('.xml', ''),
248
- description: await extractDescriptionFromXML(`${extensionDirectory}+path.sep+${file}`)
247
+ description: await extractDescriptionFromXML(path.join(extensionDirectory, file))
249
248
  };
250
249
  }));
250
+ if (extensions.length !== 0) {
251
+ extensionsList = JSON.stringify(extensions, null, 2);
252
+ }
253
+ return extensionsList;
251
254
  }
252
255
  catch (error) {
253
- extensionsList = 'Error reading extensions directory: ' + error;
254
- }
255
- if (extensions.length !== 0) {
256
- extensionsList = JSON.stringify(extensions, null, 2);
256
+ return `Error: error reading extensions directory - ${error instanceof Error ? error.message : error}`;
257
257
  }
258
- return extensionsList;
259
258
  }
260
259
  ;
261
260
  /**
@@ -284,7 +283,7 @@ export class OpenMSX {
284
283
  return decodeHtmlEntities(output.trim());
285
284
  }
286
285
  catch (error) {
287
- return `Error: ${error instanceof Error ? error.message : 'Unknown error'}`;
286
+ return `Error: ${error instanceof Error ? error.message : error}`;
288
287
  }
289
288
  }
290
289
  /**
package/dist/server.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * through TCL commands via stdio.
7
7
  *
8
8
  * @package @nataliapc/mcp-openmsx
9
- * @version 1.1.0
9
+ * @version 1.1.3
10
10
  * @author Natalia Pujol Cremades (@nataliapc)
11
11
  * @license GPL2
12
12
  */
@@ -21,7 +21,7 @@ import fs from "fs/promises";
21
21
  import path from "path";
22
22
  import { openMSXInstance } from "./openmsx.js";
23
23
  // Version info for CLI
24
- const PACKAGE_VERSION = "1.1.0";
24
+ const PACKAGE_VERSION = "1.1.3";
25
25
  // Defaults for openMSX paths
26
26
  var OPENMSX_EXECUTABLE = 'openmsx';
27
27
  var OPENMSX_SHARE_DIR = '/usr/share/openmsx';
@@ -39,7 +39,7 @@ function registerAllTools(server) {
39
39
  "emu_control",
40
40
  // Description of the tool (what it does)
41
41
  "Controls an openMSX emulator. Commands: " +
42
- "'launch [machine] [extensions]': opens a powered-on openMSX emulator; machine and extensions parameters can be specified; use 'machineList' and 'extensionList' tools to obtain valid values. " +
42
+ "'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. " +
43
43
  "'close': closes the openMSX emulator. " +
44
44
  "'powerOn': powers on the openMSX emulator. " +
45
45
  "'powerOff': powers off the openMSX emulator. " +
@@ -59,7 +59,7 @@ function registerAllTools(server) {
59
59
  },
60
60
  // Handler for the tool (function to be executed when the tool is called)
61
61
  async ({ command, machine, extensions, emuspeed, seconds }) => {
62
- let result = "Error";
62
+ let result = '';
63
63
  switch (command) {
64
64
  case "launch":
65
65
  result = await openMSXInstance.emu_launch(OPENMSX_EXECUTABLE, machine || "", extensions || []);
@@ -69,23 +69,23 @@ function registerAllTools(server) {
69
69
  break;
70
70
  case "powerOn":
71
71
  result = await openMSXInstance.sendCommand('set power on');
72
- result += result === "Ok" ? ": openMSX emulator powered on" : "";
72
+ result = result === "true" ? "openMSX emulator powered on" : "Error: " + result;
73
73
  break;
74
74
  case "powerOff":
75
75
  result = await openMSXInstance.sendCommand('set power off');
76
- result += result === "Ok" ? ": openMSX emulator powered off" : "";
76
+ result = result === "false" ? "openMSX emulator powered off" : "Error: " + result;
77
77
  break;
78
78
  case "reset":
79
79
  result = await openMSXInstance.sendCommand('reset');
80
- result += result === "Ok" ? ": openMSX emulator reset" : "";
80
+ result = result === "" ? "openMSX emulator reset successful" : "Error: " + result;
81
81
  break;
82
82
  case 'getEmulatorSpeed':
83
83
  result = await openMSXInstance.sendCommand('set speed');
84
- result = !isNaN(Number(result)) ? `Current emulator speed is ${result}%` : result;
84
+ result = !isNaN(Number(result)) ? `Current emulator speed is ${result}%` : "Error: " + result;
85
85
  break;
86
86
  case 'setEmulatorSpeed':
87
87
  result = await openMSXInstance.sendCommand(`set speed ${emuspeed}`);
88
- result = !isNaN(Number(result)) ? `Emulator speed set to ${emuspeed}%` : result;
88
+ result = !isNaN(Number(result)) ? `Emulator speed set to ${emuspeed}%` : "Error: " + result;
89
89
  break;
90
90
  case "machineList":
91
91
  result = await openMSXInstance.getMachineList(MACHINES_DIR);
@@ -206,13 +206,13 @@ function registerAllTools(server) {
206
206
  // Name of the tool (used to call it)
207
207
  "emu_vdp",
208
208
  // Description of the tool (what it does)
209
- "Manage VDP (Video Display Processor). Commands: " +
210
- "'getPalette': return the current V9938/V9958 colors palette in format RGB333. " +
211
- "'getRegisters': return all the VDP register values. " +
212
- "'getRegisterValue <register>': return the value of a specific VDP register (0-31) in decimal format. " +
213
- "'setRegisterValue <register> <value>': set a hexadecimal value to a specific VDP register (0-31). " +
214
- "'screenGetMode': returns the current screen mode (0-12) as a number which would also be used for the basic command SCREEN. " +
215
- "'screenGetFullText': return the full content of an MSX text screen (screen 0 or 1) as a string, useful for debugging. ",
209
+ "Manage the VDP (Video Display Processor). Commands: " +
210
+ "'getPalette': returns the current V9938/V9958 color palette in RGB333 format. " +
211
+ "'getRegisters': returns all VDP register values. " +
212
+ "'getRegisterValue <register>': returns the value of a specific VDP register (0-31) in decimal format. " +
213
+ "'setRegisterValue <register> <value>': sets a hexadecimal value to a specific VDP register (0-31). " +
214
+ "'screenGetMode': returns the current screen mode (0-12) as a number, which matches the BASIC SCREEN command. " +
215
+ "'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. ",
216
216
  // Schema for the tool (input validation)
217
217
  {
218
218
  command: z.enum(["getPalette", "getRegisters", "getRegisterValue", "setRegisterValue", "screenGetMode", "screenGetFullText"]),
@@ -605,11 +605,13 @@ function registerAllTools(server) {
605
605
  "emu_keyboard",
606
606
  // Description of the tool (what it does)
607
607
  "Send a text to the openMSX emulator. Commands: " +
608
- "'sendText <text>': type a string in the emulated MSX, this command automatically press and release keys in the MSX keyboard matrix, is useful for automating tasks in BASIC, use \\r for Return key. ",
608
+ "'sendText <text>': type a string in the emulated MSX, this command automatically press and release keys in the MSX keyboard matrix, is useful for automating tasks in BASIC. " +
609
+ "Note: each 'text' sent is limited to 200 characters, and the 'text' is sent as if it was typed in the MSX keyboard. " +
610
+ "Note: escape keys that needs it as Return key (use \\r), double quotes (use \\\"), etc...",
609
611
  // Schema for the tool (input validation)
610
612
  {
611
613
  command: z.enum(["sendText"]),
612
- text: z.string().min(1).max(100).optional().default(''), // Key to send to the emulator
614
+ text: z.string().min(1).max(200).optional().default(''), // Key to send to the emulator
613
615
  },
614
616
  // Handler for the tool (function to be executed when the tool is called)
615
617
  async ({ command, text }) => {
@@ -662,8 +664,8 @@ function registerAllTools(server) {
662
664
  catch (error) {
663
665
  return getResponseContent([
664
666
  'Error creating screenshot: ' + response,
665
- error instanceof Error ? error.message : String(error)
666
- ]);
667
+ error instanceof Error ? error.message : String(error),
668
+ ], true);
667
669
  }
668
670
  case "to_file":
669
671
  return getResponseContent([
@@ -693,12 +695,15 @@ function registerAllTools(server) {
693
695
  ]);
694
696
  });
695
697
  }
696
- function getResponseContent(response) {
698
+ function getResponseContent(response, isError = false) {
699
+ // Check if any response line starts with "Error:" to automatically set isError to true
700
+ const hasError = isError || response.some(line => line.startsWith("Error:"));
697
701
  return {
698
702
  content: response.map(line => ({
699
703
  type: "text",
700
704
  text: line == '' ? "Ok" : line,
701
705
  })),
706
+ isError: hasError
702
707
  };
703
708
  }
704
709
  // ============================================================================
@@ -767,7 +772,7 @@ Environment variables:
767
772
 
768
773
  Examples:
769
774
  mcp-openmsx # stdio transport
770
- mcp-openmsx http # HTTP transport
775
+ mcp-openmsx http # HTTP transport
771
776
  MCP_TRANSPORT=http mcp-openmsx # HTTP via environment
772
777
  `);
773
778
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nataliapc/mcp-openmsx",
3
- "version": "1.1.0",
3
+ "version": "1.1.3",
4
4
  "description": "Model context protocol server for openMSX automation and control",
5
5
  "main": "dist/server.js",
6
6
  "type": "module",