@cyanheads/fcc-broadband-mcp-server
Access FCC broadband availability, coverage analysis, and digital divide data for US geographies and census blocks via MCP. STDIO or Streamable HTTP.
9 Tools โข 2 Resources โข 1 Prompt
Overview
FCC broadband data from Form 477 (June 2015 โ June 2021) and the Broadband Data Collection (June 2022 onward). Geocode coordinates to census blocks, look up the ISPs and advertised speeds at a block, and summarize, compare, or rank geographies by unserved population for BEAD and equity research. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|
fcc_geocode_block | Convert a lat/lon coordinate to a 2010-vintage census block FIPS code, with county and state identifiers |
fcc_search_availability | List the providers and advertised speeds at a census block, filtered by technology and speed |
fcc_get_coverage_summary | Population by provider count for one geography, split by urban/rural and tribal/non-tribal |
fcc_compare_areas | Rank coverage across several geographies of the same type, worst first |
fcc_find_underserved | Rank the most underserved areas in a state or nationwide by unserved population |
fcc_search_providers | Search holding companies by name, state, and technology; returns hoconum identifiers |
fcc_get_provider | National coverage profile for one holding company: technologies and population per speed tier |
fcc_list_filing_periods | List data vintages: Form 477 filing periods and BDC as-of dates |
fcc_list_downloads | List BDC bulk data files for one as-of date |
Resources
| Resource | Description |
|---|
fcc-broadband://geography/{type}/{id}/summary | Coverage summary for one geography at 25 Mbps |
fcc-broadband://providers/list/{offset} | One page of the Form 477 holding-company directory, 25 entries per page |
Resource data is also reachable through tools: fcc_get_coverage_summary for geographies, fcc_search_providers for holding companies.
Prompts
| Prompt | Description |
|---|
broadband_equity_analysis | Structure a digital divide analysis that chains FCC coverage with Census and BLS data |
Capability reference
latitude and longitude in decimal degrees, resolved against 2010 census boundaries through the FCC Geo API
- Returns
blockFips (15 digits, ready for fcc_search_availability), censusVintage, countyFips, countyName, stateFips, stateCode, and stateName; coordinates over water or outside US coverage fail as block_not_found
block_fips (15 digits, 2010 vintage) is required; tech_filter (Form 477 technology codes), min_speed_down (Mbps), and consumer (true consumer, false business) narrow the rows
- One row per offering with
hoconum, techCode / techLabel, maxDownloadMbps, maxUploadMbps, consumer, and business, plus totalProviders counting distinct holding companies; an unfiltered block with no providers fails as block_not_found, while a filtered query that matches nothing returns an empty list with a notice
geography_type is one of nation, state, county, cd, place, cbsa, tribal; geography_id is required for every type but nation; urban_rural_filter (all / R / U) and tribal_filter (all / T / N) narrow the population
- Returns
population by provider count (noCoverage, oneProvider, twoProviders, threeOrMore, total), coveragePct, unservedPct, competitivePct, and a per-segment breakdown; fails as geography_not_found, invalid_geography_combo, or invalid_geography_id_shape
- 2 to 50
geography_ids of one geography_type (every type but nation), or compare_all_states: true for the 50 states plus DC; sort_by is unserved_pct (default), unserved_pop, coverage_pct, or competitive_pct
- Every sort ranks worst first, so
rank 1 is the area most in need; rows carry id, name when resolved, the provider-count populations, and all three percentages. Fails as missing_geography_ids, invalid_all_states_combo, invalid_geography_id_shape, or no_data_found
state (USPS state or territory code) scopes the search, otherwise it runs nationwide; geography_type is county (default), cd, place, or cbsa; urban_rural_filter defaults to R, min_unserved_pop to 1, and limit to 20 (max 100)
- Areas ranked by
noCoverage, each with rank, id, name, oneProvider, total, unservedPct, and coveragePct; totalFound counts matches before the limit. An unrecognized state fails as unknown_state, and filters that match nothing return an empty ranking with a notice
name_search (case-insensitive partial name), state, and tech_filter are all optional and must hold on the same filing; limit defaults to 50 (max 200)
- Deduplicated holding companies with
hoconum, holdingCompanyName, and national statesServed / techCodes that the filters never narrow. No match returns an empty list with a notice; a live search over its 30-second budget fails as live_search_timeout
- Against the live API,
scanTruncated: true marks the list as a sample and totalCount is omitted; a full-corpus mirror (FCC_MIRROR_ENABLED=true) returns every match
hoconum (digits only) from fcc_search_providers or the providers directory resource
- Returns
techCodes / techLabels and speedTierPopulation, which counts each person once and omits empty tiers; business-only carriers come back with both lists empty. Fails as provider_not_found, or live_provider_timeout past the 30-second budget
- Always returns the Form 477 periods;
include_bdc: true adds BDC as-of dates when credentials are configured
periods newest first, each with asOfDate and source (form477 or bdc), plus form477Count, bdcCount, and hasBdcCredentials; missing credentials leave bdcCount at 0 rather than raising an error
as_of_date (YYYY-MM-DD) is required; filter by data_type (availability default, or challenge), category (Summary, State, Provider), technology_type (Fixed Broadband, Mobile Broadband, Mobile Voice), state, and provider_name; limit defaults to 50 (max 200), paged with offset
- Returns file metadata and a
downloadUrl per file, not file contents; totalFiles counts every match and nextOffset is omitted on the last page. Fails as credentials_required or invalid_as_of_date
fcc-broadband://geography/{type}/{id}/summary resource
type takes the seven fcc_get_coverage_summary geography types; id is a FIPS GEOID checked for digit count, 0 for nation
- Fixed at 25 Mbps and
acfosw; returns population, the three percentages, and segments as application/json. Use fcc_get_coverage_summary for other thresholds or filters
fcc-broadband://providers/list/{offset} resource
offset pages the directory 25 entries at a time in hoconum order; start at 0 and follow nextOffset
- Entries carry
hoconum and, when resolved, holdingCompanyName; each page reports total and count. To find one company by name, use fcc_search_providers
broadband_equity_analysis prompt
- Arguments:
region (free text) and focus (underserved, rural, tribal, or all), both required
- Returns one user message laying out a five-step analysis: baseline coverage, focus area, demographic cross-reference, provider landscape, and BEAD context
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-specific:
- Three FCC sources, all US government public-domain data: Form 477 on FCC Open Data (Socrata), the BDC Public Data API, and the FCC Geo API
- Every Form 477 tool and resource works without credentials and reads the June 2021 filing, the last one; BDC data is exposed only as bulk-file manifests, and
fcc_list_downloads needs FCC_BDC_USERNAME and FCC_BDC_HASH_VALUE, failing as credentials_required without them
- The area tools share one vocabulary:
tech_filter letters (acfosw for every technology, the default; a DSL, c cable, f fiber, o other, s satellite, w fixed wireless) and speed_down tiers (0.2, 4, 10, 25, 100, 250, 1000 Mbps; default 25, and 100 is the BEAD threshold)
- Coverage analysis reads the pre-aggregated Area Table (
xvwq-qtaj), not the block-level deployment table, so state- and county-scale queries stay fast
- Optional local Form 477 mirror (
FCC_MIRROR_ENABLED=true) serves queries from SQLite, falling back to the live API for anything not ingested
Agent-friendly output:
- Filter echo: search, summary, and ranking tools return
appliedFilters, and every Form 477 result carries dataVintage
- Partial results say so:
scanTruncated and scanRowCap flag a result drawn from a capped upstream scan (fcc_search_providers, fcc_find_underserved), and an empty match returns a notice instead of an error
- Typed failures: reasons such as
block_not_found, invalid_geography_id_shape, and credentials_required, each with a recovery hint
Getting started
Public Hosted Instance
A public instance is available at https://fcc-broadband.caseyjhand.com/mcp โ no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"fcc-broadband-mcp-server": {
"type": "streamable-http",
"url": "https://fcc-broadband.caseyjhand.com/mcp"
}
}
}
Self-Hosted / Local
Add the following to your MCP client configuration file.
{
"mcpServers": {
"fcc-broadband-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/fcc-broadband-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"fcc-broadband-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/fcc-broadband-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- Optional: FCC BDC credentials for
fcc_list_downloads and BDC filing periods. Generate a token at broadbandmap.fcc.gov under "Manage API Access".
- Optional: a Socrata app token (
FCC_OPENDATA_APP_TOKEN) for higher Form 477 rate limits.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/fcc-broadband-mcp-server.git
- Navigate into the directory:
cd fcc-broadband-mcp-server
- Install dependencies:
- Configure environment:
Configuration
| Variable | Description | Default |
|---|
FCC_BDC_USERNAME | FCC account email for the BDC API. Needed by fcc_list_downloads and for BDC dates in fcc_list_filing_periods. | none |
FCC_BDC_HASH_VALUE | API token hash from broadbandmap.fcc.gov "Manage API Access". Pairs with FCC_BDC_USERNAME. | none |
FCC_OPENDATA_APP_TOKEN | Socrata app token; raises FCC Open Data rate limits. | none |
FCC_MIRROR_ENABLED | Serve Form 477 queries from the local SQLite mirror where its coverage allows. | false |
FCC_MIRROR_PATH | Directory holding the mirror SQLite files. | data/fcc-mirror |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto. | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry. | false |
See .env.example for the full list of optional overrides.
Running the server
Local development
-
Build and run:
bun run rebuild
bun run start:stdio
bun run start:http
-
Run checks and tests:
bun run devcheck
bun run test
bun run lint:mcp
Docker
docker build -t fcc-broadband-mcp-server .
docker run --rm -p 3010:3010 fcc-broadband-mcp-server
The Dockerfile defaults to HTTP transport and stateless session mode, logging to /var/log/fcc-broadband-mcp-server. OpenTelemetry peer dependencies are installed by default โ build with --build-arg OTEL_ENABLED=false to omit them. The image also ships the mirror scripts and a writable data/ directory.
Form 477 is frozen at the June 2021 filing, so it can be ingested once into a local SQLite index. The mirror is off by default, and the full corpus is about 9 GB of source data and takes hours to ingest, so the bootstrap can be scoped to the states you query:
bun run mirror:init -- --states 11,53
bun run mirror:init -- --full
bun run mirror:verify
Then set FCC_MIRROR_ENABLED=true. In Docker, run the same scripts with docker exec <container> bun run mirror:init -- --states 11,53. Run them from a source checkout or the image: the scripts import through the @/ path alias, which an npm install does not map.
- Block and geography lookups serve from the mirror once the queried state is fully ingested; anything else falls back to the live API.
fcc_search_providers, fcc_get_provider, and geographies with no state in their GEOID (cbsa, tribal, nation) serve from the mirror only after a --full ingest.
- Mirror name search matches word prefixes (
comc finds "Comcast") where the live API matches any substring (mcast), so a mid-word fragment can return fewer results.
- The ingest is idempotent and resumes an interrupted run from its saved cursor. Set
FCC_OPENDATA_APP_TOKEN first for a full ingest. Bun uses the built-in bun:sqlite; Node.js needs the optional better-sqlite3 peer dependency.
Project structure
| Directory | Purpose |
|---|
src/index.ts | createApp() entry point โ registers tools, resources, and prompts and inits services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Nine tools across FCC Open Data, the BDC API, and the Geo API. |
src/mcp-server/resources | Resource definitions (*.resource.ts). Geography summary and provider directory. |
src/mcp-server/prompts | Prompt definitions (*.prompt.ts). Broadband equity analysis prompt. |
src/services/open-data | FCC Open Data (Socrata) service for Form 477 deployment, area, and provider tables; mirror/ holds the local SQLite mirror. |
src/services/bdc-api | BDC Public Data API service โ authenticated filing periods and download manifests. |
src/services/geo-api | FCC Geo API service โ lat/lon to census block FIPS. |
scripts/ | Build, devcheck, and packaging scripts, plus fcc-mirror-init.ts / fcc-mirror-verify.ts. |
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 domain type โ return output schema; never fabricate missing fields
- Socrata returns every field as a string, so parse counts and speeds (
has_0โhas_3more, maxaddown, maxadup) explicitly
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 โ see LICENSE for details.