ng-postcode
This MCP server lets AI assistants work with Nigeria's NDAPS postcodes by validating, looking up, autocompleting, locating, and resolving addresses.
Validate a Nigerian postcode offline and return its canonical, spaced, and compact forms plus segment breakdown and typo suggestions.
Look up a postcode to confirm it is assigned to a building at the free level; higher-level address/building details require paid access.
Autocomplete a partly typed postcode by suggesting the next segment.
Find the nearest building postcode from latitude/longitude, with an optional search radius.
Resolve a free-text Nigerian address to a postcode only as precisely as evidence allows, using location pins, landmarks, and geocode queries; may return a partial result with a question.
Note: validation is free and offline; most other tools need a NIPOST API key, and the resolver is pre-alpha with unmeasured real-address accuracy.
ng-postcode
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 |
| Parse, validate and format codes offline; client for the postcode.gov.ng API |
| |
| The same behaviour, with sync and async clients |
| |
| The same behaviour for JavaScript and TypeScript, with a |
| |
| The same behaviour for Java 17 and later; the core has no dependencies |
| |
| MCP server: validate, look up, autocomplete, find by location, resolve addresses |
| |
| The same server for Node, without address resolution |
| |
| Polars expressions that parse, format and group codes by place, compiled from the Rust core |
| |
| The postcode as native 8-byte types with index support, compiled from the Rust core | Package from Releases, then | |
| The same functions as a DuckDB extension, compiled from the Rust core |
| |
| Resolve free-text addresses to postcodes, only as precisely as the evidence allows (pre-alpha) |
|
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 | 1On 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');
-- EK01A03FK01The 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 falseA CSV file
pip install ng-postcode
ng-postcode --column postcode customers.csv --out checked.csvThe 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.txtThe format
Segment | Example | Shape |
State |
| 2 letters |
LGA |
| 2 digits, 01 to 99 |
District |
| 3 letters or digits |
Area |
| 2 letters |
Building unit |
| 2 digits, 01 to 99 |
Form | Example | Use it to |
Canonical |
| Write a code and pass it between systems |
Spaced |
| Show a code to people |
Compact |
| 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 fromspec/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. Runscripts/live_check.pywith 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_addresstool 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 librariesCI 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 toolsautocomplete_postcodeAutocomplete a Nigerian postcodeARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| partial | Yes | The start of a code, e.g. 'EK 01 A'. |
Output Schema
| Name | Required | Description |
|---|---|---|
| segment | Yes | The segment being completed. |
| suggestions | Yes |
TDQS
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.
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.
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.
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.
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.
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 locationARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| latitude | Yes | Latitude in decimal degrees, e.g. 7.6211. | |
| longitude | Yes | Longitude in decimal degrees, e.g. 5.2214. | |
| max_distance_m | No | Search radius in metres. Defaults to 25. |
Output Schema
| Name | Required | Description |
|---|---|---|
| area | Yes | Enclosing area code, e.g. EK-01-A03-FK. |
| note | No | Why the answer may be incomplete, such as a test key. |
| unit | Yes | |
| depth | No | How deep the match goes, e.g. unit. |
| found | Yes | False when no building is within the radius. |
| state | Yes | |
| message | Yes | |
| district | Yes | |
| radius_m | Yes | The radius the API actually applied. |
TDQS
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.
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.
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.
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.
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.
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 postcodeBRead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| level | No | Always 1. Address and building details are a separate paid tool. | |
| postcode | Yes | Code in any style, e.g. EK-01-A03-FK-01. |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | Why the answer may be incomplete, such as a test key. |
| valid | Yes | Whether the code is assigned to a building. |
| status | No | valid, not_found, or invalid for a malformed code. |
| postcode | Yes | |
| verified | No | |
| point_geometry | No | Level 5, unstructured. |
| building_use_status | Yes | Level 3 and up, e.g. residential. |
| other_building_info | No | Level 4 and up, unstructured. |
| recent_house_address | Yes | Level 2 and up. Personal data. |
| administrative_address | Yes | Level 2 and up. |
TDQS
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.
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.
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.
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.
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.
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 postcodeARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| address | Yes | The address exactly as the user gave it. | |
| latitude | No | Latitude of a location pin the user shared. | |
| landmarks | No | Landmarks named in the address, each with how the address relates to it. Use 'at' only when the address is the landmark itself. | |
| longitude | No | Longitude of that pin. Give it with latitude. | |
| geocode_queries | No | Up 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
| Name | Required | Description |
|---|---|---|
| code | Yes | A full postcode at building level, else a prefix. |
| level | Yes | |
| method | Yes | |
| status | Yes | |
| evidence | Yes | |
| question | Yes | What to ask the user to get a better answer. |
| confidence | Yes |
TDQS
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.
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.
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.
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.
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.
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 postcodeARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| postcode | Yes | Code in any style, e.g. 'ek 01 a03 fk 01'. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | Yes | Why the code is malformed. |
| valid | Yes | Whether the code is well formed. It may still be unassigned. |
| spaced | Yes | Form shown to people, e.g. EK 01 A03 FK 01. |
| compact | Yes | Compact form for storage, e.g. EK01A03FK01. |
| postcode | Yes | Canonical form, e.g. EK-01-A03-FK-01. |
| segments | Yes | |
| suggestion | Yes | A 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
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.
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.
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.
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.
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.
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 tool update
v0.4.6- Changed
lookup_postcode1 field changed- changed
Input schema / properties / level / descriptionPrevious value: -"Always 1. Higher levels are lookup_postcode_details."New value: +"Always 1. Address and building details are a separate paid tool."
5 tool updates
v0.4.5- Changed
autocomplete_postcode1 field changed- added
Input schema / properties / partial / maxLengthAdded value: +32
- Changed
find_postcode_at_location1 field changed- added
Output schema / properties / noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Why the answer may be incomplete, such as a test key.", + "title": "Note" +}
- Changed
lookup_postcode4 fields changed- changed
Input schema / properties / level / descriptionPrevious 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." - changed
Input schema / properties / level / maximumPrevious value: -5New value: +1 - added
Input schema / properties / postcode / maxLengthAdded value: +64 - added
Output schema / properties / noteAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Why the answer may be incomplete, such as a test key.", + "title": "Note" +}
- Changed
resolve_address5 fields changed- added
Input schema / $defs / Landmark / descriptionAdded value: +"A landmark as a caller may send it: the name has a limit." - added
Input schema / $defs / Landmark / properties / name / maxLengthAdded value: +120 - added
Input schema / properties / address / maxLengthAdded value: +500 - changed
Input schema / properties / geocode_queries / anyOfPrevious value: -[ - { - "items": { - "type": "string" - }, - "maxItems": 3, - "type": "array" - }, - { - "type": "null" - } -]New value: +[ + { + "items": { + "maxLength": 200, + "type": "string" + }, + "maxItems": 3, + "type": "array" + }, + { + "type": "null" + } +] - changed
Input schema / properties / landmarks / anyOfPrevious value: -[ - { - "items": { - "$ref": "#/$defs/Landmark" - }, - "type": "array" - }, - { - "type": "null" - } -]New value: +[ + { + "items": { + "$ref": "#/$defs/Landmark" + }, + "maxItems": 10, + "type": "array" + }, + { + "type": "null" + } +]
- Changed
validate_postcode1 field changed- added
Input schema / properties / postcode / maxLengthAdded value: +64
2 tool updates
v0.4.1- Changed
find_postcode_at_location2 fields changed- added
Input schema / properties / latitude / descriptionAdded value: +"Latitude in decimal degrees, e.g. 7.6211." - added
Input schema / properties / longitude / descriptionAdded value: +"Longitude in decimal degrees, e.g. 5.2214."
- Changed
resolve_address2 fields changed- changed
Input schema / properties / latitude / descriptionPrevious value: -"A location pin the user shared."New value: +"Latitude of a location pin the user shared." - added
Input schema / properties / longitude / descriptionAdded value: +"Longitude of that pin. Give it with latitude."
5 tool updates
v0.2.4- First observed
autocomplete_postcode - First observed
find_postcode_at_location - First observed
lookup_postcode - First observed
resolve_address - First observed
validate_postcode
TDQS
Scored across 5 tools
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.
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.
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.
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
Related MCP Connectors
Postcodes MCP — wraps postcodes.io UK postcode API (free, no auth)
MCP server for EasyPost — rate shipments, buy & refund labels, track packages, verify addresses.
Base Adresse Nationale (BAN) MCP — France's official keyless geocoding API…
UK postcode lookup, nearest postcodes, autocomplete and distance (OS open data)
Related MCP Servers
- AlicenseNot gradedqualityFmaintenanceMCP server for Australian address search and validation powered by Addressr.6 npmApache 2.0

io.github.stucchi/italyofficial
AlicenseNot gradedqualityDmaintenanceMCP server exposing Italian open data (municipalities, provinces, regions, postal codes, coordinates, geographic data) through simple, developer-friendly tools.1MIT- AlicenseNot gradedqualityBmaintenanceMCP server for geocoding, reverse geocoding, place/POI search, and distance calculation using OpenStreetMap Nominatim, with no API key required.21 npm1MIT
- AlicenseAqualityCmaintenanceMCP server that resolves postal/ZIP codes to place names, states, and coordinates using Zippopotam.us. Free, no API key required.1MIT