@pipeworx/mcp-ban-fr 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 +139 -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-ban-fr
|
|
2
|
+
|
|
3
|
+
Base Adresse Nationale (BAN) MCP — France's official keyless geocoding API
|
|
4
|
+
|
|
5
|
+
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 250+ 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
|
+
"ban-fr": {
|
|
20
|
+
"url": "https://gateway.pipeworx.io/ban-fr/mcp"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or connect to the full Pipeworx gateway for access to all 250+ 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 Ban Fr 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-ban-fr",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Base Adresse Nationale (BAN) MCP — France's official keyless geocoding API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"keywords": ["mcp", "mcp-server", "model-context-protocol", "pipeworx", "ban-fr"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/pipeworx-io/mcp-ban-fr"
|
|
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/ban-fr",
|
|
4
|
+
"title": "Ban Fr",
|
|
5
|
+
"description": "Base Adresse Nationale (BAN) MCP — France's official keyless geocoding API",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"websiteUrl": "https://pipeworx.io/packs/ban-fr",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/pipeworx-io/mcp-ban-fr",
|
|
10
|
+
"source": "github"
|
|
11
|
+
},
|
|
12
|
+
"remotes": [
|
|
13
|
+
{
|
|
14
|
+
"type": "streamable-http",
|
|
15
|
+
"url": "https://gateway.pipeworx.io/ban-fr/mcp"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
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
|
+
* Base Adresse Nationale (BAN) MCP — France's official keyless geocoding API
|
|
21
|
+
* (api-adresse.data.gouv.fr). Covers French addresses, streets, localities and
|
|
22
|
+
* communes only. Responses are GeoJSON FeatureCollections; each feature's
|
|
23
|
+
* `score` is 0-1 (higher = better match). Keyless, no auth.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
const BASE = 'https://api-adresse.data.gouv.fr';
|
|
28
|
+
const UA = 'pipeworx-mcp-ban-fr/1.0 (+https://pipeworx.io)';
|
|
29
|
+
|
|
30
|
+
const TYPES = ['housenumber', 'street', 'locality', 'municipality'] as const;
|
|
31
|
+
|
|
32
|
+
const tools: McpToolExport['tools'] = [
|
|
33
|
+
{
|
|
34
|
+
name: 'geocode',
|
|
35
|
+
description:
|
|
36
|
+
'Forward-geocode a French address, street, locality or commune. Returns scored GeoJSON matches (score 0-1) with label, coordinates, postcode, citycode (INSEE) and context. France only.',
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
q: { type: 'string', description: 'Free-text query, e.g. "8 bd du port" or "rue de la paix paris".' },
|
|
41
|
+
limit: { type: 'number', description: 'Max results (default 5, server caps at 20).' },
|
|
42
|
+
type: { type: 'string', enum: [...TYPES], description: 'Restrict to a feature type.' },
|
|
43
|
+
postcode: { type: 'string', description: 'Filter by 5-digit postal code, e.g. "95000".' },
|
|
44
|
+
citycode: { type: 'string', description: 'Filter by INSEE commune code, e.g. "95127".' },
|
|
45
|
+
autocomplete: { type: 'boolean', description: 'Treat q as a partial/autocomplete prefix (default false).' },
|
|
46
|
+
lat: { type: 'number', description: 'Latitude to bias/prioritise results geographically (pair with lon).' },
|
|
47
|
+
lon: { type: 'number', description: 'Longitude to bias/prioritise results geographically (pair with lat).' },
|
|
48
|
+
},
|
|
49
|
+
required: ['q'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'reverse_geocode',
|
|
54
|
+
description:
|
|
55
|
+
'Reverse-geocode a coordinate to the nearest French address/feature. Returns scored GeoJSON matches with label, postcode, citycode (INSEE), context and distance (metres). France only.',
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
lat: { type: 'number', description: 'Latitude (WGS84).' },
|
|
60
|
+
lon: { type: 'number', description: 'Longitude (WGS84).' },
|
|
61
|
+
type: { type: 'string', enum: [...TYPES], description: 'Restrict to a feature type.' },
|
|
62
|
+
},
|
|
63
|
+
required: ['lat', 'lon'],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'search_municipality',
|
|
68
|
+
description:
|
|
69
|
+
'Look up a French commune (municipality) by name to resolve its INSEE citycode, postcode, population and centre coordinates. Shortcut for geocode with type=municipality.',
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
q: { type: 'string', description: 'Commune name, e.g. "montpellier".' },
|
|
74
|
+
limit: { type: 'number', description: 'Max results (default 5).' },
|
|
75
|
+
},
|
|
76
|
+
required: ['q'],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
82
|
+
switch (name) {
|
|
83
|
+
case 'geocode': {
|
|
84
|
+
const params = new URLSearchParams({ q: reqStr(args, 'q', '"8 bd du port"') });
|
|
85
|
+
setNum(params, 'limit', args.limit);
|
|
86
|
+
setStr(params, 'type', args.type);
|
|
87
|
+
setStr(params, 'postcode', args.postcode);
|
|
88
|
+
setStr(params, 'citycode', args.citycode);
|
|
89
|
+
if (args.autocomplete !== undefined) params.set('autocomplete', args.autocomplete ? '1' : '0');
|
|
90
|
+
setNum(params, 'lat', args.lat);
|
|
91
|
+
setNum(params, 'lon', args.lon);
|
|
92
|
+
return banGet(`/search/?${params}`);
|
|
93
|
+
}
|
|
94
|
+
case 'reverse_geocode': {
|
|
95
|
+
const params = new URLSearchParams({
|
|
96
|
+
lat: String(reqNum(args, 'lat')),
|
|
97
|
+
lon: String(reqNum(args, 'lon')),
|
|
98
|
+
});
|
|
99
|
+
setStr(params, 'type', args.type);
|
|
100
|
+
return banGet(`/reverse/?${params}`);
|
|
101
|
+
}
|
|
102
|
+
case 'search_municipality': {
|
|
103
|
+
const params = new URLSearchParams({ q: reqStr(args, 'q', '"montpellier"'), type: 'municipality' });
|
|
104
|
+
setNum(params, 'limit', args.limit);
|
|
105
|
+
return banGet(`/search/?${params}`);
|
|
106
|
+
}
|
|
107
|
+
default:
|
|
108
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function banGet(path: string): Promise<unknown> {
|
|
113
|
+
const res = await fetch(`${BASE}${path}`, { headers: { Accept: 'application/json', 'User-Agent': UA } });
|
|
114
|
+
if (!res.ok) throw new Error(`BAN: ${res.status} ${await res.text().then((t) => t.slice(0, 200))}`);
|
|
115
|
+
return res.json();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function reqStr(args: Record<string, unknown>, key: string, example: string): string {
|
|
119
|
+
const v = args[key];
|
|
120
|
+
if (typeof v !== 'string' || !v.trim()) throw new Error(`Required argument "${key}" is missing. Pass a string like ${example}.`);
|
|
121
|
+
return v;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function reqNum(args: Record<string, unknown>, key: string): number {
|
|
125
|
+
const v = typeof args[key] === 'string' ? Number(args[key]) : args[key];
|
|
126
|
+
if (typeof v !== 'number' || !Number.isFinite(v)) throw new Error(`Required argument "${key}" must be a number.`);
|
|
127
|
+
return v;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function setStr(params: URLSearchParams, key: string, v: unknown): void {
|
|
131
|
+
if (typeof v === 'string' && v.trim()) params.set(key, v);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function setNum(params: URLSearchParams, key: string, v: unknown): void {
|
|
135
|
+
const n = typeof v === 'string' ? Number(v) : v;
|
|
136
|
+
if (typeof n === 'number' && Number.isFinite(n)) params.set(key, String(n));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
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
|
+
}
|