A template for building MCP servers that expose federal government datasets to Claude and other MCP-compatible clients.
fed-gov-mcp-template/
├── src/
│ └── fed_gov_mcp/
│ ├── __init__.py
│ ├── server.py # FastMCP app + registration calls
│ ├── config.py # Settings loaded from env vars / .env
│ ├── routes.py # Custom HTTP routes (health check, etc.)
│ ├── tools/
│ │ ├── __init__.py # register_tools() aggregator
│ │ └── example.py # Placeholder tool — replace with your API
│ ├── prompts/
│ │ ├── __init__.py # register_prompts() aggregator
│ │ └── example.py # Placeholder prompt
│ └── resources/
│ ├── __init__.py # register_resources() aggregator
│ └── example.py # Placeholder resource
├── tests/
│ └── test_tools.py
├── .github/
│ └── workflows/ci.yml # Lint + test on push/PR
├── Dockerfile # For remote HTTP deployment
├── .env.example # Copy to .env and fill in
├── DEPLOYMENT.md # Remote deployment guide
├── pyproject.toml
└── main.py # Root entry point
- uv —
pip install uvorbrew install uv
cp .env.example .env
uv syncuv run python main.py
# or
uv run fed-gov-mcpThe server starts in stdio mode by default — it communicates over stdin/stdout using JSON-RPC, which is how Claude Desktop and Claude Code connect to local MCP servers.
Each federal dataset integration lives in its own file under src/fed_gov_mcp/tools/.
Step 1 — Create a tool file:
# src/fed_gov_mcp/tools/census.py
import httpx
from fastmcp import FastMCP
from ..config import settings
def register_census_tools(mcp: FastMCP) -> None:
@mcp.tool()
def get_state_population(state_fips: str, year: int = 2022) -> dict:
"""Get ACS population estimate for a US state.
Data source: Census Bureau ACS 1-Year Estimates.
Updated annually; ~1 year lag (2022 data released late 2023).
Args:
state_fips: 2-digit FIPS code (e.g. "06" for California).
year: Survey year (2010–2022).
Returns:
Dict with state name and population estimate.
"""
with httpx.Client(timeout=30.0) as client:
response = client.get(
f"https://api.census.gov/data/{year}/acs/acs1",
params={
"get": "B01001_001E,NAME",
"for": f"state:{state_fips}",
"key": settings.census_api_key,
},
)
response.raise_for_status()
header, *rows = response.json()
return dict(zip(header, rows[0])) if rows else {}Step 2 — Wire it up in tools/__init__.py:
from .census import register_census_tools
def register_tools(mcp: FastMCP) -> None:
register_example_tools(mcp)
register_census_tools(mcp)Step 3 — Add any API keys to config.py:
class Settings(BaseSettings):
...
census_api_key: str = "" # https://api.census.gov/data/key_signup.htmlAnd document it in .env.example:
CENSUS_API_KEY=your_key_here
Prompts are reusable message templates — they appear as conversation starters or slash commands in Claude's UI.
Step 1 — Create a prompt file:
# src/fed_gov_mcp/prompts/census.py
from fastmcp import FastMCP
def register_census_prompts(mcp: FastMCP) -> None:
@mcp.prompt()
def explore_census_data(state: str, topic: str = "population") -> str:
"""Start an exploration session for Census Bureau data.
Args:
state: State name or abbreviation to focus on.
topic: What to investigate — e.g. "population", "income", "housing".
"""
return (
f"I want to explore {topic} data for {state} using the Census Bureau ACS. "
"Please start by describing what estimates are available, "
"then help me investigate trends and comparisons."
)Step 2 — Wire it up in prompts/__init__.py:
from .census import register_census_prompts
def register_prompts(mcp: FastMCP) -> None:
register_example_prompts(mcp)
register_census_prompts(mcp)Resources expose static or semi-static data (reference tables, data dictionaries, schemas) that Claude can read directly without calling a tool.
Step 1 — Create a resource file:
# src/fed_gov_mcp/resources/census.py
from fastmcp import FastMCP
def register_census_resources(mcp: FastMCP) -> None:
@mcp.resource("census://reference/state-fips")
def state_fips_codes() -> str:
"""FIPS code reference table for all US states and territories.
Use these codes as the state_fips argument when calling Census tools.
"""
return (
"State FIPS Code Reference\n\n"
"01 Alabama 02 Alaska 04 Arizona 05 Arkansas\n"
"06 California 08 Colorado 09 Connecticut ...\n"
"(replace with full table or load from a bundled CSV)"
)For parameterized resources, include a path variable in the URI:
@mcp.resource("census://docs/{topic}")
def census_docs(topic: str) -> str:
"""API documentation for a Census Bureau topic."""
docs = {
"acs1": "ACS 1-Year: annual, areas ≥65k population ...",
"acs5": "ACS 5-Year: rolling average, all geographies ...",
}
return docs.get(topic, f"No documentation found for topic '{topic}'.")Step 2 — Wire it up in resources/__init__.py:
from .census import register_census_resources
def register_resources(mcp: FastMCP) -> None:
register_example_resources(mcp)
register_census_resources(mcp)Custom routes are only active when MCP_TRANSPORT=streamable-http. Add infrastructure endpoints (readiness probes, version info, metrics) in routes.py rather than server.py.
# src/fed_gov_mcp/routes.py
from fastmcp import FastMCP
from starlette.requests import Request
from starlette.responses import JSONResponse
def register_routes(mcp: FastMCP) -> None:
@mcp.custom_route("/health", methods=["GET"])
async def health_check(request: Request) -> JSONResponse:
return JSONResponse({"status": "healthy", "service": "mcp-server"})
@mcp.custom_route("/version", methods=["GET"])
async def version(request: Request) -> JSONResponse:
return JSONResponse({"version": "1.0.0"})All settings are loaded from environment variables (or .env).
| Variable | Default | Description |
|---|---|---|
MCP_TRANSPORT |
stdio |
stdio for local, streamable-http for remote |
MCP_HOST |
0.0.0.0 |
Bind address (HTTP mode only) |
MCP_PORT |
8000 |
Port (HTTP mode only) |
Add dataset-specific keys as typed fields in src/fed_gov_mcp/config.py.
Add to your project's .claude/settings.json or user MCP settings:
{
"mcpServers": {
"fed-gov-mcp": {
"command": "uv",
"args": ["run", "fed-gov-mcp"],
"cwd": "/absolute/path/to/fed-gov-mcp-template"
}
}
}Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"fed-gov-mcp": {
"command": "uv",
"args": ["run", "fed-gov-mcp"],
"cwd": "/absolute/path/to/fed-gov-mcp-template"
}
}
}See DEPLOYMENT.md for cloud deployment instructions. Once deployed, connect with:
{
"mcpServers": {
"fed-gov-mcp": {
"type": "http",
"url": "https://your-server.example.com/mcp"
}
}
}# Install dev dependencies
uv sync --group dev
# Run tests
uv run pytest tests/ -v
# Lint
uv run ruff check .
# Format
uv run ruff format .Each federal API gets its own file in tools/. This keeps the tool list scannable and lets you add or remove an entire integration by touching two lines in tools/__init__.py.
Return dict/list with consistent keys. Let Claude narrate and interpret the data rather than building narrative into your tool implementations.
Federal datasets often have publication lags. State the update frequency and "as-of" date in your tool docstrings. Claude will surface this to users when relevant.
Most federal APIs paginate. Either expose offset/limit to the caller, or handle multi-page fetches internally — but document which you chose and what the max sensible limit is.
Federal APIs can be slow or intermittently unresponsive. Always pass timeout=30.0 (or higher for large exports) to httpx.Client.
Reference data (state FIPS codes, agency lists) can be cached. Time-sensitive data (spending figures, health statistics) should not be cached without surfacing a retrieved_at or as_of field in the response.
See DEPLOYMENT.md.