Skip to main content
Glama

ng-postcode

CI crates.io PyPI MCP server npm npm MCP server

Developer tools for Nigeria's National Digital Alphanumeric Postcode System (NDAPS), the building-level postcode NIPOST launched in October 2026: libraries for Rust and Python (ng-postcode), JavaScript (ng-postcode-js on npm), and Java (ng-postcode-api), MCP servers for AI assistants on PyPI and npm, and a resolver that turns described addresses into postcodes.

A postcode has 11 characters in five segments, written EK-01-A03-FK-01: state, LGA, district, area and building unit. NIPOST's own example, FC 02 A09 DB 09, is the same shape with spaces.

Packages

Package

What it does

Install

Source

ng-postcode (Rust)

Parse, validate and format codes offline; client for the postcode.gov.ng API

cargo add ng-postcode

src/, docs

ng-postcode (Python)

The same behaviour, with sync and async clients

pip install ng-postcode

python/

ng-postcode-js

The same behaviour for JavaScript and TypeScript, with a fetch client

npm install ng-postcode-js

js/packages/ng-postcode-js

ng-postcode and ng-postcode-api (Java)

The same behaviour for Java 17 and later; the core has no dependencies

io.github.adeniyikayodee:ng-postcode-api

java/

ng-postcode-mcp

MCP server: validate, look up, autocomplete, find by location, resolve addresses

uvx ng-postcode-mcp

mcp/

ng-postcode-mcp (npm)

The same server for Node, without address resolution

npx ng-postcode-mcp

js/packages/ng-postcode-mcp

ng-postcode-polars

Polars expressions that parse, format and group codes by place, compiled from the Rust core

pip install ng-postcode-polars

native/polars

ng_postcode (PostgreSQL)

The postcode as native 8-byte types with index support, compiled from the Rust core

Package from Releases, then CREATE EXTENSION ng_postcode

native/pg

ng_postcode (DuckDB)

The same functions as a DuckDB extension, compiled from the Rust core

INSTALL ng_postcode FROM community (DuckDB 1.5.6)

ng-postcode-duckdb

ng-address-resolver

Resolve free-text addresses to postcodes, only as precisely as the evidence allows (pre-alpha)

pip install ng-address-resolver

agent/

Each package has its own README with full usage.

For databases, sql/ holds the postcode as a PostgreSQL domain and as DuckDB macros, in plain SQL that runs the same shared cases.

Two names are easy to get wrong. On npm the library is ng-postcode-js, because ng-postcode there is another project. ng-address-resolver is imported as ng_address.

For online stores, ng-postcode-woocommerce is a WooCommerce plugin built on the same rules and the same shared test cases: it checks and tidies the postcode at checkout, finds it from the customer's location, and makes shipping zones match on postcode prefixes.

Related MCP server: io.github.stucchi/italy-opendata

Quick start

Parsing and validation are offline and need no key.

Python

from ng_postcode import Postcode, parse

match parse("ek 01 a03 fk 01"):
    case Postcode() as code:
        print(code, code.compact)  # EK-01-A03-FK-01 EK01A03FK01
    case error:
        print(error)               # e.g. "invalid lga segment"

JavaScript and TypeScript

import { Postcode, parse } from "ng-postcode-js";

const code = parse("ek 01 a03 fk 01");
if (code instanceof Postcode) {
  console.log(String(code), code.compact); // EK-01-A03-FK-01 EK01A03FK01
} else {
  console.log(String(code));               // e.g. "invalid lga segment"
}

Java

import io.github.adeniyikayodee.ngpostcode.Postcode;

if (Postcode.parse("ek 01 a03 fk 01") instanceof Postcode code) {
    System.out.println(code + " " + code.compact()); // EK-01-A03-FK-01 EK01A03FK01
}

Rust

use ng_postcode::{Postcode, Segment};

let code: Postcode = "ek 01 a03 fk 01".parse()?;
assert_eq!(code.to_string(), "EK-01-A03-FK-01");
assert_eq!(code.prefix(Segment::Area), "EK-01-A03-FK");

AI assistants

The MCP server works with any MCP client. It runs over stdio as uvx ng-postcode-mcp, with the API key in the environment. Most clients take this entry in their MCP settings:

{
  "mcpServers": {
    "ng-postcode": {
      "command": "uvx",
      "args": ["ng-postcode-mcp"],
      "env": { "NG_POSTCODE_API_KEY": "nipost_live_..." }
    }
  }
}

