Skip to content

GSA-TTS/fed-gov-mcp-template

Repository files navigation

Federal Government MCP Template

A template for building MCP servers that expose federal government datasets to Claude and other MCP-compatible clients.

Built with FastMCP and uv.


Repo Structure

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

Getting Started

Prerequisites

  • uvpip install uv or brew install uv

Install

cp .env.example .env
uv sync

Run

uv run python main.py
# or
uv run fed-gov-mcp

The 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.


Adding Your First Tool

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.html

And document it in .env.example:

CENSUS_API_KEY=your_key_here

Adding a Prompt

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)

Adding a Resource

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)

Adding a Custom HTTP Route

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"})

Configuration

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.


Connecting to Claude

Claude Code (local stdio)

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"
    }
  }
}

Claude Desktop (local stdio)

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"
    }
  }
}

Remote HTTP

See DEPLOYMENT.md for cloud deployment instructions. Once deployed, connect with:

{
  "mcpServers": {
    "fed-gov-mcp": {
      "type": "http",
      "url": "https://your-server.example.com/mcp"
    }
  }
}

Development

# Install dev dependencies
uv sync --group dev

# Run tests
uv run pytest tests/ -v

# Lint
uv run ruff check .

# Format
uv run ruff format .

Design Notes

One file per dataset

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 structured data, not prose

Return dict/list with consistent keys. Let Claude narrate and interpret the data rather than building narrative into your tool implementations.

Document freshness explicitly

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.

Expose pagination parameters

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.

Use explicit timeouts

Federal APIs can be slow or intermittently unresponsive. Always pass timeout=30.0 (or higher for large exports) to httpx.Client.

Never cache without a timestamp

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.


Remote Deployment

See DEPLOYMENT.md.

About

A template for building MCP servers that expose federal government datasets to Claude and other MCP-compatible clients.

Resources

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors