Skip to main content
Glama

GameMaker Documentation MCP Server

by Petah
gamemaker-docs-server.js13.2 kB
#!/usr/bin/env node import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import DocsIndexer from './lib/docs-indexer.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); class GameMakerDocsServer { constructor(docsIndexer) { this.docsIndexer = docsIndexer; this.server = new Server( { name: 'gamemaker-docs', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.setupHandlers(); } setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'lookup_gamemaker_function', description: 'Look up GameMaker Language (GML) function documentation', inputSchema: { type: 'object', properties: { function_name: { type: 'string', description: 'The name of the GML function to look up (e.g., "draw_sprite", "instance_create_layer")', }, include_examples: { type: 'boolean', description: 'Whether to include code examples in the response (default: true)', default: true, }, }, required: ['function_name'], }, }, { name: 'search_gamemaker_docs', description: 'Search GameMaker documentation for topics, concepts, or keywords', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search term or phrase (e.g., "collision detection", "audio", "networking")', }, max_results: { type: 'number', description: 'Maximum number of results to return (default: 5)', default: 5, }, }, required: ['query'], }, }, { name: 'list_functions_by_category', description: 'List all functions in a specific category or module', inputSchema: { type: 'object', properties: { category: { type: 'string', description: 'Category name (e.g., "Drawing", "Audio", "Instance", "String")', }, }, required: ['category'], }, }, { name: 'get_markdown_file', description: 'Fetch the content of a specific markdown file from the documentation', inputSchema: { type: 'object', properties: { file_path: { type: 'string', description: 'Path to the markdown file (e.g., "GameMaker_Language/GML_Reference/Drawing/Sprites_And_Tiles/draw_sprite.md")', }, }, required: ['file_path'], }, }, { name: 'init_gamemaker_agent', description: 'Get GameMaker Language (GML) coding guide and MCP tool overview. Call this first to understand GML syntax, best practices, and available documentation tools.', inputSchema: { type: 'object', properties: {}, additionalProperties: false, }, }, ], }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'lookup_gamemaker_function': return await this.lookupFunction(args.function_name, args.include_examples); case 'search_gamemaker_docs': return await this.searchDocs(args.query, args.max_results || 5); case 'list_functions_by_category': return await this.listFunctionsByCategory(args.category); case 'get_markdown_file': return await this.getMarkdownFile(args.file_path); case 'init_gamemaker_agent': return await this.initGameMakerAgent(); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: `Error: ${error.message}`, }, ], }; } }); } async lookupFunction(functionName, includeExamples = true) { const result = await this.docsIndexer.lookupFunction(functionName); if (!result.found) { return { content: [ { type: 'text', text: `Function "${functionName}" not found in documentation.\n\n` + (result.suggestions && result.suggestions.length > 0 ? `Did you mean one of these?\n${result.suggestions.map(f => `- ${f}`).join('\n')}` : 'No similar functions found. Try using the search tool instead.'), }, ], }; } let documentation = result.documentation || `Found reference to "${functionName}" but couldn't extract detailed documentation.`; // Remove code examples if requested if (!includeExamples) { documentation = documentation.replace(/```[\s\S]*?```/g, '[Code example removed]'); } // Add reference information if (result.allReferences && result.allReferences.length > 1) { const otherRefs = result.allReferences.filter(ref => ref !== result.function); if (otherRefs.length > 0) { const otherPaths = [...new Set(otherRefs.map(ref => this.docsIndexer.convertToRootRelative(ref.file)))]; documentation += `\n\n**Also found in:**\n${otherPaths.map(path => `- ${path}`).join('\n')}`; } } return { content: [ { type: 'text', text: documentation, }, ], }; } async searchDocs(query, maxResults) { const results = await this.docsIndexer.searchDocs(query, maxResults); if (results.length === 0) { return { content: [ { type: 'text', text: `No results found for "${query}". Try different keywords or check spelling.`, }, ], }; } const formattedResults = results.map((result, index) => `## Result ${index + 1}: ${result.file}\n**Category:** ${result.category}\n**Relevance Score:** ${result.score}\n\n${result.section}\n\n---\n` ).join('\n'); return { content: [ { type: 'text', text: `Found ${results.length} result(s) for "${query}":\n\n${formattedResults}`, }, ], }; } async listFunctionsByCategory(category) { const allFunctions = this.docsIndexer.getAllFunctions(); const categoryLower = category.toLowerCase(); const functions = allFunctions.filter(func => func.category && func.category.toLowerCase().includes(categoryLower) ); if (functions.length === 0) { const categories = this.docsIndexer.getCategories(); const similarCategories = categories.filter(cat => cat.toLowerCase().includes(categoryLower) ).slice(0, 5); return { content: [ { type: 'text', text: `No functions found for category "${category}". ` + (similarCategories.length > 0 ? `\n\nSimilar categories:\n${similarCategories.map(c => `- ${c}`).join('\n')}` : 'Try searching for specific terms or use the search tool instead.'), }, ], }; } const functionList = functions.map(f => `- ${f.name} (${f.type})`).join('\n'); return { content: [ { type: 'text', text: `Functions in category "${category}" (${functions.length} total):\n\n${functionList}`, }, ], }; } async getMarkdownFile(filePath) { const result = await this.docsIndexer.getMarkdownFile(filePath); if (!result.found) { return { content: [ { type: 'text', text: `File "${filePath}" not found.\n\n` + (result.suggestions && result.suggestions.length > 0 ? `Similar files:\n${result.suggestions.map(f => `- ${f}`).join('\n')}` : 'No similar files found.') + `\n\nError: ${result.error}`, }, ], }; } return { content: [ { type: 'text', text: `**File:** ${result.relativePath}\n\n${result.content}`, }, ], }; } async initGameMakerAgent() { const { promises: fs } = await import('node:fs'); try { const llmsFilePath = join(__dirname, '..', 'init.txt'); const content = await fs.readFile(llmsFilePath, 'utf-8'); return { content: [ { type: 'text', text: content, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error loading GameMaker agent guide: ${error.message}`, }, ], }; } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('GameMaker Documentation MCP Server running on stdio'); } } // Main execution async function main() { const docsPath = process.argv[2] || join(__dirname, '..', 'md'); if (!docsPath) { console.error('Usage: node gamemaker-docs-server.js [path-to-markdown-docs]'); console.error(' node gamemaker-docs-server.js (uses ../md)'); process.exit(1); } const { promises: fs } = await import('node:fs'); try { await fs.access(docsPath); } catch (error) { console.error(`Error: Documentation path "${docsPath}" does not exist or is not accessible.`); process.exit(1); } const docsIndexer = new DocsIndexer(docsPath); await docsIndexer.buildIndex(); const server = new GameMakerDocsServer(docsIndexer); await server.run(); } // ES module equivalent of require.main === module if (import.meta.url === `file://${process.argv[1]}`) { main().catch(error => { console.error('Server failed:', error); process.exit(1); }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Petah/gamemaker-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server