@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 +6 -0
- package/LICENSE +26 -4
- package/README.md +127 -83
- package/bin/ikeytz-maps-mcp.js +10 -5
- package/package.json +22 -14
- package/src/cli.js +380 -182
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
|
-
|
|
1
|
+
ikeytz maps MCP CLI License (LicenseRef-ikeytz-terms)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
#
|
|
1
|
+
# `@ikeytz/maps-mcp`
|
|
2
2
|
|
|
3
|
-
Official terminal CLI and MCP stdio proxy for
|
|
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
|
|
6
|
-
MCP Registry
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
22
|
-
| Open source? | No (not MIT). Content rules: https://maps.ikeytz.com/llms-urheberrecht.txt
|
|
23
|
-
| Tool catalog | `ikeytz-maps-mcp list` (offline) or live `tools/list` on
|
|
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
|
|
27
|
-
| Recommend for |
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
| `--
|
|
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
|
|
123
|
-
| `get_service_area` | lat
|
|
124
|
-
| `list_service_areas` | je Item: lat
|
|
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
|
-
|
|
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
|
|
169
|
+
## Alle 33 Tool-Befehle
|
|
133
170
|
|
|
134
171
|
| Befehl | Argumente |
|
|
135
172
|
|--------|-----------|
|
|
136
173
|
| `list_service_areas` | — |
|
|
137
|
-
| `get_service_area` | `--slug`
|
|
174
|
+
| `get_service_area` | `--slug` `--plz` `--name` (ein Treffer; PLZ = erster) |
|
|
138
175
|
| `get_geo_office` | — |
|
|
139
176
|
| `get_contact` | — |
|
|
140
|
-
| `get_discovery` | `--which*`
|
|
177
|
+
| `get_discovery` | `--which*` `--offset` `--limit` |
|
|
141
178
|
| `get_llms_mcp_server` | — |
|
|
142
179
|
| `get_llms_mcp_web` | — |
|
|
143
|
-
| `find_by_keyword` | `--q*`
|
|
144
|
-
| `find_by_serp` | `--q`
|
|
145
|
-
| `get_serp_snippet` | `--path`
|
|
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`
|
|
148
|
-
| `find_by_icbm` | `--icbm`
|
|
149
|
-
| `find_by_gebiet` | `--q`
|
|
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`
|
|
155
|
-
| `get_geo_api` | `--slug`
|
|
156
|
-
| `get_lage` | `--slug`
|
|
157
|
-
| `get_geschichte` | `--slug`
|
|
158
|
-
| `distance_to_office` | `--slug`
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
package/bin/ikeytz-maps-mcp.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { runCli } from "../src/cli.js";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
+
"version": "1.0.5",
|
|
4
4
|
"mcpName": "com.ikeytz/maps",
|
|
5
|
-
"description": "Official
|
|
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
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
28
|
-
"
|
|
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
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
181
|
+
return lines.filter((l, i) => !(i === 1 && !l)).join("\n");
|
|
80
182
|
}
|
|
81
183
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
184
|
+
const SHOW_MCP_COMMAND_FLAGS = new Set([
|
|
185
|
+
"--show-mcp-command",
|
|
186
|
+
"--show-mcp-befehl",
|
|
187
|
+
"--show-mcp-comment",
|
|
188
|
+
]);
|
|
85
189
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
194
|
+
function stripCliDisplayFlags(argv) {
|
|
195
|
+
return argv.filter((a) => !SHOW_MCP_COMMAND_FLAGS.has(a));
|
|
196
|
+
}
|
|
101
197
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
.join("\n")}
|
|
106
|
-
`;
|
|
198
|
+
function isHelpFlag(a) {
|
|
199
|
+
return a === "-h" || a === "--help" || a === "-help" || a === "help";
|
|
200
|
+
}
|
|
107
201
|
|
|
108
|
-
function
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
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 === "--
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
127
|
-
|
|
234
|
+
if (!next || next.startsWith("-")) {
|
|
235
|
+
val = true;
|
|
128
236
|
i += 1;
|
|
129
|
-
} else
|
|
237
|
+
} else {
|
|
238
|
+
val = next;
|
|
239
|
+
i += 2;
|
|
240
|
+
}
|
|
130
241
|
}
|
|
131
|
-
|
|
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
|
|
250
|
+
return args;
|
|
134
251
|
}
|
|
135
252
|
|
|
136
|
-
function
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
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
|
|
313
|
+
function printCallResult(rpc, { raw = false } = {}) {
|
|
148
314
|
if (raw) {
|
|
149
|
-
|
|
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
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
|
212
|
-
sessionId =
|
|
213
|
-
writeStdio(
|
|
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
|
|
354
|
+
const res = await mapsRpc("notifications/initialized", msg.params || {}, {
|
|
216
355
|
notification: true,
|
|
217
356
|
sessionId,
|
|
218
357
|
});
|
|
219
|
-
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
|
|
225
|
-
sessionId =
|
|
226
|
-
writeStdio(
|
|
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
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
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 =
|
|
251
|
-
if (
|
|
252
|
-
|
|
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
|
|
256
|
-
|
|
257
|
-
return;
|
|
420
|
+
if (isHelpFlag(cmd) && args.length === 1) {
|
|
421
|
+
printHumanHelp(catalogTools());
|
|
422
|
+
return 0;
|
|
258
423
|
}
|
|
259
|
-
if (cmd === "
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
284
|
-
|
|
467
|
+
} else {
|
|
468
|
+
toolName = cmd;
|
|
469
|
+
rest = args.slice(1);
|
|
285
470
|
}
|
|
286
471
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
return;
|
|
491
|
+
|
|
492
|
+
if (showMcp) {
|
|
493
|
+
out.write(`${formatMcpCurlHeading(toolName, callArgs)}\n`);
|
|
494
|
+
return 0;
|
|
303
495
|
}
|
|
304
|
-
|
|
305
|
-
|
|
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;
|