@proximap/mcp 1.0.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 +57 -0
- package/dist/index.js +454 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ameya Borkar
|
|
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,57 @@
|
|
|
1
|
+
# @proximap/mcp
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) server that exposes
|
|
4
|
+
[proximap](https://github.com/AmeyaBorkar/proximap) as tools for AI agents
|
|
5
|
+
(e.g. Claude). Powered by OpenStreetMap; no API keys required.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
- **`find_nearby_amenities`** — `{ query, radiusMeters?, categories?, filters?, accessible?,
|
|
10
|
+
open?, rankBy?, mode?, explain?, concise?, limit?, language? }` → amenities near a place/`lat,lng`,
|
|
11
|
+
ranked by distance or road-network travel time. Supports facet filters (diet/payment/wifi/wheelchair),
|
|
12
|
+
accessibility-first ranking, open-now/open-at, a one-line `summary` per result, and a token-economy
|
|
13
|
+
`concise` mode.
|
|
14
|
+
- **`geocode`** — `{ query, limit?, language? }` → the best guess plus ranked candidates and an
|
|
15
|
+
`ambiguous` flag when several distinct places share the name (the many "Springfield"s) — present the
|
|
16
|
+
candidates instead of guessing.
|
|
17
|
+
- **`list_categories`** — `{}` → the category terms proximap understands (for `find_nearby_amenities`).
|
|
18
|
+
- **`detect_amenity_gaps`** — `{ query, categories?, searchRadiusMeters?, thresholdMeters?, language? }`
|
|
19
|
+
→ which everyday amenities are missing or far, framed as "not found in OSM," never asserted as truth.
|
|
20
|
+
- **`walkability_score`** — `{ query, idealMeters?, maxMeters?, language? }` → a 0–100 score with a
|
|
21
|
+
transparent per-category breakdown, the missing categories, and a data-confidence note.
|
|
22
|
+
- **`compare_locations`** — `{ locations[2+], weights?, idealMeters?, maxMeters?, language? }` → a ranked
|
|
23
|
+
scorecard of candidate locations with a per-dimension winner.
|
|
24
|
+
- **`reachable_amenities`** — `{ query, within, mode?, categories?, language? }` → the amenities reachable
|
|
25
|
+
within a time budget (real isochrone where available), plus the polygon.
|
|
26
|
+
- **`plan_errands`** — `{ query, categories[1+], mode?, end?, candidatesPerCategory?, language? }` → the
|
|
27
|
+
shortest trip that visits one of each category (Generalized TSP), with the chosen places and totals.
|
|
28
|
+
|
|
29
|
+
All tools are powered by OpenStreetMap (+ the key-free Valhalla engine for travel time/isochrones) and
|
|
30
|
+
return compact JSON. Travel times and routing degrade gracefully to straight-line estimates if the
|
|
31
|
+
routing engine is unavailable.
|
|
32
|
+
|
|
33
|
+
## Install & run
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @proximap/mcp
|
|
37
|
+
proximap-mcp # starts a stdio MCP server
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Register with an MCP client
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"proximap": {
|
|
46
|
+
"command": "proximap-mcp"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For Claude Code, add the snippet above to your `.mcp.json` (or use
|
|
53
|
+
`claude mcp add proximap proximap-mcp`).
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import {
|
|
7
|
+
categoryVocabulary,
|
|
8
|
+
compareLocations,
|
|
9
|
+
detectGaps,
|
|
10
|
+
disambiguateLocation,
|
|
11
|
+
findNearbyAmenities,
|
|
12
|
+
planErrands,
|
|
13
|
+
reachableAmenities,
|
|
14
|
+
ValhallaRoutingProvider,
|
|
15
|
+
walkabilityScore
|
|
16
|
+
} from "@proximap/core";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
|
|
19
|
+
// src/payload.ts
|
|
20
|
+
function toNearbyPayload(result, options = {}) {
|
|
21
|
+
const { origin, results, total, routing } = result;
|
|
22
|
+
const head = {
|
|
23
|
+
origin: {
|
|
24
|
+
name: origin.name,
|
|
25
|
+
displayName: origin.displayName,
|
|
26
|
+
lat: origin.location.lat,
|
|
27
|
+
lng: origin.location.lng
|
|
28
|
+
},
|
|
29
|
+
total,
|
|
30
|
+
count: results.length,
|
|
31
|
+
routing: routing ?? null
|
|
32
|
+
};
|
|
33
|
+
if (options.concise) {
|
|
34
|
+
return {
|
|
35
|
+
...head,
|
|
36
|
+
results: results.map((poi) => ({
|
|
37
|
+
rank: poi.rank,
|
|
38
|
+
name: poi.name ?? null,
|
|
39
|
+
category: poi.category,
|
|
40
|
+
...poi.travelSeconds !== void 0 ? { travelSeconds: poi.travelSeconds } : { distanceMeters: Math.round(poi.distanceMeters) },
|
|
41
|
+
lat: poi.location.lat,
|
|
42
|
+
lng: poi.location.lng,
|
|
43
|
+
...poi.rankingReason ? { summary: poi.rankingReason } : {}
|
|
44
|
+
}))
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
...head,
|
|
49
|
+
results: results.map((poi) => ({
|
|
50
|
+
rank: poi.rank,
|
|
51
|
+
name: poi.name ?? null,
|
|
52
|
+
category: poi.category,
|
|
53
|
+
kind: poi.kind ?? null,
|
|
54
|
+
distanceMeters: Math.round(poi.distanceMeters),
|
|
55
|
+
travelSeconds: poi.travelSeconds ?? null,
|
|
56
|
+
travelMeters: poi.travelMeters ?? null,
|
|
57
|
+
lat: poi.location.lat,
|
|
58
|
+
lng: poi.location.lng,
|
|
59
|
+
osmId: poi.id,
|
|
60
|
+
completeness: poi.completeness ?? null,
|
|
61
|
+
lastVerified: poi.lastVerified ?? null,
|
|
62
|
+
wheelchair: poi.tags.wheelchair ?? null,
|
|
63
|
+
wheelchairDescription: poi.tags["wheelchair:description"] ?? null,
|
|
64
|
+
openState: poi.openState ?? null,
|
|
65
|
+
nextChange: poi.nextChange ?? null,
|
|
66
|
+
rankingReason: poi.rankingReason ?? null
|
|
67
|
+
}))
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
var compactPlace = (place) => ({
|
|
71
|
+
name: place.name,
|
|
72
|
+
displayName: place.displayName,
|
|
73
|
+
lat: place.location.lat,
|
|
74
|
+
lng: place.location.lng,
|
|
75
|
+
kind: place.kind ?? null
|
|
76
|
+
});
|
|
77
|
+
function toDisambiguationPayload(result) {
|
|
78
|
+
return {
|
|
79
|
+
query: result.query,
|
|
80
|
+
ambiguous: result.ambiguous,
|
|
81
|
+
best: result.best ? compactPlace(result.best) : null,
|
|
82
|
+
candidates: result.candidates.map(compactPlace)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function toErrandsPayload(plan) {
|
|
86
|
+
return {
|
|
87
|
+
origin: {
|
|
88
|
+
name: plan.origin.name,
|
|
89
|
+
displayName: plan.origin.displayName,
|
|
90
|
+
lat: plan.origin.location.lat,
|
|
91
|
+
lng: plan.origin.location.lng
|
|
92
|
+
},
|
|
93
|
+
end: plan.end ? {
|
|
94
|
+
displayName: plan.end.displayName,
|
|
95
|
+
lat: plan.end.location.lat,
|
|
96
|
+
lng: plan.end.location.lng
|
|
97
|
+
} : null,
|
|
98
|
+
mode: plan.mode,
|
|
99
|
+
totalSeconds: plan.totalSeconds,
|
|
100
|
+
totalMeters: plan.totalMeters,
|
|
101
|
+
missing: plan.missing,
|
|
102
|
+
stops: plan.stops.map((stop) => ({
|
|
103
|
+
category: stop.category,
|
|
104
|
+
name: stop.poi.name ?? null,
|
|
105
|
+
kind: stop.poi.kind ?? null,
|
|
106
|
+
osmId: stop.poi.id,
|
|
107
|
+
lat: stop.poi.location.lat,
|
|
108
|
+
lng: stop.poi.location.lng,
|
|
109
|
+
legSeconds: stop.legSeconds,
|
|
110
|
+
legMeters: stop.legMeters
|
|
111
|
+
}))
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function toReachablePayload(result) {
|
|
115
|
+
return {
|
|
116
|
+
origin: {
|
|
117
|
+
name: result.origin.name,
|
|
118
|
+
displayName: result.origin.displayName,
|
|
119
|
+
lat: result.origin.location.lat,
|
|
120
|
+
lng: result.origin.location.lng
|
|
121
|
+
},
|
|
122
|
+
withinMinutes: result.withinMinutes,
|
|
123
|
+
mode: result.mode,
|
|
124
|
+
isochrone: result.isochrone,
|
|
125
|
+
// [lng, lat] ring, or null
|
|
126
|
+
count: result.count,
|
|
127
|
+
results: result.results.map((poi) => ({
|
|
128
|
+
rank: poi.rank,
|
|
129
|
+
name: poi.name ?? null,
|
|
130
|
+
category: poi.category,
|
|
131
|
+
kind: poi.kind ?? null,
|
|
132
|
+
travelSeconds: poi.travelSeconds ?? null,
|
|
133
|
+
travelMeters: poi.travelMeters ?? null,
|
|
134
|
+
distanceMeters: Math.round(poi.distanceMeters),
|
|
135
|
+
lat: poi.location.lat,
|
|
136
|
+
lng: poi.location.lng,
|
|
137
|
+
osmId: poi.id
|
|
138
|
+
}))
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function toGapsPayload(report) {
|
|
142
|
+
return {
|
|
143
|
+
origin: {
|
|
144
|
+
name: report.origin.name,
|
|
145
|
+
displayName: report.origin.displayName,
|
|
146
|
+
lat: report.origin.location.lat,
|
|
147
|
+
lng: report.origin.location.lng
|
|
148
|
+
},
|
|
149
|
+
searchRadiusMeters: report.searchRadiusMeters,
|
|
150
|
+
thresholdMeters: report.thresholdMeters,
|
|
151
|
+
gaps: report.gaps,
|
|
152
|
+
missing: report.missing
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function toScorePayload(report) {
|
|
156
|
+
return {
|
|
157
|
+
origin: {
|
|
158
|
+
name: report.origin.name,
|
|
159
|
+
displayName: report.origin.displayName,
|
|
160
|
+
lat: report.origin.location.lat,
|
|
161
|
+
lng: report.origin.location.lng
|
|
162
|
+
},
|
|
163
|
+
score: report.score,
|
|
164
|
+
confidence: report.confidence,
|
|
165
|
+
decay: report.decay,
|
|
166
|
+
breakdown: report.breakdown,
|
|
167
|
+
missing: report.missing
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function toComparePayload(report) {
|
|
171
|
+
return {
|
|
172
|
+
ranked: report.ranked.map((entry) => ({
|
|
173
|
+
index: entry.index,
|
|
174
|
+
displayName: entry.origin.displayName,
|
|
175
|
+
score: entry.score
|
|
176
|
+
})),
|
|
177
|
+
best: report.best ? {
|
|
178
|
+
index: report.best.index,
|
|
179
|
+
displayName: report.best.origin.displayName,
|
|
180
|
+
score: report.best.score
|
|
181
|
+
} : null,
|
|
182
|
+
weights: report.weights,
|
|
183
|
+
dimensions: report.dimensions,
|
|
184
|
+
locations: report.locations.map((location) => ({
|
|
185
|
+
name: location.origin.name,
|
|
186
|
+
displayName: location.origin.displayName,
|
|
187
|
+
lat: location.origin.location.lat,
|
|
188
|
+
lng: location.origin.location.lng,
|
|
189
|
+
score: location.score,
|
|
190
|
+
confidence: location.confidence,
|
|
191
|
+
missing: location.missing,
|
|
192
|
+
breakdown: location.breakdown
|
|
193
|
+
}))
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/index.ts
|
|
198
|
+
var VERSION = "1.0.0";
|
|
199
|
+
function jsonResult(data) {
|
|
200
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
201
|
+
}
|
|
202
|
+
function errorResult(error) {
|
|
203
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
204
|
+
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
205
|
+
}
|
|
206
|
+
var server = new McpServer({ name: "proximap", version: VERSION });
|
|
207
|
+
server.registerTool(
|
|
208
|
+
"find_nearby_amenities",
|
|
209
|
+
{
|
|
210
|
+
title: "Find nearby amenities",
|
|
211
|
+
description: 'Find amenities, utilities, and points of interest near a place, ranked by distance. Accepts a place name, an address, or a "lat,lng" string. Powered by OpenStreetMap.',
|
|
212
|
+
inputSchema: {
|
|
213
|
+
query: z.string().describe('Place name, address, or "lat,lng" coordinates'),
|
|
214
|
+
radiusMeters: z.number().positive().optional().describe("Search radius in metres (default 1000)"),
|
|
215
|
+
categories: z.array(z.string()).optional().describe("Restrict to categories or terms, e.g. coffee, pharmacy, petrol"),
|
|
216
|
+
filters: z.object({
|
|
217
|
+
diet: z.union([z.string(), z.array(z.string())]).optional(),
|
|
218
|
+
cuisine: z.union([z.string(), z.array(z.string())]).optional(),
|
|
219
|
+
payment: z.union([z.string(), z.array(z.string())]).optional(),
|
|
220
|
+
internetAccess: z.boolean().optional(),
|
|
221
|
+
outdoorSeating: z.boolean().optional(),
|
|
222
|
+
takeaway: z.boolean().optional(),
|
|
223
|
+
delivery: z.boolean().optional(),
|
|
224
|
+
wheelchair: z.union([z.string(), z.array(z.string())]).optional()
|
|
225
|
+
}).optional().describe("Facet filters: diet/cuisine/payment, wifi, takeaway, wheelchair, etc."),
|
|
226
|
+
accessible: z.boolean().optional().describe("Rank step-free / wheelchair-accessible places first"),
|
|
227
|
+
open: z.string().optional().describe(
|
|
228
|
+
'Keep only places open at this time: "now", or an ISO datetime like "2026-06-20T21:00". Unknown-hours places are kept and labelled.'
|
|
229
|
+
),
|
|
230
|
+
rankBy: z.enum(["distance", "travelTime"]).optional().describe("Order by straight-line distance (default) or road-network travel time"),
|
|
231
|
+
mode: z.enum(["walk", "bike", "drive"]).optional().describe("Travel mode for rankBy=travelTime (default walk)"),
|
|
232
|
+
explain: z.boolean().optional().describe("Add a short ranking reason / summary to each result"),
|
|
233
|
+
concise: z.boolean().optional().describe("Return a slim, high-signal payload (with a one-line summary) for token economy"),
|
|
234
|
+
limit: z.number().int().positive().optional().describe("Maximum number of results (default 30)"),
|
|
235
|
+
language: z.string().optional().describe('Preferred language for names, e.g. "en"')
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
async ({
|
|
239
|
+
query,
|
|
240
|
+
radiusMeters,
|
|
241
|
+
categories,
|
|
242
|
+
filters,
|
|
243
|
+
accessible,
|
|
244
|
+
open,
|
|
245
|
+
rankBy,
|
|
246
|
+
mode,
|
|
247
|
+
explain,
|
|
248
|
+
concise,
|
|
249
|
+
limit,
|
|
250
|
+
language
|
|
251
|
+
}) => {
|
|
252
|
+
try {
|
|
253
|
+
const openOption = open === "now" ? "now" : open ? { at: open } : void 0;
|
|
254
|
+
const explainOn = Boolean(explain || concise);
|
|
255
|
+
const result = await findNearbyAmenities(query, {
|
|
256
|
+
...radiusMeters ? { radiusMeters } : {},
|
|
257
|
+
...categories ? { categories } : {},
|
|
258
|
+
...filters ? { filters } : {},
|
|
259
|
+
...accessible ? { accessible } : {},
|
|
260
|
+
...openOption ? { open: openOption } : {},
|
|
261
|
+
// Travel-time ranking uses the key-free public Valhalla engine.
|
|
262
|
+
...rankBy === "travelTime" ? {
|
|
263
|
+
rankBy: "travelTime",
|
|
264
|
+
routing: new ValhallaRoutingProvider(),
|
|
265
|
+
...mode ? { mode } : {}
|
|
266
|
+
} : {},
|
|
267
|
+
...explainOn ? { explain: true } : {},
|
|
268
|
+
...limit ? { limit } : {},
|
|
269
|
+
...language ? { language } : {}
|
|
270
|
+
});
|
|
271
|
+
return jsonResult(toNearbyPayload(result, { concise: Boolean(concise) }));
|
|
272
|
+
} catch (error) {
|
|
273
|
+
return errorResult(error);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
server.registerTool(
|
|
278
|
+
"geocode",
|
|
279
|
+
{
|
|
280
|
+
title: "Geocode a place",
|
|
281
|
+
description: 'Resolve a place name or address to coordinates using OpenStreetMap. Returns the best guess plus ranked candidates, and an `ambiguous` flag when several distinct places share the name (e.g. the many "Springfield"s) \u2014 present the candidates instead of guessing.',
|
|
282
|
+
inputSchema: {
|
|
283
|
+
query: z.string().describe("Place name or address to look up"),
|
|
284
|
+
limit: z.number().int().positive().optional().describe("Maximum candidates (default 5)"),
|
|
285
|
+
language: z.string().optional().describe('Preferred language, e.g. "en"')
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
async ({ query, limit, language }) => {
|
|
289
|
+
try {
|
|
290
|
+
const result = await disambiguateLocation(query, {
|
|
291
|
+
limit: limit ?? 5,
|
|
292
|
+
...language ? { language } : {}
|
|
293
|
+
});
|
|
294
|
+
return jsonResult(toDisambiguationPayload(result));
|
|
295
|
+
} catch (error) {
|
|
296
|
+
return errorResult(error);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
server.registerTool(
|
|
301
|
+
"list_categories",
|
|
302
|
+
{
|
|
303
|
+
title: "List categories",
|
|
304
|
+
description: "List the category terms proximap understands, for use with find_nearby_amenities. Returns each canonical term and its top-level category.",
|
|
305
|
+
inputSchema: {}
|
|
306
|
+
},
|
|
307
|
+
async () => jsonResult(categoryVocabulary())
|
|
308
|
+
);
|
|
309
|
+
server.registerTool(
|
|
310
|
+
"detect_amenity_gaps",
|
|
311
|
+
{
|
|
312
|
+
title: "Detect amenity gaps",
|
|
313
|
+
description: 'Report which everyday amenities are missing or far from a place \u2014 a "what is absent" check. Absence is framed as "not found in OSM within the threshold", not asserted as ground truth.',
|
|
314
|
+
inputSchema: {
|
|
315
|
+
query: z.string().describe('Place name, address, or "lat,lng" coordinates'),
|
|
316
|
+
categories: z.array(z.string()).optional().describe("Category terms to check (defaults to everyday needs)"),
|
|
317
|
+
searchRadiusMeters: z.number().positive().optional().describe("How far to search for the nearest match (default 5000)"),
|
|
318
|
+
thresholdMeters: z.number().positive().optional().describe("Distance beyond which a category is a gap (default 1500)"),
|
|
319
|
+
language: z.string().optional().describe("Preferred language for names")
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
async ({ query, categories, searchRadiusMeters, thresholdMeters, language }) => {
|
|
323
|
+
try {
|
|
324
|
+
const report = await detectGaps(query, {
|
|
325
|
+
...categories ? { categories } : {},
|
|
326
|
+
...searchRadiusMeters ? { searchRadiusMeters } : {},
|
|
327
|
+
...thresholdMeters ? { thresholdMeters } : {},
|
|
328
|
+
...language ? { language } : {}
|
|
329
|
+
});
|
|
330
|
+
return jsonResult(toGapsPayload(report));
|
|
331
|
+
} catch (error) {
|
|
332
|
+
return errorResult(error);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
server.registerTool(
|
|
337
|
+
"walkability_score",
|
|
338
|
+
{
|
|
339
|
+
title: "Walkability score",
|
|
340
|
+
description: "Rate how walkable / well-served a location is on a 0-100 scale, with a transparent per-category breakdown (nearest distance + sub-score), the categories missing nearby, and a confidence note reflecting OSM data density. An open, tunable alternative to proprietary walkability indices.",
|
|
341
|
+
inputSchema: {
|
|
342
|
+
query: z.string().describe('Place name, address, or "lat,lng" coordinates'),
|
|
343
|
+
idealMeters: z.number().positive().optional().describe("Distance still scoring full marks, \u22485-min walk (default 400)"),
|
|
344
|
+
maxMeters: z.number().positive().optional().describe("Distance scoring zero, \u224830-min walk (default 2400)"),
|
|
345
|
+
language: z.string().optional().describe("Preferred language for names")
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
async ({ query, idealMeters, maxMeters, language }) => {
|
|
349
|
+
try {
|
|
350
|
+
const decay = {};
|
|
351
|
+
if (idealMeters) decay.idealMeters = idealMeters;
|
|
352
|
+
if (maxMeters) decay.maxMeters = maxMeters;
|
|
353
|
+
const report = await walkabilityScore(query, {
|
|
354
|
+
...Object.keys(decay).length > 0 ? { decay } : {},
|
|
355
|
+
...language ? { language } : {}
|
|
356
|
+
});
|
|
357
|
+
return jsonResult(toScorePayload(report));
|
|
358
|
+
} catch (error) {
|
|
359
|
+
return errorResult(error);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
);
|
|
363
|
+
server.registerTool(
|
|
364
|
+
"compare_locations",
|
|
365
|
+
{
|
|
366
|
+
title: "Compare locations",
|
|
367
|
+
description: "Compare two or more candidate locations (e.g. places to live) by access to weighted daily-need categories, returning a ranked scorecard with a per-dimension winner and a confidence note. Key-free and OSM-native. Out of scope (not in OSM): transit frequency, school quality, crime, prices.",
|
|
368
|
+
inputSchema: {
|
|
369
|
+
locations: z.array(z.string()).min(2).describe('Two or more place names, addresses, or "lat,lng" strings'),
|
|
370
|
+
weights: z.record(z.string(), z.number().positive()).optional().describe('Category term to weight, e.g. { "food": 2, "transport": 3 }'),
|
|
371
|
+
idealMeters: z.number().positive().optional().describe("Full-marks distance (default 400)"),
|
|
372
|
+
maxMeters: z.number().positive().optional().describe("Zero-score distance (default 2400)"),
|
|
373
|
+
language: z.string().optional().describe("Preferred language for names")
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
async ({ locations, weights, idealMeters, maxMeters, language }) => {
|
|
377
|
+
try {
|
|
378
|
+
const categories = weights ? Object.entries(weights).map(([term, weight]) => ({ term, weight })) : void 0;
|
|
379
|
+
const decay = {};
|
|
380
|
+
if (idealMeters) decay.idealMeters = idealMeters;
|
|
381
|
+
if (maxMeters) decay.maxMeters = maxMeters;
|
|
382
|
+
const report = await compareLocations(locations, {
|
|
383
|
+
...categories ? { categories } : {},
|
|
384
|
+
...Object.keys(decay).length > 0 ? { decay } : {},
|
|
385
|
+
...language ? { language } : {}
|
|
386
|
+
});
|
|
387
|
+
return jsonResult(toComparePayload(report));
|
|
388
|
+
} catch (error) {
|
|
389
|
+
return errorResult(error);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
);
|
|
393
|
+
server.registerTool(
|
|
394
|
+
"reachable_amenities",
|
|
395
|
+
{
|
|
396
|
+
title: "Reachable amenities",
|
|
397
|
+
description: "List the amenities reachable within a time budget (e.g. a 15-minute walk) using a real isochrone where available, falling back to a travel-time threshold. Returns the filtered, time-sorted amenity list (the answer), plus the isochrone polygon. Powered by OpenStreetMap + the key-free Valhalla engine.",
|
|
398
|
+
inputSchema: {
|
|
399
|
+
query: z.string().describe('Place name, address, or "lat,lng" coordinates'),
|
|
400
|
+
within: z.number().positive().describe("Time budget in minutes (e.g. 15)"),
|
|
401
|
+
mode: z.enum(["walk", "bike", "drive"]).optional().describe("Travel mode (default walk)"),
|
|
402
|
+
categories: z.array(z.string()).optional().describe("Restrict to categories or terms, e.g. grocery, pharmacy"),
|
|
403
|
+
language: z.string().optional().describe("Preferred language for names")
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
async ({ query, within, mode, categories, language }) => {
|
|
407
|
+
try {
|
|
408
|
+
const result = await reachableAmenities(query, {
|
|
409
|
+
within,
|
|
410
|
+
routing: new ValhallaRoutingProvider(),
|
|
411
|
+
...mode ? { mode } : {},
|
|
412
|
+
...categories ? { categories } : {},
|
|
413
|
+
...language ? { language } : {}
|
|
414
|
+
});
|
|
415
|
+
return jsonResult(toReachablePayload(result));
|
|
416
|
+
} catch (error) {
|
|
417
|
+
return errorResult(error);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
);
|
|
421
|
+
server.registerTool(
|
|
422
|
+
"plan_errands",
|
|
423
|
+
{
|
|
424
|
+
title: "Plan errands",
|
|
425
|
+
description: 'Plan the shortest trip from an origin that visits one of each requested category (e.g. a pharmacy AND an ATM AND a grocery) \u2014 the Generalized-TSP "pick one per set, then optimize" workflow. Returns the chosen places in visit order with per-leg and total travel. Categories with no candidate nearby are reported as missing, not faked.',
|
|
426
|
+
inputSchema: {
|
|
427
|
+
query: z.string().describe('Starting place name, address, or "lat,lng" coordinates'),
|
|
428
|
+
categories: z.array(z.string()).min(1).describe('Categories to hit one of each, e.g. ["pharmacy", "atm", "grocery"]'),
|
|
429
|
+
mode: z.enum(["walk", "bike", "drive"]).optional().describe("Travel mode (default walk)"),
|
|
430
|
+
end: z.string().optional().describe('Optional fixed end point (place or "lat,lng")'),
|
|
431
|
+
candidatesPerCategory: z.number().int().positive().optional().describe("Nearest candidates considered per category (default 5)"),
|
|
432
|
+
language: z.string().optional().describe("Preferred language for names")
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
async ({ query, categories, mode, end, candidatesPerCategory, language }) => {
|
|
436
|
+
try {
|
|
437
|
+
const plan = await planErrands(query, {
|
|
438
|
+
categories,
|
|
439
|
+
...mode ? { mode } : {},
|
|
440
|
+
...end ? { end } : {},
|
|
441
|
+
...candidatesPerCategory ? { candidatesPerCategory } : {},
|
|
442
|
+
...language ? { language } : {}
|
|
443
|
+
});
|
|
444
|
+
return jsonResult(toErrandsPayload(plan));
|
|
445
|
+
} catch (error) {
|
|
446
|
+
return errorResult(error);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
);
|
|
450
|
+
var transport = new StdioServerTransport();
|
|
451
|
+
await server.connect(transport);
|
|
452
|
+
process.stderr.write(`proximap MCP server ${VERSION} ready (stdio)
|
|
453
|
+
`);
|
|
454
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/payload.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n categoryVocabulary,\n compareLocations,\n detectGaps,\n disambiguateLocation,\n findNearbyAmenities,\n planErrands,\n reachableAmenities,\n ValhallaRoutingProvider,\n walkabilityScore,\n} from '@proximap/core';\nimport { z } from 'zod';\nimport {\n toComparePayload,\n toDisambiguationPayload,\n toErrandsPayload,\n toGapsPayload,\n toNearbyPayload,\n toReachablePayload,\n toScorePayload,\n} from './payload';\n\nconst VERSION = '1.0.0';\n\nfunction jsonResult(data: unknown) {\n return { content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }] };\n}\n\nfunction errorResult(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return { content: [{ type: 'text' as const, text: `Error: ${message}` }], isError: true };\n}\n\nconst server = new McpServer({ name: 'proximap', version: VERSION });\n\nserver.registerTool(\n 'find_nearby_amenities',\n {\n title: 'Find nearby amenities',\n description:\n 'Find amenities, utilities, and points of interest near a place, ranked by distance. ' +\n 'Accepts a place name, an address, or a \"lat,lng\" string. Powered by OpenStreetMap.',\n inputSchema: {\n query: z.string().describe('Place name, address, or \"lat,lng\" coordinates'),\n radiusMeters: z\n .number()\n .positive()\n .optional()\n .describe('Search radius in metres (default 1000)'),\n categories: z\n .array(z.string())\n .optional()\n .describe('Restrict to categories or terms, e.g. coffee, pharmacy, petrol'),\n filters: z\n .object({\n diet: z.union([z.string(), z.array(z.string())]).optional(),\n cuisine: z.union([z.string(), z.array(z.string())]).optional(),\n payment: z.union([z.string(), z.array(z.string())]).optional(),\n internetAccess: z.boolean().optional(),\n outdoorSeating: z.boolean().optional(),\n takeaway: z.boolean().optional(),\n delivery: z.boolean().optional(),\n wheelchair: z.union([z.string(), z.array(z.string())]).optional(),\n })\n .optional()\n .describe('Facet filters: diet/cuisine/payment, wifi, takeaway, wheelchair, etc.'),\n accessible: z\n .boolean()\n .optional()\n .describe('Rank step-free / wheelchair-accessible places first'),\n open: z\n .string()\n .optional()\n .describe(\n 'Keep only places open at this time: \"now\", or an ISO datetime like ' +\n '\"2026-06-20T21:00\". Unknown-hours places are kept and labelled.',\n ),\n rankBy: z\n .enum(['distance', 'travelTime'])\n .optional()\n .describe('Order by straight-line distance (default) or road-network travel time'),\n mode: z\n .enum(['walk', 'bike', 'drive'])\n .optional()\n .describe('Travel mode for rankBy=travelTime (default walk)'),\n explain: z\n .boolean()\n .optional()\n .describe('Add a short ranking reason / summary to each result'),\n concise: z\n .boolean()\n .optional()\n .describe('Return a slim, high-signal payload (with a one-line summary) for token economy'),\n limit: z\n .number()\n .int()\n .positive()\n .optional()\n .describe('Maximum number of results (default 30)'),\n language: z.string().optional().describe('Preferred language for names, e.g. \"en\"'),\n },\n },\n async ({\n query,\n radiusMeters,\n categories,\n filters,\n accessible,\n open,\n rankBy,\n mode,\n explain,\n concise,\n limit,\n language,\n }) => {\n try {\n const openOption = open === 'now' ? 'now' : open ? { at: open } : undefined;\n // Concise mode needs the per-result summary, so it implies explain.\n const explainOn = Boolean(explain || concise);\n const result = await findNearbyAmenities(query, {\n ...(radiusMeters ? { radiusMeters } : {}),\n ...(categories ? { categories } : {}),\n ...(filters ? { filters } : {}),\n ...(accessible ? { accessible } : {}),\n ...(openOption ? { open: openOption } : {}),\n // Travel-time ranking uses the key-free public Valhalla engine.\n ...(rankBy === 'travelTime'\n ? {\n rankBy: 'travelTime' as const,\n routing: new ValhallaRoutingProvider(),\n ...(mode ? { mode } : {}),\n }\n : {}),\n ...(explainOn ? { explain: true } : {}),\n ...(limit ? { limit } : {}),\n ...(language ? { language } : {}),\n });\n return jsonResult(toNearbyPayload(result, { concise: Boolean(concise) }));\n } catch (error) {\n return errorResult(error);\n }\n },\n);\n\nserver.registerTool(\n 'geocode',\n {\n title: 'Geocode a place',\n description:\n 'Resolve a place name or address to coordinates using OpenStreetMap. Returns the best ' +\n 'guess plus ranked candidates, and an `ambiguous` flag when several distinct places share ' +\n 'the name (e.g. the many \"Springfield\"s) — present the candidates instead of guessing.',\n inputSchema: {\n query: z.string().describe('Place name or address to look up'),\n limit: z.number().int().positive().optional().describe('Maximum candidates (default 5)'),\n language: z.string().optional().describe('Preferred language, e.g. \"en\"'),\n },\n },\n async ({ query, limit, language }) => {\n try {\n const result = await disambiguateLocation(query, {\n limit: limit ?? 5,\n ...(language ? { language } : {}),\n });\n return jsonResult(toDisambiguationPayload(result));\n } catch (error) {\n return errorResult(error);\n }\n },\n);\n\nserver.registerTool(\n 'list_categories',\n {\n title: 'List categories',\n description:\n 'List the category terms proximap understands, for use with find_nearby_amenities. ' +\n 'Returns each canonical term and its top-level category.',\n inputSchema: {},\n },\n async () => jsonResult(categoryVocabulary()),\n);\n\nserver.registerTool(\n 'detect_amenity_gaps',\n {\n title: 'Detect amenity gaps',\n description:\n 'Report which everyday amenities are missing or far from a place — a \"what is absent\" check. ' +\n 'Absence is framed as \"not found in OSM within the threshold\", not asserted as ground truth.',\n inputSchema: {\n query: z.string().describe('Place name, address, or \"lat,lng\" coordinates'),\n categories: z\n .array(z.string())\n .optional()\n .describe('Category terms to check (defaults to everyday needs)'),\n searchRadiusMeters: z\n .number()\n .positive()\n .optional()\n .describe('How far to search for the nearest match (default 5000)'),\n thresholdMeters: z\n .number()\n .positive()\n .optional()\n .describe('Distance beyond which a category is a gap (default 1500)'),\n language: z.string().optional().describe('Preferred language for names'),\n },\n },\n async ({ query, categories, searchRadiusMeters, thresholdMeters, language }) => {\n try {\n const report = await detectGaps(query, {\n ...(categories ? { categories } : {}),\n ...(searchRadiusMeters ? { searchRadiusMeters } : {}),\n ...(thresholdMeters ? { thresholdMeters } : {}),\n ...(language ? { language } : {}),\n });\n return jsonResult(toGapsPayload(report));\n } catch (error) {\n return errorResult(error);\n }\n },\n);\n\nserver.registerTool(\n 'walkability_score',\n {\n title: 'Walkability score',\n description:\n 'Rate how walkable / well-served a location is on a 0-100 scale, with a transparent ' +\n 'per-category breakdown (nearest distance + sub-score), the categories missing nearby, ' +\n 'and a confidence note reflecting OSM data density. An open, tunable alternative to ' +\n 'proprietary walkability indices.',\n inputSchema: {\n query: z.string().describe('Place name, address, or \"lat,lng\" coordinates'),\n idealMeters: z\n .number()\n .positive()\n .optional()\n .describe('Distance still scoring full marks, ≈5-min walk (default 400)'),\n maxMeters: z\n .number()\n .positive()\n .optional()\n .describe('Distance scoring zero, ≈30-min walk (default 2400)'),\n language: z.string().optional().describe('Preferred language for names'),\n },\n },\n async ({ query, idealMeters, maxMeters, language }) => {\n try {\n const decay: { idealMeters?: number; maxMeters?: number } = {};\n if (idealMeters) decay.idealMeters = idealMeters;\n if (maxMeters) decay.maxMeters = maxMeters;\n const report = await walkabilityScore(query, {\n ...(Object.keys(decay).length > 0 ? { decay } : {}),\n ...(language ? { language } : {}),\n });\n return jsonResult(toScorePayload(report));\n } catch (error) {\n return errorResult(error);\n }\n },\n);\n\nserver.registerTool(\n 'compare_locations',\n {\n title: 'Compare locations',\n description:\n 'Compare two or more candidate locations (e.g. places to live) by access to weighted ' +\n 'daily-need categories, returning a ranked scorecard with a per-dimension winner and a ' +\n 'confidence note. Key-free and OSM-native. Out of scope (not in OSM): transit frequency, ' +\n 'school quality, crime, prices.',\n inputSchema: {\n locations: z\n .array(z.string())\n .min(2)\n .describe('Two or more place names, addresses, or \"lat,lng\" strings'),\n weights: z\n .record(z.string(), z.number().positive())\n .optional()\n .describe('Category term to weight, e.g. { \"food\": 2, \"transport\": 3 }'),\n idealMeters: z.number().positive().optional().describe('Full-marks distance (default 400)'),\n maxMeters: z.number().positive().optional().describe('Zero-score distance (default 2400)'),\n language: z.string().optional().describe('Preferred language for names'),\n },\n },\n async ({ locations, weights, idealMeters, maxMeters, language }) => {\n try {\n const categories = weights\n ? Object.entries(weights).map(([term, weight]) => ({ term, weight }))\n : undefined;\n const decay: { idealMeters?: number; maxMeters?: number } = {};\n if (idealMeters) decay.idealMeters = idealMeters;\n if (maxMeters) decay.maxMeters = maxMeters;\n const report = await compareLocations(locations, {\n ...(categories ? { categories } : {}),\n ...(Object.keys(decay).length > 0 ? { decay } : {}),\n ...(language ? { language } : {}),\n });\n return jsonResult(toComparePayload(report));\n } catch (error) {\n return errorResult(error);\n }\n },\n);\n\nserver.registerTool(\n 'reachable_amenities',\n {\n title: 'Reachable amenities',\n description:\n 'List the amenities reachable within a time budget (e.g. a 15-minute walk) using a real ' +\n 'isochrone where available, falling back to a travel-time threshold. Returns the filtered, ' +\n 'time-sorted amenity list (the answer), plus the isochrone polygon. Powered by OpenStreetMap ' +\n '+ the key-free Valhalla engine.',\n inputSchema: {\n query: z.string().describe('Place name, address, or \"lat,lng\" coordinates'),\n within: z.number().positive().describe('Time budget in minutes (e.g. 15)'),\n mode: z.enum(['walk', 'bike', 'drive']).optional().describe('Travel mode (default walk)'),\n categories: z\n .array(z.string())\n .optional()\n .describe('Restrict to categories or terms, e.g. grocery, pharmacy'),\n language: z.string().optional().describe('Preferred language for names'),\n },\n },\n async ({ query, within, mode, categories, language }) => {\n try {\n const result = await reachableAmenities(query, {\n within,\n routing: new ValhallaRoutingProvider(),\n ...(mode ? { mode } : {}),\n ...(categories ? { categories } : {}),\n ...(language ? { language } : {}),\n });\n return jsonResult(toReachablePayload(result));\n } catch (error) {\n return errorResult(error);\n }\n },\n);\n\nserver.registerTool(\n 'plan_errands',\n {\n title: 'Plan errands',\n description:\n 'Plan the shortest trip from an origin that visits one of each requested category ' +\n '(e.g. a pharmacy AND an ATM AND a grocery) — the Generalized-TSP \"pick one per set, then ' +\n 'optimize\" workflow. Returns the chosen places in visit order with per-leg and total travel. ' +\n 'Categories with no candidate nearby are reported as missing, not faked.',\n inputSchema: {\n query: z.string().describe('Starting place name, address, or \"lat,lng\" coordinates'),\n categories: z\n .array(z.string())\n .min(1)\n .describe('Categories to hit one of each, e.g. [\"pharmacy\", \"atm\", \"grocery\"]'),\n mode: z.enum(['walk', 'bike', 'drive']).optional().describe('Travel mode (default walk)'),\n end: z.string().optional().describe('Optional fixed end point (place or \"lat,lng\")'),\n candidatesPerCategory: z\n .number()\n .int()\n .positive()\n .optional()\n .describe('Nearest candidates considered per category (default 5)'),\n language: z.string().optional().describe('Preferred language for names'),\n },\n },\n async ({ query, categories, mode, end, candidatesPerCategory, language }) => {\n try {\n const plan = await planErrands(query, {\n categories,\n ...(mode ? { mode } : {}),\n ...(end ? { end } : {}),\n ...(candidatesPerCategory ? { candidatesPerCategory } : {}),\n ...(language ? { language } : {}),\n });\n return jsonResult(toErrandsPayload(plan));\n } catch (error) {\n return errorResult(error);\n }\n },\n);\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\nprocess.stderr.write(`proximap MCP server ${VERSION} ready (stdio)\\n`);\n","import type {\n ComparisonReport,\n Disambiguation,\n ErrandPlan,\n GapReport,\n NearbyResult,\n Place,\n ReachableResult,\n WalkabilityReport,\n} from '@proximap/core';\n\n/**\n * Flatten a nearby-search result for agents. `concise` returns a slim, high-signal\n * payload (rank/name/category/metric/coords + a one-line summary) — materially\n * smaller than the detailed shape, for token economy.\n */\nexport function toNearbyPayload(result: NearbyResult, options: { concise?: boolean } = {}) {\n const { origin, results, total, routing } = result;\n const head = {\n origin: {\n name: origin.name,\n displayName: origin.displayName,\n lat: origin.location.lat,\n lng: origin.location.lng,\n },\n total,\n count: results.length,\n routing: routing ?? null,\n };\n\n if (options.concise) {\n return {\n ...head,\n results: results.map((poi) => ({\n rank: poi.rank,\n name: poi.name ?? null,\n category: poi.category,\n ...(poi.travelSeconds !== undefined\n ? { travelSeconds: poi.travelSeconds }\n : { distanceMeters: Math.round(poi.distanceMeters) }),\n lat: poi.location.lat,\n lng: poi.location.lng,\n ...(poi.rankingReason ? { summary: poi.rankingReason } : {}),\n })),\n };\n }\n\n return {\n ...head,\n results: results.map((poi) => ({\n rank: poi.rank,\n name: poi.name ?? null,\n category: poi.category,\n kind: poi.kind ?? null,\n distanceMeters: Math.round(poi.distanceMeters),\n travelSeconds: poi.travelSeconds ?? null,\n travelMeters: poi.travelMeters ?? null,\n lat: poi.location.lat,\n lng: poi.location.lng,\n osmId: poi.id,\n completeness: poi.completeness ?? null,\n lastVerified: poi.lastVerified ?? null,\n wheelchair: poi.tags.wheelchair ?? null,\n wheelchairDescription: poi.tags['wheelchair:description'] ?? null,\n openState: poi.openState ?? null,\n nextChange: poi.nextChange ?? null,\n rankingReason: poi.rankingReason ?? null,\n })),\n };\n}\n\nconst compactPlace = (place: Place) => ({\n name: place.name,\n displayName: place.displayName,\n lat: place.location.lat,\n lng: place.location.lng,\n kind: place.kind ?? null,\n});\n\n/** Flatten a geocoding disambiguation: best guess + ranked candidates + an ambiguity flag. */\nexport function toDisambiguationPayload(result: Disambiguation) {\n return {\n query: result.query,\n ambiguous: result.ambiguous,\n best: result.best ? compactPlace(result.best) : null,\n candidates: result.candidates.map(compactPlace),\n };\n}\n\n/** Flatten an errand plan for agent consumption. */\nexport function toErrandsPayload(plan: ErrandPlan) {\n return {\n origin: {\n name: plan.origin.name,\n displayName: plan.origin.displayName,\n lat: plan.origin.location.lat,\n lng: plan.origin.location.lng,\n },\n end: plan.end\n ? {\n displayName: plan.end.displayName,\n lat: plan.end.location.lat,\n lng: plan.end.location.lng,\n }\n : null,\n mode: plan.mode,\n totalSeconds: plan.totalSeconds,\n totalMeters: plan.totalMeters,\n missing: plan.missing,\n stops: plan.stops.map((stop) => ({\n category: stop.category,\n name: stop.poi.name ?? null,\n kind: stop.poi.kind ?? null,\n osmId: stop.poi.id,\n lat: stop.poi.location.lat,\n lng: stop.poi.location.lng,\n legSeconds: stop.legSeconds,\n legMeters: stop.legMeters,\n })),\n };\n}\n\n/** Flatten an isochrone-reachability result for agent consumption. */\nexport function toReachablePayload(result: ReachableResult) {\n return {\n origin: {\n name: result.origin.name,\n displayName: result.origin.displayName,\n lat: result.origin.location.lat,\n lng: result.origin.location.lng,\n },\n withinMinutes: result.withinMinutes,\n mode: result.mode,\n isochrone: result.isochrone, // [lng, lat] ring, or null\n count: result.count,\n results: result.results.map((poi) => ({\n rank: poi.rank,\n name: poi.name ?? null,\n category: poi.category,\n kind: poi.kind ?? null,\n travelSeconds: poi.travelSeconds ?? null,\n travelMeters: poi.travelMeters ?? null,\n distanceMeters: Math.round(poi.distanceMeters),\n lat: poi.location.lat,\n lng: poi.location.lng,\n osmId: poi.id,\n })),\n };\n}\n\n/** Map geocoding candidates to compact records. */\nexport function toGeocodePayload(places: Place[]) {\n return places.map((place) => ({\n name: place.name,\n displayName: place.displayName,\n lat: place.location.lat,\n lng: place.location.lng,\n kind: place.kind ?? null,\n }));\n}\n\n/** Flatten an amenity-gap report for agent consumption. */\nexport function toGapsPayload(report: GapReport) {\n return {\n origin: {\n name: report.origin.name,\n displayName: report.origin.displayName,\n lat: report.origin.location.lat,\n lng: report.origin.location.lng,\n },\n searchRadiusMeters: report.searchRadiusMeters,\n thresholdMeters: report.thresholdMeters,\n gaps: report.gaps,\n missing: report.missing,\n };\n}\n\n/** Flatten a walkability report for agent consumption. */\nexport function toScorePayload(report: WalkabilityReport) {\n return {\n origin: {\n name: report.origin.name,\n displayName: report.origin.displayName,\n lat: report.origin.location.lat,\n lng: report.origin.location.lng,\n },\n score: report.score,\n confidence: report.confidence,\n decay: report.decay,\n breakdown: report.breakdown,\n missing: report.missing,\n };\n}\n\n/** Flatten a location-comparison report for agent consumption. */\nexport function toComparePayload(report: ComparisonReport) {\n return {\n ranked: report.ranked.map((entry) => ({\n index: entry.index,\n displayName: entry.origin.displayName,\n score: entry.score,\n })),\n best: report.best\n ? {\n index: report.best.index,\n displayName: report.best.origin.displayName,\n score: report.best.score,\n }\n : null,\n weights: report.weights,\n dimensions: report.dimensions,\n locations: report.locations.map((location) => ({\n name: location.origin.name,\n displayName: location.origin.displayName,\n lat: location.origin.location.lat,\n lng: location.origin.location.lng,\n score: location.score,\n confidence: location.confidence,\n missing: location.missing,\n breakdown: location.breakdown,\n })),\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@proximap/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Model Context Protocol server for proximap — geospatial tools for AI agents and Claude.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"model-context-protocol",
|
|
8
|
+
"claude",
|
|
9
|
+
"ai",
|
|
10
|
+
"maps",
|
|
11
|
+
"geocoding",
|
|
12
|
+
"amenities"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Ameya Borkar <ameyaborkar17@gmail.com>",
|
|
16
|
+
"homepage": "https://github.com/AmeyaBorkar/proximap#readme",
|
|
17
|
+
"bugs": "https://github.com/AmeyaBorkar/proximap/issues",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/AmeyaBorkar/proximap.git",
|
|
21
|
+
"directory": "packages/mcp"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"bin": {
|
|
25
|
+
"proximap-mcp": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"prepublishOnly": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
45
|
+
"@proximap/core": "^1.0.0",
|
|
46
|
+
"zod": "^4.4.3"
|
|
47
|
+
}
|
|
48
|
+
}
|