This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
MFS exposes many sources (code, docs, messages, databases, object stores) as one file-like, searchable namespace. It is a monorepo of two cooperating processes:
cli/— a thin Rust CLI (mfs). It is only an HTTP client of the server; it holds no business logic. Don't add behavior here that belongs on the server.server/python/— the Python server (mfs-server, FastAPI). It owns connectors, the ingest pipeline, the vector index, and storage, exposed over an HTTP/v1control plane. This is where almost all work happens.
The SDKs (sdks/python, sdks/typescript) are generated from
protocol/openapi.yaml. server-rs/ is an optional PyO3 acceleration wheel with a
transparent pure-Python fallback (mfs_server.common.accel) — never required.
CONTRIBUTING.md and docs/architecture.md are the long-form references; the
points below are the ones that need several files to piece together.
All Python commands run from server/python/. dev is an extra, not a
dependency-group — uv run pytest fails (no pytest); always use --extra dev:
uv sync # default deps (local ONNX, Milvus Lite, SQLite)
uv run --extra dev pytest # all unit/smoke tests
uv run --extra dev pytest tests/test_engine_chunkable_e2e.py::test_chunkable_path_drains_through_pipeline # one test
uv run --extra dev ruff format --check src/ tests/ # what CI enforces
uv run --extra dev ruff check src/ tests/ # lint (loose; not gated in CI)
uv run mfs-server run # run server from checkout, binds 127.0.0.1:13619- A few tests import optional connector SDKs (e.g.
lark_oapi) and fail to collect without them;--ignorethose files oruv sync --extra all-connectors. - CI (
.github/workflows/lint.yml) enforces formatting only —ruff format --checkandcargo fmt --all -- --check. Tests are not gated, so run them yourself.
Rust:
cd cli && cargo build --release && cargo test
cargo fmt --manifest-path cli/Cargo.toml --all -- --check # CI format-checks cli
cargo fmt --manifest-path server-rs/Cargo.toml --all -- --check # ...and server-rsAll server state (config, token, SQLite metadata, Milvus Lite vectors, caches, and
the ONNX model cache) lives under one root, $MFS_HOME (default ~/.mfs). The
vector and metadata backends are selectable by environment/config (Milvus Lite vs a
remote Milvus/Zilliz; SQLite vs Postgres) — see docs/deployment.md.
Connectors → path tree. Each connector (server/python/src/mfs_server/connectors/<name>/plugin.py)
presents an external system as a path tree and classifies each object's
object_kind; it never touches the index, embeddings, or cache directly. The
contract is connectors/base.py (ConnectorPlugin): 5 abstract methods
(stat / list / fingerprint / sync / object_kind_of) plus read or
read_records, with optional overrides (grep, search, preset_for, …).
Registration is register(MyPlugin) in the connector's __init__.py plus an entry
in registry.py:load_builtin() — there is no central REGISTRY dict.
object_kind and chunk_kind are framework-fixed Literals in base.py.
Adding a value is a framework change (RFC), not a connector change.
Ingest runs two lanes (engine/, see docs/ingest-pipeline.md):
- Object Lane — each object goes producer → in-memory chunk queue → a
process-shared
EmbedConsumerthat embeds + upserts chunks to Milvus, then a finalize hook (_on_pipeline_object_indexed) writes theobjectsmetadata row. - Job Lane — cross-object work (directory summaries) that can't be done per-object.
The consumer is decoupled and shared across jobs; this is the subtle part. The core
invariant is a chunk exists in Milvus iff a committed objects row points at it
(directory summaries are the deliberate exception — they have no objects row).
Cancellation/removal and a startup GC reconcile against this invariant, so any new
finalize/cleanup path must preserve it.
Storage (storage/): metadata in SQLite or Postgres (connectors, objects,
jobs, and the queues — a queue is just a table), vectors in Milvus
(connector_uri is the partition key), an artifact cache, and a separate
transformation cache (model outputs). Shapes: docs/schema.md, docs/caching.md.
A connector's config/credentials are described in four places that must stay consistent; the code is the source of truth, the rest document it at increasing detail (skill < wizard < docs):
- Code —
connectors/<name>/plugin.py(keys it reads) + the user'sconnector.toml. - Wizard —
server/connector_schemas.py(an important-fields subset). - Skill —
skills/mfs-ingest/reference/connectors/<name>.md(short, for agents). - Docs —
docs/connectors/<name>.md(full human walkthrough).
When you touch one, grep the connector name across the other three and reconcile;
field names and enum values must match the plugin exactly. Secrets are never
plaintext in TOML — the TOML carries an env:VAR / file:/abs/path reference that
the server resolves at plugin-build time; a plugin reads credentials only through
the framework, never os.environ[...].
Versions are lockstep across mfs-cli, mfs-server, the SDKs, server-rs, and
the deployments/ image tags (Dockerfile comment + docker-compose.yml). A single
v* tag triggers three publish workflows (release.yml → CLI binaries + GitHub
Release, publish-crates.yml → crates.io, publish-pypi.yml → PyPI via Trusted
Publishing). server-rs is deliberately not published to PyPI. Merging to
main does not publish; pushing the tag does.
The bump is manual + CI-verified, not auto-written: a human edits every version
string by hand, then lint.yml's version-lockstep job is the single source of
truth for which files must agree — read that job to get the exact current list
rather than trusting a stale list here. Bump every file it checks, open a PR titled
release: cut X.Y.Z, merge, then tag the merge commit and push the tag — that push
is the only irreversible step (it publishes to PyPI/crates.io).
The next version number is not auto-inferred from feat: commit titles alone —
release-drafter only bumps minor/major off an explicit minor/breaking label a
maintainer adds by hand; routine feat: PRs default to a patch bump so 0.4.x stays
small and incremental.
skills/ holds two agent skills — mfs-ingest (register/update sources) and
mfs-find (search/browse). They are agent-agnostic: describe intent + the mfs
CLI/API, never couple to a specific agent's tools or UI.