Get Bill Detail
bills_get_billRetrieve full details of a UK parliamentary bill including sponsors, current stage, long title, summary, and Royal Assent date. Specify a bill ID and optionally limit summary text length.
Instructions
Get full detail for a specific parliamentary bill.
Returns sponsors, current stage, long title, summary, and Royal Assent date if enacted. Summary text is capped per max_summary_chars — check summary_truncated in the response to see if it was cut.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes | BillDetailInput with bill_id and optional max_summary_chars. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Bill ID | |
| short_title | Yes | Short title of the bill | |
| long_title | No | Full long title | |
| summary | No | Bill summary text, possibly truncated per max_summary_chars. Check summary_truncated and summary_original_length for full-text info. | |
| summary_truncated | No | True if summary was cut to fit max_summary_chars | |
| summary_original_length | No | Original summary length in characters before any truncation | |
| current_house | No | House where the bill currently sits | |
| originating_house | No | House where the bill was introduced | |
| current_stage | No | Current legislative stage | |
| sponsors | No | Bill sponsors | |
| stages | No | Legislative stages the bill has passed through | |
| is_act | No | Whether the bill has received Royal Assent | |
| royal_assent_date | No | Date Royal Assent was given | |
| url | Yes | Parliament URL for this bill |
Implementation Reference
- src/modules/bills/tools.py:232-249 (handler)The async handler function for bills_get_bill. It receives a BillDetailInput, fetches bill detail from the UK Parliament Bills API, and returns a parsed BillDetail model.
@mcp.tool( name="get_bill", annotations={"title": "Get Bill Detail", "readOnlyHint": True, "destructiveHint": False, "idempotentHint": True, "openWorldHint": True}, ) async def bills_get_bill(params: BillDetailInput, ctx: Context) -> BillDetail: """Get full detail for a specific parliamentary bill. Returns sponsors, current stage, long title, summary, and Royal Assent date if enacted. Summary text is capped per max_summary_chars — check summary_truncated in the response to see if it was cut. Args: params: BillDetailInput with bill_id and optional max_summary_chars. """ client: httpx.AsyncClient = ctx.lifespan_context["http"] resp = await client.get(f"{BILLS_BASE}/Bills/{params.bill_id}") resp.raise_for_status() return _parse_bill_detail(resp.json(), params.max_summary_chars) - src/modules/bills/tools.py:68-82 (schema)Input schema for bills_get_bill. Requires bill_id (int, >=1) and optional max_summary_chars (int, 500-50000, default 5000).
class BillDetailInput(BaseModel): model_config = ConfigDict(str_strip_whitespace=True, extra="forbid") bill_id: int = Field(..., description="Bill ID from bills_search_bills results.", ge=1) max_summary_chars: int = Field( 5000, ge=500, le=50000, description=( "Maximum characters of the bill summary text to return. Default " "5,000 (~1,250 tokens) covers most bills. Raise for substantive " "government bills (Finance Act, Levelling-up) whose summary runs " "longer. Check summary_truncated in the response to see if it was cut." ), ) - src/modules/bills/models.py:74-99 (schema)Output schema (response model) for bills_get_bill. Contains all bill detail fields: id, title, summary (with truncation info), house, stage, sponsors, stages, is_act, royal_assent_date, and URL.
class BillDetail(BaseModel): model_config = ConfigDict(str_strip_whitespace=True) id: int = Field(..., description="Bill ID") short_title: str = Field(..., description="Short title of the bill") long_title: str | None = Field(None, description="Full long title") summary: str | None = Field(None, description=( "Bill summary text, possibly truncated per max_summary_chars. " "Check summary_truncated and summary_original_length for full-text info." )) summary_truncated: bool = Field( False, description="True if summary was cut to fit max_summary_chars", ) summary_original_length: int = Field( 0, description="Original summary length in characters before any truncation", ) current_house: str | None = Field(None, description="House where the bill currently sits") originating_house: str | None = Field(None, description="House where the bill was introduced") current_stage: str | None = Field(None, description="Current legislative stage") sponsors: list[BillSponsor] = Field(default_factory=list, description="Bill sponsors") stages: list[BillStage] = Field(default_factory=list, description="Legislative stages the bill has passed through") is_act: bool = Field(False, description="Whether the bill has received Royal Assent") royal_assent_date: Date | None = Field(None, description="Date Royal Assent was given") url: str = Field(..., description="Parliament URL for this bill") - src/modules/bills/tools.py:111-177 (helper)Helper function that parses the raw API JSON response into a BillDetail model, extracting sponsors, current stage, summary (with truncation), and other metadata.
def _parse_bill_detail(data: dict, max_summary_chars: int) -> BillDetail: sponsors = [] for s in data.get("sponsors", []): member = s.get("member", {}) sponsors.append(BillSponsor( name=member.get("name", s.get("name", "Unknown")), party=member.get("party"), house=_parse_house(member.get("house")), )) stages = [] current_stage_name = None current_stage_data = data.get("currentStage") if isinstance(current_stage_data, dict): stage_name = current_stage_data.get("description") or current_stage_data.get("stageName", "Unknown") current_stage_name = stage_name sitting_date = None sittings = current_stage_data.get("stageSittings", []) if sittings and isinstance(sittings, list): date_str = sittings[0].get("date", "") if date_str: try: sitting_date = date.fromisoformat(date_str[:10]) except ValueError: pass stages.append(BillStage( name=stage_name, house=_parse_house(current_stage_data.get("house")), date=sitting_date, is_current=True, )) royal_assent_date = None raw_summary = data.get("summary") summary: str | None if raw_summary: summary_original_length = len(raw_summary) if summary_original_length > max_summary_chars: summary_truncated = True summary = raw_summary[:max_summary_chars] + " …[truncated]" else: summary_truncated = False summary = raw_summary else: summary = None summary_truncated = False summary_original_length = 0 return BillDetail( id=data.get("billId", 0), short_title=data.get("shortTitle", "Unknown"), long_title=data.get("longTitle"), summary=summary, summary_truncated=summary_truncated, summary_original_length=summary_original_length, current_house=_parse_house(data.get("currentHouse")), originating_house=_parse_house(data.get("originatingHouse")), current_stage=current_stage_name, sponsors=sponsors, stages=stages, is_act=data.get("isAct", False), royal_assent_date=royal_assent_date, url=f"https://bills.parliament.uk/bills/{data.get('billId', 0)}", ) - src/modules/bills/__init__.py:8-20 (registration)Registration of the bills MCP server. register_tools(bills_mcp) is called, which is where the @mcp.tool decorator registers bills_get_bill as the tool named 'get_bill'.
bills_mcp = FastMCP( name="bills", instructions=( "Search and retrieve UK parliamentary bills. " "Use bills_search_bills to find bills by keyword, session, house, or legislative stage. " "Use bills_get_bill to get full detail (sponsors, stages, progress) for a specific bill. " "All data from bills-api.parliament.uk. No authentication required." ), ) bills_mcp.add_middleware(ResponseCachingMiddleware(call_tool_settings=CallToolSettings(ttl=3600))) register_tools(bills_mcp)