Skip to main content
Glama

cfr-free

CI License: MIT

Self-hosted Title 38 CFR data service: it builds a local, full-text searchable index of Title 38 of the Code of Federal Regulations (the VA benefits rulebook) straight from the public eCFR API, and serves it through an MCP server (for AI assistants like Claude) and a small HTTP API ("Titan-style" query surface). No cloud dependencies, no API keys, no telemetry — one Node process and one SQLite file.

What you get

  • Local full-text search over every section and appendix of Title 38, powered by SQLite FTS5 (BM25-ranked with citation/heading weighting, relevance snippets, and the matched terms shown per hit).

  • MCP server with six read-only tools any MCP client can use: search_regulations, get_section, browse_title38, index_status, list_parts, health.

  • HTTP JSON API for scripts and apps: search, read sections, browse the CFR hierarchy, health check, Prometheus metrics.

  • Freshness tracking: the index records the eCFR issue date it was built from and warns when it goes stale or drifts from the structure tree.

  • Backup-friendly: cfr-free export <path> writes a consistent snapshot of the index you can copy anywhere.

Related MCP server: mcp-fda-regulations

Requirements

  • Node.js ≥ 22.5 (uses the built-in node:sqlite; SQLite ≥ 3.50 with FTS5, bundled with Node 22.5+)

  • Network access only for cfr-free sync (~2–3 MB from ecfr.gov); serving and searching are fully offline.

Quickstart

npm install -g cfr-free    # or: npx cfr-free
cfr-free sync              # build the local index (~2-3 MB download)
cfr-free serve             # HTTP API on http://127.0.0.1:8787
# or
cfr-free mcp               # MCP server on stdio (default command)

cfr-free export ./backup.sqlite   # consistent snapshot of the local index

Docker

docker run -d --name cfr-free -p 8787:8787 -v cfr-data:/data \
  ghcr.io/deffenda/cfr-free:latest

The container syncs on first start (~15 s, ~2–3 MB from eCFR), then serves the HTTP API; the index persists in the cfr-data volume mounted at /data. Any CLI command works the same way, e.g. docker run --rm -v cfr-data:/data ghcr.io/deffenda/cfr-free:latest sync to refresh, or … export /data/backup.sqlite to snapshot into the volume.

The image runs as a non-root user and binds 0.0.0.0 with the remote opt-in already set — inside a container the published port (-p) is the exposure gate, and loopback would make it unreachable. Outside Docker the CLI still defaults to 127.0.0.1 with CFR_ALLOW_REMOTE=1 required. When you do expose it, a per-peer token-bucket rate limit (60-burst, 10 req/s) turns on automatically. Images are published to GHCR on v* tags with build-provenance attestations.

Prefer Compose? docker compose up -d --build serves the API on 127.0.0.1:8787 with a named volume, healthcheck, and sync left explicit (docker compose run --rm cfr-free sync) so starts never trigger a surprise download.

Use with Claude (MCP)

Add to your MCP client config (e.g. claude_desktop_config.json):

{
  "mcpServers": {
    "cfr-free": {
      "command": "cfr-free",
      "args": ["mcp"]
    }
  }
}

Then ask things like "What does 38 CFR 4.125 say about diagnosing mental disorders?" or "Search Title 38 for traumatic brain injury compensation."

MCP tools

Tool

Arguments

Returns

search_regulations

query, page?, per_page?, part?

Ranked hits: citation (+ 38 C.F.R. § display form, ecfr.gov link), heading, snippet, score, matches — with exact-citation pinning, "quoted phrase" search, and claims-acronym expansion (PTSD, TDIU, SMC, …)

get_section

citation (e.g. 38 CFR 4.125)

Full section text as markdown + ecfr.gov link + cross-references with availability flags

browse_title38

chapter?, part?, subpart?

Children of the requested hierarchy level

index_status

—

Issue date, last sync, row counts, staleness, drift flag, and what the last sync amended/added/removed

list_parts

—

Parts in the index with titles and section/appendix counts

health

—

One-call readiness verdict: ready, stale, degraded (structure drift), or empty

All tools are read-only and query only the local index — they never hit the network. Search and section responses carry a freshness envelope (indexStale + a notice when the local index has aged out) so a client can never unknowingly quote a lapsed version.

Beyond tools, the server exposes two MCP resources that clients can read directly — cfr://index/status (freshness JSON: issue date, last sync, row counts, staleness, drift) and cfr://title38/parts (parts with titles and counts) — plus two prompts: cfr_research (topic, part?), which walks a model through the search → read → cite workflow, and cfr_claim_evaluation (issue, part?, citation?), which encodes the adjudicator workflow: readiness gate → locate criteria → quote verbatim → follow cross-references → paste-ready citations with source URLs.

HTTP API

