@pipeworx/mcp-geoboundaries 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 +55 -0
- package/package.json +20 -0
- package/server.json +18 -0
- package/src/index.ts +175 -0
- package/tsconfig.json +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pipeworx
|
|
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
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# mcp-geoboundaries
|
|
2
|
+
|
|
3
|
+
geoBoundaries MCP — open database of political administrative boundaries.
|
|
4
|
+
|
|
5
|
+
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 697+ live data sources.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
| Tool | Description |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
Add to your MCP client (Claude Desktop, Cursor, Windsurf, etc.):
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"geoboundaries": {
|
|
20
|
+
"url": "https://gateway.pipeworx.io/geoboundaries/mcp"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or connect to the full Pipeworx gateway for access to all 697+ data sources:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"pipeworx": {
|
|
32
|
+
"url": "https://gateway.pipeworx.io/mcp"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Using with ask_pipeworx
|
|
39
|
+
|
|
40
|
+
Instead of calling tools directly, you can ask questions in plain English:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
ask_pipeworx({ question: "your question about Geoboundaries data" })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The gateway picks the right tool and fills the arguments automatically.
|
|
47
|
+
|
|
48
|
+
## More
|
|
49
|
+
|
|
50
|
+
- [All tools and guides](https://github.com/pipeworx-io/examples)
|
|
51
|
+
- [pipeworx.io](https://pipeworx.io)
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pipeworx/mcp-geoboundaries",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "geoBoundaries MCP — open database of political administrative boundaries.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"keywords": ["mcp", "mcp-server", "model-context-protocol", "pipeworx", "geoboundaries"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/pipeworx-io/mcp-geoboundaries"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.7.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.pipeworx-io/geoboundaries",
|
|
4
|
+
"title": "Geoboundaries",
|
|
5
|
+
"description": "geoBoundaries MCP — open database of political administrative boundaries.",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"websiteUrl": "https://pipeworx.io/packs/geoboundaries",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/pipeworx-io/mcp-geoboundaries",
|
|
10
|
+
"source": "github"
|
|
11
|
+
},
|
|
12
|
+
"remotes": [
|
|
13
|
+
{
|
|
14
|
+
"type": "streamable-http",
|
|
15
|
+
"url": "https://gateway.pipeworx.io/geoboundaries/mcp"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
interface McpToolDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: 'object';
|
|
6
|
+
properties: Record<string, unknown>;
|
|
7
|
+
required?: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface McpToolExport {
|
|
12
|
+
tools: McpToolDefinition[];
|
|
13
|
+
callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
14
|
+
meter?: { credits: number };
|
|
15
|
+
cost?: Record<string, unknown>;
|
|
16
|
+
provider?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* geoBoundaries MCP — open database of political administrative boundaries.
|
|
21
|
+
*
|
|
22
|
+
* Source: https://www.geoboundaries.org (keyless, CC-BY for the gbOpen product).
|
|
23
|
+
* Returns boundary metadata (unit counts, year, source, license) plus download
|
|
24
|
+
* URLs for full + simplified GeoJSON and TopoJSON, and can optionally fetch the
|
|
25
|
+
* simplified GeoJSON geometry itself (can be large — see size caveat below).
|
|
26
|
+
*
|
|
27
|
+
* Concepts:
|
|
28
|
+
* - ISO3: 3-letter ISO-3166-1 alpha-3 country code (e.g. USA, FRA, KEN, IND).
|
|
29
|
+
* - ADM level: ADM0 = country, ADM1 = state/province/region,
|
|
30
|
+
* ADM2 = county/district, ADM3/ADM4 = finer subdivisions, ALL = every level.
|
|
31
|
+
* - product: gbOpen (default, openly CC-BY licensed) | gbHumanitarian | gbAuthoritative.
|
|
32
|
+
* Not every ISO3/ADM/product combination exists; missing combos return 404.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
const BASE = 'https://www.geoboundaries.org/api/current';
|
|
37
|
+
const UA = 'pipeworx-mcp-geoboundaries/1.0 (+https://pipeworx.io)';
|
|
38
|
+
const VALID_ADM = ['ADM0', 'ADM1', 'ADM2', 'ADM3', 'ADM4', 'ALL'];
|
|
39
|
+
const VALID_PRODUCT = ['gbOpen', 'gbHumanitarian', 'gbAuthoritative'];
|
|
40
|
+
|
|
41
|
+
// Hard cap on geometry we'll pull into a tool response. "Simplified" GeoJSON
|
|
42
|
+
// for a large country (e.g. USA ADM1) is ~5 MB; full geometry is much bigger.
|
|
43
|
+
const MAX_GEOMETRY_BYTES = 4_000_000;
|
|
44
|
+
|
|
45
|
+
const tools: McpToolExport['tools'] = [
|
|
46
|
+
{
|
|
47
|
+
name: 'get_boundaries',
|
|
48
|
+
description:
|
|
49
|
+
'Get administrative boundary metadata for a country from geoBoundaries: ' +
|
|
50
|
+
'unit count, year represented, source, license, and download URLs for full ' +
|
|
51
|
+
'GeoJSON (gjDownloadURL), simplified GeoJSON (simplifiedGeometryGeoJSON), ' +
|
|
52
|
+
'TopoJSON (tjDownloadURL) and a static zip. ISO3 is the 3-letter country code ' +
|
|
53
|
+
'(USA, FRA, KEN, IND). ADM level: ADM0=country, ADM1=state/province, ' +
|
|
54
|
+
'ADM2=county/district, ADM3/ADM4=finer, ALL=every level. Default product gbOpen is CC-BY.',
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
iso3: { type: 'string', description: 'ISO-3166-1 alpha-3 country code, e.g. "USA", "FRA", "KEN".' },
|
|
59
|
+
adm: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
enum: VALID_ADM,
|
|
62
|
+
description: 'Admin level: ADM0=country, ADM1=state/province, ADM2=county/district, ADM3, ADM4, or ALL.',
|
|
63
|
+
},
|
|
64
|
+
product: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
enum: VALID_PRODUCT,
|
|
67
|
+
description: 'Boundary product. gbOpen (default, CC-BY) | gbHumanitarian | gbAuthoritative.',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
required: ['iso3', 'adm'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'get_geometry',
|
|
75
|
+
description:
|
|
76
|
+
'Fetch the actual simplified GeoJSON FeatureCollection geometry for a country/ADM level ' +
|
|
77
|
+
'from geoBoundaries. WARNING: geometry payloads are large (a simplified national ' +
|
|
78
|
+
'ADM1 set can be several MB); this only returns the simplified geometry and refuses ' +
|
|
79
|
+
'payloads over ~4 MB — for those use get_boundaries and download the URL directly. ' +
|
|
80
|
+
'Prefer get_boundaries unless you explicitly need the coordinates. Not valid for adm="ALL".',
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
iso3: { type: 'string', description: 'ISO-3166-1 alpha-3 country code, e.g. "FRA".' },
|
|
85
|
+
adm: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
enum: ['ADM0', 'ADM1', 'ADM2', 'ADM3', 'ADM4'],
|
|
88
|
+
description: 'Admin level (single level only; "ALL" is not supported here).',
|
|
89
|
+
},
|
|
90
|
+
product: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
enum: VALID_PRODUCT,
|
|
93
|
+
description: 'Boundary product. gbOpen (default, CC-BY) | gbHumanitarian | gbAuthoritative.',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
required: ['iso3', 'adm'],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
102
|
+
switch (name) {
|
|
103
|
+
case 'get_boundaries': {
|
|
104
|
+
const { iso3, adm, product } = parseArgs(args, VALID_ADM);
|
|
105
|
+
return gbGet(`/${product}/${iso3}/${adm}/`);
|
|
106
|
+
}
|
|
107
|
+
case 'get_geometry': {
|
|
108
|
+
const { iso3, adm, product } = parseArgs(args, ['ADM0', 'ADM1', 'ADM2', 'ADM3', 'ADM4']);
|
|
109
|
+
const meta = (await gbGet(`/${product}/${iso3}/${adm}/`)) as Record<string, unknown>;
|
|
110
|
+
const url = meta.simplifiedGeometryGeoJSON;
|
|
111
|
+
if (typeof url !== 'string' || !url) {
|
|
112
|
+
throw new Error('geoBoundaries: no simplifiedGeometryGeoJSON available for this combination.');
|
|
113
|
+
}
|
|
114
|
+
const res = await fetch(url, { headers: { Accept: 'application/json', 'User-Agent': UA } });
|
|
115
|
+
if (!res.ok) {
|
|
116
|
+
const body = await res.text().then((t) => t.slice(0, 200));
|
|
117
|
+
throw new Error(`geoBoundaries: ${res.status} ${body}`);
|
|
118
|
+
}
|
|
119
|
+
const text = await res.text();
|
|
120
|
+
if (text.length > MAX_GEOMETRY_BYTES) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`geoBoundaries: simplified geometry is ${text.length} bytes (> ${MAX_GEOMETRY_BYTES} cap). ` +
|
|
123
|
+
`Download it directly instead: ${url}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
iso3,
|
|
128
|
+
adm,
|
|
129
|
+
product,
|
|
130
|
+
source: url,
|
|
131
|
+
license: meta.boundaryLicense,
|
|
132
|
+
admUnitCount: meta.admUnitCount,
|
|
133
|
+
geojson: JSON.parse(text),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
default:
|
|
137
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function parseArgs(
|
|
142
|
+
args: Record<string, unknown>,
|
|
143
|
+
allowedAdm: string[],
|
|
144
|
+
): { iso3: string; adm: string; product: string } {
|
|
145
|
+
const iso3raw = args.iso3;
|
|
146
|
+
if (typeof iso3raw !== 'string' || !/^[A-Za-z]{3}$/.test(iso3raw.trim())) {
|
|
147
|
+
throw new Error('Required argument "iso3" must be a 3-letter ISO-3166-1 alpha-3 code, e.g. "USA".');
|
|
148
|
+
}
|
|
149
|
+
const iso3 = iso3raw.trim().toUpperCase();
|
|
150
|
+
|
|
151
|
+
const admRaw = args.adm;
|
|
152
|
+
const adm = typeof admRaw === 'string' ? admRaw.trim().toUpperCase() : '';
|
|
153
|
+
if (!allowedAdm.includes(adm)) {
|
|
154
|
+
throw new Error(`Required argument "adm" must be one of: ${allowedAdm.join(', ')}.`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const productRaw = args.product;
|
|
158
|
+
const product = typeof productRaw === 'string' && productRaw.trim() ? productRaw.trim() : 'gbOpen';
|
|
159
|
+
if (!VALID_PRODUCT.includes(product)) {
|
|
160
|
+
throw new Error(`Argument "product" must be one of: ${VALID_PRODUCT.join(', ')}.`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { iso3, adm, product };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function gbGet(path: string): Promise<unknown> {
|
|
167
|
+
const res = await fetch(`${BASE}${path}`, { headers: { Accept: 'application/json', 'User-Agent': UA } });
|
|
168
|
+
if (!res.ok) {
|
|
169
|
+
const body = await res.text().then((t) => t.slice(0, 200));
|
|
170
|
+
throw new Error(`geoBoundaries: ${res.status} ${body}`);
|
|
171
|
+
}
|
|
172
|
+
return res.json();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export default { tools, callTool, meter: { credits: 1 } } satisfies McpToolExport;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|