Skip to main content
Glama

Look Up UK Postcode

govuk_lookup_postcode
Read-onlyIdempotent

Retrieve administrative geography for any UK postcode, including local authority, constituency, and region, to direct users to the correct local service.

Instructions

Look up a UK postcode to retrieve its local authority, region, constituency, and other administrative geography.

Useful for determining which council area, parliamentary constituency, or NHS region a postcode falls within. Commonly used to direct users to the correct local service on GOV.UK (e.g. council tax, planning, waste).

Uses the postcodes.io public API (no key required).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
postcodeYesUK postcode, e.g. 'SW1A 2AA' or 'NG1 1AA'. Spaces optional.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
postcodeNoCanonicalised postcode as returned by postcodes.io.
latitudeNoLatitude in decimal degrees (WGS84).
longitudeNoLongitude in decimal degrees (WGS84).
countryNoCountry, e.g. 'England', 'Scotland', 'Wales', 'Northern Ireland'.
regionNoONS region, e.g. 'East Midlands'.
parliamentary_constituencyNoParliamentary constituency (pre-2025 boundary).
parliamentary_constituency_2025NoParliamentary constituency under the 2025 boundaries.
local_authorityNoLocal authority / council covering the postcode.
admin_countyNoAdministrative county, where applicable (null in unitary areas).
nhs_integrated_care_boardNoNHS Integrated Care Board, where available.
codesNoGSS codes for all administrative geographies covering this postcode.

