cfr-free
Read-only MCP server for searching, reading, browsing, and auditing Title 38 CFR data, with local index tools plus live eCFR historical, diff, and redline lookups.
Search the local Title 38 CFR index with ranked results and optional filters for part, kind, and chapter.
Read full CFR sections or appendices as markdown with citations and source links.
Browse the Title 38 hierarchy by chapter, part, subpart, or subject group.
Check local index freshness, structure drift, row counts, corpus hash, and readiness status.
List all indexed CFR parts with section and appendix counts.
Fetch a provision as it stood at a specific date from eCFR.
Diff two eCFR issue dates to see changed, added, or removed provisions.
Generate a line-level redline of one provision between two eCFR issue dates.
Exposes Prometheus-compatible metrics via the /metrics HTTP endpoint, enabling monitoring and alerting on server health and usage.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@cfr-freeWhat does 38 CFR 4.125 say about diagnosing mental disorders?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
cfr-free
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).
Honest about images: a few provisions publish their rating tables as eCFR images rather than text. Those provisions carry an explicit
[image content not included: <path>]marker where the graphic belongs, so a missing table is never mistaken for complete text. Nothing is fetched or transcribed — the service stays fully offline.Read-only web UI (search, browse, section reads, line-level redlines, copy-a-citation) over the same JSON API.
MCP server with nine read-only tools any MCP client can use: six offline index-backed ones (
search_regulations,get_section,browse_title38,index_status,list_parts,health) plus three that fetch the live eCFR versioner per call (get_section_at(one provision as it stood at a date),diff_issues,redline).HTTP JSON API for scripts and apps: search, read sections, browse the CFR hierarchy, health check, Prometheus metrics.
Measured retrieval: a frozen query set and runner score the search over citations, abbreviations and plain-language questions — see
docs/research/retrieval-baseline-2026-10-20.md.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> [jsonl|md]writes a consistent snapshot of the index you can copy anywhere (sqlite default;jsonlandmdfor pipeline ingestion).
Related MCP server: cfr-mcp
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,cfr-free diff,cfr-free redlineandcfr-free historical(~2–3 MB from ecfr.gov per full-title fetch; the two-date views fetch twice); the startup refresh of a stale or empty index bysearch,serveandmcp(unlessCFR_AUTO_SYNC=0); the opt-in periodic re-sync inserveandmcpwhenCFR_REFRESH_HOURSis set (default 0 = off);serve's/api/v1/diffand/api/v1/redlineroutes; andmcp's three eCFR-backed tools (get_section_at,diff_issues,redline), which fetch from eCFR on every call. Reads from a fresh index never touch the network.
Quickstart
Five ways in, depending on what you have. The first needs nothing but Docker; the second needs only npm; the third needs only Node and runs the package straight from a release tarball (no compiler); the fourth needs npm and a compiler; the fifth needs a checkout.
New here? First 10 minutes picks one of these routes and walks it end to end: build the local index, search it, read a provision and cite it.
1. Docker (nothing to install)
docker run -d --name cfr-free -p 8787:8787 -v cfr-data:/data \
ghcr.io/deffenda/cfr-free:latestThe image is public and pulls anonymously. It syncs the index on first
start (~5 s, ~2–3 MB from eCFR) and serves the API on
http://127.0.0.1:8787; the index persists in the cfr-data volume. 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. Details under
Docker below.
2. From npm (no compiler, no Docker)
npm install -g cfr-free
# or, with no install at all:
npx -y cfr-free version # -> 0.2.1
npx -y cfr-free sync # build the local index (~2-3 MB, ~5 s)
npx -y cfr-free serve # HTTP API on http://127.0.0.1:8787The published tarball ships the compiled dist/, so this route needs
neither a compiler nor a checkout — it is the fastest way in.
3. From a release tarball (no compiler)
npx -y https://github.com/deffenda/cfr-free/releases/download/v0.2.1/cfr-free-0.2.1.tgz version # -> 0.2.1
npx -y https://github.com/deffenda/cfr-free/releases/download/v0.2.1/cfr-free-0.2.1.tgz serve # HTTP API on http://127.0.0.1:8787Every GitHub release carries the package tarball at
releases/download/<tag>/cfr-free-<version>.tgz; for v0.2.1 that
tarball holds the same contents the npm registry serves (verified by
unpacking both: identical file trees). npx installs the tarball
directly from the URL — the cfr-free package itself never comes from
the registry, no compiler runs on first use, and the version in the URL
pins the release exactly. npx does still fetch the package's runtime
dependencies from npm (they are not bundled). Verified on the v0.2.1
release, 2026-10-29: both commands above ran end to end (version →
0.2.1; serve answered /health with HTTP 200). Swap the tag and
filename together to pick another release.
4. From GitHub (no publish step)
npx -y github:deffenda/cfr-free version # -> 0.2.2
npx -y github:deffenda/cfr-free serve # HTTP API on http://127.0.0.1:8787npx fetches and compiles the project on first use, so the first run takes
a minute and needs a compiler (npm runs the project's prepare script when
it installs from a git URL, and that script runs tsc). The Docker route
above needs neither.
To use it as a dependency of your own project:
npm install github:deffenda/cfr-freeA global install from a git URL (
npm install -g github:…) does not work, and the failure is npm's rather than the project's. Measured on npm 11.20.0, five runs in a row left the SAME broken end state: the package is not installed, and thecfr-freecommand npm puts in your PATH points at a file that does not exist, so running it fails with "no such file or directory".Whether npm reports the failure varies, and the quiet case is the more misleading one: most runs print "added 104 packages" and exit 0, so nothing looks wrong until you run the command; some abort part-way with
ENOENT … chmod … node_modules/which/bin/node-which(exit 254). On npm 10.9.9 the same class of failure presents as a dropped dependency tree (ENOENT … readlink … node_modules/.bin/fxparser). All of this was measured, and it reproduces on the unmodified repository too. Usenpx, a project-local install, the npm route above, or the Docker image instead — all four were re-verified on npm 11.20.0.
5. From a source checkout
# requires Node >= 22.5
git clone https://github.com/deffenda/cfr-free.git
cd cfr-free
npm ci # install the pinned dependencies
npm run build # compile to dist/ (the CLI entry point is dist/cli.js)
node dist/cli.js version # -> 0.2.2 (verifies the build)
node dist/cli.js sync # build the local index (~2-3 MB download, ~5 s)
node dist/cli.js serve # HTTP API on http://127.0.0.1:8787
# or
node dist/cli.js mcp # MCP server on stdio (default command)
node dist/cli.js export ./backup.sqlite # consistent snapshot of the local index
node dist/cli.js search PTSD # ranked provisions from the local index (top 10)
node dist/cli.js diff 2026-01-01 2026-06-01 # provision changes between two issue dates
node dist/cli.js redline 2026-01-01 2026-06-01 4.125 # line-level redline of one provisionnpm link in the checkout puts cfr-free on your PATH, after which the
examples above work verbatim as cfr-free ….
cfr-free sync writes the index at ~/.cfr-free/index.sqlite. To try it
against a throwaway index instead of your own, point CFR_INDEX_PATH
somewhere else:
CFR_INDEX_PATH=/tmp/cfr-smoke.sqlite node dist/cli.js sync # ~5 s, then delete the fileDocker
docker run -d --name cfr-free -p 8787:8787 -v cfr-data:/data \
ghcr.io/deffenda/cfr-free:latestThe container syncs on first start (~5 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.
The package is on npm (npm view cfr-free version prints the latest; see
Quickstart). Releases publish from the v*-tag workflow
with OIDC provenance (no publish token), gated behind the
NPM_TRUSTED_PUBLISHING repository variable plus one-time
trusted-publisher registration on npmjs.com. The publish job verifies
before it publishes that the packed tarball carries the executable and
every file it imports, and that the tag matches the package.json
version — so a release cannot ship a broken package.
Verify the install (smoke checklist)
Every command below was run against an isolated throwaway index
(CFR_INDEX_PATH=/tmp/smoke.sqlite) on the 2026-10-16 corpus; the
outputs are what you should see. Nothing outside /tmp is touched.
The install routes were measured, not assumed. A clean global install of
the packed tarball into a throwaway prefix was run and the installed binary
was exercised (--version, and a search against a copy of the index); the
GitHub Release asset route — the same tarball, fetched from a release URL —
was created, downloaded and installed end to end in a scratch
repository; it now also runs straight from this repository's own
releases (v0.2.1, verified 2026-10-29, see Quickstart). A
project-local npm install github:deffenda/cfr-free was run and its
binary exercised, and npx -y github:deffenda/cfr-free was run, MCP over
stdio included.
The route that does not work — npm install -g github:… — was measured
too, and the failure is npm's (see Quickstart). The sequence
below was run against a provisioned checkout; what it does not cover is an
install on a machine with no npm cache and no network beyond the first
fetch.
export CFR_INDEX_PATH=/tmp/smoke.sqlite # never your real index
# 1. the build is what you think it is
node dist/cli.js version # -> 0.2.2
# 2. network: build the index (~2-3 MB, ~5 s)
node dist/cli.js sync
# -> synced 2953 sections + 17 appendices (issue date 2026-10-16; ...
# 9 provisions contain image content not included; 2134 ms; corpus hash ...)
# 3. HTTP read (no network from here on)
CFR_PORT=8791 node dist/cli.js serve & SERVER_PID=$!
curl -s http://127.0.0.1:8791/health | head -c 80
# -> {"status":"ok","issueDate":"2026-10-16","syncedAt":...
curl -s 'http://127.0.0.1:8791/api/v1/search?q=4.130&per_page=1' | head -c 80
# -> {"query":"4.130","page":1,"perPage":1,"total":42,...
curl -s http://127.0.0.1:8791/api/v1/sections/4.130 | head -c 80
# -> {"citation":"4.130","kind":"section","heading":"Schedule of ratings...
# 4. offline restart: reads come from the local index, no network
kill "$SERVER_PID"; CFR_PORT=8791 node dist/cli.js serve & SERVER_PID=$!
# /health answers identically — that is the point
# 5. MCP over stdio: initialize, then call a tool
printf '%s\n%s\n%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"1.0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_section","arguments":{"citation":"38 C.F.R. § 4.130"}}}' \
| node dist/cli.js mcp | tail -1 | head -c 120
# -> {"result":{"content":[{"type":"text","text":"{\n \"citation\": \"4.130\"...
# (note: the DOTTED display citation resolves — P1-03)
# 6. export: the whole corpus, offline
node dist/cli.js export /tmp/smoke.jsonl jsonl
# -> exported 2970 provisions as jsonl (17062107 bytes) to /tmp/smoke.jsonl
kill "$SERVER_PID"; rm -f /tmp/smoke.sqlite /tmp/smoke.jsonl # clean upOnly steps 1–2 need the network; 3–6 are fully offline once the index exists. If step 3 answers and step 4 answers identically after the restart, your install reads from the local index exactly as intended.
Use with Claude (MCP)
Add to your MCP client config (e.g. claude_desktop_config.json):
{
"mcpServers": {
"cfr-free": {
"command": "npx",
"args": ["-y", "cfr-free", "mcp"]
}
}
}Using Claude Code? The same server registers in one command (verified 2026-10-29):
claude mcp add cfr-free -- npx -y cfr-free mcpThat writes the local scope — this project only, the default;
claude mcp add -s user cfr-free -- npx -y cfr-free mcp registers it for
every project instead.
All of these forms run npx -y cfr-free mcp under the hood: nothing
installed, no path to edit, and the published tarball ships compiled —
no compiler runs on first use. The JSON config also works from the git URL
("args": ["-y", "github:deffenda/cfr-free", "mcp"]), which npx fetches
and compiles on first use (verified end to end: the list_parts tool
answers over stdio). If you built it from a checkout, use the absolute
path to the built CLI, because MCP clients do not inherit your shell's
PATH:
{
"mcpServers": {
"cfr-free": {
"command": "/absolute/path/to/cfr-free/dist/cli.js",
"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 |
|
| Ranked hits: citation (+ |
|
| Full section text as markdown + ecfr.gov link + cross-references with availability flags |
|
| Children of the requested hierarchy level (a |
| — | Issue date, last sync, row counts, staleness, drift flag, corpus receipt hash, and what the last sync amended/added/removed |
| — | Parts in the index with titles and section/appendix counts |
| — | One-call readiness verdict: |
|
| One provision as it stood at a date: the eCFR revision in force then, fetched statelessly on every call (no local index; the point-in-time archive starts 2017-01-01) |
|
| Provision-level diff between two eCFR issue dates: changed/added/removed citations (capped, with a truncation flag) |
|
| Line-level redline of one provision between two eCFR issue dates: status, both sides' text, and per-line hunks |
All tools are read-only; the six index tools query only the local index and
never hit the network, while get_section_at, diff_issues and redline
fetch from eCFR on each call (a point-in-time revision, or two issue dates'
full-title XML). 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 three MCP resources that clients can read
directly — cfr://index/status (freshness JSON: issue date, last sync, row
counts, staleness, drift, corpus receipt hash), cfr://title38/parts (parts with titles and
counts), and cfr://title38/definitions (§ 3.1 master definitions section
detail) — 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 |
|
| — |
|
| — (Prometheus text format) |
|
|
|
|
| — (URL-encode citations with spaces) |
|
|
|
|
|
|
|
|
|
/health and /metrics additionally expose diskFree/diskTotal and the
cfr_free_disk_free_bytes/cfr_free_disk_total_bytes gauges when the platform
provides fs.statfs (as of boot or last refresh round); omitted otherwise.
The three index-backed reads (/api/v1/search, /api/v1/sections/:citation,
/api/v1/browse) send a strong ETag over the response payload. Resend it as
If-None-Match and the server answers 304 Not Modified with no body when the
text has not changed, so a client can confirm the provision it quotes without
downloading it again:
etag=$(curl -sD - -o /dev/null 'http://127.0.0.1:8787/api/v1/sections/4.125' \
| awk 'tolower($1) == "etag:" { print $2 }' | tr -d '\r')
curl -s -o /dev/null -w '%{http_code}\n' -H "If-None-Match: $etag" \
'http://127.0.0.1:8787/api/v1/sections/4.125' # 304/health, /metrics and the two eCFR-fetching routes (/api/v1/diff,
/api/v1/redline) carry no validator, and an error response is never a 304.
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 full request/response contract (schemas, error envelopes) is documented in
docs/openapi.yaml (OpenAPI 3.1).
The API is intentionally minimal: GET-only, no request bodies, no
templating, bound to 127.0.0.1 by default. The deliberate non-JSON
responses are /metrics (Prometheus exposition text for scrapers) and the
static web UI assets. 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).
The serve root / also serves a read-only web UI (search + section reader
with chapter/part browse + a redline view over the same JSON API, hash-routed) —
no auth, loopback bind by default like the rest of the surface.
Configuration
All configuration is environment variables (flags are deliberately avoided so MCP client configs stay uniform):
Variable | Default | Meaning |
|
| SQLite index location |
|
| Index older than this is reported stale |
|
| Sync at startup when index empty/stale |
|
| Hours between background refreshes while serving (0 = off) |
|
| HTTP bind address |
|
| HTTP bind port |
| unset | Set |
|
|
|
Logs are structured JSON lines on stderr only — stdout is reserved for MCP's JSON-RPC protocol.
Exit codes
Code | Meaning |
| Success |
| Generic error (sync failure, corrupt index, …) |
| Usage or configuration error |
| Index unavailable (empty, and auto-sync disabled) |
| HTTP port could not be bound (in use or permission denied) |
| Refused to overwrite an existing export file (remove it or pick another path) |
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 + workflow 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 onlyTalks 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 facadesrc/mcp/— MCP server and tool registrationssrc/titan/— HTTP JSON APIsrc/commands/— one entrypoint per subcommand:mcp,sync,diff,redline,historical,search,serve,export(helpandversiondispatch fromsrc/cli.ts)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: lint/strict-TS/typecheck/coverage/Audit gates (plus local trivy/semgrep/osv-scanner/gitleaks scans), 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
Available Tools
9 toolsbrowse_title38Browse the CFR structureARead-onlyIdempotent
Navigate the Title 38 hierarchy (title → chapter → part → subpart → subject group) and list what exists at a level. With no arguments, returns the chapters. Use this to discover valid parts/subparts before searching. Most sections live inside a subject group: pass the subject_group identifier you saw in a previous response to list the sections it holds.
| Name | Required | Description | Default |
|---|---|---|---|
| part | No | part number, e.g. "4" | |
| chapter | No | chapter identifier, e.g. "I" | |
| subpart | No | subpart letter, e.g. "B" (requires part) | |
| subject_group | No | subject-group identifier, e.g. "ECFRd158704dcab040e" (requires part) |
Output Schema
| Name | Required | Description |
|---|---|---|
| node | Yes | |
| path | Yes | |
| children | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive, closed-world), so the description's job is to add workflow context — which it does by disclosing the no-args default return and the fact that section listing requires an identifier obtained from a prior response. It doesn't discuss result size, pagination, or ordering, so it isn't exhaustive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the hierarchy shape, then the default behavior, then the workflow hint. No filler and every sentence carries information an agent needs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value explanation is unnecessary, and the description still covers default behavior and the call chain for each level. The only omissions are pagination/volume expectations for large listings, which matters mildly for a browse tool with four optional filters.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% with examples for each param, so the baseline is 3; the description adds genuine meaning on top by explaining the hierarchy ordering and that subject_group identifiers come from a previous response rather than being constructed by the agent. That prerequisite is not derivable from the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource — 'Navigate the Title 38 hierarchy (title → chapter → part → subpart → subject group) and list what exists at a level' — and enumerates the exact levels, so an agent knows precisely what the tool returns. It also implies the boundary versus search_regulations ('before searching') and get_section, which is enough to separate it from siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says when to use it ('to discover valid parts/subparts before searching') and describes the no-argument default ('returns the chapters'). The chained-call guidance ('pass the subject_group identifier you saw in a previous response') tells the agent the prerequisite for deeper levels. It stops short of naming specific alternative tools by name, which keeps it at 4.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
diff_issuesDiff two eCFR issue datesARead-onlyIdempotent
Provision-level diff of Title 38 between two eCFR issue dates: which citations changed, which were added, and which were removed between from_date and to_date (both YYYY-MM-DD, both required — this tool has no default-to; for "what changed since my index was built" run cfr-free diff <from> locally, which resolves to from the index issue date). Stateless: both dates' full-title XML is fetched from eCFR and hashed per citation on the fly — no local index needed, no cache. Each citation list is capped at 100 entries; truncated: true means at least one list was cut. Identical dates or an impossible calendar date (e.g. month 13) are rejected as errors before any fetch.
| Name | Required | Description | Default |
|---|---|---|---|
| to_date | Yes | eCFR issue date, YYYY-MM-DD | |
| from_date | Yes | eCFR issue date, YYYY-MM-DD |
Output Schema
| Name | Required | Description |
|---|---|---|
| added | Yes | citations added at the to-date — capped at 100 |
| notice | Yes | the limit on what the two dates mean: eCFR serves the revision in force on the requested date and does not report which issue date that is, so the dates are selectors and not a stamp of the revision served |
| changed | Yes | citations whose content changed — text or heading — capped at 100 |
| removed | Yes | citations removed by the to-date — capped at 100 |
| truncated | Yes | true when any citation list was cut at the 100-citation cap |
| toIssueDate | Yes | |
| fromIssueDate | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover readOnly/idempotent/non-destructive, but the description adds genuinely new behavioral facts: stateless operation, full-title XML fetched from eCFR and hashed per citation, no cache or local index, and a 100-entry cap per list surfaced via `truncated: true`. The error-before-fetch behavior for invalid dates is also disclosed. Only gap is no indication of latency/cost implications of fetching two full-title XML documents.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the purpose and the changed/added/removed triad, then alternative routing, then behavior, then error cases — a sensible order. The parenthetical about the local CLI is long and dense, but it carries real routing value rather than filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, yet the description still flags the one return-value detail an agent must act on (`truncated: true` meaning a list was cut) plus the 100-entry cap. Combined with stated statelessness, fetch behavior, and error preconditions, nothing an agent needs to call this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and already documents both params as YYYY-MM-DD, so the baseline is 3; the description adds real value by stressing both are required with no default-to and that identical or impossible dates are rejected pre-fetch. It does not elaborate on the directional semantics beyond the names `from_date`/`to_date`.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Provision-level diff of Title 38 between two eCFR issue dates') and enumerates the three output categories (changed, added, removed). It clearly separates itself from the local CLI `cfr-free diff`, but never mentions the sibling tool `redline`, so an agent must infer how this differs from that overlapping diff tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly names the alternative workflow: 'for "what changed since my index was built" run `cfr-free diff <from>` locally, which resolves `to` from the index issue date.' It also states the precondition that both dates are required and that there is no default-to, plus the rejection conditions (identical dates, impossible calendar dates).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_sectionRead a CFR sectionARead-onlyIdempotent
Fetch the full text of one Title 38 CFR section or appendix as markdown. Accepts flexible citations: "4.125", "§ 4.125", "38 CFR 4.125", or appendix names like "Appendix A to Part 4". Returns heading, hierarchy path, issue date and an ecfr.gov permalink.
| Name | Required | Description | Default |
|---|---|---|---|
| citation | Yes | the citation to read, e.g. "38 CFR 4.125" |
Output Schema
| Name | Required | Description |
|---|---|---|
| kind | Yes | |
| part | Yes | |
| path | Yes | |
| text | Yes | |
| notice | Yes | |
| chapter | Yes | |
| citedBy | Yes | |
| heading | Yes | |
| subpart | Yes | |
| citation | Yes | |
| markdown | Yes | |
| issueDate | Yes | |
| sourceUrl | Yes | |
| indexStale | Yes | |
| references | Yes | |
| sourceUrlAtIndexDate | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, openWorld=false, so the safety profile is covered; the description adds the output medium (markdown) and names the returned fields (heading, hierarchy path, issue date, permalink). It doesn't say what happens on an ambiguous or unmatched citation, which is the main remaining behavioral unknown for a lookup tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, each doing distinct work: what it returns, what input it accepts, what comes back. No repetition of the name or title and no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter read with full schema coverage, complete annotations, and a declared output schema, the description covers purpose, accepted input forms, and output medium. Nothing an agent needs in order to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, and the description earns above that by enumerating genuinely different citation syntaxes (bare section, §-prefixed, fully qualified, appendix names) that the schema only gestures at with one example. It stops short of stating whether leading/trailing whitespace or part qualifiers are tolerated.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Starts with a specific verb+resource pair ("Fetch the full text of one Title 38 CFR section or appendix") plus the output format (markdown), which is far more informative than the title. It doesn't disambiguate from the similarly named sibling get_section_at, so an agent comparing the two must infer the difference from the sibling's schema rather than this text.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage context is implied by "one ... section or appendix" (i.e., a single known citation, not a search), but there is no explicit when-to-use vs. search_regulations, browse_title38, or get_section_at. The accepted-format list helps the agent shape input more than it helps choose this tool over its siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_section_atRead one provision as it stood at a dateARead-onlyIdempotent
One Title 38 provision as it stood at a requested date (YYYY-MM-DD, required). eCFR serves the revision IN FORCE on that date, so a date that is not itself an issue date resolves to the revision that was in force then; the upstream does not report which issue date it served, so the response names the REQUESTED date and never a served-revision date. Stateless: the revision is fetched from eCFR on every call — no local index, no cache. eCFR's point-in-time archive starts at 2017-01-01; a date before that is rejected as not_found. Use this for date-of-claim and CUE questions ("what did this section say when the claim was filed?"); for the CURRENT text use get_section.
| Name | Required | Description | Default |
|---|---|---|---|
| at_date | Yes | eCFR issue date, YYYY-MM-DD | |
| citation | Yes | the citation to read, e.g. "38 CFR 4.130" |
Output Schema
| Name | Required | Description |
|---|---|---|
| kind | Yes | |
| part | Yes | |
| path | Yes | |
| text | Yes | |
| chapter | Yes | |
| heading | Yes | |
| subpart | Yes | |
| citation | Yes | |
| markdown | Yes | |
| sourceUrl | Yes | |
| requestedDate | Yes | |
| sourceUrlAtRequestedDate | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the read-only/idempotent annotations, the description discloses key behavior: eCFR serves the revision in force on the date, the response names the requested date rather than the served revision, and calls are stateless with no cache. It also states that dates before 2017-01-01 are rejected as not_found.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with purpose, followed by date-resolution behavior, statelessness, archive boundary, and usage routing. It is dense but every sentence contributes, and there is no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given two required params, full schema coverage, an output schema, and read-only annotations, the description supplies everything an agent needs: date semantics, rejection boundary, statelessness, and sibling differentiation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description adds real semantic value about at_date: it resolves to the revision in force, the upstream does not report the served issue date, and pre-2017 dates are rejected. Citation semantics are largely left to the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: reading one Title 38 provision as it stood at a requested date. It explicitly distinguishes itself from the sibling get_section by naming that alternative for current text.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit use cases for date-of-claim and CUE questions, and explicitly routes current-text requests to get_section. The before-2017 exclusion is also stated as a rejection condition.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
healthIndex readinessARead-onlyIdempotent
One-call readiness verdict for the local index: "ready" (query away), "stale" (older than CFR_MAX_AGE_DAYS — verify results against the current eCFR), "degraded" (structure drift — provisions may be missing; verify against the current eCFR), or "empty" (no index yet — run cfr-free sync). Clients should gate queries on this before spending tool calls.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| stale | Yes | |
| status | Yes | |
| syncedAt | Yes | |
| issueDate | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, so the safety profile is covered. The description adds real value beyond that: the semantics of each state, the remediation for 'empty' (run `cfr-free sync`), and the obligation to verify 'stale'/'degraded' results against the current eCFR. It does not mention latency or whether the check is cheap, so a 4 rather than a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Single front-loaded sentence that leads with the verdict concept, then defines each state inline. Dense with nested parentheses and em-dashes, which makes it slightly harder to scan, but no sentence is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present the description need not explain return values, yet it still defines the four verdicts and their follow-up actions — more than enough for a zero-param read tool. The one gap is failing to clarify the boundary with the sibling 'index_status'.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is nothing for the description to disambiguate; the baseline of 4 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('one-call readiness verdict for the local index') and enumerates all four possible verdicts with their meanings, so an agent knows exactly what comes back. It does not, however, distinguish itself from the sibling 'index_status', which sounds like an overlapping concept — that ambiguity keeps it from a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Clients should gate queries on this before spending tool calls' gives an explicit when-to-use rule and even a cost rationale. It stops short of naming an alternative or a when-not-to-use case (e.g., after a sync is known to be fresh), and never mentions the similar-sounding 'index_status' sibling.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
index_statusLocal index freshnessARead-onlyIdempotent
Report how current the local Title 38 index is: eCFR issue date, last sync time, row counts, the corpus receipt hash (a sha256 over the sorted per-provision (citation, text+heading) pairs at rebuild time, null on older indexes) and whether the index is stale relative to CFR_MAX_AGE_DAYS. Check this before relying on search results.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| stale | Yes | |
| sections | Yes | |
| syncedAt | Yes | |
| issueDate | Yes | |
| appendices | Yes | |
| corpusHash | Yes | |
| maxAgeDays | Yes | |
| lastChanges | Yes | |
| parserVersion | Yes | |
| structureDrift | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, so the safety profile is carried. The description adds value annotations cannot: the receipt hash is a sha256 over sorted (citation, text+heading) pairs computed at rebuild time and is null on older indexes, and staleness is judged against the CFR_MAX_AGE_DAYS threshold. That is genuine behavioral context about data provenance and edge cases.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences: the first front-loads the verb and resource then enumerates the report contents, the second gives the actionable rule. The parenthetical about the receipt hash is long but every clause carries information an agent cannot get from the schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be re-explained, and annotations plus the description together cover safety, provenance, and staleness. The one gap is what an agent should do with a stale verdict beyond 'check this before relying' — no remediation or fallback path is described.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so there is no parameter semantics to explain and the baseline is 4. Nothing in the description misleadingly implies inputs that do not exist.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb ('Report') plus specific resource ('the local Title 38 index') with the exact fields returned enumerated: eCFR issue date, sync time, row counts, receipt hash, staleness. That is far beyond tautology. It does not, however, draw any line against the sibling 'health' tool, which is the most likely confusion for an agent deciding between status-style tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Check this before relying on search results' gives a clear triggering condition tied to search_regulations. It stops short of naming alternatives or saying what to do when the report comes back stale, so it is clear context rather than a full when/when-not/alternatives rule set.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_partsList CFR parts in the local indexARead-onlyIdempotent
Enumerate every Title 38 CFR part present in the local index, with its structure-tree title and section/appendix counts. Use this to discover which parts are searchable.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| parts | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive, and closed-world behavior, so the safety profile is fully covered. The description adds that results come specifically from the 'local index' and what fields are returned, but the return fields are already in the output schema, so the incremental behavioral disclosure is modest.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences: the first states scope and return content, the second states the use case. Front-loaded and free of filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With a rich output schema and complete annotations, the description covers purpose, scope, and use case, leaving little for an agent to guess. Only minor details (ordering, whether the index can be stale/incomplete) are unstated.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There are zero parameters, so the baseline is 4; nothing about parameter meaning needs documenting and the schema fully covers the (empty) input.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Enumerate') and resource ('Title 38 CFR part') scoped to the local index, and states the returned fields (structure-tree title, section/appendix counts). It is clear on its own, but it never names or contrasts with siblings like browse_title38 or search_regulations, which could plausibly overlap.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Use this to discover which parts are searchable' gives a concrete when-to-use condition tied to a downstream action. It stops short of stating when-not-to-use or pointing to an alternative sibling for related tasks, so it is clear context without exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
redlineLine redline of one provision between two eCFR issue datesARead-onlyIdempotent
Line-level redline of one Title 38 provision between two eCFR issue dates: which lines of citation changed, were added, or were removed between from_date and to_date (both YYYY-MM-DD, both required — this tool has no default-to). The dates are SELECTORS — eCFR serves the revision in force on the requested date; they are not a verified stamp of which issue date was served. Returns the status (changed/added/removed/unchanged), both sides' raw text, and one hunk per line (same/add/del with 1-based anchors into each side's text). Identical or impossible dates (e.g. month 13) are rejected as errors before any fetch; a citation missing from BOTH dates is a not_found error after the fetches. Stateless: both dates' full-title XML is fetched from eCFR per call — no local index needed, no cache. When either side exceeds the core's line cap the per-line walk is replaced by two whole-text coarse blocks (coarse: true) — passed through uncapped here, the core's own bound.
| Name | Required | Description | Default |
|---|---|---|---|
| to_date | Yes | eCFR issue date, YYYY-MM-DD | |
| citation | Yes | the citation to redline, e.g. "38 CFR 4.125" (≤ 120 chars) | |
| from_date | Yes | eCFR issue date, YYYY-MM-DD |
Output Schema
| Name | Required | Description |
|---|---|---|
| hunks | Yes | |
| coarse | Yes | |
| notice | Yes | the limit on what the two dates mean: eCFR serves the revision in force on the requested date and does not report which issue date that is, so the dates are selectors and not a stamp of the revision served |
| status | Yes | |
| toText | No | |
| citation | Yes | |
| fromText | No | |
| toIssueDate | Yes | |
| fromIssueDate | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/idempotent annotations: discloses that dates are selectors rather than verified issue stamps, that full-title XML is refetched per call with no cache, and the exact error ordering (date validation before fetch, not_found after fetch). Also explains the coarse-block degradation when the core line cap is exceeded.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose and organized around output, error behavior, and statelessness, with no filler sentences. It is dense to the point of being a single long paragraph, which slightly hurts scanability.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a stateless remote-fetch diff tool, it covers return shape, failure modes, cost implications (full-title fetch per call), and degraded-mode output, and an output schema already documents the payload. Nothing an agent needs in order to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real meaning the schema cannot: the dates act as selectors for the revision in force, not as a guarantee of which issue date was served. The citation parameter itself gets no added semantics beyond the schema example.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: a line-level redline of one Title 38 provision between two eCFR issue dates, enumerating the outcomes (changed/added/removed/unchanged). It is clearly narrower than whole-title siblings like diff_issues or single-date get_section_at.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Clear usage context: both dates are required and there is no default-to, so the agent knows it must supply a comparison window. However, it never names diff_issues or get_section_at as alternatives, so routing between the comparison tools is left implicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_regulationsSearch Title 38 CFR regulationsARead-onlyIdempotent
Full-text search across the local Title 38 (VA) CFR index using ranked keyword matching. Returns section citations, headings, relevance snippets, bm25 scores (lower is better) and the query terms visible in each hit. The CANDIDATE SET is every provision matching AT LEAST ONE query term, so a provision that answers part of the question stays in the results instead of being excluded by the words it does not use; total counts those candidates (plus the pinned citation when the candidate set did not already hold it). The hits are ordered by: (1) a hit whose HEADING carries the query's content terms as a phrase (or the corpus phrase a synonym stands for), (2) a hit matching EVERY query term ahead of one matching only some, (3) bm25, lower is better. Use part to scope results to a single CFR part (e.g. "4" for ratings, "17" for prosthetics), kind to restrict results to one provision kind ("section" or "appendix"), and chapter to scope to one chapter by its Roman-numeral identifier (e.g. "I"; exact, case-sensitive). Follow up with get_section to read a hit in full. If the query itself is a citation (e.g. "4.130" or "38 CFR 4.16"), that exact section is pinned to the top of page 1 (totals include the pin on every page) and marked direct: true — the reliable client signal; the pinned hit's inResults flag reports whether it also matched the ranked results. Quoted spans (e.g. "exact regulatory language") are emitted as FTS5 phrase queries — the last token gets a prefix wildcard (so "language" also matches "languages"); earlier tokens are matched exactly. Hits are ranked by LEXICAL OVERLAP with the query, not by legal authority: the first hit is the provision sharing the most words with the query, which is often not the provision that governs the question. Read each hit's heading and fetch its full text with get_section before you cite it. Each response also carries matchedTerms and unmatchedTerms: the query's content words (stopwords, single characters and bare numerals removed), split into those the returned hits answered and those they did not. A non-empty unmatchedTerms means the returned text does not speak to that part of your question — the governing provision may be a later hit, may be reachable only under different wording, or may not be in this index. Never cite a provision you have not read in full, and never state that Title 38 does not address a question on the basis of this response alone.
| Name | Required | Description | Default |
|---|---|---|---|
| kind | No | restrict to one provision kind: section or appendix | |
| page | No | 1-based result page (default 1) | |
| part | No | restrict to one CFR part number | |
| query | Yes | search terms, e.g. "traumatic brain injury compensation" | |
| chapter | No | restrict to one chapter, exact case-sensitive Roman numeral (e.g. "I") | |
| per_page | No | results per page (default 10, max 20) |
Output Schema
| Name | Required | Description |
|---|---|---|
| hits | Yes | |
| page | Yes | |
| query | Yes | |
| total | Yes | |
| notice | Yes | |
| perPage | Yes | |
| matchMode | Yes | |
| indexStale | Yes | |
| matchedTerms | Yes | |
| indexIssueDate | Yes | |
| unmatchedTerms | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial behavior beyond the read-only annotations: candidate-set semantics, three-tier ranking order, bm25 interpretation, citation pinning with `direct` and `inResults`, quoted-phrase/prefix behavior, and explicit caveats that ranking is lexical not authoritative. These details materially affect how an agent should interpret and cite results.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the purpose and core search semantics, and nearly every sentence carries operational meaning. It is dense and long, but the length is largely justified by the complexity of ranking, pinning, and matchedTerms behavior.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given six parameters, full schema descriptions, an output schema, and read-only annotations, the description covers what an agent needs: result ordering, candidate-set scope, citation pinning, matched/unmatched terms, and citation-safety guidance.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description still adds useful semantics: example part numbers ('4' for ratings, '17' for prosthetics), clarification that chapter is exact and case-sensitive, and the effect of `page` on pinned citation totals.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: full-text search across the local Title 38 CFR index. It clearly distinguishes itself from siblings by telling the agent to follow up with get_section to read a hit in full.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides concrete scoping guidance for `part`, `kind`, and `chapter`, and explains that citation queries are pinned. It does not explicitly say when to use browse_title38 or get_section_at instead, but the follow-up workflow with get_section is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
7 tool updates
v0.2.1- Changed
browse_title381 field changed- added
Input schema / properties / subject_groupAdded value: +{ + "description": "subject-group identifier, e.g. \"ECFRd158704dcab040e\" (requires part)", + "maxLength": 40, + "type": "string" +}
- Added
diff_issues - Changed
get_section3 fields changed- added
Output schema / properties / citedByAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / sourceUrlAtIndexDateAdded value: +{ + "type": "string" +} - changed
Output schema / requiredPrevious value: -[ - "citation", - "kind", - "heading", - "chapter", - "part", - "subpart", - "path", - "issueDate", - "markdown", - "text", - "sourceUrl", - "references", - "indexStale", - "notice" -]New value: +[ + "citation", + "kind", + "heading", + "chapter", + "part", + "subpart", + "path", + "issueDate", + "markdown", + "text", + "sourceUrl", + "sourceUrlAtIndexDate", + "references", + "citedBy", + "indexStale", + "notice" +]
- Added
get_section_at - Changed
index_status2 fields changed- added
Output schema / properties / corpusHashAdded value: +{ + "type": [ + "string", + "null" + ] +} - changed
Output schema / requiredPrevious value: -[ - "issueDate", - "syncedAt", - "sections", - "appendices", - "stale", - "maxAgeDays", - "parserVersion", - "structureDrift", - "lastChanges" -]New value: +[ + "issueDate", + "syncedAt", + "sections", + "appendices", + "stale", + "maxAgeDays", + "parserVersion", + "structureDrift", + "corpusHash", + "lastChanges" +]
- Added
redline - Changed
search_regulations8 fields changed- added
Input schema / properties / chapterAdded value: +{ + "description": "restrict to one chapter, exact case-sensitive Roman numeral (e.g. \"I\")", + "maxLength": 8, + "minLength": 1, + "pattern": "^[IVXLCDM]+$", + "type": "string" +} - added
Input schema / properties / kindAdded value: +{ + "description": "restrict to one provision kind: section or appendix", + "enum": [ + "section", + "appendix" + ], + "type": "string" +} - added
Output schema / properties / hits / items / properties / partTitleAdded value: +{ + "type": [ + "string", + "null" + ] +} - added
Output schema / properties / hits / items / properties / sourceUrlAtIndexDateAdded value: +{ + "type": "string" +} - changed
Output schema / properties / hits / items / requiredPrevious value: -[ - "citation", - "citationDisplay", - "sourceUrl", - "direct", - "heading", - "part", - "path", - "snippet", - "score", - "matches", - "inResults" -]New value: +[ + "citation", + "citationDisplay", + "sourceUrl", + "sourceUrlAtIndexDate", + "direct", + "heading", + "part", + "partTitle", + "path", + "snippet", + "score", + "matches", + "inResults" +] - added
Output schema / properties / matchedTermsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / unmatchedTermsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - changed
Output schema / requiredPrevious value: -[ - "query", - "page", - "perPage", - "total", - "matchMode", - "indexIssueDate", - "indexStale", - "notice", - "hits" -]New value: +[ + "query", + "page", + "perPage", + "total", + "matchMode", + "indexIssueDate", + "indexStale", + "notice", + "matchedTerms", + "unmatchedTerms", + "hits" +]
6 tool updates
v0.1.0- First observed
browse_title38 - First observed
get_section - First observed
health - First observed
index_status - First observed
list_parts - First observed
search_regulations
TDQS
Scored across 9 tools
Two pairs overlap meaningfully: `health` and `index_status` both report index readiness/staleness (health is a verdict, index_status is a report), and `list_parts` and `browse_title38` both serve hierarchy discovery. The temporal tools (get_section vs get_section_at, diff_issues vs redline) are clearly delineated, but the agents still have to guess between the readiness and discovery pairs.
Most tools follow a verb_noun pattern (list_parts, search_regulations, get_section, browse_title38, diff_issues), with `get_section_at` a sensible variant. `health` and `redline` are bare noun/verb outliers that break the pattern, though they remain readable.
Nine tools is well scoped: discovery, search, current and historical retrieval, change detection, and readiness checks each earn their place without bloat.
Coverage is strong across the CFR lifecycle: discovery, full-text search, current and point-in-time retrieval, title-level diff, and line-level redline. Minor gaps remain — no tool to trigger or repair a sync/index rebuild (CLI-only), and historical text is only retrievable per-provision, not searchable.
Maintenance
Related MCP Connectors
Read a CFR or eCFR section, or search US federal regulations and the Federal Register.
Veterans benefits and VA claims reference: VA disability ratings, compensation rates, 38 CFR search
Search and read US federal regulations with legal and inline citations linking to ecfr.io.
Search, quote and link U.S. federal procurement regulations from official sources.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to access US FDA regulations (21 CFR) through natural language queries, providing tools for retrieving regulatory information.215 npmMIT
- AlicenseAqualityAmaintenanceEnables 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.71MIT
- FlicenseNot gradedqualityCmaintenanceEnables 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.-
- AlicenseAqualityBmaintenanceEnables searching and reading the US Code of Federal Regulations through tools that return excerpts, full text, legal citations, and linked source URLs, with read-only, stateless access.3MIT