Skip to main content
Glama
Reid-n0rc

fcc-ecfs-mcp

by Reid-n0rc
README.md
# fcc-ecfs-mcp

[![codecov](https://codecov.io/gh/Reid-n0rc/fcc-ecfs-mcp/graph/badge.svg)](https://codecov.io/gh/Reid-n0rc/fcc-ecfs-mcp)

An [MCP](https://modelcontextprotocol.io) server for the FCC's [Electronic Comment
Filing System (ECFS) public API](https://www.fcc.gov/ecfs/help/public_api). Implements
every endpoint in ECFS's public OpenAPI spec — filings, a single filing, proceedings,
a filing's documents, and non-docketed filing inboxes — plus an escape hatch for
anything else. Lets an MCP client (Claude Desktop, Claude Code, etc.) search and fetch
across all of them.

## Tools

| Tool | ECFS endpoint | Description |
| --- | --- | --- |
| `ecfs_search_filings` | `GET /filings` | Search filings by free text, proceeding/docket number, filer name, submission type, or date received. |
| `ecfs_get_filing` | `GET /filing/{id}` | Fetch a single filing by its submission ID. |
| `ecfs_search_proceedings` | `GET /proceedings` | Search proceedings (dockets), e.g. by docket number. |
| `ecfs_get_download_plan` | `GET /filings?type=downloadplan` | Get date-bucketed queries safe for exhaustively paging a large docket (e.g. a heavily-commented NPRM) — plain offset/limit paging over a big result set can return duplicate or missing filings. |
| `ecfs_search_documents` | `GET /documents` | List a filing's document/attachment metadata (filename, page count, byte size, OCR status) by submission ID. Does not return file contents — ECFS's public API is metadata-only. |
| `ecfs_download_document` | ECFS website (headless Chrome) | Download a filing's document (PDF) and save it inside your download folder; returns the saved path, size, and SHA-256. See [Downloading documents](#downloading-documents). |
| `ecfs_list_inboxes` | `GET /inbox` | List the available inboxes for non-docketed filings. |
| `ecfs_raw_request` | any | Escape hatch: call any ECFS path/query params not covered above. `api_key` is added automatically. |

## Setup

### Option A: Claude Code plugin (recommended)

This repo is a Claude Code plugin — it bundles the MCP server and the slash commands
below into a single install, and works from any directory (no manual config editing,
no `cd`-ing into this repo first).

1. Get a free API key at <https://www.fcc.gov/ecfs/help/public_api>.
2. Install the plugin directly from this repo:

   ```
   /plugin install Reid-n0rc/fcc-ecfs-mcp
   ```

   Claude Code will prompt for your FCC ECFS API key (stored securely, not pasted into
   a config file) and an optional download folder for `ecfs_download_document`
   (default `~/Downloads/ecfs`), and register both the `fcc-ecfs` MCP server and the
   `/fcc-ecfs:*` slash commands automatically.
3. To download documents, have [Google Chrome](https://www.google.com/chrome/) installed.

### Option B: Manual MCP server config

For non-Claude-Code MCP clients (Claude Desktop, etc.), or if you'd rather manage the
server yourself:

1. Install dependencies and build:

   ```bash
   npm install
   npm run build
   ```

2. Provide the key via the `ECFS_API_KEY` environment variable — **never commit it or
   pass it as a tool argument**. For local development, copy `.env.example` to `.env`
   (git-ignored) and fill it in. The server reads `.env` from the root of this checkout,
   not from the client's working directory, and values in the client's `env` block always
   win. If you installed the server with npm or npx instead of cloning it, no `.env` is
   read: set everything in the client's `env` block.

3. Add to your client's MCP config:

   ```json
   {
     "mcpServers": {
       "fcc-ecfs": {
         "command": "node",
         "args": ["/absolute/path/to/fcc-ecfs-mcp/dist/index.js"],
         "env": {
           "ECFS_API_KEY": "your-api-key-here",
           "ECFS_DOWNLOAD_DIR": "~/Downloads/ecfs"
         }
       }
     }
   }
   ```

   Prefer sourcing `ECFS_API_KEY` from your OS keychain or a secrets manager (e.g.
   1Password, `gpg`-encrypted dotfiles) rather than pasting it into a config file
   where possible. `ECFS_DOWNLOAD_DIR` is optional (default `~/Downloads/ecfs`).

## Slash commands

`commands/` ships Claude Code slash commands that wrap the tools above. Installed as
a plugin, they're namespaced under `fcc-ecfs`:

| Command | Description |
| --- | --- |
| `/fcc-ecfs:fcc-ecfs <query>` | General dispatcher — picks the right ECFS tool for the request. |
| `/fcc-ecfs:fcc-search-filings <args>` | Search filings by docket, filer, type, date, or text. |
| `/fcc-ecfs:fcc-search-proceedings <docket>` | Look up a proceeding/docket by number or name. |
| `/fcc-ecfs:fcc-get-filing <submission id>` | Fetch a single filing by its submission ID. |
| `/fcc-ecfs:fcc-list-documents <submission id(s)>` | List document/attachment metadata for one or more filings. |
| `/fcc-ecfs:fcc-download-plan <args>` | Get a download plan for exhaustively paging a large docket. |
| `/fcc-ecfs:fcc-download-document <id, URL, or description>` | Download a filing's document (PDF) to the download folder. |

## Development

```bash
npm run dev        # run the server directly with tsx
npm test           # run the unit test suite (vitest)
npm run test:live  # run live tests against the real ECFS API (needs ECFS_API_KEY)
npm run test:download  # download a real document via headless Chrome (local only)
npm run typecheck  # type-check without emitting
```

CI (`.github/workflows/ci.yml`) runs the unit tests and the live tests on every push to
`main` and every PR, plus weekly and on manual dispatch; docs-only changes skip the test
jobs. Live tests use the `ECFS_API_KEY` secret (`gh secret set ECFS_API_KEY`, and again
with `--app dependabot` for Dependabot PRs). Fork PRs can't read secrets, so they skip the
live tests.

Changes to `main` go through a pull request that needs an approving review from the
repo owner (`.github/CODEOWNERS`) and a passing `ci-ok` check, which rolls up every CI job.
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full workflow.

To test the plugin locally before publishing a change, rebuild and point Claude Code at
the working tree:

```bash
npm run build
claude --plugin-dir .
```

Note that the plugin loader never runs `npm run build` on install — it only runs
`npm ci` to fetch dependencies. `dist/` must be committed and up to date before pushing.

## Downloading documents

The ECFS public API is **metadata-only**: no endpoint returns a document's file bytes or
text. A document's `location` (or a filing's `documents[].src`) points at
`https://www.fcc.gov/ecfs/document/{id_submission}/{n}` on the ECFS *website*, which
refuses non-browser HTTP clients such as `curl` with `403 Forbidden`.

`ecfs_download_document` therefore opens that page in headless Google Chrome (via
`playwright-core`, using your installed Chrome — no bundled browser download) and saves
the PDF the page loads. Headless Chrome is presented under its regular Chrome user agent,
since the site refuses the default `HeadlessChrome` one. No window is shown.

- Files are always saved inside `ECFS_DOWNLOAD_DIR` (plugin setting "Download folder";
  default `~/Downloads/ecfs`), named `{id_submission}_{ECFS filename}` and always with a
  `.pdf` extension. The model can put a file in a subfolder (`output_dir`, e.g. one per
  docket), but can't save outside the download folder. To save elsewhere, change the
  setting.
- Re-downloading an identical file reuses the existing copy; a different file with the
  same name gets a numeric suffix rather than overwriting.
- Each download takes a few seconds. The tool fetches one document per call; it isn't
  meant for bulk-downloading whole dockets.

## Security notes

- The API key is read only from `process.env.ECFS_API_KEY` — it is never accepted as a
  tool input, so it cannot be echoed back into a model's context or transcript.
- Error messages and thrown errors redact the `api_key` query parameter before they are
  ever logged or returned to a client. Response *bodies* are also scrubbed of the literal
  key value before being returned — some ECFS response types (e.g. `type=downloadplan`)
  echo the key back verbatim in generated URLs, so URL-only redaction isn't sufficient.
- `.env` is git-ignored; only `.env.example` (no real key) is committed.
- The server never reads a `.env` from its working directory, only the checkout's own
  `.env`, so it can't pick up unrelated secrets from the project an MCP client was
  launched in. `DOTENV_*` variables don't affect it.
- `ecfs_download_document` only writes inside the download folder (`ECFS_DOWNLOAD_DIR`),
  checked both on the requested path and on its real path after resolving symlinks, and
  every saved file ends in `.pdf`. Tool arguments can be influenced by filing text the
  model has read, so the server doesn't trust `output_dir` to point anywhere else.

To report a vulnerability, see [SECURITY.md](SECURITY.md).

## License

MIT

TDQS

A3.7/5.0

Scored across 8 tools

Disambiguation4/5

Most tools have clearly distinct purposes: filing lookup, document download, document listing, proceeding search, and inbox listing. Minor overlap comes from ecfs_raw_request being a universal escape hatch and from ecfs_get_download_plan sharing filters with ecfs_search_filings, though both descriptions clarify when to use them.

Naming Consistency4/5

All tools use a consistent ecfs_ snake_case prefix and mostly follow a verb_noun pattern such as ecfs_get_filing, ecfs_search_documents, and ecfs_list_inboxes. The main deviation is ecfs_raw_request, which is still readable but does not follow the same action-verb style.

Tool Count5/5

Eight tools is well within the expected range for a focused ECFS API server. Each tool covers a meaningful operation, and the set is not padded with redundant endpoints.

Completeness5/5

For a read-only ECFS research server, the surface covers the core lifecycle: searching proceedings, searching filings, fetching a filing, listing documents, downloading documents, and handling non-docketed inboxes. The raw request tool also provides an escape hatch for any uncovered API paths.

Maintenance

ActivityMaintained
ResponsivenessResponsive