Search Disqualified Directors
disqualified_searchCheck if a named person is disqualified from acting as a UK company director, retrieving disqualification details and officer IDs.
Instructions
Check whether a named individual is banned from acting as a UK company director.
Use this tool when asked to check disqualified, banned, or barred directors. Query must be an individual's name (e.g. "Richard Howson") — NOT a company name, which always returns zero results.
Returns names, dates of birth, disqualification period snippets, and officer IDs that can be used with disqualified_profile for full details.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Name of the person to search for | |
| items_per_page | No | Results per page (max 100). Default 20. | |
| start_index | No | Pagination offset (0-based). Default 0. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query applied. | |
| total_results | Yes | Total matching records upstream at Companies House. | |
| start_index | Yes | Pagination offset for this page. | |
| items_per_page | Yes | Page size requested. | |
| returned | Yes | Items actually returned on this page. | |
| has_more | Yes | True if more items may exist beyond this page. Re-call with start_index=start_index+items_per_page to continue. | |
| items | No | Matching disqualified officer records. |
Implementation Reference
- disqualified.py:36-108 (registration)The `register_tools` function registers the `disqualified_search` tool on the FastMCP server via the `@mcp.tool` decorator at line 45-54. The tool is named 'disqualified_search' and is exposed as an MCP tool.
# --------------------------------------------------------------------------- # Tool registration # --------------------------------------------------------------------------- def register_tools(mcp: FastMCP) -> None: # ------------------------------------------------------------------ # # 1. disqualified_search # ------------------------------------------------------------------ # @mcp.tool( name="disqualified_search", annotations={ "title": "Search Disqualified Directors", "readOnlyHint": True, "destructiveHint": False, "idempotentHint": True, "openWorldHint": True, }, ) async def disqualified_search( query: Annotated[str, Field(description="Name of the person to search for", min_length=2, max_length=200)], items_per_page: Annotated[int, Field(description="Results per page (max 100). Default 20.", ge=1, le=100)] = 20, start_index: Annotated[int, Field(description="Pagination offset (0-based). Default 0.", ge=0, le=10000)] = 0, ) -> DisqualifiedSearchResult: """Check whether a named individual is banned from acting as a UK company director. Use this tool when asked to check disqualified, banned, or barred directors. Query must be an individual's name (e.g. "Richard Howson") — NOT a company name, which always returns zero results. Returns names, dates of birth, disqualification period snippets, and officer IDs that can be used with disqualified_profile for full details. """ try: async with companies_house_client() as client: resp = await _request_with_retry( client, "GET", "/search/disqualified-officers", params={ "q": query.strip(), "items_per_page": items_per_page, "start_index": start_index, }, ) data = resp.json() except Exception: data = {} raw_items = data.get("items", []) or [] total_results = int(data.get("total_results", 0) or 0) items = [ DisqualifiedSearchItem( officer_id=_extract_officer_id(raw.get("links") or {}), title=raw.get("title"), date_of_birth=raw.get("date_of_birth"), snippet=raw.get("snippet"), address=raw.get("address") or {}, links=raw.get("links") or {}, ) for raw in raw_items ] has_more = (start_index + len(items)) < total_results return DisqualifiedSearchResult( query=query, total_results=total_results, start_index=start_index, items_per_page=items_per_page, returned=len(items), has_more=has_more, items=items, ) - disqualified.py:55-108 (handler)The `disqualified_search` async function is the core handler that executes the tool logic. It takes a person's name (query), items_per_page, and start_index, then calls the Companies House API endpoint `/search/disqualified-officers` with retry logic, parses results into `DisqualifiedSearchItem` objects, and returns a `DisqualifiedSearchResult`.
async def disqualified_search( query: Annotated[str, Field(description="Name of the person to search for", min_length=2, max_length=200)], items_per_page: Annotated[int, Field(description="Results per page (max 100). Default 20.", ge=1, le=100)] = 20, start_index: Annotated[int, Field(description="Pagination offset (0-based). Default 0.", ge=0, le=10000)] = 0, ) -> DisqualifiedSearchResult: """Check whether a named individual is banned from acting as a UK company director. Use this tool when asked to check disqualified, banned, or barred directors. Query must be an individual's name (e.g. "Richard Howson") — NOT a company name, which always returns zero results. Returns names, dates of birth, disqualification period snippets, and officer IDs that can be used with disqualified_profile for full details. """ try: async with companies_house_client() as client: resp = await _request_with_retry( client, "GET", "/search/disqualified-officers", params={ "q": query.strip(), "items_per_page": items_per_page, "start_index": start_index, }, ) data = resp.json() except Exception: data = {} raw_items = data.get("items", []) or [] total_results = int(data.get("total_results", 0) or 0) items = [ DisqualifiedSearchItem( officer_id=_extract_officer_id(raw.get("links") or {}), title=raw.get("title"), date_of_birth=raw.get("date_of_birth"), snippet=raw.get("snippet"), address=raw.get("address") or {}, links=raw.get("links") or {}, ) for raw in raw_items ] has_more = (start_index + len(items)) < total_results return DisqualifiedSearchResult( query=query, total_results=total_results, start_index=start_index, items_per_page=items_per_page, returned=len(items), has_more=has_more, items=items, ) - models.py:323-376 (schema)`DisqualifiedSearchItem` (lines 323-351) and `DisqualifiedSearchResult` (lines 354-376) are Pydantic models defining the output schema for the disqualified_search tool. They include fields like officer_id, title, date_of_birth, address, pagination fields (total_results, start_index, has_more), and the list of items.
class DisqualifiedSearchItem(BaseModel): """A single hit in a disqualified officers search.""" model_config = BASE_CFG officer_id: str | None = Field( None, description=( "Companies House officer ID extracted from the self link. Pass to " "disqualified_profile for the full disqualification record." ), ) title: str | None = Field( None, description="Display title (typically the officer's name)." ) date_of_birth: str | None = Field( None, description="Date of birth as returned by the search API." ) snippet: str | None = Field( None, description="Upstream match snippet highlighting query terms." ) address: dict[str, Any] = Field( default_factory=dict, description="Last known address of the disqualified officer.", ) links: dict[str, Any] = Field( default_factory=dict, description="Upstream relational links (self, etc.).", ) class DisqualifiedSearchResult(BaseModel): """Paginated disqualified officers search result.""" model_config = BASE_CFG query: str = Field(..., description="Search query applied.") total_results: int = Field( ..., description="Total matching records upstream at Companies House." ) start_index: int = Field(..., description="Pagination offset for this page.") items_per_page: int = Field(..., description="Page size requested.") returned: int = Field(..., description="Items actually returned on this page.") has_more: bool = Field( ..., description=( "True if more items may exist beyond this page. Re-call with " "start_index=start_index+items_per_page to continue." ), ) items: list[DisqualifiedSearchItem] = Field( default_factory=list, description="Matching disqualified officer records.", ) - server.py:158-164 (registration)The top-level server registration at line 160 calls `disqualified.register_tools(mcp)`, which triggers the registration of the `disqualified_search` tool from the disqualified module.
companies_house.register_tools(mcp) charity.register_tools(mcp) disqualified.register_tools(mcp) land_registry.register_tools(mcp) gazette.register_tools(mcp) hmrc_vat.register_tools(mcp) search_fetch.register_tools(mcp) - disqualified.py:28-33 (helper)The `_extract_officer_id` helper function extracts the officer ID from the 'self' link in the API response, used to populate the `officer_id` field in `DisqualifiedSearchItem`.
def _extract_officer_id(links: dict[str, Any]) -> str | None: self_link = (links or {}).get("self", "") if isinstance(links, dict) else "" if not self_link: return None tail = self_link.rstrip("/").rsplit("/", 1)[-1] return tail or None