@showmeonmap/mcp 0.6.0 → 0.7.1
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/README.md +12 -4
- package/dist/fetchNoRedirect.js +16 -0
- package/dist/generalizeGeometry.js +232 -0
- package/dist/http.js +2 -0
- package/dist/index.js +2 -1
- package/dist/nextActions.js +340 -0
- package/dist/serverInstructions.js +34 -0
- package/dist/sessionStoreD1.js +28 -8
- package/dist/tools.js +774 -107
- package/dist/version.js +3 -0
- package/dist/viewer.js +117 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,8 @@ The server speaks standard MCP/stdio; it's a thin proxy to the ShowMeOnMap HTTP
|
|
|
37
37
|
}
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
**In Claude, no key at all:** add `https://showmeonmap.com/api/mcp` as a custom connector (Claude Code: `claude mcp add --transport http showmeonmap https://showmeonmap.com/api/mcp`, then `/mcp`). Claude signs you in with OAuth the first time it needs a map and bills your credits the same way; disconnect from the account menu → API keys → Connected apps.
|
|
41
|
+
|
|
40
42
|
The hosted endpoint speaks streamable HTTP (stateless), is rate-limited to 30 requests/min per key, and works anywhere remote MCP servers do — Claude custom connectors, the OpenAI Responses API (`server_url`), Gemini managed agents (`mcp_server`), Cursor, Copilot Studio, Bedrock.
|
|
41
43
|
|
|
42
44
|
---
|
|
@@ -90,13 +92,18 @@ Ask Claude Desktop things like:
|
|
|
90
92
|
|
|
91
93
|
---
|
|
92
94
|
|
|
93
|
-
## Tool surface (
|
|
95
|
+
## Tool surface (29 tools)
|
|
94
96
|
|
|
95
97
|
**Workspace lifecycle (6)**: `build_map`, `mutate_map`, `get_state`, `export_workspace`, `undo`, `redo`.
|
|
96
98
|
**Direct ops (14)**: `add_layer`, `filter_layer`, `select_within`, `focus_area`, `query_features`, `remove_layer`, `clear_filters`, `rename_layer`, `restyle_layer`, `set_camera`, `set_time`, `tag`, `add_annotation`, `remove_annotation`.
|
|
97
99
|
**Multi-source analysis (3)**: `add_fusion_layer`, `correlate_layers`, `run_analysis`.
|
|
98
100
|
**Export (2)**: `export_layer_data`, `export_image`.
|
|
99
101
|
**Feedback channel (1)**: `report_blocker`.
|
|
102
|
+
**Saved workflows (3)**: `save_workflow`, `list_workflows`, `run_workflow`.
|
|
103
|
+
|
|
104
|
+
**0.7.1**: `build_map`'s `next_actions` come from the map itself (real data filters and the plan's own follow-ups, not generic suggestions); a Cloudflare error page from the backend reaches the model as a typed message with its code and Ray ID instead of raw HTML; shared maps keep the fields they paint and say when they hold a sample of the features.
|
|
105
|
+
|
|
106
|
+
**0.7.0** (everything merged since 0.6.0): the three workflow tools (with `batch` runs over a value list); `run_analysis` adds spatial join, merge/append, near and dissolve; `rationale` is optional on every tool; no tuple schemas (Claude dropped `set_camera` / `add_annotation`); fact-only tool descriptions; `export_layer_data` works on a saved map; share links store the features the map shows after `filter_layer` and its counts follow the filter; the server reports its real version; and an npm install no longer writes rationale logs into your `node_modules`.
|
|
100
107
|
|
|
101
108
|
`focus_area` (0.4.0) refines an existing map to a **named** area — no coordinates required. Pass a `layerId` plus an `areaRef` (`{kind:'corridor', street, from?, to?}`, `{kind:'place', name}`, or `{kind:'aroundIntersection', street, cross}`) and the server resolves the real geometry from OpenStreetMap, then applies one of three `mode`s: `filter` (hide non-matching), `select` (extract to a new layer), or `clip` (geometrically trim — lines are split at the boundary). The reply carries a grounded `contextAnswer` such as `"kept 37 of 150 features"`. A resolver miss returns a `clarification` with real nearby candidates rather than a guessed location.
|
|
102
109
|
|
|
@@ -113,14 +120,15 @@ Every tool returns a **three-channel envelope**:
|
|
|
113
120
|
"shareable_url": "http://localhost:3000/m/…",
|
|
114
121
|
"metadata": { "title": "…", "layer_count": 2, "last_updated": "…" },
|
|
115
122
|
"next_actions": [
|
|
116
|
-
{ "label": "
|
|
123
|
+
{ "label": "Show only Earthquakes with magnitude ≥ 5.3 (5 of 10 features)", "tool": "filter_layer",
|
|
124
|
+
"argsTemplate": { "layerId": "smo_usgs_earthquakes", "predicate": { "op": "gte", "field": "magnitude", "value": 5.3 } } }
|
|
117
125
|
],
|
|
118
126
|
"outOfScope": { "reason": "…" },
|
|
119
127
|
"clarification": { "question": "…", "candidates": [ … ] }
|
|
120
128
|
}
|
|
121
129
|
```
|
|
122
130
|
|
|
123
|
-
The calling agent reasons over `structured`, surfaces `shareable_url` to its end user for interactive drill-down, and uses `next_actions` as typed hints for follow-up tool calls.
|
|
131
|
+
The calling agent reasons over `structured`, surfaces `shareable_url` to its end user for interactive drill-down, and uses `next_actions` as typed hints for follow-up tool calls: each hint's `argsTemplate` is exactly the args (minus `map_id`) of the named `tool`. Hints come from the map's own data, followups and current state; an empty list means nothing useful to suggest.
|
|
124
132
|
|
|
125
133
|
For the full Op catalog and predicate grammar that the V2 tools accept, fetch the MCP resource at `showmeonmap://docs/workspace-ops-spec` — agents should not guess these shapes.
|
|
126
134
|
|
|
@@ -162,7 +170,7 @@ Spawns a fresh server process, exercises `initialize` → `tools/list` → every
|
|
|
162
170
|
- **In-memory session cache.** `map_id` → workspace mapping lives in the server process; restarts lose state. Phase I-3 (in the parent repo's plan) wires D1 persistence so `shareable_url` actually resolves across sessions.
|
|
163
171
|
- **No preview PNG.** The envelope's `shareable_url` points at the app's `/m/<id>` viewer, but workspaces aren't yet persisted in the gallery. Until I-3, share the raw structured response.
|
|
164
172
|
- **stdio bin + hosted remote endpoint.** This package's bin is stdio; remote hosts use `https://showmeonmap.com/api/mcp` (streamable HTTP, stateless). The standalone Node HTTP entry (`packages/mcp-server/src/http.ts`) remains for self-hosting a checkout.
|
|
165
|
-
- **Auth is per-user API
|
|
173
|
+
- **Auth is a per-user bearer**: an API key (`smo_live_…`, minted at showmeonmap.com) or, on the hosted endpoint, an OAuth access token from Claude's sign-in. Either travels as a bearer on every backend call and bills its owner.
|
|
166
174
|
|
|
167
175
|
---
|
|
168
176
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fetch() that refuses to follow redirects — portable to Cloudflare Workers,
|
|
3
|
+
* where this package also runs (the hosted /api/mcp route). The Workers
|
|
4
|
+
* runtime rejects `redirect: 'error'` outright, so every self-POST threw in
|
|
5
|
+
* production (build_map broken after #949, fixed 2026-09-28). Uses
|
|
6
|
+
* `redirect: 'manual'` and refuses any 3xx instead. Mirrors
|
|
7
|
+
* src/lib/http/fetchNoRedirect.ts (this package cannot import from the app).
|
|
8
|
+
*/
|
|
9
|
+
export async function fetchNoRedirect(input, init = {}) {
|
|
10
|
+
const response = await fetch(input, { ...init, redirect: 'manual' });
|
|
11
|
+
if (response.type === 'opaqueredirect' || (response.status >= 300 && response.status < 400)) {
|
|
12
|
+
void response.body?.cancel().catch(() => { });
|
|
13
|
+
throw new Error(`Refused to follow a redirect (HTTP ${response.status}) from ${new URL(input).pathname}`);
|
|
14
|
+
}
|
|
15
|
+
return response;
|
|
16
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generalize oversize line/polygon layers before a map enters the MCP session
|
|
3
|
+
* store. Design: docs/superpowers/specs/2026-10-06-mcp-geometry-generalization-design.md
|
|
4
|
+
*
|
|
5
|
+
* The global choropleth handlers inline 10m world-atlas geometry — EU-27 is
|
|
6
|
+
* 1.8 MB, the world 21 MB — and the hosted store keeps the whole workspace in
|
|
7
|
+
* ONE `mcp_sessions.workspace` cell, which D1 caps at 2 MB. "Map GDP per
|
|
8
|
+
* capita across Europe" therefore failed with SQLITE_TOOBIG in the first
|
|
9
|
+
* real-host Claude test (2026-10-06), and the EU-27 retry that did fit was
|
|
10
|
+
* over the card's ~850 KB budget ("too large to show here").
|
|
11
|
+
*
|
|
12
|
+
* Rules (pinned by __tests__/tools/generalizeGeometry.test.ts):
|
|
13
|
+
* - only a layer whose serialized data exceeds LAYER_BUDGET (600 KB) is touched;
|
|
14
|
+
* - points are never altered;
|
|
15
|
+
* - no feature is dropped and no property value changes — only geometry;
|
|
16
|
+
* - every ring keeps ≥ 4 points and stays closed;
|
|
17
|
+
* - within a feature, parts/holes smaller than 3 × tolerance are dropped,
|
|
18
|
+
* never the feature's largest part;
|
|
19
|
+
* - the first rung of the ladder that fits wins (least simplification);
|
|
20
|
+
* - a generalized layer says so: `_smo_generalized` on its data and a
|
|
21
|
+
* legend subtitle suffix.
|
|
22
|
+
* Dependency-free: this package ships to npm with only the SDK and zod.
|
|
23
|
+
*/
|
|
24
|
+
/** Per-layer target. The card takes ~850 KB of layers per response (deferred
|
|
25
|
+
* layers load separately), and the world 10m choropleth fits at 0.1° (557 KB);
|
|
26
|
+
* 500 KB pushed it to 0.2° (≈ 22 km), visibly coarse. */
|
|
27
|
+
export const LAYER_BUDGET = 600_000;
|
|
28
|
+
export const WORKSPACE_CEILING = 1_800_000;
|
|
29
|
+
/** Douglas–Peucker tolerances in degrees, tried after quantize-only (0). */
|
|
30
|
+
export const TOLERANCES = [0, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2];
|
|
31
|
+
export const SIMPLIFIED_NOTE = 'boundaries simplified for this view';
|
|
32
|
+
function round4(n) {
|
|
33
|
+
return Math.round(n * 1e4) / 1e4;
|
|
34
|
+
}
|
|
35
|
+
/** Iterative Douglas–Peucker on an open or closed polyline. */
|
|
36
|
+
function douglasPeucker(pts, tol) {
|
|
37
|
+
if (pts.length <= 2 || tol <= 0)
|
|
38
|
+
return pts;
|
|
39
|
+
const keep = new Uint8Array(pts.length);
|
|
40
|
+
keep[0] = 1;
|
|
41
|
+
keep[pts.length - 1] = 1;
|
|
42
|
+
const t2 = tol * tol;
|
|
43
|
+
const stack = [[0, pts.length - 1]];
|
|
44
|
+
while (stack.length) {
|
|
45
|
+
const [a, b] = stack.pop();
|
|
46
|
+
const [x1, y1] = pts[a];
|
|
47
|
+
const [x2, y2] = pts[b];
|
|
48
|
+
const dx = x2 - x1;
|
|
49
|
+
const dy = y2 - y1;
|
|
50
|
+
const len2 = dx * dx + dy * dy;
|
|
51
|
+
let maxD = 0;
|
|
52
|
+
let maxI = -1;
|
|
53
|
+
for (let i = a + 1; i < b; i++) {
|
|
54
|
+
const [x, y] = pts[i];
|
|
55
|
+
let t = len2 ? ((x - x1) * dx + (y - y1) * dy) / len2 : 0;
|
|
56
|
+
t = t < 0 ? 0 : t > 1 ? 1 : t;
|
|
57
|
+
const ex = x - x1 - t * dx;
|
|
58
|
+
const ey = y - y1 - t * dy;
|
|
59
|
+
const d = ex * ex + ey * ey;
|
|
60
|
+
if (d > maxD) {
|
|
61
|
+
maxD = d;
|
|
62
|
+
maxI = i;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (maxD > t2 && maxI > 0) {
|
|
66
|
+
keep[maxI] = 1;
|
|
67
|
+
stack.push([a, maxI], [maxI, b]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const out = [];
|
|
71
|
+
for (let i = 0; i < pts.length; i++)
|
|
72
|
+
if (keep[i])
|
|
73
|
+
out.push(pts[i]);
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
function extent(ring) {
|
|
77
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
78
|
+
for (const [x, y] of ring) {
|
|
79
|
+
if (x < minX)
|
|
80
|
+
minX = x;
|
|
81
|
+
if (y < minY)
|
|
82
|
+
minY = y;
|
|
83
|
+
if (x > maxX)
|
|
84
|
+
maxX = x;
|
|
85
|
+
if (y > maxY)
|
|
86
|
+
maxY = y;
|
|
87
|
+
}
|
|
88
|
+
return Math.hypot(maxX - minX, maxY - minY);
|
|
89
|
+
}
|
|
90
|
+
function quantize(pts) {
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const p of pts) {
|
|
93
|
+
const q = [round4(p[0]), round4(p[1]), ...p.slice(2)];
|
|
94
|
+
const prev = out[out.length - 1];
|
|
95
|
+
if (prev && prev[0] === q[0] && prev[1] === q[1])
|
|
96
|
+
continue;
|
|
97
|
+
out.push(q);
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
/** A closed ring with ≥ 4 points — falls back to a minimal closed ring from the original. */
|
|
102
|
+
function simplifyRing(ring, tol) {
|
|
103
|
+
let s = douglasPeucker(quantize(ring), tol);
|
|
104
|
+
if (s.length && (s[0][0] !== s[s.length - 1][0] || s[0][1] !== s[s.length - 1][1]))
|
|
105
|
+
s = [...s, s[0]];
|
|
106
|
+
if (s.length >= 4)
|
|
107
|
+
return s;
|
|
108
|
+
if (ring.length < 4)
|
|
109
|
+
return ring;
|
|
110
|
+
const n = ring.length;
|
|
111
|
+
return [ring[0], ring[Math.floor(n / 3)], ring[Math.floor((2 * n) / 3)], ring[0]];
|
|
112
|
+
}
|
|
113
|
+
function simplifyPolygon(rings, tol, dropBelow) {
|
|
114
|
+
return rings
|
|
115
|
+
.filter((r, i) => i === 0 || dropBelow <= 0 || extent(r) >= dropBelow)
|
|
116
|
+
.map((r) => simplifyRing(r, tol));
|
|
117
|
+
}
|
|
118
|
+
function simplifyLine(line, tol) {
|
|
119
|
+
const s = douglasPeucker(quantize(line), tol);
|
|
120
|
+
return s.length >= 2 ? s : line;
|
|
121
|
+
}
|
|
122
|
+
function simplifyGeometry(g, tol) {
|
|
123
|
+
const dropBelow = 3 * tol;
|
|
124
|
+
const c = g.coordinates;
|
|
125
|
+
switch (g.type) {
|
|
126
|
+
case 'Polygon':
|
|
127
|
+
return { ...g, coordinates: simplifyPolygon(c, tol, dropBelow) };
|
|
128
|
+
case 'MultiPolygon': {
|
|
129
|
+
const polys = c;
|
|
130
|
+
const sizes = polys.map((p) => (p[0] ? extent(p[0]) : 0));
|
|
131
|
+
const largest = sizes.indexOf(Math.max(...sizes));
|
|
132
|
+
const kept = polys.filter((_, i) => i === largest || dropBelow <= 0 || sizes[i] >= dropBelow);
|
|
133
|
+
return { ...g, coordinates: kept.map((p) => simplifyPolygon(p, tol, dropBelow)) };
|
|
134
|
+
}
|
|
135
|
+
case 'LineString':
|
|
136
|
+
return { ...g, coordinates: simplifyLine(c, tol) };
|
|
137
|
+
case 'MultiLineString':
|
|
138
|
+
return { ...g, coordinates: c.map((l) => simplifyLine(l, tol)) };
|
|
139
|
+
default:
|
|
140
|
+
return g; // points and anything else are never altered
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function hasShapeGeometry(fc) {
|
|
144
|
+
return fc.features.some((f) => /Polygon|LineString/.test(f.geometry?.type ?? ''));
|
|
145
|
+
}
|
|
146
|
+
function size(v) {
|
|
147
|
+
return JSON.stringify(v).length;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The least-simplified version of `fc` that fits `budget`, or null when it
|
|
151
|
+
* already fits / has no line or polygon geometry. Returns the last rung when
|
|
152
|
+
* nothing fits — the caller's ceiling check decides what happens then.
|
|
153
|
+
*/
|
|
154
|
+
export function generalizeCollection(fc, budget = LAYER_BUDGET, ladder = TOLERANCES) {
|
|
155
|
+
if (fc._smo_generalized || size(fc) <= budget || !hasShapeGeometry(fc))
|
|
156
|
+
return null;
|
|
157
|
+
let last = fc;
|
|
158
|
+
let lastTol = 0;
|
|
159
|
+
for (const tol of ladder) {
|
|
160
|
+
last = {
|
|
161
|
+
...fc,
|
|
162
|
+
features: fc.features.map((f) => (f.geometry ? { ...f, geometry: simplifyGeometry(f.geometry, tol) } : f)),
|
|
163
|
+
};
|
|
164
|
+
lastTol = tol;
|
|
165
|
+
if (size(last) <= budget)
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
return { data: { ...last, _smo_generalized: { toleranceDeg: lastTol } }, toleranceDeg: lastTol };
|
|
169
|
+
}
|
|
170
|
+
function isInlineCollection(layer) {
|
|
171
|
+
const d = layer.source?.data;
|
|
172
|
+
return !!d && d.type === 'FeatureCollection' && Array.isArray(d.features);
|
|
173
|
+
}
|
|
174
|
+
function withNote(subtitle) {
|
|
175
|
+
return typeof subtitle === 'string' && subtitle.trim()
|
|
176
|
+
? `${subtitle} · ${SIMPLIFIED_NOTE}`
|
|
177
|
+
: SIMPLIFIED_NOTE.charAt(0).toUpperCase() + SIMPLIFIED_NOTE.slice(1);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Walk `root` (a workspace or a mapPlan) and generalize every oversize inline
|
|
181
|
+
* layer. Copy-on-write: unchanged branches are shared, `root` is never mutated.
|
|
182
|
+
*/
|
|
183
|
+
export function generalizeLayers(root, budget = LAYER_BUDGET) {
|
|
184
|
+
const generalized = [];
|
|
185
|
+
const visit = (node) => {
|
|
186
|
+
if (Array.isArray(node)) {
|
|
187
|
+
let changed = false;
|
|
188
|
+
const out = node.map((n) => {
|
|
189
|
+
const v = visit(n);
|
|
190
|
+
if (v !== n)
|
|
191
|
+
changed = true;
|
|
192
|
+
return v;
|
|
193
|
+
});
|
|
194
|
+
return changed ? out : node;
|
|
195
|
+
}
|
|
196
|
+
if (!node || typeof node !== 'object')
|
|
197
|
+
return node;
|
|
198
|
+
const obj = node;
|
|
199
|
+
if (obj.source && isInlineCollection(obj)) {
|
|
200
|
+
const g = generalizeCollection(obj.source.data, budget);
|
|
201
|
+
if (!g)
|
|
202
|
+
return node;
|
|
203
|
+
generalized.push({
|
|
204
|
+
layerId: String(obj.id ?? ''),
|
|
205
|
+
...(typeof obj.title === 'string' ? { title: obj.title } : {}),
|
|
206
|
+
toleranceDeg: g.toleranceDeg,
|
|
207
|
+
});
|
|
208
|
+
return { ...obj, subtitle: withNote(obj.subtitle), source: { ...obj.source, data: g.data } };
|
|
209
|
+
}
|
|
210
|
+
let changed = false;
|
|
211
|
+
const out = {};
|
|
212
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
213
|
+
const nv = visit(v);
|
|
214
|
+
if (nv !== v)
|
|
215
|
+
changed = true;
|
|
216
|
+
out[k] = nv;
|
|
217
|
+
}
|
|
218
|
+
return changed ? out : node;
|
|
219
|
+
};
|
|
220
|
+
return { value: visit(root), generalized };
|
|
221
|
+
}
|
|
222
|
+
/** Approximate on-the-wire size of a stored value, in UTF-8 bytes. */
|
|
223
|
+
export function storedBytes(v) {
|
|
224
|
+
return new TextEncoder().encode(JSON.stringify(v ?? null)).length;
|
|
225
|
+
}
|
|
226
|
+
/** "≈ 500 m" / "≈ 11 km" for a tolerance in degrees (1° ≈ 111 km). */
|
|
227
|
+
export function describeTolerance(deg) {
|
|
228
|
+
if (deg <= 0)
|
|
229
|
+
return '≈ 10 m';
|
|
230
|
+
const m = deg * 111_000;
|
|
231
|
+
return m < 1000 ? `≈ ${Math.round(m / 100) * 100} m` : `≈ ${Math.round(m / 1000)} km`;
|
|
232
|
+
}
|
package/dist/http.js
CHANGED
|
@@ -45,6 +45,7 @@ import { createServer as createHttpServer } from 'node:http';
|
|
|
45
45
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
46
46
|
import { randomUUID } from 'node:crypto';
|
|
47
47
|
import { createServer as createMcpServer } from "./tools.js";
|
|
48
|
+
import { PACKAGE_VERSION } from "./version.js";
|
|
48
49
|
const BASE_URL = process.env.SHOWMEONMAP_BASE_URL ?? 'http://localhost:3000';
|
|
49
50
|
const API_KEY = process.env.SHOWMEONMAP_API_KEY;
|
|
50
51
|
const HTTP_PORT = Number(process.env.MCP_HTTP_PORT ?? 3333);
|
|
@@ -199,6 +200,7 @@ async function handleRequest(req, res) {
|
|
|
199
200
|
baseUrl: BASE_URL,
|
|
200
201
|
apiKey: API_KEY,
|
|
201
202
|
sessionStore: SHARED_SESSION_STORE,
|
|
203
|
+
version: PACKAGE_VERSION,
|
|
202
204
|
});
|
|
203
205
|
await mcpServer.connect(transport);
|
|
204
206
|
await transport.handleRequest(req, res, body);
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
31
31
|
import { createServer } from "./tools.js";
|
|
32
|
+
import { PACKAGE_VERSION } from "./version.js";
|
|
32
33
|
// Defaults to production so `npx @showmeonmap/mcp` works with only an API key.
|
|
33
34
|
// Override with SHOWMEONMAP_BASE_URL=http://localhost:3000 for local dev.
|
|
34
35
|
const BASE_URL = process.env.SHOWMEONMAP_BASE_URL ?? 'https://showmeonmap.com';
|
|
@@ -40,7 +41,7 @@ async function main() {
|
|
|
40
41
|
// HTTP transport opts into D1 separately via http.ts. Callers can override
|
|
41
42
|
// by injecting their own SessionStore into createServer if needed.
|
|
42
43
|
const sessionStore = undefined;
|
|
43
|
-
const server = createServer({ baseUrl: BASE_URL, apiKey: API_KEY, sessionStore });
|
|
44
|
+
const server = createServer({ baseUrl: BASE_URL, apiKey: API_KEY, sessionStore, version: PACKAGE_VERSION });
|
|
44
45
|
const transport = new StdioServerTransport();
|
|
45
46
|
await server.connect(transport);
|
|
46
47
|
process.stderr.write(`[showmeonmap-mcp] stdio ready · base=${BASE_URL}\n`);
|