[project] # Distribution name only — the import package stays `manna`, as does the # primary console script (see [project.scripts] for the `manna-mcp` alias and # why it exists). Bare `manna` is admin-prohibited on PyPI (name-retention # blocklist); a release request is on file with pypi/support. name = "manna-mcp" version = "0.9.0" description = "MANNA — MCP Architecture for NOIRLab, NRAO, and Additional Archives" requires-python = ">=3.12" readme = "README.md" license = { text = "MIT" } authors = [{ name = "NSF-Simons CosmicAI Institute" }] maintainers = [{ name = "Dan Gause", email = "danpgause@gmail.com" }] keywords = [ "mcp", "model-context-protocol", "ivoa", "virtual-observatory", "tap", "adql", "astronomy", "alma", "nrao", "noirlab", ] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Scientific/Engineering :: Astronomy", ] dependencies = [ "fastmcp>=3.0,<4.0", "pyvo>=1.8,<2.0", "astropy>=6.0", "pydantic>=2.7", "pydantic-settings>=2.5", "starlette>=0.40", "uvicorn[standard]>=0.32", "httpx>=0.27", ] [project.urls] Homepage = "https://github.com/NSF-Simons-CosmicAI-Institute/manna" Repository = "https://github.com/NSF-Simons-CosmicAI-Institute/manna" Issues = "https://github.com/NSF-Simons-CosmicAI-Institute/manna/issues" [project.scripts] manna = "manna.__main__:main" # Alias matching the distribution name. MCP clients build the launch command # from the registry record as `uvx `, and uvx resolves an # executable named after the package — `uvx manna-mcp` fails outright when the # only script is `manna`. Keep both: `manna` is the documented CLI, `manna-mcp` # is what the registry-generated invocation needs. manna-mcp = "manna.__main__:main" [dependency-groups] dev = [ "pytest>=8.3", "pytest-asyncio>=0.24", "pytest-recording>=0.13", "pytest-cov>=5.0", "vcrpy>=6.0", "inline-snapshot>=0.13", "ruff>=0.7", "pyright>=1.1.380", "pre-commit>=3.7", # Test-only: exercises the save_recipe snippet's pandas round-trip # (tests/unit/test_save_recipe.py). Docker runtime uses --no-dev, so # the image is unaffected. "pandas>=2.0", ] # Documentation site (docs/). Not needed to run the server. # uv sync --group docs # uv run sphinx-build -W --keep-going -b html docs docs/_build/html docs = [ "sphinx>=8.0", "myst-nb>=1.2", "furo>=2024.8", "sphinx-copybutton>=0.5", "sphinx-design>=0.6", # <3: 3.x eagerly imports every intersphinx-referenced module; astropy's # wcsaxes raises pytest Skipped (a BaseException) under it and the build dies. "sphinx-autodoc-typehints>=2.0,<3", ] # Agentic evaluation harness (evals/). Not needed to run the server. # uv sync --group eval eval = [ "anthropic>=0.40", "openai>=1.40", "pyyaml>=6.0", ] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/manna"] # Keep the sdist as lean as the wheel: library code + the files uv/pip need to # build it. tests/, evals/, deploy/, notebooks/, docs/ stay repo-only. [tool.hatch.build.targets.sdist] include = [ "/src/manna", "/README.md", "/LICENSE", "/pyproject.toml", ] [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"] addopts = "-ra --strict-markers" # evals/ isn't an installed package (it's a repo-root dir); put the root on sys.path so the # offline eval-harness unit tests in tests/evals/ can `import evals.*`. pythonpath = ["."] [tool.coverage.run] source = ["src/manna"] branch = true omit = [ "*/__main__.py", ] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "raise NotImplementedError", "if __name__ == .__main__.:", "if TYPE_CHECKING:", ] show_missing = true skip_covered = true [tool.pyright] # Scoped to library code for now. tests/ has ~165 Optional-subscript findings # from the in-memory MCP Client result pattern — clean those up before adding. include = ["src"] pythonVersion = "3.12" venvPath = "." venv = ".venv" # Permissive start — ratchet to "standard"/"strict" as annotations improve. typeCheckingMode = "basic" [tool.ruff] line-length = 100 target-version = "py312" [tool.ruff.lint] select = ["E", "F", "I", "B", "UP", "W"] ignore = ["E501"]