@nataliapc/mcp-openmsx 1.2.3 → 1.2.4

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.
@@ -0,0 +1,69 @@
1
+ import { z } from "zod";
2
+ import { basicInstructions } from "./server_resources.js";
3
+ // ============================================================================
4
+ // Prompts available in the MCP server
5
+ // https://modelcontextprotocol.io/docs/concepts/prompts
6
+ export async function registerPrompts(server) {
7
+ server.registerPrompt(
8
+ // Name of the prompt (used to call it)
9
+ "basic", {
10
+ title: "MSX BASIC Instruction Manual",
11
+ // Description of the prompt (what it does)
12
+ description: "MSX BASIC instructions manual",
13
+ // Schema for the prompt (input validation)
14
+ argsSchema: {
15
+ instruction: z.string()
16
+ .min(1, "Instruction name cannot be empty")
17
+ .max(50, "Instruction name too long")
18
+ .describe("Name of the MSX BASIC instruction to get information about (e.g. 'PRINT', 'FOR', 'POKE'). Case insensitive.")
19
+ .transform(val => val.trim().toUpperCase()),
20
+ }
21
+ },
22
+ // Handler for the prompt (function to be executed when the prompt is called)
23
+ ({ instruction }) => {
24
+ // Normalize instruction name for better matching
25
+ const normalizedInstruction = instruction.replace(/[()]/g, '').trim();
26
+ let prompt = '';
27
+ // Check if instruction is in the known list
28
+ const isKnownInstruction = basicInstructions.some(basic => basic.replace(/[()]/g, '').toUpperCase() === normalizedInstruction);
29
+ if (isKnownInstruction) {
30
+ prompt = `Provide comprehensive information about the MSX BASIC instruction '${instruction}'.
31
+ STRICT REQUIREMENTS:
32
+ - NEVER invent or assume any information not explicitly found in the MCP resources
33
+ - ONLY use information from 'msxdocs:' resources and tools #msxdocs_resource_get and #vector_db_query
34
+ - If specific details (ranges, values, behaviors) are not in the resources, state "Not specified in available documentation"
35
+ - Do NOT use general knowledge about MSX systems - stick ONLY to what the resources contain
36
+ - When citing parameters or ranges, quote them EXACTLY as written in the source documentation
37
+ MANDATORY SECTIONS:
38
+ - Include these sections if information is available in resources: 'Description', 'Syntax', 'Parameters', 'Notes', 'Usage examples', 'Related instructions', and 'Availability'.
39
+ - You MUST respond in the user language, unless otherwise specified.
40
+ SOURCES:
41
+ - Always cite the specific MCP resources used at the end in section 'Sources' with exact resource uri.
42
+ - For each resource, add a markdown link to the resource if external url is available.`;
43
+ }
44
+ else {
45
+ const suggestions = basicInstructions
46
+ .filter(basic => basic.toUpperCase().includes(normalizedInstruction) ||
47
+ normalizedInstruction.includes(basic.replace(/[()]/g, '').toUpperCase()))
48
+ .slice(0, 5);
49
+ // If the instruction is not known, suggest alternatives
50
+ prompt = `Explain that '${instruction}' does not appear to be a standard MSX BASIC instruction.
51
+ STRICT REQUIREMENTS:
52
+ - ONLY use information found in MCP resources via #vector_db_query tool
53
+ - Do NOT assume or invent any information about MSX BASIC instructions
54
+ - If suggesting alternatives, provide ONLY what is found in the search results
55
+ ${suggestions.length > 0 ? `Suggest one of these: ${suggestions.join(', ')} instructions as a correct search string. List them with a brief description using ONLY information found in the resources 'msxdocs://basic_wiki/{instruction}'` : 'Use #vector_db_query to search for similar instructions.'}
56
+ IMPORTANT: Review the available MSX BASIC instructions using the available MCP resources. You may still try to find information using the available resources, as it could be documented under a different name or as part of another instruction. Base your response EXCLUSIVELY on what the resources and tools return.
57
+ You MUST respond in the user language, unless otherwise specified.`;
58
+ }
59
+ return {
60
+ messages: [{
61
+ role: "assistant",
62
+ content: {
63
+ type: "text",
64
+ text: prompt,
65
+ },
66
+ }]
67
+ };
68
+ });
69
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * @package @nataliapc/mcp-openmsx
3
+ * @author Natalia Pujol Cremades (@nataliapc)
4
+ * @license GPL2
5
+ */
6
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import fs from "fs/promises";
8
+ import path from "path";
9
+ import { fetchCleanWebpage, addFileExtension, listResourcesDirectory } from "./utils.js";
10
+ // Source: https://www.msx.org/wiki/Category:MSX-BASIC_Instructions
11
+ export const basicInstructions = [
12
+ "ABS()", "AND", "ASC()", "ATN()", "AUTO", "BASE()", "BEEP", "BIN$()", "BLOAD", "BSAVE", "CALL", "CALL ADJUST",
13
+ "CALL IMPOSE", "CALL OPTIONS", "CALL PAUSE", "CALL PCMPLAY", "CALL PCMREC", "CDBL()", "CHR$()", "CINT()", "CIRCLE",
14
+ "CLEAR", "CLOAD", "CLOAD?", "CLOSE", "CLS", "COLOR", "COLOR=", "COLOR SPRITE()", "COLOR SPRITE$()", "CONT", "COPY",
15
+ "COPY SCREEN", "COS()", "CSAVE", "CSNG()", "CSRLIN", "DATA", "DEFDBL", "DEF FN", "DEFINT", "DEFSNG", "DEFSTR",
16
+ "DEF USR", "DELETE", "DIM", "DRAW", "ELSE", "END", "EOF()", "EQV", "ERASE", "ERL", "ERR", "ERROR", "EXP()", "FIELD",
17
+ "FIX()", "FN", "FOR...NEXT", "FRE()", "GET DATE", "GET TIME", "GOSUB", "GOTO", "HEX$()", "IF...GOTO...ELSE",
18
+ "IF...THEN...ELSE", "IMP", "INKEY$", "INP()", "INPUT", "INPUT$()", "INSTR()", "INT()", "INTERVAL", "KEY", "KEY()",
19
+ "LEFT$()", "LEN()", "LET", "LINE", "LINE INPUT", "LIST", "LLIST", "LOAD", "LOCATE", "LOG()", "LPOS()", "LPRINT",
20
+ "MAXFILES", "MERGE", "MID$()", "MOD", "MOTOR", "NEW", "NOT", "OCT$()", "ON...GOSUB", "ON...GOTO", "ON ERROR GOTO",
21
+ "ON INTERVAL GOSUB", "ON KEY GOSUB", "ON SPRITE GOSUB", "ON STOP GOSUB", "ON STRIG GOSUB", "OPEN", "OR", "OUT",
22
+ "PAD()", "PAINT", "PDL()", "PEEK()", "PLAY", "PLAY()", "POINT", "POKE", "POS()", "PRESET", "PRINT", "PSET",
23
+ "PUT KANJI", "PUT SPRITE", "READ", "REM", "RENUM", "RESTORE", "RESUME", "RETURN", "RIGHT$()", "RND()", "RUN",
24
+ "SAVE", "SCREEN", "SET ADJUST", "SET BEEP", "SET DATE", "SET PAGE", "SET PASSWORD", "SET PROMPT", "SET SCREEN",
25
+ "SET SCROLL", "SET TIME", "SET TITLE", "SET VIDEO", "SGN()", "SIN()", "SOUND", "SPACE$()", "SPC()", "SPRITE",
26
+ "SPRITE$()", "SQR()", "STICK()", "STOP", "STR$()", "STRIG()", "STRING$()", "SWAP", "TAB()", "TAN()", "TIME",
27
+ "TROFF", "TRON", "USR()", "VAL()", "VARPTR()", "VDP()", "VPEEK()", "VPOKE", "WAIT", "WIDTH", "XOR"
28
+ ];
29
+ const regResources = [];
30
+ export function getRegisteredResourcesList() {
31
+ return regResources;
32
+ }
33
+ // ============================================================================
34
+ // Resources available in the MCP server
35
+ // https://modelcontextprotocol.io/docs/concepts/resources
36
+ export async function registerResources(server, resourcesDir) {
37
+ // ============================================================================
38
+ // MSX Documentation resources
39
+ const resdocs = (await listResourcesDirectory(resourcesDir)).sort();
40
+ for (let index = 0; index < resdocs.length; index++) {
41
+ // Read the toc.json file if exists, otherwise skip this section
42
+ const sectionName = resdocs[index];
43
+ const tocFile = path.join(resourcesDir, `${sectionName}/toc.json`);
44
+ let tocContent = { toc: [] };
45
+ try {
46
+ tocContent = JSON.parse(await fs.readFile(tocFile, 'utf8'));
47
+ }
48
+ catch (error) {
49
+ // The toc.json file does not exist or is invalid, skip this section
50
+ continue;
51
+ }
52
+ // Register each item in the toc.json as a resource
53
+ tocContent.toc.forEach((item, itemIndex) => {
54
+ const itemName = path.parse(item.uri.split('/').pop()).base || '';
55
+ let resource = {
56
+ uri: item.uri,
57
+ filename: '',
58
+ resource: server.registerResource(
59
+ // Name of the resource (used to call it)
60
+ `msxdocs_${sectionName}_${item.title.replace(/[^a-z0-9]+/gi, '_').toLowerCase()}`,
61
+ // Resource URI template
62
+ item.uri,
63
+ // Metadata for the resource
64
+ {
65
+ title: item.title || `MSX Documentation '${sectionName}': ${itemName}`,
66
+ description: item.description || `Documentation for MSX resource '${sectionName}': ${itemName}`,
67
+ mimeType: item.mimeType || 'text/markdown',
68
+ },
69
+ // Handler for the resource (function to be executed when the resource is called)
70
+ async (uri) => {
71
+ let resourceContent;
72
+ let mimeType;
73
+ if (uri.href.startsWith('http://') || uri.href.startsWith('https://')) {
74
+ // Fetch the resource from the URL
75
+ try {
76
+ [resourceContent, mimeType] = await fetchCleanWebpage(uri.href);
77
+ }
78
+ catch (error) {
79
+ // Throw exception (MCP protocol requirement)
80
+ throw error;
81
+ }
82
+ }
83
+ else {
84
+ // Read the resource from the local MCP server resources directory
85
+ try {
86
+ let resourceFile;
87
+ [mimeType, resourceFile] = await addFileExtension(path.join(resourcesDir, `${sectionName}/${itemName}`));
88
+ resourceContent = await fs.readFile(resourceFile, 'utf8');
89
+ }
90
+ catch (error) {
91
+ // Throw exception (MCP protocol requirement)
92
+ throw new Error(`Error reading resource ${sectionName}/${item.uri}: ${error instanceof Error ? error.message : String(error)}`);
93
+ }
94
+ }
95
+ return {
96
+ contents: [{
97
+ uri: uri.href,
98
+ text: resourceContent,
99
+ mimeType: mimeType || 'text/plain',
100
+ }],
101
+ };
102
+ })
103
+ };
104
+ regResources.push(resource);
105
+ });
106
+ }
107
+ ;
108
+ // Source: https://www.msx.org/wiki/Category:MSX-BASIC_Instructions
109
+ server.resource("msxdocs_basic_wiki", new ResourceTemplate("msxdocs://basic_wiki/{instruction}", {
110
+ list: undefined,
111
+ complete: {
112
+ instruction: (value) => basicInstructions,
113
+ },
114
+ }), {
115
+ title: "MSX BASIC Instructions Documentation",
116
+ description: "Documentation about all the standard MSX BASIC instructions from www.msx.org/wiki/Category:MSX-BASIC_Instructions",
117
+ mimeType: "text/html",
118
+ }, async (uri, variables) => {
119
+ let instruction = variables.instruction;
120
+ let resourceContent;
121
+ let mimeType;
122
+ // urldecode the instruction to avoid issues with special characters
123
+ instruction = decodeURIComponent(instruction).replaceAll(' ', '_');
124
+ try {
125
+ let resourceFile;
126
+ [mimeType, resourceFile] = await addFileExtension(path.join(resourcesDir, 'programming', 'basic_wiki', instruction));
127
+ resourceContent = await fs.readFile(resourceFile, 'utf8');
128
+ }
129
+ catch (error) {
130
+ // Throw exception (MCP protocol requirement)
131
+ throw new Error(`Error reading resource programming/basic_wiki/${instruction}: ${error instanceof Error ? error.message : String(error)}`);
132
+ }
133
+ return {
134
+ contents: [{
135
+ uri: uri.href,
136
+ text: resourceContent,
137
+ mimeType: mimeType || 'text/plain',
138
+ }],
139
+ };
140
+ });
141
+ }