To run it with Node, use "command": "npx" and "args": ["-y", "ng-postcode-mcp"]; that edition has every tool except address resolution. Per-client steps for Cursor, VS Code, Codex, Claude and others are in the MCP server README. Validation works without a key; the other tools need one from the NIPOST developer dashboard. The server is listed in the MCP Registry as io.github.Adeniyikayodee/ng-postcode.

PostgreSQL

Load the plain SQL file with psql -f sql/postgres.sql. It needs no extension, so it runs on managed services.

CREATE TABLE customers (
    id       bigint PRIMARY KEY,
    postcode ng_postcode            -- refuses anything that is not a compact code
);

INSERT INTO customers VALUES (1, ng_postcode_parse('ek 01 a03 fk 01'));

SELECT ng_postcode_format(ng_postcode_truncate(postcode, 'district')) AS district, count(*)
FROM customers
GROUP BY 1;
-- EK-01-A03 | 1

On a server where you can install an extension, the native extension has the same functions and stores a code in 8 bytes. Unpack the package for your PostgreSQL version from Releases, then run CREATE EXTENSION ng_postcode.

DuckDB

Load the same functions with duckdb my.db < sql/duckdb.sql.

SELECT ng_postcode_parse('ek 01 a03 fk 01');
-- EK01A03FK01

The compiled extension, ng-postcode-duckdb, has the same functions: on DuckDB 1.5.6, run INSTALL ng_postcode FROM community, then LOAD ng_postcode.

Polars

import polars as pl
import ng_postcode_polars  # pip install ng-postcode-polars

frame = pl.DataFrame({"postcode": ["ek 01 a03 fk 01", "EK-01-A03-ZZ-14", "100001"]})

print(frame.with_columns(code=pl.col("postcode").ng.parse(), ok=pl.col("postcode").ng.is_valid()))
# ek 01 a03 fk 01  EK01A03FK01  true
# EK-01-A03-ZZ-14  EK01A03ZZ14  true
# 100001           null         false

A CSV file

pip install ng-postcode
ng-postcode --column postcode customers.csv --out checked.csv

The output keeps every row and adds the compact code, a status of valid, suggested, invalid, or empty, a suggested correction, and the reason for each invalid row.

For coding agents

Language models trained before October 2026 expect a six-digit Nigerian postcode. To point a coding agent at the current format, paste this into your project's AGENTS.md, CLAUDE.md, or editor rules:

Nigerian postcodes: since 1 October 2026 a building's postcode is NIPOST's 11-character
digital postcode (NDAPS), such as EK-01-A03-FK-01, and a six-digit pattern does not match it.
Validate and format it with ng-postcode (pip, cargo), ng-postcode-js (npm), or
io.github.adeniyikayodee:ng-postcode (Maven). On npm, ng-postcode is a different project.
Reference: https://adeniyikayodee.github.io/ng-postcode/llms.txt

The format

Segment

Example

Shape

State

EK

2 letters

LGA

01

2 digits, 01 to 99

District

A03

3 letters or digits

Area

FK

2 letters

Building unit

01

2 digits, 01 to 99

Form

Example

Use it to

Canonical

EK-01-A03-FK-01

Write a code and pass it between systems

Spaced

EK 01 A03 FK 01

Show a code to people

Compact

EK01A03FK01

Store and compare codes

Input may be hyphenated, spaced or compact, in either case. The compact form matches ^[A-Z]{2}(0[1-9]|[1-9][0-9])[A-Z0-9]{3}[A-Z]{2}(0[1-9]|[1-9][0-9])$. A well-formed code is not necessarily assigned to a building; only the NIPOST API can confirm that.

Passing a postcode between systems

docs/schemas/postcode-reference.schema.json defines a small JSON object for handing a location from one system or AI agent to another: the code, its level (state to building), and optionally a confidence and whether NIPOST confirmed it. A resolved or partial resolve_address answer already fits it.

For a whole address, docs/address-record.md wraps the reference in an address record and maps it onto ISO 20022, FHIR, schema.org, vCard, and UPU's address layout for Nigeria.

