Back to Directory/Developer Tools

io.github.cyanheads/federal-regulations-mcp-server

Search and trace US federal rules across the Federal Register, eCFR, and Regulations.gov.

Developer ToolsTypeScriptv0.5.2

@cyanheads/federal-regulations-mcp-server

Search and trace US federal rules across the Federal Register (proposed/final rules and notices), the eCFR (codified, point-in-time CFR full text, locally mirrored), and Regulations.gov (rulemaking dockets and public comments) via MCP. STDIO or Streamable HTTP.

7 Tools • 2 Resources

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework


Overview

US federal regulatory law across three official sources: the Federal Register (proposed/final rules and notices), the eCFR (codified, point-in-time CFR text), and Regulations.gov (rulemaking dockets and public comments). Search rules, trace a document from proposal through comments to codified text, and read CFR sections from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.

Tools

ToolDescription
regulations_search_rulesSearch Federal Register proposed rules, final rules, notices, and presidential documents by query, type, agency, and date range, ranked by relevance or date
regulations_get_documentFetch one Federal Register document by number, with the docket ID, CFR parts, and comment count that chain into other tools
regulations_browse_cfrList CFR titles, a title's chapters, or every section and appendix in a part, or full-text-search the codified CFR
regulations_get_cfr_sectionRead codified CFR text for a section, whole part, or appendix, current or as of a past date
regulations_get_docketPull a rulemaking docket and its filed documents from Regulations.gov (key required)
regulations_find_commentsFetch public comments on a document or docket, or one comment's full body and attachments (key required)
regulations_list_open_commentsList proposed rules, comment-requesting final rules, and optionally notices currently open for public comment, soonest closing first

Resources

ResourceDescription
regulations://document/{documentNumber}A single Federal Register document — metadata plus cross-source handles (mirrors regulations_get_document)
regulations://cfr/{title}/{part}/{section}Codified text of a current CFR section (mirrors regulations_get_cfr_section)

All resource data is also reachable via the tool surface — tool-only MCP clients lose nothing.

Capability reference

regulations_search_rules tool

  • Keyless; full-text query optional, or browse by type (PRORULE/RULE/NOTICE/PRESDOCU), agencies (Federal Register slug, e.g. environmental-protection-agency), and published_after/published_before (real calendar days, YYYY-MM-DD)
  • order is relevance, newest, or oldest; omitted, it is relevance with a query and newest without one
  • per_page 2–100 (default 20), page 1–50 — the Federal Register caps navigation at 50 pages / 5,000 records and total matches at 10,000; narrow the date window rather than paging deeper
  • Each result carries documentNumber (→ regulations_get_document), agencies as { name, slug } (the slug feeds back into agencies; null when the Federal Register lists an agency by raw name only), docketIds (→ regulations_get_docket / find_comments), regulationIdNumbers, and cfrReferences (→ regulations_get_cfr_section)
  • invalid_filter when the Federal Register rejects a filter value (an agency name or acronym instead of a slug), naming the parameter; upstream_unavailable on a 5xx/timeout, retryable after a brief wait

regulations_get_document tool

  • Keyless; fetch one document by document_number (format \d{4}-\d+, e.g. 2025-14555)
  • Full metadata (title, type, agencies as { name, slug }, abstract, action, effective/comment dates, RINs) plus cross-source handles: docketId, regulationsGovDocumentId, commentCount, and cfrReferences
  • include_full_text inlines the plain-text body as a character window: max_chars (default 64,000, up to 200,000) from offset (default 0). fullTextLength reports the whole body's length and fullTextNextOffset the offset to resume from while text remains; documents of about ten printed pages or fewer come back whole, while a major final rule runs past a million characters
  • Passing offset or max_chars implies include_full_text; pairing either with an explicit include_full_text: false fails as full_text_disabled. An offset at or past the end returns empty fullText with the length and a notice
  • fullText is plain text: links reduce to their text, and email addresses the published body obfuscates are decoded
  • not_found when the FR number doesn't exist; upstream_unavailable on a 5xx/timeout

