@ikeytz/maps-mcp 1.0.3 → 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,16 @@
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
+
9
+ ## 1.0.4
10
+
11
+ - Legal tools: get_legal, Urheberrecht/Copyright/Impressum (maps text), AGB/Nutzung/Datenschutz/Widerruf (www URLs).
12
+ - CLI flags like www: `list` prints global flags, `list --global`, per-tool `-h` with args + `--show-mcp-command`.
13
+
3
14
  ## 1.0.3
4
15
 
5
16
  - More maps-only commands: slugs, PLZ index, hub, embed, geo_api, Lage, Geschichte, Distanz, URL-resolve, Google-Maps-Paste.
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.3
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,100 +117,110 @@ 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
134
+ ikeytz-maps-mcp list # 33 Tools (offline) + global CLI flags
135
+ ikeytz-maps-mcp list --global # nur globale Flags
101
136
  ikeytz-maps-mcp describe NAME # = ikeytz-maps-mcp NAME -h
102
137
  ikeytz-maps-mcp call NAME … # oder: ikeytz-maps-mcp NAME …
103
138
  ```
104
139
 
105
140
  `*` = required · Schema offline: `ikeytz-maps-mcp <tool> -h`
106
141
 
107
- ## CLI-Flags (nie an MCP gesendet)
142
+ ## CLI-Flags ( nie an MCP gesendet )
108
143
 
109
144
  | Flag | Wirkung |
110
145
  |------|---------|
111
- | `-h`, `--help` | Pro-Tool-Help (offline) |
146
+ | `-h`, `--help`, `-help` | Pro-Tool-Help (offline); ohne Tool = volle Hilfe |
112
147
  | `--show-mcp-command` | curl 1a/1b/2 only |
113
- | `--raw` | JSON structuredContent |
148
+ | `--show-mcp-befehl` | Alias |
149
+ | `--show-mcp-comment` | Alias (gleicher Curl-Block) |
150
+ | `--raw` | JSON `structuredContent` |
114
151
  | `--json '{…}'` | Roh-Arguments |
152
+ | `list --global` | nur globale Flags (offline) |
153
+
154
+ Diese Flags sind **Terminal-CLI**. MCP Inspector / Cursor zeigt nur **Tools**, keine CLI-Flags.
115
155
 
116
156
  ## Geo-Tools — Payload (vom Server, CLI spiegelt 1:1)
117
157
 
118
158
  | Tool | Koordinaten-Felder in der Antwort |
119
159
  |------|-----------------------------------|
120
- | `get_geo_office` | lat, lng, google_maps, icbm (+ Adresse; kein Walk-in) |
121
- | `get_service_area` | lat, lng, google_maps, icbm, embed_map, geo_api (+ slug, plz, …) |
122
- | `list_service_areas` | je Item: lat, lng, google_maps, icbm, embed_map, geo_api |
123
-
124
- `embed_map`: `https://maps.ikeytz.com/{slug}`
125
- `geo_api`: `https://maps.ikeytz.com/api/v1/embed/ort/{slug}` (public JSON, kein Passwort)
126
- `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` |
127
163
 