Design

  • One behaviour, four languages: Rust, Python, JavaScript and Java all run the cases in spec/, so they cannot drift apart. The Node MCP server registers its tools from spec/mcp.json, which the Python server generates.

  • Offline first: parsing and validation never touch the network. API access is a separate, optional layer.

  • Errors are values: expected failures, such as a malformed code or a rejected API key, come back as return values.

  • Careful with money and guesses: the MCP server caps lookups at the free level unless told otherwise, and never corrects a mistyped code into a paid call. The resolver gives an area or district code when that is all the evidence supports.

Status

  • The NIPOST API needs a key for every endpoint, from the developer dashboard. Offline validation needs nothing.

  • The API layer is tested against responses captured from the live API with a level 1 key, kept in spec/responses.json. Run scripts/live_check.py with your own key to repeat the comparison. Lookup levels 2 and up need a higher-access key and are tested only against NIPOST's documented examples.

  • The resolver and the resolve_address tool are pre-release. They work against the live API, but their accuracy on real addresses is unmeasured. Described addresses need a geocoder you run or pay for; text alone rarely identifies a building, so ask users for a location pin when the exact building matters.

  • Notes for integrators on how the live API behaves, each with the date it was observed, are in docs/api-differences.md.

Development

See CONTRIBUTING.md for the shared spec, how to add a language, and the commit standards.

cargo test --all-features                              # Rust
cd python && uv run --group dev pytest                 # Python library
cd mcp && uv run --group dev pytest                    # MCP server
cd agent && uv run --group dev pytest                  # resolver
cd js && npm ci && npm test                            # JavaScript library and Node MCP server
cd java && ./mvnw verify                               # Java libraries

CI runs formatting, linting, type checks and tests for every package. Releases publish from tags (v* to crates.io; py-v*, mcp-v* and agent-v* to PyPI and the MCP Registry; js-v* and js-mcp-v* to npm) through trusted publishing, so no tokens are stored. Maven Central has no trusted publishing, so java-v* uses a token and a signing key held as environment secrets, and the upload is published by hand. An mcp-v* release also creates a GitHub release, which Glama rebuilds its listing from, and republishes the Smithery bundle with a key held the same way.

License

MIT

Available Tools

5 tools
autocomplete_postcodeAutocomplete a Nigerian postcodeA
Read-onlyIdempotent

Suggest the next segment of a partly typed Nigerian postcode. Free.

Use it while a user is still typing or only knows the start of their code. For a complete code, use validate_postcode, then lookup_postcode.

ParametersJSON Schema
NameRequiredDescriptionDefault
partialYesThe start of a code, e.g. 'EK 01 A'.

Output Schema

ParametersJSON Schema
NameRequiredDescription
segmentYesThe segment being completed.
suggestionsYes

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already declare readOnlyHint, idempotentHint and openWorldHint, so the safety profile is covered. The description adds genuinely useful non-structured context — that the call is free — plus the correct call sequence for the complete-code path. It does not describe result-set size or partial-match behavior for non-existent prefixes, but that is largely covered by the output schema.

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?

Three short sentences, zero filler, and the identifying purpose is front-loaded before the routing advice. Every sentence earns its place.

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?

With one fully documented parameter, annotations covering safety, and an output schema handling return values, nothing an agent needs in order to select or invoke this tool is missing. Sibling routing is included for the ambiguous complete-code case.

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

Parameters3/5

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

Schema coverage is 100% and the schema itself supplies the example ('EK 01 A') and length bounds, so the description has little left to add. It conveys the 'partly typed / prefix' semantic through the usage sentence, which is a small value-add over the schema.

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?

States a specific verb (suggest/autocomplete), a specific resource (Nigerian postcode), and the exact scope (next segment of a partly typed code). It is immediately distinguishable from validate_postcode and lookup_postcode, which handle complete codes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names the triggering condition (user still typing, or only the start of the code is known) and explicitly routes the complete-code case to validate_postcode then lookup_postcode. Both when-to-use and the alternative path are stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_postcode_at_locationFind the postcode at a locationA
Read-onlyIdempotent

Return the postcode of the nearest building to a coordinate in Nigeria.

Names and the house address are withheld unless NG_POSTCODE_MAX_LEVEL is 2 or more.

ParametersJSON Schema
NameRequiredDescriptionDefault
latitudeYesLatitude in decimal degrees, e.g. 7.6211.
longitudeYesLongitude in decimal degrees, e.g. 5.2214.
max_distance_mNoSearch radius in metres. Defaults to 25.

Output Schema

