Skip to main content
Glama
paulieb89

UK Legal Research MCP Server

Search Parliamentary Divisions

votes_search_divisions
Read-onlyIdempotent

Search 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

TableJSON Schema
NameRequiredDescriptionDefault
paramsYesDivisionSearchInput with optional query, house, date range, member filter.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryNoThe search term, if any (None = browse recent)
houseYesCommons or Lords
offsetNoSkip applied to this page
limitNoPage size requested
totalYesNumber of divisions returned in this call
has_moreNoTrue if a full page was returned (more may exist)
divisionsNoMatching divisions. Use the integer `id` field with votes_get_division to fetch the full voter list.

Implementation Reference

  • 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,
        )
  • 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)."
        ))
  • 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."
            ),
        )
  • Registration: register_tools(votes_mcp) is called to register all tools on the votes FastMCP instance.
    register_tools(votes_mcp)
  • 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},
        )
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations indicate read-only, idempotent behavior. The description adds that it returns summaries including title, date, vote counts, and motion status, going beyond annotations. Pagination is not mentioned at the top level but is covered in parameter descriptions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with purpose, and every sentence is essential. No wasted words or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With a complete output schema and thorough parameter schema, the description adequately covers the tool's purpose and basic behavior. It provides sufficient context for correct invocation, including guidance on when to use the sibling tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already fully documents each parameter. The description does not add additional semantic value beyond what is in the schema, meeting the baseline expectation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool searches parliamentary divisions (votes) in the Commons or Lords, and explicitly distinguishes itself from the sibling votes_get_division by directing users to that tool for full voter lists.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use this tool (searching for division summaries) and directs users to an alternative (votes_get_division) for detailed voter lists, though it does not explicitly list all exclusions or prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/paulieb89/uk-legal-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server