Search Parliamentary Divisions
votes_search_divisionsSearch parliamentary divisions in the UK Commons or Lords. Filter by title, date, or member to find vote results and passage status.
Instructions
Search parliamentary divisions (votes) in the Commons or Lords.
Returns division summaries including title, date, vote counts, and whether the motion passed. Use votes_get_division with the division ID for full voter lists.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes | DivisionSearchInput with optional query, house, date range, member filter. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | The search term, if any (None = browse recent) | |
| house | Yes | Commons or Lords | |
| offset | No | Skip applied to this page | |
| limit | No | Page size requested | |
| total | Yes | Number of divisions returned in this call | |
| has_more | No | True if a full page was returned (more may exist) | |
| divisions | No | Matching divisions. Use the integer `id` field with votes_get_division to fetch the full voter list. |
Implementation Reference
- src/modules/votes/tools.py:112-156 (handler)The main handler function that executes the 'votes_search_divisions' tool logic. Accepts DivisionSearchInput params, builds query parameters, calls the Parliament API (Commons or Lords), parses results using _parse_commons_summary or _parse_lords_summary, and returns a DivisionsSearchResult.
async def votes_search_divisions(params: DivisionSearchInput, ctx: Context) -> DivisionsSearchResult: """Search parliamentary divisions (votes) in the Commons or Lords. Returns division summaries including title, date, vote counts, and whether the motion passed. Use votes_get_division with the division ID for full voter lists. Args: params: DivisionSearchInput with optional query, house, date range, member filter. """ client: httpx.AsyncClient = ctx.lifespan_context["http"] url = _search_url(params.house) qp: dict = { "queryParameters.take": params.limit, "queryParameters.skip": params.offset, } if params.query: qp["queryParameters.searchTerm"] = params.query if params.from_date: qp["queryParameters.startDate"] = params.from_date.isoformat() if params.to_date: qp["queryParameters.endDate"] = params.to_date.isoformat() if params.member_id: qp["queryParameters.memberId"] = params.member_id resp = await client.get(url, params=qp) resp.raise_for_status() data = resp.json() items = data if isinstance(data, list) else data.get("results", data.get("items", [])) if params.house == "Lords": divisions = [_parse_lords_summary(item) for item in items] else: divisions = [_parse_commons_summary(item) for item in items] return DivisionsSearchResult( query=params.query, house=params.house, offset=params.offset, limit=params.limit, total=len(divisions), has_more=len(divisions) == params.limit, divisions=divisions, ) - src/modules/votes/tools.py:25-46 (schema)DivisionSearchInput Pydantic model defining input schema: query, house (Commons/Lords), from_date, to_date, member_id, offset, limit.
class DivisionSearchInput(BaseModel): model_config = ConfigDict(str_strip_whitespace=True, extra="forbid") query: str | None = Field(None, description=( "Search term for division titles, e.g. 'Rwanda' or 'Online Safety Bill'. " "Omit to browse recent divisions." ), max_length=500) house: Literal["Commons", "Lords"] = Field("Commons", description="Which house to search.") from_date: date | None = Field(None, description="Start date (YYYY-MM-DD).") to_date: date | None = Field(None, description="End date (YYYY-MM-DD).") member_id: int | None = Field(None, description=( "Filter to divisions where this member voted. " "Get the member ID from parliament_find_member." ), ge=1) offset: int = Field(0, ge=0, le=2000, description=( "Number of divisions to skip before this page. Default 0. " "Re-call with offset=offset+returned while has_more is true." )) limit: int = Field(25, ge=1, le=100, description=( "Maximum divisions to return. Default 25 (Commons API max-per-page)." )) - src/modules/votes/models.py:47-70 (schema)DivisionsSearchResult Pydantic model defining the output schema: query, house, offset, limit, total, has_more, and list of DivisionSummary objects.
class DivisionsSearchResult(BaseModel): """Result of a parliamentary divisions search. Wraps the list of matching divisions with search metadata so the LLM client sees a real nested object on the wire rather than a stringified JSON blob. """ model_config = ConfigDict(str_strip_whitespace=True) query: str | None = Field(None, description="The search term, if any (None = browse recent)") house: str = Field(..., description="Commons or Lords") offset: int = Field(0, description="Skip applied to this page") limit: int = Field(25, description="Page size requested") total: int = Field(..., description="Number of divisions returned in this call") has_more: bool = Field(False, description="True if a full page was returned (more may exist)") divisions: list[DivisionSummary] = Field( default_factory=list, description=( "Matching divisions. Use the integer `id` field with " "votes_get_division to fetch the full voter list." ), ) - src/modules/votes/__init__.py:19-21 (registration)Registration: register_tools(votes_mcp) is called to register all tools on the votes FastMCP instance.
register_tools(votes_mcp) - src/modules/votes/tools.py:106-111 (registration)Registration decorator: @mcp.tool(name='search_divisions') registers the function under the name 'search_divisions' (the MCP tool name, invoked as votes_search_divisions via the module prefix).
def register_tools(mcp: FastMCP) -> None: @mcp.tool( name="search_divisions", annotations={"title": "Search Parliamentary Divisions", "readOnlyHint": True, "destructiveHint": False, "idempotentHint": True, "openWorldHint": True}, )