[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "distillery-mcp" version = "0.8.0" description = "Persistent shared memory for Claude Code — MCP server with DuckDB vector search, semantic dedup, and ambient intelligence for team knowledge" readme = "README.md" requires-python = ">=3.11" authors = [ {name = "Distillery Contributors"} ] license = {text = "Apache-2.0"} keywords = [ "claude-code", "mcp", "mcp-server", "fastmcp", "agent-memory", "long-term-memory", "llm-wiki", "knowledge-base", "second-brain", "team-knowledge", "embeddings", "semantic-search", "vector-search", "duckdb", "hnsw", "ambient-intelligence", ] classifiers = [ "Development Status :: 3 - Alpha", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "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 :: Software Development :: Libraries :: Python Modules", ] dependencies = [ "duckdb~=1.5.0", "pyyaml>=6.0,<7.0", "httpx>=0.24.0,<1.0", "fastmcp>=2.12.0,<5.0", "defusedxml>=0.7.0,<1.0", # Transitive via fastmcp/starlette; pin floor for GHSA-pp6c-gr5w-3c5g # and GHSA-5rvq-cxj2-64vf (fixed in 0.0.30). "python-multipart>=0.0.30", ] [project.optional-dependencies] dev = [ "pytest>=7.0", "pytest-asyncio>=0.21.0", "mypy>=1.0", "ruff>=0.1.0", "pytest-cov>=4.0", "pytest-timeout>=2.1.0", "types-PyYAML>=6.0", "types-defusedxml>=0.7.0", "pytest-httpx>=0.30.0", # Bench tooling: SHA-pinned dataset downloads from HuggingFace. # Dev-only — never required by the runtime install. "huggingface_hub>=0.24,<1.0", ] docs = [ "mkdocs-material>=9.5", ] eval = [] graph = [ "networkx>=3.2,<4.0", ] ragas = [ "ragas>=0.2.0", "datasets>=2.14.0", ] fastembed = [ # Pinned exactly for benchmark reproducibility — anyone reruns the bench # offline and gets byte-identical embeddings. Bump only with a SHA panel # update in bench/results/. "fastembed==0.9.1", ] # Observability. Optional: the server runs unchanged without it, and even # when installed it stays dark until LOGFIRE_TOKEN / OTEL_EXPORTER_OTLP_ENDPOINT # is set. Logfire is the OTLP SDK + UI; the otlp exporter enables dual-export # to a self-hosted collector (e.g. the GCP Cloud Run otel-collector). otel = [ # Upper-bounded: the secret-scrubbing defaults and the env-driven OTLP # export behaviour observability.py relies on are logfire internals that # can shift across majors, so a major bump is a deliberate, reviewed change. "logfire[asgi,httpx,system-metrics]>=2.0,<6", "opentelemetry-exporter-otlp>=1.20,<2", ] [project.scripts] distillery = "distillery.cli:main" distillery-mcp = "distillery.mcp.__main__:main" [tool.hatch.build.targets.wheel] packages = ["src/distillery"] [tool.pytest.ini_options] testpaths = ["tests"] asyncio_mode = "auto" addopts = ["-v", "--strict-markers", "--tb=short"] # Per-test wall-clock cap so a single deadlocked test can never hang CI for the # 6-hour job default. 120s is far above the slowest real test (~seconds) yet # well below a hang. ``thread`` method reliably fires even when the hang is # inside a C extension (DuckDB) that ignores SIGALRM, and dumps every thread's # stack on expiry so the culprit is identifiable. timeout = 120 timeout_method = "thread" markers = [ "unit: unit tests", "integration: integration tests", "eval: Claude-powered skill evaluation tests (requires claude CLI)", "bench: long-running retrieval-quality benchmarks (LongMemEval, etc.) — opt-in", "live_probe: opt out of the autouse feed-probe stub (tests exercise real _probe_url)", ] [tool.mypy] python_version = "3.11" strict = true [[tool.mypy.overrides]] module = "tests.*" disallow_untyped_defs = false disallow_incomplete_defs = false [[tool.mypy.overrides]] # FastMCP's @server.tool decorator doesn't preserve function signatures in its # type stubs (returns FunctionTool, not the original callable type). Suppress # the resulting untyped-decorator noise for the MCP server module only. module = "distillery.mcp.server" disable_error_code = ["untyped-decorator"] [[tool.mypy.overrides]] # The eval module has optional dependencies (anthropic, ragas, datasets) that # are not installed in the base dev environment. Suppress import-not-found for # the eval sub-package and its optional third-party dependencies. module = [ "distillery.eval.*", "ragas", "ragas.*", "datasets", "datasets.*", ] ignore_missing_imports = true [[tool.mypy.overrides]] # fastembed is an optional dependency exposed via the [fastembed] dep group. # The provider module imports it lazily, but mypy needs to resolve the type # import in the TYPE_CHECKING block — silence missing-import noise when the # package is not installed in the dev environment. module = ["fastembed", "fastembed.*"] ignore_missing_imports = true [[tool.mypy.overrides]] # NetworkX is gated behind the optional [graph] extra and ships without # bundled stubs in the supported version range. Treat its imports as # untyped — the graph package keeps a typed surface around them. module = [ "networkx", "networkx.*", ] ignore_missing_imports = true [[tool.mypy.overrides]] # logfire + opentelemetry are gated behind the optional [otel] extra and are # not installed in the base dev/CI environment. observability.py guards every # import; treat them as untyped so mypy --strict passes without the extra. module = [ "logfire", "logfire.*", "opentelemetry.*", ] ignore_missing_imports = true [tool.ruff] line-length = 100 target-version = "py311" [tool.ruff.lint] select = ["E", "W", "F", "I", "N", "UP", "B", "C4", "SIM"] ignore = ["E501"] [tool.ruff.lint.isort] known-first-party = ["distillery"] [tool.coverage.run] source = ["src/distillery"] omit = [ "src/distillery/mcp/__main__.py", # Telemetry wiring: its active branches require the optional [otel] extra, # which CI does not install, so they can't be covered (like __main__.py). "src/distillery/observability.py", "src/distillery/mcp/middleware.py", "src/distillery/mcp/server.py", "src/distillery/store/__main__.py", "src/distillery/eval/runner.py", ] [tool.coverage.report] fail_under = 80 show_missing = true