128
- 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`
129
168
 
130
- ## Alle Tool-Befehle (HTTP + WebMCP + CLI, dieselbe Liste)
169
+ ## Alle 33 Tool-Befehle
131
170
 
132
171
  | Befehl | Argumente |
133
172
  |--------|-----------|
134
173
  | `list_service_areas` | — |
135
- | `get_service_area` | `--slug` · `--plz` · `--name` (ein Treffer; PLZ = erster) |
174
+ | `get_service_area` | `--slug` `--plz` `--name` (ein Treffer; PLZ = erster) |
136
175
  | `get_geo_office` | — |
137
176
  | `get_contact` | — |
138
- | `get_discovery` | `--which*` · `--offset` · `--limit` |
177
+ | `get_discovery` | `--which*` `--offset` `--limit` |
139
178
  | `get_llms_mcp_server` | — |
140
179
  | `get_llms_mcp_web` | — |
141
- | `find_by_keyword` | `--q*` · `--limit` |
142
- | `find_by_serp` | `--q` · `--path` · `--url` · `--slug` · `--limit` |
143
- | `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` |
144
183
  | `find_by_plz` | `--plz*` (alle Gebiete dieser PLZ) |
145
- | `find_by_geo` | `--lat` · `--lng` · `--icbm` · `--radius_km` · `--limit` |
146
- | `find_by_icbm` | `--icbm` · `--lat` · `--lng` · `--radius_km` · `--limit` |
147
- | `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` |
148
187
  | `find_by_area` | Alias von `find_by_gebiet` |
149
188
  | `list_slugs` | — |
150
189
  | `list_plz` | — |
151
190
  | `get_hub` | — |
152
- | `get_embed_map` | `--slug` · `--plz` · `--name` |
153
- | `get_geo_api` | `--slug` · `--plz` · `--name` |
154
- | `get_lage` | `--slug` · `--plz` · `--name` |
155
- | `get_geschichte` | `--slug` · `--plz` · `--name` |
156
- | `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` |
157
196
  | `resolve_maps_url` | `--url` |
158
197
  | `find_by_google_maps` | `--q` (Koordinaten-Paste) |
198
+ | `get_legal` | `--doc*` urheberrecht\|copyright\|impressum\|agb\|nutzungsbedingungen\|datenschutz\|widerruf |
199
+ | `get_urheberrecht` | — (maps `/llms-urheberrecht.txt`) |
200
+ | `get_copyright` | — |
201
+ | `get_impressum` | — |
202
+ | `get_agb` | — (www-URL) |
203
+ | `get_nutzungsbedingungen` | — (www-URL) |
204
+ | `get_datenschutz` | — (www-URL) |
205
+ | `get_widerruf` | — (www-URL) |
159
206
 
160
- 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`).
161
208
 
162
209
  ## MCP hosts (any client — not only Cursor / Claude)
163
210
 
164
- ### Option A — HTTP (empfohlen, kein npm nötig)
165
-
166
- Jeder Host mit Streamable HTTP MCP:
211
+ **Option A — HTTP (empfohlen, kein npm nötig)**
212
+ Jeder Host mit **Streamable HTTP MCP**:
167
213
 
168
214
  | Feld | Wert |
169
215
  |------|------|
170
- | URL | https://maps.ikeytz.com/mcp |
171
- | Registry | com.ikeytz/maps |
216
+ | URL | `https://maps.ikeytz.com/mcp` |
217
+ | Registry | `com.ikeytz/maps` |
172
218
  | Card | https://maps.ikeytz.com/.well-known/mcp/server-card.json |
173
219
 
174
- Beispiele: Windsurf, Cline, Continue, Claude Desktop (Remote), eigene Agenten — sofern MCP HTTP unterstützt.
175
-
176
- ### Option B — stdio via npm (dieses Paket)
220
+ Beispiele: Windsurf, Cline, Continue, Claude Desktop (Remote), eigene Agenten — **sofern MCP HTTP unterstützt**.
177
221
 
178
- 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, …):
179
224
 
180
225
  ```json
181
226
  {
@@ -188,7 +233,7 @@ Jeder Host mit `command` + `args` (Cursor, Claude Desktop, Windsurf, …):
188
233
  }
189
234
  ```
190
235
 
191
- Global installiert: `"command": "ikeytz-maps-mcp"` (ohne args, leerer stdio-Proxy).
236
+ Global installiert: `"command": "ikeytz-maps-mcp"` (ohne `args`, leerer stdio-Proxy).
192
237
 
193
238
  www bleibt **zweiter** Eintrag:
194
239
 
@@ -199,11 +244,10 @@ www bleibt **zweiter** Eintrag:
199
244
  }
200
245
  ```
201
246
 
202
- ### Option C — Terminal only
203
-
204
- `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.
205
249
 
206
- 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).
207
251
 
208
252
  ## Env
209
253
 
