[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", "mike>=2.1.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" "Release Notes" = "https://github.com/spkane/freecad-robust-mcp-and-more/blob/main/src/freecad_mcp/RELEASE_NOTES.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) # Use `just release::tag- ` to create release tags. # Commitizen is used for commit message validation and changelog generation.