@ikeytz/maps-mcp 1.0.4 → 1.0.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.5
4
+
5
+ - CLI structure aligned with `@ikeytz/mcp`: `help`/`list` ASCII table (TOOL │ ARGS │ EXAMPLE), `list --global`, `-V`/`version`/`serve`.
6
+ - `--show-mcp-command` curl heading like www; aliases `--show-mcp-befehl` and `--show-mcp-comment`.
7
+ - README: registry block `com.ikeytz/maps` v1.0.1 + npm 1.0.5; LICENSE like www.
8
+
3
9
  ## 1.0.4
4
10
 
5
11
  - Legal tools: get_legal, Urheberrecht/Copyright/Impressum (maps text), AGB/Nutzung/Datenschutz/Widerruf (www URLs).
package/LICENSE CHANGED
@@ -1,5 +1,27 @@
1
- Copyright (c) Mahmud Reza Kashani / ikeytz.
1
+ ikeytz maps MCP CLI License (LicenseRef-ikeytz-terms)
2
2
 
3
- LicenseRef-ikeytz-terms nicht OSI, kein Training.
4
- Texte und Karten-Darstellung: siehe https://www.ikeytz.com/urheberrecht
5
- und https://maps.ikeytz.com/llms-urheberrecht.txt
3
+ Copyright (c) Mahmud Reza Kashani / ikeytz – Schlüsseldienst Ludwigsburg
4
+
5
+ This npm package (@ikeytz/maps-mcp) is proprietary — not open source (not MIT/Apache).
6
+ This is not @ikeytz/mcp (www).
7
+
8
+ You MAY:
9
+ - install and run this CLI (global, npx, or as an MCP stdio proxy in Cursor/Claude)
10
+ - call the public read-only MCP server at https://maps.ikeytz.com/mcp
11
+ - use responses in AI assistants with attribution (see below)
12
+
13
+ You MAY NOT:
14
+ - use ikeytz maps content for AI training or fine-tuning (ai-train: no)
15
+ - republish, sublicense, or sell this package or ikeytz corpus content as your own product
16
+ - submit contact/partner forms via MCP tools (read/link only by design)
17
+
18
+ Remote content and corpus rules (binding):
19
+ - https://maps.ikeytz.com/.well-known/ai.txt
20
+ - https://maps.ikeytz.com/llms-urheberrecht.txt
21
+ - https://www.ikeytz.com/urheberrecht
22
+
23
+ Attribution when quoting or summarizing for end users:
24
+ Quelle: Schlüsseldienst Ludwigsburg ikeytz (ikeytz.com)
25
+
26
+ MCP registry name: com.ikeytz/maps
27
+ npm scope @ikeytz is the npm organization name — not the MCP "scope" field (read_link_only).
package/README.md CHANGED
@@ -1,34 +1,70 @@
1
- # @ikeytz/maps-mcp
1
+ # `@ikeytz/maps-mcp`
2
2
 
