{
  "markdown": "# LAIN-mcp\n\n[![SafeSkill 88/100](https://img.shields.io/badge/SafeSkill-88%2F100_Passes%20with%20Notes-yellow)](https://safeskill.dev/scan/spuentesp-lain)\nLAIN builds a map of how all the code in your project connects — what calls what, what depends on what, which files tend to change together. Then it lets your AI coding assistant ask questions about that map. So instead of the AI just looking at one file and guessing, it can ask \"if I change this function, what else breaks?\" and get a real answer. It plugs into any AI agent that supports MCP and runs in the background while you work.\n\n## See it run\n\n![LAIN Command Center demo](docs/screenshots/spa-demo.gif)\n\n[Download MP4](docs/screenshots/spa-demo.mp4) · [Download WebM](docs/screenshots/spa-demo.webm)\n\n- Federation overview, repo health, and the call graph — answered in well under a second.\n- Edit `repos.yaml` from the Repos tab; the server hot-reloads without dropping a request.\n- Try any MCP tool straight from the Tools tab; *Copy as cURL* hands the agent a shareable snippet.\n\n> [!NOTE]\n> The hero GIF is large (~4 MB) so it autoplays inline on GitHub. For sharper playback, the [MP4](docs/screenshots/spa-demo.mp4) and [WebM](docs/screenshots/spa-demo.webm) siblings sit alongside it in `docs/screenshots/`.\n\n## How it fits together\n\n```mermaid\nflowchart LR\n    A[\"AI Agent<br/>(Claude Code / Kimi / Agy / Codex)\"] -->|MCP<br/>JSON-RPC| L[\"lain\"]\n    L -->|reads| FS[\".lain/<br/>graph.bin\"]\n    L -->|runs| ENG[\"LSP / NLP / git<br/>engines\"]\n    L -->|answers| T[\"MCP tools<br/>(get_blast_radius,<br/>explain_symbol, …)\"]\n    A --> T\n```\n\n`lain` is a long-running MCP server that indexes your code once and\nkeeps it fresh while you work. The agent speaks MCP (JSON-RPC over\nstdio or HTTP); the server answers structural questions across one\nrepo (`lain mcp`) or many repos (`lain server --config repos.yaml`).\n\n## Documentation\n\n| Doc | What's in it |\n|-----|--------------|\n| **[`docs/QUICKSTART.md`](docs/QUICKSTART.md)** | Five-minute tour |\n| **[`docs/USER_MANUAL.md`](docs/USER_MANUAL.md)** | Operator + agent manual |\n| **[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)** | How and why — design rationale |\n| **[`docs/TECHNICAL.md`](docs/TECHNICAL.md)** | Source-level internals |\n| **[`docs/FEDERATION.md`](docs/FEDERATION.md)** | Multi-repo operating guide |\n| **[`docs/REPOS_YAML.md`](docs/REPOS_YAML.md)** | `repos.yaml` schema |\n| **[`docs/query-language.md`](docs/query-language.md)** | `query_graph` ops-array reference |\n| **[`docs/quickstart-tools.md`](docs/quickstart-tools.md)** | All MCP tools |\n| **[`docs/command-center.md`](docs/command-center.md)** | Command Center SPA |\n| **[`docs/hot-reload.md`](docs/hot-reload.md)** | Config hot-reload |\n| **[`docs/multiplayer.md`](docs/multiplayer.md)** | Multi-agent coordination |\n| **[`docs/hooks.md`](docs/hooks.md)** | Pre-edit hooks |\n| **[`docs/INDEX.md`](docs/INDEX.md)** | Docs index |\n\n## TL;DR — install in 30 seconds\n\n```bash\n# Install (interactive — adds `lain` to PATH)\ncurl -fsSL https://raw.githubusercontent.com/spuentesp/lain/main/install.sh | bash\n\n# Reload your shell, then verify\nsource ~/.zshrc   # or ~/.bashrc\nlain --version\n```\n\nSee [QUICKSTART.md](docs/QUICKSTART.md) for the full install matrix (Homebrew, build-from-source, non-interactive flags, ONNX model).\n\n## What is Lain?\n\nLain is a persistent code-intelligence MCP server. The headline is\n`lain server`: a long-running process that reads a `repos.yaml` config,\nindexes every registered repository (locally, by clone, or by shallow\nfetch), and answers structural questions across them through MCP\ntools. The server also serves a Command Center dashboard at `GET /` for\nhumans who want to inspect the federation, edit the config, run\nqueries, and exercise the MCP tool surface directly.\n\nThe value over LSP-only or RAG-based approaches is cross-file\nstructural reasoning: agents can ask about blast radius, transitive\ndependency traces, anchor identification, co-change correlation, and\ncontextual build failure decoration, so they reason about callers\nrather than just the failing line. Written in Rust, persists across\nsessions, and hot-reloads its `repos.yaml` / `workspaces.yaml` config\nwithout a restart.\n\n---\n\n## The commands\n\nAfter install, `lain` exposes these subcommands:\n\n| Command | Purpose |\n|---------|---------|\n| `lain server` | Start the MCP server (the headline). Reads `repos.yaml`, serves MCP tools + the Command Center dashboard. Hot-reloads the config when it changes. |\n| `lain mcp` | Single-repo MCP server on stdio. Walks up from cwd for `.git` — the stable \"drop in a clone and run\" entrypoint. No `repos.yaml` required. |\n| `lain workspaces` | Manage `workspaces.yaml`. Create, list, show, activate (`use`), forget named groups of repos. |\n| `lain repos` | Manage `repos.yaml`. Add, list, remove a repo entry. |\n| `lain query` | Run a `query_graph` ops-array against the project's persisted graph. |\n| `lain oneshot` | One-shot MCP query: boots a transient `lain mcp` server, sends a single `tools/call`, prints the result as a table, and exits. For \"just grep the symbols without keeping a server alive\". |\n| `lain init` | Scaffold a `repos.yaml` for the current directory. Walks up for `.git`, then writes a minimal config pointing at the discovered workspace. |\n| `lain ask` | Single-user LLM-assisted query (uses `semantic_search` when an embedding model is loaded; falls back to lexical heuristics via `explain_symbol`). |\n| `lain hooks` | Agent pre-edit hook entry point: `claim` / `release` files, `overlap-check` for commit-time symbol overlap, `lock` / `unlock` for the zero-daemon filesystem-fallback layer. |\n| `lain doctor` | \"One version of truth\" diagnostic. Checks binary version + git SHA, hook script presence, config/hooks dirs (reaping session files older than 30 days), presence registry, and — when `LAIN_URL`/`LAIN_SERVER_URL` is set — both server reachability **and the live MCP surface**, calling `tools/list` and failing if it errors or advertises zero tools. Exits 0 clean, 1 on a hard failure. |\n| `lain schema` | Emit the canonical tool-surface schema dump (`dump [--out PATH]` defaults to `./docs/tool-schema.json`). Pair with `make schema && git diff --exit-code docs/tool-schema.json` in CI to fail on schema drift. |\n| `scripts/demo.sh` | Capability demonstration and benchmark. Boots a real server against a synthetic repo whose call graph is known by construction, checks lain's answers against that ground truth (not merely that it answered), then benchmarks the same tools against this repo at ~3.5k nodes. `--quick` skips the build and benchmark phases; `--json FILE` writes machine-readable results; `--force-build` overrides `--quick` / `--no-build`; `--allow-stale` skips the binary-freshness check. Exits non-zero if any check fails (or if the binary is older than any source file and `--allow-stale` was not passed). |\n\nThe cut surface (`agents`, `hook`, `projects`, top-level `use`) is\ngone — those concerns are reached through the commands above. `server`\nplus the two config CLIs (`workspaces`, `repos`) cover everything the\nprior surface did, scoped to a single project directory that owns a\n`repos.yaml`.\n\nThis table is checked against `lain --help` by\n`tests/cli_surface.rs`, so it cannot drift from the binary again.\n\n---\n\n## Quick Start\n\n1. **Install** — see [QUICKSTART.md § Install](docs/QUICKSTART.md#install).\n2. **Configure** — see [QUICKSTART.md § Federation (multi-repo)](docs/QUICKSTART.md#federation-multi-repo).\n3. **Wire your agent** — see [QUICKSTART.md § Single-repo (recommended default)](docs/QUICKSTART.md#single-repo-recommended-default).\n\n---\n\n## Command Center\n\nFor a narrated tour of every tab, see [command-center.md § Tour](docs/command-center.md#tour).\n\nWhen `lain server` runs with `--transport http`, it serves the Command\nCenter dashboard at `GET /`. It's a self-contained vanilla-JS SPA that\ntalks back to the running server over the same JSON-RPC endpoint the\nMCP tools use. No separate API, no auth portal.\n\n![Command Center — Overview tab](docs/screenshots/command-center-overview.png)\n\nTabs:\n\n- **Overview** — `get_health` + `get_federation_health` in one view.\n- **Graph** — D3 force-directed graph of the active workspace.\n- **Repos** — per-repo table (id, path, health, node/edge counts).\n- **Query** — runs `query_graph` against the federation.\n- **Tools** — auto-generated MCP tool tester. Calls `tools/list`, then\n  renders a form per tool by introspecting its `inputSchema`. *Copy as\n  cURL* copies a `curl -X POST http://localhost:9999/mcp ...` snippet\n  to the clipboard.\n\n![Command Center — Repos tab](docs/screenshots/command-center-repos.png)\n\nThe status bar in the footer polls every 2 s for `get_server_status`\nand `get_reload_status` so hand-edits to `repos.yaml` /\n`workspaces.yaml` show up live.\n\nSee [`docs/command-center.md`](docs/command-center.md) for the full\nwalkthrough.\n\n---\n\n## Hot Reload\n\n`lain server` watches `repos.yaml` and `workspaces.yaml` and rebuilds\nits federation state when they change — no restart needed. Both the\n`notify` watcher (for hand-edits) and the CLI (via `lain repos add`\nor `lain workspaces create`) trigger the same `ReloadBus`.\n\nWhen you run `lain repos add my-repo …`, the CLI writes the YAML\natomically (write to temp file, then `rename`), then signals the\nrunning server over a Unix socket at\n`~/.local/lain/run/<repos-stem>.sock`. The server's rebuild task\ndiffs the new file against the live federation and applies add / remove\noperations against `FederatedIndex`. `get_reload_status` reports the\nstate (`idle` / `rebuilding` / `failed`); the Command Center status\nbar shows it live.\n\nSee [`docs/hot-reload.md`](docs/hot-reload.md) for the full picture\n(internals, observability, failure modes, caveats).\n\n---\n\n## Federation mode\n\nFor org-wide structural questions — \"who else uses this function?\",\n\"what depends on this service?\" — run `lain server --config\n./repos.yaml`. Federation mode exposes six MCP tools (`list_repos`,\n`get_repo_info`, `get_federation_health`, `search_org`,\n`get_cross_repo_blast_radius`,\n`get_cross_repo_blast_radius_for_repo`) that answer questions\nspanning repos. See [`docs/FEDERATION.md`](docs/FEDERATION.md) for the\nfull guide and [`docs/REPOS_YAML.md`](docs/REPOS_YAML.md) for the\nconfig schema.\n\n---\n\n## Key Features\n\n- **Federation mode** — index N repos and answer org-wide structural questions across them.\n- **Command Center** — vanilla-JS SPA at `GET /` for human inspection, config editing, query running, and MCP tool testing.\n- **Hot reload** — `repos.yaml` / `workspaces.yaml` changes apply without restarting the server.\n\n### Query Language (`query_graph`)\n\nJSON-based ops array for flexible graph traversals:\n\n```json\n{\n  \"ops\": [\n    { \"op\": \"find\", \"type\": \"Function\" },\n    { \"op\": \"connect\", \"edge\": \"Calls\", \"depth\": { \"min\": 1, \"max\": 3 } },\n    { \"op\": \"filter\", \"label\": \"test\" },\n    { \"op\": \"semantic_filter\", \"like\": \"error handling\", \"threshold\": 0.35 },\n    { \"op\": \"limit\", \"count\": 10 }\n  ]\n}\n```\n\nAvailable ops: `find`, `connect`, `filter`, `semantic_filter`, `group`,\n`sort`, `limit`.\n\n### Dependency Intelligence\n\n- **`get_call_chain`** — Shortest path between two functions.\n- **`get_blast_radius`** — Everything affected by a change.\n- **`trace_dependency`** — What a symbol depends on.\n- **`get_coupling_radar`** — Files that change together.\n\n### Architectural Analysis\n\n- **`find_anchors`** — Most-called, most-stable symbols (architectural pillars).\n- **`list_entry_points`** — Find `main()`, route handlers, app initialization.\n- **`get_context_depth`** — How far from an entry point (abstraction layers).\n- **`explore_architecture`** — High-level tree of modules and files.\n\n### Search\n\n- **`semantic_search`** — Find code by meaning, not just names. Uses local ONNX embeddings with hybrid scoring (cosine similarity + stemmed token-overlap) and shows body excerpts in the response. BGE-small-en-v1.5 is the recommended model (better than MiniLM for technical corpora); use a query prefix to enable BGE-style asymmetric retrieval.\n\n### Code Health\n\n- **`find_dead_code`** — Potentially unreachable code (filters trait defaults, common names).\n- **`suggest_refactor_targets`** — High-coupling, low-stability nodes.\n\n### Project Management\n\nA project is a directory containing `repos.yaml` (and optionally\n`workspaces.yaml`). Manage it directly with the CLI:\n\n- **`lain repos add <name> <url>`** — register a repo in `repos.yaml`.\n- **`lain repos list`** — show registered repos.\n- **`lain repos remove <name>`** — unregister a repo.\n- **`lain workspaces create <name> --members a,b,c`** — declare a named workspace.\n- **`lain workspaces list`** — show all workspaces.\n- **`lain workspaces use <name>`** — activate a workspace (writes `~/.config/lain/active_workspace`).\n- **`lain workspaces current`** — print the active workspace.\n- **`lain workspaces forget <name>`** — remove a workspace.\n\n## Where to go next\n\n- Operate `lain` for a team → [USER_MANUAL.md](docs/USER_MANUAL.md)\n- Federation operating guide → [FEDERATION.md](docs/FEDERATION.md)\n- Full MCP tool reference → [quickstart-tools.md](docs/quickstart-tools.md)\n- Command Center narrated tour → [command-center.md](docs/command-center.md)\n\n---\n\n## Requirements\n\n| Requirement | Details |\n|-------------|---------|\n| Rust (build only) | 1.75 or newer |\n| Git | Required for co-change analysis |\n| ONNX Model | Optional — for `semantic_search` |\n\n### Optional: Semantic Search\n\nFor `semantic_search` to work, you need an ONNX embedding model. The\neasiest setup uses the provided install script with `--download-model`.\nOtherwise, drop a model into `.lain/models/`:\n\n```bash\nmkdir -p .lain/models\n\n# Option A: bge-small-en-v1.5 (recommended — better MTEB scores, 384d, ~120MB)\ncurl -L https://huggingface.co/BAAI/bge-small-en-v1.5/resolve/main/onnx/model.onnx \\\n  -o .lain/models/model.onnx\ncurl -L https://huggingface.co/BAAI/bge-small-en-v1.5/resolve/main/tokenizer.json \\\n  -o .lain/models/tokenizer.json\n\n# Option B: all-MiniLM-L6-v2 (smaller, 384d, ~80MB)\ncurl -L https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/onnx/model.onnx \\\n  -o .lain/models/model.onnx\ncurl -L https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/tokenizer.json \\\n  -o .lain/models/tokenizer.json\n```\n\nExport the model path so the server picks it up:\n\n```bash\nexport LAIN_EMBEDDING_MODEL=$PWD/.lain/models/model.onnx\n```\n\nFor BGE-style asymmetric retrieval (better for short queries), set\nthe query prefix in `.lain/tuning.toml`:\n\n```toml\nquery_prefix = \"Represent this sentence for searching relevant passages: \"\n```\n\nWithout the model, `semantic_search` is filtered from `tools/list`\nentirely. Other features still work. The binary drops the tool rather\nthan advertise one that always says \"unavailable\".\n\n---\n\n## MCP Transport Modes\n\n| Mode | Command | Use Case |\n|------|---------|----------|\n| `stdio` | `--transport stdio` | Claude Code, MCP clients |\n| `http` | `--transport http --port 9999` | Command Center dashboard + curl-driven MCP |\n\nThe HTTP transport is no longer combined with stdio in a single\n`both` mode — start two `lain server` processes (or use the HTTP\ntransport and exercise tools via `curl` against `/mcp`).\n\n---\n\n## Troubleshooting\n\nFor first-time setup, see [QUICKSTART.md § First aid](docs/QUICKSTART.md#first-aid) before reading this section.\n\n**Hand-edit not picked up?**\n\nThe hot-reload watcher is non-recursive and uses atomic rename.\nEditing the file in place (`vim repos.yaml`) triggers a notify event\nwithin ~1 s. If you've moved the file across directories, save it\nback into the same directory.\n\n**Repo stuck in `indexing` / `degraded` / `unavailable` / `missing`?**\n\n```bash\n# Check federation health\ncurl -s -X POST http://localhost:9999/mcp \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{\"name\":\"get_federation_health\",\"arguments\":{}},\"id\":1}'\n```\n\nThe Command Center's Overview tab shows the same numbers in a single\nview.\n\n**Force a reload:**\n\n```bash\ncurl -s -X POST http://localhost:9999/mcp \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{\"name\":\"request_reload\",\"arguments\":{}},\"id\":1}'\n```\n\n**View all available tools:**\n\n```bash\ncurl -s -X POST http://localhost:9999/mcp \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{\"name\":\"get_agent_strategy\",\"arguments\":{}},\"id\":1}'\n```\n\n**`run_build` / `run_tests` fail with \"not found\"?**\n\nThe server inherits the environment of whatever launched it, and an\neditor-launched MCP server usually has no version-manager shims on\n`PATH`. lain searches the toolchain's known install locations (rustup,\nnvm, pyenv, volta, mise, asdf and friends) before giving up, and the\nerror names every way to fix it. To teach it a manager it doesn't know,\nadd `program_dirs` / `program_resolver` to that toolchain's profile —\nsee [`toolchains/README.md`](toolchains/README.md).\n\n**Answers look stale, or a symbol \"doesn't exist\" that clearly does?**\n\n`lain mcp` blocks on the first re-index before its stdio loop comes\nup, so the first tool call after `initialize` already sees a\npopulated graph (or `LAIN_REINDEX_TIMEOUT` was exceeded — see below).\nThe legacy \"second call works, first doesn't\" footgun is gone.\n\nIf you still see stale or missing symbols, check `get_health`:\n\n- **`Build:`** tells you the version and git SHA of the process\n  answering, and warns when a newer binary is on disk. An MCP stdio\n  server is spawned once by its client and outlives every rebuild, so\n  it can be older than your source tree — restart the client to pick up\n  a new build.\n- **`Status:`** reads `Degraded ⚠` when the last re-index failed OR\n  timed out, which means \"not in this graph\", not \"does not exist\". A\n  timeout banner means `LAIN_REINDEX_TIMEOUT` (default 300s for the\n  outer startup budget, 60s for the per-repo pipeline under it) was\n  too short for your working tree — raise it past **both** defaults\n  (`LAIN_REINDEX_TIMEOUT=600` covers a `tokio`-sized repo on a cold\n  cache) and restart.\n\n**Two agents not seeing each other?**\n\nThey must share one workspace. Presence is exchanged through the state\nfile under `~/.local/lain/state/`, so agents on the same repo see each\nother's claims even when each console spawned its own stdio server.\n`list_active_agents` and `list_occupancy` are the quickest check.\n\n---\n\n## Regenerating the demo video\n\nThe hero recording above is checked in. Re-record it after any SPA change:\n\n```bash\nmake record-demo\n```\n\nOr: `npm run record-demo --prefix tests/js` (runs only the Playwright driver;\nyou still need `scripts/record-spa-demo.sh` for the ffmpeg encoding pass).\n\nFor the offline (synthetic) fixture, run `make record-demo-small`.\n\n---\n\n## License\n\nMIT — Copyright (c) 2026 spuentesp\n",
  "bytes": 18890,
  "sha": "1dc4f69a893c40ba1f32326a88e5699fed0350c3bf2ded6039b524ad6b88b710",
  "repo_slug": "spuentesp/lain",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_spuentesp_lain_05472938/readme"
}