ng-postcode-mcp 0.0.0-stage → 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +54 -2
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +11 -0
- package/dist/cli.js.map +1 -0
- package/dist/contract.generated.json +899 -0
- package/dist/server.d.ts +19 -0
- package/dist/server.js +162 -0
- package/dist/server.js.map +1 -0
- package/package.json +54 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kayode Adeniyi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,55 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ng-postcode-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MCP server for Nigeria's National Digital Alphanumeric Postcode System (NDAPS), the building-level postcode NIPOST launched in October 2026. It lets AI assistants validate postcodes offline, look them up, autocomplete them and find them by location through the [postcode.gov.ng](https://docs.postcode.gov.ng) API.
|
|
4
|
+
|
|
5
|
+
This is the Node edition of the [ng-postcode](https://github.com/Adeniyikayodee/ng-postcode) server. It registers its tools from the same contract as the Python edition, so names, descriptions and schemas are identical. The Python edition also has `resolve_address` and an HTTP transport.
|
|
6
|
+
|
|
7
|
+
<!-- mcp-name: io.github.Adeniyikayodee/ng-postcode -->
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
It runs over stdio with `npx` and needs Node 20.10 or later. Most clients take this entry in their MCP settings:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"ng-postcode": {
|
|
17
|
+
"command": "npx",
|
|
18
|
+
"args": ["-y", "ng-postcode-mcp"],
|
|
19
|
+
"env": { "NG_POSTCODE_API_KEY": "nipost_live_..." }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Get an API key from the [NIPOST developer dashboard](https://dashboard.postcode.gov.ng). Validation works without one.
|
|
26
|
+
|
|
27
|
+
## Tools
|
|
28
|
+
|
|
29
|
+
| Tool | What it does | Needs a key | Cost |
|
|
30
|
+
| --- | --- | --- | --- |
|
|
31
|
+
| `validate_postcode` | Checks structure offline; returns canonical forms, segments and a suggested fix for look-alike characters | No | Free |
|
|
32
|
+
| `lookup_postcode` | Confirms a code is assigned; level 2 adds the address, level 3 building use | Yes | Level 1 free, 2+ uses credits |
|
|
33
|
+
| `autocomplete_postcode` | Suggests the next segment of a partly typed code | Yes | Free tier |
|
|
34
|
+
| `find_postcode_at_location` | Returns the postcode of the nearest building to a coordinate | Yes | Free tier |
|
|
35
|
+
|
|
36
|
+
All tools are read-only. Errors come back as messages the model can act on.
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
| Variable | Default | Purpose |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| `NG_POSTCODE_API_KEY` | none | NIPOST API key. Read from the environment only; never passed through tools. |
|
|
43
|
+
| `NG_POSTCODE_MAX_LEVEL` | `1` | Highest lookup level tools may request. Levels 2+ consume credits, so raise it deliberately. |
|
|
44
|
+
| `NG_POSTCODE_BASE_URL` | `https://api.postcode.gov.ng` | Alternative API host, such as a staging stack. |
|
|
45
|
+
|
|
46
|
+
## Safety
|
|
47
|
+
|
|
48
|
+
- Lookups default to level 1, which is free. A model cannot spend credits unless you raise `NG_POSTCODE_MAX_LEVEL`.
|
|
49
|
+
- A mistyped code is never corrected and sent to the API silently. The server returns the suggestion and asks the model to confirm it with the user.
|
|
50
|
+
- Names and house addresses are withheld from location results unless the cap is 2 or more. Treat them as personal data under the Nigeria Data Protection Act.
|
|
51
|
+
- The key is never sent to another host: the client refuses redirects.
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** `npx ng-postcode-mcp`: serves over stdio, so nothing else may print to stdout. */
|
|
3
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
4
|
+
import { createServer, settingsFromEnv } from "./server.js";
|
|
5
|
+
const settings = settingsFromEnv(process.env);
|
|
6
|
+
if (typeof settings === "string") {
|
|
7
|
+
console.error(`ng-postcode-mcp: ${settings}`);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
serveStdio(() => createServer(settings));
|
|
11
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,qFAAqF;AAErF,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE5D,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAC9C,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;IACjC,OAAO,CAAC,KAAK,CAAC,oBAAoB,QAAQ,EAAE,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AACD,UAAU,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,899 @@
|
|
|
1
|
+
{
|
|
2
|
+
"instructions": "Tools for Nigeria's 11-character building postcode, e.g. EK-01-A03-FK-01 (state, LGA, district, area, building unit).\n- validate_postcode is offline and free: use it first on any code a user typed.\n- lookup_postcode level 1 only confirms a code exists. Levels 2 and up add the address and building details, consume NIPOST credits, and are capped by the server's NG_POSTCODE_MAX_LEVEL.\n- resolve_address turns a described address into a code. It answers only as precisely as its evidence allows: pass on its level and confidence, and ask the user its question instead of guessing. A location pin is the most reliable input.\n- Never substitute a suggested correction without confirming it with the user.\n- Addresses returned are personal data: use them only for the user's request.",
|
|
3
|
+
"tools": [
|
|
4
|
+
{
|
|
5
|
+
"name": "validate_postcode",
|
|
6
|
+
"title": "Validate a Nigerian postcode",
|
|
7
|
+
"description": "Check a postcode's structure offline and return its canonical forms and segments.\n\nFree and instant. Does not confirm the code is assigned to a building; use\nlookup_postcode for that.",
|
|
8
|
+
"inputSchema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"postcode": {
|
|
12
|
+
"description": "Code in any style, e.g. 'ek 01 a03 fk 01'.",
|
|
13
|
+
"title": "Postcode",
|
|
14
|
+
"type": "string"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"required": [
|
|
18
|
+
"postcode"
|
|
19
|
+
],
|
|
20
|
+
"title": "validate_postcodeArguments"
|
|
21
|
+
},
|
|
22
|
+
"outputSchema": {
|
|
23
|
+
"$defs": {
|
|
24
|
+
"Segments": {
|
|
25
|
+
"properties": {
|
|
26
|
+
"state": {
|
|
27
|
+
"title": "State",
|
|
28
|
+
"type": "string"
|
|
29
|
+
},
|
|
30
|
+
"lga": {
|
|
31
|
+
"title": "Lga",
|
|
32
|
+
"type": "string"
|
|
33
|
+
},
|
|
34
|
+
"district": {
|
|
35
|
+
"title": "District",
|
|
36
|
+
"type": "string"
|
|
37
|
+
},
|
|
38
|
+
"area": {
|
|
39
|
+
"title": "Area",
|
|
40
|
+
"type": "string"
|
|
41
|
+
},
|
|
42
|
+
"unit": {
|
|
43
|
+
"title": "Unit",
|
|
44
|
+
"type": "string"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"required": [
|
|
48
|
+
"state",
|
|
49
|
+
"lga",
|
|
50
|
+
"district",
|
|
51
|
+
"area",
|
|
52
|
+
"unit"
|
|
53
|
+
],
|
|
54
|
+
"title": "Segments",
|
|
55
|
+
"type": "object"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"properties": {
|
|
59
|
+
"valid": {
|
|
60
|
+
"description": "Whether the code is well formed. It may still be unassigned.",
|
|
61
|
+
"title": "Valid",
|
|
62
|
+
"type": "boolean"
|
|
63
|
+
},
|
|
64
|
+
"postcode": {
|
|
65
|
+
"anyOf": [
|
|
66
|
+
{
|
|
67
|
+
"type": "string"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"type": "null"
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"description": "Canonical form, e.g. EK-01-A03-FK-01.",
|
|
74
|
+
"title": "Postcode"
|
|
75
|
+
},
|
|
76
|
+
"compact": {
|
|
77
|
+
"anyOf": [
|
|
78
|
+
{
|
|
79
|
+
"type": "string"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"type": "null"
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"description": "Compact form for storage, e.g. EK01A03FK01.",
|
|
86
|
+
"title": "Compact"
|
|
87
|
+
},
|
|
88
|
+
"spaced": {
|
|
89
|
+
"anyOf": [
|
|
90
|
+
{
|
|
91
|
+
"type": "string"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"type": "null"
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
"description": "Form shown to people, e.g. EK 01 A03 FK 01.",
|
|
98
|
+
"title": "Spaced"
|
|
99
|
+
},
|
|
100
|
+
"segments": {
|
|
101
|
+
"anyOf": [
|
|
102
|
+
{
|
|
103
|
+
"$ref": "#/$defs/Segments"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"type": "null"
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
"error": {
|
|
111
|
+
"anyOf": [
|
|
112
|
+
{
|
|
113
|
+
"type": "string"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"type": "null"
|
|
117
|
+
}
|
|
118
|
+
],
|
|
119
|
+
"description": "Why the code is malformed.",
|
|
120
|
+
"title": "Error"
|
|
121
|
+
},
|
|
122
|
+
"suggestion": {
|
|
123
|
+
"anyOf": [
|
|
124
|
+
{
|
|
125
|
+
"type": "string"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"type": "null"
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
"description": "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.",
|
|
132
|
+
"title": "Suggestion"
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"required": [
|
|
136
|
+
"valid",
|
|
137
|
+
"postcode",
|
|
138
|
+
"compact",
|
|
139
|
+
"spaced",
|
|
140
|
+
"segments",
|
|
141
|
+
"error",
|
|
142
|
+
"suggestion"
|
|
143
|
+
],
|
|
144
|
+
"title": "Validation",
|
|
145
|
+
"type": "object"
|
|
146
|
+
},
|
|
147
|
+
"annotations": {
|
|
148
|
+
"readOnlyHint": true,
|
|
149
|
+
"idempotentHint": true,
|
|
150
|
+
"openWorldHint": false
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"name": "lookup_postcode",
|
|
155
|
+
"title": "Look up a Nigerian postcode",
|
|
156
|
+
"description": "Confirm a postcode is assigned and, at higher levels, return its address details.",
|
|
157
|
+
"inputSchema": {
|
|
158
|
+
"type": "object",
|
|
159
|
+
"properties": {
|
|
160
|
+
"postcode": {
|
|
161
|
+
"description": "Code in any style, e.g. EK-01-A03-FK-01.",
|
|
162
|
+
"title": "Postcode",
|
|
163
|
+
"type": "string"
|
|
164
|
+
},
|
|
165
|
+
"level": {
|
|
166
|
+
"default": 1,
|
|
167
|
+
"description": "1: validity only (free). 2: adds the administrative and recent house address. 3: adds building use. Levels 2+ consume NIPOST credits.",
|
|
168
|
+
"maximum": 5,
|
|
169
|
+
"minimum": 1,
|
|
170
|
+
"title": "Level",
|
|
171
|
+
"type": "integer"
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"required": [
|
|
175
|
+
"postcode"
|
|
176
|
+
],
|
|
177
|
+
"title": "lookup_postcodeArguments"
|
|
178
|
+
},
|
|
179
|
+
"outputSchema": {
|
|
180
|
+
"$defs": {
|
|
181
|
+
"Address": {
|
|
182
|
+
"properties": {
|
|
183
|
+
"state_name": {
|
|
184
|
+
"anyOf": [
|
|
185
|
+
{
|
|
186
|
+
"type": "string"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"type": "null"
|
|
190
|
+
}
|
|
191
|
+
],
|
|
192
|
+
"title": "State Name"
|
|
193
|
+
},
|
|
194
|
+
"lga_name": {
|
|
195
|
+
"anyOf": [
|
|
196
|
+
{
|
|
197
|
+
"type": "string"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"type": "null"
|
|
201
|
+
}
|
|
202
|
+
],
|
|
203
|
+
"title": "Lga Name"
|
|
204
|
+
},
|
|
205
|
+
"locality_name": {
|
|
206
|
+
"anyOf": [
|
|
207
|
+
{
|
|
208
|
+
"type": "string"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"type": "null"
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
"title": "Locality Name"
|
|
215
|
+
},
|
|
216
|
+
"zone": {
|
|
217
|
+
"anyOf": [
|
|
218
|
+
{
|
|
219
|
+
"type": "string"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"type": "null"
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
"description": "Geopolitical zone, e.g. SOUTH WEST.",
|
|
226
|
+
"title": "Zone"
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
"required": [
|
|
230
|
+
"state_name",
|
|
231
|
+
"lga_name",
|
|
232
|
+
"locality_name",
|
|
233
|
+
"zone"
|
|
234
|
+
],
|
|
235
|
+
"title": "Address",
|
|
236
|
+
"type": "object"
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
"properties": {
|
|
240
|
+
"postcode": {
|
|
241
|
+
"title": "Postcode",
|
|
242
|
+
"type": "string"
|
|
243
|
+
},
|
|
244
|
+
"valid": {
|
|
245
|
+
"description": "Whether the code is assigned to a building.",
|
|
246
|
+
"title": "Valid",
|
|
247
|
+
"type": "boolean"
|
|
248
|
+
},
|
|
249
|
+
"status": {
|
|
250
|
+
"anyOf": [
|
|
251
|
+
{
|
|
252
|
+
"type": "string"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"type": "null"
|
|
256
|
+
}
|
|
257
|
+
],
|
|
258
|
+
"default": null,
|
|
259
|
+
"description": "valid, not_found, or invalid for a malformed code.",
|
|
260
|
+
"title": "Status"
|
|
261
|
+
},
|
|
262
|
+
"verified": {
|
|
263
|
+
"anyOf": [
|
|
264
|
+
{
|
|
265
|
+
"type": "boolean"
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"type": "null"
|
|
269
|
+
}
|
|
270
|
+
],
|
|
271
|
+
"default": null,
|
|
272
|
+
"title": "Verified"
|
|
273
|
+
},
|
|
274
|
+
"administrative_address": {
|
|
275
|
+
"anyOf": [
|
|
276
|
+
{
|
|
277
|
+
"$ref": "#/$defs/Address"
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"type": "null"
|
|
281
|
+
}
|
|
282
|
+
],
|
|
283
|
+
"description": "Level 2 and up."
|
|
284
|
+
},
|
|
285
|
+
"recent_house_address": {
|
|
286
|
+
"anyOf": [
|
|
287
|
+
{
|
|
288
|
+
"type": "string"
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"type": "null"
|
|
292
|
+
}
|
|
293
|
+
],
|
|
294
|
+
"description": "Level 2 and up. Personal data.",
|
|
295
|
+
"title": "Recent House Address"
|
|
296
|
+
},
|
|
297
|
+
"building_use_status": {
|
|
298
|
+
"anyOf": [
|
|
299
|
+
{
|
|
300
|
+
"type": "string"
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"type": "null"
|
|
304
|
+
}
|
|
305
|
+
],
|
|
306
|
+
"description": "Level 3 and up, e.g. residential.",
|
|
307
|
+
"title": "Building Use Status"
|
|
308
|
+
},
|
|
309
|
+
"other_building_info": {
|
|
310
|
+
"default": null,
|
|
311
|
+
"description": "Level 4 and up, unstructured.",
|
|
312
|
+
"title": "Other Building Info"
|
|
313
|
+
},
|
|
314
|
+
"point_geometry": {
|
|
315
|
+
"default": null,
|
|
316
|
+
"description": "Level 5, unstructured.",
|
|
317
|
+
"title": "Point Geometry"
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
"required": [
|
|
321
|
+
"postcode",
|
|
322
|
+
"valid",
|
|
323
|
+
"administrative_address",
|
|
324
|
+
"recent_house_address",
|
|
325
|
+
"building_use_status"
|
|
326
|
+
],
|
|
327
|
+
"title": "PostcodeDetails",
|
|
328
|
+
"type": "object"
|
|
329
|
+
},
|
|
330
|
+
"annotations": {
|
|
331
|
+
"readOnlyHint": true,
|
|
332
|
+
"idempotentHint": true,
|
|
333
|
+
"openWorldHint": true
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"name": "autocomplete_postcode",
|
|
338
|
+
"title": "Autocomplete a Nigerian postcode",
|
|
339
|
+
"description": "Suggest completions for the next segment of a partly typed postcode.",
|
|
340
|
+
"inputSchema": {
|
|
341
|
+
"type": "object",
|
|
342
|
+
"properties": {
|
|
343
|
+
"partial": {
|
|
344
|
+
"description": "The start of a code, e.g. 'EK 01 A'.",
|
|
345
|
+
"minLength": 1,
|
|
346
|
+
"title": "Partial",
|
|
347
|
+
"type": "string"
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
"required": [
|
|
351
|
+
"partial"
|
|
352
|
+
],
|
|
353
|
+
"title": "autocomplete_postcodeArguments"
|
|
354
|
+
},
|
|
355
|
+
"outputSchema": {
|
|
356
|
+
"$defs": {
|
|
357
|
+
"Completion": {
|
|
358
|
+
"properties": {
|
|
359
|
+
"code": {
|
|
360
|
+
"description": "The value of the next segment, e.g. A03, not a full prefix.",
|
|
361
|
+
"title": "Code",
|
|
362
|
+
"type": "string"
|
|
363
|
+
},
|
|
364
|
+
"label": {
|
|
365
|
+
"anyOf": [
|
|
366
|
+
{
|
|
367
|
+
"type": "string"
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"type": "null"
|
|
371
|
+
}
|
|
372
|
+
],
|
|
373
|
+
"default": null,
|
|
374
|
+
"description": "A name for the code, when NIPOST sends one.",
|
|
375
|
+
"title": "Label"
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
"required": [
|
|
379
|
+
"code"
|
|
380
|
+
],
|
|
381
|
+
"title": "Completion",
|
|
382
|
+
"type": "object"
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
"properties": {
|
|
386
|
+
"segment": {
|
|
387
|
+
"anyOf": [
|
|
388
|
+
{
|
|
389
|
+
"enum": [
|
|
390
|
+
"state",
|
|
391
|
+
"lga",
|
|
392
|
+
"district",
|
|
393
|
+
"area",
|
|
394
|
+
"unit"
|
|
395
|
+
],
|
|
396
|
+
"type": "string"
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
"type": "null"
|
|
400
|
+
}
|
|
401
|
+
],
|
|
402
|
+
"description": "The segment being completed.",
|
|
403
|
+
"title": "Segment"
|
|
404
|
+
},
|
|
405
|
+
"suggestions": {
|
|
406
|
+
"items": {
|
|
407
|
+
"$ref": "#/$defs/Completion"
|
|
408
|
+
},
|
|
409
|
+
"title": "Suggestions",
|
|
410
|
+
"type": "array"
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
"required": [
|
|
414
|
+
"segment",
|
|
415
|
+
"suggestions"
|
|
416
|
+
],
|
|
417
|
+
"title": "Completions",
|
|
418
|
+
"type": "object"
|
|
419
|
+
},
|
|
420
|
+
"annotations": {
|
|
421
|
+
"readOnlyHint": true,
|
|
422
|
+
"idempotentHint": true,
|
|
423
|
+
"openWorldHint": true
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
"name": "find_postcode_at_location",
|
|
428
|
+
"title": "Find the postcode at a location",
|
|
429
|
+
"description": "Return the postcode of the nearest building to a coordinate in Nigeria.\n\nNames and the house address are withheld unless NG_POSTCODE_MAX_LEVEL is 2 or more.",
|
|
430
|
+
"inputSchema": {
|
|
431
|
+
"type": "object",
|
|
432
|
+
"properties": {
|
|
433
|
+
"latitude": {
|
|
434
|
+
"description": "Latitude in decimal degrees, e.g. 7.6211.",
|
|
435
|
+
"maximum": 90,
|
|
436
|
+
"minimum": -90,
|
|
437
|
+
"title": "Latitude",
|
|
438
|
+
"type": "number"
|
|
439
|
+
},
|
|
440
|
+
"longitude": {
|
|
441
|
+
"description": "Longitude in decimal degrees, e.g. 5.2214.",
|
|
442
|
+
"maximum": 180,
|
|
443
|
+
"minimum": -180,
|
|
444
|
+
"title": "Longitude",
|
|
445
|
+
"type": "number"
|
|
446
|
+
},
|
|
447
|
+
"max_distance_m": {
|
|
448
|
+
"anyOf": [
|
|
449
|
+
{
|
|
450
|
+
"maximum": 250,
|
|
451
|
+
"minimum": 0,
|
|
452
|
+
"type": "number"
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
"type": "null"
|
|
456
|
+
}
|
|
457
|
+
],
|
|
458
|
+
"default": null,
|
|
459
|
+
"description": "Search radius in metres. Defaults to 25.",
|
|
460
|
+
"title": "Max Distance M"
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
"required": [
|
|
464
|
+
"latitude",
|
|
465
|
+
"longitude"
|
|
466
|
+
],
|
|
467
|
+
"title": "find_postcode_at_locationArguments"
|
|
468
|
+
},
|
|
469
|
+
"outputSchema": {
|
|
470
|
+
"$defs": {
|
|
471
|
+
"NearestBuilding": {
|
|
472
|
+
"properties": {
|
|
473
|
+
"postcode": {
|
|
474
|
+
"title": "Postcode",
|
|
475
|
+
"type": "string"
|
|
476
|
+
},
|
|
477
|
+
"display": {
|
|
478
|
+
"title": "Display",
|
|
479
|
+
"type": "string"
|
|
480
|
+
},
|
|
481
|
+
"distance_m": {
|
|
482
|
+
"anyOf": [
|
|
483
|
+
{
|
|
484
|
+
"type": "number"
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
"type": "null"
|
|
488
|
+
}
|
|
489
|
+
],
|
|
490
|
+
"title": "Distance M"
|
|
491
|
+
},
|
|
492
|
+
"confidence": {
|
|
493
|
+
"anyOf": [
|
|
494
|
+
{
|
|
495
|
+
"type": "string"
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
"type": "null"
|
|
499
|
+
}
|
|
500
|
+
],
|
|
501
|
+
"description": "high, medium or low, graded by distance.",
|
|
502
|
+
"title": "Confidence"
|
|
503
|
+
},
|
|
504
|
+
"state_name": {
|
|
505
|
+
"anyOf": [
|
|
506
|
+
{
|
|
507
|
+
"type": "string"
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
"type": "null"
|
|
511
|
+
}
|
|
512
|
+
],
|
|
513
|
+
"description": "Level 2 and up.",
|
|
514
|
+
"title": "State Name"
|
|
515
|
+
},
|
|
516
|
+
"lga_name": {
|
|
517
|
+
"anyOf": [
|
|
518
|
+
{
|
|
519
|
+
"type": "string"
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
"type": "null"
|
|
523
|
+
}
|
|
524
|
+
],
|
|
525
|
+
"description": "Level 2 and up.",
|
|
526
|
+
"title": "Lga Name"
|
|
527
|
+
},
|
|
528
|
+
"locality_name": {
|
|
529
|
+
"anyOf": [
|
|
530
|
+
{
|
|
531
|
+
"type": "string"
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
"type": "null"
|
|
535
|
+
}
|
|
536
|
+
],
|
|
537
|
+
"description": "Level 2 and up.",
|
|
538
|
+
"title": "Locality Name"
|
|
539
|
+
},
|
|
540
|
+
"address": {
|
|
541
|
+
"anyOf": [
|
|
542
|
+
{
|
|
543
|
+
"type": "string"
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
"type": "null"
|
|
547
|
+
}
|
|
548
|
+
],
|
|
549
|
+
"description": "Recent house address. Level 2 and up.",
|
|
550
|
+
"title": "Address"
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
"required": [
|
|
554
|
+
"postcode",
|
|
555
|
+
"display",
|
|
556
|
+
"distance_m",
|
|
557
|
+
"confidence",
|
|
558
|
+
"state_name",
|
|
559
|
+
"lga_name",
|
|
560
|
+
"locality_name",
|
|
561
|
+
"address"
|
|
562
|
+
],
|
|
563
|
+
"title": "NearestBuilding",
|
|
564
|
+
"type": "object"
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
"properties": {
|
|
568
|
+
"found": {
|
|
569
|
+
"description": "False when no building is within the radius.",
|
|
570
|
+
"title": "Found",
|
|
571
|
+
"type": "boolean"
|
|
572
|
+
},
|
|
573
|
+
"unit": {
|
|
574
|
+
"anyOf": [
|
|
575
|
+
{
|
|
576
|
+
"$ref": "#/$defs/NearestBuilding"
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
"type": "null"
|
|
580
|
+
}
|
|
581
|
+
]
|
|
582
|
+
},
|
|
583
|
+
"area": {
|
|
584
|
+
"anyOf": [
|
|
585
|
+
{
|
|
586
|
+
"type": "string"
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
"type": "null"
|
|
590
|
+
}
|
|
591
|
+
],
|
|
592
|
+
"description": "Enclosing area code, e.g. EK-01-A03-FK.",
|
|
593
|
+
"title": "Area"
|
|
594
|
+
},
|
|
595
|
+
"district": {
|
|
596
|
+
"anyOf": [
|
|
597
|
+
{
|
|
598
|
+
"type": "string"
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
"type": "null"
|
|
602
|
+
}
|
|
603
|
+
],
|
|
604
|
+
"title": "District"
|
|
605
|
+
},
|
|
606
|
+
"state": {
|
|
607
|
+
"anyOf": [
|
|
608
|
+
{
|
|
609
|
+
"type": "string"
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
"type": "null"
|
|
613
|
+
}
|
|
614
|
+
],
|
|
615
|
+
"title": "State"
|
|
616
|
+
},
|
|
617
|
+
"message": {
|
|
618
|
+
"anyOf": [
|
|
619
|
+
{
|
|
620
|
+
"type": "string"
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
"type": "null"
|
|
624
|
+
}
|
|
625
|
+
],
|
|
626
|
+
"title": "Message"
|
|
627
|
+
},
|
|
628
|
+
"radius_m": {
|
|
629
|
+
"anyOf": [
|
|
630
|
+
{
|
|
631
|
+
"type": "number"
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
"type": "null"
|
|
635
|
+
}
|
|
636
|
+
],
|
|
637
|
+
"description": "The radius the API actually applied.",
|
|
638
|
+
"title": "Radius M"
|
|
639
|
+
},
|
|
640
|
+
"depth": {
|
|
641
|
+
"anyOf": [
|
|
642
|
+
{
|
|
643
|
+
"type": "string"
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
"type": "null"
|
|
647
|
+
}
|
|
648
|
+
],
|
|
649
|
+
"default": null,
|
|
650
|
+
"description": "How deep the match goes, e.g. unit.",
|
|
651
|
+
"title": "Depth"
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
"required": [
|
|
655
|
+
"found",
|
|
656
|
+
"unit",
|
|
657
|
+
"area",
|
|
658
|
+
"district",
|
|
659
|
+
"state",
|
|
660
|
+
"message",
|
|
661
|
+
"radius_m"
|
|
662
|
+
],
|
|
663
|
+
"title": "Location",
|
|
664
|
+
"type": "object"
|
|
665
|
+
},
|
|
666
|
+
"annotations": {
|
|
667
|
+
"readOnlyHint": true,
|
|
668
|
+
"idempotentHint": true,
|
|
669
|
+
"openWorldHint": true
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
"name": "resolve_address",
|
|
674
|
+
"title": "Resolve a Nigerian address to a postcode",
|
|
675
|
+
"description": "Turn a described Nigerian address into a postcode, only as precisely as the\nevidence allows.\n\nA full building code comes only from a postcode written in the address, a location\npin, or a landmark that is the address itself. Otherwise the result is an area or\ndistrict prefix with a `question` for the user. Read the address yourself and fill\n`landmarks` and `geocode_queries`; the address text is sent to the configured\ngeocoder.",
|
|
676
|
+
"inputSchema": {
|
|
677
|
+
"type": "object",
|
|
678
|
+
"$defs": {
|
|
679
|
+
"Landmark": {
|
|
680
|
+
"additionalProperties": false,
|
|
681
|
+
"properties": {
|
|
682
|
+
"name": {
|
|
683
|
+
"title": "Name",
|
|
684
|
+
"type": "string"
|
|
685
|
+
},
|
|
686
|
+
"relation": {
|
|
687
|
+
"description": "How the address relates to the landmark. 'at' means the address is the landmark itself; any other value means a different building nearby.",
|
|
688
|
+
"enum": [
|
|
689
|
+
"at",
|
|
690
|
+
"near",
|
|
691
|
+
"opposite",
|
|
692
|
+
"behind",
|
|
693
|
+
"beside",
|
|
694
|
+
"after",
|
|
695
|
+
"before",
|
|
696
|
+
"off",
|
|
697
|
+
"inside"
|
|
698
|
+
],
|
|
699
|
+
"title": "Relation",
|
|
700
|
+
"type": "string"
|
|
701
|
+
}
|
|
702
|
+
},
|
|
703
|
+
"required": [
|
|
704
|
+
"name",
|
|
705
|
+
"relation"
|
|
706
|
+
],
|
|
707
|
+
"title": "Landmark",
|
|
708
|
+
"type": "object"
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
"properties": {
|
|
712
|
+
"address": {
|
|
713
|
+
"description": "The address exactly as the user gave it.",
|
|
714
|
+
"title": "Address",
|
|
715
|
+
"type": "string"
|
|
716
|
+
},
|
|
717
|
+
"landmarks": {
|
|
718
|
+
"anyOf": [
|
|
719
|
+
{
|
|
720
|
+
"items": {
|
|
721
|
+
"$ref": "#/$defs/Landmark"
|
|
722
|
+
},
|
|
723
|
+
"type": "array"
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
"type": "null"
|
|
727
|
+
}
|
|
728
|
+
],
|
|
729
|
+
"default": null,
|
|
730
|
+
"description": "Landmarks named in the address, each with how the address relates to it. Use 'at' only when the address is the landmark itself.",
|
|
731
|
+
"title": "Landmarks"
|
|
732
|
+
},
|
|
733
|
+
"geocode_queries": {
|
|
734
|
+
"anyOf": [
|
|
735
|
+
{
|
|
736
|
+
"items": {
|
|
737
|
+
"type": "string"
|
|
738
|
+
},
|
|
739
|
+
"maxItems": 3,
|
|
740
|
+
"type": "array"
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
"type": "null"
|
|
744
|
+
}
|
|
745
|
+
],
|
|
746
|
+
"default": null,
|
|
747
|
+
"description": "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'.",
|
|
748
|
+
"title": "Geocode Queries"
|
|
749
|
+
},
|
|
750
|
+
"latitude": {
|
|
751
|
+
"anyOf": [
|
|
752
|
+
{
|
|
753
|
+
"maximum": 90,
|
|
754
|
+
"minimum": -90,
|
|
755
|
+
"type": "number"
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
"type": "null"
|
|
759
|
+
}
|
|
760
|
+
],
|
|
761
|
+
"default": null,
|
|
762
|
+
"description": "Latitude of a location pin the user shared.",
|
|
763
|
+
"title": "Latitude"
|
|
764
|
+
},
|
|
765
|
+
"longitude": {
|
|
766
|
+
"anyOf": [
|
|
767
|
+
{
|
|
768
|
+
"maximum": 180,
|
|
769
|
+
"minimum": -180,
|
|
770
|
+
"type": "number"
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
"type": "null"
|
|
774
|
+
}
|
|
775
|
+
],
|
|
776
|
+
"default": null,
|
|
777
|
+
"description": "Longitude of that pin. Give it with latitude.",
|
|
778
|
+
"title": "Longitude"
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
"required": [
|
|
782
|
+
"address"
|
|
783
|
+
],
|
|
784
|
+
"title": "resolve_addressArguments"
|
|
785
|
+
},
|
|
786
|
+
"outputSchema": {
|
|
787
|
+
"additionalProperties": false,
|
|
788
|
+
"description": "The answer, never more precise than the evidence behind it.",
|
|
789
|
+
"properties": {
|
|
790
|
+
"status": {
|
|
791
|
+
"enum": [
|
|
792
|
+
"resolved",
|
|
793
|
+
"partial",
|
|
794
|
+
"unresolved"
|
|
795
|
+
],
|
|
796
|
+
"title": "Status",
|
|
797
|
+
"type": "string"
|
|
798
|
+
},
|
|
799
|
+
"code": {
|
|
800
|
+
"anyOf": [
|
|
801
|
+
{
|
|
802
|
+
"type": "string"
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
"type": "null"
|
|
806
|
+
}
|
|
807
|
+
],
|
|
808
|
+
"description": "A full postcode at building level, else a prefix.",
|
|
809
|
+
"title": "Code"
|
|
810
|
+
},
|
|
811
|
+
"level": {
|
|
812
|
+
"anyOf": [
|
|
813
|
+
{
|
|
814
|
+
"enum": [
|
|
815
|
+
"building",
|
|
816
|
+
"area",
|
|
817
|
+
"district",
|
|
818
|
+
"lga"
|
|
819
|
+
],
|
|
820
|
+
"type": "string"
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
"type": "null"
|
|
824
|
+
}
|
|
825
|
+
],
|
|
826
|
+
"title": "Level"
|
|
827
|
+
},
|
|
828
|
+
"confidence": {
|
|
829
|
+
"anyOf": [
|
|
830
|
+
{
|
|
831
|
+
"enum": [
|
|
832
|
+
"high",
|
|
833
|
+
"medium",
|
|
834
|
+
"low"
|
|
835
|
+
],
|
|
836
|
+
"type": "string"
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
"type": "null"
|
|
840
|
+
}
|
|
841
|
+
],
|
|
842
|
+
"title": "Confidence"
|
|
843
|
+
},
|
|
844
|
+
"method": {
|
|
845
|
+
"anyOf": [
|
|
846
|
+
{
|
|
847
|
+
"enum": [
|
|
848
|
+
"typed",
|
|
849
|
+
"location",
|
|
850
|
+
"geocoded"
|
|
851
|
+
],
|
|
852
|
+
"type": "string"
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
"type": "null"
|
|
856
|
+
}
|
|
857
|
+
],
|
|
858
|
+
"title": "Method"
|
|
859
|
+
},
|
|
860
|
+
"question": {
|
|
861
|
+
"anyOf": [
|
|
862
|
+
{
|
|
863
|
+
"type": "string"
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
"type": "null"
|
|
867
|
+
}
|
|
868
|
+
],
|
|
869
|
+
"description": "What to ask the user to get a better answer.",
|
|
870
|
+
"title": "Question"
|
|
871
|
+
},
|
|
872
|
+
"evidence": {
|
|
873
|
+
"items": {
|
|
874
|
+
"type": "string"
|
|
875
|
+
},
|
|
876
|
+
"title": "Evidence",
|
|
877
|
+
"type": "array"
|
|
878
|
+
}
|
|
879
|
+
},
|
|
880
|
+
"required": [
|
|
881
|
+
"status",
|
|
882
|
+
"code",
|
|
883
|
+
"level",
|
|
884
|
+
"confidence",
|
|
885
|
+
"method",
|
|
886
|
+
"question",
|
|
887
|
+
"evidence"
|
|
888
|
+
],
|
|
889
|
+
"title": "Resolution",
|
|
890
|
+
"type": "object"
|
|
891
|
+
},
|
|
892
|
+
"annotations": {
|
|
893
|
+
"readOnlyHint": true,
|
|
894
|
+
"idempotentHint": true,
|
|
895
|
+
"openWorldHint": true
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
]
|
|
899
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server for Nigeria's NIPOST digital postcode.
|
|
3
|
+
*
|
|
4
|
+
* Tool names, descriptions and schemas come from `spec/mcp.json`, the contract the
|
|
5
|
+
* Python server generates, so the two cannot drift. Validation runs offline; the
|
|
6
|
+
* other tools call the postcode.gov.ng API with the key in NG_POSTCODE_API_KEY.
|
|
7
|
+
*/
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
9
|
+
export interface Settings {
|
|
10
|
+
readonly apiKey: string | undefined;
|
|
11
|
+
readonly maxLevel: number;
|
|
12
|
+
readonly baseUrl: string;
|
|
13
|
+
/** Your own `fetch`, as tests use. */
|
|
14
|
+
readonly fetch?: typeof fetch;
|
|
15
|
+
}
|
|
16
|
+
/** Read settings from the environment, or describe what is wrong with them. */
|
|
17
|
+
export declare function settingsFromEnv(env: Record<string, string | undefined>): Settings | string;
|
|
18
|
+
export declare function createServer(settings: Settings): McpServer;
|
|
19
|
+
export declare const VERSION = "0.1.0";
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server for Nigeria's NIPOST digital postcode.
|
|
3
|
+
*
|
|
4
|
+
* Tool names, descriptions and schemas come from `spec/mcp.json`, the contract the
|
|
5
|
+
* Python server generates, so the two cannot drift. Validation runs offline; the
|
|
6
|
+
* other tools call the postcode.gov.ng API with the key in NG_POSTCODE_API_KEY.
|
|
7
|
+
*/
|
|
8
|
+
import { fromJsonSchema, McpServer } from "@modelcontextprotocol/server";
|
|
9
|
+
import { Postcode, parse, parseLenient } from "ng-postcode-js";
|
|
10
|
+
import { ApiError, autocomplete, BASE_URL, lookup, reverse, } from "ng-postcode-js/api";
|
|
11
|
+
import { Client, TransportError } from "ng-postcode-js/client";
|
|
12
|
+
import contract from "./contract.generated.json" with { type: "json" };
|
|
13
|
+
const KEY_URL = "https://dashboard.postcode.gov.ng";
|
|
14
|
+
const HINTS = {
|
|
15
|
+
auth_required: `Supply a NIPOST API key from ${KEY_URL}.`,
|
|
16
|
+
invalid_api_key: `The NIPOST API key is invalid or revoked; create a new key at ${KEY_URL}.`,
|
|
17
|
+
insufficient_credits: "The NIPOST account is out of credits; top up or use level 1.",
|
|
18
|
+
level_not_granted: `The key is not granted this lookup level; use a lower level or request more access at ${KEY_URL}.`,
|
|
19
|
+
};
|
|
20
|
+
const STATUS_HINTS = {
|
|
21
|
+
403: "The key lacks the scope or access level for this request.",
|
|
22
|
+
429: "NIPOST rate limit reached; wait before retrying.",
|
|
23
|
+
};
|
|
24
|
+
const LEVEL_2_FIELDS = ["state_name", "lga_name", "locality_name", "address"];
|
|
25
|
+
/** Read settings from the environment, or describe what is wrong with them. */
|
|
26
|
+
export function settingsFromEnv(env) {
|
|
27
|
+
const level = (env.NG_POSTCODE_MAX_LEVEL ?? "1").trim();
|
|
28
|
+
if (!/^[1-5]$/.test(level))
|
|
29
|
+
return `NG_POSTCODE_MAX_LEVEL must be 1 to 5, got '${level}'`;
|
|
30
|
+
return {
|
|
31
|
+
apiKey: env.NG_POSTCODE_API_KEY?.trim() || undefined,
|
|
32
|
+
maxLevel: Number(level),
|
|
33
|
+
baseUrl: env.NG_POSTCODE_BASE_URL?.trim() || BASE_URL,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** A failure the model can act on; its message becomes the tool's error text. */
|
|
37
|
+
class ToolError extends Error {
|
|
38
|
+
}
|
|
39
|
+
export function createServer(settings) {
|
|
40
|
+
const client = settings.apiKey
|
|
41
|
+
? new Client(settings.apiKey, {
|
|
42
|
+
baseUrl: settings.baseUrl,
|
|
43
|
+
...(settings.fetch ? { fetch: settings.fetch } : {}),
|
|
44
|
+
})
|
|
45
|
+
: undefined;
|
|
46
|
+
const api = () => {
|
|
47
|
+
if (client)
|
|
48
|
+
return client;
|
|
49
|
+
throw new ToolError(`This tool needs a NIPOST API key in NG_POSTCODE_API_KEY. Get one at ${KEY_URL}.`);
|
|
50
|
+
};
|
|
51
|
+
const handlers = {
|
|
52
|
+
validate_postcode: ({ postcode }) => validation(String(postcode)),
|
|
53
|
+
lookup_postcode: async ({ postcode, level = 1 }) => {
|
|
54
|
+
if (Number(level) > settings.maxLevel) {
|
|
55
|
+
throw new ToolError(`Level ${level} is above this server's cap of ${settings.maxLevel}. Levels 2+ consume NIPOST credits; the user can raise NG_POSTCODE_MAX_LEVEL to allow it.`);
|
|
56
|
+
}
|
|
57
|
+
return unwrap(await api().send(lookup(checked(String(postcode)), Number(level))));
|
|
58
|
+
},
|
|
59
|
+
autocomplete_postcode: async ({ partial }) => {
|
|
60
|
+
if (!String(partial).trim())
|
|
61
|
+
throw new ToolError("Give at least the first characters of a postcode.");
|
|
62
|
+
return unwrap(await api().send(autocomplete(String(partial))));
|
|
63
|
+
},
|
|
64
|
+
find_postcode_at_location: async ({ latitude, longitude, max_distance_m }) => {
|
|
65
|
+
const at = { lat: Number(latitude), lng: Number(longitude) };
|
|
66
|
+
const metres = max_distance_m == null ? undefined : Number(max_distance_m);
|
|
67
|
+
const { coordinate: _, ...found } = unwrap(await api().send(reverse(at, metres)));
|
|
68
|
+
return capped(found, settings.maxLevel);
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
const server = new McpServer({ name: "ng-postcode", title: "Nigeria Postcode", version: VERSION }, { instructions: instructionsFor(Object.keys(handlers)) });
|
|
72
|
+
for (const tool of contract.tools) {
|
|
73
|
+
const handler = handlers[tool.name];
|
|
74
|
+
if (!handler)
|
|
75
|
+
continue;
|
|
76
|
+
server.registerTool(tool.name, {
|
|
77
|
+
title: tool.title,
|
|
78
|
+
description: tool.description,
|
|
79
|
+
inputSchema: fromJsonSchema(tool.inputSchema),
|
|
80
|
+
outputSchema: fromJsonSchema(tool.outputSchema),
|
|
81
|
+
annotations: tool.annotations,
|
|
82
|
+
}, async (args) => {
|
|
83
|
+
try {
|
|
84
|
+
const output = (await handler(args));
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
87
|
+
structuredContent: output,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (!(error instanceof ToolError))
|
|
92
|
+
throw error;
|
|
93
|
+
return { content: [{ type: "text", text: error.message }], isError: true };
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return server;
|
|
98
|
+
}
|
|
99
|
+
export const VERSION = "0.1.0";
|
|
100
|
+
/** The shared instructions, without the lines about tools this server does not have. */
|
|
101
|
+
function instructionsFor(tools) {
|
|
102
|
+
const missing = contract.tools.map((tool) => tool.name).filter((name) => !tools.includes(name));
|
|
103
|
+
return contract.instructions
|
|
104
|
+
.split("\n")
|
|
105
|
+
.filter((line) => !missing.some((name) => line.startsWith(`- ${name} `)))
|
|
106
|
+
.join("\n");
|
|
107
|
+
}
|
|
108
|
+
function validation(text) {
|
|
109
|
+
const code = parse(text);
|
|
110
|
+
if (code instanceof Postcode) {
|
|
111
|
+
const { state, lga, district, area, unit } = code;
|
|
112
|
+
return {
|
|
113
|
+
valid: true,
|
|
114
|
+
postcode: String(code),
|
|
115
|
+
compact: code.compact,
|
|
116
|
+
spaced: code.spaced,
|
|
117
|
+
segments: { state, lga, district, area, unit },
|
|
118
|
+
error: null,
|
|
119
|
+
suggestion: null,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
valid: false,
|
|
124
|
+
postcode: null,
|
|
125
|
+
compact: null,
|
|
126
|
+
spaced: null,
|
|
127
|
+
segments: null,
|
|
128
|
+
error: String(code),
|
|
129
|
+
suggestion: suggestion(text),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function suggestion(text) {
|
|
133
|
+
const fixed = parseLenient(text);
|
|
134
|
+
return "postcode" in fixed ? String(fixed.postcode) : null;
|
|
135
|
+
}
|
|
136
|
+
/** The parsed code, or a ToolError the model can act on. Never auto-corrects a paid call. */
|
|
137
|
+
function checked(text) {
|
|
138
|
+
const code = parse(text);
|
|
139
|
+
if (code instanceof Postcode)
|
|
140
|
+
return code;
|
|
141
|
+
const hint = suggestion(text);
|
|
142
|
+
const maybe = hint ? ` Did you mean ${hint}? Confirm with the user first.` : "";
|
|
143
|
+
throw new ToolError(`'${text}' is not a valid postcode: ${code}.${maybe}`);
|
|
144
|
+
}
|
|
145
|
+
/** The location without the fields a level 2 key adds, unless the cap allows them. */
|
|
146
|
+
function capped(location, maxLevel) {
|
|
147
|
+
if (maxLevel >= 2 || !location.unit)
|
|
148
|
+
return location;
|
|
149
|
+
const withheld = Object.fromEntries(LEVEL_2_FIELDS.map((field) => [field, null]));
|
|
150
|
+
return { ...location, unit: { ...location.unit, ...withheld } };
|
|
151
|
+
}
|
|
152
|
+
function unwrap(result) {
|
|
153
|
+
if (result instanceof TransportError) {
|
|
154
|
+
throw new ToolError(`Could not reach the NIPOST API: ${result}`);
|
|
155
|
+
}
|
|
156
|
+
if (result instanceof ApiError) {
|
|
157
|
+
const hint = HINTS[result.code] ?? STATUS_HINTS[result.status] ?? "";
|
|
158
|
+
throw new ToolError(`NIPOST API error ${result}. ${hint}`.trim());
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ng-postcode-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Nigeria's NIPOST digital postcode (NDAPS): validate, look up, autocomplete and find postcodes by location.",
|
|
5
|
+
"mcpName": "io.github.Adeniyikayodee/ng-postcode",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Kayode Adeniyi",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Adeniyikayodee/ng-postcode.git",
|
|
11
|
+
"directory": "js/packages/ng-postcode-mcp"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://adeniyikayodee.github.io/ng-postcode/",
|
|
14
|
+
"bugs": "https://github.com/Adeniyikayodee/ng-postcode/issues",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"mcp-server",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"ai-agents",
|
|
20
|
+
"nigeria",
|
|
21
|
+
"postcode",
|
|
22
|
+
"nipost",
|
|
23
|
+
"ndaps",
|
|
24
|
+
"geocoding"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"bin": {
|
|
28
|
+
"ng-postcode-mcp": "dist/cli.js"
|
|
29
|
+
},
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/server.d.ts",
|
|
33
|
+
"default": "./dist/server.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc -p tsconfig.build.json",
|
|
43
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
44
|
+
"prepack": "npm run build"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@modelcontextprotocol/server": "^2.3.0",
|
|
48
|
+
"ng-postcode-js": "^0.1.0"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20.10"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@modelcontextprotocol/client": "^2.3.0"
|
|
55
|
+
}
|
|
56
|
+
}
|