regulations_browse_cfr tool

  • Keyless; mode: "structure" lists all 50 titles, a title's top-level divisions (chapters or subtitles), or — with title + part — every section and appendix in the part, flattened, each carrying its subpart and subjectGroup; mode: "search" full-text-searches the codified CFR
  • title (1–50) and part scope both modes; a part without title is rejected (title_required_for_part) since part numbers repeat across titles
  • date is point-in-time in both modes and must be a real calendar day; structure mode rejects a date past the title's up-to-date date (date_out_of_range), and search indexes 2017-01-03 onward
  • page (1-based, default 1) and per_page (1–50, default 20) page search results and a part's listing; page and totalCount come back with a notice naming the next page, and a page past the end returns no rows plus the last page. Labels are plain text (eCFR's inline markup stripped)
  • Search returns one row per section: eCFR's live index answers one hit per section version, so repeats are collapsed. countBasis says what totalCount counts — sections, or eCFR's own section_versions count until the whole hit list has been read. Live search pages through eCFR's first 10,000 hits only; a page past them fails as page_out_of_window
  • Every search result reports source (mirror/live) and sourceScope — the mirror only answers a title it holds, never an all-titles query when scoped, so anything it can't answer falls through to the live eCFR API
  • query_required when mode="search" has no query; title_not_found / date_out_of_range / page_out_of_window / upstream_unavailable round out the errors

