Search Disqualified Directors
disqualified_searchCheck whether an individual is disqualified from being a UK company director. Enter a person's name to retrieve disqualification records and dates of birth.
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:55-108 (handler)The handler function for the disqualified_search tool. Calls Companies House /search/disqualified-officers API with the query name, pagination params, 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-351 (schema)DisqualifiedSearchItem Pydantic model representing a single hit in disqualified officers search results (officer_id, title, date_of_birth, snippet, address, links).
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.).", ) - models.py:354-376 (schema)DisqualifiedSearchResult Pydantic model representing the paginated search result (query, total_results, start_index, items_per_page, returned, has_more, items).
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.", ) - disqualified.py:40-54 (registration)Registration of the disqualified_search tool via @mcp.tool(name="disqualified_search", ...) inside register_tools(), called from server.py line 160.
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, }, ) - disqualified.py:28-33 (helper)Helper _extract_officer_id() that parses the officer ID from the 'self' link in the API response.
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