Method

Path

Query params

GET / HEAD

/health

—

GET / HEAD

/metrics

— (Prometheus text format)

GET / HEAD

/api/v1/search

q (required), page, per_page, part

GET / HEAD

/api/v1/sections/:citation

— (URL-encode citations with spaces)

GET / HEAD

/api/v1/browse

chapter, part, subpart

curl 'http://127.0.0.1:8787/api/v1/search?q=traumatic%20brain%20injury&part=4'
curl 'http://127.0.0.1:8787/api/v1/sections/4.125'
curl 'http://127.0.0.1:8787/api/v1/browse?chapter=I&part=17'
curl 'http://127.0.0.1:8787/metrics'

The API is intentionally minimal: GET-only, JSON-only, no request bodies, no templating, bound to 127.0.0.1 by default. The single deliberate exception is /metrics, which serves Prometheus exposition text for scrapers. There is no authentication — if you bind it to a non-loopback address you must put it behind your own reverse proxy/auth (CFR_ALLOW_REMOTE=1 is required to allow that, as an explicit opt-in).

Configuration

All configuration is environment variables (flags are deliberately avoided so MCP client configs stay uniform):

Variable

Default

Meaning

CFR_INDEX_PATH

~/.cfr-free/index.sqlite

SQLite index location

CFR_MAX_AGE_DAYS

7

Index older than this is reported stale

CFR_AUTO_SYNC

1

Sync at startup when index empty/stale

CFR_HOST

127.0.0.1

HTTP bind address

CFR_PORT

8787

HTTP bind port

CFR_ALLOW_REMOTE

unset

Set 1 to permit non-loopback binds

CFR_LOG_LEVEL

info

debug | info | warn | error

Logs are structured JSON lines on stderr only — stdout is reserved for MCP's JSON-RPC protocol.

Exit codes

Code

Meaning

0

Success

1

Generic error (sync failure, corrupt index, …)

2

Usage or configuration error

3

Index unavailable (empty, and auto-sync disabled)

4

HTTP port could not be bound (in use or permission denied)

Scripts can branch on these — e.g. retry later on 3, pick another port on 4.

Development

npm ci
npm run ci           # format:check + lint + typecheck + tests + build + audit
npm run test:coverage
npm run scan         # gitleaks + osv-scanner + trivy + semgrep (skips missing tools)

Local LLM review (Gemma via LM Studio)

npm run review            # reviews docs/SPEC.md + src/ with the local model
npm run review:spec       # spec only

Talks to LM Studio's OpenAI-compatible endpoint (http://localhost:1234 by default, model google/gemma-4-12b). Configure with LMSTUDIO_BASE_URL, LMSTUDIO_MODEL, LLM_REVIEW_TIMEOUT_MS, or --model / --base-url / --max-tokens / --instruction flags. Reasoning models are handled automatically (retries with a larger token budget when the visible answer has not been emitted yet).

Architecture

eCFR API ──sync──▶ SQLite (sections + FTS5 + structure meta)
                     ▲                 ▲
                     │                 │
              MCP tools (stdio)   HTTP API (node:http)
  • src/core/ — eCFR client, XML parser, FTS5 store, sync, service facade

  • src/mcp/ — MCP server and tool registrations

  • src/titan/ — HTTP JSON API

  • src/commands/ — sync, serve, mcp

  • docs/SPEC.md — design decisions and review dispositions

Sync is atomic: the whole index is replaced inside one BEGIN IMMEDIATE transaction under WAL, so concurrent readers never see a partial build, and an interrupted sync simply rolls back.

Data source & attribution

Regulatory content comes from the eCFR API operated by the Office of the Federal Register and the Government Publishing Office. eCFR content is in the public domain (17 U.S.C. § 105). cfr-free is an independent project and is not endorsed by or affiliated with the VA, the OFR, or GPO. This tool is not legal advice; verify citations against the official eCFR.

Security

See SECURITY.md for reporting vulnerabilities and the security posture summary. Reported by default: Snyk/Sonar-ready lint and CI gates, zero runtime dependencies beyond the MCP SDK, XML parser hardening (no DOCTYPE, bounded depth/size), FTS query sanitization (no SQL injection surface), and loopback-only networking by default.

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Enables AI assistants to retrieve, search, and track changes to US Code of Federal Regulations sections via the eCFR API, returning actual regulation text with citations and point-in-time date support.
    7
    21 PyPI
    1
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables agents to search a local, fully offline personal document library (Markdown/txt/PDF/docx) via SQLite FTS5 trigram search with subject-terminology expansion, returning every hit with its domain, document title, section path, and page or offset provenance. It also exposes domain statistics, incremental ingestion, and document deprecation as tools, alongside a read-only, runtime-persona-scoped ask/coverage mode.
    -