regulations_get_cfr_section tool

  • Keyless; reads one section (title+part+section), a whole part (title+part, section omitted), or one appendix (title(+part)+appendix) — section and appendix are mutually exclusive (conflicting_target)
  • section accepts the cite the way people write it: "61" in part 141, "§ 141.61", "Sec. 141.61", and "141.61(c)" (paragraph designators dropped; the whole section comes back) all read 40 CFR 141.61. An identifier that resolves as given is never rewritten (14 CFR 241 "25", 26 CFR "48.4061(a)"); a rewrite returns the identifier read in section / cfrCite plus a notice
  • Text is one window of bodyText: offset (default 0) and max_chars (default 64,000, max 200,000) select it, and bodyTextOffset / bodyTextLength / bodyTextNextOffset (present while text remains) page through it — the same contract as regulations_get_document's full text. An offset past the end is an empty window with a notice
  • A whole-part fetch adds sections[], a text-free index of the sections in the window (section, heading, cfrCite, and the offset each starts at in the part's text — pass it as offset to jump there)
  • date for point-in-time text, 2017-01-01 through the title's up-to-date date; anything outside that is date_out_of_range, checked before any text request, and a date that is not a real calendar day is rejected at input validation
  • appendix must be passed verbatim as eCFR / regulations_browse_cfr emits it (e.g. Appendix A-1 to Part 50), not a short form
  • A whole-part fetch lists its appendices' identifiers and headings without inlining their text — call again with appendix to read one
  • source (mirror/live) reports provenance; current single-section reads are mirror-served when ready, everything else (historical dates, whole-part, appendix reads) falls back to the live eCFR versioner
  • not_found / location_required / upstream_unavailable round out the errors

regulations_get_docket tool · key required

  • Requires REGULATIONS_GOV_API_KEY; fetch a docket by docket_id (e.g. EPA-HQ-OAR-2025-0194)
  • document_types filters to Proposed Rule / Rule / Notice / Supporting & Related Material / Other — a docket often holds hundreds of supporting materials
  • per_page 5–250 (default 25), page 1–20 — Regulations.gov caps a query at 5,000 records
  • Each document's objectId chains into regulations_find_comments; frDocNum chains back to regulations_get_document
  • auth_required (missing/rejected key) names the env var and signup URL; not_found / rate_limited (429, 1,000 req/hr) / upstream_unavailable round out the errors

regulations_find_comments tool · key required

  • Requires REGULATIONS_GOV_API_KEY; exactly one of docket_id, document_object_id, fr_document_number, or comment_id — zero or two is rejected (target_required / multiple_targets), never resolved by precedence
  • comment_id returns one comment's full body and attachments; the other three list a set — the list endpoint carries no body text, so read a comment's substance via comment_id
  • When a comment's substance is a PDF/DOCX attachment, bodyText is a stub and attachmentOnly is true, with the attachment download URLs
  • per_page 5–250 (default 25), page 1–20 — Regulations.gov caps a query at 5,000 records; narrow a high-volume docket with document_object_id
  • auth_required / not_found / rate_limited (429, 1,000 req/hr) / upstream_unavailable round out the errors

regulations_list_open_comments tool

  • Keyless; lists documents currently open for public comment, sorted by closing date soonest first (same-day closes by document number) across the whole open window — filter by type, query, agencies (Federal Register slug), and closing_before (a real calendar day, YYYY-MM-DD)
  • type takes PRORULE, RULE, and NOTICE; the default (also used for an empty list) is ["PRORULE", "RULE"] — proposed rules plus the direct final and interim final rules that take comment. NOTICE adds several hundred information-collection and other notices
  • per_page 1–100 (default 20), page from 1; totalCount, totalPages, and nextPage describe the window. The Federal Register can't sort by comment date, so the window is fetched whole (one request up to 2,000 documents, then 2,000 per request) and paged locally; past the Federal Register's 10,000-document limit the response is truncated and holds the 10,000 most recently published matches
  • Each row carries daysRemaining, documentNumber (→ regulations_get_document), agencies as { name, slug }, and docketIds (→ regulations_find_comments)
  • Fully functional keyless; when REGULATIONS_GOV_API_KEY is set, commentCount is enriched from the Federal Register document's own embedded Regulations.gov info (no extra call) — keyed reports which
  • invalid_filter when the Federal Register rejects a filter value, naming the parameter; upstream_unavailable on a 5xx/timeout

regulations://document/{documentNumber} resource

  • Same payload as regulations_get_document without full text — metadata plus cross-source handles, the body never inlined
  • documentNumber format \d{4}-\d+ (e.g. 2025-14555)
  • not_found / upstream_unavailable mirror the tool's errors

regulations://cfr/{title}/{part}/{section} resource

  • Same payload as regulations_get_cfr_section at the current date and its default 64,000-character window — sections only; read an appendix via the tool's appendix input instead
  • The section resolves as the tool resolves it ("61", "§ 141.61", "141.61(c)"), with a notice naming the rewrite; a section longer than one window carries bodyTextNextOffset, and the rest is read through the tool's offset
  • Mirror-backed with a live eCFR fallback; source (mirror/live) reports provenance
  • not_found / upstream_unavailable mirror the tool's errors

Features

Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.

Federal Register / eCFR / Regulations.gov-specific:

  • One workflow over three official sources — the agent sees regulatory verbs (search_rules, get_cfr_section, find_comments), not three API clients
  • Cross-source stitching — every Federal Register document surfaces its docket ID and CFR-part handles next to the tools that consume them, priming the proposal → comments → final → codified-text trace
  • Keyless core — the Federal Register + eCFR tools (5 of 7) are a complete deployment with no API key; the Regulations.gov leg (REGULATIONS_GOV_API_KEY) layers on top
  • Locally mirrored codified CFR — the eCFR is synced once into embedded SQLite + FTS5 and queried by exact cite or full text, falling back to the live API whenever the mirror's title coverage can't answer
  • A 45-second wall-clock budget per request, shared across every upstream call, retry, and backoff a tool makes — a stalled source answers with an actionable upstream_unavailable well inside a client's request timeout

Agent-friendly output:

  • Provenance — source: "mirror" | "live" on every CFR read, and a sourceScope line on search naming what that corpus covers
  • Honest truncation — Federal Register (50-page/5,000-record) and Regulations.gov (20-page/5,000-record) ceilings are surfaced via truncated/notice enrichment, never silently dropped
  • Attachment-aware comments — attachmentOnly flags when a comment's substance is a file rather than inline text, with the download URLs, on both the structured and text surfaces
  • Actionable auth_required errors — the two keyed tools name the env var and free signup URL rather than passing through a raw 401/403

Getting started

Public Hosted Instance

A public instance is available at https://federal-regulations.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:

{
  "mcpServers": {
    "federal-regulations-mcp-server": {
      "type": "streamable-http",
      "url": "https://federal-regulations.caseyjhand.com/mcp"
    }
  }
}

Self-Hosted / Local

Add the following to your MCP client configuration file. The Federal Register and eCFR tools work with no key; set REGULATIONS_GOV_API_KEY (free at api.data.gov/signup) to enable the Regulations.gov docket and comment tools.

{
  "mcpServers": {
    "federal-regulations-mcp-server": {
      "type": "stdio",
      "command": "bunx",
      "args": ["@cyanheads/federal-regulations-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "MCP_LOG_LEVEL": "info",
        "REGULATIONS_GOV_API_KEY": "your-key-here"
      }
    }
  }
}

Or with npx (no Bun required):

{
  "mcpServers": {
    "federal-regulations-mcp-server": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@cyanheads/federal-regulations-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "MCP_LOG_LEVEL": "info",
        "REGULATIONS_GOV_API_KEY": "your-key-here"
      }
    }
  }
}

