Look Up UK Postcode
govuk_lookup_postcodeRetrieve 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
| Name | Required | Description | Default |
|---|---|---|---|
| postcode | Yes | UK postcode, e.g. 'SW1A 2AA' or 'NG1 1AA'. Spaces optional. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| postcode | No | Canonicalised postcode as returned by postcodes.io. | |
| latitude | No | Latitude in decimal degrees (WGS84). | |
| longitude | No | Longitude in decimal degrees (WGS84). | |
| country | No | Country, e.g. 'England', 'Scotland', 'Wales', 'Northern Ireland'. | |
| region | No | ONS region, e.g. 'East Midlands'. | |
| parliamentary_constituency | No | Parliamentary constituency (pre-2025 boundary). | |
| parliamentary_constituency_2025 | No | Parliamentary constituency under the 2025 boundaries. | |
| local_authority | No | Local authority / council covering the postcode. | |
| admin_county | No | Administrative county, where applicable (null in unitary areas). | |
| nhs_integrated_care_board | No | NHS Integrated Care Board, where available. | |
| codes | No | GSS codes for all administrative geographies covering this postcode. |
Implementation Reference
- govuk_mcp/server.py:380-429 (handler)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, ) - govuk_mcp/models.py:238-281 (schema)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.", ) - govuk_mcp/server.py:380-391 (registration)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( - govuk_mcp/server.py:153-154 (helper)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"] - govuk_mcp/server.py:64-82 (helper)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