get_cfr_structure
Retrieve the hierarchical table of contents for a CFR title or subset, returning a nested tree of titles, chapters, parts, subparts, and sections with identifiers and descriptions.
Instructions
Get the hierarchical table of contents for a CFR title or subset.
Returns a nested tree of titles, chapters, parts, subparts, and sections with identifiers, descriptions, and byte sizes.
IMPORTANT: Does NOT support section-level filtering (returns 400). Use part or subpart, then walk the children to find sections.
Common patterns:
chapter='1' for all FAR parts
chapter='2' for all DFARS parts
part='15' for FAR Part 15 structure
subpart='15.3' for just that subpart's sections
part/subpart/chapter accept int or string.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| title_number | No | ||
| date | No | ||
| chapter | No | ||
| subchapter | No | ||
| part | No | ||
| subpart | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- The tool handler function for 'get_cfr_structure'. Validates inputs, resolves the date, builds a request to the eCFR structure API endpoint, and returns the hierarchical table of contents as JSON.
@mcp.tool(annotations={"title": "Get CFR Structure", "readOnlyHint": True, "destructiveHint": False}) async def get_cfr_structure( title_number: int = 48, date: str | None = None, chapter: Any = None, subchapter: Any = None, part: Any = None, subpart: Any = None, ) -> dict[str, Any]: """Get the hierarchical table of contents for a CFR title or subset. Returns a nested tree of titles, chapters, parts, subparts, and sections with identifiers, descriptions, and byte sizes. IMPORTANT: Does NOT support section-level filtering (returns 400). Use part or subpart, then walk the children to find sections. Common patterns: - chapter='1' for all FAR parts - chapter='2' for all DFARS parts - part='15' for FAR Part 15 structure - subpart='15.3' for just that subpart's sections part/subpart/chapter accept int or string. """ title_number = _validate_title_number(title_number) date = _validate_date_ymd(date, field="date") chapter = _validate_chapter(chapter, title_number=title_number) subchapter = _coerce_cfr_str(subchapter, field="subchapter", maxlen=8) part = _coerce_cfr_str(part, field="part", strip_prefixes=True) subpart = _coerce_cfr_str(subpart, field="subpart", strip_prefixes=True) if date is None: date = await _resolve_date(title_number) path = f"/api/versioner/v1/structure/{date}/title-{title_number}.json" params: dict[str, str] = {} if chapter: params["chapter"] = chapter if subchapter: params["subchapter"] = subchapter if part: params["part"] = part if subpart: params["subpart"] = subpart return await _get_json(path, params, timeout=DEFAULT_TIMEOUT_STRUCTURE) - servers/ecfr-mcp/src/ecfr_mcp/server.py:628-628 (registration)Registration of 'get_cfr_structure' as an MCP tool via the @mcp.tool decorator. The mcp instance is created as FastMCP('ecfr') on line 36.
@mcp.tool(annotations={"title": "Get CFR Structure", "readOnlyHint": True, "destructiveHint": False}) - Input validation/schema enforcement for parameters: validates title_number (1-50), date (YYYY-MM-DD), chapter (validated against known chapters for title 48), and coerce/normalize subchapter, part, subpart strings.
title_number = _validate_title_number(title_number) date = _validate_date_ymd(date, field="date") chapter = _validate_chapter(chapter, title_number=title_number) subchapter = _coerce_cfr_str(subchapter, field="subchapter", maxlen=8) part = _coerce_cfr_str(part, field="part", strip_prefixes=True) subpart = _coerce_cfr_str(subpart, field="subpart", strip_prefixes=True) - Helper used by get_cfr_structure to resolve the latest available date when none is provided by the caller.
async def _resolve_date(title_number: int) -> str: """Resolve the latest available date for a CFR title. Called before any versioner endpoint. Using today's date often returns 404 because eCFR lags 1-2 business days. Raises ValueError with an actionable message for reserved titles (which have null up_to_date_as_of) rather than building a URL with 'None' in it. """ data = await _get_json("/api/versioner/v1/titles.json") titles = _as_list(_safe_dict(data).get("titles")) for title in titles: t = _safe_dict(title) if _safe_int(t.get("number")) == title_number: utd = t.get("up_to_date_as_of") if not isinstance(utd, str) or not utd.strip(): reason = "this title is marked 'reserved'" if t.get("reserved") else ( "the API did not return up_to_date_as_of" ) raise ValueError( f"Cannot resolve a date for title {title_number}: {reason}. " f"Reserved or un-issued titles have no published content." ) return utd raise ValueError(f"Title {title_number} not found in eCFR titles list.") - HTTP GET helper for JSON endpoints used by get_cfr_structure to fetch the structure data from the eCFR API.
async def _get_json( path: str, params: dict[str, Any] | None = None, timeout: float = DEFAULT_TIMEOUT_JSON, ) -> dict[str, Any]: """GET helper for JSON endpoints. Always returns a dict (empty if API returned null).""" try: r = await _get_client().get(path, params=params or {}, timeout=timeout) except httpx.RequestError as e: raise RuntimeError(f"Network error calling eCFR: {e}") from e if r.status_code >= 400: raise RuntimeError(_format_error(r.status_code, r.text)) try: data = r.json() except (ValueError, _json.JSONDecodeError) as e: preview = _clean_error_body(r.text or "(empty body)")[:200] ct = r.headers.get("content-type", "?") raise RuntimeError( f"eCFR returned a non-JSON response (status {r.status_code}, " f"content-type={ct!r}): {preview}" ) from e if data is None: return {} if not isinstance(data, (dict, list)): raise RuntimeError( f"eCFR returned unexpected JSON type {type(data).__name__}: {str(data)[:200]}" ) return data if isinstance(data, dict) else {"_list": data}