Or with Docker:

{
  "mcpServers": {
    "federal-regulations-mcp-server": {
      "type": "stdio",
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "MCP_TRANSPORT_TYPE=stdio",
        "-e", "REGULATIONS_GOV_API_KEY=your-key-here",
        "ghcr.io/cyanheads/federal-regulations-mcp-server:latest"
      ]
    }
  }
}

Omit the REGULATIONS_GOV_API_KEY line entirely to run the keyless core (Federal Register + eCFR). The two Regulations.gov tools then return an actionable auth_required error, and regulations_list_open_comments runs without comment counts.

For Streamable HTTP, set the transport and start the server:

MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp

Prerequisites

  • Bun v1.3 or higher (or Node.js v24+).
  • Optional: a free api.data.gov key for the Regulations.gov tools (regulations_get_docket, regulations_find_comments, and comment counts in regulations_list_open_comments). The Federal Register and eCFR tools need no key. The shared key allows 1,000 requests/hour.

Installation

  1. Clone the repository:
git clone https://github.com/cyanheads/federal-regulations-mcp-server.git
  1. Navigate into the directory:
cd federal-regulations-mcp-server
  1. Install dependencies:
bun install
  1. Configure environment:
cp .env.example .env
# optionally set REGULATIONS_GOV_API_KEY

Configuration

All configuration is validated at startup via Zod schemas in src/config/server-config.ts. Key environment variables:

VariableDescriptionDefault
REGULATIONS_GOV_API_KEYapi.data.gov key for the Regulations.gov tools (get_docket, find_comments, and comment-count enrichment in list_open_comments). Optional — the Federal Register and eCFR tools work without it.—
FEDERAL_REGISTER_BASE_URLFederal Register API v1 base URL.https://www.federalregister.gov/api/v1
ECFR_BASE_URLeCFR API base URL.https://www.ecfr.gov/api
REGULATIONS_GOV_BASE_URLRegulations.gov API v4 base URL.https://api.regulations.gov/v4
ECFR_MIRROR_PATHFilesystem path for the eCFR SQLite mirror database../data/ecfr-mirror.sqlite
ECFR_MIRROR_REFRESH_CRONCron expression for an in-process mirror refresh (HTTP transport only), e.g. 0 4 * * 0. Unset registers no job. Each run re-harvests every configured title in full, and a tick is skipped until mirror:init has completed once.— (no job)
ECFR_MIRROR_TITLESComma-separated CFR title numbers to scope the mirror to (e.g. 21,40). Omit to mirror all 50 titles. Cites and searches outside the set fall through to the live eCFR API, as does any all-titles search while this is set.— (all titles)
MCP_TRANSPORT_TYPETransport: stdio or http.stdio
MCP_HTTP_PORTPort for the HTTP server.3010
MCP_AUTH_MODEAuth mode: none, jwt, or oauth.none
MCP_LOG_LEVELLog level (RFC 5424).info
STORAGE_PROVIDER_TYPEStorage backend.in-memory
OTEL_ENABLEDEnable OpenTelemetry instrumentation.false

See .env.example for the full list of optional overrides.

Running the server

