[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "astrodynamics-mcp" version = "0.4.0" description = "Model Context Protocol server giving any LLM client authoritative astrodynamics tools — TLE/SGP4, Lambert, ground-station access, time/frame conversions, porkchop, B-plane." readme = "README.md" requires-python = ">=3.10" license = "MIT" license-files = ["LICENSE"] authors = [{ name = "Dimitrije Jankovic" }] keywords = [ "mcp", "model-context-protocol", "llm", "astrodynamics", "orbital-mechanics", "mission-analysis", "sgp4", "tle", "lambert", "ephemeris", ] classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", "Operating System :: POSIX :: Linux", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3 :: Only", "Topic :: Scientific/Engineering :: Astronomy", "Topic :: Scientific/Engineering :: Physics", "Typing :: Typed", ] dependencies = [ "mcp>=1.27", "sgp4>=2.25", "lamberthub>=1.0", "skyfield>=1.54", "astropy>=6.1", "httpx>=0.27", "pydantic>=2.6", "platformdirs>=4.0", ] [project.optional-dependencies] gmat = ["gmat-run>=0.6", "gmat-sweep>=0.6"] # Pin the spiceypy minor: a minor bump can swap the bundled CSPICE toolkit # under the numeric goldens, so allow 8.1.x patch fixes but not 8.2. spice = ["spiceypy>=8.1,<8.2"] # matplotlib backs the static-plot tools; gmat-czml backs the CZML 3D-view # tool. Both ship only behind this extra — the base install stays viz-free, # the same gate the [gmat] / [spice] extras use. Pin both minors: # matplotlib's PNG output is byte-identical only within a minor (a minor # bump can shift rendering under the deterministic-render guarantee), and # gmat-czml is a fast-moving 0.x sibling whose schema can change across a # minor. viz = ["matplotlib>=3.10,<3.11", "gmat-czml>=0.3,<0.4"] [project.scripts] astrodynamics-mcp = "astrodynamics_mcp.cli:main" [project.urls] Homepage = "https://github.com/astro-tools/astrodynamics-mcp" Repository = "https://github.com/astro-tools/astrodynamics-mcp" Issues = "https://github.com/astro-tools/astrodynamics-mcp/issues" Documentation = "https://astro-tools.github.io/astrodynamics-mcp/" Changelog = "https://github.com/astro-tools/astrodynamics-mcp/blob/main/CHANGELOG.md" [dependency-groups] dev = [ "pytest>=8.0", "pytest-asyncio>=0.23", "pytest-cov>=5.0", "pytest-xdist>=3.6", "ruff>=0.6", "mypy>=1.11", # Pin to the 0.3.x line — the version that's actually published on PyPI. "inspect-ai>=0.3.200", # eval/prompts/*.yaml loader. "pyyaml>=6.0", "types-pyyaml>=6.0", "openai>=2.38.0", ] docs = [ "mkdocs-material>=9.5", "mkdocstrings[python]>=0.26", ] [tool.hatch.build.targets.wheel] packages = ["src/astrodynamics_mcp"] [tool.ruff] line-length = 100 target-version = "py310" src = ["src", "tests", "eval", "examples"] # Bundle source under packaging/ holds templated files (e.g. `{{VERSION}}` in # pyproject.toml) that aren't valid until the release workflow substitutes them. extend-exclude = ["packaging"] [tool.ruff.lint] select = [ "E", "W", # pycodestyle "F", # pyflakes "I", # isort "B", # flake8-bugbear "UP", # pyupgrade "SIM", # flake8-simplify "RUF", # ruff-specific ] [tool.mypy] python_version = "3.10" strict = true files = ["src/astrodynamics_mcp", "tests", "eval", "examples"] # sgp4 ships no py.typed marker; tools/propagation.py uses the Satrec surface only. [[tool.mypy.overrides]] module = ["sgp4", "sgp4.*"] ignore_missing_imports = true # lamberthub ships no py.typed marker; tools/lambert.py treats the solver surface as Any. [[tool.mypy.overrides]] module = ["lamberthub", "lamberthub.*"] ignore_missing_imports = true # skyfield ships no py.typed marker; tools/access.py uses the API surface (load, Topos, EarthSatellite). [[tool.mypy.overrides]] module = ["skyfield", "skyfield.*"] ignore_missing_imports = true # inspect-ai is a dev-only eval-suite runner; eval/ scoring imports it lazily. [[tool.mypy.overrides]] module = ["inspect_ai", "inspect_ai.*"] ignore_missing_imports = true # gmat-run ships with the [gmat] extra only; the unit-test environment doesn't # install it. The tools/gmat.py guard tolerates the missing module, so mypy # should not flag the import either. [[tool.mypy.overrides]] module = ["gmat_run", "gmat_run.*"] ignore_missing_imports = true # gmat-sweep ships with the [gmat] extra alongside gmat-run; same story — # the tools/gmat.py registration is gated on the import, so mypy should # treat the unresolved module as opaque rather than failing strict mode. [[tool.mypy.overrides]] module = ["gmat_sweep", "gmat_sweep.*"] ignore_missing_imports = true # pandas is pulled in transitively by gmat-sweep and only used inside the # tools/gmat.py sweep body. pandas-stubs is heavy and not currently a dep; # treat the surface we touch (DataFrame construction, column access, NumPy # round-trips) as Any to keep the dependency surface lean. [[tool.mypy.overrides]] module = ["pandas", "pandas.*"] ignore_missing_imports = true # spiceypy ships with the [spice] extra only and carries no usable type stubs; # the unit-test environment doesn't install it. The tools/spice.py guard # tolerates the missing module, so mypy should treat the import as opaque # rather than failing strict mode. [[tool.mypy.overrides]] module = ["spiceypy", "spiceypy.*"] ignore_missing_imports = true # astropy ships partial type info but the surface we touch (utils.iers, # the Bulletin A table indexing pattern) is not annotated. Treat the # imports we use as Any inside the small surfaces in data/iers.py. [[tool.mypy.overrides]] module = ["astropy", "astropy.*"] ignore_missing_imports = true follow_imports = "skip" # matplotlib ships with the [viz] extra only; the unit-test environment does # not install it, and the deterministic-render helper imports it lazily behind # the [viz] import guard. Treat the surface we touch (pyplot, Figure.savefig) # as opaque rather than failing strict mode — mirrors the astropy override. [[tool.mypy.overrides]] module = ["matplotlib", "matplotlib.*"] ignore_missing_imports = true follow_imports = "skip" # gmat-czml ships with the [viz] extra only; the CZML tool slot is gated on its # import, the way the [gmat] / [spice] slots are gated on theirs. The test # environment doesn't install it, so treat the unresolved module as opaque. [[tool.mypy.overrides]] module = ["gmat_czml", "gmat_czml.*"] ignore_missing_imports = true [tool.pytest.ini_options] minversion = "8.0" testpaths = ["tests"] # eval/ is a sibling of src/; make it importable from tests/ via `from eval.* import ...`. pythonpath = ["."] # Default to unit tests only. Integration tests (HTTP-transport round-trips, live data-source calls) opt in with `-m integration`. addopts = "-ra --strict-markers --strict-config -m 'not integration'" markers = [ "integration: tests that require live external data sources or a real HTTP transport (deselect with -m 'not integration')", "gmat_installed: tests that require a working GMAT install + the [gmat] extra (gmat-run / gmat-sweep); auto-skipped when gmat_run is not importable", ] asyncio_mode = "auto" [tool.coverage.run] source = ["src/astrodynamics_mcp"] branch = true [tool.coverage.report] show_missing = true exclude_also = [ "if TYPE_CHECKING:", "raise NotImplementedError", "@overload", ]