Skip to main content
Glama

OpenSCAD MCP Server

by jhacksman
main.py.new12.5 kB
import os import logging import uuid import json from typing import Dict, Any, List, Optional, Tuple from fastapi import FastAPI, Request, Response, HTTPException from fastapi.responses import JSONResponse, FileResponse from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates import uvicorn from mcp import MCPServer, MCPTool, MCPToolCall, MCPToolCallResult # Import configuration from src.config import * # Import components from src.nlp.parameter_extractor import ParameterExtractor from src.models.code_generator import CodeGenerator from src.openscad_wrapper.wrapper import OpenSCADWrapper from src.utils.cad_exporter import CADExporter from src.visualization.headless_renderer import HeadlessRenderer from src.printer_discovery.printer_discovery import PrinterDiscovery, PrinterInterface # Import AI components from src.ai.venice_api import VeniceImageGenerator from src.ai.gemini_api import GeminiImageGenerator from src.models.cuda_mvs import CUDAMultiViewStereo from src.workflow.image_approval import ImageApprovalTool from src.workflow.multi_view_to_model_pipeline import MultiViewToModelPipeline # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) # Create FastAPI app app = FastAPI(title="OpenSCAD MCP Server") # Add CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Create directories os.makedirs("scad", exist_ok=True) os.makedirs("output", exist_ok=True) os.makedirs("output/models", exist_ok=True) os.makedirs("output/preview", exist_ok=True) os.makedirs("output/images", exist_ok=True) os.makedirs("output/multi_view", exist_ok=True) os.makedirs("output/approved_images", exist_ok=True) os.makedirs("templates", exist_ok=True) os.makedirs("static", exist_ok=True) # Initialize components parameter_extractor = ParameterExtractor() code_generator = CodeGenerator("scad", "output") openscad_wrapper = OpenSCADWrapper("scad", "output") cad_exporter = CADExporter() headless_renderer = HeadlessRenderer() printer_discovery = PrinterDiscovery() # Initialize AI components venice_generator = None gemini_generator = None cuda_mvs = None approval_tool = None multi_view_pipeline = None # Initialize Venice.ai generator if API key is available if VENICE_API_KEY: venice_generator = VeniceImageGenerator(VENICE_API_KEY, IMAGES_DIR) logger.info("Venice.ai image generator initialized") else: logger.info("Venice.ai API key not provided, skipping initialization") # Initialize Google Gemini generator if API key is available if GEMINI_API_KEY: gemini_generator = GeminiImageGenerator(GEMINI_API_KEY, IMAGES_DIR) logger.info("Google Gemini image generator initialized") else: logger.warning("Google Gemini API key not provided, multi-view workflow will not be available") # Initialize CUDA MVS if path is available if os.path.exists(CUDA_MVS_PATH): cuda_mvs = CUDAMultiViewStereo(CUDA_MVS_PATH, MODELS_DIR) logger.info("CUDA Multi-View Stereo initialized") else: logger.warning(f"CUDA MVS not found at {CUDA_MVS_PATH}, 3D reconstruction will not be available") # Initialize image approval tool approval_tool = ImageApprovalTool(APPROVED_IMAGES_DIR) logger.info("Image approval tool initialized") # Initialize multi-view pipeline if required components are available if gemini_generator and cuda_mvs: multi_view_pipeline = MultiViewToModelPipeline( gemini_generator=gemini_generator, venice_generator=venice_generator, cuda_mvs=cuda_mvs, openscad_wrapper=openscad_wrapper, approval_tool=approval_tool, output_dir=OUTPUT_DIR ) logger.info("Multi-view to model pipeline initialized") else: logger.warning("Multi-view to model pipeline not initialized due to missing components") # Store models in memory models = {} printers = {} # Create MCP server mcp_server = MCPServer() # Mount static files app.mount("/static", StaticFiles(directory="static"), name="static") # Create Jinja2 templates templates = Jinja2Templates(directory="templates") # Create model preview template with open("templates/preview.html", "w") as f: f.write(""" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>OpenSCAD Model Preview</title> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; } .container { max-width: 1200px; margin: 0 auto; padding: 20px; } h1 { color: #333; margin-bottom: 20px; } .preview-container { display: flex; flex-wrap: wrap; gap: 20px; margin-top: 20px; } .preview-image { border: 1px solid #ddd; border-radius: 5px; padding: 10px; background-color: white; } .preview-image img { max-width: 100%; height: auto; } .preview-image h3 { margin-top: 10px; margin-bottom: 5px; color: #555; } .parameters { margin-top: 20px; background-color: #f9f9f9; padding: 15px; border-radius: 5px; border: 1px solid #eee; } .parameters h2 { margin-top: 0; color: #333; } .parameters table { width: 100%; border-collapse: collapse; } .parameters table th, .parameters table td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; } .parameters table th { background-color: #f2f2f2; } .actions { margin-top: 20px; display: flex; gap: 10px; } .actions button { padding: 10px 15px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; } .actions button:hover { background-color: #45a049; } </style> </head> <body> <div class="container"> <h1>OpenSCAD Model Preview: {{ model_id }}</h1> <div class="parameters"> <h2>Parameters</h2> <table> <tr> <th>Parameter</th> <th>Value</th> </tr> {% for key, value in parameters.items() %} <tr> <td>{{ key }}</td> <td>{{ value }}</td> </tr> {% endfor %} </table> </div> <div class="preview-container"> {% for view, image_path in previews.items() %} <div class="preview-image"> <h3>{{ view|title }} View</h3> <img src="{{ image_path }}" alt="{{ view }} view"> </div> {% endfor %} </div> <div class="actions"> <button onclick="window.location.href='/download/{{ model_id }}'">Download Model</button> </div> </div> </body> </html> """) # Create multi-view approval template with open("templates/multi_view_approval.html", "w") as f: f.write(""" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Multi-View Image Approval</title> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; } .container { max-width: 1200px; margin: 0 auto; padding: 20px; } h1 { color: #333; margin-bottom: 20px; } .images-container { display: flex; flex-wrap: wrap; gap: 20px; margin-top: 20px; } .image-card { border: 1px solid #ddd; border-radius: 5px; padding: 10px; background-color: white; width: calc(50% - 20px); box-sizing: border-box; } .image-card img { max-width: 100%; height: auto; } .image-card h3 { margin-top: 10px; margin-bottom: 5px; color: #555; } .image-card .metadata { font-size: 0.9em; color: #777; margin-bottom: 10px; } .image-card .actions { display: flex; gap: 10px; margin-top: 10px; } .image-card .actions button { padding: 8px 12px; border: none; border-radius: 4px; cursor: pointer; } .approve-btn { background-color: #4CAF50; color: white; } .approve-btn:hover { background-color: #45a049; } .deny-btn { background-color: #f44336; color: white; } .deny-btn:hover { background-color: #d32f2f; } .submit-container { margin-top: 20px; text-align: center; } .submit-container button { padding: 10px 20px; background-color: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1.1em; } .submit-container button:hover { background-color: #0b7dda; } </style> <script> function toggleApproval(id, approved) { const card = document.getElementById(`card-${id}`); const approveBtn = document.getElementById(`approve-${id}`); const denyBtn = document.getElementById(`deny-${id}`); if (approved) { card.style.borderColor = '#4CAF50'; approveBtn.disabled = true; denyBtn.disabled = false; } else { card.style.borderColor = '#f44336'; approveBtn.disabled = false; denyBtn.disabled = true; } document.getElementById(`approval-${id}`).value = approved; } function submitApprovals() { document.getElementById('approval-form').submit(); } </script> </head> <body> <div class="container"> <h1>Multi-View Image Approval</h1> <p>Please approve or deny each image for 3D reconstruction.</p> <form id="approval-form" action="/approve_images" method="post"> <input type="hidden" name="pipeline_id" value="{{ pipeline_id }}"> <div class="images-container"> {% for image in images %} <div id="card-{{ image.approval_id }}" class="image-card"> <h3>{{ image.metadata.view_direction }}</h3> <div class="metadata"> <p>{{ image.metadata.prompt }}</p> </div> <img src="{{ image.image_url }}" alt="{{ image.metadata.view_direction }}"> <input type="hidden" id="approval-{{ image.approval_id }}" name="approvals[{{ image.approval_id }}]" value="false"> <div class="actions"> <button id="approve-{{ image.approval_id }}" type="button" class="approve-btn" onclick="toggleApproval('{{ image.approval_id }}', true)">Approve</button> <button id="deny-{{ image.approval_id }}" type="button" class="deny-btn" onclick="toggleApproval('{{ image.approval_id }}', false)">Deny</button> </div> </div> {% endfor %} </div> <div class="submit-container"> <button type="button" onclick="submitApprovals()">Submit Approvals</button> </div> </form> </div> </body> </html> """)

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/jhacksman/OpenSCAD-MCP-Server'

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