@cyanheads/fcc-spectrum-mcp-server
MCP serverSearchSearch FCC radio licenses, find nearby transmitter sites, and see who is licensed on a frequency.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the fcc spectrum search licenses tool from @cyanheads/fcc-spectrum-mcp-server
Install @cyanheads/fcc-spectrum-mcp-server
The server’s own address, for the clients that take one directly. Or connect ahel once and every client you use reads it from one address, with the account kept on ahel rather than in each client’s config.
Claude Code
claude mcp add --transport http --scope user cyanheads-fcc-spectrum-mcp-serve 'https://fcc-spectrum.caseyjhand.com/mcp'Run it once in your project, then open /mcp to approve any sign-in the server asks for.
Claude Desktop
https://fcc-spectrum.caseyjhand.com/mcpAdd a custom connector in Settings, paste this address, and approve the sign-in.
Cursor
cursor://anysphere.cursor-deeplink/mcp/install?name=cyanheads-fcc-spectrum-mcp-serve&config=eyJ1cmwiOiJodHRwczovL2ZjYy1zcGVjdHJ1bS5jYXNleWpoYW5kLmNvbS9tY3AifQ==Open the link and Cursor adds the server at that address.
ChatGPT
https://fcc-spectrum.caseyjhand.com/mcpIn Settings, enable Developer mode, create an MCP app, and paste this address. Your plan and workspace must allow custom apps.
Codex
codex mcp add cyanheads-fcc-spectrum-mcp-serve --url 'https://fcc-spectrum.caseyjhand.com/mcp'Run it once, then sign in with codex mcp login cyanheads-fcc-spectrum-mcp-serve if the server asks for an account.
From the project's README
As published by cyanheads/fcc-spectrum-mcp-server in README.md.
Public Hosted Server: https://fcc-spectrum.caseyjhand.com/mcp
Overview
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, FCC Registration Number (FRN), or market code; read a license's sites, antennas, frequencies, power, and emission designators; find licensed transmitter sites near 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, a local Streamable HTTP server, or the public hosted endpoint above.
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.
Tools
| Tool | Description |
|---|---|
fcc_spectrum_search_licenses | Search licenses and spectrum leases by callsign, licensee name, FRN, market code, 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. |
Resources
| 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.
Capability reference
fcc_spectrum_search_licenses tool
- Filters:
callsign,licensee(every word must match the start of a word in the name),frn,market_code(a geographic-area license's market, matched exactly:PEA016,CMA020,D06037,NW;pea16reads asPEA016),radio_service, andstate(the licensee's mailing state) — at least one required;statusnarrows toA(default),C,E,L,P,T,X, orany limit1–100 (default 25) with cursor paging; each row carries theusito pass tofcc_spectrum_get_license, plusisLease,licenseeRedacted, and location and frequency counts- Typed errors:
no_criteria,unknown_radio_service,service_not_indexed,invalid_cursor,index_not_ready
fcc_spectrum_get_license tool
- Exactly one of
callsignorusi(identifier_requiredotherwise); a callsign shared by several records returns the active one, else the most recent, and lists the others inotherCallsignRecords - Pages large records: up to 50 sites, 100 antennas, 100 leases, and
max_frequenciesfrequency rows (1–1000, default 100) per call;nextLocationOffsetandnextLeaseOffsetfeedlocation_offsetandlease_offseton the next call location_numberstarts the locations at a site'slocationNumberfromfcc_spectrum_find_transmittersorfcc_spectrum_search_frequencies, or at the next filed number when the record has no such location; with a nonzerolocation_offsetit fails (location_start_conflict)- A miss returns
found: falsewithguidanceand up to 5 callsign-prefixcandidates, not an error;technicalRetained: falsemarks a record whose status keeps no sites or frequencies
fcc_spectrum_find_transmitters tool
latitude/longitudeas decimal degrees or DMS strings,radius_km0.1–100 (default 5); optionalfrequency_low/frequency_highinunit(kHz,MHzdefault,GHz),radio_service,location_type(one ULS location type code, case-insensitive:Fkeeps fixed sites only,Mmobile andTtemporary-fixed areas), andstatus(Adefault,L,X, oranyfor all three)- Sites nearest first,
limit1–100 (default 25) with cursor paging; each site lists up tomax_frequencies_per_sitefrequencies (1–50, default 10), withfrequencyCountcarrying the full count - Each site carries
locationTypeCodeandlocationTypeLabelwhen a type is filed, andradiusKmwhen a radius is; a mobile or temporary-fixed operating area is returned at its filed center, anddistanceKmmeasures to that center - Typed errors:
invalid_frequency_range,unknown_radio_service,service_not_indexed,invalid_cursor,index_not_ready
fcc_spectrum_search_frequencies tool
frequency_lowrequired,frequency_highoptional, inunit; a site assignment matches when its occupied band (widened by its emission bandwidth) overlaps the query, a market block when its filed edges do;kindissite,market, orboth(default)- Narrow by
state,radio_service,licensee,frn,market_code, andstatus(Adefault,L,X,any);limit1–200 (default 50) with cursor paging, frequency ascending; each row'skindsays whether it is a site assignment or a market block market_codeandfrntest the license, so they filter site rows and market rows alike: cellularCMAlicenses file sites, not blocks. Withstate, both apply. Anfrnsearch returns individual licensees redacted- A site row at a single site carries its
locationTypeCode,locationTypeLabel, and, when filed,radiusKm; a row under a location number several sites share carries none of them, as it carries no coordinates - Typed errors:
invalid_frequency_range,unknown_radio_service,service_not_indexed,invalid_cursor,index_not_ready
fcc_spectrum_list_reference tool
topic:radio_services,license_statuses,location_types,antenna_types,applicant_types,operator_classes, orcoverage;filternarrowsradio_servicesto entries containing every word givencoveragereportsindex.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 built
fcc-spectrum://license/{callsign} resource
- The first page of
fcc_spectrum_get_license(up to 100 frequency rows) asapplication/json, withdataAsOf, totals, and anoticenaming the tool call that reads the rest; cached 1 hour - Typed errors:
index_not_ready,license_not_found
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.
FCC ULS-specific:
- Keyless and local at request time — every query reads the SQLite index; network access is needed only to build and refresh it
- Index generations — a weekly rebuild writes a new file beside the one being served, and the server switches to it within a minute; the daily refresh and a schema upgrade update the served file in place, each change in one transaction
- Occupied-band frequency matching — each assignment's band is widened by the bandwidth parsed from its emission designators, so a query near a wide channel's edge still finds it
- Spectrum leases are first-class records —
isLeasemarks them, the licensee shown is the lessee, and lease links are listed in both directions - Site state is derived from coordinates when the filing leaves it blank (common for microwave and BRS/EBS), and flagged
stateFromCoordinates - Individual licensees are redacted by default, and contact details are never ingested — see Individual-licensee redaction
Agent-friendly output:
- Freshness and filter echo — every data response carries
dataAsOf, and search tools echoappliedFilters - Truncation disclosure —
truncated,shown,cap, andtotalCountplus anoticenaming the next call, so a partial page is never read as the whole - Absent stays absent — fields ULS leaves blank are omitted, and coordinates that fail validation are kept as DMS text (
coordinatesDms) - Discriminated output —
found,kind,isLease,licenseeRedacted, andtechnicalRetainedlet callers branch on data, not string parsing
Getting started
Public Hosted Instance
A public instance is available at https://fcc-spectrum.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"fcc-spectrum-mcp-server": {
"type": "streamable-http",
"url": "https://fcc-spectrum.caseyjhand.com/mcp"
}
}
}
The hosted index is rebuilt weekly and refreshed daily from the FCC bulk files, with individual licensees redacted.
Self-Hosted / Local
The 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.
First-run setup
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:
- HTTP transport. The server schedules the weekly rebuild on Sundays at 16:00 and the daily refresh at 17:00, in the process's local time (the Docker image runs in UTC; the FCC publishes snapshots Sunday morning US Eastern). A daily refresh that has fallen more than six days behind runs the weekly rebuild instead. Neither job runs until
mirror:inithas published a first index. Each job runs in a child process on the server's runtime, so queries and/healthzkeep answering while it writes, and its log lines appear in the server log; on shutdown a running job is stopped and resumes at the next run. - stdio. Nothing is scheduled. Run
mirror:refreshdaily andmirror:initweekly from cron or another scheduler; daily files never remove licenses, so removals arrive with the weekly rebuild. - Disk. A rebuild writes a new generation beside the one being served, so leave room for a second index plus the largest zip (about 420 MB). Older generations are deleted at the start of the next rebuild.
- Concurrency. An ingest lock lets one
mirror:init,mirror:refresh, or scheduled job write at a time; an interruptedmirror:initresumes from its last completed step when rerun.
Prerequisites
- Bun v1.4 or higher (or Node.js v24+).
bun:sqliteis built into Bun; under Node, installbetter-sqlite3beside the server (declared as an optional peer dependency). - Disk for the index at
FCC_SPECTRUM_MIRROR_DIR: about 1.8 GB on the default groups, plus rebuild room as above. - Network access to
data.fcc.govwhen building or refreshing the index. Queries need none.
Installation
For local development, or to build the index from source:
- Clone the repository:
git clone https://github.com/cyanheads/fcc-spectrum-mcp-server.git
- Navigate into the directory:
cd fcc-spectrum-mcp-server
- Install dependencies:
bun install
- Build the index (see First-run setup):
FCC_SPECTRUM_MIRROR_DIR=/path/to/fcc-uls bun run mirror:init
Configuration
| 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. | 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.
Service groups
The FCC splits its weekly snapshots into service groups. 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 |
Individual-licensee redaction
ULS records name the person behind many licenses, amateur and otherwise: individuals also hold land mobile, paging, and microwave licenses. FCC_SPECTRUM_REDACT_INDIVIDUALS is on by default and fails safe:
- A record is an individual's when its applicant type is
I, or blank in the amateur and GMRS services. A blank type or typeH(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. Its licensee name is thennullwithlicenseeRedacted: true, and its licensee city, its sites' street addresses, and its site names are omitted, since some individuals file their own name as a site name. - Trustee names are withheld on every license, since a trustee is always a person.
- Licensee name search excludes individuals, and the response
noticesays so. A callsign, USI, or FRN lookup still returns the record, redacted. - Site coordinates, city, county, state, and technical data stay: they are the spectrum record.
- Redaction applies when a response is built, so changing the setting needs a restart, not a rebuild.
Licensee mailing street addresses, ZIP codes, PO boxes, attention lines, phone numbers, fax numbers, and email addresses are never ingested, for anyone.
Known limitations
- Dense-band frequency searches return partial pages. Over a crowded band (all of 150–174 MHz, or the whole spectrum),
fcc_spectrum_search_frequenciesstops each call short. A page can hold fewer rows thanlimitand still carrynextCursor, andtotalCountis then a lower bound (totalIsLowerBound: true). A sparse filter across a wide band can return several empty pages before its first rows; astate,radio_service,licensee,frn, ormarket_codefilter narrow enough keeps the exact count. - Sites, frequencies, and market blocks are kept only for live licenses (
Aactive,Lpending legal,Xterm pending). Expired, cancelled, and terminated records keep their licensee, status, dates, and lease links. - Market blocks match by state, not by county.
statematches a market block when the FCC's market-area definitions place its market in that state (a multi-state market matches each of its states), when its market name carries the state code, or when its license files a site there; nothing maps a county or a coordinate to the markets covering it. Nationwide and Gulf of Mexico markets match no state, and Tribal land (TL) and MVDDS (MVD) markets with no state code in the name are skipped;market_codewithoutstatereaches any of them. - Radius search matches an operating area by its center. ULS files mobile and temporary-fixed locations as a center and a radius of operation.
fcc_spectrum_find_transmittersreturns such an area at its center, withradiusKm, when the center lies withinradius_km; it does not match an area because the search point falls inside it. Locations filed without coordinates, as most mobile, temporary, itinerant, and control-station locations are, are not returned. - Derived states are approximate near borders, within about a kilometer of a state line; territories other than Puerto Rico get no derived state.
- Removals lag up to a week. Daily files never delete licenses; the weekly rebuild does. A refresh gap longer than the daily window forces a full rebuild.
- Priority Access Licenses are not found by frequency. ULS files each 3.5 GHz PAL as a 10 MHz channel width with no frequency; list them with
fcc_spectrum_search_licensesandradio_servicePL. - Frequencies at a shared location number cannot be tied to a site. ULS files frequencies against the location number, so when a license files several sites under one number, no frequency can be placed at a single site.
- Redaction is per record. A person who also holds licenses filed under a non-individual applicant type appears by name on those records, which carry the same FRN.
- Station class codes pass through undecoded (
FB2,FXO,MO). - Coordinates are NAD83 as filed, with no datum shift.
- A few implausible emission bandwidths pass. A designator 20% of its frequency or wider is ignored as a filing error; narrower implausible filings are taken as filed.
Running the server
Local development
-
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:
Shortened here. Read the whole README on GitHub.
Tools it offers (5)
What this server listed when ahel dialed its public endpoint in Oct 2026, with no key and no account of yours. The names are the server’s own.
fcc_spectrum_search_licensesfcc_spectrum_get_licensefcc_spectrum_find_transmittersfcc_spectrum_search_frequenciesfcc_spectrum_list_reference
Signals
- GitHub stars
- 1
- Last commit
- Oct 2026
Advanced
- Delivery
- fcc-spectrum-mcp-server MCP server → your ahel connector (mcp.ahel.ai) → your AI.
- Item type
- mcp-server
- Key
io-github-cyanheads-fcc-spectrum-mcp-server- Source
- github.com/cyanheads/fcc-spectrum-mcp-server
- Hosted endpoint
https://fcc-spectrum.caseyjhand.com/mcp
github.com/cyanheads/fcc-spectrum-mcp-server