- Overview
- Quick Start
- Authentication & Connection
- Error Handling
- API Categories
- Best Practices
- Example Workflows
The FreeCAD MCP API provides programmatic access to FreeCAD through the Model Control Protocol (MCP). It enables automation of CAD operations, macro execution, and model generation through a client-server architecture.
Architecture:
- Server: Runs inside FreeCAD (
freecad_mcp_server.py), listens onlocalhost:9876 - Client: Python client (
freecad_mcp_client.py) usingstdioor TCP communication - Protocol: JSON-based command/response structure
Supported Communication Modes:
- stdio: For MCP-compatible tools (Claude Desktop, Claude Code, Cursor)
- TCP: For direct socket communication on port 9876
# macOS
cd ~/Library/Application\ Support/FreeCAD/Mod/FreeCAD-MCP
uv venv .venv --python 3.12
source .venv/bin/activate
uv pip install mcp-server httpx
# Windows
cd D:\FreeCAD\Mod\FreeCAD-MCP
uv venv .venv --python 3.12
.venv\Scripts\activate
uv pip install mcp-server httpx
# Linux
cd ~/.local/share/FreeCAD/Mod/FreeCAD-MCP
uv venv .venv --python 3.12
source .venv/bin/activate
uv pip install mcp-server httpx- Open FreeCAD
- Switch to "FreeCAD MCP" workbench
- Click "FreeCAD_MCP_Show" to open control panel
- Click "Start Server"
from freecad_mcp_client import create_macro
# Create a new macro
result = create_macro("my_first_macro", "basic")
print(result)
# Output: {"result": "success", "message": "Macro file created successfully: ..."}No authentication required for local connections. The server binds to localhost:9876 by default.
Security Notes:
- Server only accepts local connections
- Code validation prevents dangerous operations
- Allowed modules:
FreeCAD,Part,Draft,Sketcher,PartDesign,math,numpy,Mesh,Arch,TechDraw,Spreadsheet,Import - Blocked operations:
__import__,eval,exec,open,compile, file system access
For Claude Desktop/Code (add to ~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"freecad": {
"type": "stdio",
"command": "/path/to/.venv/bin/python",
"args": ["/path/to/freecad_mcp_client.py"],
"timeout": 60
}
}
}For Direct Python Usage:
import asyncio
from freecad_mcp_client import send_command_to_freecad
async def call_api():
command = {"type": "list_documents", "params": {}}
result = await send_command_to_freecad(command)
return result
result = asyncio.run(call_api())All API responses follow this structure:
Success Response:
{
"result": "success",
"message": "Operation completed",
"data": { ... }
}Error Response:
{
"result": "error",
"message": "Error description",
"traceback": "Full Python traceback"
}| Error Type | Cause | Solution |
|---|---|---|
| Connection Failed | Server not running | Start FreeCAD MCP server |
| Module Not Allowed | Importing restricted module | Use allowed modules only |
| Invalid Macro Name | Invalid characters | Use only letters, numbers, underscores, hyphens |
| File Not Found | Macro path incorrect | Check macro path, use absolute paths |
| No Active Document | Document not open | Create or open a document first |
| Syntax Error | Invalid Python code | Validate code syntax |
from freecad_mcp_client import run_macro
try:
result = run_macro("my_macro.FCMacro")
if result["result"] == "error":
print(f"Error: {result['message']}")
print(f"Traceback: {result.get('traceback', 'N/A')}")
else:
print(f"Success: {result['message']}")
except Exception as e:
print(f"Client error: {str(e)}")Creates a new FreeCAD macro file.
Function Signature:
def create_macro(macro_name: str, template_type: str = "default") -> Dict[str, Any]Parameters:
macro_name(str, required): Macro file name (only letters, numbers, underscores, hyphens)template_type(str, optional): Template type"default": Basic comment header"basic": Imports FreeCAD and GUI"part": Includes Part module"sketch": Includes Sketcher module
Returns:
{
"result": "success",
"message": "Macro file created successfully: /path/to/macro.FCMacro"
}Example:
from freecad_mcp_client import create_macro
# Create a part design macro
result = create_macro("parametric_gear", "part")
print(result)Common Errors:
- Invalid macro name characters: Returns error with allowed character list
- File already exists: Overwrites existing file
Updates the content of an existing macro file.
Function Signature:
def update_macro(macro_name: str, code: str) -> Dict[str, Any]Parameters:
macro_name(str, required): Macro file name (without .FCMacro extension)code(str, required): Python code content
Returns:
{
"result": "success",
"message": "Macro file updated successfully: /path/to/macro.FCMacro"
}Code Normalization: The function automatically adds missing imports:
import FreeCAD as Appimport FreeCADGui as Guiimport Partimport math
Example:
from freecad_mcp_client import update_macro
code = """
# Create a cylinder
radius = 10
height = 20
cylinder = Part.makeCylinder(radius, height)
Part.show(cylinder)
"""
result = update_macro("my_cylinder", code)Common Errors:
- File not found: Returns error with expected path
- Invalid code syntax: Returns syntax error details
Executes a FreeCAD macro file.
Function Signature:
def run_macro(macro_path: str, params: Dict[str, Any] = None) -> Dict[str, Any]Parameters:
macro_path(str, required): Macro file path (absolute or relative)- Absolute:
/full/path/to/macro.FCMacro - Relative:
macro_nameormacro_name.FCMacro
- Absolute:
params(dict, optional): Parameters to inject into macrodoc_name: Document name to use/create- Custom parameters accessible in macro code
Path Resolution:
- If absolute path: Use directly
- If relative path:
- Search in FreeCAD macro directory
- Auto-append
.FCMacroextension if missing - Search in current working directory
- Search in project directory
Returns:
{
"result": "success",
"message": "Macro executed successfully in document MyDoc",
"document": "MyDoc"
}Parameter Injection: Parameters are injected as Python variables at the top of the macro:
# User calls: run_macro("gear.FCMacro", {"radius": 15, "teeth": 20})
# Macro receives:
radius = 15
teeth = 20
# Original macro code follows...Example:
from freecad_mcp_client import run_macro
# Simple execution
result = run_macro("gear.FCMacro")
# With parameters
result = run_macro("parametric_gear.FCMacro", {
"radius": 25,
"teeth": 30,
"height": 10,
"doc_name": "GearAssembly"
})
print(f"Created in document: {result['document']}")Automatic Post-Execution:
- Document recompute
- View adjusted to axonometric
- Zoom to fit all objects
Common Errors:
- Macro not found: Lists searched paths
- Syntax error in macro: Returns traceback
- Runtime error: Returns execution traceback
- No GUI available: Returns error (requires FreeCAD GUI mode)
Validates macro code syntax and security.
Function Signature:
def validate_macro_code(macro_name: str = None, code: str = None) -> Dict[str, Any]Parameters:
macro_name(str, optional): Macro file name to validatecode(str, optional): Code string to validate- Note: Provide either
macro_nameorcode, not both
Returns:
{
"result": "success",
"message": "Macro code validation successful"
}Validation Checks:
- Syntax validation using Python AST parser
- Module import restrictions
- Dangerous built-in detection (
eval,exec,open, etc.) - Attribute access restrictions (no
__dict__,__globals__, etc.)
Example:
from freecad_mcp_client import validate_macro_code
# Validate file
result = validate_macro_code(macro_name="my_macro")
# Validate code string
code = """
import Part
cylinder = Part.makeCylinder(10, 20)
Part.show(cylinder)
"""
result = validate_macro_code(code=code)
if result["result"] == "error":
print(f"Validation failed: {result['message']}")Common Errors:
- Syntax error: Returns line number and description
- Forbidden import: Lists allowed modules
- Dangerous operation: Specifies blocked function/attribute
Lists all open FreeCAD documents.
Function Signature:
def list_documents() -> Dict[str, Any]Parameters: None
Returns:
{
"result": "success",
"documents": [
{
"name": "Unnamed",
"label": "Unnamed",
"object_count": 5,
"is_active": true
},
{
"name": "GearAssembly",
"label": "Gear Assembly",
"object_count": 12,
"is_active": false
}
]
}Example:
from freecad_mcp_client import list_documents
result = list_documents()
for doc in result["documents"]:
print(f"{doc['name']}: {doc['object_count']} objects")Status: Implemented in client, requires server handler implementation
Gets details about the currently active document.
Function Signature:
def get_active_document() -> Dict[str, Any]Parameters: None
Returns:
{
"result": "success",
"document": {
"name": "GearAssembly",
"label": "Gear Assembly",
"object_count": 12,
"objects": [
{"name": "Body", "type": "PartDesign::Body", "label": "Body"},
{"name": "Sketch", "type": "Sketcher::SketchObject", "label": "Sketch"}
]
}
}Example:
from freecad_mcp_client import get_active_document
result = get_active_document()
if result["result"] == "success":
doc = result["document"]
print(f"Active: {doc['name']} with {doc['object_count']} objects")Common Errors:
- No active document: Returns error message
Status: Implemented in client, requires server handler implementation
Creates a new FreeCAD document.
Function Signature:
def create_document(name: str) -> Dict[str, Any]Parameters:
name(str, required): Document name (only letters, numbers, underscores)
Returns:
{
"result": "success",
"message": "Document created successfully",
"document_name": "MyProject"
}Example:
from freecad_mcp_client import create_document
result = create_document("GearAssembly")Common Errors:
- Invalid name characters: Returns error
- Document already exists: May overwrite or return error
Status: Implemented in client, requires server handler implementation
Saves the active document to a file.
Function Signature:
def save_document(filename: str) -> Dict[str, Any]Parameters:
filename(str, required): Absolute file path (must end with.FCStd)
Returns:
{
"result": "success",
"message": "Document saved successfully",
"filepath": "/path/to/document.FCStd"
}Example:
from freecad_mcp_client import save_document
result = save_document("/Users/username/Documents/gear_assembly.FCStd")Common Errors:
- No active document: Returns error
- Invalid path: Returns error
- Permission denied: Returns error
Status: Implemented in client, requires server handler implementation
Closes a document by name.
Function Signature:
def close_document(name: str) -> Dict[str, Any]Parameters:
name(str, required): Document name to close
Returns:
{
"result": "success",
"message": "Document closed successfully"
}Example:
from freecad_mcp_client import close_document
result = close_document("GearAssembly")Common Errors:
- Document not found: Returns error
- Unsaved changes: May prompt or auto-discard
Status: Implemented in client, requires server handler implementation
Lists all objects in a document.
Function Signature:
def list_objects(document_name: str = None) -> Dict[str, Any]Parameters:
document_name(str, optional): Document name (uses active document if not specified)
Returns:
{
"result": "success",
"objects": [
{
"name": "Body",
"type": "PartDesign::Body",
"label": "Body",
"visibility": true
},
{
"name": "Sketch",
"type": "Sketcher::SketchObject",
"label": "Sketch001",
"visibility": true
},
{
"name": "Pad",
"type": "PartDesign::Pad",
"label": "Pad",
"visibility": true
}
]
}Example:
from freecad_mcp_client import list_objects
# List objects in active document
result = list_objects()
# List objects in specific document
result = list_objects("GearAssembly")
for obj in result["objects"]:
print(f"{obj['label']} ({obj['type']})")Status: Implemented in client, requires server handler implementation
Gets detailed properties of an object.
Function Signature:
def get_object_properties(object_name: str, document_name: str = None) -> Dict[str, Any]Parameters:
object_name(str, required): Name of the objectdocument_name(str, optional): Document name (uses active document if not specified)
Returns:
{
"result": "success",
"properties": {
"name": "Pad",
"type": "PartDesign::Pad",
"label": "Pad",
"placement": {
"base": [0.0, 0.0, 0.0],
"rotation": [0.0, 0.0, 0.0, 1.0]
},
"shape_info": {
"volume": 1256.64,
"surface_area": 942.48,
"bounding_box": {
"x_min": -10, "x_max": 10,
"y_min": -10, "y_max": 10,
"z_min": 0, "z_max": 20
}
},
"visibility": true
}
}Example:
from freecad_mcp_client import get_object_properties
result = get_object_properties("Pad", "GearAssembly")
props = result["properties"]
print(f"Volume: {props['shape_info']['volume']}")Status: Implemented in client, requires server handler implementation
Deletes an object from a document.
Function Signature:
def delete_object(object_name: str, document_name: str = None) -> Dict[str, Any]Parameters:
object_name(str, required): Name of the object to deletedocument_name(str, optional): Document name (uses active document if not specified)
Returns:
{
"result": "success",
"message": "Object deleted successfully"
}Example:
from freecad_mcp_client import delete_object
result = delete_object("Sketch001")Common Errors:
- Object not found: Returns error
- Object has dependencies: May return error or cascade delete
Status: Implemented in client, requires server handler implementation
Note: Part design operations are currently implemented through macro execution. Below are recommended macro patterns for common operations.
Creates a new PartDesign Body.
Macro Pattern:
# Create via macro
code = """
import FreeCAD as App
import PartDesign
doc = App.ActiveDocument
body = doc.addObject('PartDesign::Body', 'Body')
doc.recompute()
"""
from freecad_mcp_client import update_macro, run_macro
update_macro("create_body", code)
run_macro("create_body.FCMacro")Creates a sketch on a body or face.
Macro Pattern:
code = """
import FreeCAD as App
import Sketcher
doc = App.ActiveDocument
body = doc.getObject('Body')
# Create sketch on XY plane
sketch = doc.addObject('Sketcher::SketchObject', 'Sketch')
sketch.Support = (body, [''])
sketch.MapMode = 'FlatFace'
doc.recompute()
"""
update_macro("create_sketch", code)
run_macro("create_sketch.FCMacro")Adds geometric elements to a sketch.
Circle:
code = """
import FreeCAD as App
from FreeCAD import Vector
doc = App.ActiveDocument
sketch = doc.getObject('Sketch')
# Add circle at origin with radius 10
sketch.addGeometry(Part.Circle(Vector(0, 0, 0), Vector(0, 0, 1), 10))
doc.recompute()
"""Rectangle:
code = """
# Add rectangle
sketch.addGeometry(Part.LineSegment(Vector(-10, -10, 0), Vector(10, -10, 0)))
sketch.addGeometry(Part.LineSegment(Vector(10, -10, 0), Vector(10, 10, 0)))
sketch.addGeometry(Part.LineSegment(Vector(10, 10, 0), Vector(-10, 10, 0)))
sketch.addGeometry(Part.LineSegment(Vector(-10, 10, 0), Vector(-10, -10, 0)))
"""Line:
code = """
# Add line
sketch.addGeometry(Part.LineSegment(Vector(0, 0, 0), Vector(10, 10, 0)))
"""Arc:
code = """
# Add arc
import math
arc = Part.ArcOfCircle(
Part.Circle(Vector(0, 0, 0), Vector(0, 0, 1), 10),
0, math.pi/2 # Start angle, end angle
)
sketch.addGeometry(arc)
"""Adds constraints to sketch geometry.
Macro Pattern:
code = """
import Sketcher
doc = App.ActiveDocument
sketch = doc.getObject('Sketch')
# Distance constraint
sketch.addConstraint(Sketcher.Constraint('Distance', 0, 10.0))
# Coincident constraint
sketch.addConstraint(Sketcher.Constraint('Coincident', 0, 2, 1, 1))
# Horizontal constraint
sketch.addConstraint(Sketcher.Constraint('Horizontal', 0))
# Vertical constraint
sketch.addConstraint(Sketcher.Constraint('Vertical', 1))
# Radius constraint
sketch.addConstraint(Sketcher.Constraint('Radius', 0, 15.0))
doc.recompute()
"""Creates a pad (extrusion) from a sketch.
Macro Pattern:
code = """
import PartDesign
doc = App.ActiveDocument
body = doc.getObject('Body')
sketch = doc.getObject('Sketch')
# Create pad
pad = doc.addObject('PartDesign::Pad', 'Pad')
pad.Profile = sketch
pad.Length = 20.0
body.addObject(pad)
doc.recompute()
"""With Parameters:
from freecad_mcp_client import run_macro
result = run_macro("extrude.FCMacro", {
"length": 25.0,
"reversed": False
})Creates a revolution from a sketch.
Macro Pattern:
code = """
import PartDesign
from FreeCAD import Vector
doc = App.ActiveDocument
body = doc.getObject('Body')
sketch = doc.getObject('Sketch')
# Create revolution
revolution = doc.addObject('PartDesign::Revolution', 'Revolution')
revolution.Profile = sketch
revolution.ReferenceAxis = (sketch, ['V_Axis'])
revolution.Angle = 360.0
body.addObject(revolution)
doc.recompute()
"""Creates a pocket (cut) from a sketch.
Macro Pattern:
code = """
import PartDesign
doc = App.ActiveDocument
body = doc.getObject('Body')
sketch = doc.getObject('Sketch')
# Create pocket
pocket = doc.addObject('PartDesign::Pocket', 'Pocket')
pocket.Profile = sketch
pocket.Length = 10.0
body.addObject(pocket)
doc.recompute()
"""Creates fillets on edges.
Macro Pattern:
code = """
import PartDesign
doc = App.ActiveDocument
body = doc.getObject('Body')
pad = doc.getObject('Pad')
# Create fillet
fillet = doc.addObject('PartDesign::Fillet', 'Fillet')
fillet.Base = (pad, ['Edge1', 'Edge2'])
fillet.Radius = 2.0
body.addObject(fillet)
doc.recompute()
"""Creates chamfers on edges.
Macro Pattern:
code = """
import PartDesign
doc = App.ActiveDocument
body = doc.getObject('Body')
pad = doc.getObject('Pad')
# Create chamfer
chamfer = doc.addObject('PartDesign::Chamfer', 'Chamfer')
chamfer.Base = (pad, ['Edge1', 'Edge2'])
chamfer.Size = 2.0
body.addObject(chamfer)
doc.recompute()
"""Linear Pattern:
code = """
import PartDesign
doc = App.ActiveDocument
body = doc.getObject('Body')
feature = doc.getObject('Pocket')
# Create linear pattern
pattern = doc.addObject('PartDesign::LinearPattern', 'LinearPattern')
pattern.Originals = [feature]
pattern.Direction = (doc.getObject('Sketch'), ['H_Axis'])
pattern.Length = 100
pattern.Occurrences = 5
body.addObject(pattern)
doc.recompute()
"""Polar Pattern:
code = """
import PartDesign
doc = App.ActiveDocument
body = doc.getObject('Body')
feature = doc.getObject('Pocket')
# Create polar pattern
pattern = doc.addObject('PartDesign::PolarPattern', 'PolarPattern')
pattern.Originals = [feature]
pattern.Axis = (doc.getObject('Sketch'), ['N_Axis'])
pattern.Angle = 360
pattern.Occurrences = 8
body.addObject(pattern)
doc.recompute()
"""Exports an object as STL file (3D printing format).
Function Signature:
def export_stl(object_name: str, filepath: str, mesh_deviation: float = 0.1) -> Dict[str, Any]Parameters:
object_name(str, required): Name of object to exportfilepath(str, required): Absolute output path (auto-appends.stl)mesh_deviation(float, optional): Mesh quality (0.01-1.0, lower=finer)
Returns:
{
"result": "success",
"message": "STL exported successfully",
"filepath": "/path/to/output.stl"
}Example:
from freecad_mcp_client import export_stl
# Export with default quality
result = export_stl("Body", "/Users/username/Desktop/gear.stl")
# Export with high quality (fine mesh)
result = export_stl("Body", "/Users/username/Desktop/gear_hq.stl", mesh_deviation=0.01)Status: Implemented in client, requires server handler implementation
Exports objects as STEP file (CAD interchange format).
Function Signature:
def export_step(filepath: str, objects: str = None) -> Dict[str, Any]Parameters:
filepath(str, required): Absolute output path (auto-appends.step)objects(str, optional): Comma-separated object names (exports all if None)
Returns:
{
"result": "success",
"message": "STEP exported successfully",
"filepath": "/path/to/output.step"
}Example:
from freecad_mcp_client import export_step
# Export all objects
result = export_step("/Users/username/Desktop/assembly.step")
# Export specific objects
result = export_step("/Users/username/Desktop/parts.step", "Body,Body001,Body002")Status: Implemented in client, requires server handler implementation
Exports objects as IGES file (legacy CAD format).
Function Signature:
def export_iges(filepath: str, objects: str = None) -> Dict[str, Any]Parameters:
filepath(str, required): Absolute output path (auto-appends.iges)objects(str, optional): Comma-separated object names (exports all if None)
Returns:
{
"result": "success",
"message": "IGES exported successfully",
"filepath": "/path/to/output.iges"
}Example:
from freecad_mcp_client import export_iges
result = export_iges("/Users/username/Desktop/model.iges", "Body")Status: Implemented in client, requires server handler implementation
Exports an object as OBJ file (3D graphics format).
Function Signature:
def export_obj(object_name: str, filepath: str, mesh_deviation: float = 0.1) -> Dict[str, Any]Parameters:
object_name(str, required): Name of object to exportfilepath(str, required): Absolute output path (auto-appends.obj)mesh_deviation(float, optional): Mesh quality (0.01-1.0)
Returns:
{
"result": "success",
"message": "OBJ exported successfully",
"filepath": "/path/to/output.obj"
}Example:
from freecad_mcp_client import export_obj
result = export_obj("Body", "/Users/username/Desktop/model.obj", 0.05)Status: Implemented in client, requires server handler implementation
Exports 2D drawing/TechDraw page as SVG.
Function Signature:
def export_svg(filepath: str, page_name: str = None) -> Dict[str, Any]Parameters:
filepath(str, required): Absolute output path (auto-appends.svg)page_name(str, optional): TechDraw page name (uses first page if None)
Returns:
{
"result": "success",
"message": "SVG exported successfully",
"filepath": "/path/to/output.svg"
}Example:
from freecad_mcp_client import export_svg
# Export first page
result = export_svg("/Users/username/Desktop/drawing.svg")
# Export specific page
result = export_svg("/Users/username/Desktop/section.svg", "Page001")Status: Implemented in client, requires server handler implementation
Exports TechDraw page as PDF.
Function Signature:
def export_pdf(filepath: str, page_name: str = None) -> Dict[str, Any]Parameters:
filepath(str, required): Absolute output path (auto-appends.pdf)page_name(str, optional): TechDraw page name (uses first page if None)
Returns:
{
"result": "success",
"message": "PDF exported successfully",
"filepath": "/path/to/output.pdf"
}Example:
from freecad_mcp_client import export_pdf
result = export_pdf("/Users/username/Desktop/drawing.pdf", "Assembly_View")Status: Implemented in client, requires server handler implementation
Sets the 3D view orientation.
Function Signature:
def set_view(params: Dict[str, Any]) -> Dict[str, Any]Parameters:
params(dict, required): View parametersview_type(str): View type code"1": Front view"2": Top view"3": Right view"7": Isometric/axonometric view
Returns:
{
"result": "success",
"view_name": "isometric"
}Example:
from freecad_mcp_client import set_view
# Set to isometric view
result = set_view({"view_type": "7"})
# Set to front view
result = set_view({"view_type": "1"})Common Errors:
- No GUI available: Returns error
- No active view: Returns error
- Invalid view type: Returns error with valid options
Status: Not yet implemented. Use macro pattern:
code = """
import FreeCADGui as Gui
from FreeCAD import Vector
view = Gui.ActiveDocument.ActiveView
camera = view.getCameraNode()
# Set camera position
camera.position.setValue(100, 100, 100)
camera.pointAt(Vector(0, 0, 0), Vector(0, 0, 1))
Gui.updateGui()
"""Status: Automatically called after run_macro. For manual use:
code = """
import FreeCADGui as Gui
view = Gui.ActiveDocument.ActiveView
view.fitAll()
Gui.updateGui()
"""Status: Not yet implemented. Use macro pattern:
code = """
import FreeCADGui as Gui
view = Gui.ActiveDocument.ActiveView
view.saveImage('/path/to/screenshot.png', 1920, 1080, 'White')
"""Status: Not yet implemented. Use macro pattern:
code = """
import FreeCADGui as Gui
view = Gui.ActiveDocument.ActiveView
# Available styles: "Flat Lines", "Shaded", "Wireframe", "Points", "Hidden Line"
view.setAnimationEnabled(False)
view.setRenderStyle("Flat Lines")
Gui.updateGui()
"""Note: Measurement and analysis operations are currently implemented through macro execution.
Macro Pattern:
code = """
import FreeCAD as App
doc = App.ActiveDocument
obj = doc.getObject('Body')
bbox = obj.Shape.BoundBox
result = {
'x_min': bbox.XMin, 'x_max': bbox.XMax,
'y_min': bbox.YMin, 'y_max': bbox.YMax,
'z_min': bbox.ZMin, 'z_max': bbox.ZMax,
'x_length': bbox.XLength,
'y_length': bbox.YLength,
'z_length': bbox.ZLength
}
print(f"Bounding Box: {result}")
"""Macro Pattern:
code = """
from FreeCAD import Vector
point1 = Vector(0, 0, 0)
point2 = Vector(10, 10, 10)
distance = point1.distanceToPoint(point2)
print(f"Distance: {distance}")
"""Macro Pattern:
code = """
import FreeCAD as App
doc = App.ActiveDocument
obj = doc.getObject('Body')
if hasattr(obj.Shape, 'Volume'):
volume = obj.Shape.Volume
print(f"Volume: {volume} mm³")
else:
print("Object has no volume")
"""Macro Pattern:
code = """
import FreeCAD as App
doc = App.ActiveDocument
obj = doc.getObject('Body')
if hasattr(obj.Shape, 'Area'):
area = obj.Shape.Area
print(f"Surface Area: {area} mm²")
else:
print("Object has no surface area")
"""Macro Pattern:
code = """
import FreeCAD as App
doc = App.ActiveDocument
obj = doc.getObject('Body')
if hasattr(obj.Shape, 'CenterOfMass'):
com = obj.Shape.CenterOfMass
print(f"Center of Mass: X={com.x}, Y={com.y}, Z={com.z}")
else:
print("Cannot compute center of mass")
"""Macro Pattern:
code = """
import FreeCAD as App
import Part
doc = App.ActiveDocument
obj = doc.getObject('Body')
shape = obj.Shape
analysis = {
'is_valid': shape.isValid(),
'is_closed': shape.isClosed(),
'is_null': shape.isNull(),
'volume': shape.Volume if hasattr(shape, 'Volume') else 0,
'area': shape.Area if hasattr(shape, 'Area') else 0,
'num_faces': len(shape.Faces),
'num_edges': len(shape.Edges),
'num_vertices': len(shape.Vertexes),
'shape_type': shape.ShapeType
}
print(f"Shape Analysis: {analysis}")
"""Always check response status:
result = run_macro("my_macro.FCMacro")
if result["result"] != "success":
print(f"Error: {result['message']}")
returnUse absolute paths for file operations:
import os
output_path = os.path.abspath(os.path.expanduser("~/Desktop/model.stl"))
export_stl("Body", output_path)Validate parameters before API calls:
import re
def is_valid_macro_name(name):
return bool(re.match(r'^[a-zA-Z0-9_-]+$', name))
macro_name = "my-gear"
if not is_valid_macro_name(macro_name):
print("Invalid macro name")
else:
create_macro(macro_name)Let the client normalize imports:
# Don't manually add imports
code = """
import FreeCAD as App
import Part
# ...
"""
# Just write your logic, imports are auto-added
code = """
cylinder = Part.makeCylinder(10, 20)
Part.show(cylinder)
"""
update_macro("cylinder", code)Design macros with parameters:
# gear.FCMacro
code = """
# Parameters (injected by run_macro)
# radius, teeth, height
import Part
import math
# Generate gear geometry
gear = Part.makeCylinder(radius, height)
Part.show(gear)
"""
# Run with different parameters
run_macro("gear.FCMacro", {"radius": 20, "teeth": 24, "height": 10})
run_macro("gear.FCMacro", {"radius": 30, "teeth": 36, "height": 15})Use loops for batch processing:
models = [
("gear", {"radius": 10}),
("shaft", {"length": 50}),
("housing", {"width": 100})
]
for macro_name, params in models:
result = run_macro(f"{macro_name}.FCMacro", params)
if result["result"] == "success":
print(f"Created {macro_name}")Check server availability:
from freecad_mcp_client import get_report
try:
result = get_report()
if result["result"] == "success":
print("Server is running")
except Exception as e:
print(f"Server not available: {e}")Monitor operations via logs:
import os
import platform
# Locate log file
if platform.system() == "Windows":
log_path = os.path.join(os.environ['TEMP'], "freecad_mcp_log.txt")
else:
log_path = "/tmp/freecad_mcp_log.txt"
# Read recent logs
with open(log_path, 'r') as f:
logs = f.readlines()
print("Recent logs:", logs[-10:])from freecad_mcp_client import create_macro, update_macro, run_macro, export_stl
# Step 1: Create macro
create_macro("parametric_gear", "part")
# Step 2: Write gear generation code
code = """
import math
# Parameters: radius, teeth, height (injected by run_macro)
# Create basic gear shape (simplified)
gear = Part.makeCylinder(radius, height)
# Position and display
gear.Placement.Base = Vector(0, 0, 0)
Part.show(gear, 'Gear')
"""
update_macro("parametric_gear", code)
# Step 3: Generate gear with different sizes
sizes = [
{"radius": 20, "teeth": 20, "height": 10, "doc_name": "SmallGear"},
{"radius": 30, "teeth": 30, "height": 15, "doc_name": "MediumGear"},
{"radius": 40, "teeth": 40, "height": 20, "doc_name": "LargeGear"}
]
for params in sizes:
result = run_macro("parametric_gear.FCMacro", params)
if result["result"] == "success":
# Export to STL
doc_name = params["doc_name"]
export_stl("Gear", f"/Users/username/Desktop/{doc_name}.stl", 0.05)
print(f"Created and exported {doc_name}")from freecad_mcp_client import run_macro, update_macro
# Create analysis macro
analysis_code = """
import FreeCAD as App
doc = App.ActiveDocument
obj = doc.getObject('Body')
if obj and hasattr(obj, 'Shape'):
shape = obj.Shape
results = {
'volume': shape.Volume,
'surface_area': shape.Area,
'center_of_mass': {
'x': shape.CenterOfMass.x,
'y': shape.CenterOfMass.y,
'z': shape.CenterOfMass.z
},
'bounding_box': {
'x_length': shape.BoundBox.XLength,
'y_length': shape.BoundBox.YLength,
'z_length': shape.BoundBox.ZLength
}
}
# Write results to file
import json
with open('/tmp/analysis_results.json', 'w') as f:
json.dump(results, f, indent=2)
print("Analysis complete")
else:
print("No Body object found")
"""
update_macro("analyze_part", analysis_code)
run_macro("analyze_part.FCMacro")
# Read results
import json
with open('/tmp/analysis_results.json', 'r') as f:
results = json.load(f)
print(f"Volume: {results['volume']:.2f} mm³")
print(f"Surface Area: {results['surface_area']:.2f} mm²")from freecad_mcp_client import run_macro, export_stl, export_step, close_document
models = ["gear", "shaft", "housing", "bearing"]
for model in models:
# Generate model
result = run_macro(f"{model}.FCMacro", {"doc_name": model.capitalize()})
if result["result"] == "success":
doc_name = result["document"]
# Export to multiple formats
export_stl("Body", f"/Users/username/Desktop/{model}.stl", 0.05)
export_step(f"/Users/username/Desktop/{model}.step", "Body")
# Close document to free memory
close_document(doc_name)
print(f"Processed {model}")User: Create a parametric flange with 6 bolt holes
Claude: I'll create a parametric flange macro for you.
[Creates macro with parameters: outer_radius, inner_radius, thickness, bolt_count, bolt_radius]
User: Generate flange with outer radius 50mm, inner radius 20mm, 8 holes
Claude: Executing with your parameters...
[Runs macro, displays result, exports STL]
User: Increase thickness to 15mm
Claude: Updating and regenerating...
[Modifies parameters, re-runs macro]
Implementation:
# Claude internally executes:
from freecad_mcp_client import create_macro, update_macro, run_macro
# Create flange macro with full parametric code
# (Implementation details in macro)
# Run with user parameters
result = run_macro("flange.FCMacro", {
"outer_radius": 50,
"inner_radius": 20,
"thickness": 15,
"bolt_count": 8,
"bolt_radius": 4,
"doc_name": "CustomFlange"
})User: [Uploads image of technical drawing]
User: Recreate this table in FreeCAD
Claude: [Analyzes drawing, generates macro]
[Creates macro with detected dimensions]
[Executes macro, displays result]
[Exports model]
Implementation:
# Pseudo-code (requires external vision analysis)
# 1. Extract dimensions from image
dimensions = analyze_drawing(image_path)
# 2. Generate macro code
code = generate_table_macro(dimensions)
# 3. Create and run macro
update_macro("table_from_drawing", code)
result = run_macro("table_from_drawing.FCMacro")
# 4. Export result
export_step("/Users/username/Desktop/table.step")| Category | Tool | Client | Server | Status |
|---|---|---|---|---|
| Macro | create_macro | ✅ | ✅ | Complete |
| update_macro | ✅ | ✅ | Complete | |
| run_macro | ✅ | ✅ | Complete | |
| validate_macro_code | ✅ | ✅ | Complete | |
| Document | list_documents | ✅ | ❌ | Client Only |
| get_active_document | ✅ | ❌ | Client Only | |
| create_document | ✅ | ❌ | Client Only | |
| save_document | ✅ | ❌ | Client Only | |
| close_document | ✅ | ❌ | Client Only | |
| Object | list_objects | ✅ | ❌ | Client Only |
| get_object_properties | ✅ | ❌ | Client Only | |
| delete_object | ✅ | ❌ | Client Only | |
| Export | export_stl | ✅ | ❌ | Client Only |
| export_step | ✅ | ❌ | Client Only | |
| export_iges | ✅ | ❌ | Client Only | |
| export_obj | ✅ | ❌ | Client Only | |
| export_svg | ✅ | ❌ | Client Only | |
| export_pdf | ✅ | ❌ | Client Only | |
| View | set_view | ✅ | ✅ | Complete |
| get_report | ✅ | ✅ | Complete | |
| Part Design | All operations | Macro | Macro | Via Macros |
| Analysis | All operations | Macro | Macro | Via Macros |
Legend:
- ✅ Implemented
- ❌ Not Implemented (requires server handler)
- Macro: Implemented via macro execution patterns
- GitHub: https://github.com/ATOI-Ming/FreeCAD-MCP
- Issues: Bug Tracker
- FreeCAD Documentation: https://wiki.freecad.org
- MCP Protocol: Model Context Protocol Docs
Last Updated: 2026-01-14 Version: 0.1.0 License: MIT