ng-postcode-mcp 0.2.2__tar.gz → 0.2.4__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ng-postcode-mcp
3
- Version: 0.2.2
3
+ Version: 0.2.4
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.1
26
- Requires-Dist: ng-postcode[client]<0.3,>=0.2
25
+ Requires-Dist: ng-address-resolver<0.2,>=0.1.4
26
+ Requires-Dist: ng-postcode[client]<0.3,>=0.2.1
27
27
  Description-Content-Type: text/markdown
28
28
 
29
29
  # ng-postcode-mcp
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ng-postcode-mcp"
7
- version = "0.2.2"
7
+ version = "0.2.4"
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.1,<0.2", "ng-postcode[client]>=0.2,<0.3"]
29
+ dependencies = ["mcp>=2.3,<3", "ng-address-resolver>=0.1.4,<0.2", "ng-postcode[client]>=0.2.1,<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.2",
6
+ "version": "0.2.4",
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.2",
16
+ "version": "0.2.4",
17
17
  "runtimeHint": "uvx",
18
18
  "transport": {
19
19
  "type": "stdio"
@@ -65,6 +65,7 @@ HINTS = {
65
65
  "level_not_granted": "The key is not granted this lookup level; use a lower level or "
66
66
  f"request more access at {KEY_URL}.",
67
67
  }
68
+ LEVEL_2_FIELDS = ("state_name", "lga_name", "locality_name", "address")
68
69
  STATUS_HINTS = {
69
70
  403: "The key lacks the scope or access level for this request.",
70
71
  429: "NIPOST rate limit reached; wait before retrying.",
@@ -298,11 +299,14 @@ def create_server(
298
299
  Field(ge=0, le=250, description="Search radius in metres. Defaults to 25."),
299
300
  ] = None,
300
301
  ) -> Location:
301
- """Return the postcode of the nearest building to a coordinate in Nigeria."""
302
+ """Return the postcode of the nearest building to a coordinate in Nigeria.
303
+
304
+ Names and the house address are withheld unless NG_POSTCODE_MAX_LEVEL is 2 or more.
305
+ """
302
306
  result = await api(ctx).send(
303
307
  reverse(Coordinate(lat=latitude, lng=longitude), max_distance_m)
304
308
  )
305
- return Location.model_validate(unwrap(result))
309
+ return capped(Location.model_validate(unwrap(result)), settings.max_level)
306
310
 
307
311
  @server.tool(
308
312
  title="Resolve a Nigerian address to a postcode",
@@ -383,6 +387,14 @@ def geocoder_for(settings: Settings, http: httpx.AsyncClient | None) -> Nominati
383
387
  )
384
388
 
385
389
 
390
+ def capped(location: Location, max_level: int) -> Location:
391
+ """The location without the fields a level 2 key adds, unless the cap allows them."""
392
+ if max_level >= 2 or location.unit is None:
393
+ return location
394
+ unit = location.unit.model_copy(update=dict.fromkeys(LEVEL_2_FIELDS))
395
+ return location.model_copy(update={"unit": unit})
396
+
397
+
386
398
  def completions(found: Autocomplete) -> Completions:
387
399
  return Completions(
388
400
  segment=None if found.segment is None else found.segment.value,
@@ -61,6 +61,13 @@ async def call(
61
61
  return result
62
62
 
63
63
 
64
+ @pytest.mark.anyio
65
+ async def test_queries_without_landmarks_never_give_a_building() -> None:
66
+ queries = {k: v for k, v in BEHIND_FABIAN.items() if k != "landmarks"}
67
+ answer = (await call(queries)).structured_content
68
+ assert (answer["status"], answer["level"]) == ("partial", "area")
69
+
70
+
64
71
  @pytest.mark.anyio
65
72
  async def test_the_host_models_reading_drives_the_answer() -> None:
66
73
  searched: list[httpx.Request] = []
@@ -22,6 +22,7 @@ from ng_postcode_mcp.server import (
22
22
  Location,
23
23
  NearestBuilding,
24
24
  PostcodeDetails,
25
+ capped,
25
26
  unwrap,
26
27
  )
27
28
 
@@ -181,6 +182,26 @@ async def test_autocomplete_and_reverse() -> None:
181
182
  assert (near.structured_content["found"], near.structured_content["radius_m"]) == (False, 25.0)
182
183
 
183
184
 
185
+ def test_the_level_cap_withholds_names_and_addresses() -> None:
186
+ unit = NearestBuilding(
187
+ postcode="EK-01-A03-FK-01",
188
+ display="EK 01 A03 FK 01",
189
+ distance_m=8.0,
190
+ confidence="high",
191
+ state_name="EKITI",
192
+ lga_name="ADO EKITI",
193
+ locality_name="ADO EKITI",
194
+ address="NTA ROAD",
195
+ )
196
+ found = Location(
197
+ found=True, unit=unit, area=None, district=None, state=None, message=None, radius_m=25.0
198
+ )
199
+ assert capped(found, 2) == found
200
+ held = capped(found, 1).unit
201
+ assert held is not None
202
+ assert (held.postcode, held.address, held.state_name) == ("EK-01-A03-FK-01", None, None)
203
+
204
+
184
205
  @pytest.mark.anyio
185
206
  async def test_rejects_out_of_range_arguments() -> None:
186
207
  result = await call("find_postcode_at_location", {"latitude": 95, "longitude": 5.22})
File without changes