ng-postcode-mcp 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ng_postcode_mcp-0.2.0 → ng_postcode_mcp-0.2.2}/PKG-INFO +21 -10
- {ng_postcode_mcp-0.2.0 → ng_postcode_mcp-0.2.2}/README.md +18 -7
- {ng_postcode_mcp-0.2.0 → ng_postcode_mcp-0.2.2}/pyproject.toml +2 -2
- {ng_postcode_mcp-0.2.0 → ng_postcode_mcp-0.2.2}/server.json +2 -2
- {ng_postcode_mcp-0.2.0 → ng_postcode_mcp-0.2.2}/src/ng_postcode_mcp/server.py +16 -3
- {ng_postcode_mcp-0.2.0 → ng_postcode_mcp-0.2.2}/tests/test_tools.py +17 -0
- {ng_postcode_mcp-0.2.0 → ng_postcode_mcp-0.2.2}/.gitignore +0 -0
- {ng_postcode_mcp-0.2.0 → ng_postcode_mcp-0.2.2}/LICENSE +0 -0
- {ng_postcode_mcp-0.2.0 → ng_postcode_mcp-0.2.2}/src/ng_postcode_mcp/__init__.py +0 -0
- {ng_postcode_mcp-0.2.0 → ng_postcode_mcp-0.2.2}/src/ng_postcode_mcp/__main__.py +0 -0
- {ng_postcode_mcp-0.2.0 → ng_postcode_mcp-0.2.2}/src/ng_postcode_mcp/py.typed +0 -0
- {ng_postcode_mcp-0.2.0 → ng_postcode_mcp-0.2.2}/tests/test_metadata.py +0 -0
- {ng_postcode_mcp-0.2.0 → ng_postcode_mcp-0.2.2}/tests/test_resolve_tool.py +0 -0
- {ng_postcode_mcp-0.2.0 → ng_postcode_mcp-0.2.2}/tests/test_stdio.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ng-postcode-mcp
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: MCP server for Nigeria's NIPOST digital postcode (NDAPS): validate, look up, reverse-geocode and resolve addresses to postcodes.
|
|
5
5
|
Project-URL: Repository, https://github.com/Adeniyikayodee/ng-postcode
|
|
6
6
|
Project-URL: Issues, https://github.com/Adeniyikayodee/ng-postcode/issues
|
|
@@ -22,8 +22,8 @@ Classifier: Topic :: Scientific/Engineering :: GIS
|
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
24
|
Requires-Dist: mcp<3,>=2.3
|
|
25
|
-
Requires-Dist: ng-address-resolver<0.2,>=0.1
|
|
26
|
-
Requires-Dist: ng-postcode[client]<0.
|
|
25
|
+
Requires-Dist: ng-address-resolver<0.2,>=0.1.1
|
|
26
|
+
Requires-Dist: ng-postcode[client]<0.3,>=0.2
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
|
|
29
29
|
# ng-postcode-mcp
|
|
@@ -58,19 +58,21 @@ The assistant reads the address and passes its landmarks and map searches to the
|
|
|
58
58
|
| A street only | District code, low confidence |
|
|
59
59
|
| A town only, or nothing found | No code, plus a question |
|
|
60
60
|
|
|
61
|
-
Text alone rarely identifies a building, so ask users for a location pin when the exact building matters. This tool is pre-release:
|
|
61
|
+
Text alone rarely identifies a building, so ask users for a location pin when the exact building matters. This tool is pre-release: it works against the live API, but its accuracy on real addresses is unmeasured.
|
|
62
62
|
|
|
63
63
|
## Install
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
Works with any MCP client. The server runs over stdio:
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
| Setting | Value |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| Command | `uvx` |
|
|
70
|
+
| Arguments | `ng-postcode-mcp` |
|
|
71
|
+
| Environment | `NG_POSTCODE_API_KEY` (optional for `validate_postcode`) |
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
claude mcp add ng-postcode -e NG_POSTCODE_API_KEY=nipost_live_... -- uvx ng-postcode-mcp
|
|
71
|
-
```
|
|
73
|
+
It needs [uv](https://docs.astral.sh/uv/) installed. Get an API key from the [NIPOST developer dashboard](https://dashboard.postcode.gov.ng).
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
Most clients take this entry in their MCP settings:
|
|
74
76
|
|
|
75
77
|
```json
|
|
76
78
|
{
|
|
@@ -84,6 +86,15 @@ claude mcp add ng-postcode -e NG_POSTCODE_API_KEY=nipost_live_... -- uvx ng-post
|
|
|
84
86
|
}
|
|
85
87
|
```
|
|
86
88
|
|
|
89
|
+
| Client | How to add it |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| Claude Code | `claude mcp add ng-postcode -e NG_POSTCODE_API_KEY=nipost_live_... -- uvx ng-postcode-mcp` |
|
|
92
|
+
| Claude Desktop | The entry above, in its MCP server settings |
|
|
93
|
+
| Codex | `codex mcp add ng-postcode --env NG_POSTCODE_API_KEY=nipost_live_... -- uvx ng-postcode-mcp` |
|
|
94
|
+
| Cursor | The entry above, in `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global) |
|
|
95
|
+
| VS Code | The same server object in `.vscode/mcp.json`, under a top-level `"servers"` key instead of `"mcpServers"` |
|
|
96
|
+
| Others | Any client that launches stdio servers: use the command, arguments and environment above |
|
|
97
|
+
|
|
87
98
|
## Configuration
|
|
88
99
|
|
|
89
100
|
| Variable | Default | Purpose |
|
|
@@ -30,19 +30,21 @@ The assistant reads the address and passes its landmarks and map searches to the
|
|
|
30
30
|
| A street only | District code, low confidence |
|
|
31
31
|
| A town only, or nothing found | No code, plus a question |
|
|
32
32
|
|
|
33
|
-
Text alone rarely identifies a building, so ask users for a location pin when the exact building matters. This tool is pre-release:
|
|
33
|
+
Text alone rarely identifies a building, so ask users for a location pin when the exact building matters. This tool is pre-release: it works against the live API, but its accuracy on real addresses is unmeasured.
|
|
34
34
|
|
|
35
35
|
## Install
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Works with any MCP client. The server runs over stdio:
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
| Setting | Value |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| Command | `uvx` |
|
|
42
|
+
| Arguments | `ng-postcode-mcp` |
|
|
43
|
+
| Environment | `NG_POSTCODE_API_KEY` (optional for `validate_postcode`) |
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
claude mcp add ng-postcode -e NG_POSTCODE_API_KEY=nipost_live_... -- uvx ng-postcode-mcp
|
|
43
|
-
```
|
|
45
|
+
It needs [uv](https://docs.astral.sh/uv/) installed. Get an API key from the [NIPOST developer dashboard](https://dashboard.postcode.gov.ng).
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
Most clients take this entry in their MCP settings:
|
|
46
48
|
|
|
47
49
|
```json
|
|
48
50
|
{
|
|
@@ -56,6 +58,15 @@ claude mcp add ng-postcode -e NG_POSTCODE_API_KEY=nipost_live_... -- uvx ng-post
|
|
|
56
58
|
}
|
|
57
59
|
```
|
|
58
60
|
|
|
61
|
+
| Client | How to add it |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| Claude Code | `claude mcp add ng-postcode -e NG_POSTCODE_API_KEY=nipost_live_... -- uvx ng-postcode-mcp` |
|
|
64
|
+
| Claude Desktop | The entry above, in its MCP server settings |
|
|
65
|
+
| Codex | `codex mcp add ng-postcode --env NG_POSTCODE_API_KEY=nipost_live_... -- uvx ng-postcode-mcp` |
|
|
66
|
+
| Cursor | The entry above, in `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global) |
|
|
67
|
+
| VS Code | The same server object in `.vscode/mcp.json`, under a top-level `"servers"` key instead of `"mcpServers"` |
|
|
68
|
+
| Others | Any client that launches stdio servers: use the command, arguments and environment above |
|
|
69
|
+
|
|
59
70
|
## Configuration
|
|
60
71
|
|
|
61
72
|
| Variable | Default | Purpose |
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ng-postcode-mcp"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "MCP server for Nigeria's NIPOST digital postcode (NDAPS): validate, look up, reverse-geocode and resolve addresses to postcodes."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -26,7 +26,7 @@ classifiers = [
|
|
|
26
26
|
"Topic :: Scientific/Engineering :: GIS",
|
|
27
27
|
"Typing :: Typed",
|
|
28
28
|
]
|
|
29
|
-
dependencies = ["mcp>=2.3,<3", "ng-address-resolver>=0.1,<0.2", "ng-postcode[client]>=0.
|
|
29
|
+
dependencies = ["mcp>=2.3,<3", "ng-address-resolver>=0.1.1,<0.2", "ng-postcode[client]>=0.2,<0.3"]
|
|
30
30
|
|
|
31
31
|
[project.scripts]
|
|
32
32
|
ng-postcode-mcp = "ng_postcode_mcp.server:main"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.Adeniyikayodee/ng-postcode",
|
|
4
4
|
"title": "Nigeria Postcode",
|
|
5
5
|
"description": "Validate, look up and resolve addresses to Nigeria's NIPOST digital postcodes (NDAPS).",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.2",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/Adeniyikayodee/ng-postcode",
|
|
9
9
|
"source": "github",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
{
|
|
14
14
|
"registryType": "pypi",
|
|
15
15
|
"identifier": "ng-postcode-mcp",
|
|
16
|
-
"version": "0.2.
|
|
16
|
+
"version": "0.2.2",
|
|
17
17
|
"runtimeHint": "uvx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
|
@@ -62,6 +62,8 @@ HINTS = {
|
|
|
62
62
|
"auth_required": f"Set NG_POSTCODE_API_KEY to a key from {KEY_URL}.",
|
|
63
63
|
"invalid_api_key": f"NG_POSTCODE_API_KEY is invalid or revoked; create a new key at {KEY_URL}.",
|
|
64
64
|
"insufficient_credits": "The NIPOST account is out of credits; top up or use level 1.",
|
|
65
|
+
"level_not_granted": "The key is not granted this lookup level; use a lower level or "
|
|
66
|
+
f"request more access at {KEY_URL}.",
|
|
65
67
|
}
|
|
66
68
|
STATUS_HINTS = {
|
|
67
69
|
403: "The key lacks the scope or access level for this request.",
|
|
@@ -136,6 +138,10 @@ class Address(FromLibrary):
|
|
|
136
138
|
class PostcodeDetails(FromLibrary):
|
|
137
139
|
postcode: str
|
|
138
140
|
valid: bool = Field(description="Whether the code is assigned to a building.")
|
|
141
|
+
status: str | None = Field(
|
|
142
|
+
default=None, description="valid, not_found, or invalid for a malformed code."
|
|
143
|
+
)
|
|
144
|
+
verified: bool | None = None
|
|
139
145
|
administrative_address: Address | None = Field(description="Level 2 and up.")
|
|
140
146
|
recent_house_address: str | None = Field(description="Level 2 and up. Personal data.")
|
|
141
147
|
building_use_status: str | None = Field(description="Level 3 and up, e.g. residential.")
|
|
@@ -144,8 +150,10 @@ class PostcodeDetails(FromLibrary):
|
|
|
144
150
|
|
|
145
151
|
|
|
146
152
|
class Completion(FromLibrary):
|
|
147
|
-
code: str
|
|
148
|
-
label: str
|
|
153
|
+
code: str = Field(description="The value of the next segment, e.g. A03, not a full prefix.")
|
|
154
|
+
label: str | None = Field(
|
|
155
|
+
default=None, description="A name for the code, when NIPOST sends one."
|
|
156
|
+
)
|
|
149
157
|
|
|
150
158
|
|
|
151
159
|
class Completions(BaseModel):
|
|
@@ -174,6 +182,7 @@ class Location(FromLibrary):
|
|
|
174
182
|
state: str | None
|
|
175
183
|
message: str | None
|
|
176
184
|
radius_m: float | None = Field(description="The radius the API actually applied.")
|
|
185
|
+
depth: str | None = Field(default=None, description="How deep the match goes, e.g. unit.")
|
|
177
186
|
|
|
178
187
|
|
|
179
188
|
@dataclass(frozen=True, slots=True)
|
|
@@ -264,10 +273,14 @@ def create_server(
|
|
|
264
273
|
structured_output=True,
|
|
265
274
|
)
|
|
266
275
|
async def autocomplete_postcode(
|
|
267
|
-
partial: Annotated[
|
|
276
|
+
partial: Annotated[
|
|
277
|
+
str, Field(min_length=1, description="The start of a code, e.g. 'EK 01 A'.")
|
|
278
|
+
],
|
|
268
279
|
ctx: Context[State, Any],
|
|
269
280
|
) -> Completions:
|
|
270
281
|
"""Suggest completions for the next segment of a partly typed postcode."""
|
|
282
|
+
if not partial.strip():
|
|
283
|
+
raise ToolError("Give at least the first characters of a postcode.")
|
|
271
284
|
result = await api(ctx).send(autocomplete(partial))
|
|
272
285
|
return completions(unwrap(result))
|
|
273
286
|
|
|
@@ -9,8 +9,10 @@ from typing import Any
|
|
|
9
9
|
import httpx
|
|
10
10
|
import pytest
|
|
11
11
|
from mcp import Client
|
|
12
|
+
from mcp.server.mcpserver.exceptions import ToolError
|
|
12
13
|
from mcp.types import CallToolResult, TextContent
|
|
13
14
|
from ng_postcode import api
|
|
15
|
+
from ng_postcode.api import ApiError
|
|
14
16
|
from pydantic import BaseModel
|
|
15
17
|
|
|
16
18
|
from ng_postcode_mcp import Settings, create_server, settings_from_env
|
|
@@ -20,6 +22,7 @@ from ng_postcode_mcp.server import (
|
|
|
20
22
|
Location,
|
|
21
23
|
NearestBuilding,
|
|
22
24
|
PostcodeDetails,
|
|
25
|
+
unwrap,
|
|
23
26
|
)
|
|
24
27
|
|
|
25
28
|
Handler = Callable[[httpx.Request], httpx.Response]
|
|
@@ -208,3 +211,17 @@ def test_settings_from_env() -> None:
|
|
|
208
211
|
assert settings_from_env({"NG_POSTCODE_MAX_LEVEL": "9"}) == (
|
|
209
212
|
"NG_POSTCODE_MAX_LEVEL must be 1 to 5, got '9'"
|
|
210
213
|
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@pytest.mark.anyio
|
|
217
|
+
async def test_a_blank_autocomplete_never_reaches_the_api() -> None:
|
|
218
|
+
seen: list[httpx.Request] = []
|
|
219
|
+
result = await call("autocomplete_postcode", {"partial": " "}, seen=seen)
|
|
220
|
+
assert result.is_error
|
|
221
|
+
assert seen == []
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def test_a_level_the_key_lacks_gets_a_specific_hint() -> None:
|
|
225
|
+
error = ApiError(403, "level_not_granted", "this key is granted up to lookup level 1")
|
|
226
|
+
with pytest.raises(ToolError, match="request more access"):
|
|
227
|
+
unwrap(error)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|