ParametersJSON Schema
NameRequiredDescription
areaYesEnclosing area code, e.g. EK-01-A03-FK.
noteNoWhy the answer may be incomplete, such as a test key.
unitYes
depthNoHow deep the match goes, e.g. unit.
foundYesFalse when no building is within the radius.
stateYes
messageYes
districtYes
radius_mYesThe radius the API actually applied.

TDQS

A3.8/5.0
Behavior4/5

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

Annotations already declare readOnlyHint, idempotentHint and openWorldHint, so the safety profile is covered. The description adds genuinely new behavioral context beyond the annotations: output is config-dependent, with names and house addresses withheld unless NG_POSTCODE_MAX_LEVEL >= 2, which tells the agent to expect redacted results in the default configuration.

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 sentences, zero waste, with the core action and geographic scope front-loaded and the conditional output-redaction caveat following. Every sentence carries information the agent needs.

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?

An output schema exists, so return values need no explanation, and the description covers the geographic scope plus the redaction behavior. It stops short of noting what happens when no building falls within max_distance_m, which would be useful for a proximity search, but overall it is sufficient to call the tool correctly.

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

Parameters3/5

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

Schema description coverage is 100%, so latitude, longitude, and max_distance_m (with its 25 m default and 250 m cap) are already fully documented in the schema. The description adds no syntax or format detail beyond that, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Return the postcode of the nearest building to a coordinate') and constrains it geographically to Nigeria. It does not name or contrast with any sibling (lookup_postcode, resolve_address, etc.), so the agent must infer the distinction from the coordinate-based framing, but the purpose itself is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by the input shape: it is the tool for converting a lat/long to a postcode. There is no explicit when-to-use versus alternatives such as lookup_postcode or resolve_address, and no stated exclusions (e.g., what to do for coordinates outside Nigeria).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

lookup_postcodeLook up a Nigerian postcodeB
Read-onlyIdempotent

Confirm a postcode is assigned to a building. Free.

Address and building details are a paid lookup, listed as its own tool only when the server's NG_POSTCODE_MAX_LEVEL is 2 or more.

ParametersJSON Schema
NameRequiredDescriptionDefault
levelNoAlways 1. Address and building details are a separate paid tool.
postcodeYesCode in any style, e.g. EK-01-A03-FK-01.

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteNoWhy the answer may be incomplete, such as a test key.
validYesWhether the code is assigned to a building.
statusNovalid, not_found, or invalid for a malformed code.
postcodeYes
verifiedNo
point_geometryNoLevel 5, unstructured.
building_use_statusYesLevel 3 and up, e.g. residential.
other_building_infoNoLevel 4 and up, unstructured.
recent_house_addressYesLevel 2 and up. Personal data.
administrative_addressYesLevel 2 and up.

TDQS

B3.4/5.0
Behavior3/5

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

Annotations already declare readOnlyHint, idempotentHint, and openWorldHint, so the safety profile is covered. The description adds genuine non-annotation behavior: that this check is free and that a companion paid tool only appears when a server setting is 2+. It says nothing about how the check resolves, error behavior, or result semantics.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the core purpose in one short sentence, then adds pricing and server-gating context. It is compact and every sentence carries information, though the second paragraph is slightly awkward and reads as an aside.

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

Completeness3/5

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

An output schema exists so return values need not be explained, and annotations cover the safety traits. What is missing is routing clarity inside a five-tool family of postcode helpers, plus any indication of what a negative confirmation means for the caller.

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

Parameters3/5

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

Schema description coverage is 100% and both parameters carry their own descriptions (level 'Always 1', postcode accepting any style). The description adds no syntax, format, or normalization guidance beyond that, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb and resource: 'Confirm a postcode is assigned to a building.' That is materially more precise than the title. It does not, however, distinguish itself from the closest sibling, validate_postcode, which an agent will reasonably confuse with this confirmation check.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It notes the lookup is free and that address/building details live in a separate paid tool gated by NG_POSTCODE_MAX_LEVEL >= 2, which is conditional context for a sibling. It never states when to choose this over validate_postcode, autocomplete_postcode, or resolve_address, so selection is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

resolve_addressResolve a Nigerian address to a postcodeA
Read-onlyIdempotent

Turn a described Nigerian address into a postcode, only as precisely as the evidence allows.

A full building code comes only from a postcode written in the address, a location pin, or a landmark that is the address itself. Otherwise the result is an area or district prefix with a question for the user. Read the address yourself and fill landmarks and geocode_queries; the address text is sent to the configured geocoder.

