@nataliapc/mcp-openmsx 1.1.13 → 1.1.14

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/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.13
9
+ * @version 1.1.14
10
10
  * @author Natalia Pujol Cremades (@nataliapc)
11
11
  * @license GPL2
12
12
  */
@@ -18,12 +18,11 @@ 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";
22
21
  import path from "path";
23
22
  import { openMSXInstance } from "./openmsx.js";
24
- import { encodeTypeText, isErrorResponse, getResponseContent } from "./utils.js";
23
+ import { fetchCleanWebpage, addFileExtension, listResourcesDirectory, encodeTypeText, isErrorResponse, getResponseContent } from "./utils.js";
25
24
  // Version info for CLI
26
- const PACKAGE_VERSION = "1.1.13";
25
+ const PACKAGE_VERSION = "1.1.14";
27
26
  const resourcesDir = path.join(path.dirname(new URL(import.meta.url).pathname), "../resources");
28
27
  // Defaults for openMSX paths
29
28
  var OPENMSX_EXECUTABLE = 'openmsx';
@@ -864,22 +863,22 @@ The parameter scrbasename is the name of the filename (without path) to save the
864
863
  });
865
864
  // ============================================================================
866
865
  // MSX Documentation resources
867
- const resdocs = (await listResourcesDirectory()).sort();
866
+ const resdocs = (await listResourcesDirectory(resourcesDir)).sort();
868
867
  for (let index = 0; index < resdocs.length; index++) {
869
868
  const sectionName = resdocs[index];
870
869
  const tocFile = path.join(resourcesDir, `${sectionName}/toc.json`);
871
870
  const tocContent = JSON.parse(await fs.readFile(tocFile, 'utf8'));
872
871
  tocContent.toc.forEach((item, itemIndex) => {
873
- const itemName = item.uri.split('/').pop() || '';
872
+ const itemName = path.parse(item.uri.split('/').pop()).name || '';
874
873
  server.registerResource(
875
874
  // Name of the resource (used to call it)
876
- `msxdocs_${sectionName}_${itemName}`,
875
+ `msxdocs_${sectionName}_${item.title.replace(/[^a-z0-9]+/gi, '_').toLowerCase()}`,
877
876
  // Resource URI template
878
877
  item.uri,
879
878
  // Metadata for the resource
880
879
  {
881
- title: item.title || `MSX Documentation '${sectionName}' - ${itemName}`,
882
- description: item.description || `Documentation for MSX resource '${sectionName}' - ${itemName}`,
880
+ title: item.title || `MSX Documentation '${sectionName}': ${itemName}`,
881
+ description: item.description || `Documentation for MSX resource '${sectionName}': ${itemName}`,
883
882
  mimeType: item.mimeType || 'text/markdown',
884
883
  },
885
884
  // Handler for the resource (function to be executed when the resource is called)
@@ -889,14 +888,11 @@ The parameter scrbasename is the name of the filename (without path) to save the
889
888
  if (uri.href.startsWith('http://') || uri.href.startsWith('https://')) {
890
889
  // Fetch the resource from the URL
891
890
  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';
891
+ [resourceContent, mimeType] = await fetchCleanWebpage(uri.href);
896
892
  }
897
893
  catch (error) {
898
894
  // Throw exception (MCP protocol requirement)
899
- throw new Error(`Error fetching resource from ${uri.href}: ${error instanceof Error ? error.message : String(error)}`);
895
+ throw error;
900
896
  }
901
897
  }
902
898
  else {
@@ -922,16 +918,16 @@ The parameter scrbasename is the name of the filename (without path) to save the
922
918
  });
923
919
  }
924
920
  ;
925
- server.resource("msxdocs_msxorg_wiki", new ResourceTemplate("msxdocs://msxorg_wiki/{section}", {
921
+ server.resource("msxdocs_basic_wiki", new ResourceTemplate("msxdocs://basic_wiki/{instruction}", {
926
922
  list: undefined,
927
923
  complete: {
928
- section: (value) => [
924
+ instruction: (value) => [
929
925
  "ABS()", "AND", "ASC()", "ATN()", "AUTO", "BASE()", "BEEP", "BIN$()", "BLOAD", "BSAVE", "CALL", "CALL ADJUST", "CALL PAUSE", "CALL PCMPLAY", "CALL PCMREC",
930
926
  "CDBL()", "CHR$()", "CINT()", "CIRCLE", "CLEAR", "CLOAD", "CLOAD?", "CLOSE", "CLS", "COLOR", "COLOR=", "COLOR", "COLOR", "CONT", "COPY", "COPY", "COS()", "CSAVE",
931
927
  "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",
928
+ "ERROR", "EXP()", "FIX()", "FN", "FOR...NEXT", "FRE()", "GET DATE", "GET TIME", "GOSUB", "GOTO", "HEX$()", "IF...GOTO...ELSE", "IF...THEN...ELSE", "IMP",
929
+ "INKEY$", "INP()", "INPUT", "INPUT$()", "INSTR()", "INT()", "INTERVAL", "KEY", "KEY()", "LEFT$()", "LEN()", "LET", "LINE", "LINE INPUT", "LIST", "LLIST",
930
+ "LOAD", "LOCATE", "LOG()", "LPOS()", "LPRINT", "MAXFILES", "MERGE", "MID$()", "MOD", "MOTOR", "NEW", "NOT", "OCT$()", "ON...GOSUB", "ON...GOTO",
935
931
  "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
932
  "PEEK()", "PLAY", "PLAY()", "POINT", "POKE", "POS()", "PRESET", "PRINT", "PSET", "PUT KANJI", "PUT SPRITE", "READ", "REM", "RENUM", "RESTORE", "RESUME",
937
933
  "RETURN", "RIGHT$()", "RND()", "RUN", "SAVE", "SCREEN", "SET ADJUST", "SET BEEP", "SET DATE", "SET PAGE", "SET PASSWORD", "SET PROMPT", "SET SCREEN",
@@ -941,25 +937,20 @@ The parameter scrbasename is the name of the filename (without path) to save the
941
937
  ],
942
938
  },
943
939
  }), {
944
- title: "BASIC MSX Documentation",
945
- description: "Documentation about all the standard MSX-BASIC instructions.",
940
+ title: "MSX BASIC Instructions Documentation",
941
+ description: "Documentation about all the standard MSX BASIC instructions from www.msx.org",
946
942
  mimeType: "text/html",
947
943
  }, 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}`;
944
+ const instruction = variables.instruction.replace(/ /g, '_').replace(/\?/g, '%3F').replace(/=/g, '%3D');
945
+ const url = `https://www.msx.org/wiki/${instruction}`;
950
946
  let resourceContent;
951
947
  let mimeType;
952
948
  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, '');
949
+ [resourceContent, mimeType] = await fetchCleanWebpage(url);
959
950
  }
960
951
  catch (error) {
961
952
  // Throw exception (MCP protocol requirement)
962
- throw new Error(`Error fetching resource "${uri}" from "${url}": ${error instanceof Error ? error.message : String(error)}`);
953
+ throw error;
963
954
  }
964
955
  return {
965
956
  contents: [{
@@ -970,41 +961,6 @@ The parameter scrbasename is the name of the filename (without path) to save the
970
961
  };
971
962
  });
972
963
  }
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];
994
- }
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
- }
1007
- }
1008
964
  // ============================================================================
