[project] name = "data-aggregator-mcp" version = "0.75.4" description = "Research-data acquisition MCP — find and fetch datasets across archives, omics registries, and literature" readme = "README.md" license = "MIT" license-files = ["LICENSE"] authors = [{ name = "Jaret Arnold" }] requires-python = ">=3.11" classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Science/Research", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: Scientific/Engineering :: Bio-Informatics", ] keywords = [ "mcp", "model-context-protocol", "datasets", "zenodo", "datacite", "ncbi", "sra", "geo", "bioinformatics", "research-data", ] dependencies = [ # Floor is a security floor, not a feature floor: <1.28.1 carries # GHSA-vj7q-gjh5-988w (WebSocket transport skips Host/Origin validation), # GHSA-jpw9-pfvf-9f58 (HTTP transports serve session requests without # verifying the authenticated principal), and GHSA-hvrp-rf83-w775 (task # handlers leak across clients). The first two are directly reachable from # the HTTP transport this package now ships. # # Every 2.x release is above the patched versions of all three (1.27.2 and # 1.28.1), so the floor is the major itself. # # The floor is 2 because this server is built on the 2.x low-level API: # handlers are `on_*` constructor arguments of `Server` taking # `(ServerRequestContext, params)` and returning the typed `*Result`. The # 1.x decorator registration (`@server.list_tools()` etc.) does not exist in # 2.x and the 2.x constructor arguments do not exist in 1.x, so the module # imports under exactly one major. The `declared-deps` CI job installs from # this range alone (not uv.lock) and imports the server module, so widening # the range past what the code loads under fails there, not at a user's # first `pip install`. "mcp>=2,<3", "httpx>=0.27", "pydantic>=2.6", "defusedxml>=0.7", ] [project.optional-dependencies] dev = [ "pytest>=8.0", "pytest-asyncio>=0.23", "pytest-httpx>=0.30", "pytest-cov>=5.0", "ruff>=0.3", "pyyaml>=6.0", "jsonschema>=4.20", "mypy>=2.1.0", "pyld>=2.0", "roc-validator>=0.12", ] operate = [ "duckdb>=1.1", "pyarrow>=17", "fsspec>=2024.6", "aiohttp>=3.9", ] [project.scripts] data-aggregator-mcp = "data_aggregator_mcp.server:main" [project.urls] Homepage = "https://github.com/musharna/data-aggregator-mcp" Repository = "https://github.com/musharna/data-aggregator-mcp" Issues = "https://github.com/musharna/data-aggregator-mcp/issues" Changelog = "https://github.com/musharna/data-aggregator-mcp/blob/main/CHANGELOG.md" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/data_aggregator_mcp"] [tool.pytest.ini_options] testpaths = ["tests"] asyncio_mode = "auto" [tool.mypy] python_version = "3.11" ignore_missing_imports = true warn_unused_ignores = true [tool.ruff] line-length = 100 target-version = "py311" [tool.ruff.lint] # E/F (default) plus import-sorting, bugbear, pyupgrade, and simplify. # E501 (line-too-long) is intentionally left to the formatter — `ruff format` # wraps code; E501 would otherwise only nag about long strings/comments/URLs. select = ["E", "F", "I", "B", "UP", "SIM"] ignore = ["E501"] [tool.mutmut] source_paths = ["src"] also_copy = [".editorconfig", ".github", ".gitignore", ".mcp.json", ".pre-commit-config.yaml", ".zenodo.json", "CHANGELOG.md", "CITATION.cff", "CLAUDE.md", "CODE_OF_CONDUCT.md", "CONTRIBUTING.md", "LICENSE", "PUBLISH.md", "README.md", "SECURITY.md", "assets", "benchmarks", "docs", "examples", "glama.json", "scripts", "server.json"] # test_entrypoint_smoke drives the stdio server as a subprocess, which the in-process mutmut output capture closes # test_search_cli_real_subprocess is a live Zenodo call with a 90 s timeout that flaked two nights in the baseline # test_every_request_json_call_names_expect_itself scans src/, which under mutmut is the rewritten copy whose mutants drop expect= # test_only_the_http_layer_parses_a_json_body scans src/ by function name, which mutmut's copy renames (x___mutmut_) pytest_add_cli_args = ["--ignore=tests/test_entrypoint_smoke.py", "--deselect", "tests/test_search_cli.py::test_search_cli_real_subprocess", "--deselect", "tests/test_expect_required.py::test_every_request_json_call_names_expect_itself", "--deselect", "tests/test_expect_required.py::test_only_the_http_layer_parses_a_json_body"]