ParametersJSON Schema
NameRequiredDescriptionDefault
addressYesThe address exactly as the user gave it.
latitudeNoLatitude of a location pin the user shared.
landmarksNoLandmarks named in the address, each with how the address relates to it. Use 'at' only when the address is the landmark itself.
longitudeNoLongitude of that pin. Give it with latitude.
geocode_queriesNoUp to three map search strings you derive from the address: named places and streets with the town and state, most specific first, without directional words like 'back of'.

Output Schema

ParametersJSON Schema
NameRequiredDescription
codeYesA full postcode at building level, else a prefix.
levelYes
methodYes
statusYes
evidenceYes
questionYesWhat to ask the user to get a better answer.
confidenceYes

TDQS

A3.9/5.0
Behavior4/5

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

Annotations already declare readOnlyHint, openWorldHint and idempotentHint, so the safety profile is covered. The description adds genuinely non-obvious behavior: results may be deliberately imprecise with a `question` returned for the user, and the address text is transmitted to the configured geocoder. That external-disclosure detail is valuable beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action, followed by the precision rule that governs the output. Two tight paragraphs with no filler, though the second paragraph mixes workflow instruction with external-disclosure context slightly awkwardly.

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?

An output schema exists, so return shape needn't be explained, and the description still flags the `question` field. Combined with the precision rules and geocoder disclosure, an agent has enough to call this correctly; only sibling routing remains unaddressed.

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

Parameters3/5

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

Schema description coverage is 100%, so the baseline is 3; every parameter including the Landmark relation enum is already documented in the schema. The description's 'read the address yourself and fill landmarks and geocode_queries' reiterates the workflow but adds no syntax or format detail beyond what the schema provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb+resource ('Turn a described Nigerian address into a postcode') and adds the key behavioral nuance of precision tiers (full building code vs area/district prefix). It does not, however, explicitly differentiate itself from siblings like lookup_postcode or find_postcode_at_location, so an agent must infer the distinction.

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 gives clear operational guidance: read the address yourself, populate `landmarks` and `geocode_queries`, and expect only a prefix when no pin/postcode/at-landmark evidence exists. It stops short of naming alternatives or stating when-not to use this tool versus the sibling postcode tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

validate_postcodeValidate a Nigerian postcodeA
Read-onlyIdempotent

Check a Nigerian postcode's structure offline; return its canonical forms and segments.

Free and instant. Does not confirm the code is assigned to a building; use lookup_postcode for that.

ParametersJSON Schema
NameRequiredDescriptionDefault
postcodeYesCode in any style, e.g. 'ek 01 a03 fk 01'.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorYesWhy the code is malformed.
validYesWhether the code is well formed. It may still be unassigned.
spacedYesForm shown to people, e.g. EK 01 A03 FK 01.
compactYesCompact form for storage, e.g. EK01A03FK01.
postcodeYesCanonical form, e.g. EK-01-A03-FK-01.
segmentsYes
suggestionYesA well-formed code if look-alike characters (O/0, I/1, S/5, B/8) were the only problem. Confirm it with the user before using it.

TDQS

A4.5/5.0
Behavior4/5

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

Annotations already declare readOnlyHint, idempotentHint and openWorldHint=false; the description reinforces this by advertising offline, instant operation and, more importantly, discloses the key limitation that structural validity does not imply an assigned building. It does not describe behavior on malformed input (e.g. error vs. empty result), which is a minor remaining gap.

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 short sentences: the capability first, then the important caveat and the routing alternative. No filler, nothing that could be trimmed without losing information.

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?

With an output schema present, the description needn't explain return values, and it covers the scope of validity (structural, not assignment) and the routing decision. For a single-parameter read-only validator, nothing an agent needs to call it correctly is missing.

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

Parameters3/5

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

There is a single parameter whose schema description coverage is 100%, including the 'any style' example format, so the schema already carries the semantics. The description adds nothing about accepted input forms beyond what the schema provides, so the baseline 3 applies.

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?