1009
965
  // Cleanup handlers for graceful shutdown of MCP server
1010
966
  // Ensure openMSX emulator is closed when MCP server stops
package/dist/utils.js CHANGED
@@ -5,6 +5,8 @@
5
5
  * @license GPL2
6
6
  */
7
7
  import fs from 'fs/promises';
8
+ import path from 'path';
9
+ import mime from 'mime-types';
8
10
  /**
9
11
  * Extract description from XML file
10
12
  * @param filePath - Full path to the XML file
@@ -21,6 +23,73 @@ export async function extractDescriptionFromXML(filePath) {
21
23
  return 'Error reading description';
22
24
  }
23
25
  }
26
+ /**
27
+ * Add file extension to a file path if it exists in the same directory and determine its MIME type
28
+ * @param filePath - Full path to the file excluding extension
29
+ * @returns Promise<string[]> - An array containing the MIME type and the full file path with extension
30
+ */
31
+ export async function addFileExtension(filePath) {
32
+ // Get directory and filename
33
+ const directory = path.dirname(filePath);
34
+ const filename = path.basename(filePath);
35
+ try {
36
+ // Get all files in directory that start with our filename
37
+ const files = await fs.readdir(directory);
38
+ const matchingFiles = files.filter(file => file.startsWith(filename));
39
+ if (matchingFiles.length > 0) {
40
+ const fileFound = path.join(directory, matchingFiles[0]);
41
+ return [
42
+ mime.lookup(fileFound) || 'text/plain',
43
+ fileFound
44
+ ];
45
+ }
46
+ }
47
+ catch (error) {
48
+ console.error('Error reading directory:', error);
49
+ }
50
+ // Return original if no matches found
51
+ return ['text/plain', filePath];
52
+ }
53
+ /**
54
+ * List all folders in the resources directory
55
+ * @param resourcesDir - Path to the resources directory
56
+ * @returns Promise<string[]> - List of folder names in the resources directory
57
+ */
58
+ export async function listResourcesDirectory(resourcesDir) {
59
+ try {
60
+ const directories = await fs.readdir(resourcesDir, { withFileTypes: true });
61
+ const folderNames = directories
62
+ .filter(dirent => dirent.isDirectory())
63
+ .map(dirent => dirent.name);
64
+ return folderNames;
65
+ }
66
+ catch (error) {
67
+ console.error("Error reading resources directory:", error);
68
+ return [];
69
+ }
70
+ }
71
+ /**
72
+ * Fetch a webpage and return its content (without scripts, styles, or links) and its MIME type
73
+ * @param url - URL of the webpage to fetch
74
+ * @returns Promise<[string, string]> - A tuple containing the webpage content and its MIME type
75
+ */
76
+ export async function fetchCleanWebpage(url) {
77
+ let resourceContent;
78
+ let mimeType = 'text/plain';
79
+ try {
80
+ resourceContent = await fetch(url).then(response => {
81
+ mimeType = response.headers.get('content-type') || 'text/plain';
82
+ return response.text();
83
+ }) || 'Error downloading content';
84
+ // Remove script, style, and link tags from the content
85
+ resourceContent = resourceContent.replace(/<script\b[^>]*>[\s\S]*?<\/script>|<style\b[^>]*>[\s\S]*?<\/style>|<link\b[^>]*\/?>/gi, '');
86
+ }
87
+ catch (error) {
88
+ // Throw exception (MCP protocol requirement)
89
+ throw new Error(`Error fetching resource from ${url}: ${error instanceof Error ? error.message : String(error)}`);
90
+ }
91
+ return [resourceContent, mimeType];
92
+ }
24
93
  /**
25
94
  * Decode HTML entities in a string to plain text
26
95
  * @param text - String containing HTML entities
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nataliapc/mcp-openmsx",
3
- "version": "1.1.13",
3
+ "version": "1.1.14",
4
4
  "description": "Model context protocol server for openMSX automation and control",
5
5
  "main": "dist/server.js",
6
6
  "type": "module",