Implementation Reference

  • The MCP tool handler for 'govuk_lookup_postcode'. Registered via @mcp.tool decorator, it takes a postcode string, calls the postcodes.io API, and returns a GovukPostcode model with administrative geography data (local authority, region, constituency, etc.).
    @mcp.tool(
        name="govuk_lookup_postcode",
        annotations={
            "title": "Look Up UK Postcode",
            "readOnlyHint": True,
            "destructiveHint": False,
            "idempotentHint": True,
            "openWorldHint": True,
        },
    )
    @_timed_tool
    async def govuk_lookup_postcode(
        postcode: Annotated[str, Field(description="UK postcode, e.g. 'SW1A 2AA' or 'NG1 1AA'. Spaces optional.", min_length=5, max_length=8)],
        ctx: Context,
    ) -> GovukPostcode:
        """Look up a UK postcode to retrieve its local authority, region, constituency,
        and other administrative geography.
    
        Useful for determining which council area, parliamentary constituency, or
        NHS region a postcode falls within. Commonly used to direct users to the
        correct local service on GOV.UK (e.g. council tax, planning, waste).
    
        Uses the postcodes.io public API (no key required).
        """
        client = _client(ctx)
        postcode = postcode.replace(" ", "").upper()
        url = f"{LOCATIONS_BASE}/postcodes/{postcode}"
    
        resp = await client.get(url)
        resp.raise_for_status()
        data = resp.json().get("result", {}) or {}
    
        codes = data.get("codes", {}) or {}
    
        return GovukPostcode(
            postcode=data.get("postcode"),
            latitude=data.get("latitude"),
            longitude=data.get("longitude"),
            country=data.get("country"),
            region=data.get("region"),
            parliamentary_constituency=data.get("parliamentary_constituency"),
            parliamentary_constituency_2025=data.get("parliamentary_constituency_2025"),
            local_authority=GovukLocalAuthority(
                name=data.get("admin_district"),
                code=codes.get("admin_district"),
            ),
            admin_county=data.get("admin_county"),
            nhs_integrated_care_board=data.get("integrated_care_board"),
            codes=codes,
        )
  • Output model schemas for the postcode lookup tool: GovukPostcode (the main response model) and GovukLocalAuthority (nested model for council/local authority). Both have rich Field descriptions for schema visibility.
    # ---------------------------------------------------------------------------
    # govuk_lookup_postcode — Shape C (single postcode)
    # ---------------------------------------------------------------------------
    
    
    class GovukLocalAuthority(BaseModel):
        """Local authority covering a postcode."""
    
        model_config = ConfigDict(str_strip_whitespace=True)
    
        name: Optional[str] = Field(None, description="Local authority / council name.")
        code: Optional[str] = Field(None, description="GSS code for the local authority.")
    
    
    class GovukPostcode(BaseModel):
        """UK postcode lookup result with administrative geography."""
    
        model_config = ConfigDict(str_strip_whitespace=True)
    
        postcode: Optional[str] = Field(None, description="Canonicalised postcode as returned by postcodes.io.")
        latitude: Optional[float] = Field(None, description="Latitude in decimal degrees (WGS84).")
        longitude: Optional[float] = Field(None, description="Longitude in decimal degrees (WGS84).")
        country: Optional[str] = Field(None, description="Country, e.g. 'England', 'Scotland', 'Wales', 'Northern Ireland'.")
        region: Optional[str] = Field(None, description="ONS region, e.g. 'East Midlands'.")
        parliamentary_constituency: Optional[str] = Field(
            None, description="Parliamentary constituency (pre-2025 boundary)."
        )
        parliamentary_constituency_2025: Optional[str] = Field(
            None, description="Parliamentary constituency under the 2025 boundaries."
        )
        local_authority: GovukLocalAuthority = Field(
            default_factory=GovukLocalAuthority,
            description="Local authority / council covering the postcode.",
        )
        admin_county: Optional[str] = Field(
            None, description="Administrative county, where applicable (null in unitary areas)."
        )
        nhs_integrated_care_board: Optional[str] = Field(
            None, description="NHS Integrated Care Board, where available."
        )
        codes: dict[str, Any] = Field(
            default_factory=dict,
            description="GSS codes for all administrative geographies covering this postcode.",
        )
  • The tool is registered via the @mcp.tool() decorator with the name 'govuk_lookup_postcode' on the FastMCP instance 'mcp'. The decorator serves as the registration mechanism.
    @mcp.tool(
        name="govuk_lookup_postcode",
        annotations={
            "title": "Look Up UK Postcode",
            "readOnlyHint": True,
            "destructiveHint": False,
            "idempotentHint": True,
            "openWorldHint": True,
        },
    )
    @_timed_tool
    async def govuk_lookup_postcode(
  • Helper function `_client(ctx)` used by the handler to retrieve the shared httpx.AsyncClient from the lifespan context.
    def _client(ctx: Context) -> httpx.AsyncClient:
        return ctx.lifespan_context["client"]
  • The `_timed_tool` decorator wraps the handler with Prometheus metrics (call counter and latency histogram).
    def _timed_tool(fn):
        tool_name = fn.__name__
    
        @functools.wraps(fn)
        async def wrapped(*args, **kwargs):
            t0 = time.perf_counter()
            try:
                result = await fn(*args, **kwargs)
                tool_calls_total.labels(tool_name, TRANSPORT, REGION, "ok").inc()
                return result
            except BaseException:
                tool_calls_total.labels(tool_name, TRANSPORT, REGION, "error").inc()
                raise
            finally:
                tool_duration_seconds.labels(tool_name, TRANSPORT, REGION).observe(
                    time.perf_counter() - t0
                )
    
        return wrapped
Behavior4/5

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

Annotations already provide readOnlyHint, idempotentHint, etc. Description adds valuable context: 'Uses the postcodes.io public API (no key required).' This discloses the external source and keyless access. No contradiction with annotations.

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 brief paragraphs, first immediately states purpose, second adds usage and API info. No filler, every sentence adds value.

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?

Tool has output schema for return values. Description covers purpose, usage scenarios, and API source. Complete for a simple lookup tool with good annotations and schema.

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?

Input schema has 100% coverage with description, maxLength, minLength. Description adds practical details: 'Spaces optional', example formats (e.g., 'SW1A 2AA' or 'NG1 1AA'), which aids agent in constructing valid inputs.

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?

Description clearly states the tool's purpose: 'Look up a UK postcode to retrieve its local authority, region, constituency, and other administrative geography.' The verb 'look up' and resource 'UK postcode' are specific. Siblings are all about content, organisations, sections, etc., making this tool distinct.

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?

Description explains when to use: 'determining which council area, etc.' and usage context: 'direct users to the correct local service.' It does not explicitly state when not to use, but context is clear. Mentions the API used, but no alternative tools suggested.

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/govuk-mcp'

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