* fix: lots of fixes and name refactoring * feat: Add workbench preferences * fix: MCP bridge status widget and just command fixes * fix(tests): Use the correct mesa-glx package * fix(ci): Add fontconfig to GUI test dependencies FreeCAD GUI was failing to start with: "Fontconfig error: Cannot load default config file: No such file" Added fontconfig and fonts-dejavu-core packages to the GUI test job dependencies to resolve the font configuration issue. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(addon): Extract path utilities into shared module Create path_utils.py module that consolidates duplicated path-finding logic from commands.py and InitGui.py: - get_addon_path(): Find addon directory with caching and fallbacks - get_icon_path(): Get full path to an icon file - get_icons_dir(): Get path to icons directory - get_workbench_icon(): Get path to workbench main icon This removes ~100 lines of duplicated code while preserving the same behavior including _addon_path_cache and all fallback methods. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(addon): Prevent stale plugin state on startup failure The StartMCPBridgeCommand.Activated method could leave _mcp_plugin in a partially initialized state if FreecadMCPPlugin.start() failed after the plugin was instantiated. Changes: - Create plugin in a local variable first - Only assign to _mcp_plugin after start() succeeds - Explicitly clear _mcp_plugin and _running_config in exception handlers to ensure clean state for subsequent retry attempts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Lot of broad improvements * fix(ci): Use blocking headless_server.py for GUI tests The GUI test was using startup_bridge.py which is non-blocking (designed for interactive use). For CI, even in GUI mode, we need the blocking headless_server.py that calls run_forever() to keep FreeCAD running. GUI features are still available since we use the 'freecad' executable instead of 'freecadcmd'. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(addon): Rename headless_server.py to blocking_bridge.py The old name was misleading because: - It works with both GUI (freecad) and headless (freecadcmd) modes - The key characteristic is that it BLOCKS with run_forever() New naming convention clarifies the difference: - blocking_bridge.py: Starts bridge and blocks (for CI, servers) - startup_bridge.py: Starts bridge and returns (for interactive GUI) Updated all references across: - GitHub workflow (macro-test.yaml) - Just commands (freecad.just) - Unit tests (test_addon_structure.py) - Documentation (5 files) - CLAUDE.md Also improved the script to detect GUI mode dynamically using FreeCAD.GuiUp and display the appropriate status message. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(just): Remove erroneous rm of startup_bridge.py on error The startup script is now a permanent source file in the repository, not a generated temporary file. The rm -f would have deleted source code if FreeCAD wasn't found. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: General improvements * fix: Lots of general fixes and only stable to PyPi * fix: small cleanup * fix: Small fixes and hopefully fixes the GUI tests * fix: Add proper library paths for FreeCAD GUI in CI - Create wrapper scripts instead of symlinks for AppImage binaries - Set LD_LIBRARY_PATH, QT_PLUGIN_PATH for GUI mode - Add diagnostic output to identify startup failures * fix: Use apprun for GUI tests in CI * fix: Improving Xvfb tests * fix: GUI tests worlk * chore: remove invalid --no-splash comments * fix: ARM64 architecture support and other fixes * fix: cleanup * test: just commands test suite * test: improve just command tests * fix: more general improvements * fix: more cleanup * fix: more updates * fix: small tweaks --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
341 lines
10 KiB
TOML
341 lines
10 KiB
TOML
[build-system]
|
|
requires = ["hatchling", "hatch-vcs"]
|
|
build-backend = "hatchling.build"
|
|
|
|
[project]
|
|
name = "freecad-robust-mcp"
|
|
dynamic = ["version"]
|
|
description = "Robust MCP Server for FreeCAD integration with Claude Code and other AI assistants"
|
|
readme = "README.md"
|
|
license = "MIT"
|
|
requires-python = ">=3.11"
|
|
authors = [
|
|
{ name = "Sean P. Kane", email = "spkane@gmail.com" }
|
|
]
|
|
maintainers = [
|
|
{ name = "Sean P. Kane", email = "spkane@gmail.com" }
|
|
]
|
|
keywords = [
|
|
"freecad",
|
|
"mcp",
|
|
"model-context-protocol",
|
|
"claude",
|
|
"claude-code",
|
|
"ai",
|
|
"llm",
|
|
"cad",
|
|
"3d-modeling",
|
|
"3d-printing",
|
|
"parametric-design",
|
|
"automation",
|
|
]
|
|
classifiers = [
|
|
"Development Status :: 4 - Beta",
|
|
"Environment :: Console",
|
|
"Framework :: AsyncIO",
|
|
"Intended Audience :: Developers",
|
|
"Intended Audience :: Manufacturing",
|
|
"Intended Audience :: Science/Research",
|
|
"License :: OSI Approved :: MIT License",
|
|
"Natural Language :: English",
|
|
"Operating System :: OS Independent",
|
|
"Operating System :: MacOS",
|
|
"Operating System :: POSIX :: Linux",
|
|
"Operating System :: Microsoft :: Windows",
|
|
"Programming Language :: Python :: 3",
|
|
"Programming Language :: Python :: 3.11",
|
|
"Programming Language :: Python :: 3.12",
|
|
"Programming Language :: Python :: 3.13",
|
|
"Programming Language :: Python :: 3 :: Only",
|
|
"Topic :: Scientific/Engineering",
|
|
"Topic :: Scientific/Engineering :: Visualization",
|
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
"Typing :: Typed",
|
|
]
|
|
|
|
dependencies = [
|
|
"mcp>=1.25.0",
|
|
"pydantic>=2.10.0",
|
|
"pydantic-settings>=2.7.0",
|
|
]
|
|
|
|
[project.optional-dependencies]
|
|
dev = [
|
|
# Testing
|
|
"pytest>=8.3.0",
|
|
"pytest-asyncio>=0.25.0",
|
|
"pytest-cov>=6.0.0",
|
|
"pytest-timeout>=2.3.0",
|
|
"pytest-watch>=4.2.0",
|
|
# Linting and formatting
|
|
"mypy>=1.14.0",
|
|
"ruff>=0.8.0",
|
|
# Security scanning
|
|
"bandit[toml]>=1.8.0",
|
|
"safety>=3.2.0",
|
|
"detect-secrets>=1.5.0",
|
|
# Pre-commit and commit conventions
|
|
"pre-commit>=4.0.0",
|
|
"commitizen>=4.1.0",
|
|
# Documentation
|
|
"mkdocs>=1.6.0",
|
|
"mkdocs-material>=9.7.1",
|
|
"mkdocstrings[python]>=0.27.0",
|
|
"mkdocs-git-revision-date-localized-plugin>=1.2.0",
|
|
"mkdocs-minify-plugin>=0.8.0",
|
|
"mkdocs-redirects>=1.2.0",
|
|
"mkdocs-glightbox>=0.4.0",
|
|
"mkdocs-macros-plugin>=1.0.0",
|
|
"md-toc>=9.0.0",
|
|
"codespell>=2.3.0",
|
|
# Publishing
|
|
"twine>=6.0.0",
|
|
"build>=1.2.0",
|
|
]
|
|
|
|
[project.scripts]
|
|
freecad-mcp = "freecad_mcp.server:main"
|
|
|
|
[project.urls]
|
|
Homepage = "https://github.com/spkane/freecad-robust-mcp-and-more"
|
|
Documentation = "https://github.com/spkane/freecad-robust-mcp-and-more#readme"
|
|
Repository = "https://github.com/spkane/freecad-robust-mcp-and-more.git"
|
|
Issues = "https://github.com/spkane/freecad-robust-mcp-and-more/issues"
|
|
Changelog = "https://github.com/spkane/freecad-robust-mcp-and-more/blob/main/CHANGELOG.md"
|
|
"Docker Hub" = "https://hub.docker.com/r/spkane/freecad-robust-mcp"
|
|
|
|
[tool.hatch.version]
|
|
source = "vcs"
|
|
# Use git tags for versioning: v0.1.0 -> 0.1.0
|
|
# For untagged commits: 0.1.0.dev5+g1a2b3c4
|
|
|
|
[tool.hatch.version.raw-options]
|
|
# Fall back version when not in a git repo (e.g., source tarball)
|
|
version_scheme = "guess-next-dev"
|
|
local_scheme = "node-and-date"
|
|
|
|
[tool.hatch.build.hooks.vcs]
|
|
version-file = "src/freecad_mcp/_version.py"
|
|
|
|
[tool.hatch.build.targets.wheel]
|
|
packages = ["src/freecad_mcp"]
|
|
|
|
[tool.ruff]
|
|
target-version = "py311"
|
|
line-length = 88
|
|
src = ["src", "tests", "macros"]
|
|
extend-include = ["*.FCMacro"]
|
|
# Exclude auto-generated files (hatch-vcs version file)
|
|
exclude = ["src/freecad_mcp/_version.py"]
|
|
|
|
[tool.ruff.lint]
|
|
select = [
|
|
"E", # pycodestyle errors
|
|
"W", # pycodestyle warnings
|
|
"F", # Pyflakes
|
|
"I", # isort
|
|
"B", # flake8-bugbear
|
|
"C4", # flake8-comprehensions
|
|
"UP", # pyupgrade
|
|
"ARG", # flake8-unused-arguments
|
|
"SIM", # flake8-simplify
|
|
"TCH", # flake8-type-checking
|
|
"PTH", # flake8-use-pathlib
|
|
"ERA", # eradicate (commented-out code)
|
|
"PL", # Pylint
|
|
"RUF", # Ruff-specific rules
|
|
"S", # flake8-bandit (security)
|
|
"ANN", # flake8-annotations
|
|
"ASYNC", # flake8-async
|
|
"D", # pydocstyle
|
|
]
|
|
ignore = [
|
|
"D104", # Missing docstring in public package - __init__.py often just imports
|
|
"D107", # Missing docstring in __init__ - class docstring covers this
|
|
"ANN401", # Allow Any in type annotations - FreeCAD types are dynamic
|
|
"ANN001", # Allow missing type annotations - FastMCP doesn't export types
|
|
"PLR0913",# Too many arguments - CAD APIs have many params
|
|
"PLR0915",# Too many statements - registration functions are long
|
|
"PLC0415",# Allow imports in functions - FreeCAD must be imported lazily
|
|
"PLR2004",# Allow magic numbers in comparisons - CAD dimensions
|
|
"PLW0603",# Allow global statement - bridge lifecycle management
|
|
"E501", # Line too long - embedded Python code strings
|
|
"RUF022", # Don't sort __all__ - prefer logical grouping
|
|
"S110", # Allow try-except-pass for optional operations
|
|
"ARG002", # Allow unused arguments - needed for interface compliance
|
|
]
|
|
|
|
[tool.ruff.lint.pydocstyle]
|
|
convention = "google"
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
"tests/**/*.py" = [
|
|
"S101", # Allow asserts in tests
|
|
"S108", # Allow /tmp in tests
|
|
"ANN", # Don't require annotations in tests
|
|
"D", # Don't require docstrings in tests
|
|
"PLR2004",# Allow magic numbers in tests
|
|
"PTH", # Allow os.path in tests - matches embedded FreeCAD code style
|
|
"SIM105", # Allow try-except-pass in tests
|
|
"SIM117", # Allow nested with statements
|
|
"B017", # Allow blind exception catch in tests
|
|
]
|
|
# FreeCAD macros have relaxed rules - they are standalone GUI scripts
|
|
# But still require essential docstrings (module, class, public function)
|
|
"**/*.FCMacro" = [
|
|
"ANN", # Don't require type annotations - FreeCAD types are complex
|
|
"D104", # Missing docstring in public package - N/A for macros
|
|
"D105", # Missing docstring in magic method - __init__ etc don't need them
|
|
"D107", # Missing docstring in __init__ - class docstring covers this
|
|
"D203", # Blank line before class docstring (conflicts with D211)
|
|
"D212", # Multi-line docstring start (conflicts with D213)
|
|
"PLR0911",# Allow many return statements - GUI code paths vary
|
|
"PLR0912",# Allow many branches - GUI code is complex
|
|
"PLR0913",# Allow many arguments - CAD APIs have many params
|
|
"PLR0915",# Allow many statements - macros are self-contained
|
|
"PTH", # Allow os.path - better FreeCAD compatibility than pathlib
|
|
"SIM102", # Allow nested if statements - improves readability
|
|
"SIM103", # Allow return after if-else for clarity
|
|
"SIM108", # Allow if-else over ternary for readability
|
|
"S112", # Allow try-except-continue - used for geometry iteration
|
|
"F841", # Allow unused variables - Qt signal connections
|
|
"UP008", # Allow super(Class, self) - PySide compatibility
|
|
]
|
|
|
|
[tool.mypy]
|
|
python_version = "3.11"
|
|
# Not using strict mode due to FastMCP lacking type stubs
|
|
warn_return_any = false
|
|
warn_unused_configs = true
|
|
disallow_untyped_defs = false
|
|
disallow_incomplete_defs = false
|
|
check_untyped_defs = true
|
|
disallow_untyped_decorators = false
|
|
no_implicit_optional = true
|
|
warn_redundant_casts = true
|
|
warn_unused_ignores = false
|
|
warn_no_return = true
|
|
follow_imports = "silent"
|
|
ignore_missing_imports = true
|
|
|
|
[[tool.mypy.overrides]]
|
|
module = "tests.*"
|
|
disallow_untyped_defs = false
|
|
disallow_incomplete_defs = false
|
|
|
|
[[tool.mypy.overrides]]
|
|
module = [
|
|
"freecad_mcp.tools",
|
|
"freecad_mcp.tools.*",
|
|
]
|
|
disallow_untyped_decorators = false
|
|
disallow_untyped_defs = false
|
|
warn_return_any = false
|
|
|
|
[[tool.mypy.overrides]]
|
|
module = [
|
|
"freecad_mcp.resources",
|
|
"freecad_mcp.resources.*",
|
|
]
|
|
disallow_untyped_decorators = false
|
|
disallow_untyped_defs = false
|
|
warn_return_any = false
|
|
|
|
[[tool.mypy.overrides]]
|
|
module = [
|
|
"freecad_mcp.prompts",
|
|
"freecad_mcp.prompts.*",
|
|
]
|
|
disallow_untyped_decorators = false
|
|
disallow_untyped_defs = false
|
|
|
|
[[tool.mypy.overrides]]
|
|
module = [
|
|
"freecad_mcp.bridge",
|
|
"freecad_mcp.bridge.*",
|
|
]
|
|
warn_return_any = false
|
|
|
|
[[tool.mypy.overrides]]
|
|
module = [
|
|
"freecad_mcp.freecad_plugin",
|
|
"freecad_mcp.freecad_plugin.*",
|
|
]
|
|
warn_return_any = false
|
|
disallow_untyped_defs = false
|
|
|
|
[[tool.mypy.overrides]]
|
|
module = "freecad_mcp.server"
|
|
disallow_untyped_decorators = false
|
|
disallow_untyped_defs = false
|
|
|
|
[tool.pytest.ini_options]
|
|
testpaths = ["tests"]
|
|
asyncio_mode = "auto"
|
|
asyncio_default_fixture_loop_scope = "function"
|
|
addopts = [
|
|
"-ra",
|
|
"-q",
|
|
"--strict-markers",
|
|
"--strict-config",
|
|
]
|
|
markers = [
|
|
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
|
"integration: marks integration tests requiring FreeCAD",
|
|
"gui: marks tests requiring FreeCAD GUI mode (not headless)",
|
|
]
|
|
|
|
[tool.coverage.run]
|
|
source = ["src/freecad_mcp"]
|
|
branch = true
|
|
parallel = true
|
|
# Exclude bridge modules from unit test coverage - they require a running
|
|
# FreeCAD instance. These modules are exercised by integration tests which
|
|
# run separately (just testing::integration). To verify bridge coverage:
|
|
# uv run pytest tests/integration --cov=freecad_mcp.bridge --cov-report=term-missing
|
|
omit = [
|
|
"src/freecad_mcp/bridge/embedded.py",
|
|
"src/freecad_mcp/bridge/socket.py",
|
|
"src/freecad_mcp/bridge/xmlrpc.py",
|
|
]
|
|
|
|
[tool.coverage.report]
|
|
exclude_lines = [
|
|
"pragma: no cover",
|
|
"def __repr__",
|
|
"raise AssertionError",
|
|
"raise NotImplementedError",
|
|
"if TYPE_CHECKING:",
|
|
"if __name__ == .__main__.:",
|
|
"@abstractmethod",
|
|
]
|
|
show_missing = true
|
|
fail_under = 80
|
|
|
|
[tool.bandit]
|
|
exclude_dirs = ["tests", "docs"]
|
|
skips = [
|
|
"B101", # Allow asserts
|
|
"B102", # exec is required for FreeCAD Python execution
|
|
"B104", # Binding to 0.0.0.0 is intentional for container/network access
|
|
"B110", # try-except-pass used for optional cleanup
|
|
"B112", # try-except-continue used for iterating over faces safely
|
|
"B411", # XML-RPC is required for FreeCAD compatibility
|
|
]
|
|
|
|
[tool.codespell]
|
|
skip = ".git,*.lock,*.toml"
|
|
ignore-words-list = "crate"
|
|
|
|
[tool.commitizen]
|
|
name = "cz_conventional_commits"
|
|
version_scheme = "semver2"
|
|
major_version_zero = true
|
|
# Note: This project uses component-specific tags for releases:
|
|
# - robust-mcp-server-vX.Y.Z (MCP server: PyPI, Docker, GitHub release)
|
|
# - robust-mcp-workbench-vX.Y.Z (FreeCAD workbench)
|
|
# - macro-cut-object-for-magnets-vX.Y.Z
|
|
# - macro-multi-export-vX.Y.Z
|
|
# Use `just release::tag-<component> <version>` to create release tags.
|
|
# Commitizen is used for commit message validation and changelog generation.
|