Local development

  • Build and run:

    # One-time build
    bun run rebuild
    
    # Run the built server
    bun run start:stdio
    # or
    bun run start:http
    
  • Populate/refresh the eCFR mirror (out-of-band, idempotent — the codified-text tools work against the live eCFR API until this completes, so it's a latency optimization, not a hard dependency):

    bun run mirror:init      # full build across all 50 titles, resumable (scope with ECFR_MIRROR_TITLES)
    bun run mirror:refresh   # re-harvest every configured title against the latest eCFR issues
    bun run mirror:verify    # report row counts and the last-synced issue date
    
  • Run checks and tests:

    bun run devcheck   # Lint, format, typecheck, security, changelog sync
    bun run test       # Vitest test suite
    bun run lint:mcp   # Validate MCP definitions against the linter rules
    

Docker

docker build -t federal-regulations-mcp-server .
docker run --rm -e REGULATIONS_GOV_API_KEY=your-key -p 3010:3010 federal-regulations-mcp-server

The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/federal-regulations-mcp-server. The build stage installs dependencies with --ignore-scripts — better-sqlite3 is a build/ingest-only dependency whose native compile is skipped, and the runtime reads the mirror through Bun's built-in bun:sqlite. Populate the mirror in a running container with docker exec <container> bun run mirror:init; mount a volume over /usr/src/app/data so the synced index survives container recreation. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.

Project structure

DirectoryPurpose
src/index.tscreateApp() entry point — registers tools/resources, inits the three services, and schedules the mirror refresh on HTTP when ECFR_MIRROR_REFRESH_CRON is set.
src/configServer-specific environment variable parsing and validation with Zod.
src/mcp-server/toolsTool definitions (*.tool.ts) — the seven regulations_* tools.
src/mcp-server/resourcesResource definitions (*.resource.ts) — the document and CFR-section resources.
src/services/federal-registerFederal Register API v1 client (keyless).
src/services/ecfreCFR API client (keyless) — versioner, structure, search, and section XML parsing.
src/services/ecfr-mirroreCFR codified-text mirror (MirrorService — SQLite + FTS5), its read path, and the opt-in refresh job.
src/services/regulations-govRegulations.gov v4 client (X-Api-Key) — dockets and comments.
scripts/ecfr-mirror-*.tsOut-of-band mirror lifecycle: init, refresh, verify.
tests/Unit and integration tests mirroring src/.

Development guide

See CLAUDE.md for development guidelines and architectural rules. The short version:

  • Handlers throw, framework catches — no try/catch in tool logic
  • Use ctx.log for request-scoped logging, ctx.state for tenant-scoped storage
  • Register new tools and resources via the barrels in src/mcp-server/*/definitions/index.ts
  • Wrap external API calls: validate raw → normalize to the domain type → return the output schema; never fabricate missing upstream fields

Data disclaimer

eCFR content is "authoritative but unofficial" per the Office of the Federal Register — not the official legal edition of the Code of Federal Regulations; verify against govinfo.gov for legal research. This server is not affiliated with or endorsed by the Office of the Federal Register, the Government Publishing Office, or the General Services Administration.

Contributing

Issues are welcome. Run checks and tests before submitting:

bun run devcheck
bun run test

License

Apache-2.0 — see LICENSE for details.

Installation

Source-derived launch command. Check the maintainer’s required arguments and credentials before running:

bash
npx -y @cyanheads/federal-regulations-mcp-server

Set up in your AI client

Merge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.

json
{
  "mcpServers": {
    "io-github-cyanheads-federal-regulations-mcp-server": {
      "command": "npx",
      "args": [
        "-y",
        "@cyanheads/federal-regulations-mcp-server"
      ]
    }
  }
}

Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.

Claude Desktop setup reference

Package

@cyanheads/federal-regulations-mcp-servernpm

Compatible MCP Clients

io.github.cyanheads/federal-regulations-mcp-server works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.

  • Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.
  • Cursor~/.cursor/mcp.jsonRestart Cursor for changes to take effect.
  • VS Code.vscode/mcp.jsonReload VS Code window for changes to take effect.
  • Windsurf~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect.
  • Claude Code.mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.

Learn More