Search FCC radio licenses, find nearby transmitter sites, and see who is licensed on a frequency.
Search FCC radio licenses, find nearby transmitter sites, and see who is licensed on a frequency via MCP. STDIO or Streamable HTTP.
US radio spectrum licensing from the FCC Universal Licensing System (ULS), served from a local SQLite index of the FCC's weekly and daily bulk files. Look up a callsign, licensee, or FCC Registration Number (FRN); read a license's sites, antennas, frequencies, power, and emission designators; find licensed transmitter sites within a radius of a point; and see who is authorized on a frequency or band, market-area spectrum blocks included. No API key, and no call to the FCC at request time. Runs as a stdio process or a local Streamable HTTP server.
The default index covers land mobile (private, commercial, and broadcast auxiliary), microwave, cellular, market-area wireless, paging, coast stations, broadband radio (BRS/EBS), and amateur licenses, plus spectrum leases. GMRS, ship, and aircraft licenses are opt-in. Broadcast stations (AM/FM/TV) and satellite earth stations are separate FCC systems and are not included.
| Tool | Description |
|---|---|
fcc_spectrum_search_licenses | Search licenses and spectrum leases by callsign, licensee name, FRN, radio service, status, or licensee state. |
fcc_spectrum_get_license | Read one license or lease in full by callsign or USI: licensee, status and dates, locations, antennas, frequencies with emissions, market blocks, and lease links. |
fcc_spectrum_find_transmitters | Find licensed transmitter sites within a radius of a coordinate, nearest first, with the frequencies authorized at each. |
fcc_spectrum_search_frequencies | Find site assignments and market-area spectrum blocks whose occupied band overlaps a frequency or band. |
fcc_spectrum_list_reference | Decode ULS codes (radio services, statuses, location and antenna types, applicant types, operator classes) and report index coverage and freshness. |
| Resource | Description |
|---|---|
fcc-spectrum://license/{callsign} | One license or lease by callsign as JSON — the first page fcc_spectrum_get_license returns. |
Also reachable via fcc_spectrum_get_license.
fcc_spectrum_search_licenses toolcallsign, licensee (every word must match the start of a word in the name), frn, radio_service, and state (the licensee's mailing state) — at least one required; status narrows to A (default), C, E, L, P, T, X, or anylimit 1–100 (default 25) with cursor paging; each row carries the usi to pass to fcc_spectrum_get_license, plus isLease, licenseeRedacted, and location and frequency countsno_criteria, unknown_radio_service, service_not_indexed, invalid_cursor, index_not_readyfcc_spectrum_get_license toolcallsign or usi (identifier_required otherwise); a callsign shared by several records returns the active one, else the most recent, and lists the others in otherCallsignRecordsmax_frequencies frequency rows (1–1000, default 100), and 100 leases; nextLocationOffset and nextLeaseOffset feed location_offset and lease_offset on the next callfound: false with guidance (plus up to 5 callsign-prefix candidates), not an error; technicalRetained: false marks a record whose status keeps no sites or frequenciesfcc_spectrum_find_transmitters toollatitude / longitude as decimal degrees or DMS strings, radius_km 0.1–100 (default 5); optional frequency_low / frequency_high in unit (kHz, MHz default, GHz), radio_service, and status (A default, L, X, or any for all three)limit 1–100 (default 25) with cursor paging; each site lists up to max_frequencies_per_site frequencies (1–50, default 10), with frequencyCount carrying the full countinvalid_frequency_range, unknown_radio_service, service_not_indexed, invalid_cursor, index_not_readyfcc_spectrum_search_frequencies toolfrequency_low required, frequency_high optional, in unit; a site assignment matches when its occupied band (widened by its emission bandwidth) overlaps the query, a market block when its filed edges do; kind is site, market, or both (default)state, radio_service, licensee, and status (A default, L, X, any); limit 1–200 (default 50) with cursor paging, frequency ascending; each row's kind says whether it is a site assignment or a market blockinvalid_frequency_range, unknown_radio_service, service_not_indexed, invalid_cursor, index_not_readyfcc_spectrum_list_reference tooltopic: radio_services, license_statuses, location_types, antenna_types, applicant_types, operator_classes, or coverage; filter narrows radio_services to entries containing every word givencoverage reports index.status (none, building, ready), per-group record, site, and frequency counts with snapshot times, and whether redaction is on; it works before the index is builtfcc-spectrum://license/{callsign} resourcefcc_spectrum_get_license for a callsign (up to 100 frequency rows) as application/json, with dataAsOf, location, site, and frequency totals, and a notice naming the tool call that reads the rest; cached 1 hourindex_not_ready, license_not_foundBuilt 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.
FCC ULS-specific:
isLease marks them, the licensee shown is the lessee, and lease links are listed in both directionsstateFromCoordinatesAgent-friendly output:
dataAsOf, and search tools echo appliedFilters with normalized values and defaultstruncated, shown, cap, and totalCount plus a notice that names the next call, so a partial page is never read as the wholecoordinatesDms) when they fail validationfound, kind, isLease, licenseeRedacted, and technicalRetained let callers branch on data, not string parsingThe index must be built once before any search works — see First-run setup. The package does not ship FCC data; until
mirror:inithas run, data tools fail withindex_not_readyandfcc_spectrum_list_referencewith topiccoveragereports the build state.
Add the following to your MCP client configuration file:
{
"mcpServers": {
"fcc-spectrum-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/fcc-spectrum-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"FCC_SPECTRUM_MIRROR_DIR": "/path/to/fcc-uls"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"fcc-spectrum-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/fcc-spectrum-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"FCC_SPECTRUM_MIRROR_DIR": "/path/to/fcc-uls"
}
}
}
}
Or with Docker (mount a volume at /usr/src/app/.mirror so the index persists across containers):
{
"mcpServers": {
"fcc-spectrum-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"-v", "fcc-uls:/usr/src/app/.mirror",
"ghcr.io/cyanheads/fcc-spectrum-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 FCC_SPECTRUM_MIRROR_DIR=/path/to/fcc-uls bun run start:http
# Server listens at http://localhost:3010/mcp
FCC_SPECTRUM_MIRROR_DIR must name the directory mirror:init built. A relative path resolves against the process's working directory, which an MCP client chooses, so use an absolute path.
The index is built from the FCC's public ULS bulk files at data.fcc.gov — weekly full snapshots per service group, plus daily incrementals. On the default groups a build downloads about 1.1 GB of zips, one at a time; a measured build produced 3,270,502 license records and an index of about 1.8 GB in about 3 minutes.
Build it once from a source checkout (see Installation) or the Docker image:
# Download the weekly snapshots and build the index (resumable; skips when nothing is newer)
FCC_SPECTRUM_MIRROR_DIR=/path/to/fcc-uls bun run mirror:init
# Check it: SQLite integrity plus per-file line counts against the FCC's counts files
FCC_SPECTRUM_MIRROR_DIR=/path/to/fcc-uls bun run mirror:verify
# Apply the daily incrementals published since the last build
FCC_SPECTRUM_MIRROR_DIR=/path/to/fcc-uls bun run mirror:refresh
With Docker, run the same commands against the volume: docker run --rm -v fcc-uls:/usr/src/app/.mirror ghcr.io/cyanheads/fcc-spectrum-mcp-server:latest bun run mirror:init.
Keeping it current:
mirror:init has published a first index.mirror:refresh daily and mirror:init weekly from cron or another scheduler; daily files never remove licenses, so removals arrive with the weekly rebuild.mirror:init or mirror:refresh run (or scheduled job) write at a time; an interrupted mirror:init resumes from its last completed step when rerun.bun:sqlite is built into Bun; under Node, install better-sqlite3 beside the server (declared as an optional peer dependency).FCC_SPECTRUM_MIRROR_DIR: about 1.8 GB on the default groups, plus rebuild room as above.data.fcc.gov when building or refreshing the index. Queries need none.For local development, or to build the index from source:
git clone https://github.com/cyanheads/fcc-spectrum-mcp-server.git
cd fcc-spectrum-mcp-server
bun install
FCC_SPECTRUM_MIRROR_DIR=/path/to/fcc-uls bun run mirror:init
| Variable | Description | Default |
|---|---|---|
FCC_SPECTRUM_MIRROR_DIR | Directory holding the index generations, current.json, the ingest lock, and temporary zips. Use an absolute path; a relative one resolves against the working directory. | .mirror/fcc-uls |
FCC_SPECTRUM_SERVICES | Comma-separated weekly service groups to index, case-insensitive. Opt-in: gmrs, ship, aircr. An unknown name fails startup and lists the valid set. | LMpriv,LMcomm,LMbcast,micro,cell,market,paging,coast,mdsitfs,amat |
FCC_SPECTRUM_REDACT_INDIVIDUALS | Redact individual licensees and trustee names, and exclude individuals from name search. Only false, 0, no, or off disables it; unset, empty, or anything else keeps it on. | true |
FCC_SPECTRUM_BASE_URL | ULS bulk file host root, read only when building or refreshing the index. | https://data.fcc.gov/download/pub/uls |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_SESSION_MODE | Session mode: auto (resolves to stateful), stateful, or stateless. The server declares stateless in source because no tool asks the caller for input mid-call; setting this overrides that declaration. | stateless |
MCP_HTTP_PORT | Port for the HTTP server. | 3010 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path where the MCP server is mounted. | /mcp |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
OTEL_ENABLED | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | false |
See .env.example for the full list of optional overrides.
The FCC splits its weekly snapshots into service groups, named as its files are. Adding a group takes effect at the next mirror:init; a removed group drops out at the next weekly rebuild.
| Group | Contents | Indexed |
|---|---|---|
LMpriv | Private land mobile (public safety, business, industrial) | default |
LMcomm | Commercial land mobile | default |
LMbcast | Broadcast auxiliary land mobile | default |
micro | Point-to-point microwave | default |
cell | Cellular | default |
market | Market-area wireless (PCS, AWS, 700 MHz, 3.45 and 3.7 GHz, and other auctioned blocks) | default |
paging | Paging | default |
coast | Coast stations | default |
mdsitfs | Broadband Radio Service and Educational Broadband Service (BRS/EBS) | default |
amat | Amateur radio | default |
gmrs | General Mobile Radio Service — licensee records only | opt-in |
ship | Ship stations — licensee records only | opt-in |
aircr | Aircraft stations — licensee records only | opt-in |
ULS records name the person behind many licenses, and not only amateur ones: individuals also hold land mobile, paging, and microwave licenses. FCC_SPECTRUM_REDACT_INDIVIDUALS is on by default and fails safe:
I, or blank in the amateur and GMRS services. A blank type or type H (Other) in any service also counts when the filing carries a person's name parts, or when the licensee name contains no organization word (Inc, County, Church, Wireless, …), so an organization named without one is redacted too. While redaction is on, its licensee name and city are null with licenseeRedacted: true, and its sites' street addresses are omitted.notice says so. A callsign, USI, or FRN lookup still returns the record, redacted.Licensee mailing street addresses, ZIP codes, PO boxes, attention lines, phone numbers, fax numbers, and email addresses are never ingested, for anyone.
fcc_spectrum_search_frequencies over a crowded band (all of 150–174 MHz, or the whole spectrum) reads the band in frequency order and stops each call short. A page can hold fewer rows than limit and still carry nextCursor, and totalCount is then a lower bound (totalIsLowerBound: true). A sparse filter across a wide band can return several empty pages before its first rows; a state, radio_service, or licensee filter narrow enough to search directly keeps the exact count.A active, L pending legal, X term pending). Expired, cancelled, and terminated records keep their licensee, status, dates, and lease links.fcc_spectrum_search_licenses and radio_service PL.FB2, FXO, MO).Build and run:
# One-time build
bun run rebuild
# Build the index (see First-run setup)
bun run mirror:init
# Run the built server
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
docker build -t fcc-spectrum-mcp-server .
# Build the index once against the volume:
docker run --rm -v fcc-uls:/usr/src/app/.mirror fcc-spectrum-mcp-server bun run mirror:init
# Serve over HTTP:
docker run --rm -p 3010:3010 -v fcc-uls:/usr/src/app/.mirror fcc-spectrum-mcp-server
The Dockerfile defaults to HTTP transport with stateless sessions, ships the mirror:init / mirror:refresh / mirror:verify CLI, pre-creates a writable .mirror directory owned by the runtime user (mount a volume there), and logs to /var/log/fcc-spectrum-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers the tools and resource, composes the server instructions, inits the index service, and starts the ingest schedule under HTTP transport. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts), shared input schemas, and format helpers. |
src/mcp-server/resources | Resource definitions (*.resource.ts). |
src/services/uls | ULS service — bulk client, zip reader, record parsers, ingester, index generations, the read path with its redaction chokepoint, and the ingest schedule. |
scripts/fcc-mirror-*.ts | Index lifecycle CLI — mirror:init, mirror:refresh, mirror:verify. |
tests/ | Unit and integration tests mirroring src/, with fixture ULS records. |
See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging; data access goes through the ULS index servicesrc/mcp-server/*/definitions/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
License data comes from the FCC Universal Licensing System (ULS), a US government work in the public domain (17 U.S.C. §105). Credit it as "FCC Universal Licensing System (ULS)" with the dataAsOf time each response carries. This project redistributes none of that data; operators download it from the FCC when building the index. This server is independent of the FCC and not endorsed by it.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y @cyanheads/fcc-spectrum-mcp-serverMerge 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.
{
"mcpServers": {
"io-github-cyanheads-fcc-spectrum-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/fcc-spectrum-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 referencefcc-spectrum-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.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..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.