States a specific verb and resource ('Check a Nigerian postcode's structure offline') plus what it returns ('canonical forms and segments'). It explicitly contrasts itself with the sibling lookup_postcode, so an agent can distinguish structural validation from assignment lookup.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Names the alternative tool explicitly and the condition that selects it ('Does not confirm the code is assigned to a building; use lookup_postcode for that'). It also signals the cost/availability profile ('Free and instant'), which helps an agent prefer this validator before a heavier lookup.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev0.4.6
    • Changedlookup_postcode1 field changed
      • changedInput schema / properties / level / description
        Previous value: -"Always 1. Higher levels are lookup_postcode_details."New value: +"Always 1. Address and building details are a separate paid tool."
  2. 5 tool updatesv0.4.5
    • Changedautocomplete_postcode1 field changed
      • addedInput schema / properties / partial / maxLength
        Added value: +32
    • Changedfind_postcode_at_location1 field changed
      • addedOutput schema / properties / note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Why the answer may be incomplete, such as a test key.",
        +  "title": "Note"
        +}
    • Changedlookup_postcode4 fields changed
      • changedInput schema / properties / level / description
        Previous value: -"1: validity only (free). 2: adds the administrative and recent house address. 3: adds building use. Levels 2+ consume NIPOST credits."New value: +"Always 1. Higher levels are lookup_postcode_details."
      • changedInput schema / properties / level / maximum
        Previous value: -5New value: +1
      • addedInput schema / properties / postcode / maxLength
        Added value: +64
      • addedOutput schema / properties / note
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Why the answer may be incomplete, such as a test key.",
        +  "title": "Note"
        +}
    • Changedresolve_address5 fields changed
      • addedInput schema / $defs / Landmark / description
        Added value: +"A landmark as a caller may send it: the name has a limit."
      • addedInput schema / $defs / Landmark / properties / name / maxLength
        Added value: +120
      • addedInput schema / properties / address / maxLength
        Added value: +500
      • changedInput schema / properties / geocode_queries / anyOf
        Previous value: -[
        -  {
        -    "items": {
        -      "type": "string"
        -    },
        -    "maxItems": 3,
        -    "type": "array"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "items": {
        +      "maxLength": 200,
        +      "type": "string"
        +    },
        +    "maxItems": 3,
        +    "type": "array"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedInput schema / properties / landmarks / anyOf
        Previous value: -[
        -  {
        -    "items": {
        -      "$ref": "#/$defs/Landmark"
        -    },
        -    "type": "array"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "items": {
        +      "$ref": "#/$defs/Landmark"
        +    },
        +    "maxItems": 10,
        +    "type": "array"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
    • Changedvalidate_postcode1 field changed
      • addedInput schema / properties / postcode / maxLength
        Added value: +64
  3. 2 tool updatesv0.4.1
    • Changedfind_postcode_at_location2 fields changed
      • addedInput schema / properties / latitude / description
        Added value: +"Latitude in decimal degrees, e.g. 7.6211."
      • addedInput schema / properties / longitude / description
        Added value: +"Longitude in decimal degrees, e.g. 5.2214."
    • Changedresolve_address2 fields changed
      • changedInput schema / properties / latitude / description
        Previous value: -"A location pin the user shared."New value: +"Latitude of a location pin the user shared."
      • addedInput schema / properties / longitude / description
        Added value: +"Longitude of that pin. Give it with latitude."
  4. 5 tool updatesv0.2.4
    • First observedautocomplete_postcode
    • First observedfind_postcode_at_location
    • First observedlookup_postcode
    • First observedresolve_address
    • First observedvalidate_postcode

TDQS

A3.9/5.0

Scored across 5 tools

Disambiguation4/5

Each tool targets a distinct step (validate structure, confirm assignment, autocomplete, reverse-geocode, forward-resolve), and descriptions explicitly cross-reference which to use when. Some boundary blur remains because validate_postcode, lookup_postcode, and resolve_address all return canonical postcode info, but the guidance resolves most confusion.

Naming Consistency4/5

All names are snake_case verb_noun (validate_postcode, lookup_postcode, autocomplete_postcode, find_postcode_at_location, resolve_address). find_postcode_at_location breaks the tight noun-only pattern by appending a locative clause, but the convention is still clearly readable and consistent.

Tool Count5/5

Five tools is well-scoped for a focused Nigerian postcode service, with one tool per core operation and no redundant or filler entries. Each earns its place.

Completeness4/5

The surface covers the full lifecycle: structure validation, assignment lookup, autocomplete, reverse geocoding, and forward address resolution, with graceful degradation for paid-level gating. Minor gaps exist (e.g. no batch validation), but they are easy to work around.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers