Atlas / MCP servers / spuentesp / lain

lainBLOCK

mcp/spuentesp/lain

High-performance MCP server for AI coding agents: persistent knowledge graph, blast radius analysis, semantic search. Built in Rust.

Verdict
BLOCK
Grade
D
Trust score
69 /100
Exposed tools
26 22r · 4w · 0d
Transport
stdio
License
MIT
Stars
9
01

Overview

From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.

A code graph for coding agents. Lain maps callers, dependencies, symbols, and Git co-changes, then exposes them through the Model Context Protocol (MCP). Before an agent edits a function, it can inspect what depends on it and how the rest of the code reaches it.

The demo traces a symbol across the bytes and tokio repositories. Agents query the same graph through MCP; the browser shows what Lain indexed and where an answer came from.

Ask your agent:

If I change validate_token, what breaks?
Show me the call chain from entry to helper_a.
Which files usually change with auth.rs?
Is another agent already editing this code?

Lain answers with symbol names and source paths from a local index. The index stays on disk between runs and can cover more than one repository. It follows commits, not uncommitted edits: after committing, a re-index pass (lain reindex or a fresh session) brings it current — and answers that may miss recent work say so instead of going quiet. Semantic search is optional; graph queries work without an embedding model.

Install

Using the release installer:

curl -fsSL https://raw.githubusercontent.com/spuentesp/lain/main/install.sh | bash
source ~/.zshrc   # or ~/.bashrc
lain --version

Rust 1.89 or newer is only needed when building from source. The quickstart covers non-interactive installation and the optional local embedding model.

Connect an agent

Run one setup command from the repository you want Lain to index:

lain setup --agent claude-code
lain setup --agent codex
lain setup --agent cursor
lain setup --agent vscode
lain setup --agent continue

Use lain setup --agent generic for another MCP host. It writes this project-level .mcp.json, with the absolute path of the lain binary that ran setup as command:

{
"mcpServers": {
"lain": {
"command": "/home/you/.local/bin/lain",
Read from source at commit 19bc35bd911bOBSERVED · 2026-10-11
Advertisement
02

Connect

Built from this server's own package name, version and transport as found in its source — not copied from anyone's documentation, so it cannot drift against a page we do not control. Replace the environment placeholders with a token scoped to the least it needs.

claude-code (npm)
claude mcp add lain-mcp -- npx -y @spuentesp/[email protected]
03

Exposed tools (26)

22 read · 4 write · 0 destructive.

ToolRiskDescription
assess_changereadImpact assessment
check_bindingreadValidate a proposed consumer→endpoint link and emit the YAML for
debug_sleepreadSleep for the given number of seconds (useful for testing job infrastructure).
diff_contractsreadProvider and consumer changes between two snapshots, classified, with impact paths and coverage (§12).
find_anchorsreadFind key files
get_agent_strategyreadReturn the recommended tool sequence and quick-reference for working with Lain.
get_capabilitiesreadReturn the current repository capability states, freshness, and indexing progress. Warming states are retryable.
get_contractreadProviders, schema fields, and bound consumers for one endpoint (§12).
get_coveragereadWhat the view saw and what it could not resolve (§9.7). With an
get_healthwriteReturn server health, node/edge counts, last enriched commit, and language-server availability.
get_job_statusreadGet the current status and output of a previously-spawned background job.
get_servicereadConsumers of one service: endpoints, calling code, fields used, and the
get_snapshotreadRead a snapshot
install_language_serverwriteInstall one or more language servers. Pass a single
list_contractsreadEndpoints the federation exposes, filterable by service, repo, and kind (http/topic).
list_packagesreadThe skill menu: every capability package with its pitch, level, why it is off by default, and its tools.
list_servicesreadEvery service in the federation with its repo, paths, endpoint count and consumer counts. Works on
list_unresolvedreadEvery ambiguous and unresolved consumer with the candidate endpoints that could match (§9.7).
load_packagereadOpt a package (verify, arch, raw, notes, session, social, ops) into this session
prepare_snapshotwritePin every repo to a commit, index once, reuse the result. Same inputs after ref resolution produce the same id (§8.4);
random_extensionreadCustom
read_sourcereadA bounded line range of a file at a view
register_job_webhookreadRegister a webhook URL to receive notifications when background jobs complete.
resolve_evidencewriteCheck refs exist at their commit; return context. Bad refs are
trace_impactreadImpact paths from an endpoint, field, or symbol (§5.2 + §12). Exactly one of endpoint/field/symbol is required.
understand_repositoryreadSemantic map
04

Trust audit

BLOCKgrade D · trust 69/100 Do not install this without reading the findings. The audit found something that could harm you or your machine.

LayerWhat it checksResult
L0Provenance & inventoryPASS
L1Static analysis of the codeFAIL
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfacePASS
L4Behavioural (sandbox)SKIPPED

What the source does

Filesystem
declared (13 observation(s))
Network
declared (3 observation(s))
Shell
declared (3 observation(s))
Dependencies
not all pinned
Secrets in source
none-found

Findings (25)

HIGHCode injection · code.deserialize · CWE-78, CWE-94, CWE-95
src/server/sensors/dynamic_dispatch_sensor.rs:590
def load_blob(blob):\n    return pickle.loads(blob)\n",
Why it matters. deserialises untrusted bytes into live objects
Fix. use json or yaml.safe_load
HIGHFilesystem / path · fs.credential_store · CWE-22, CWE-59
src/server/mcp/contract_tools/evidence.rs:875
const PATTERNS: &[&str] = &[".env", ".npmrc", ".pypirc", ".netrc"];
Why it matters. touches a credential store
HIGHFilesystem / path · fs.credential_store · CWE-22, CWE-59
src/server/mcp/contract_tools/evidence.rs:887
if basename_lower.starts_with("id_rsa")
Why it matters. touches a credential store
HIGHFilesystem / path · fs.credential_store · CWE-22, CWE-59
src/server/mcp/contract_tools/evidence.rs:889
|| basename_lower.starts_with("id_ecdsa")
Why it matters. touches a credential store
HIGHFilesystem / path · fs.credential_store · CWE-22, CWE-59
src/server/mcp/contract_tools/evidence.rs:890
|| basename_lower.starts_with("id_ed25519")
Why it matters. touches a credential store
HIGHFilesystem / path · fs.credential_store · CWE-22, CWE-59
src/server/mcp/contract_tools/evidence.rs:977
"id_rsa",
Why it matters. touches a credential store
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
src/cli/hooks.rs:985
sanitize_agent_name("../../../../etc/passwd"),
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
src/server/annotations.rs:964
assert_eq!(canonical_file("../../etc/passwd"), "");
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
src/server/mcp/command_center_assets.rs:39
pub const BLAST_RADIUS_HTML: &str = include_str!("../../ui/blast-radius.html");
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
src/server/mcp/command_center_assets.rs:42
pub const COUPLING_HTML: &str = include_str!("../../ui/coupling.html");
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
src/server/mcp/command_center_assets.rs:45
pub const CALL_CHAIN_HTML: &str = include_str!("../../ui/call-chain.html");
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
.github/actions/lain-health-badge/health.sh:173
if ! curl -fsS --max-time 600 -o "$RESP" -X POST http://127.0.0.1:9999/mcp \
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
.github/actions/lain-health-badge/health.sh:189
curl -fsS --max-time 900 -X POST http://127.0.0.1:9999/mcp \
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
.github/actions/lain-health-badge/health.sh:207
if CAPS_RESP=$(curl -fsS --max-time 30 -X POST http://127.0.0.1:9999/mcp \
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
.github/actions/lain-health-badge/health.sh:322
BR_TEXT=$(curl -fsS --max-time 30 -X POST http://127.0.0.1:9999/mcp \
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
.github/actions/lain-health-badge/health.sh:361
ANN_RESP=$(curl -fsS --max-time 30 -X POST http://127.0.0.1:9999/mcp \
LOWObfuscation / stealth · obf.decode_call · CWE-506, CWE-94
scripts/export-release-provenance.py:22
statement = json.loads(base64.b64decode(envelope['payload'], validate=True))
LOWObfuscation / stealth · obf.homoglyph · CWE-506, CWE-94
tests/graph_benchmark.rs:134
"N", "Graph (μs)", "Naive Grep (μs)", "Speedup", "Nodes"
LOWObfuscation / stealth · obf.homoglyph · CWE-506, CWE-94
tests/graph_benchmark.rs:178
"N", "Graph (μs)", "Naive LSP (μs)", "Speedup", "Affected"
LOWObfuscation / stealth · obf.homoglyph · CWE-506, CWE-94
tests/graph_benchmark.rs:230
"N", "Graph (μs)", "Multi-File (μs)", "Speedup", "Related"
LOWObfuscation / stealth · obf.homoglyph · CWE-506, CWE-94
tests/graph_benchmark.rs:282
"N", "Graph (μs)", "Naive (μs)", "Speedup"
LOWObfuscation / stealth · obf.homoglyph · CWE-506, CWE-94
tests/graph_benchmark.rs:339
"N", "Find (μs)", "Traverse (μs)", "Total"
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
tests/e2e/requirements.txt
pytest, requests
Why it matters. 2 requirement(s) not pinned with ==
Fix. pin exact versions
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
tests/js/package.json
playwright
Why it matters. 1 dependency range(s) float
Fix. pin exact versions or ship a lockfile
LOWPrompt injection · prompt.transfer_instruction · CWE-94, CWE-1427
docs/command-center.md:179
a `curl -X POST http://localhost:9999/mcp ...` snippet to the clipboard).
Why it matters. an instruction to move sensitive data to an outside destination
Fix. remove; a skill never needs the user's secrets off the machine

Gates applied: no_behavioural_pass.

Audited 2026-10-11 · audit v0.4.1 · source sha 19bc35bd911bfull audit observations/trust-audit/mcp-server/spuentesp__lain.json · Report an issue / request a re-scan
05

Audit history

Every audit this server has had. A grade with a past is a grade somebody is still checking.

DateSourceVerdictGradeScoreChange
2026-10-1119bc35bd911bBLOCKD69first audit
06

Questions

What is the lain MCP server?

High-performance MCP server for AI coding agents: persistent knowledge graph, blast radius analysis, semantic search. Built in Rust.

What tools does lain expose?

26 in total: 22 read-only, 4 that write, and 0 that can delete or overwrite. Every one is listed on this page with its risk.

Is lain safe to connect to an agent?

No — not without reading the findings first. The audit graded it D (69/100) and found 6 critical or high issues in the source. Each one is listed on this page with the file and line it is on.

What credentials does lain need?

It reads SECRET_KEY from the environment. Give it a token scoped to the least it needs — an agent that can be talked into calling a tool can be talked into calling it with your credentials.

How does lain run?

It speaks stdio, so it runs as a local process your client starts. It is published on npm as @spuentesp/lain-mcp at 0.9.0.

How current is this page?

The grade is for one exact copy of the source (19bc35bd911b), read on 2026-10-11. The repository is watched and re-audited when it changes.

Advertisement