Skip to main content
Glama

OpenSCAD MCP Server

by rahulgarg123
index.ts5.7 kB
#!/usr/bin/env node 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 { execFile } from 'child_process'; import { promises as fs } from 'fs'; import { tmpdir } from 'os'; import { join } from 'path'; import { promisify } from 'util'; const execFileAsync = promisify(execFile); const OPENSCAD_BINARY = process.env.OPENSCAD_BINARY || '/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD'; interface OpenSCADResult { stdout: string; stderr: string; success: boolean; error?: string; } class OpenSCADMCPServer { private server: Server; constructor() { this.server = new Server( { name: 'openscad-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.setupHandlers(); } private setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'render_openscad', description: 'Render OpenSCAD code to PNG using OpenSCAD in headless mode', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'The OpenSCAD code to render', }, output_path: { type: 'string', description: 'Path where the rendered PNG image should be saved', }, camera: { type: 'string', description: 'Camera parameters: translate_x,y,z,rot_x,y,z,dist or eye_x,y,z,center_x,y,z', }, }, required: ['code', 'output_path'], }, }, ], }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === 'render_openscad') { return await this.handleRenderOpenSCAD(request.params.arguments || {}); } throw new Error(`Unknown tool: ${request.params.name}`); }); } private async handleRenderOpenSCAD(args: any): Promise<{ content: Array<{ type: string; text?: string; data?: string; mimeType?: string }> }> { try { const { code, output_path, camera } = args; if (!code || typeof code !== 'string') { throw new Error('OpenSCAD code is required and must be a string'); } if (!output_path || typeof output_path !== 'string') { throw new Error('Output path is required and must be a string'); } const result = await this.renderOpenSCAD(code, output_path, camera); // Return text response with file path information let responseText = `OpenSCAD rendering ${result.success ? 'completed successfully' : 'failed'}\n\nOutput:\n${result.stdout}${result.stderr ? '\n\nErrors/Warnings:\n' + result.stderr : ''}`; if (result.success) { responseText += `\n\nRendered image saved to: ${output_path}`; } if (result.error) { responseText += `\n\nError: ${result.error}`; } return { content: [{ type: 'text', text: responseText }] }; } catch (error) { return { content: [ { type: 'text', text: `Error rendering OpenSCAD: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } } private async renderOpenSCAD(code: string, outputPath: string, camera?: string): Promise<OpenSCADResult> { const tempDir = tmpdir(); const timestamp = Date.now(); const inputFile = join(tempDir, `openscad_input_${timestamp}.scad`); try { // Write OpenSCAD code to temporary file await fs.writeFile(inputFile, code, 'utf8'); // Build command arguments: input file, --render flag, output file const args = [inputFile, '--render', '--autocenter', '--viewall','--imgsize=640,480','--backend=manifold', '-o', outputPath]; // Add camera parameters if provided if (camera) { args.push('--camera', camera); } // Execute OpenSCAD with 10 minute timeout const { stdout, stderr } = await execFileAsync(OPENSCAD_BINARY, args, { timeout: 600000, // 10 minutes }); // Check if output file was created successfully const outputExists = await fs.access(outputPath).then(() => true).catch(() => false); // Clean up temporary input file only await this.cleanupFiles([inputFile]); return { stdout: stdout || '', stderr: stderr || '', success: outputExists, }; } catch (error) { // Clean up temporary input file even on error await this.cleanupFiles([inputFile]); return { stdout: '', stderr: '', success: false, error: error instanceof Error ? error.message : String(error), }; } } private async cleanupFiles(files: string[]): Promise<void> { for (const file of files) { try { await fs.unlink(file); } catch (error) { // Ignore cleanup errors - files may not exist } } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('OpenSCAD MCP Server running on stdio'); } } // Start the server const server = new OpenSCADMCPServer(); server.run().catch((error) => { console.error('Failed to start server:', 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/rahulgarg123/openscad-mcp'

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