22 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
-
GitHub Actions CI/CD workflows:
pre-commit.yaml: Runs all pre-commit hooks on every push and PRtest.yaml: Runs unit tests when MCP source files change, integration tests on main branchdocker.yaml: Builds multi-arch Docker images, pushes to GHCR on main/tagsdocker-release.yaml: Builds and pushes to Docker Hub on semantic version tags (v1.2.3)- Multi-arch support (linux/amd64, linux/arm64)
- Semantic version tags:
1.2.3,1.2,1, andlatestfor stable releases - Prerelease tags (v1.2.3-beta) only get the full version tag
- SBOM and provenance attestations for supply chain security
- Automatic Docker Hub README sync
pypi-release.yaml: Builds and publishes to PyPI on semantic version tags- Validates version in pyproject.toml matches git tag
- Tests installation on Ubuntu and macOS before publishing
- Stable releases (v1.2.3) go to PyPI, prereleases (v1.2.3-beta) to TestPyPI
- Trusted publishing with OIDC (no API tokens needed)
- Attaches wheel and sdist to GitHub Release
macro-test.yaml: Tests FreeCAD macros when macro files change (validates syntax, runs tests)- Integration tests start FreeCAD headless with MCP bridge automatically
-
GitHub issue templates:
- Bug report template with structured fields for connection mode, FreeCAD version, OS, logs
- Feature request template with impact assessment and use case sections
- Issue template config with links to documentation and discussions
-
Dependabot configuration:
- Weekly updates for Python dependencies, GitHub Actions, and Docker base images
- Grouped updates for development dependencies
-
PyPI package name:
freecad-robust-mcp(due to existingfreecad-mcpon PyPI)- Install via:
pip install freecad-robust-mcp - CLI command remains
freecad-mcp - Python import remains
import freecad_mcp
- Install via:
-
Enhanced PyPI and Docker Hub metadata:
- Updated pyproject.toml with expanded classifiers, keywords, and URLs for better discoverability
- Added maintainers, Changelog URL, and Docker Hub URL to project metadata
- Added
py.typedmarker file for PEP 561 typed package support - Enhanced Dockerfile with full OCI image labels (vendor, authors, documentation, base image)
- Added
twineandbuildto dev dependencies for package validation
-
Docker integration test workflow (
just docker::test):- Builds the Docker image with a
:testtag - Automatically starts FreeCAD headless with MCP bridge if not already running
- Runs containerized MCP server and verifies communication
- Confirms response is from Docker container (Linux OS, Docker detection)
- Displays container hostname and raw MCP responses for debugging
- Automatically cleans up started FreeCAD process on completion
- Builds the Docker image with a
-
New MCP tool
get_mcp_server_environment:- Returns environment info about where the MCP server is running
- Includes: hostname, os_name, os_version, platform, python_version
- Docker detection:
in_dockerboolean anddocker_container_id(first 12 chars) - Environment variables: FREECAD_MODE, FREECAD_SOCKET_HOST/PORT, FREECAD_XMLRPC_PORT
- Useful for verifying which MCP server instance you're connected to
- Documented in README.md, CLAUDE.md, and docs/MCP_TOOLS_REFERENCE.md
-
Docker support for MCP server:
- New
Dockerfilewith BuildKit optimizations and multi-stage build - Multi-arch support via
docker buildx(linux/amd64, linux/arm64) - Cache mounts for apt, pip, and uv for faster rebuilds
- Non-root user for security
- Health check for container orchestration
- Environment variables for connection configuration (FREECAD_MODE, FREECAD_SOCKET_HOST, etc.)
.dockerignoreto exclude development files from build context- Docker just commands:
just docker::build,just docker::build-multi,just docker::build-push
- New
-
Modular justfile organization:
- Reorganized justfile into modules in
just/directory for better organization just/docker.just- Docker build and run commandsjust/quality.just- Code quality and linting commandsjust/testing.just- Test execution commandsjust/freecad.just- FreeCAD plugin and macro commandsjust/documentation.just- Documentation building commands- All existing shortcut commands preserved (e.g.,
just test,just lint,just check) - New module syntax available:
just docker::build,just quality::secrets, etc. - Use
just --list <module>to see commands in a specific module
- Reorganized justfile into modules in
-
Improved cut face detection for curved objects in CutObjectForMagnets macro:
_find_cut_face_name()now uses a two-strategy approach:- Strategy 1: Match faces by normal direction (works for flat/planar objects)
- Strategy 2: Match faces by proximity to cut plane (works for curved objects like vases)
- For curved objects where no face has a matching normal (dot product < 0.99), the function now finds faces whose center lies closest to the cut plane
- This fixes the "Could not find cut face" error when cutting vases and other curved shapes at angles
- Tested successfully with a vase model cut at 30° angle using a datum plane
-
Undo support for CutObjectForMagnets macro:
- Each major step is now wrapped in a FreeCAD transaction
- Users can undo individual steps via Edit → Undo in the GUI
- Transaction steps: Create Bottom Body, Create Top Body, Create Bottom Hole Sketch, Create Top Hole Sketch, Create Bottom Magnet Holes, Create Top Magnet Holes, Hide Original Object
- If any step fails, the transaction is aborted to prevent partial state
-
VS Code workspace configuration:
- Added
.vscode/settings.jsonwith cSpell configuration matching codespell pre-commit - Custom dictionary includes all project-specific terms from
.codespell-ignore-words.txt - Configured Ruff as default Python formatter with format-on-save
- Added
.vscode/extensions.jsonrecommending Code Spell Checker, Ruff, Pylance, etc.
- Added
-
Integration tests for CutObjectForMagnets macro:
test_cut_solid_box_with_partdesign_holes- Tests cutting solid objects with PartDesign::Hole featurestest_cut_hollow_cylinder_with_partdesign_holes- Tests cutting hollow objects (vase-shaped) with PartDesign::Holetest_boolean_hole_creation_fallback- Tests the_create_holes_booleanfallback methodtest_boolean_vs_partdesign_comparison- Compares output of both hole creation methodstest_single_hole/test_many_holes- Edge case tests for hole count variations- Tests are in
tests/integration/test_cut_object_for_magnets.pyand run viajust test-integration
-
Status bar display for MCP bridge in GUI mode:
- Shows "🔌 MCP Bridge running (XML-RPC:9875) | waiting for connections..." when idle
- Shows "🔌 MCP Bridge active (XML-RPC:9875) | X requests | last: Ys ago" after activity
- Updates every 5 seconds to show current status
- Automatically clears when bridge is stopped
-
Initial MCP server implementation for FreeCAD integration
-
Bridge architecture supporting multiple connection modes:
- Embedded mode (headless FreeCAD)
- Socket mode (JSON-RPC)
- XML-RPC mode (neka-nat compatible)
-
MCP tools for FreeCAD operations:
- Document management (create, open, save, close)
- Object manipulation (create, modify, delete)
- PartDesign workflow (sketches, pads, pockets, fillets)
- Import/export (STEP, STL, 3MF, OBJ, IGES)
- Macro management
- View and screenshot capture
-
export_3mf- Export objects to 3MF format (modern 3D printing format with color/material support) -
Additional primitive creation tools:
create_cone- Create cone primitives with configurable radiicreate_torus- Create torus (donut) primitivescreate_wedge- Create wedge/ramp primitivescreate_helix- Create helix curves for threads and springs
-
Transform and manipulation tools:
scale_object- Scale objects uniformly or non-uniformlyrotate_object- Rotate objects around arbitrary axescopy_object- Create copies with optional offsetmirror_object- Mirror objects across planes (XY, XZ, YZ)
-
Selection management tools:
get_selection- Get current selection with sub-elementsset_selection- Programmatically select objectsclear_selection- Clear all selections
-
Advanced PartDesign tools:
revolution_sketch- Create revolution solids from sketchesgroove_sketch- Create subtractive revolutionscreate_hole- Create parametric holes with threading support (ISO, UNC, UNF)linear_pattern- Create linear patterns of featurespolar_pattern- Create circular patterns of featuresmirrored_feature- Mirror PartDesign featuresloft_sketches- Create lofts through multiple profilessweep_sketch- Sweep profiles along paths
-
Additional sketch geometry tools:
add_sketch_line- Add lines with construction mode optionadd_sketch_arc- Add arcs with center/radius/anglesadd_sketch_point- Add points for hole centers
-
View and document control tools:
zoom_in/zoom_out- View zoom controlsset_camera_position- Set camera position and look-at pointundo/redo- Document undo/redo operationsget_undo_redo_status- Query undo/redo staterecompute- Force document recomputationget_console_log- Get console messages, warnings, errors
-
Parts library tools:
list_parts_library- List available library partsinsert_part_from_library- Insert parts from library with positioning
-
MCP resources exposing FreeCAD state:
- Version and status information
- Document and object listings
- Workbench information
- Console output
-
MCP prompts for guided workflows:
- Part design assistance
- Export/import guidance
- Shape analysis
- Macro development help
- Troubleshooting guides
-
Comprehensive pre-commit hook configuration:
- Ruff for linting and formatting
- MyPy for type checking
- Bandit for security scanning
- Multi-layer secrets detection (Gitleaks, detect-secrets, TruffleHog)
- Markdownlint and mdformat for documentation
- Codespell for spell checking
-
Project documentation:
- CLAUDE.md with AI assistant guidelines
- ARCHITECTURE.md with system design
- README.md with usage instructions
Changed
- Reorganized README.md structure:
- Split into "For Users" and "For Developers" top-level sections
- Each section has MCP Server and FreeCAD Macros subsections
- Added Docker installation and Claude Code configuration for containerized usage
- pip installation now shown as primary option for users
- mise/just shown as alternative for developers
- Improved docs/index.md landing page:
- Now properly links to all documentation files in the repo
- Added documentation table with descriptions
- Updated quick start, features, and connection modes sections
- CLAUDE.md now documents
uvpackage manager usage anduv runrequirement for Python tools - Added proper MIT license attribution to files inspired by neka-nat/freecad-mcp:
src/freecad_mcp/bridge/xmlrpc.py- XML-RPC protocol designsrc/freecad_mcp/freecad_plugin/server.py- Queue-based thread safety pattern
- Improved error messaging for connection failures:
- XML-RPC bridge now provides detailed instructions when FreeCAD plugin is not running
- Error message explains how to start the MCP bridge in FreeCAD
- References the new
just install-macrocommand for easy setup
- New justfile commands for FreeCAD integration:
just install-macro- Installs StartMCPBridge.FCMacro with dynamic project pathsjust uninstall-macro- Removes the installed macrojust install-freecad-plugin- Installs full plugin to FreeCAD Mod directoryjust uninstall-freecad-plugin- Removes the installed pluginjust run-headless- Launches FreeCAD in headless mode with MCP bridgejust run-headless-custom <path>- Launch with custom FreeCADCmd pathjust run-gui- Launches FreeCAD GUI with MCP bridge auto-startedjust run-gui-custom <path>- Launch GUI with custom FreeCAD path- Supports macOS, Linux, and Windows paths automatically
- Headless mode support for FreeCAD plugin:
- Plugin now detects when Qt is unavailable and uses thread-based queue processing
- New
run_forever()method for running in headless/console mode - New
headless_server.pyscript for easy headless startup - Note: Screenshots and view features are not available in headless mode
- Improved error handling for GUI-dependent tools:
- All view tools now check
FreeCAD.GuiUpand return clear error messages in headless mode - Tools return
{"success": False, "error": "..."}instead of raising exceptions - Affected tools:
set_object_visibility,set_display_mode,set_object_color,zoom_in,zoom_out,set_camera_position,get_screenshot - Non-GUI tools (
undo,redo,recompute) now also return structured error responses
- All view tools now check
- Updated documentation for connection modes:
- README.md now documents all three modes:
xmlrpc,socket,embedded - Changed default recommended mode from
embeddedtoxmlrpc(works on all platforms) - Added
FREECAD_XMLRPC_PORTenvironment variable documentation - Documented platform limitations (embedded mode only works on Linux)
- Updated Python version requirement from 3.12+ to 3.11 (FreeCAD ABI compatibility)
- ARCHITECTURE.md deployment modes updated to reflect current implementation
- README.md now documents all three modes:
Fixed
- Justfile module working directory issue:
- Fixed
just freecad::run-headlessand other FreeCAD commands failing with wrong path - Issue:
$(pwd)in module recipes returnedjust/directory instead of project root - Solution: All module files now use
project_root := justfile_directory()variable - Added documentation in CLAUDE.md about justfile working directory behavior in modules
- Fixed
- Cut face detection for re-cutting already-cut objects in CutObjectForMagnets macro:
- When cutting an object that was previously cut, the macro now correctly identifies the NEW cut face
- Previously, the algorithm only checked face normals but not proximity to the cut plane
- This caused it to potentially select an OLD cut face from a previous operation
- New algorithm uses three strategies with combined normal + distance checks:
- Planar faces with exact normal match AND close to cut plane (ideal case)
- Any faces with good normal match close to cut plane (for curved objects)
- Fallback to best normal match with warning (for edge cases)
- Sketch attachment failure in CutObjectForMagnets macro:
- Changed
_find_cut_face_name()to usebody.BaseFeatureinstead ofbody.Tip - Changed
_create_hole_sketch()to attach sketches to BaseFeature instead of Tip - Root cause: When re-running the macro on a failed state,
body.Tipwas the brokenMagnetHolesfeature (with 0 faces) instead of the solidBaseFeature - This caused the macro to select non-planar faces (Cone, Toroid) instead of the
actual planar cut face, resulting in "Invalid" sketch state with
FlatFaceMapMode - Now correctly finds Face4 (the planar cut face with dot=1.0) instead of Face81 (Cone)
- Changed
- Python version mismatch crash - Embedded FreeCAD mode was crashing with SIGSEGV
- FreeCAD bundles
libpython3.11.dyliband requires Python 3.11 for ABI compatibility - Using Python 3.12+ caused
EXC_BAD_ACCESSwhen loading FreeCAD.so (callingPySys_GetObjectfrom wrong Python version) - Changed
.mise.tomlto use Python 3.11 and updatedpyproject.tomlaccordingly
- FreeCAD bundles
- Embedded mode crashes on macOS - Even with correct Python version, embedded mode fails
- FreeCAD's
FreeCAD.solinks to@rpath/libpython3.11.dylibwhich conflicts with external Python interpreters - Documented that embedded mode only works on Linux; macOS/Windows must use
xmlrpcorsocketmode - Updated
.mcp.jsonto usexmlrpcmode by default - Added FreeCAD connection modes documentation to CLAUDE.md
- FreeCAD's
- Server startup crash with
AttributeError: 'FastMCP' object has no attribute 'on_startup'- Migrated from deprecated
mcp.on_startup()/on_shutdown()hooks to the modernlifespanasync context manager pattern - This fix is required for compatibility with MCP SDK 1.25.0+
- Migrated from deprecated
- Pre-commit hook compatibility issues:
- Ruff UP038: Updated isinstance calls to use modern union syntax
- MyPy: Added type ignore comments for FastMCP dynamic attributes
- Codespell: Added FreeCAD-specific terms to ignore list
- Markdownlint/mdformat: Added mdformat-simple-breaks plugin for consistent horizontal rules
- Justfile commands now use
uv runprefix for all Python tools (pytest, ruff, mypy, etc.) - Headless server import error -
just run-headlesswas failing with'freecad_mcp' is not a package- The
freecad_mcp/__init__.pyimports the MCP SDK which isn't available in FreeCAD's Python environment - Fixed by importing
FreecadMCPPlugindirectly from the module file instead of through the package hierarchy - Added explicit
flush=Trueto print statements for immediate output in FreeCAD's buffered stdout
- The
- Headless mode queue processor not running - XML-RPC
executecalls would hang indefinitely- The code was checking for Qt (PySide) availability to determine GUI vs headless mode
- PySide6 is available in FreeCAD headless mode, but Qt timers don't fire without an event loop
- Fixed by using
FreeCAD.GuiUpto correctly detect headless mode and start the queue processor thread
- Renamed CutWithConnectors macro to CutObjectForMagnets:
- Macro file renamed from
CutWithConnectors.FCMacrotoCutObjectForMagnets.FCMacro - Directory renamed from
Cut_with_ConnectorstoCut_Object_for_Magnets - Updated class names:
CutWithConnectorsDialog→CutObjectForMagnetsDialog - Updated dialog title and UI labels to reference "magnets" instead of "connectors"
- Updated justfile commands to reference new file paths
- All functionality preserved - just a naming update for clarity
- Macro file renamed from
- CutObjectForMagnets macro - hole placement improvements:
- Changed from spacing-based to count-based hole placement (default: 6 holes)
- Holes now distributed evenly along the perimeter of the cut face
- Both halves now have identical hole positions that align when joined
- Fixed cut face detection to find the correct face at the cut plane (was finding original object faces)
- Fixed hole positioning for hollow objects (ring-shaped cross-sections)
- Safety check correctly rejects holes that would penetrate inner walls on thin-walled objects
- Added smart hole repositioning: when a hole fails the safety check, the macro tries to find a nearby safe position by:
- Moving further inward from the edge (1.5x, 2x, 2.5x, 3x inset)
- Moving along the perimeter to find a thicker section (±5%, ±10%, ±15%, ±20%)
- Hole validation now checks BOTH parts simultaneously to ensure holes align perfectly
- Added minimum hole spacing check: holes must be at least 2x diameter apart (one hole width between them)
- Automatically reduces hole count when requested holes would overlap
- Updated default values: diameter=3mm, depth=3mm, holes=6
- Added dual clearance system with preferred and minimum values:
- "Edge Clearance (Preferred)": default 2mm - used for initial hole placement
- "Edge Clearance (Minimum)": default 0.5mm - used as fallback during repositioning
- Smart repositioning now tries clearances from preferred down to minimum before moving holes
- This keeps holes in ideal positions when possible, only moving closer to edges when necessary
- Dialog now shows body name and type being cut
- Smart plane detection: selecting both object and plane auto-sets the plane as cut plane
- Added object selection combo box: users can now change the object to cut in the dialog (not just from initial selection)
- CutObjectForMagnets macro - PartDesign::Hole implementation (parametric holes):
- Holes now created as native
PartDesign::Holefeatures instead of Part boolean operations - Cut halves created as
PartDesign::Bodyobjects usingPartDesign::BaseFeature - Hole centers defined via
Sketcher::SketchObjectattached to cut face - Benefits of new approach:
- Holes appear in the feature tree and can be edited after creation
- Change hole diameter/depth by modifying the Hole feature properties
- Sketch shows hole center points for visualization
- Full PartDesign parametric workflow integration
- New helper methods added:
_create_body_from_shape()- Wraps Part::Shape in PartDesign::Body_find_cut_face_name()- Locates cut face by normal direction_world_to_sketch_coords()- Transforms world coords to sketch-local 2D_create_hole_sketch()- Creates sketch with points at hole centers_create_hole_feature()- Creates PartDesign::Hole from sketch
- Original boolean hole method kept as
_create_holes_boolean()for potential fallback - FreeCAD 1.0+ API compatibility fixes:
- Fixed
_create_body_from_shape()to usebody.BaseFeature = featureproperty instead ofbody.newObject("PartDesign::BaseFeature") - Fixed sketch attachment to use
AttachmentSupportproperty instead of deprecatedSupport - Fixed
_create_hole_feature()to useDepthType = "Dimension"(string) andHoleCutType = "None"instead of numeric values
- Fixed
- Tested successfully with solid objects (box) and hollow objects (vase-shaped cylinder)
- Holes now created as native