@@ -215,10 +259,20 @@ Alle Optionen nutzen dieselbe SSOT: `https://maps.ikeytz.com/mcp` (read/link onl
215
259
 
216
260
  Nicht `IKEYTZ_MCP_URL` — das ist www (`@ikeytz/mcp`).
217
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
+
218
272
  ## Legal
219
273
 
220
- License: LicenseRef-ikeytz-terms (LICENSE in this package)
221
- https://www.ikeytz.com/urheberrecht
222
- https://maps.ikeytz.com/llms-urheberrecht.txt
223
- https://maps.ikeytz.com/.well-known/ai.txt
224
- 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.3",
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,123 +1,327 @@
1
- import { spawn } from "node:child_process";
2
- import { callTool, mapsMcpUrl, mapsRpc, packageVersion, showMcpCommand, toolByName, toolNames } from "./proxy.js";
3
-
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
- ];
19
- const NUMBER_FLAGS = new Set(["offset", "limit", "lat", "lng", "radius_km"]);
20
-
21
- const HELP = `ikeytz-maps-mcp ${packageVersion()}
22
- Remote: ${mapsMcpUrl()}
23
- Nur maps. Nicht @ikeytz/mcp / www.
24
-
25
- Ohne Args stdio-Proxy (Cursor/Claude)
26
- help | --help diese Hilfe
27
- --version Version
28
- update npm i -g @ikeytz/maps-mcp@latest
29
- list ${toolNames().length} Tools (offline)
30
- describe <tool> Schema (offline)
31
- call <tool> … wie <tool> …
32
- <tool> Payload von maps /mcp
33
- <tool> -h Args (offline)
34
- <tool> --show-mcp-command | --show-mcp-befehl
35
- <tool> --raw volles JSON-RPC
36
- <tool> --json '{}'
37
-
38
- Tools:
39
- ${toolNames()
40
- .map((n) => ` ${n}`)
41
- .join("\n")}
42
- `;
43
-
44
- function parseArgv(argv) {
45
- const flags = {};
46
- const positionals = [];
47
- for (let i = 0; i < argv.length; i++) {
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
+ */
7
+
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
+ }
92
+
93
+ function globalCliFlagsLines() {
94
+ return [
95
+ "",
96
+ "Global CLI flags (every tool — not sent to MCP)",
97
+ " -h, --help, -help per-tool help (offline)",
98
+ " --raw JSON structuredContent",
99
+ " --show-mcp-command curl 1a/1b/2 only — no MCP call",
100
+ " aliases: --show-mcp-befehl · --show-mcp-comment",
101
+ "",
102
+ "Default tool output payload only (no ok/tool/meta envelope)",
103
+ " --name=value --name value --json '{...}'",
104
+ ];
105
+ }
106
+
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`);
148
+ }
149
+
150
+ function formatDescribe(tool) {
151
+ const schema = tool.inputSchema || { type: "object", properties: {} };
152
+ const props = schema.properties || {};
153
+ const req = new Set(schema.required || []);
154
+ const lines = [tool.name, tool.description || "", "", "Arguments"];
155
+ const keys = Object.keys(props);
156
+ if (!keys.length) {
157
+ lines.push(" (none)");
158
+ } else {
159
+ for (const key of keys) {
160
+ const p = props[key] || {};
161
+ const type = p.enum ? p.enum.join("|") : p.type || "any";
162
+ const star = req.has(key) ? "*" : " ";
163
+ const desc = p.description ? ` ${p.description}` : "";
164
+ lines.push(` ${star} --${key.padEnd(16)} ${type}${desc}`);
165
+ }
166
+ if (req.size) {
167
+ lines.push("");
168
+ lines.push(" * required");
169
+ }
170
+ }
171
+ lines.push("");
172
+ lines.push("CLI flags (not sent to MCP)");
173
+ lines.push(" -h, --help, -help this help (offline, no MCP call)");
174
+ lines.push(" --raw JSON structuredContent");
175
+ lines.push(" --show-mcp-command curl 1a/1b/2 only (aliases: --show-mcp-befehl, --show-mcp-comment)");
176
+ lines.push("");
177
+ lines.push("Examples");
178
+ lines.push(` ${exampleForTool(tool.name)}`);
179
+ lines.push(` ikeytz-maps-mcp ${tool.name} -h`);
180
+ lines.push(` ikeytz-maps-mcp ${tool.name} --show-mcp-command`);
181
+ return lines.filter((l, i) => !(i === 1 && !l)).join("\n");
182
+ }
183
+
184
+ const SHOW_MCP_COMMAND_FLAGS = new Set([
185
+ "--show-mcp-command",
186
+ "--show-mcp-befehl",
187
+ "--show-mcp-comment",
188
+ ]);
189
+
190
+ function wantsShowMcpCommand(argv) {
191
+ return argv.some((a) => SHOW_MCP_COMMAND_FLAGS.has(a));
192
+ }
193
+
194
+ function stripCliDisplayFlags(argv) {
195
+ return argv.filter((a) => !SHOW_MCP_COMMAND_FLAGS.has(a));
196
+ }
197
+
198
+ function isHelpFlag(a) {
199
+ return a === "-h" || a === "--help" || a === "-help" || a === "help";
200
+ }
201
+
202
+ function parseToolArgs(argv) {
203
+ const args = {};
204
+ let i = 0;
205
+ while (i < argv.length) {
48
206
  const a = argv[i];
49
- if (a === "--raw") flags.raw = true;
50
- else if (a === "-h" || a === "--help") flags.help = true;
51
- else if (a === "--version") flags.version = true;
52
- else if (a === "--show-mcp-command" || a === "--show-mcp-befehl") flags.showMcp = true;
53
- else if (a === "--json") flags.json = argv[++i] || "";
54
- else if (a.startsWith("--json=")) flags.json = a.slice(7);
55
- 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("--")) {
56
224
  const eq = a.indexOf("=");
57
- if (eq > 0) flags[a.slice(2, eq)] = a.slice(eq + 1);
58
- else {
59
- 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);
60
233
  const next = argv[i + 1];
61
- if (next && !next.startsWith("-")) {
62
- flags[key] = next;
234
+ if (!next || next.startsWith("-")) {
235
+ val = true;
63
236
  i += 1;
64
- } else flags[key] = true;
237
+ } else {
238
+ val = next;
239
+ i += 2;
240
+ }
65
241
  }
66
- } 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}`);
67
249
  }