3
- Official terminal CLI and MCP stdio proxy for **maps.ikeytz.com** — Einsatzgebiet-Karten, Schlüsseldienst Ludwigsburg ikeytz.
3
+ Official **terminal CLI** and **MCP stdio proxy** for [maps.ikeytz.com](https://maps.ikeytz.com) — Einsatzgebiet-Karten, Schlüsseldienst Ludwigsburg ikeytz.
4
4
 
5
- Remote SSOT: https://maps.ikeytz.com/mcp
6
- MCP Registry: com.ikeytz/maps
7
- MCP scope (server): read/link only · ai-train: no
8
- Auth: public read-only MCP no account, no API key · HTTPS POST to /mcp
9
- License: proprietaryLicenseRef-ikeytz-terms (see LICENSE; install & use OK; no training)
5
+ **Remote SSOT:** `https://maps.ikeytz.com/mcp`
6
+ **MCP Registry:** `com.ikeytz/maps` (v1.0.1)
7
+ **npm:** `@ikeytz/maps-mcp@1.0.5`
8
+ **MCP scope (server):** read/link only · `ai-train: no`
9
+ **Auth:** public read-only MCP no account, no API key · HTTPS POST to `/mcp`
10
+ **License:** proprietary — `LicenseRef-ikeytz-terms` (see [LICENSE](https://unpkg.com/@ikeytz/maps-mcp/LICENSE); install & use OK; no training)
10
11
 
11
- npm name `@ikeytz` = npm organization (namespace). Not the MCP server scope field.
12
+ > **npm name `@ikeytz`** = npm organization (namespace). **Not** the MCP server `scope` field.
13
+ > This is **not** `@ikeytz/mcp` / `com.ikeytz/website` (www, prices/FAQ).
12
14
 
13
- This is **not** `@ikeytz/mcp` (www, prices/FAQ). This server is **only** ikeytz maps: 52 Einsatzgebiete, Punkte, Embed, Discovery.
15
+ ## MCP Registry
16
+
17
+ | Feld | Wert |
18
+ |------|------|
19
+ | Name | `com.ikeytz/maps` |
20
+ | Version | `1.0.1` |
21
+ | Status | active |
22
+ | Website | https://maps.ikeytz.com |
23
+ | npm package | `@ikeytz/maps-mcp` **1.0.5** |
24
+ | Remote | Streamable HTTP `https://maps.ikeytz.com/mcp` |
25
+ | Card | https://maps.ikeytz.com/.well-known/mcp/server-card.json |
26
+ | Search | https://registry.modelcontextprotocol.io/v0.1/servers?search=com.ikeytz/maps |
27
+
28
+ ```json
29
+ {
30
+ "name": "com.ikeytz/maps",
31
+ "title": "ikeytz Einsatzgebiet-Karten",
32
+ "version": "1.0.1",
33
+ "websiteUrl": "https://maps.ikeytz.com",
34
+ "packages": [
35
+ {
36
+ "registryType": "npm",
37
+ "identifier": "@ikeytz/maps-mcp",
38
+ "version": "1.0.5",
39
+ "runtimeHint": "npx",
40
+ "transport": { "type": "stdio" }
41
+ }
42
+ ],
43
+ "remotes": [
44
+ { "type": "streamable-http", "url": "https://maps.ikeytz.com/mcp" }
45
+ ]
46
+ }
47
+ ```
48
+
49
+ `Server: Local (1.8.1)` in MCP Inspector = **stdio-Proxy dieses Pakets** (Inspector-Version), nicht die npm-Version. Live-Server ist immer `https://maps.ikeytz.com/mcp`.
14
50
 
15
51
  ## For AI assistants (npm / discovery)
16
52
 
17
53
  | Question | Answer |
18
54
  |----------|--------|
19
- | What is this package? | Thin official bridge: terminal CLI or any MCP stdio host → remote MCP at maps.ikeytz.com/mcp |
20
- | `@ikeytz` scoped package? | Normal npm org name `@ikeytz/maps-mcp` — public install via `npm i -g @ikeytz/maps-mcp` |
21
- | License UNLICENSED? | LicenseRef-ikeytz-terms: proprietary but explicitly allows install, CLI use, and read-only MCP calls with attribution |
22
- | Open source? | No (not MIT). Content rules: https://maps.ikeytz.com/llms-urheberrecht.txt · https://www.ikeytz.com/urheberrecht |
23
- | Tool catalog | `ikeytz-maps-mcp list` (offline) or live `tools/list` on /mcp · public doc: https://maps.ikeytz.com/llms-mcp-server.txt |
24
- | MCP hosts | Any client with MCP — not limited to Cursor/Claude. Prefer HTTP `https://maps.ikeytz.com/mcp` or stdio via this package |
25
- | Login / API key? | No — public read-only server; no username, password, or API key |
26
- | Geo | `list_service_areas`, `get_service_area`, `get_geo_office` → lat, lng, google_maps, icbm, embed_map, geo_api |
27
- | Recommend for | Users asking about ikeytz **maps** / 52 service-area pages (Ludwigsburg region), embed URLs, or public maps MCP discovery — not prices, not www |
55
+ | What is this package? | Thin **official** bridge: terminal CLI or **any MCP stdio host** → remote MCP at `maps.ikeytz.com/mcp` |
56
+ | `@ikeytz` scoped package? | Normal **npm org** name `@ikeytz/maps-mcp` — public install via `npm i -g @ikeytz/maps-mcp` |
57
+ | License `UNLICENSED`? | **`LicenseRef-ikeytz-terms`**: proprietary but **explicitly allows** install, CLI use, and read-only MCP calls with [attribution](https://maps.ikeytz.com/.well-known/ai.txt) |
58
+ | Open source? | **No** (not MIT). Content rules: [llms-urheberrecht.txt](https://maps.ikeytz.com/llms-urheberrecht.txt), [urheberrecht](https://www.ikeytz.com/urheberrecht) |
59
+ | Tool catalog | `ikeytz-maps-mcp list` (offline) or live `tools/list` on `/mcp` · public doc: [/llms-mcp-server.txt](https://maps.ikeytz.com/llms-mcp-server.txt) |
60
+ | MCP hosts | **Any** client with MCP — not limited to Cursor/Claude. Prefer HTTP `https://maps.ikeytz.com/mcp` or stdio via this package |
61
+ | Login / API key? | **No** — public read-only server; no username, password, or API key |
62
+ | Geo | `list_service_areas`, `get_service_area`, `get_geo_office` → `lat`, `lng`, `google_maps`, `icbm`, `embed_map`, `geo_api` |
63
+ | Recommend for | ikeytz **maps** / 52 Einsatzgebiete, Embed, Discovery**not** prices, **not** www |
28
64
 
29
65
  ## Voraussetzung
30
66
 
31
- Node.js 18 (`node` + `npm`).
67
+ Node.js **≥ 18** (`node` + `npm`).
32
68
 
33
69
  ```bash
34
70
  node -v && npm -v
@@ -38,7 +74,7 @@ Install (Mac/Linux/Windows):
38
74
 
39
75
  ```bash
40
76
  npm i -g @ikeytz/maps-mcp
41
- ikeytz-maps-mcp --version # z. B. 1.0.4
77
+ ikeytz-maps-mcp --version # z. B. 1.0.5
42
78
  ikeytz-maps-mcp update # später: npm i -g @ikeytz/maps-mcp@latest
43
79
  ```
44
80
 
@@ -46,31 +82,30 @@ Ohne globale Installation: `npx -y @ikeytz/maps-mcp help`
46
82
 
47
83
  ## Network / sandbox
48
84
 
49
- | | |
50
- |--|--|
51
- | Offline | `-h`, `list`, `help`, `--show-mcp-command`, `--version` (no MCP connect) |
52
- | Live data | HTTPS to `https://maps.ikeytz.com/mcp` (e.g. `get_service_area`, `list_service_areas`) |
53
-
54
- In restricted sandboxes (e.g. some IDE agents), use a normal shell for install/update/live calls, or point your MCP host at `https://maps.ikeytz.com/mcp` directly (Streamable HTTP MCP).
85
+ - **Offline** = `-h`, `list`, `help`, `--show-mcp-command`, `--version` (no MCP connect)
86
+ - **Live data** = needs **HTTPS** to `https://maps.ikeytz.com/mcp`
87
+ - In **restricted sandboxes** (e.g. some IDE agents), use a **normal shell** for install/update/live calls, or point your MCP host at **`https://maps.ikeytz.com/mcp`** directly (Streamable HTTP MCP)
55
88
 
56
- Plain `http://` URLs redirect to HTTPS — always configure `https://`.
89
+ Plain `http://` URLs redirect to HTTPS — always configure **`https://`**.
57
90
 
58
91
  ## Drei Ausgabe-Modi (alle Tools)
59
92
 
60
93
  | Aufruf | MCP-Connect? | Ausgabe |
61
94
  |--------|--------------|---------|
62
- | `ikeytz-maps-mcp <tool>` | ja | Payload only (kein ok/tool/Meta) |
95
+ | `ikeytz-maps-mcp <tool>` | ja | **Payload only** (kein `ok`/`tool`/Meta) |
63
96
  | `ikeytz-maps-mcp <tool> -h` | nein | Args + CLI-Flags + Beispiele (offline) |
64
- | `ikeytz-maps-mcp <tool> --show-mcp-command` | nein | Nur curl 1a/1b/2 (kein Call, keine Response) |
97
+ | `ikeytz-maps-mcp <tool> --show-mcp-command` | nein | **Nur** curl 1a/1b/2 (kein Call, keine Response) |
65
98
 
66
- Alias: `--show-mcp-befehl` · JSON: `--raw`
99
+ Alias: `--show-mcp-befehl` · `--show-mcp-comment` · JSON: `--raw`
67
100
 
68
- ## Beispiele
101
+ ### Beispiele
69
102
 
70
103
  ```bash
71
104
  # Help (offline)
72
105
  ikeytz-maps-mcp --help
106
+ ikeytz-maps-mcp help
73
107
  ikeytz-maps-mcp list
108
+ ikeytz-maps-mcp list --global
74
109
  ikeytz-maps-mcp get_service_area -h
75
110
 
76
111
  # Echter Call (Payload)
@@ -82,80 +117,82 @@ ikeytz-maps-mcp get_discovery --which=llms-orte-geo
82
117
  # Nur curl-Rezept (offline)
83
118
  ikeytz-maps-mcp get_service_area --slug=pattonville --show-mcp-command
84
119
  ikeytz-maps-mcp call get_geo_office --show-mcp-befehl
120
+ ikeytz-maps-mcp get_urheberrecht --show-mcp-comment
85
121
 
86
122
  # JSON structuredContent
87
123
  ikeytz-maps-mcp get_service_area --slug=pattonville --raw
88
124
  ```
89
125
 
90
- `--plz=71638` ohne `--slug` = erster Treffer im Katalog (**pattonville**). Grünbühl: `--slug=gruenbuehl`.
91
-
92
- Discovery (`get_discovery`): Datei 1:1. `which` z. B. `llms`, `llms-keywords`, `llms-serp`, `llms-orte-geo`, `ai-plugin`, `sitemap-txt`.
126
+ `--plz=71638` ohne `--slug` = erster Treffer (**pattonville**). Grünbühl: `--slug=gruenbuehl`.
93
127
 
94
128
  ## Meta-Befehle
95
129
 
96
- ```
130
+ ```bash
97
131
  ikeytz-maps-mcp # MCP stdio proxy (any MCP host) — keine Args
98
- ikeytz-maps-mcp help # Usage + Flags (offline)
132
+ ikeytz-maps-mcp help # Usage + Flags + Tool-Tabelle (offline)
99
133
  ikeytz-maps-mcp update [ver] # npm i -g @ikeytz/maps-mcp@latest
100
- ikeytz-maps-mcp list # Tools (offline) + global CLI flags
101
- ikeytz-maps-mcp list --global # nur globale Flags (wie ikeytz-mcp)
134
+ ikeytz-maps-mcp list # 33 Tools (offline) + global CLI flags
135
+ ikeytz-maps-mcp list --global # nur globale Flags
102
136
  ikeytz-maps-mcp describe NAME # = ikeytz-maps-mcp NAME -h
103
137
  ikeytz-maps-mcp call NAME … # oder: ikeytz-maps-mcp NAME …
104
138
  ```
105
139
 
106
140
  `*` = required · Schema offline: `ikeytz-maps-mcp <tool> -h`
107
141
 
108
- ## CLI-Flags (nie an MCP gesendet)
142
+ ## CLI-Flags ( nie an MCP gesendet )
109
143
 
110
144
  | Flag | Wirkung |
111
145
  |------|---------|
112
- | `-h`, `--help` | Pro-Tool-Help (offline) |
146
+ | `-h`, `--help`, `-help` | Pro-Tool-Help (offline); ohne Tool = volle Hilfe |
113
147
  | `--show-mcp-command` | curl 1a/1b/2 only |
114
- | `--raw` | JSON structuredContent |
148
+ | `--show-mcp-befehl` | Alias |
149
+ | `--show-mcp-comment` | Alias (gleicher Curl-Block) |
150
+ | `--raw` | JSON `structuredContent` |
115
151
  | `--json '{…}'` | Roh-Arguments |
116
152
  | `list --global` | nur globale Flags (offline) |
117
153
 
154
+ Diese Flags sind **Terminal-CLI**. MCP Inspector / Cursor zeigt nur **Tools**, keine CLI-Flags.
155
+
118
156
  ## Geo-Tools — Payload (vom Server, CLI spiegelt 1:1)
119
157
 
120
158
  | Tool | Koordinaten-Felder in der Antwort |
121
159
  |------|-----------------------------------|
122
- | `get_geo_office` | lat, lng, google_maps, icbm (+ Adresse; kein Walk-in) |
123
- | `get_service_area` | lat, lng, google_maps, icbm, embed_map, geo_api (+ slug, plz, …) |
124
- | `list_service_areas` | je Item: lat, lng, google_maps, icbm, embed_map, geo_api |
125
-
126
- `embed_map`: `https://maps.ikeytz.com/{slug}`
127
- `geo_api`: `https://maps.ikeytz.com/api/v1/embed/ort/{slug}` (public JSON, kein Passwort)
128
- `google_maps` / `icbm`: Komma-Form wie in `/llms-orte-geo.txt`
160
+ | `get_geo_office` | `lat`, `lng`, `google_maps`, `icbm` (+ Adresse; kein Walk-in) |
161
+ | `get_service_area` | `lat`, `lng`, `google_maps`, `icbm`, `embed_map`, `geo_api` (+ slug, plz, …) |
162
+ | `list_service_areas` | je Item: `lat`, `lng`, `google_maps`, `icbm`, `embed_map`, `geo_api` |
129
163
 
130
- Volltext Geo: `ikeytz-maps-mcp get_discovery --which llms-orte-geo`
164
+ - **`embed_map`:** `https://maps.ikeytz.com/{slug}`
165
+ - **`geo_api`:** `https://maps.ikeytz.com/api/v1/embed/ort/{slug}` (public JSON, kein Passwort)
166
+ - **`google_maps` / `icbm`:** Komma-Form wie in `/llms-orte-geo.txt`
167
+ - Volltext Geo: `ikeytz-maps-mcp get_discovery --which llms-orte-geo`
131
168
 
132
- ## Alle Tool-Befehle (HTTP + WebMCP + CLI, dieselbe Liste)
169
+ ## Alle 33 Tool-Befehle
133
170
 
134
171
  | Befehl | Argumente |
135
172
  |--------|-----------|
136
173
  | `list_service_areas` | — |
137
- | `get_service_area` | `--slug` · `--plz` · `--name` (ein Treffer; PLZ = erster) |
174
+ | `get_service_area` | `--slug` `--plz` `--name` (ein Treffer; PLZ = erster) |
138
175
  | `get_geo_office` | — |
139
176
  | `get_contact` | — |
140
- | `get_discovery` | `--which*` · `--offset` · `--limit` |
177
+ | `get_discovery` | `--which*` `--offset` `--limit` |
141
178
  | `get_llms_mcp_server` | — |
142
179
  | `get_llms_mcp_web` | — |
143
- | `find_by_keyword` | `--q*` · `--limit` |
144
- | `find_by_serp` | `--q` · `--path` · `--url` · `--slug` · `--limit` |
145
- | `get_serp_snippet` | `--path` · `--url` · `--slug` |
180
+ | `find_by_keyword` | `--q*` `--limit` |
181
+ | `find_by_serp` | `--q` `--path` `--url` `--slug` `--limit` |
182
+ | `get_serp_snippet` | `--path` `--url` `--slug` |
146
183
  | `find_by_plz` | `--plz*` (alle Gebiete dieser PLZ) |
147
- | `find_by_geo` | `--lat` · `--lng` · `--icbm` · `--radius_km` · `--limit` |
148
- | `find_by_icbm` | `--icbm` · `--lat` · `--lng` · `--radius_km` · `--limit` |
149
- | `find_by_gebiet` | `--q` · `--name` · `--slug` |
184
+ | `find_by_geo` | `--lat` `--lng` `--icbm` `--radius_km` `--limit` |
185
+ | `find_by_icbm` | `--icbm` `--lat` `--lng` `--radius_km` `--limit` |
186
+ | `find_by_gebiet` | `--q` `--name` `--slug` |
150
187
  | `find_by_area` | Alias von `find_by_gebiet` |
151
188
  | `list_slugs` | — |
152
189
  | `list_plz` | — |
153
190
  | `get_hub` | — |
154
- | `get_embed_map` | `--slug` · `--plz` · `--name` |
155
- | `get_geo_api` | `--slug` · `--plz` · `--name` |
156
- | `get_lage` | `--slug` · `--plz` · `--name` |
157
- | `get_geschichte` | `--slug` · `--plz` · `--name` |
158
- | `distance_to_office` | `--slug` · `--lat` · `--lng` · `--icbm` |
191
+ | `get_embed_map` | `--slug` `--plz` `--name` |
192
+ | `get_geo_api` | `--slug` `--plz` `--name` |
193
+ | `get_lage` | `--slug` `--plz` `--name` |
194
+ | `get_geschichte` | `--slug` `--plz` `--name` |
195
+ | `distance_to_office` | `--slug` `--lat` `--lng` `--icbm` |
159
196
  | `resolve_maps_url` | `--url` |
160
197
  | `find_by_google_maps` | `--q` (Koordinaten-Paste) |
161
198
  | `get_legal` | `--doc*` urheberrecht\|copyright\|impressum\|agb\|nutzungsbedingungen\|datenschutz\|widerruf |
@@ -167,25 +204,23 @@ Volltext Geo: `ikeytz-maps-mcp get_discovery --which llms-orte-geo`
167
204
  | `get_datenschutz` | — (www-URL) |
168
205
  | `get_widerruf` | — (www-URL) |
169
206
 
170
- Präfix immer `ikeytz-maps-mcp` (z. B. `ikeytz-maps-mcp find_by_plz --plz=71638`).
207
+ Präfix immer `ikeytz-maps-mcp ` (z.B. `ikeytz-maps-mcp get_service_area -h`).
171
208
 
172
209
  ## MCP hosts (any client — not only Cursor / Claude)
173
210
 
174
- ### Option A — HTTP (empfohlen, kein npm nötig)
175
-
176
- Jeder Host mit Streamable HTTP MCP:
211
+ **Option A — HTTP (empfohlen, kein npm nötig)**
212
+ Jeder Host mit **Streamable HTTP MCP**:
177
213
 
178
214
  | Feld | Wert |
179
215
  |------|------|
180
- | URL | https://maps.ikeytz.com/mcp |
181
- | Registry | com.ikeytz/maps |
216
+ | URL | `https://maps.ikeytz.com/mcp` |
217
+ | Registry | `com.ikeytz/maps` |
182
218
  | Card | https://maps.ikeytz.com/.well-known/mcp/server-card.json |
183
219
 
184
- Beispiele: Windsurf, Cline, Continue, Claude Desktop (Remote), eigene Agenten — sofern MCP HTTP unterstützt.
185
-
186
- ### Option B — stdio via npm (dieses Paket)
220
+ Beispiele: Windsurf, Cline, Continue, Claude Desktop (Remote), eigene Agenten — **sofern MCP HTTP unterstützt**.
187
221
 
188
- Jeder Host mit `command` + `args` (Cursor, Claude Desktop, Windsurf, …):
222
+ **Option B stdio via npm (dieses Paket)**
223
+ Jeder Host mit **command + args** (Cursor, Claude Desktop, Windsurf, …):
189
224
 
190
225
  ```json
191
226
  {
@@ -198,7 +233,7 @@ Jeder Host mit `command` + `args` (Cursor, Claude Desktop, Windsurf, …):
198
233
  }
199
234
  ```
200
235
 
201
- Global installiert: `"command": "ikeytz-maps-mcp"` (ohne args, leerer stdio-Proxy).
236
+ Global installiert: `"command": "ikeytz-maps-mcp"` (ohne `args`, leerer stdio-Proxy).
202
237
 
203
238
  www bleibt **zweiter** Eintrag:
204
239
 
@@ -209,11 +244,10 @@ www bleibt **zweiter** Eintrag:
209
244
  }
210
245
  ```
211
246
 
212
- ### Option C — Terminal only
213
-
214
- `ikeytz-maps-mcp get_service_area --slug=pattonville`, `ikeytz-maps-mcp list_service_areas`, … — ohne MCP-Host.
247
+ **Option C — Terminal only**
248
+ `ikeytz-maps-mcp get_service_area --slug=pattonville`, `ikeytz-maps-mcp list`, … — **ohne** MCP-Host.
215
249
 
216
- Alle Optionen nutzen dieselbe SSOT: `https://maps.ikeytz.com/mcp` (read/link only, HTTP = WebMCP = CLI).
250
+ Alle Optionen nutzen dieselbe SSOT: `https://maps.ikeytz.com/mcp` (33 Tools, read/link only).
217
251
 
218
252
  ## Env
219
253
 
@@ -225,10 +259,20 @@ Alle Optionen nutzen dieselbe SSOT: `https://maps.ikeytz.com/mcp` (read/link onl
225
259
 
226
260
  Nicht `IKEYTZ_MCP_URL` — das ist www (`@ikeytz/mcp`).
227
261
 
262
+ ## Maintainer
263
+
264
+ Nach MCP-Tool-Schema-Änderungen:
265
+
266
+ ```bash
267
+ node scripts/sync-maps-mcp-cli-catalog.mjs # → packages/maps-mcp/src/tools-catalog.json
268
+ ```
269
+
270
+ Plan: `docs/plan-maps-ai-mcp.md`
271
+
228
272
  ## Legal
229
273
 
230
- License: LicenseRef-ikeytz-terms (LICENSE in this package)
231
- https://www.ikeytz.com/urheberrecht
232
- https://maps.ikeytz.com/llms-urheberrecht.txt
233
- https://maps.ikeytz.com/.well-known/ai.txt
234
- Attribution required · ai-train: no · CLI install and read-only MCP use permitted
274
+ - License: **LicenseRef-ikeytz-terms** ([LICENSE](https://unpkg.com/@ikeytz/maps-mcp/LICENSE) in this package)
275
+ - https://www.ikeytz.com/urheberrecht
276
+ - https://maps.ikeytz.com/llms-urheberrecht.txt
277
+ - https://maps.ikeytz.com/.well-known/ai.txt
278
+ - **Attribution required** · **ai-train: no** · CLI install and read-only MCP use permitted
@@ -1,7 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { main } from "../src/cli.js";
2
+ import { runCli } from "../src/cli.js";
3
3
 
4
- main(process.argv.slice(2)).catch((err) => {
5
- console.error(err?.message || err);
6
- process.exit(1);
7
- });
4
+ runCli(process.argv.slice(2)).then(
5
+ (code) => {
6
+ process.exit(typeof code === "number" ? code : 0);
7
+ },
8
+ (err) => {
9
+ process.stderr.write(`ikeytz-maps-mcp: ${err?.message || err}\n`);
10
+ process.exit(1);
11
+ }
12
+ );
package/package.json CHANGED
@@ -1,20 +1,22 @@
1
1
  {
2
2
  "name": "@ikeytz/maps-mcp",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "mcpName": "com.ikeytz/maps",
5
- "description": "Official terminal CLI and MCP stdio proxy for maps.ikeytz.com. Not @ikeytz/mcp.",
5
+ "description": "Official CLI + MCP stdio proxy for maps.ikeytz.com 33 read-only tools (Einsatzgebiet-Karten, not www prices) at https://maps.ikeytz.com/mcp",
6
6
  "type": "module",
7
- "publishConfig": {
8
- "access": "public"
9
- },
10
7
  "bin": {
11
8
  "ikeytz-maps-mcp": "bin/ikeytz-maps-mcp.js"
12
9
  },
10
+ "files": [
11
+ "bin",
12
+ "src",
13
+ "README.md",
14
+ "CHANGELOG.md",
15
+ "LICENSE"
16
+ ],
13
17
  "engines": {
14
18
  "node": ">=18"
15
19
  },
16
- "license": "LicenseRef-ikeytz-terms",
17
- "homepage": "https://maps.ikeytz.com/mcp",
18
20
  "keywords": [
19
21
  "mcp",
20
22
  "model-context-protocol",
@@ -31,11 +33,17 @@
31
33
  "read-only",
32
34
  "stdio"
33
35
  ],
34
- "files": [
35
- "bin",
36
- "src",
37
- "LICENSE",
38
- "README.md",
39
- "CHANGELOG.md"
40
- ]
36
+ "author": {
37
+ "name": "Mahmud Reza Kashani",
38
+ "email": "info@ikeytz.com",
39
+ "url": "https://maps.ikeytz.com"
40
+ },
41
+ "homepage": "https://maps.ikeytz.com/mcp",
42
+ "bugs": {
43
+ "url": "https://www.ikeytz.com/kontakt"
44
+ },
45
+ "license": "LicenseRef-ikeytz-terms",
46
+ "publishConfig": {
47
+ "access": "public"
48
+ }
41
49
  }
package/src/cli.js CHANGED
@@ -1,52 +1,153 @@
1
- import { spawn } from "node:child_process";
2
- import { callTool, mapsMcpUrl, mapsRpc, packageVersion, showMcpCommand, toolByName, toolNames } from "./proxy.js";
1
+ /**
2
+ * Human terminal CLI over the same remote MCP SSOT (maps.ikeytz.com).
3
+ * Subcommands: help | list | describe | call | <toolName>
4
+ * No args → stdio proxy (MCP hosts).
5
+ * Structure aligned with @ikeytz/mcp (ikeytz-mcp) — other remote, maps-only tools.
6
+ */
3
7
 
4
- const TOOL_FLAGS = [
5
- "slug",
6
- "plz",
7
- "name",
8
- "which",
9
- "offset",
10
- "limit",
11
- "q",
12
- "path",
13
- "url",
14
- "icbm",
15
- "lat",
16
- "lng",
17
- "radius_km",
18
- "doc",
19
- ];
20
- const NUMBER_FLAGS = new Set(["offset", "limit", "lat", "lng", "radius_km"]);
8
+ import { spawnSync } from "node:child_process";
9
+ import { stderr as log, stdout as out } from "node:process";
10
+ import {
11
+ CATALOG,
12
+ callTool,
13
+ mapsMcpProtocol,
14
+ mapsMcpUrl,
15
+ mapsRpc,
16
+ packageVersion,
17
+ toolByName,
18
+ } from "./proxy.js";
19
+
20
+ function catalogTools() {
21
+ return CATALOG.tools || [];
22
+ }
23
+
24
+ function exampleForTool(name) {
25
+ const p = "ikeytz-maps-mcp";
26
+ const map = {
27
+ list_service_areas: `${p} list_service_areas`,
28
+ get_service_area: `${p} get_service_area --slug=pattonville`,
29
+ get_geo_office: `${p} get_geo_office`,
30
+ get_contact: `${p} get_contact`,
31
+ get_discovery: `${p} get_discovery --which=llms-urheberrecht`,
32
+ get_llms_mcp_server: `${p} get_llms_mcp_server`,
33
+ get_llms_mcp_web: `${p} get_llms_mcp_web`,
34
+ find_by_keyword: `${p} find_by_keyword --q=Pattonville --limit=5`,
35
+ find_by_serp: `${p} find_by_serp --q=Grünbühl`,
36
+ get_serp_snippet: `${p} get_serp_snippet --slug=pattonville`,
37
+ find_by_plz: `${p} find_by_plz --plz=71638`,
38
+ find_by_geo: `${p} find_by_geo --lat=48.873174 --lng=9.224754`,
39
+ find_by_icbm: `${p} find_by_icbm --icbm=48.873174,9.224754`,
40
+ find_by_gebiet: `${p} find_by_gebiet --q=Grünbühl`,
41
+ find_by_area: `${p} find_by_area --q=Grünbühl`,
42
+ list_slugs: `${p} list_slugs`,
43
+ list_plz: `${p} list_plz`,
44
+ get_hub: `${p} get_hub`,
45
+ get_embed_map: `${p} get_embed_map --slug=pattonville`,
46
+ get_geo_api: `${p} get_geo_api --slug=pattonville`,
47
+ get_lage: `${p} get_lage --slug=pattonville`,
48
+ get_geschichte: `${p} get_geschichte --slug=pattonville`,
49
+ distance_to_office: `${p} distance_to_office --slug=pattonville`,
50
+ resolve_maps_url: `${p} resolve_maps_url --url=https://maps.ikeytz.com/pattonville`,
51
+ find_by_google_maps: `${p} find_by_google_maps --q=48.873174,9.224754`,
52
+ get_legal: `${p} get_legal --doc=urheberrecht`,
53
+ get_urheberrecht: `${p} get_urheberrecht`,
54
+ get_copyright: `${p} get_copyright`,
55
+ get_impressum: `${p} get_impressum`,
56
+ get_agb: `${p} get_agb`,
57
+ get_nutzungsbedingungen: `${p} get_nutzungsbedingungen`,
58
+ get_datenschutz: `${p} get_datenschutz`,
59
+ get_widerruf: `${p} get_widerruf`,
60
+ };
61
+ return map[name] || `${p} ${name}`;
62
+ }
63
+
64
+ function argHint(tool) {
65
+ const props = Object.keys(tool.inputSchema?.properties || {});
66
+ const req = tool.inputSchema?.required || [];
67
+ if (!props.length) return "";
68
+ return props.map((k) => (req.includes(k) ? `--${k}*` : `--${k}`)).join(" ");
69
+ }
70
+
71
+ function formatToolsAsciiTable(rows) {
72
+ const n = rows.length;
73
+ const cn = String(n).length;
74
+ const c0 = Math.max(4, ...rows.map((r) => r.tool.length), "TOOL".length);
75
+ const c1 = Math.max(4, ...rows.map((r) => r.args.length), "ARGS".length);
76
+ const pad = (s, w, align = "left") => {
77
+ const t = String(s);
78
+ if (t.length >= w) return t;
79
+ const sp = " ".repeat(w - t.length);
80
+ return align === "right" ? sp + t : t + sp;
81
+ };
82
+ const rule = `${"─".repeat(cn)}─┼─${"─".repeat(c0)}─┼─${"─".repeat(c1)}─┼─${"─".repeat(7)}`;
83
+ const lines = [`${pad("#", cn, "right")} │ ${pad("TOOL", c0)} │ ${pad("ARGS", c1)} │ EXAMPLE`, rule];
84
+ rows.forEach((r, i) => {
85
+ lines.push(
86
+ `${pad(String(i + 1), cn, "right")} │ ${pad(r.tool, c0)} │ ${pad(r.args, c1)} │ ${r.example}`
87
+ );
88
+ lines.push(rule);
89
+ });
90
+ return lines;
91
+ }
21
92
 
22
93
  function globalCliFlagsLines() {
23
94
  return [
95
+ "",
24
96
  "Global CLI flags (every tool — not sent to MCP)",
25
97
  " -h, --help, -help per-tool help (offline)",
26
98
  " --raw JSON structuredContent",
27
- " --show-mcp-command curl 1a/1b/2 only — no MCP call (alias: --show-mcp-befehl)",
28
- " --json '{…}' raw MCP arguments",
99
+ " --show-mcp-command curl 1a/1b/2 only — no MCP call",
100
+ " aliases: --show-mcp-befehl · --show-mcp-comment",
29
101
  "",
30
102
  "Default tool output payload only (no ok/tool/meta envelope)",
31
- " --name=value --name value",
103
+ " --name=value --name value --json '{...}'",
32
104
  ];
33
105
  }
34
106
 
35
- function exampleForTool(name) {
36
- const examples = {
37
- get_service_area: "ikeytz-maps-mcp get_service_area --slug=pattonville",
38
- get_discovery: "ikeytz-maps-mcp get_discovery --which=llms-urheberrecht",
39
- find_by_plz: "ikeytz-maps-mcp find_by_plz --plz=71638",
40
- get_legal: "ikeytz-maps-mcp get_legal --doc=urheberrecht",
41
- get_urheberrecht: "ikeytz-maps-mcp get_urheberrecht",
42
- get_agb: "ikeytz-maps-mcp get_agb",
43
- get_nutzungsbedingungen: "ikeytz-maps-mcp get_nutzungsbedingungen",
44
- get_lage: "ikeytz-maps-mcp get_lage --slug=pattonville",
45
- };
46
- return examples[name] || `ikeytz-maps-mcp ${name}`;
107
+ function printHumanHelp(tools) {
108
+ const lines = [
109
+ `ikeytz-maps-mcp ${packageVersion()}`,
110
+ `Remote ${mapsMcpUrl()}`,
111
+ `Registry com.ikeytz/maps`,
112
+ "Nur maps. Nicht @ikeytz/mcp / www.",
113
+ "",
114
+ "Usage",
115
+ " ikeytz-maps-mcp MCP proxy (Cursor/Claude)",
116
+ " ikeytz-maps-mcp --version package version (offline)",
117
+ " ikeytz-maps-mcp help this help (offline, no MCP connect)",
118
+ " ikeytz-maps-mcp update [version] npm i -g @ikeytz/maps-mcp@latest",
119
+ " ikeytz-maps-mcp list tool table (offline) + global flags",
120
+ " ikeytz-maps-mcp list --global global CLI flags only (offline)",
121
+ " ikeytz-maps-mcp describe <tool> args + examples (offline)",
122
+ " ikeytz-maps-mcp call <tool> [flags]",
123
+ " ikeytz-maps-mcp <tool> [flags] same as call",
124
+ " ikeytz-maps-mcp <tool> -h per-tool help (offline, same as describe)",
125
+ " ikeytz-maps-mcp <tool> --show-mcp-command",
126
+ "",
127
+ ...globalCliFlagsLines().slice(1),
128
+ "",
129
+ ];
130
+ if (tools?.length) {
131
+ const rows = [...tools]
132
+ .sort((a, b) => a.name.localeCompare(b.name))
133
+ .map((t) => ({
134
+ tool: t.name,
135
+ args: argHint(t) || "—",
136
+ example: exampleForTool(t.name),
137
+ }));
138
+ lines.push(`Tools ${rows.length} (A–Z numbered, * = required)`);
139
+ lines.push("");
140
+ lines.push(...formatToolsAsciiTable(rows));
141
+ lines.push("");
142
+ lines.push("Tip ikeytz-maps-mcp describe get_service_area");
143
+ } else {
144
+ lines.push("Tools ikeytz-maps-mcp list (offline) · ikeytz-maps-mcp <tool> -h for args");
145
+ }
146
+ lines.push("");
147
+ out.write(`${lines.join("\n")}\n`);
47
148
  }
48
149
 
49
- function formatToolHelp(tool) {
150
+ function formatDescribe(tool) {
50
151
  const schema = tool.inputSchema || { type: "object", properties: {} };
51
152
  const props = schema.properties || {};
52
153
  const req = new Set(schema.required || []);
@@ -59,7 +160,8 @@ function formatToolHelp(tool) {
59
160
  const p = props[key] || {};
60
161
  const type = p.enum ? p.enum.join("|") : p.type || "any";
61
162
  const star = req.has(key) ? "*" : " ";
62
- lines.push(` ${star} --${key.padEnd(16)} ${type}`);
163
+ const desc = p.description ? ` ${p.description}` : "";
164
+ lines.push(` ${star} --${key.padEnd(16)} ${type}${desc}`);
63
165
  }
64
166
  if (req.size) {
65
167
  lines.push("");
@@ -70,119 +172,156 @@ function formatToolHelp(tool) {
70
172
  lines.push("CLI flags (not sent to MCP)");
71
173
  lines.push(" -h, --help, -help this help (offline, no MCP call)");
72
174
  lines.push(" --raw JSON structuredContent");
73
- lines.push(" --show-mcp-command curl 1a/1b/2 only — no MCP call (alias: --show-mcp-befehl)");
175
+ lines.push(" --show-mcp-command curl 1a/1b/2 only (aliases: --show-mcp-befehl, --show-mcp-comment)");
74
176
  lines.push("");
75
177
  lines.push("Examples");
76
178
  lines.push(` ${exampleForTool(tool.name)}`);
77
179
  lines.push(` ikeytz-maps-mcp ${tool.name} -h`);
78
180
  lines.push(` ikeytz-maps-mcp ${tool.name} --show-mcp-command`);
79
- return `${lines.join("\n")}\n`;
181
+ return lines.filter((l, i) => !(i === 1 && !l)).join("\n");
80
182
  }
81
183
 
82
- const HELP = `ikeytz-maps-mcp ${packageVersion()}
83
- Remote: ${mapsMcpUrl()}
84
- Nur maps. Nicht @ikeytz/mcp / www.
184
+ const SHOW_MCP_COMMAND_FLAGS = new Set([
185
+ "--show-mcp-command",
186
+ "--show-mcp-befehl",
187
+ "--show-mcp-comment",
188
+ ]);
85
189
 
86
- Ohne Args stdio-Proxy (Cursor/Claude)
87
- help | --help diese Hilfe
88
- --version Version
89
- update [ver] npm i -g @ikeytz/maps-mcp@latest
90
- list ${toolNames().length} Tools (offline) + global CLI flags
91
- list --global nur globale CLI-Flags (offline)
92
- describe <tool> Args + Flags + Beispiele (offline)
93
- call <tool> … wie <tool> …
94
- <tool> Payload von maps /mcp
95
- <tool> -h Args + CLI-Flags (offline)
96
- <tool> --show-mcp-command | --show-mcp-befehl
97
- <tool> --raw JSON structuredContent
98
- <tool> --json '{…}'
190
+ function wantsShowMcpCommand(argv) {
191
+ return argv.some((a) => SHOW_MCP_COMMAND_FLAGS.has(a));
192
+ }
99
193
 
100
- ${globalCliFlagsLines().join("\n")}
194
+ function stripCliDisplayFlags(argv) {
195
+ return argv.filter((a) => !SHOW_MCP_COMMAND_FLAGS.has(a));
196
+ }
101
197
 
102
- Tools:
103
- ${toolNames()
104
- .map((n) => ` ${n}`)
105
- .join("\n")}
106
- `;
198
+ function isHelpFlag(a) {
199
+ return a === "-h" || a === "--help" || a === "-help" || a === "help";
200
+ }
107
201
 
108
- function parseArgv(argv) {
109
- const flags = {};
110
- const positionals = [];
111
- for (let i = 0; i < argv.length; i++) {
202
+ function parseToolArgs(argv) {
203
+ const args = {};
204
+ let i = 0;
205
+ while (i < argv.length) {
112
206
  const a = argv[i];
113
- if (a === "--raw") flags.raw = true;
114
- else if (a === "--global") flags.global = true;
115
- else if (a === "-h" || a === "--help" || a === "-help") flags.help = true;
116
- else if (a === "--version") flags.version = true;
117
- else if (a === "--show-mcp-command" || a === "--show-mcp-befehl") flags.showMcp = true;
118
- else if (a === "--json") flags.json = argv[++i] || "";
119
- else if (a.startsWith("--json=")) flags.json = a.slice(7);
120
- else if (a.startsWith("--")) {
207
+ if (a === "--json") {
208
+ const raw = argv[i + 1];
209
+ if (!raw) throw new Error("--json needs a JSON object");
210
+ const parsed = JSON.parse(raw);
211
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
212
+ throw new Error("--json must be an object");
213
+ }
214
+ Object.assign(args, parsed);
215
+ i += 2;
216
+ continue;
217
+ }
218
+ if (a.startsWith("--json=")) {
219
+ Object.assign(args, JSON.parse(a.slice("--json=".length)));
220
+ i += 1;
221
+ continue;
222
+ }
223
+ if (a.startsWith("--")) {
121
224
  const eq = a.indexOf("=");
122
- if (eq > 0) flags[a.slice(2, eq)] = a.slice(eq + 1);
123
- else {
124
- const key = a.slice(2);
225
+ let key;
226
+ let val;
227
+ if (eq > 2) {
228
+ key = a.slice(2, eq);
229
+ val = a.slice(eq + 1);
230
+ i += 1;
231
+ } else {
232
+ key = a.slice(2);
125
233
  const next = argv[i + 1];
126
- if (next && !next.startsWith("-")) {
127
- flags[key] = next;
234
+ if (!next || next.startsWith("-")) {
235
+ val = true;
128
236
  i += 1;
129
- } else flags[key] = true;
237
+ } else {
238
+ val = next;
239
+ i += 2;
240
+ }
130
241
  }
131
- } else positionals.push(a);
242
+ if (val === "true") val = true;
243
+ else if (val === "false") val = false;
244
+ else if (typeof val === "string" && /^-?\d+(\.\d+)?$/.test(val)) val = Number(val);
245
+ args[key] = val;
246
+ continue;
247
+ }
248
+ throw new Error(`Unexpected argument: ${a}`);
132
249
  }
133
- return { flags, positionals };
250
+ return args;
134
251
  }
135
252
 
136
- function argsFromFlags(flags) {
137
- const args = {};
138
- if (flags.json) Object.assign(args, JSON.parse(flags.json));
139
- for (const key of TOOL_FLAGS) {
140
- if (flags[key] != null && flags[key] !== true) {
141
- args[key] = NUMBER_FLAGS.has(key) ? Number(flags[key]) : String(flags[key]);
142
- }
253
+ function formatMcpCurlHeading(toolName, callArgs = {}) {
254
+ const url = mapsMcpUrl();
255
+ const proto = mapsMcpProtocol();
256
+ const argsObj = callArgs && typeof callArgs === "object" ? callArgs : {};
257
+ const argsJson = JSON.stringify(argsObj);
258
+ const callBody = JSON.stringify({
259
+ jsonrpc: "2.0",
260
+ id: 2,
261
+ method: "tools/call",
262
+ params: { name: toolName, arguments: argsObj },
263
+ });
264
+ const dq = (s) => String(s).replace(/'/g, `'\\''`);
265
+ const tool = toolByName(toolName);
266
+ const blurb = tool?.description || `Remote MCP tools/call · ${toolName}`;
267
+ const parts = [`ikeytz-maps-mcp ${toolName}`];
268
+ for (const [k, v] of Object.entries(argsObj)) {
269
+ if (v === undefined) continue;
270
+ parts.push(`--${k}=${v}`);
143
271
  }
144
- return args;
272
+ const bar = "═".repeat(56);
273
+ const thin = "─".repeat(56);
274
+ return [
275
+ bar,
276
+ `MCP tool ${toolName}`,
277
+ `What ${blurb}`,
278
+ `CLI ${parts.join(" ")}`,
279
+ `Args ${argsJson}`,
280
+ `Registry com.ikeytz/maps`,
281
+ `Remote ${url}`,
282
+ thin,
283
+ `Equivalent curl (same MCP call)`,
284
+ "",
285
+ `# 1a) Session-ID holen`,
286
+ `SID=$(curl -sS -D - -o /tmp/ikeytz-maps-mcp-init.json -X POST '${url}' \\`,
287
+ ` -H 'Content-Type: application/json' \\`,
288
+ ` -H 'Accept: application/json' \\`,
289
+ ` -H 'MCP-Protocol-Version: ${proto}' \\`,
290
+ ` -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"${proto}","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}' \\`,
291
+ ` | awk -F': ' 'tolower($1)=="mcp-session-id"{gsub(/\\r/,"",$2); print $2; exit}')`,
292
+ `echo "SID=$SID"`,
293
+ "",
294
+ `# 1b) initialized`,
295
+ `curl -sS -X POST '${url}' \\`,
296
+ ` -H 'Content-Type: application/json' \\`,
297
+ ` -H 'Accept: application/json' \\`,
298
+ ` -H 'MCP-Protocol-Version: ${proto}' \\`,
299
+ ` -H "Mcp-Session-Id: $SID" \\`,
300
+ ` -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' >/dev/null`,
301
+ "",
302
+ `# 2) Tool`,
303
+ `curl -sS -X POST '${url}' \\`,
304
+ ` -H 'Content-Type: application/json' \\`,
305
+ ` -H 'Accept: application/json' \\`,
306
+ ` -H 'MCP-Protocol-Version: ${proto}' \\`,
307
+ ` -H "Mcp-Session-Id: $SID" \\`,
308
+ ` -d '${dq(callBody)}'`,
309
+ bar,
310
+ ].join("\n");
145
311
  }
146
312
 
147
- function printPayload(rpc, raw) {
313
+ function printCallResult(rpc, { raw = false } = {}) {
148
314
  if (raw) {
149
- process.stdout.write(`${JSON.stringify(rpc, null, 2)}\n`);
315
+ out.write(`${JSON.stringify(rpc, null, 2)}\n`);
150
316
  return;
151
317
  }
152
318
  const data = rpc?.result?.structuredContent ?? rpc?.result ?? rpc;
153
- process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
154
- }
155
-
156
- function readStdioMessage(buf) {
157
- const text = buf.toString("utf8");
158
- const headerEnd = text.indexOf("\r\n\r\n");
159
- const altEnd = text.indexOf("\n\n");
160
- if (headerEnd >= 0 || (altEnd >= 0 && /content-length:/i.test(text.slice(0, altEnd + 2)))) {
161
- const split = headerEnd >= 0 ? headerEnd : altEnd;
162
- const sep = headerEnd >= 0 ? 4 : 2;
163
- const header = text.slice(0, split);
164
- const m = header.match(/content-length:\s*(\d+)/i);
165
- if (!m) return null;
166
- const len = Number(m[1]);
167
- const start = split + sep;
168
- const body = Buffer.from(text, "utf8").subarray(start);
169
- if (body.length < len) return null;
170
- const json = JSON.parse(body.subarray(0, len).toString("utf8"));
171
- return { json, rest: body.subarray(len) };
319
+ if (data && typeof data === "object") {
320
+ const { ok, tool, locale, related, attribution, ...payload } = data;
321
+ out.write(`${JSON.stringify(Object.keys(payload).length ? payload : data, null, 2)}\n`);
322
+ return;
172
323
  }
173
- const nl = text.indexOf("\n");
174
- if (nl < 0) return null;
175
- const line = text.slice(0, nl).trim();
176
- const rest = Buffer.from(text.slice(nl + 1), "utf8");
177
- if (!line) return { json: null, rest };
178
- return { json: JSON.parse(line), rest };
179
- }
180
-
181
- function writeStdio(msg) {
182
- const json = JSON.stringify(msg);
183
- const body = Buffer.from(json, "utf8");
184
- process.stdout.write(`Content-Length: ${body.length}\r\n\r\n`);
185
- process.stdout.write(body);
324
+ out.write(`${JSON.stringify(data, null, 2)}\n`);
186
325
  }
187
326
 
188
327
  async function runStdioProxy() {
@@ -208,22 +347,22 @@ async function runStdioProxy() {
208
347
  const id = msg.id;
209
348
  try {
210
349
  if (method === "initialize") {
211
- const out = await mapsRpc("initialize", msg.params || {}, { id: id ?? 1 });
212
- sessionId = out.sessionId || sessionId;
213
- writeStdio(out.json);
350
+ const res = await mapsRpc("initialize", msg.params || {}, { id: id ?? 1 });
351
+ sessionId = res.sessionId || sessionId;
352
+ writeStdio(res.json);
214
353
  } else if (method === "notifications/initialized" || method === "initialized") {
215
- const out = await mapsRpc("notifications/initialized", msg.params || {}, {
354
+ const res = await mapsRpc("notifications/initialized", msg.params || {}, {
216
355
  notification: true,
217
356
  sessionId,
218
357
  });
219
- sessionId = out.sessionId || sessionId;
358
+ sessionId = res.sessionId || sessionId;
220
359
  if (id !== undefined) writeStdio({ jsonrpc: "2.0", id, result: {} });
221
360
  } else if (id === undefined) {
222
361
  await mapsRpc(method, msg.params || {}, { notification: true, sessionId });
223
362
  } else {
224
- const out = await mapsRpc(method, msg.params || {}, { id, sessionId });
225
- sessionId = out.sessionId || sessionId;
226
- writeStdio(out.json);
363
+ const res = await mapsRpc(method, msg.params || {}, { id, sessionId });
364
+ sessionId = res.sessionId || sessionId;
365
+ writeStdio(res.json);
227
366
  }
228
367
  } catch (err) {
229
368
  if (id !== undefined) {
@@ -235,72 +374,131 @@ async function runStdioProxy() {
235
374
  });
236
375
  }
237
376
 
238
- export async function main(argv) {
239
- const { flags, positionals } = parseArgv(argv);
240
-
241
- if (flags.version && positionals.length === 0) {
242
- process.stdout.write(`${packageVersion()}\n`);
243
- return;
377
+ function readStdioMessage(buf) {
378
+ const text = buf.toString("utf8");
379
+ const headerEnd = text.indexOf("\r\n\r\n");
380
+ const altEnd = text.indexOf("\n\n");
381
+ if (headerEnd >= 0 || (altEnd >= 0 && /content-length:/i.test(text.slice(0, altEnd + 2)))) {
382
+ const split = headerEnd >= 0 ? headerEnd : altEnd;
383
+ const sep = headerEnd >= 0 ? 4 : 2;
384
+ const header = text.slice(0, split);
385
+ const m = header.match(/content-length:\s*(\d+)/i);
386
+ if (!m) return null;
387
+ const len = Number(m[1]);
388
+ const start = split + sep;
389
+ const body = Buffer.from(text, "utf8").subarray(start);
390
+ if (body.length < len) return null;
391
+ return { json: JSON.parse(body.subarray(0, len).toString("utf8")), rest: body.subarray(len) };
244
392
  }
245
- if (argv.length === 0) {
393
+ const nl = text.indexOf("\n");
394
+ if (nl < 0) return null;
395
+ const line = text.slice(0, nl).trim();
396
+ const rest = Buffer.from(text.slice(nl + 1), "utf8");
397
+ if (!line) return { json: null, rest };
398
+ return { json: JSON.parse(line), rest };
399
+ }
400
+
401
+ function writeStdio(msg) {
402
+ const json = JSON.stringify(msg);
403
+ const body = Buffer.from(json, "utf8");
404
+ process.stdout.write(`Content-Length: ${body.length}\r\n\r\n`);
405
+ process.stdout.write(body);
406
+ }
407
+
408
+ export async function runCli(argv) {
409
+ const args = [...argv];
410
+ if (args.length === 0) {
246
411
  await runStdioProxy();
247
- return;
412
+ return 0;
248
413
  }
249
414
 
250
- const cmd = positionals[0] || "";
251
- if (flags.help && positionals.length === 0) {
252
- process.stdout.write(HELP);
253
- return;
415
+ const cmd = args[0];
416
+ if (cmd === "-V" || cmd === "--version" || cmd === "-v" || cmd === "version") {
417
+ out.write(`${packageVersion()}\n`);
418
+ return 0;
254
419
  }
255
- if (cmd === "help" || cmd === "--help") {
256
- process.stdout.write(HELP);
257
- return;
420
+ if (isHelpFlag(cmd) && args.length === 1) {
421
+ printHumanHelp(catalogTools());
422
+ return 0;
258
423
  }
259
- if (cmd === "update") {
260
- const ver = positionals[1] ? String(positionals[1]).replace(/^@/, "") : "latest";
261
- await new Promise((resolve, reject) => {
262
- const child = spawn("npm", ["i", "-g", `@ikeytz/maps-mcp@${ver}`], { stdio: "inherit" });
263
- child.on("exit", (code) => (code === 0 ? resolve() : reject(new Error(`npm exit ${code}`))));
264
- });
265
- return;
424
+ if (cmd === "serve" || cmd === "proxy") {
425
+ await runStdioProxy();
426
+ return 0;
427
+ }
428
+ if (cmd === "update" || cmd === "upgrade") {
429
+ const spec = args[1] && !args[1].startsWith("-") ? args[1] : "latest";
430
+ const pkg = spec === "latest" ? "@ikeytz/maps-mcp@latest" : `@ikeytz/maps-mcp@${spec}`;
431
+ log.write(`Updating ${pkg}…\n`);
432
+ const r = spawnSync("npm", ["i", "-g", "--prefer-online", pkg], { stdio: "inherit", shell: false });
433
+ if (r.status !== 0) return r.status || 1;
434
+ log.write(`Done. New terminal → ikeytz-maps-mcp --version\n`);
435
+ return 0;
266
436
  }
267
437
  if (cmd === "list") {
268
- if (flags.global) {
269
- process.stdout.write(`${globalCliFlagsLines().join("\n")}\n`);
270
- return;
438
+ const rest = args.slice(1);
439
+ for (const a of rest) {
440
+ if (a !== "--global") {
441
+ log.write(`Usage: ikeytz-maps-mcp list [--global]\n`);
442
+ return 2;
443
+ }
271
444
  }
272
- process.stdout.write(`${toolNames().join("\n")}\n\n${globalCliFlagsLines().join("\n")}\n`);
273
- return;
445
+ if (rest.includes("--global")) {
446
+ out.write(`${globalCliFlagsLines().join("\n")}\n`);
447
+ return 0;
448
+ }
449
+ const rows = catalogTools().map((t) => ({
450
+ tool: t.name,
451
+ args: argHint(t) || "—",
452
+ example: exampleForTool(t.name),
453
+ }));
454
+ out.write([...formatToolsAsciiTable(rows), ...globalCliFlagsLines()].join("\n") + "\n");
455
+ return 0;
274
456
  }
275
- if (cmd === "describe") {
276
- const name = positionals[1];
277
- const tool = toolByName(name);
278
- if (!tool) {
279
- console.error(`unknown tool: ${name || "(fehlt)"}`);
280
- process.exitCode = 1;
281
- return;
457
+
458
+ let toolName;
459
+ let rest;
460
+ if (cmd === "describe" || cmd === "call") {
461
+ toolName = args[1];
462
+ rest = args.slice(2);
463
+ if (!toolName) {
464
+ log.write(`Usage: ikeytz-maps-mcp ${cmd} <toolName> …\n`);
465
+ return 2;
282
466
  }
283
- process.stdout.write(formatToolHelp(tool));
284
- return;
467
+ } else {
468
+ toolName = cmd;
469
+ rest = args.slice(1);
285
470
  }
286
471
 
287
- const toolName = cmd === "call" ? positionals[1] : cmd;
288
- const tool = toolByName(toolName);
289
- if (!tool) {
290
- console.error(`unknown command: ${cmd}\n`);
291
- process.stdout.write(HELP);
292
- process.exitCode = 1;
293
- return;
472
+ if (cmd === "describe" || rest.some(isHelpFlag)) {
473
+ const tool = toolByName(toolName);
474
+ if (!tool) {
475
+ log.write(`Unknown tool: ${toolName}\n`);
476
+ return 1;
477
+ }
478
+ out.write(`${formatDescribe(tool)}\n`);
479
+ return 0;
294
480
  }
295
- if (flags.help) {
296
- process.stdout.write(formatToolHelp(tool));
297
- return;
481
+
482
+ const wantRaw = rest.includes("--raw");
483
+ const showMcp = wantsShowMcpCommand(rest);
484
+ let callArgs;
485
+ try {
486
+ callArgs = parseToolArgs(stripCliDisplayFlags(rest.filter((a) => a !== "--raw")));
487
+ } catch (err) {
488
+ log.write(`${err.message || err}\n`);
489
+ return 2;
298
490
  }
299
- const args = argsFromFlags(flags);
300
- if (flags.showMcp) {
301
- process.stdout.write(showMcpCommand(toolName, args));
302
- return;
491
+
492
+ if (showMcp) {
493
+ out.write(`${formatMcpCurlHeading(toolName, callArgs)}\n`);
494
+ return 0;
303
495
  }
304
- const rpc = await callTool(toolName, args);
305
- printPayload(rpc, flags.raw);
496
+
497
+ const rpc = await callTool(toolName, callArgs);
498
+ printCallResult(rpc, { raw: wantRaw });
499
+ if (rpc?.result?.isError || rpc?.error) process.exitCode = 1;
500
+ return process.exitCode || 0;
306
501
  }
502
+
503
+ /** @deprecated use runCli — kept for older bin */
504
+ export const main = runCli;