Skip to main content
Glama

Fetch Full Record from UK Due Diligence Register

fetch
Read-onlyIdempotent

Retrieve full company profiles, charity records, disqualified director details, and gazette notices using prefixed IDs from UK registers.

Instructions

Fetch the full record for an ID returned by search.

Routes by prefix to the appropriate register:

  • company:{number} → Companies House full profile

  • charity:{number} → Charity Commission full profile

  • disqualification:{officer_id} → Disqualified director full record

  • notice:{notice_id} → Gazette notice full legal text

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
idYesPrefixed record ID returned by search. Format: company:{number}, charity:{number}, disqualification:{officer_id}, or notice:{notice_id}

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The `fetch` tool handler function. It takes a prefixed ID (e.g., company:1234), partitions by colon to extract the prefix and value, then routes to the appropriate backend fetch helper (_fetch_company_profile, _fetch_charity_profile, _fetch_disqualified_profile, or _fetch_gazette_notice). Returns a dict with id, title, content (JSON), and metadata.
    @mcp.tool(
        name="fetch",
        annotations={
            "title": "Fetch Full Record from UK Due Diligence Register",
            "readOnlyHint": True,
            "destructiveHint": False,
            "idempotentHint": True,
            "openWorldHint": True,
        },
    )
    async def fetch(
        id: Annotated[str, Field(description="Prefixed record ID returned by search. Format: company:{number}, charity:{number}, disqualification:{officer_id}, or notice:{notice_id}", min_length=3, max_length=100)],
    ) -> dict:
        """Fetch the full record for an ID returned by search.
    
        Routes by prefix to the appropriate register:
        - company:{number} → Companies House full profile
        - charity:{number} → Charity Commission full profile
        - disqualification:{officer_id} → Disqualified director full record
        - notice:{notice_id} → Gazette notice full legal text
        """
        prefix, _, value = id.partition(":")
        if not value:
            raise ValueError(f"Invalid ID format {id!r} — expected prefix:value")
    
        if prefix == "company":
            co = await _fetch_company_profile(_normalise_company_number(value))
            return {
                "id": id,
                "title": co.company_name or value,
                "content": co.model_dump_json(),
                "metadata": {
                    "source": "companies_house",
                    "status": co.company_status,
                    "company_type": co.company_type,
                    "date_of_creation": co.date_of_creation,
                },
            }
    
        if prefix == "charity":
            ch = await _fetch_charity_profile(value)
            return {
                "id": id,
                "title": ch.charity_name or value,
                "content": ch.model_dump_json(),
                "metadata": {
                    "source": "charity_commission",
                    "status": ch.reg_status_label,
                    "date_of_registration": ch.date_of_registration,
                },
            }
    
        if prefix == "disqualification":
            dq = await _fetch_disqualified_profile(value)
            return {
                "id": id,
                "title": dq.name or value,
                "content": dq.model_dump_json(),
                "metadata": {
                    "source": "companies_house_disqualified",
                    "officer_kind": dq.officer_kind,
                },
            }
    
        if prefix == "notice":
            data: dict[str, Any] = await _fetch_gazette_notice(value)
            title = (
                data.get("title")
                or data.get("f:notice-code")
                or f"Gazette notice {value}"
            )
            return {
                "id": id,
                "title": title,
                "content": json.dumps(data),
                "metadata": {"source": "gazette"},
            }
    
        raise ValueError(
            f"Unknown ID prefix {prefix!r}. Valid prefixes: company, charity, disqualification, notice"
        )
  • Input schema for the `fetch` tool: a single `id` parameter of type string with min_length=3 and max_length=100, described as a prefixed record ID returned by search.
    async def fetch(
        id: Annotated[str, Field(description="Prefixed record ID returned by search. Format: company:{number}, charity:{number}, disqualification:{officer_id}, or notice:{notice_id}", min_length=3, max_length=100)],
  • The `register_tools(mcp)` function that registers the `fetch` (and `search`) tool on the FastMCP instance via the `@mcp.tool(name='fetch')` decorator.
    def register_tools(mcp: FastMCP) -> None:
  • server.py:164-164 (registration)
    The call to `search_fetch.register_tools(mcp)` in server.py which triggers registration of the `fetch` tool on the main MCP server.
    search_fetch.register_tools(mcp)
  • Shared helper `_fetch_company_profile` — fetches Companies House company profile and builds a CompanyProfile object (used by the `company` prefix route in the `fetch` tool).
    async def _fetch_company_profile(company_number: str) -> CompanyProfile:
        async with companies_house_client() as client:
            resp = await _request_with_retry(client, "GET", f"/company/{company_number}")
            data = resp.json()
    
            has_charges = False
            try:
                charges_resp = await _request_with_retry(
                    client, "GET", f"/company/{company_number}/charges",
                    params={"items_per_page": 1},
                )
                charges_data = charges_resp.json()
                charges_items = charges_data.get("items") or []
                has_charges = any(
                    item.get("status") == "outstanding" for item in charges_items
                ) or (
                    charges_data.get("total_count", 0) > 0
                    and not charges_items
                )
            except Exception:
                pass
    
        accs_raw = data.get("accounts") or {}
        conf_raw = data.get("confirmation_statement") or {}
    
        return CompanyProfile(
            company_number=str(data.get("company_number") or company_number),
            company_name=data.get("company_name"),
            company_status=data.get("company_status"),
            company_type=data.get("company_type"),
            date_of_creation=data.get("date_of_creation"),
            sic_codes=list(data.get("sic_codes") or []),
            registered_office_address=data.get("registered_office_address") or {},
            has_charges=has_charges,
            accounts=CompanyAccountsSummary(
                overdue=bool(accs_raw.get("overdue", False)),
                last_accounts_made_up_to=(accs_raw.get("last_accounts") or {}).get("made_up_to"),
                next_due=accs_raw.get("next_due"),
            ),
            confirmation_statement=CompanyConfirmationStatementSummary(
                overdue=bool(conf_raw.get("overdue", False)),
                next_due=conf_raw.get("next_due"),
            ),
        )
  • Shared helper `_fetch_charity_profile` — fetches Charity Commission details and builds a CharityProfile object (used by the `charity` prefix route).
    async def _fetch_charity_profile(charity_number: str) -> CharityProfile:
        async with charity_client() as client:
            suffix = "0"
            lookup_number = charity_number
            if "-" in charity_number:
                parts = charity_number.split("-", 1)
                lookup_number = parts[0]
                suffix = parts[1]
            resp = await _request_with_retry(
                client, "GET",
                f"/allcharitydetailsV2/{lookup_number}/{suffix}",
            )
            data = resp.json()
    
        reg_num = str(data.get("reg_charity_number") or lookup_number)
        raw_status = data.get("reg_status")
    
        raw_trustees = data.get("trustee_names") or []
        trustees_total = len(raw_trustees)
        trustees = [
            CharityTrustee(trustee_name=t.get("trustee_name"))
            for t in raw_trustees[:30]
            if isinstance(t, dict)
        ]
    
        raw_www = data.get("who_what_where") or []
        www_total = len(raw_www)
        classifications = [
            CharityClassification(
                classification_type=w.get("classification_type"),
                classification_desc=w.get("classification_desc"),
            )
            for w in raw_www[:50]
            if isinstance(w, dict)
        ]
    
        countries_raw = data.get("CharityAoOCountryContinent") or []
        countries = [c.get("country") for c in countries_raw[:10] if isinstance(c, dict) and c.get("country")]
    
        return CharityProfile(
            charity_number=reg_num,
            charity_name=data.get("charity_name"),
            reg_status=raw_status,
            reg_status_label=_STATUS_LABELS.get(raw_status or "", raw_status),
            charity_type=data.get("charity_type"),
            charity_co_reg_number=data.get("charity_co_reg_number") or None,
            date_of_registration=(data.get("date_of_registration") or "")[:10] or None,
            address=_build_address(data),
            latest_income=_coerce_number(data.get("latest_income")),
            latest_expenditure=_coerce_number(data.get("latest_expenditure")),
            insolvent=bool(data.get("insolvent", False)),
  • Shared helper `_fetch_disqualified_profile` — fetches disqualification records from Companies House (used by the `disqualification` prefix route).
    async def _fetch_disqualified_profile(officer_id: str) -> DisqualifiedProfile:
        oid = officer_id.strip()
        data: dict[str, Any] | None = None
        officer_kind = "natural"
    
        for kind, endpoint in [
            ("natural", f"/disqualified-officers/natural/{oid}"),
            ("corporate", f"/disqualified-officers/corporate/{oid}"),
        ]:
            try:
                async with companies_house_client() as client:
                    resp = await _request_with_retry(client, "GET", endpoint)
                    data = resp.json()
                    officer_kind = kind
                    break
            except httpx.HTTPStatusError as exc:
                if exc.response.status_code == 404:
                    continue
                raise
    
        if data is None:
            raise LookupError(
                f"No disqualification record found for officer ID {oid!r}."
            )
    
        raw_orders = data.get("disqualifications", []) or []
        orders: list[DisqualificationOrder] = []
        for raw in raw_orders:
            company_names = list(raw.get("company_names") or [])
            total_companies = len(company_names)
            truncated = total_companies > 20
            if truncated:
                company_names = company_names[:20]
    
            orders.append(
                DisqualificationOrder(
                    disqualified_from=raw.get("disqualified_from"),
                    disqualified_until=raw.get("disqualified_until"),
                    reason=raw.get("reason") or {},
                    company_names=company_names,
                    company_names_truncated=truncated,
                    company_names_total=total_companies,
                    address=raw.get("address") or {},
                    case_identifier=raw.get("case_identifier"),
                    heard_on=raw.get("heard_on"),
  • Shared helper `_fetch_gazette_notice` — fetches Gazette notice JSON-LD data (used by the `notice` prefix route).
    async def _fetch_gazette_notice(notice_id: str) -> dict:
        url = f"https://www.thegazette.co.uk/notice/{notice_id.strip()}/data.json?view=linked-data"
        async with httpx.AsyncClient(timeout=15.0) as client:
            resp = await client.get(url, headers={"Accept": "application/json"})
            resp.raise_for_status()
            return resp.json()
Behavior5/5

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

Annotations already declare readOnly, idempotent, and openWorld hints. The description adds critical routing behavior per prefix, which is not captured by annotations and is essential for correct invocation.

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?

The description is short and well-structured: an introductory sentence followed by clear bullet points. Every sentence adds value without redundancy.

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

Completeness4/5

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

For a single-parameter tool with an output schema and rich annotations, the description is mostly complete. It could mention error handling or missing ID cases, but overall it suffices.

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

Parameters4/5

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

With 100% schema coverage, baseline is 3. The description adds significant value by detailing the prefix routing outcomes, going beyond the schema's format description.

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 fetches a full record for an ID returned by search, and explains the routing by prefix, which distinguishes it from sibling tools that target specific registers directly.

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?

It explicitly states the input comes from search results, implying proper usage context. However, it does not explicitly mention when not to use this tool or compare to alternatives like company_profile.

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-due-diligence-mcp'

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