68
- return { flags, positionals };
250
+ return args;
69
251
  }
70
252
 
71
- function argsFromFlags(flags) {
72
- const args = {};
73
- if (flags.json) Object.assign(args, JSON.parse(flags.json));
74
- for (const key of TOOL_FLAGS) {
75
- if (flags[key] != null && flags[key] !== true) {
76
- args[key] = NUMBER_FLAGS.has(key) ? Number(flags[key]) : String(flags[key]);
77
- }
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}`);
78
271
  }
79
- 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");
80
311
  }
81
312
 
82
- function printPayload(rpc, raw) {
313
+ function printCallResult(rpc, { raw = false } = {}) {
83
314
  if (raw) {
84
- process.stdout.write(`${JSON.stringify(rpc, null, 2)}\n`);
315
+ out.write(`${JSON.stringify(rpc, null, 2)}\n`);
85
316
  return;
86
317
  }
87
318
  const data = rpc?.result?.structuredContent ?? rpc?.result ?? rpc;
88
- process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
89
- }
90
-
91
- function readStdioMessage(buf) {
92
- const text = buf.toString("utf8");
93
- const headerEnd = text.indexOf("\r\n\r\n");
94
- const altEnd = text.indexOf("\n\n");
95
- if (headerEnd >= 0 || (altEnd >= 0 && /content-length:/i.test(text.slice(0, altEnd + 2)))) {
96
- const split = headerEnd >= 0 ? headerEnd : altEnd;
97
- const sep = headerEnd >= 0 ? 4 : 2;
98
- const header = text.slice(0, split);
99
- const m = header.match(/content-length:\s*(\d+)/i);
100
- if (!m) return null;
101
- const len = Number(m[1]);
102
- const start = split + sep;
103
- const body = Buffer.from(text, "utf8").subarray(start);
104
- if (body.length < len) return null;
105
- const json = JSON.parse(body.subarray(0, len).toString("utf8"));
106
- 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;
107
323
  }
108
- const nl = text.indexOf("\n");
109
- if (nl < 0) return null;
110
- const line = text.slice(0, nl).trim();
111
- const rest = Buffer.from(text.slice(nl + 1), "utf8");
112
- if (!line) return { json: null, rest };
113
- return { json: JSON.parse(line), rest };
114
- }
115
-
116
- function writeStdio(msg) {
117
- const json = JSON.stringify(msg);
118
- const body = Buffer.from(json, "utf8");
119
- process.stdout.write(`Content-Length: ${body.length}\r\n\r\n`);
120
- process.stdout.write(body);
324
+ out.write(`${JSON.stringify(data, null, 2)}\n`);
121
325
  }
122
326
 
123
327
  async function runStdioProxy() {
@@ -143,22 +347,22 @@ async function runStdioProxy() {
143
347
  const id = msg.id;
144
348
  try {
145
349
  if (method === "initialize") {
146
- const out = await mapsRpc("initialize", msg.params || {}, { id: id ?? 1 });
147
- sessionId = out.sessionId || sessionId;
148
- writeStdio(out.json);
350
+ const res = await mapsRpc("initialize", msg.params || {}, { id: id ?? 1 });
351
+ sessionId = res.sessionId || sessionId;
352
+ writeStdio(res.json);
149
353
  } else if (method === "notifications/initialized" || method === "initialized") {
150
- const out = await mapsRpc("notifications/initialized", msg.params || {}, {
354
+ const res = await mapsRpc("notifications/initialized", msg.params || {}, {
151
355
  notification: true,
152
356
  sessionId,
153
357
  });
154
- sessionId = out.sessionId || sessionId;
358
+ sessionId = res.sessionId || sessionId;
155
359
  if (id !== undefined) writeStdio({ jsonrpc: "2.0", id, result: {} });
156
360
  } else if (id === undefined) {
157
361
  await mapsRpc(method, msg.params || {}, { notification: true, sessionId });
158
362
  } else {
159
- const out = await mapsRpc(method, msg.params || {}, { id, sessionId });
160
- sessionId = out.sessionId || sessionId;
161
- writeStdio(out.json);
363
+ const res = await mapsRpc(method, msg.params || {}, { id, sessionId });
364
+ sessionId = res.sessionId || sessionId;
365
+ writeStdio(res.json);
162
366
  }
163
367
  } catch (err) {
164
368
  if (id !== undefined) {
@@ -170,67 +374,131 @@ async function runStdioProxy() {
170
374
  });
171
375
  }
172
376
 
173
- export async function main(argv) {
174
- const { flags, positionals } = parseArgv(argv);
175
-
176
- if (flags.version && positionals.length === 0) {
177
- process.stdout.write(`${packageVersion()}\n`);
178
- 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) };
179
392
  }
180
- 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) {
181
411
  await runStdioProxy();
182
- return;
412
+ return 0;
183
413
  }
184
414
 
185
- const cmd = positionals[0] || "";
186
- if (flags.help && positionals.length === 0) {
187
- process.stdout.write(HELP);
188
- return;
415
+ const cmd = args[0];
416
+ if (cmd === "-V" || cmd === "--version" || cmd === "-v" || cmd === "version") {
417
+ out.write(`${packageVersion()}\n`);
418
+ return 0;
189
419
  }
190
- if (cmd === "help" || cmd === "--help") {
191
- process.stdout.write(HELP);
192
- return;
420
+ if (isHelpFlag(cmd) && args.length === 1) {
421
+ printHumanHelp(catalogTools());
422
+ return 0;
193
423
  }
194
- if (cmd === "update") {
195
- await new Promise((resolve, reject) => {
196
- const child = spawn("npm", ["i", "-g", "@ikeytz/maps-mcp@latest"], { stdio: "inherit" });
197
- child.on("exit", (code) => (code === 0 ? resolve() : reject(new Error(`npm exit ${code}`))));
198
- });
199
- 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;
200
436
  }
201
437
  if (cmd === "list") {
202
- process.stdout.write(`${toolNames().join("\n")}\n`);
203
- 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
+ }
444
+ }
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;
204
456
  }
205
- if (cmd === "describe") {
206
- const name = positionals[1];
207
- const tool = toolByName(name);
208
- if (!tool) {
209
- console.error(`unknown tool: ${name || "(fehlt)"}`);
210
- process.exitCode = 1;
211
- 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;
212
466
  }
213
- process.stdout.write(`${JSON.stringify(tool, null, 2)}\n`);
214
- return;
467
+ } else {
468
+ toolName = cmd;
469
+ rest = args.slice(1);
215
470
  }
216
471
 
217
- const toolName = cmd === "call" ? positionals[1] : cmd;
218
- const tool = toolByName(toolName);
219
- if (!tool) {
220
- console.error(`unknown command: ${cmd}\n`);
221
- process.stdout.write(HELP);
222
- process.exitCode = 1;
223
- 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;
224
480
  }
225
- if (flags.help) {
226
- process.stdout.write(`${tool.name}\n${tool.description}\n${JSON.stringify(tool.inputSchema, null, 2)}\n`);
227
- 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;
228
490
  }
229
- const args = argsFromFlags(flags);
230
- if (flags.showMcp) {
231
- process.stdout.write(showMcpCommand(toolName, args));
232
- return;
491
+
492
+ if (showMcp) {
493
+ out.write(`${formatMcpCurlHeading(toolName, callArgs)}\n`);
494
+ return 0;
233
495
  }
234
- const rpc = await callTool(toolName, args);
235
- 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;
236
501
  }
502
+
503
+ /** @deprecated use runCli — kept for older bin */
504
+ export const main = runCli;
@@ -432,6 +432,113 @@
432
432
  }
433
433
  }
434
434
  }
435
+ },
436
+ {
437
+ "name": "get_legal",
438
+ "description": "Legal: urheberrecht|copyright|impressum (maps-Text) oder agb|nutzungsbedingungen|datenschutz|widerruf (www-URL).",
439
+ "inputSchema": {
440
+ "type": "object",
441
+ "properties": {
442
+ "doc": {
443
+ "type": "string",
444
+ "enum": [
445
+ "urheberrecht",
446
+ "copyright",
447
+ "impressum",
448
+ "agb",
449
+ "nutzungsbedingungen",
450
+ "datenschutz",
451
+ "widerruf"
452
+ ]
453
+ },
454
+ "offset": {
455
+ "type": "integer"
456
+ },
457
+ "limit": {
458
+ "type": "integer"
459
+ }
460
+ },
461
+ "required": [
462
+ "doc"
463
+ ]
464
+ }
465
+ },
466
+ {
467
+ "name": "get_urheberrecht",
468
+ "description": "Karten-Urheberrecht: maps /llms-urheberrecht.txt (HTML auf www).",
469
+ "inputSchema": {
470
+ "type": "object",
471
+ "properties": {
472
+ "offset": {
473
+ "type": "integer"
474
+ },
475
+ "limit": {
476
+ "type": "integer"
477
+ }
478
+ }
479
+ }
480
+ },
481
+ {
482
+ "name": "get_copyright",
483
+ "description": "Karten-Copyright EN: maps /llms-copyright.txt.",
484
+ "inputSchema": {
485
+ "type": "object",
486
+ "properties": {
487
+ "offset": {
488
+ "type": "integer"
489
+ },
490
+ "limit": {
491
+ "type": "integer"
492
+ }
493
+ }
494
+ }
495
+ },
496
+ {
497
+ "name": "get_impressum",
498
+ "description": "Impressum/Kontakt-Text: maps /llms-impressum-kontakt.txt.",
499
+ "inputSchema": {
500
+ "type": "object",
501
+ "properties": {
502
+ "offset": {
503
+ "type": "integer"
504
+ },
505
+ "limit": {
506
+ "type": "integer"
507
+ }
508
+ }
509
+ }
510
+ },
511
+ {
512
+ "name": "get_agb",
513
+ "description": "AGB: Link www.ikeytz.com/agb (HTML bleibt www, kein Preise-Corpus).",
514
+ "inputSchema": {
515
+ "type": "object",
516
+ "properties": {}
517
+ }
518
+ },
519
+ {
520
+ "name": "get_nutzungsbedingungen",
521
+ "description": "Nutzungsbedingungen: Link www (HTML bleibt www).",
522
+ "inputSchema": {
523
+ "type": "object",
524
+ "properties": {}
525
+ }
526
+ },
527
+ {
528
+ "name": "get_datenschutz",
529
+ "description": "Datenschutz: Link www (HTML bleibt www).",
530
+ "inputSchema": {
531
+ "type": "object",
532
+ "properties": {}
533
+ }
534
+ },
535
+ {
536
+ "name": "get_widerruf",
537
+ "description": "Widerruf: Link www (HTML bleibt www).",
538
+ "inputSchema": {
539
+ "type": "object",
540
+ "properties": {}
541
+ }
435
542
  }
436
543
  ]
437
544
  }