@nataliapc/mcp-openmsx 1.1.14 → 1.1.15
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 +16 -2
- package/dist/server.js +143 -63
- package/dist/utils.js +44 -5
- package/package.json +4 -2
- package/resources/audio/MGSC111.txt +669 -0
- package/resources/audio/opl4tech.txt +688 -0
- package/resources/audio/toc.json +10 -3
- package/resources/processors/toc.json +4 -4
- package/resources/processors/z80_detailed_instruction_set.md +80 -68
- package/resources/sdcc/sdccman.lyx +81574 -0
- package/resources/sdcc/toc.json +77 -0
- /package/resources/processors/{Z80_R800_instruction_set.md → z80_r800_instruction_set.md} +0 -0
package/README.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
*"Orchestrating a binary opera where AI conducts, MCP interprets, and openMSX acts as the 8-bit diva."*
|
|
4
4
|
|
|
5
|
+
[](https://github.com/nataliapc)
|
|
6
|
+
[](https://github.com/nataliapc/mcp-openmsx/blob/main/LICENSE)
|
|
7
|
+
[
|
|
8
|
+
](https://github.com/nataliapc/mcp-openmsx/stargazers/)
|
|
9
|
+
[](https://www.npmjs.com/package/@nataliapc/mcp-openmsx?activeTab=versions)
|
|
10
|
+
[]()
|
|
11
|
+
|
|
12
|
+
|
|
5
13
|
A [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) server for automating [openMSX emulator](https://github.com/openMSX/openMSX) instances.
|
|
6
14
|
|
|
7
15
|
This server provides comprehensive tools for MSX software development, testing, and automation through standardized MCP protocols.
|
|
@@ -64,6 +72,7 @@ The MCP server translates high-level commands from your Copilot AI into `TCL` co
|
|
|
64
72
|
- `emu_savestates`: Save and restore machine states: _`load`, `save`, `list`_.
|
|
65
73
|
- `screen_shot`: Capture emulator screen: _`as_image`, `to_file`_.
|
|
66
74
|
- `screen_dump`: Export screen data as BASIC BSAVE instruction.
|
|
75
|
+
- `msxdocs_resource_get`: Retrieve MCP resources for MCP clients that don't support MCP resources.
|
|
67
76
|
|
|
68
77
|
## 📚 Available MCP Resources
|
|
69
78
|
|
|
@@ -85,10 +94,11 @@ There are more than 60 resources available, some included directly in the MCP an
|
|
|
85
94
|
- `MSX-UNAPI`
|
|
86
95
|
- `MSX BASIC`
|
|
87
96
|
|
|
88
|
-
And
|
|
97
|
+
And books and manuals:
|
|
89
98
|
|
|
90
99
|
- `MSX2 Technical Handbook`
|
|
91
100
|
- `The MSX Red Book`
|
|
101
|
+
- `SDCC Compiler`
|
|
92
102
|
|
|
93
103
|
### Resources from:
|
|
94
104
|
|
|
@@ -98,8 +108,9 @@ And two books:
|
|
|
98
108
|
- [MSX2 Technical Handbook](https://github.com/Konamiman/MSX2-Technical-Handbook)
|
|
99
109
|
- [Konamiman MSX-UNAPI-specification](https://github.com/Konamiman/MSX-UNAPI-specification)
|
|
100
110
|
- [BiFi MSX Net](http://bifi.msxnet.org/msxnet/)
|
|
101
|
-
- [
|
|
111
|
+
- [MRC Wiki](https://www.msx.org/wiki/Main_Page)
|
|
102
112
|
- [MSX Banzai!](http://msxbanzai.tni.nl/)
|
|
113
|
+
- [SDCC](https://sdcc.sourceforge.net/)
|
|
103
114
|
|
|
104
115
|
Thanks to the authors of these resources, who have made them available under various licenses. This MCP server includes some of these resources to enhance the development experience.
|
|
105
116
|
|
|
@@ -116,6 +127,9 @@ You can use this MCP server in this basic way with the [precompiled NPM package]
|
|
|
116
127
|
|
|
117
128
|
### STDIO mode (recommended)
|
|
118
129
|
|
|
130
|
+
[](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22stdio%22%2C%22command%22%3A%20%22npx%22%2C%22args%22%3A%20%5B%22%40nataliapc%2Fmcp-openmsx%22%5D%2C%20%22env%22%3A%20%7B%22OPENMSX_SHARE_DIR%22%3A%20%22%2Fusr%2Fshare%2Fopenmsx%22%7D%7D)
|
|
131
|
+
[](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22stdio%22%2C%22command%22%3A%20%22npx%22%2C%22args%22%3A%20%5B%22%40nataliapc%2Fmcp-openmsx%22%5D%2C%20%22env%22%3A%20%7B%22OPENMSX_SHARE_DIR%22%3A%20%22%2Fusr%2Fshare%2Fopenmsx%22%7D%7D&quality=insiders)
|
|
132
|
+
|
|
119
133
|
```json
|
|
120
134
|
{
|
|
121
135
|
"servers": {
|
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.
|
|
9
|
+
* @version 1.1.15
|
|
10
10
|
* @author Natalia Pujol Cremades (@nataliapc)
|
|
11
11
|
* @license GPL2
|
|
12
12
|
*/
|
|
@@ -22,7 +22,7 @@ import path from "path";
|
|
|
22
22
|
import { openMSXInstance } from "./openmsx.js";
|
|
23
23
|
import { fetchCleanWebpage, addFileExtension, listResourcesDirectory, encodeTypeText, isErrorResponse, getResponseContent } from "./utils.js";
|
|
24
24
|
// Version info for CLI
|
|
25
|
-
const PACKAGE_VERSION = "1.1.
|
|
25
|
+
export const PACKAGE_VERSION = "1.1.15";
|
|
26
26
|
const resourcesDir = path.join(path.dirname(new URL(import.meta.url).pathname), "../resources");
|
|
27
27
|
// Defaults for openMSX paths
|
|
28
28
|
var OPENMSX_EXECUTABLE = 'openmsx';
|
|
@@ -864,84 +864,95 @@ The parameter scrbasename is the name of the filename (without path) to save the
|
|
|
864
864
|
// ============================================================================
|
|
865
865
|
// MSX Documentation resources
|
|
866
866
|
const resdocs = (await listResourcesDirectory(resourcesDir)).sort();
|
|
867
|
+
const regResources = [];
|
|
867
868
|
for (let index = 0; index < resdocs.length; index++) {
|
|
868
869
|
const sectionName = resdocs[index];
|
|
869
870
|
const tocFile = path.join(resourcesDir, `${sectionName}/toc.json`);
|
|
870
871
|
const tocContent = JSON.parse(await fs.readFile(tocFile, 'utf8'));
|
|
871
872
|
tocContent.toc.forEach((item, itemIndex) => {
|
|
872
873
|
const itemName = path.parse(item.uri.split('/').pop()).name || '';
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
874
|
+
let resource = {
|
|
875
|
+
uri: item.uri,
|
|
876
|
+
filename: '',
|
|
877
|
+
resource: server.registerResource(
|
|
878
|
+
// Name of the resource (used to call it)
|
|
879
|
+
`msxdocs_${sectionName}_${item.title.replace(/[^a-z0-9]+/gi, '_').toLowerCase()}`,
|
|
880
|
+
// Resource URI template
|
|
881
|
+
item.uri,
|
|
882
|
+
// Metadata for the resource
|
|
883
|
+
{
|
|
884
|
+
title: item.title || `MSX Documentation '${sectionName}': ${itemName}`,
|
|
885
|
+
description: item.description || `Documentation for MSX resource '${sectionName}': ${itemName}`,
|
|
886
|
+
mimeType: item.mimeType || 'text/markdown',
|
|
887
|
+
},
|
|
888
|
+
// Handler for the resource (function to be executed when the resource is called)
|
|
889
|
+
async (uri) => {
|
|
890
|
+
let resourceContent;
|
|
891
|
+
let mimeType;
|
|
892
|
+
if (uri.href.startsWith('http://') || uri.href.startsWith('https://')) {
|
|
893
|
+
// Fetch the resource from the URL
|
|
894
|
+
try {
|
|
895
|
+
[resourceContent, mimeType] = await fetchCleanWebpage(uri.href);
|
|
896
|
+
}
|
|
897
|
+
catch (error) {
|
|
898
|
+
// Throw exception (MCP protocol requirement)
|
|
899
|
+
throw error;
|
|
900
|
+
}
|
|
892
901
|
}
|
|
893
|
-
|
|
894
|
-
//
|
|
895
|
-
|
|
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
|
+
}
|
|
896
913
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
throw new Error(`Error reading resource ${sectionName}/${item.uri}: ${error instanceof Error ? error.message : String(error)}`);
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
return {
|
|
911
|
-
contents: [{
|
|
912
|
-
uri: uri.href,
|
|
913
|
-
text: resourceContent,
|
|
914
|
-
mimeType: mimeType || 'text/plain',
|
|
915
|
-
}],
|
|
916
|
-
};
|
|
917
|
-
});
|
|
914
|
+
return {
|
|
915
|
+
contents: [{
|
|
916
|
+
uri: uri.href,
|
|
917
|
+
text: resourceContent,
|
|
918
|
+
mimeType: mimeType || 'text/plain',
|
|
919
|
+
}],
|
|
920
|
+
};
|
|
921
|
+
})
|
|
922
|
+
};
|
|
923
|
+
regResources.push(resource);
|
|
918
924
|
});
|
|
919
925
|
}
|
|
920
926
|
;
|
|
927
|
+
// Source: https://www.msx.org/wiki/Category:MSX-BASIC_Instructions
|
|
928
|
+
const basicInstructions = [
|
|
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 SPRITE()", "COLOR SPRITE$()", "CONT", "COPY",
|
|
931
|
+
"COPY SCREEN", "COS()", "CSAVE", "CSNG()", "CSRLIN", "DATA", "DEFDBL", "DEF FN", "DEFINT", "DEFSNG", "DEFSTR", "DEF USR", "DELETE", "DIM", "DRAW", "ELSE",
|
|
932
|
+
"END", "EOF()", "EQV", "ERASE", "ERL", "ERR", "ERROR", "EXP()", "FIX()", "FN", "FOR...NEXT", "FRE()", "GET DATE", "GET TIME", "GOSUB", "GOTO", "HEX$()",
|
|
933
|
+
"IF...GOTO...ELSE", "IF...THEN...ELSE", "IMP", "INKEY$", "INP()", "INPUT", "INPUT$()", "INSTR()", "INT()", "INTERVAL", "KEY", "KEY()", "LEFT$()", "LEN()",
|
|
934
|
+
"LET", "LINE", "LINE INPUT", "LIST", "LLIST", "LOAD", "LOCATE", "LOG()", "LPOS()", "LPRINT", "MAXFILES", "MERGE", "MID$()", "MOD", "MOTOR", "NEW", "NOT",
|
|
935
|
+
"OCT$()", "ON...GOSUB", "ON...GOTO", "ON ERROR GOTO", "ON INTERVAL GOSUB", "ON KEY GOSUB", "ON SPRITE GOSUB", "ON STOP GOSUB", "ON STRIG GOSUB", "OPEN",
|
|
936
|
+
"OR", "OUT", "PAD()", "PAINT", "PDL()", "PEEK()", "PLAY", "PLAY()", "POINT", "POKE", "POS()", "PRESET", "PRINT", "PSET", "PUT KANJI", "PUT SPRITE", "READ",
|
|
937
|
+
"REM", "RENUM", "RESTORE", "RESUME", "RETURN", "RIGHT$()", "RND()", "RUN", "SAVE", "SCREEN", "SET ADJUST", "SET BEEP", "SET DATE", "SET PAGE", "SET PASSWORD",
|
|
938
|
+
"SET PROMPT", "SET SCREEN", "SET SCROLL", "SET TIME", "SET TITLE", "SET VIDEO", "SGN()", "SIN()", "SOUND", "SPACE$()", "SPC()", "SPRITE", "SPRITE$()",
|
|
939
|
+
"SQR()", "STICK()", "STOP", "STR$()", "STRIG()", "STRING$()", "SWAP", "TAB()", "TAN()", "TIME", "TROFF", "TRON", "USR()", "VAL()", "VARPTR()", "VDP()",
|
|
940
|
+
"VPEEK()", "VPOKE", "WAIT", "WIDTH", "XOR"
|
|
941
|
+
];
|
|
921
942
|
server.resource("msxdocs_basic_wiki", new ResourceTemplate("msxdocs://basic_wiki/{instruction}", {
|
|
922
943
|
list: undefined,
|
|
923
944
|
complete: {
|
|
924
|
-
instruction: (value) =>
|
|
925
|
-
"ABS()", "AND", "ASC()", "ATN()", "AUTO", "BASE()", "BEEP", "BIN$()", "BLOAD", "BSAVE", "CALL", "CALL ADJUST", "CALL PAUSE", "CALL PCMPLAY", "CALL PCMREC",
|
|
926
|
-
"CDBL()", "CHR$()", "CINT()", "CIRCLE", "CLEAR", "CLOAD", "CLOAD?", "CLOSE", "CLS", "COLOR", "COLOR=", "COLOR", "COLOR", "CONT", "COPY", "COPY", "COS()", "CSAVE",
|
|
927
|
-
"CSNG()", "CSRLIN", "DATA", "DEFDBL", "DEF FN", "DEFINT", "DEFSNG", "DEFSTR", "DEF USR", "DELETE", "DIM", "DRAW", "ELSE", "END", "EOF()", "EQV", "ERASE", "ERL", "ERR",
|
|
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",
|
|
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()",
|
|
932
|
-
"PEEK()", "PLAY", "PLAY()", "POINT", "POKE", "POS()", "PRESET", "PRINT", "PSET", "PUT KANJI", "PUT SPRITE", "READ", "REM", "RENUM", "RESTORE", "RESUME",
|
|
933
|
-
"RETURN", "RIGHT$()", "RND()", "RUN", "SAVE", "SCREEN", "SET ADJUST", "SET BEEP", "SET DATE", "SET PAGE", "SET PASSWORD", "SET PROMPT", "SET SCREEN",
|
|
934
|
-
"SET SCROLL", "SET TIME", "SET TITLE", "SET VIDEO", "SGN()", "SIN()", "SOUND", "SPACE$()", "SPC()", "SPRITE", "SPRITE$()", "SQR()", "STICK()", "STOP",
|
|
935
|
-
"STR$()", "STRIG()", "STRING$()", "SWAP", "TAB()", "TAN()", "TIME", "TROFF", "TRON", "USR()", "VAL()", "VARPTR()", "VDP()", "VPEEK()", "VPOKE", "WAIT",
|
|
936
|
-
"WIDTH", "XOR"
|
|
937
|
-
],
|
|
945
|
+
instruction: (value) => basicInstructions,
|
|
938
946
|
},
|
|
939
947
|
}), {
|
|
940
948
|
title: "MSX BASIC Instructions Documentation",
|
|
941
949
|
description: "Documentation about all the standard MSX BASIC instructions from www.msx.org",
|
|
942
950
|
mimeType: "text/html",
|
|
943
951
|
}, async (uri, variables) => {
|
|
944
|
-
const instruction = variables.instruction
|
|
952
|
+
const instruction = variables.instruction
|
|
953
|
+
.replace(/ /g, '_')
|
|
954
|
+
.replace(/\?/g, '%3F')
|
|
955
|
+
.replace(/=/g, '%3D');
|
|
945
956
|
const url = `https://www.msx.org/wiki/${instruction}`;
|
|
946
957
|
let resourceContent;
|
|
947
958
|
let mimeType;
|
|
@@ -960,6 +971,73 @@ The parameter scrbasename is the name of the filename (without path) to save the
|
|
|
960
971
|
}],
|
|
961
972
|
};
|
|
962
973
|
});
|
|
974
|
+
// Register the tool to get a specific MSX documentation resource
|
|
975
|
+
server.registerTool(
|
|
976
|
+
// Name of the tool (used to call it)
|
|
977
|
+
"msxdocs_resource_get", {
|
|
978
|
+
title: "Tool to get a resource",
|
|
979
|
+
// Description of the tool (what it does)
|
|
980
|
+
description: "Get a specific available MSX documentation resource from this MCP server resources.",
|
|
981
|
+
// Schema for the tool (input validation)
|
|
982
|
+
inputSchema: {
|
|
983
|
+
resourceName: z.enum(regResources.map(res => res.resource.name)).describe("Name of the resource to obtain, e.g. 'msxdocs_programming_interrupts'"),
|
|
984
|
+
},
|
|
985
|
+
},
|
|
986
|
+
// Handler for the tool (function to be executed when the tool is called)
|
|
987
|
+
async ({ resourceName }, extra) => {
|
|
988
|
+
const index = regResources.findIndex((res) => res.resource.name === resourceName);
|
|
989
|
+
const uriString = index !== -1 ? regResources[index].uri : undefined;
|
|
990
|
+
const resource = index !== -1 ? regResources[index].resource : undefined;
|
|
991
|
+
if (!resource || !uriString) {
|
|
992
|
+
return getResponseContent([
|
|
993
|
+
`Error: Resource '${resourceName}' not found.`
|
|
994
|
+
]);
|
|
995
|
+
}
|
|
996
|
+
let documentationText = '';
|
|
997
|
+
try {
|
|
998
|
+
// If the resource is found, return its content
|
|
999
|
+
let resourceContent = await resource.readCallback(new URL(uriString), extra);
|
|
1000
|
+
if (!resourceContent.contents?.length) {
|
|
1001
|
+
return getResponseContent([
|
|
1002
|
+
`Error: Resource '${resourceName}' has no content available.`
|
|
1003
|
+
]);
|
|
1004
|
+
}
|
|
1005
|
+
// Return the first content item (assuming it's the main content)
|
|
1006
|
+
const content = resourceContent.contents[0];
|
|
1007
|
+
if ('text' in content) {
|
|
1008
|
+
documentationText = content.text;
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
return getResponseContent([
|
|
1012
|
+
`Error: Resource '${resourceName}' has no content available.`
|
|
1013
|
+
]);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
catch (error) {
|
|
1017
|
+
return getResponseContent([
|
|
1018
|
+
`Error: error reading resource '${resourceName}': ${error instanceof Error ? error.message : String(error)}`
|
|
1019
|
+
]);
|
|
1020
|
+
}
|
|
1021
|
+
return {
|
|
1022
|
+
content: [{
|
|
1023
|
+
type: "text",
|
|
1024
|
+
text: `Content from resource: '${resourceName}'`,
|
|
1025
|
+
}, {
|
|
1026
|
+
type: "text",
|
|
1027
|
+
text: documentationText || 'No content available for this resource.',
|
|
1028
|
+
mimeType: resource.metadata?.mimeType || 'text/plain',
|
|
1029
|
+
} /*, {
|
|
1030
|
+
type: "resource",
|
|
1031
|
+
resource: {
|
|
1032
|
+
uri: resource.metadata?.uri || resourceName,
|
|
1033
|
+
title: resource.metadata?.title || `Resource: ${resourceName}`,
|
|
1034
|
+
mimeType: resource.metadata?.mimeType || 'text/plain',
|
|
1035
|
+
text: documentationText || 'No content available for this resource.',
|
|
1036
|
+
}
|
|
1037
|
+
}*/
|
|
1038
|
+
],
|
|
1039
|
+
};
|
|
1040
|
+
});
|
|
963
1041
|
}
|
|
964
1042
|
// ============================================================================
|
|
965
1043
|
// Cleanup handlers for graceful shutdown of MCP server
|
|
@@ -1071,8 +1149,8 @@ async function startHttpServer() {
|
|
|
1071
1149
|
}
|
|
1072
1150
|
await transport.handleRequest(req, res, req.body);
|
|
1073
1151
|
});
|
|
1074
|
-
//
|
|
1075
|
-
|
|
1152
|
+
// Reusable handle GET / DELETE requests
|
|
1153
|
+
const handleSessionRequest = async (req, res) => {
|
|
1076
1154
|
const sessionId = req.headers['mcp-session-id'];
|
|
1077
1155
|
if (!sessionId || !transports[sessionId]) {
|
|
1078
1156
|
res.status(400).send('Invalid or missing session ID');
|
|
@@ -1080,7 +1158,9 @@ async function startHttpServer() {
|
|
|
1080
1158
|
}
|
|
1081
1159
|
const transport = transports[sessionId];
|
|
1082
1160
|
await transport.handleRequest(req, res);
|
|
1083
|
-
}
|
|
1161
|
+
};
|
|
1162
|
+
app.get('/mcp', handleSessionRequest);
|
|
1163
|
+
app.delete('/mcp', handleSessionRequest);
|
|
1084
1164
|
const port = process.env.MCP_HTTP_PORT || 3000;
|
|
1085
1165
|
app.listen(port, () => {
|
|
1086
1166
|
console.log(`MCP Server listening on port ${port}`);
|
package/dist/utils.js
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
import fs from 'fs/promises';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import mime from 'mime-types';
|
|
10
|
+
import { gunzipSync } from 'zlib';
|
|
11
|
+
import { PACKAGE_VERSION } from "./server.js";
|
|
12
|
+
import sanitizeHtml from 'sanitize-html';
|
|
10
13
|
/**
|
|
11
14
|
* Extract description from XML file
|
|
12
15
|
* @param filePath - Full path to the XML file
|
|
@@ -77,12 +80,48 @@ export async function fetchCleanWebpage(url) {
|
|
|
77
80
|
let resourceContent;
|
|
78
81
|
let mimeType = 'text/plain';
|
|
79
82
|
try {
|
|
80
|
-
|
|
83
|
+
const response = await fetch(url, {
|
|
84
|
+
headers: {
|
|
85
|
+
// Accept compressed content gzip/deflate
|
|
86
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
87
|
+
// User agent to avoid blocking by some servers
|
|
88
|
+
'User-Agent': `Mozilla/5.0 (compatible; MCP-openMSX/${PACKAGE_VERSION})`
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
if (response.status === 200) {
|
|
81
92
|
mimeType = response.headers.get('content-type') || 'text/plain';
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
const contentType = response.headers.get('content-type') || '';
|
|
94
|
+
if (contentType.includes('x-gzip') || contentType.includes('gzip')) {
|
|
95
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
96
|
+
const uint8Array = new Uint8Array(arrayBuffer);
|
|
97
|
+
if (uint8Array[0] === 0x1f && uint8Array[1] === 0x8b) {
|
|
98
|
+
try {
|
|
99
|
+
const decompressed = gunzipSync(Buffer.from(uint8Array));
|
|
100
|
+
resourceContent = decompressed.toString('utf8');
|
|
101
|
+
mimeType = 'text/html';
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
resourceContent = new TextDecoder().decode(uint8Array);
|
|
105
|
+
mimeType = 'text/html';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
resourceContent = new TextDecoder().decode(uint8Array);
|
|
110
|
+
mimeType = 'text/html';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Normal case, use response.text() which automatically handles decompression
|
|
115
|
+
resourceContent = await response.text();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
120
|
+
}
|
|
121
|
+
// Remove script, style, form, and link tags from the content if it's HTML
|
|
122
|
+
if (mimeType.startsWith('text/html')) {
|
|
123
|
+
resourceContent = sanitizeHtml(resourceContent);
|
|
124
|
+
}
|
|
86
125
|
}
|
|
87
126
|
catch (error) {
|
|
88
127
|
// Throw exception (MCP protocol requirement)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nataliapc/mcp-openmsx",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.15",
|
|
4
4
|
"description": "Model context protocol server for openMSX automation and control",
|
|
5
5
|
"main": "dist/server.js",
|
|
6
6
|
"type": "module",
|
|
@@ -38,9 +38,10 @@
|
|
|
38
38
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.15.1",
|
|
42
42
|
"@types/express": "^5.0.2",
|
|
43
43
|
"express": "^5.1.0",
|
|
44
|
+
"sanitize-html": "^2.17.0",
|
|
44
45
|
"tsx": "^4.7.1",
|
|
45
46
|
"zod": "^3.24.4"
|
|
46
47
|
},
|
|
@@ -48,6 +49,7 @@
|
|
|
48
49
|
"@modelcontextprotocol/inspector": "^0.15.0",
|
|
49
50
|
"@types/mime-types": "^3.0.1",
|
|
50
51
|
"@types/node": "^22.15.27",
|
|
52
|
+
"@types/sanitize-html": "^2.16.0",
|
|
51
53
|
"shx": "^0.4.0",
|
|
52
54
|
"typescript": "^5.8.3"
|
|
53
55
|
},
|