@ikeytz/maps-mcp 1.0.4 → 1.0.6
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 +11 -0
- package/LICENSE +26 -4
- package/README.md +174 -96
- package/bin/ikeytz-maps-mcp.js +10 -5
- package/package.json +22 -14
- package/src/cli.js +393 -182
- package/src/tools-catalog.json +475 -5
package/src/cli.js
CHANGED
|
@@ -1,52 +1,166 @@
|
|
|
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
|
+
site_overview: `${p} site_overview --locale=de`,
|
|
61
|
+
get_business_identity: `${p} get_business_identity --locale=de`,
|
|
62
|
+
list_nav: `${p} list_nav --locale=de`,
|
|
63
|
+
list_footer: `${p} list_footer --locale=de`,
|
|
64
|
+
list_locales: `${p} list_locales`,
|
|
65
|
+
resolve_locale_url: `${p} resolve_locale_url --path=/pattonville --locale=de`,
|
|
66
|
+
get_prices: `${p} get_prices --locale=de`,
|
|
67
|
+
get_about: `${p} get_about --locale=de`,
|
|
68
|
+
get_links: `${p} get_links --locale=de`,
|
|
69
|
+
get_llms_txt: `${p} get_llms_txt`,
|
|
70
|
+
get_sitemap_txt: `${p} get_sitemap_txt`,
|
|
71
|
+
compose_tel_mobile: `${p} compose_tel_mobile`,
|
|
72
|
+
compose_mailto: `${p} compose_mailto`,
|
|
73
|
+
};
|
|
74
|
+
return map[name] || `${p} ${name}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function argHint(tool) {
|
|
78
|
+
const props = Object.keys(tool.inputSchema?.properties || {});
|
|
79
|
+
const req = tool.inputSchema?.required || [];
|
|
80
|
+
if (!props.length) return "";
|
|
81
|
+
return props.map((k) => (req.includes(k) ? `--${k}*` : `--${k}`)).join(" ");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function formatToolsAsciiTable(rows) {
|
|
85
|
+
const n = rows.length;
|
|
86
|
+
const cn = String(n).length;
|
|
87
|
+
const c0 = Math.max(4, ...rows.map((r) => r.tool.length), "TOOL".length);
|
|
88
|
+
const c1 = Math.max(4, ...rows.map((r) => r.args.length), "ARGS".length);
|
|
89
|
+
const pad = (s, w, align = "left") => {
|
|
90
|
+
const t = String(s);
|
|
91
|
+
if (t.length >= w) return t;
|
|
92
|
+
const sp = " ".repeat(w - t.length);
|
|
93
|
+
return align === "right" ? sp + t : t + sp;
|
|
94
|
+
};
|
|
95
|
+
const rule = `${"─".repeat(cn)}─┼─${"─".repeat(c0)}─┼─${"─".repeat(c1)}─┼─${"─".repeat(7)}`;
|
|
96
|
+
const lines = [`${pad("#", cn, "right")} │ ${pad("TOOL", c0)} │ ${pad("ARGS", c1)} │ EXAMPLE`, rule];
|
|
97
|
+
rows.forEach((r, i) => {
|
|
98
|
+
lines.push(
|
|
99
|
+
`${pad(String(i + 1), cn, "right")} │ ${pad(r.tool, c0)} │ ${pad(r.args, c1)} │ ${r.example}`
|
|
100
|
+
);
|
|
101
|
+
lines.push(rule);
|
|
102
|
+
});
|
|
103
|
+
return lines;
|
|
104
|
+
}
|
|
21
105
|
|
|
22
106
|
function globalCliFlagsLines() {
|
|
23
107
|
return [
|
|
108
|
+
"",
|
|
24
109
|
"Global CLI flags (every tool — not sent to MCP)",
|
|
25
110
|
" -h, --help, -help per-tool help (offline)",
|
|
26
111
|
" --raw JSON structuredContent",
|
|
27
|
-
" --show-mcp-command curl 1a/1b/2 only — no MCP call
|
|
28
|
-
"
|
|
112
|
+
" --show-mcp-command curl 1a/1b/2 only — no MCP call",
|
|
113
|
+
" aliases: --show-mcp-befehl · --show-mcp-comment",
|
|
29
114
|
"",
|
|
30
115
|
"Default tool output payload only (no ok/tool/meta envelope)",
|
|
31
|
-
" --name=value --name value",
|
|
116
|
+
" --name=value --name value --json '{...}'",
|
|
32
117
|
];
|
|
33
118
|
}
|
|
34
119
|
|
|
35
|
-
function
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
120
|
+
function printHumanHelp(tools) {
|
|
121
|
+
const lines = [
|
|
122
|
+
`ikeytz-maps-mcp ${packageVersion()}`,
|
|
123
|
+
`Remote ${mapsMcpUrl()}`,
|
|
124
|
+
`Registry com.ikeytz/maps`,
|
|
125
|
+
"Nur maps. Nicht @ikeytz/mcp / www.",
|
|
126
|
+
"",
|
|
127
|
+
"Usage",
|
|
128
|
+
" ikeytz-maps-mcp MCP proxy (Cursor/Claude)",
|
|
129
|
+
" ikeytz-maps-mcp --version package version (offline)",
|
|
130
|
+
" ikeytz-maps-mcp help this help (offline, no MCP connect)",
|
|
131
|
+
" ikeytz-maps-mcp update [version] npm i -g @ikeytz/maps-mcp@latest",
|
|
132
|
+
" ikeytz-maps-mcp list tool table (offline) + global flags",
|
|
133
|
+
" ikeytz-maps-mcp list --global global CLI flags only (offline)",
|
|
134
|
+
" ikeytz-maps-mcp describe <tool> args + examples (offline)",
|
|
135
|
+
" ikeytz-maps-mcp call <tool> [flags]",
|
|
136
|
+
" ikeytz-maps-mcp <tool> [flags] same as call",
|
|
137
|
+
" ikeytz-maps-mcp <tool> -h per-tool help (offline, same as describe)",
|
|
138
|
+
" ikeytz-maps-mcp <tool> --show-mcp-command",
|
|
139
|
+
"",
|
|
140
|
+
...globalCliFlagsLines().slice(1),
|
|
141
|
+
"",
|
|
142
|
+
];
|
|
143
|
+
if (tools?.length) {
|
|
144
|
+
const rows = [...tools]
|
|
145
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
146
|
+
.map((t) => ({
|
|
147
|
+
tool: t.name,
|
|
148
|
+
args: argHint(t) || "—",
|
|
149
|
+
example: exampleForTool(t.name),
|
|
150
|
+
}));
|
|
151
|
+
lines.push(`Tools ${rows.length} (A–Z numbered, * = required)`);
|
|
152
|
+
lines.push("");
|
|
153
|
+
lines.push(...formatToolsAsciiTable(rows));
|
|
154
|
+
lines.push("");
|
|
155
|
+
lines.push("Tip ikeytz-maps-mcp describe get_service_area");
|
|
156
|
+
} else {
|
|
157
|
+
lines.push("Tools ikeytz-maps-mcp list (offline) · ikeytz-maps-mcp <tool> -h for args");
|
|
158
|
+
}
|
|
159
|
+
lines.push("");
|
|
160
|
+
out.write(`${lines.join("\n")}\n`);
|
|
47
161
|
}
|
|
48
162
|
|
|
49
|
-
function
|
|
163
|
+
function formatDescribe(tool) {
|
|
50
164
|
const schema = tool.inputSchema || { type: "object", properties: {} };
|
|
51
165
|
const props = schema.properties || {};
|
|
52
166
|
const req = new Set(schema.required || []);
|
|
@@ -59,7 +173,8 @@ function formatToolHelp(tool) {
|
|
|
59
173
|
const p = props[key] || {};
|
|
60
174
|
const type = p.enum ? p.enum.join("|") : p.type || "any";
|
|
61
175
|
const star = req.has(key) ? "*" : " ";
|
|
62
|
-
|
|
176
|
+
const desc = p.description ? ` ${p.description}` : "";
|
|
177
|
+
lines.push(` ${star} --${key.padEnd(16)} ${type}${desc}`);
|
|
63
178
|
}
|
|
64
179
|
if (req.size) {
|
|
65
180
|
lines.push("");
|
|
@@ -70,119 +185,156 @@ function formatToolHelp(tool) {
|
|
|
70
185
|
lines.push("CLI flags (not sent to MCP)");
|
|
71
186
|
lines.push(" -h, --help, -help this help (offline, no MCP call)");
|
|
72
187
|
lines.push(" --raw JSON structuredContent");
|
|
73
|
-
lines.push(" --show-mcp-command curl 1a/1b/2 only
|
|
188
|
+
lines.push(" --show-mcp-command curl 1a/1b/2 only (aliases: --show-mcp-befehl, --show-mcp-comment)");
|
|
74
189
|
lines.push("");
|
|
75
190
|
lines.push("Examples");
|
|
76
191
|
lines.push(` ${exampleForTool(tool.name)}`);
|
|
77
192
|
lines.push(` ikeytz-maps-mcp ${tool.name} -h`);
|
|
78
193
|
lines.push(` ikeytz-maps-mcp ${tool.name} --show-mcp-command`);
|
|
79
|
-
return
|
|
194
|
+
return lines.filter((l, i) => !(i === 1 && !l)).join("\n");
|
|
80
195
|
}
|
|
81
196
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
197
|
+
const SHOW_MCP_COMMAND_FLAGS = new Set([
|
|
198
|
+
"--show-mcp-command",
|
|
199
|
+
"--show-mcp-befehl",
|
|
200
|
+
"--show-mcp-comment",
|
|
201
|
+
]);
|
|
85
202
|
|
|
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 '{…}'
|
|
203
|
+
function wantsShowMcpCommand(argv) {
|
|
204
|
+
return argv.some((a) => SHOW_MCP_COMMAND_FLAGS.has(a));
|
|
205
|
+
}
|
|
99
206
|
|
|
100
|
-
|
|
207
|
+
function stripCliDisplayFlags(argv) {
|
|
208
|
+
return argv.filter((a) => !SHOW_MCP_COMMAND_FLAGS.has(a));
|
|
209
|
+
}
|
|
101
210
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
.join("\n")}
|
|
106
|
-
`;
|
|
211
|
+
function isHelpFlag(a) {
|
|
212
|
+
return a === "-h" || a === "--help" || a === "-help" || a === "help";
|
|
213
|
+
}
|
|
107
214
|
|
|
108
|
-
function
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
215
|
+
function parseToolArgs(argv) {
|
|
216
|
+
const args = {};
|
|
217
|
+
let i = 0;
|
|
218
|
+
while (i < argv.length) {
|
|
112
219
|
const a = argv[i];
|
|
113
|
-
if (a === "--
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
220
|
+
if (a === "--json") {
|
|
221
|
+
const raw = argv[i + 1];
|
|
222
|
+
if (!raw) throw new Error("--json needs a JSON object");
|
|
223
|
+
const parsed = JSON.parse(raw);
|
|
224
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
225
|
+
throw new Error("--json must be an object");
|
|
226
|
+
}
|
|
227
|
+
Object.assign(args, parsed);
|
|
228
|
+
i += 2;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (a.startsWith("--json=")) {
|
|
232
|
+
Object.assign(args, JSON.parse(a.slice("--json=".length)));
|
|
233
|
+
i += 1;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (a.startsWith("--")) {
|
|
121
237
|
const eq = a.indexOf("=");
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
238
|
+
let key;
|
|
239
|
+
let val;
|
|
240
|
+
if (eq > 2) {
|
|
241
|
+
key = a.slice(2, eq);
|
|
242
|
+
val = a.slice(eq + 1);
|
|
243
|
+
i += 1;
|
|
244
|
+
} else {
|
|
245
|
+
key = a.slice(2);
|
|
125
246
|
const next = argv[i + 1];
|
|
126
|
-
if (next
|
|
127
|
-
|
|
247
|
+
if (!next || next.startsWith("-")) {
|
|
248
|
+
val = true;
|
|
128
249
|
i += 1;
|
|
129
|
-
} else
|
|
250
|
+
} else {
|
|
251
|
+
val = next;
|
|
252
|
+
i += 2;
|
|
253
|
+
}
|
|
130
254
|
}
|
|
131
|
-
|
|
255
|
+
if (val === "true") val = true;
|
|
256
|
+
else if (val === "false") val = false;
|
|
257
|
+
else if (typeof val === "string" && /^-?\d+(\.\d+)?$/.test(val)) val = Number(val);
|
|
258
|
+
args[key] = val;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
throw new Error(`Unexpected argument: ${a}`);
|
|
132
262
|
}
|
|
133
|
-
return
|
|
263
|
+
return args;
|
|
134
264
|
}
|
|
135
265
|
|
|
136
|
-
function
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
266
|
+
function formatMcpCurlHeading(toolName, callArgs = {}) {
|
|
267
|
+
const url = mapsMcpUrl();
|
|
268
|
+
const proto = mapsMcpProtocol();
|
|
269
|
+
const argsObj = callArgs && typeof callArgs === "object" ? callArgs : {};
|
|
270
|
+
const argsJson = JSON.stringify(argsObj);
|
|
271
|
+
const callBody = JSON.stringify({
|
|
272
|
+
jsonrpc: "2.0",
|
|
273
|
+
id: 2,
|
|
274
|
+
method: "tools/call",
|
|
275
|
+
params: { name: toolName, arguments: argsObj },
|
|
276
|
+
});
|
|
277
|
+
const dq = (s) => String(s).replace(/'/g, `'\\''`);
|
|
278
|
+
const tool = toolByName(toolName);
|
|
279
|
+
const blurb = tool?.description || `Remote MCP tools/call · ${toolName}`;
|
|
280
|
+
const parts = [`ikeytz-maps-mcp ${toolName}`];
|
|
281
|
+
for (const [k, v] of Object.entries(argsObj)) {
|
|
282
|
+
if (v === undefined) continue;
|
|
283
|
+
parts.push(`--${k}=${v}`);
|
|
143
284
|
}
|
|
144
|
-
|
|
285
|
+
const bar = "═".repeat(56);
|
|
286
|
+
const thin = "─".repeat(56);
|
|
287
|
+
return [
|
|
288
|
+
bar,
|
|
289
|
+
`MCP tool ${toolName}`,
|
|
290
|
+
`What ${blurb}`,
|
|
291
|
+
`CLI ${parts.join(" ")}`,
|
|
292
|
+
`Args ${argsJson}`,
|
|
293
|
+
`Registry com.ikeytz/maps`,
|
|
294
|
+
`Remote ${url}`,
|
|
295
|
+
thin,
|
|
296
|
+
`Equivalent curl (same MCP call)`,
|
|
297
|
+
"",
|
|
298
|
+
`# 1a) Session-ID holen`,
|
|
299
|
+
`SID=$(curl -sS -D - -o /tmp/ikeytz-maps-mcp-init.json -X POST '${url}' \\`,
|
|
300
|
+
` -H 'Content-Type: application/json' \\`,
|
|
301
|
+
` -H 'Accept: application/json' \\`,
|
|
302
|
+
` -H 'MCP-Protocol-Version: ${proto}' \\`,
|
|
303
|
+
` -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"${proto}","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}' \\`,
|
|
304
|
+
` | awk -F': ' 'tolower($1)=="mcp-session-id"{gsub(/\\r/,"",$2); print $2; exit}')`,
|
|
305
|
+
`echo "SID=$SID"`,
|
|
306
|
+
"",
|
|
307
|
+
`# 1b) initialized`,
|
|
308
|
+
`curl -sS -X POST '${url}' \\`,
|
|
309
|
+
` -H 'Content-Type: application/json' \\`,
|
|
310
|
+
` -H 'Accept: application/json' \\`,
|
|
311
|
+
` -H 'MCP-Protocol-Version: ${proto}' \\`,
|
|
312
|
+
` -H "Mcp-Session-Id: $SID" \\`,
|
|
313
|
+
` -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' >/dev/null`,
|
|
314
|
+
"",
|
|
315
|
+
`# 2) Tool`,
|
|
316
|
+
`curl -sS -X POST '${url}' \\`,
|
|
317
|
+
` -H 'Content-Type: application/json' \\`,
|
|
318
|
+
` -H 'Accept: application/json' \\`,
|
|
319
|
+
` -H 'MCP-Protocol-Version: ${proto}' \\`,
|
|
320
|
+
` -H "Mcp-Session-Id: $SID" \\`,
|
|
321
|
+
` -d '${dq(callBody)}'`,
|
|
322
|
+
bar,
|
|
323
|
+
].join("\n");
|
|
145
324
|
}
|
|
146
325
|
|
|
147
|
-
function
|
|
326
|
+
function printCallResult(rpc, { raw = false } = {}) {
|
|
148
327
|
if (raw) {
|
|
149
|
-
|
|
328
|
+
out.write(`${JSON.stringify(rpc, null, 2)}\n`);
|
|
150
329
|
return;
|
|
151
330
|
}
|
|
152
331
|
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) };
|
|
332
|
+
if (data && typeof data === "object") {
|
|
333
|
+
const { ok, tool, locale, related, attribution, ...payload } = data;
|
|
334
|
+
out.write(`${JSON.stringify(Object.keys(payload).length ? payload : data, null, 2)}\n`);
|
|
335
|
+
return;
|
|
172
336
|
}
|
|
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);
|
|
337
|
+
out.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
186
338
|
}
|
|
187
339
|
|
|
188
340
|
async function runStdioProxy() {
|
|
@@ -208,22 +360,22 @@ async function runStdioProxy() {
|
|
|
208
360
|
const id = msg.id;
|
|
209
361
|
try {
|
|
210
362
|
if (method === "initialize") {
|
|
211
|
-
const
|
|
212
|
-
sessionId =
|
|
213
|
-
writeStdio(
|
|
363
|
+
const res = await mapsRpc("initialize", msg.params || {}, { id: id ?? 1 });
|
|
364
|
+
sessionId = res.sessionId || sessionId;
|
|
365
|
+
writeStdio(res.json);
|
|
214
366
|
} else if (method === "notifications/initialized" || method === "initialized") {
|
|
215
|
-
const
|
|
367
|
+
const res = await mapsRpc("notifications/initialized", msg.params || {}, {
|
|
216
368
|
notification: true,
|
|
217
369
|
sessionId,
|
|
218
370
|
});
|
|
219
|
-
sessionId =
|
|
371
|
+
sessionId = res.sessionId || sessionId;
|
|
220
372
|
if (id !== undefined) writeStdio({ jsonrpc: "2.0", id, result: {} });
|
|
221
373
|
} else if (id === undefined) {
|
|
222
374
|
await mapsRpc(method, msg.params || {}, { notification: true, sessionId });
|
|
223
375
|
} else {
|
|
224
|
-
const
|
|
225
|
-
sessionId =
|
|
226
|
-
writeStdio(
|
|
376
|
+
const res = await mapsRpc(method, msg.params || {}, { id, sessionId });
|
|
377
|
+
sessionId = res.sessionId || sessionId;
|
|
378
|
+
writeStdio(res.json);
|
|
227
379
|
}
|
|
228
380
|
} catch (err) {
|
|
229
381
|
if (id !== undefined) {
|
|
@@ -235,72 +387,131 @@ async function runStdioProxy() {
|
|
|
235
387
|
});
|
|
236
388
|
}
|
|
237
389
|
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
390
|
+
function readStdioMessage(buf) {
|
|
391
|
+
const text = buf.toString("utf8");
|
|
392
|
+
const headerEnd = text.indexOf("\r\n\r\n");
|
|
393
|
+
const altEnd = text.indexOf("\n\n");
|
|
394
|
+
if (headerEnd >= 0 || (altEnd >= 0 && /content-length:/i.test(text.slice(0, altEnd + 2)))) {
|
|
395
|
+
const split = headerEnd >= 0 ? headerEnd : altEnd;
|
|
396
|
+
const sep = headerEnd >= 0 ? 4 : 2;
|
|
397
|
+
const header = text.slice(0, split);
|
|
398
|
+
const m = header.match(/content-length:\s*(\d+)/i);
|
|
399
|
+
if (!m) return null;
|
|
400
|
+
const len = Number(m[1]);
|
|
401
|
+
const start = split + sep;
|
|
402
|
+
const body = Buffer.from(text, "utf8").subarray(start);
|
|
403
|
+
if (body.length < len) return null;
|
|
404
|
+
return { json: JSON.parse(body.subarray(0, len).toString("utf8")), rest: body.subarray(len) };
|
|
244
405
|
}
|
|
245
|
-
|
|
406
|
+
const nl = text.indexOf("\n");
|
|
407
|
+
if (nl < 0) return null;
|
|
408
|
+
const line = text.slice(0, nl).trim();
|
|
409
|
+
const rest = Buffer.from(text.slice(nl + 1), "utf8");
|
|
410
|
+
if (!line) return { json: null, rest };
|
|
411
|
+
return { json: JSON.parse(line), rest };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function writeStdio(msg) {
|
|
415
|
+
const json = JSON.stringify(msg);
|
|
416
|
+
const body = Buffer.from(json, "utf8");
|
|
417
|
+
process.stdout.write(`Content-Length: ${body.length}\r\n\r\n`);
|
|
418
|
+
process.stdout.write(body);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export async function runCli(argv) {
|
|
422
|
+
const args = [...argv];
|
|
423
|
+
if (args.length === 0) {
|
|
246
424
|
await runStdioProxy();
|
|
247
|
-
return;
|
|
425
|
+
return 0;
|
|
248
426
|
}
|
|
249
427
|
|
|
250
|
-
const cmd =
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
return;
|
|
428
|
+
const cmd = args[0];
|
|
429
|
+
if (cmd === "-V" || cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
430
|
+
out.write(`${packageVersion()}\n`);
|
|
431
|
+
return 0;
|
|
254
432
|
}
|
|
255
|
-
if (cmd
|
|
256
|
-
|
|
257
|
-
return;
|
|
433
|
+
if (isHelpFlag(cmd) && args.length === 1) {
|
|
434
|
+
printHumanHelp(catalogTools());
|
|
435
|
+
return 0;
|
|
258
436
|
}
|
|
259
|
-
if (cmd === "
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
437
|
+
if (cmd === "serve" || cmd === "proxy") {
|
|
438
|
+
await runStdioProxy();
|
|
439
|
+
return 0;
|
|
440
|
+
}
|
|
441
|
+
if (cmd === "update" || cmd === "upgrade") {
|
|
442
|
+
const spec = args[1] && !args[1].startsWith("-") ? args[1] : "latest";
|
|
443
|
+
const pkg = spec === "latest" ? "@ikeytz/maps-mcp@latest" : `@ikeytz/maps-mcp@${spec}`;
|
|
444
|
+
log.write(`Updating ${pkg}…\n`);
|
|
445
|
+
const r = spawnSync("npm", ["i", "-g", "--prefer-online", pkg], { stdio: "inherit", shell: false });
|
|
446
|
+
if (r.status !== 0) return r.status || 1;
|
|
447
|
+
log.write(`Done. New terminal → ikeytz-maps-mcp --version\n`);
|
|
448
|
+
return 0;
|
|
266
449
|
}
|
|
267
450
|
if (cmd === "list") {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
451
|
+
const rest = args.slice(1);
|
|
452
|
+
for (const a of rest) {
|
|
453
|
+
if (a !== "--global") {
|
|
454
|
+
log.write(`Usage: ikeytz-maps-mcp list [--global]\n`);
|
|
455
|
+
return 2;
|
|
456
|
+
}
|
|
271
457
|
}
|
|
272
|
-
|
|
273
|
-
|
|
458
|
+
if (rest.includes("--global")) {
|
|
459
|
+
out.write(`${globalCliFlagsLines().join("\n")}\n`);
|
|
460
|
+
return 0;
|
|
461
|
+
}
|
|
462
|
+
const rows = catalogTools().map((t) => ({
|
|
463
|
+
tool: t.name,
|
|
464
|
+
args: argHint(t) || "—",
|
|
465
|
+
example: exampleForTool(t.name),
|
|
466
|
+
}));
|
|
467
|
+
out.write([...formatToolsAsciiTable(rows), ...globalCliFlagsLines()].join("\n") + "\n");
|
|
468
|
+
return 0;
|
|
274
469
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
470
|
+
|
|
471
|
+
let toolName;
|
|
472
|
+
let rest;
|
|
473
|
+
if (cmd === "describe" || cmd === "call") {
|
|
474
|
+
toolName = args[1];
|
|
475
|
+
rest = args.slice(2);
|
|
476
|
+
if (!toolName) {
|
|
477
|
+
log.write(`Usage: ikeytz-maps-mcp ${cmd} <toolName> …\n`);
|
|
478
|
+
return 2;
|
|
282
479
|
}
|
|
283
|
-
|
|
284
|
-
|
|
480
|
+
} else {
|
|
481
|
+
toolName = cmd;
|
|
482
|
+
rest = args.slice(1);
|
|
285
483
|
}
|
|
286
484
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
485
|
+
if (cmd === "describe" || rest.some(isHelpFlag)) {
|
|
486
|
+
const tool = toolByName(toolName);
|
|
487
|
+
if (!tool) {
|
|
488
|
+
log.write(`Unknown tool: ${toolName}\n`);
|
|
489
|
+
return 1;
|
|
490
|
+
}
|
|
491
|
+
out.write(`${formatDescribe(tool)}\n`);
|
|
492
|
+
return 0;
|
|
294
493
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
494
|
+
|
|
495
|
+
const wantRaw = rest.includes("--raw");
|
|
496
|
+
const showMcp = wantsShowMcpCommand(rest);
|
|
497
|
+
let callArgs;
|
|
498
|
+
try {
|
|
499
|
+
callArgs = parseToolArgs(stripCliDisplayFlags(rest.filter((a) => a !== "--raw")));
|
|
500
|
+
} catch (err) {
|
|
501
|
+
log.write(`${err.message || err}\n`);
|
|
502
|
+
return 2;
|
|
298
503
|
}
|
|
299
|
-
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
return;
|
|
504
|
+
|
|
505
|
+
if (showMcp) {
|
|
506
|
+
out.write(`${formatMcpCurlHeading(toolName, callArgs)}\n`);
|
|
507
|
+
return 0;
|
|
303
508
|
}
|
|
304
|
-
|
|
305
|
-
|
|
509
|
+
|
|
510
|
+
const rpc = await callTool(toolName, callArgs);
|
|
511
|
+
printCallResult(rpc, { raw: wantRaw });
|
|
512
|
+
if (rpc?.result?.isError || rpc?.error) process.exitCode = 1;
|
|
513
|
+
return process.exitCode || 0;
|
|
306
514
|
}
|
|
515
|
+
|
|
516
|
+
/** @deprecated use runCli — kept for older bin */
|
|
517
|
+
export const main = runCli;
|