mcp-google-map 0.4.0 → 0.6.0
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/.env.example +1 -1
- package/README.md +44 -21
- package/dist/{chunk-K27FX5QH.js → chunk-NAUIQEK4.js} +2 -2
- package/dist/chunk-NAUIQEK4.js.map +1 -0
- package/dist/cli.d.ts +128 -3
- package/dist/cli.js +1713 -329
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/docs/CONFIGURATION.md +19 -12
- package/docs/TOOLS.md +1 -1
- package/package.json +8 -6
- package/src/resources/skills/README.md +40 -0
- package/src/resources/skills/manifest.json +14 -0
- package/src/resources/skills/venue-discovery/SKILL.md +304 -0
- package/dist/chunk-K27FX5QH.js.map +0 -1
- package/docs/GEOSCOUT.md +0 -53
package/dist/cli.js
CHANGED
|
@@ -2,14 +2,177 @@
|
|
|
2
2
|
import {
|
|
3
3
|
Logger,
|
|
4
4
|
PlacesSearcher
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-NAUIQEK4.js";
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
8
|
-
import { config as
|
|
9
|
-
import { resolve } from "path";
|
|
8
|
+
import { config as dotenvConfig2 } from "dotenv";
|
|
9
|
+
import { resolve as resolve5 } from "path";
|
|
10
10
|
import yargs from "yargs";
|
|
11
11
|
import { hideBin } from "yargs/helpers";
|
|
12
12
|
|
|
13
|
+
// src/utils/configResolver.ts
|
|
14
|
+
import { config as dotenvConfig } from "dotenv";
|
|
15
|
+
import { existsSync } from "fs";
|
|
16
|
+
|
|
17
|
+
// src/utils/autoUpdate.ts
|
|
18
|
+
var AUTO_UPDATE_DISABLED_ENV = "MCP_AUTO_UPDATE_DISABLED";
|
|
19
|
+
var DEFAULT_PACKAGE_NAME = "mcp-google-map";
|
|
20
|
+
var DEFAULT_REGISTRY_BASE_URL = "https://registry.npmjs.org";
|
|
21
|
+
var DEFAULT_UPDATE_CHECK_TIMEOUT_MS = 2e3;
|
|
22
|
+
async function probeLatestVersion(deps = {}) {
|
|
23
|
+
const env = deps.env ?? process.env;
|
|
24
|
+
if (isAutoUpdateDisabled(env)) {
|
|
25
|
+
return { status: "disabled" };
|
|
26
|
+
}
|
|
27
|
+
const currentVersion = deps.currentVersion;
|
|
28
|
+
if (!currentVersion) {
|
|
29
|
+
return { status: "unknown-version" };
|
|
30
|
+
}
|
|
31
|
+
const packageName = deps.packageName ?? DEFAULT_PACKAGE_NAME;
|
|
32
|
+
const registryBaseUrl = deps.registryBaseUrl ?? DEFAULT_REGISTRY_BASE_URL;
|
|
33
|
+
const timeoutMs = deps.timeoutMs ?? DEFAULT_UPDATE_CHECK_TIMEOUT_MS;
|
|
34
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetchImpl(`${registryBaseUrl}/${packageName}/latest`, {
|
|
37
|
+
headers: { accept: "application/json" },
|
|
38
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
return {
|
|
42
|
+
status: "error",
|
|
43
|
+
message: `registry responded with HTTP ${response.status}.`
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const payload = await response.json();
|
|
47
|
+
if (typeof payload?.version !== "string") {
|
|
48
|
+
return {
|
|
49
|
+
status: "error",
|
|
50
|
+
message: "registry response has no version field."
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (!isNewerVersion(payload.version, currentVersion)) {
|
|
54
|
+
return { status: "current", currentVersion };
|
|
55
|
+
}
|
|
56
|
+
return { status: "newer", currentVersion, latestVersion: payload.version };
|
|
57
|
+
} catch (error) {
|
|
58
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
59
|
+
return { status: "error", message };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function isAutoUpdateDisabled(env = process.env) {
|
|
63
|
+
const raw = env[AUTO_UPDATE_DISABLED_ENV];
|
|
64
|
+
return typeof raw === "string" && /^(1|true|yes)$/i.test(raw.trim());
|
|
65
|
+
}
|
|
66
|
+
function isNewerVersion(candidate, current) {
|
|
67
|
+
const toSegments = (version) => version.trim().split(".").map((segment) => /^\d+$/.test(segment) ? Number(segment) : Number.NaN);
|
|
68
|
+
const candidateSegments = toSegments(candidate);
|
|
69
|
+
const currentSegments = toSegments(current);
|
|
70
|
+
if (candidateSegments.some((segment) => !Number.isInteger(segment)) || currentSegments.some((segment) => !Number.isInteger(segment))) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
const length = Math.max(candidateSegments.length, currentSegments.length);
|
|
74
|
+
for (let index = 0; index < length; index += 1) {
|
|
75
|
+
const candidatePart = candidateSegments[index] ?? 0;
|
|
76
|
+
const currentPart = currentSegments[index] ?? 0;
|
|
77
|
+
if (candidatePart !== currentPart) {
|
|
78
|
+
return candidatePart > currentPart;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/utils/configResolver.ts
|
|
85
|
+
var NODE_ENGINE_FLOOR = 22;
|
|
86
|
+
var DEFAULT_HTTP_HOST = "127.0.0.1";
|
|
87
|
+
var DEFAULT_HTTP_PORT = 3e3;
|
|
88
|
+
function resolveRuntimeConfig(env = process.env, overrides = {}) {
|
|
89
|
+
const envPort = env.MCP_SERVER_PORT?.trim() || void 0;
|
|
90
|
+
let port;
|
|
91
|
+
let portRaw;
|
|
92
|
+
let portSource;
|
|
93
|
+
if (overrides.port !== void 0) {
|
|
94
|
+
port = overrides.port;
|
|
95
|
+
portRaw = String(overrides.port);
|
|
96
|
+
portSource = "flag";
|
|
97
|
+
} else if (envPort !== void 0) {
|
|
98
|
+
const parsed = Number(envPort);
|
|
99
|
+
port = Number.isFinite(parsed) ? parsed : void 0;
|
|
100
|
+
portRaw = envPort;
|
|
101
|
+
portSource = "environment";
|
|
102
|
+
} else {
|
|
103
|
+
port = DEFAULT_HTTP_PORT;
|
|
104
|
+
portRaw = void 0;
|
|
105
|
+
portSource = "default";
|
|
106
|
+
}
|
|
107
|
+
const apiKey = overrides.apiKey ?? env.GOOGLE_MAPS_API_KEY;
|
|
108
|
+
const enabledToolsRaw = env.GOOGLE_MAPS_ENABLED_TOOLS?.trim() || void 0;
|
|
109
|
+
return {
|
|
110
|
+
apiKey,
|
|
111
|
+
apiKeySource: overrides.apiKey !== void 0 ? "flag" : apiKey ? "environment" : "none",
|
|
112
|
+
host: overrides.host ?? env.MCP_SERVER_HOST ?? DEFAULT_HTTP_HOST,
|
|
113
|
+
hostSource: overrides.host !== void 0 ? "flag" : env.MCP_SERVER_HOST ? "environment" : "default",
|
|
114
|
+
port,
|
|
115
|
+
portRaw,
|
|
116
|
+
portSource,
|
|
117
|
+
enabledToolsRaw,
|
|
118
|
+
enabledToolsSource: enabledToolsRaw ? "environment" : "default",
|
|
119
|
+
autoUpdateDisabled: isAutoUpdateDisabled(env),
|
|
120
|
+
envFile: overrides.envFile
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function maskSecret(value) {
|
|
124
|
+
if (!value) {
|
|
125
|
+
return "unset";
|
|
126
|
+
}
|
|
127
|
+
if (value.length <= 8) {
|
|
128
|
+
return "****";
|
|
129
|
+
}
|
|
130
|
+
return `${value.slice(0, 4)}****${value.slice(-4)}`;
|
|
131
|
+
}
|
|
132
|
+
function describeRuntimeConfig(config) {
|
|
133
|
+
return {
|
|
134
|
+
apiKey: { value: maskSecret(config.apiKey), source: config.apiKeySource },
|
|
135
|
+
httpHost: { value: config.host, source: config.hostSource },
|
|
136
|
+
httpPort: {
|
|
137
|
+
value: config.port ?? null,
|
|
138
|
+
raw: config.portRaw ?? null,
|
|
139
|
+
source: config.portSource
|
|
140
|
+
},
|
|
141
|
+
enabledTools: { value: config.enabledToolsRaw ?? "*", source: config.enabledToolsSource },
|
|
142
|
+
autoUpdateCheck: { enabled: !config.autoUpdateDisabled },
|
|
143
|
+
envFile: { value: config.envFile ?? null }
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function formatConfigDiagnostics(diagnostics) {
|
|
147
|
+
return [
|
|
148
|
+
`host=${diagnostics.httpHost.value} (${diagnostics.httpHost.source})`,
|
|
149
|
+
`port=${diagnostics.httpPort.value ?? "invalid"} (raw: ${diagnostics.httpPort.raw ?? "n/a"}, ${diagnostics.httpPort.source})`,
|
|
150
|
+
`apiKey=${diagnostics.apiKey.value} (${diagnostics.apiKey.source})`,
|
|
151
|
+
`enabledTools=${diagnostics.enabledTools.value} (${diagnostics.enabledTools.source})`,
|
|
152
|
+
`updateCheck=${diagnostics.autoUpdateCheck.enabled ? "enabled" : "disabled"}`
|
|
153
|
+
].join("; ");
|
|
154
|
+
}
|
|
155
|
+
function applyExplicitEnvFile(path) {
|
|
156
|
+
if (!existsSync(path)) {
|
|
157
|
+
return { ok: false, error: `env file not found: ${path}` };
|
|
158
|
+
}
|
|
159
|
+
dotenvConfig({ path });
|
|
160
|
+
return { ok: true };
|
|
161
|
+
}
|
|
162
|
+
function resolveEnabledToolNames(allToolNames, raw) {
|
|
163
|
+
const trimmed = raw?.trim();
|
|
164
|
+
if (!trimmed || trimmed === "*") {
|
|
165
|
+
return { names: [...allToolNames], matched: allToolNames.length, fallback: false, requested: [] };
|
|
166
|
+
}
|
|
167
|
+
const requested = trimmed.split(",").map((item) => item.trim()).filter(Boolean);
|
|
168
|
+
const enabled = new Set(requested);
|
|
169
|
+
const names = allToolNames.filter((name) => enabled.has(name));
|
|
170
|
+
if (names.length === 0) {
|
|
171
|
+
return { names: [...allToolNames], matched: 0, fallback: true, requested };
|
|
172
|
+
}
|
|
173
|
+
return { names, matched: names.length, fallback: false, requested };
|
|
174
|
+
}
|
|
175
|
+
|
|
13
176
|
// src/tools/maps/searchNearby.ts
|
|
14
177
|
import { z } from "zod";
|
|
15
178
|
|
|
@@ -1023,18 +1186,19 @@ var serverConfigs = [
|
|
|
1023
1186
|
}
|
|
1024
1187
|
];
|
|
1025
1188
|
function filterTools(tools) {
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
raw.split(",").map((s) => s.trim()).filter(Boolean)
|
|
1189
|
+
const resolution = resolveEnabledToolNames(
|
|
1190
|
+
tools.map((tool) => tool.name),
|
|
1191
|
+
process.env.GOOGLE_MAPS_ENABLED_TOOLS
|
|
1030
1192
|
);
|
|
1031
|
-
|
|
1032
|
-
if (filtered.length === 0) {
|
|
1193
|
+
if (resolution.fallback) {
|
|
1033
1194
|
Logger.error(`GOOGLE_MAPS_ENABLED_TOOLS matched 0 tools. Available: ${tools.map((t) => t.name).join(", ")}`);
|
|
1034
1195
|
return tools;
|
|
1035
1196
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1197
|
+
if (resolution.matched !== tools.length) {
|
|
1198
|
+
Logger.log(`GOOGLE_MAPS_ENABLED_TOOLS: ${resolution.matched}/${tools.length} tools active`);
|
|
1199
|
+
}
|
|
1200
|
+
const enabled = new Set(resolution.names);
|
|
1201
|
+
return tools.filter((tool) => enabled.has(tool.name));
|
|
1038
1202
|
}
|
|
1039
1203
|
var config_default = serverConfigs;
|
|
1040
1204
|
|
|
@@ -1140,7 +1304,20 @@ var BaseMcpServer = class {
|
|
|
1140
1304
|
};
|
|
1141
1305
|
Logger.log(`${this.serverName} connected and ready to process requests`);
|
|
1142
1306
|
}
|
|
1143
|
-
|
|
1307
|
+
/**
|
|
1308
|
+
* @brief Starts the Streamable HTTP listener and resolves once it is accepting connections.
|
|
1309
|
+
* @details Builds the Express app with the `/mcp` POST/GET/DELETE handlers,
|
|
1310
|
+
* then wraps `app.listen` in a Promise that resolves from the listen callback
|
|
1311
|
+
* and rejects on the server `error` event (e.g. EADDRINUSE), so callers get
|
|
1312
|
+
* a reliable startup outcome instead of an unhandled error. Default host is
|
|
1313
|
+
* loopback `127.0.0.1` (REQ-008); pass `0.0.0.0` to bind all interfaces.
|
|
1314
|
+
* @param port {number} TCP port to listen on (1-65535).
|
|
1315
|
+
* @param host {string} Bind address; defaults to `127.0.0.1`.
|
|
1316
|
+
* @return {Promise<void>} Resolves when listening; rejects with the listen error.
|
|
1317
|
+
* @throws {Error} Rejects when the HTTP listener emits an error (port in use, permission denied).
|
|
1318
|
+
* @satisfies REQ-008, REQ-095
|
|
1319
|
+
*/
|
|
1320
|
+
async startHttpServer(port, host = "127.0.0.1") {
|
|
1144
1321
|
const app = express();
|
|
1145
1322
|
app.use(express.json());
|
|
1146
1323
|
app.post("/mcp", async (req, res) => {
|
|
@@ -1212,42 +1389,53 @@ var BaseMcpServer = class {
|
|
|
1212
1389
|
app.get("/mcp", handleSessionRequest);
|
|
1213
1390
|
app.delete("/mcp", handleSessionRequest);
|
|
1214
1391
|
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1392
|
+
await new Promise((resolve6, reject) => {
|
|
1393
|
+
this.httpServer = app.listen(port, host, () => {
|
|
1394
|
+
Logger.log(`[${this.serverName}] HTTP server listening on ${host}:${port}`);
|
|
1395
|
+
Logger.log(`[${this.serverName}] MCP endpoint available at http://${displayHost}:${port}/mcp`);
|
|
1396
|
+
resolve6();
|
|
1397
|
+
});
|
|
1398
|
+
this.httpServer.on("error", (error) => reject(error));
|
|
1218
1399
|
});
|
|
1219
1400
|
}
|
|
1220
1401
|
async startStdio() {
|
|
1221
1402
|
const transport = new StdioServerTransport();
|
|
1222
1403
|
await this.connect(transport);
|
|
1223
1404
|
}
|
|
1405
|
+
/**
|
|
1406
|
+
* @brief Stops the HTTP listener and closes every session transport in order.
|
|
1407
|
+
* @details Idempotent: when no listener exists it logs and resolves. Otherwise
|
|
1408
|
+
* it detaches the server reference, clears the session map, awaits
|
|
1409
|
+
* `transport.close()` for every session (errors tolerated via allSettled),
|
|
1410
|
+
* calls `closeAllConnections` when available, and finally closes the socket.
|
|
1411
|
+
* Wired to SIGINT/SIGTERM through `installShutdownHandlers` (REQ-096).
|
|
1412
|
+
* @return {Promise<void>} Resolves after the socket closes; rejects when `server.close` errors.
|
|
1413
|
+
* @throws {Error} Rejects with the `server.close` error.
|
|
1414
|
+
* @satisfies REQ-096
|
|
1415
|
+
*/
|
|
1224
1416
|
async stopHttpServer() {
|
|
1225
|
-
|
|
1417
|
+
const server = this.httpServer;
|
|
1418
|
+
if (!server) {
|
|
1226
1419
|
Logger.error(`[${this.serverName}] HTTP server is not running or already stopped.`);
|
|
1227
1420
|
return;
|
|
1228
1421
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1422
|
+
this.httpServer = null;
|
|
1423
|
+
const closingSessions = Object.values(this.sessions);
|
|
1424
|
+
this.sessions = {};
|
|
1425
|
+
await Promise.allSettled(closingSessions.map((context) => context.transport.close()));
|
|
1426
|
+
if (closingSessions.length > 0) {
|
|
1427
|
+
Logger.log(`[${this.serverName}] All transports closed.`);
|
|
1428
|
+
}
|
|
1429
|
+
return new Promise((resolve6, reject) => {
|
|
1430
|
+
server.closeAllConnections?.();
|
|
1431
|
+
server.close((err) => {
|
|
1231
1432
|
if (err) {
|
|
1232
1433
|
Logger.error(`[${this.serverName}] Error stopping HTTP server:`, err);
|
|
1233
1434
|
reject(err);
|
|
1234
1435
|
return;
|
|
1235
1436
|
}
|
|
1236
1437
|
Logger.log(`[${this.serverName}] HTTP server stopped.`);
|
|
1237
|
-
|
|
1238
|
-
const closingSessions = Object.values(this.sessions).map((context) => {
|
|
1239
|
-
if (context.transport.sessionId) {
|
|
1240
|
-
delete this.sessions[context.transport.sessionId];
|
|
1241
|
-
}
|
|
1242
|
-
return Promise.resolve();
|
|
1243
|
-
});
|
|
1244
|
-
Promise.all(closingSessions).then(() => {
|
|
1245
|
-
Logger.log(`[${this.serverName}] All transports closed.`);
|
|
1246
|
-
resolve2();
|
|
1247
|
-
}).catch((transportCloseErr) => {
|
|
1248
|
-
Logger.error(`[${this.serverName}] Error during bulk transport closing:`, transportCloseErr);
|
|
1249
|
-
reject(transportCloseErr);
|
|
1250
|
-
});
|
|
1438
|
+
resolve6();
|
|
1251
1439
|
});
|
|
1252
1440
|
});
|
|
1253
1441
|
}
|
|
@@ -1255,90 +1443,851 @@ var BaseMcpServer = class {
|
|
|
1255
1443
|
|
|
1256
1444
|
// src/cli.ts
|
|
1257
1445
|
import { fileURLToPath } from "url";
|
|
1258
|
-
import { dirname } from "path";
|
|
1259
|
-
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
1446
|
+
import { dirname as dirname2 } from "path";
|
|
1447
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync4 } from "fs";
|
|
1260
1448
|
import { createInterface } from "readline";
|
|
1261
1449
|
|
|
1262
|
-
// src/utils/
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1450
|
+
// src/utils/upgradeManager.ts
|
|
1451
|
+
import { spawnSync } from "child_process";
|
|
1452
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1453
|
+
import { homedir } from "os";
|
|
1454
|
+
import { dirname, join as join2 } from "path";
|
|
1455
|
+
|
|
1456
|
+
// src/utils/cliSettings.ts
|
|
1457
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
1458
|
+
import { join, resolve } from "path";
|
|
1459
|
+
var SETTINGS_PROGRAM_NAME = "mcp-google-map";
|
|
1460
|
+
var CLI_SETTINGS_RELATIVE = [".config", SETTINGS_PROGRAM_NAME, "config.json"];
|
|
1461
|
+
var MCP_SERVER_NAME = "mcp-google-map";
|
|
1462
|
+
function resolveCliSettingsPath(home) {
|
|
1463
|
+
const root = home ?? (process.env.HOME ?? process.cwd());
|
|
1464
|
+
return resolve(root, ...CLI_SETTINGS_RELATIVE);
|
|
1270
1465
|
}
|
|
1271
|
-
function
|
|
1272
|
-
|
|
1273
|
-
const
|
|
1274
|
-
const
|
|
1275
|
-
|
|
1276
|
-
|
|
1466
|
+
function uniqueStrings(value) {
|
|
1467
|
+
if (!Array.isArray(value)) return [];
|
|
1468
|
+
const result = [];
|
|
1469
|
+
for (const item of value) {
|
|
1470
|
+
if (typeof item === "string" && item.length > 0 && !result.includes(item)) {
|
|
1471
|
+
result.push(item);
|
|
1472
|
+
}
|
|
1277
1473
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1474
|
+
return result;
|
|
1475
|
+
}
|
|
1476
|
+
function loadCliSettings(home) {
|
|
1477
|
+
try {
|
|
1478
|
+
const raw = JSON.parse(readFileSync(resolveCliSettingsPath(home), "utf-8"));
|
|
1479
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1480
|
+
return { enabled: [] };
|
|
1284
1481
|
}
|
|
1482
|
+
return { enabled: uniqueStrings(raw.enabled) };
|
|
1483
|
+
} catch {
|
|
1484
|
+
return { enabled: [] };
|
|
1285
1485
|
}
|
|
1286
|
-
return false;
|
|
1287
1486
|
}
|
|
1288
|
-
|
|
1289
|
-
const
|
|
1290
|
-
const
|
|
1487
|
+
function saveCliSettings(settings, home) {
|
|
1488
|
+
const path = resolveCliSettingsPath(home);
|
|
1489
|
+
const temporary = `${path}.tmp`;
|
|
1291
1490
|
try {
|
|
1292
|
-
|
|
1293
|
-
|
|
1491
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
1492
|
+
writeFileSync(temporary, JSON.stringify({ enabled: uniqueStrings(settings.enabled) }, null, 2) + "\n", "utf-8");
|
|
1493
|
+
renameSync(temporary, path);
|
|
1494
|
+
} catch (error) {
|
|
1495
|
+
throw new Error(`settings write failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1496
|
+
}
|
|
1497
|
+
return path;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
// src/utils/upgradeManager.ts
|
|
1501
|
+
var ANSI_GREEN = "\x1B[92m";
|
|
1502
|
+
var ANSI_RED = "\x1B[91m";
|
|
1503
|
+
var ANSI_RESET = "\x1B[0m";
|
|
1504
|
+
var green = (text) => `${ANSI_GREEN}${text}${ANSI_RESET}`;
|
|
1505
|
+
var red = (text) => `${ANSI_RED}${text}${ANSI_RESET}`;
|
|
1506
|
+
var IDLE_CACHE_DIR_NAME = "mcp-google-map";
|
|
1507
|
+
var IDLE_CACHE_FILE_NAME = "check_version_idle-time.json";
|
|
1508
|
+
var IDLE_WINDOW_NEWER_MS = 60 * 60 * 1e3;
|
|
1509
|
+
var IDLE_WINDOW_DEFAULT_MS = 24 * 60 * 60 * 1e3;
|
|
1510
|
+
var PACKAGE_UPGRADE_COMMAND = [
|
|
1511
|
+
"npm",
|
|
1512
|
+
"install",
|
|
1513
|
+
"-g",
|
|
1514
|
+
`${DEFAULT_PACKAGE_NAME}@latest`
|
|
1515
|
+
];
|
|
1516
|
+
var PACKAGE_UNINSTALL_COMMAND = [
|
|
1517
|
+
"npm",
|
|
1518
|
+
"uninstall",
|
|
1519
|
+
"-g",
|
|
1520
|
+
DEFAULT_PACKAGE_NAME
|
|
1521
|
+
];
|
|
1522
|
+
var POST_UPGRADE_INSTALL_COMMAND = [
|
|
1523
|
+
"npx",
|
|
1524
|
+
"-y",
|
|
1525
|
+
DEFAULT_PACKAGE_NAME,
|
|
1526
|
+
"install"
|
|
1527
|
+
];
|
|
1528
|
+
function resolveIdleCachePath(home) {
|
|
1529
|
+
return join2(home ?? homedir(), ".cache", IDLE_CACHE_DIR_NAME, IDLE_CACHE_FILE_NAME);
|
|
1530
|
+
}
|
|
1531
|
+
function loadIdleCache(cachePath, fs = {}) {
|
|
1532
|
+
try {
|
|
1533
|
+
const readFile = fs.readFile ?? ((path) => readFileSync2(path, "utf-8"));
|
|
1534
|
+
const raw = readFile(cachePath);
|
|
1535
|
+
const parsed = JSON.parse(raw);
|
|
1536
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
1537
|
+
return {};
|
|
1294
1538
|
}
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1539
|
+
const record = parsed;
|
|
1540
|
+
const isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
1541
|
+
return {
|
|
1542
|
+
last_success_epoch: isFiniteNumber(record.last_success_epoch) ? record.last_success_epoch : void 0,
|
|
1543
|
+
last_success_human: typeof record.last_success_human === "string" ? record.last_success_human : void 0,
|
|
1544
|
+
idle_time_epoch: isFiniteNumber(record.idle_time_epoch) ? record.idle_time_epoch : void 0,
|
|
1545
|
+
idle_time_human: typeof record.idle_time_human === "string" ? record.idle_time_human : void 0
|
|
1546
|
+
};
|
|
1547
|
+
} catch {
|
|
1548
|
+
return {};
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
function persistIdleCache(cachePath, lastSuccess, idleUntil, fs = {}, logger = Logger) {
|
|
1552
|
+
const toIso = (epoch) => epoch === void 0 ? null : new Date(epoch).toISOString();
|
|
1553
|
+
const data = {
|
|
1554
|
+
last_success_epoch: lastSuccess ?? null,
|
|
1555
|
+
last_success_human: toIso(lastSuccess),
|
|
1556
|
+
idle_time_epoch: idleUntil,
|
|
1557
|
+
idle_time_human: toIso(idleUntil)
|
|
1558
|
+
};
|
|
1559
|
+
try {
|
|
1560
|
+
const mkdir = fs.mkdir ?? ((path) => mkdirSync2(path, { recursive: true }));
|
|
1561
|
+
const writeFile = fs.writeFile ?? ((path, data2) => writeFileSync2(path, data2));
|
|
1562
|
+
mkdir(dirname(cachePath));
|
|
1563
|
+
writeFile(cachePath, JSON.stringify(data, null, 2) + "\n");
|
|
1564
|
+
} catch (error) {
|
|
1565
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1566
|
+
logger.error(red(`Version cache error: ${message}`));
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
async function checkVersionIdle(deps = {}) {
|
|
1570
|
+
const logger = deps.logger ?? Logger;
|
|
1571
|
+
const now = deps.now ?? (() => Date.now());
|
|
1572
|
+
const cachePath = deps.cachePath ?? resolveIdleCachePath();
|
|
1573
|
+
const cache = loadIdleCache(cachePath, deps.fs);
|
|
1574
|
+
if (deps.force !== true) {
|
|
1575
|
+
const idle = cache.idle_time_epoch;
|
|
1576
|
+
if (typeof idle === "number" && idle > now()) {
|
|
1577
|
+
return { checked: false, status: "idle" };
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
const probe = await probeLatestVersion(deps);
|
|
1581
|
+
if (probe.status === "disabled") {
|
|
1582
|
+
return { checked: false, status: "disabled" };
|
|
1583
|
+
}
|
|
1584
|
+
let delayMs = IDLE_WINDOW_DEFAULT_MS;
|
|
1585
|
+
let lastSuccess = typeof cache.last_success_epoch === "number" ? cache.last_success_epoch : void 0;
|
|
1586
|
+
const result = { checked: true, status: probe.status };
|
|
1587
|
+
switch (probe.status) {
|
|
1588
|
+
case "unknown-version":
|
|
1589
|
+
logger.error(red("Version check error: running package version is unknown."));
|
|
1590
|
+
break;
|
|
1591
|
+
case "error":
|
|
1592
|
+
logger.error(red(`Version check error: ${probe.message}`));
|
|
1593
|
+
break;
|
|
1594
|
+
case "current":
|
|
1595
|
+
lastSuccess = now();
|
|
1596
|
+
result.currentVersion = probe.currentVersion;
|
|
1597
|
+
logger.error(red("Version check: no newer version available."));
|
|
1598
|
+
break;
|
|
1599
|
+
case "newer": {
|
|
1600
|
+
lastSuccess = now();
|
|
1601
|
+
delayMs = IDLE_WINDOW_NEWER_MS;
|
|
1602
|
+
result.currentVersion = probe.currentVersion;
|
|
1603
|
+
result.latestVersion = probe.latestVersion;
|
|
1604
|
+
logger.log(
|
|
1605
|
+
green(
|
|
1606
|
+
`Available Version: ${probe.latestVersion} | Installed Version: ${probe.currentVersion}`
|
|
1607
|
+
)
|
|
1299
1608
|
);
|
|
1300
|
-
|
|
1609
|
+
logger.log(
|
|
1610
|
+
green(
|
|
1611
|
+
`Upgrade with: npm install -g ${DEFAULT_PACKAGE_NAME}@latest (or run: ${DEFAULT_PACKAGE_NAME} --upgrade)`
|
|
1612
|
+
)
|
|
1613
|
+
);
|
|
1614
|
+
break;
|
|
1301
1615
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1616
|
+
}
|
|
1617
|
+
const finishedAt = now();
|
|
1618
|
+
const idleUntil = finishedAt + delayMs;
|
|
1619
|
+
persistIdleCache(cachePath, lastSuccess, idleUntil, deps.fs, logger);
|
|
1620
|
+
result.idleUntilEpoch = idleUntil;
|
|
1621
|
+
result.lastSuccessEpoch = lastSuccess;
|
|
1622
|
+
return result;
|
|
1623
|
+
}
|
|
1624
|
+
function parseManagementCommand(args) {
|
|
1625
|
+
if (args.length !== 1) {
|
|
1626
|
+
return void 0;
|
|
1627
|
+
}
|
|
1628
|
+
const arg = args[0];
|
|
1629
|
+
if (arg === "--ver" || arg === "--version" || arg === "--upgrade" || arg === "--uninstall") {
|
|
1630
|
+
return arg;
|
|
1631
|
+
}
|
|
1632
|
+
return void 0;
|
|
1633
|
+
}
|
|
1634
|
+
function runPackageManagerCommand(command, deps = {}) {
|
|
1635
|
+
const logger = deps.logger ?? Logger;
|
|
1636
|
+
const spawnImpl = deps.spawn ?? spawnSync;
|
|
1637
|
+
const reportSpawnFailure = (message) => {
|
|
1638
|
+
logger.error(`Error executing ${command[0]}: ${message}`);
|
|
1639
|
+
logger.log(`Warning: run this command manually: ${command.join(" ")}`);
|
|
1640
|
+
return 1;
|
|
1641
|
+
};
|
|
1642
|
+
try {
|
|
1643
|
+
const result = spawnImpl(command[0], [...command.slice(1)], {
|
|
1644
|
+
stdio: "inherit",
|
|
1645
|
+
shell: process.platform === "win32"
|
|
1309
1646
|
});
|
|
1310
|
-
if (
|
|
1311
|
-
|
|
1312
|
-
`\u26A0\uFE0F [${DEFAULT_PACKAGE_NAME}] Automatic update check failed: registry responded with HTTP ${response.status}.`
|
|
1313
|
-
);
|
|
1314
|
-
return null;
|
|
1647
|
+
if (result.error) {
|
|
1648
|
+
return reportSpawnFailure(result.error.message);
|
|
1315
1649
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1650
|
+
return result.status ?? 1;
|
|
1651
|
+
} catch (error) {
|
|
1652
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1653
|
+
return reportSpawnFailure(message);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
function runPostUpgradeSkillInstall(options, deps = {}) {
|
|
1657
|
+
const enabled = loadCliSettings(options.home).enabled;
|
|
1658
|
+
if (enabled.length === 0) {
|
|
1659
|
+
return 0;
|
|
1660
|
+
}
|
|
1661
|
+
return runPackageManagerCommand(POST_UPGRADE_INSTALL_COMMAND, deps);
|
|
1662
|
+
}
|
|
1663
|
+
async function runVersionCommand(options, deps = {}) {
|
|
1664
|
+
await checkVersionIdle({
|
|
1665
|
+
...deps.idleCheck,
|
|
1666
|
+
force: true,
|
|
1667
|
+
currentVersion: options.currentVersion
|
|
1668
|
+
});
|
|
1669
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
1670
|
+
stdout.write(`${options.currentVersion}
|
|
1671
|
+
`);
|
|
1672
|
+
return 0;
|
|
1673
|
+
}
|
|
1674
|
+
function runUpgradeCommand(options, deps = {}) {
|
|
1675
|
+
const upgraded = runPackageManagerCommand(PACKAGE_UPGRADE_COMMAND, deps);
|
|
1676
|
+
if (upgraded !== 0) {
|
|
1677
|
+
return upgraded;
|
|
1678
|
+
}
|
|
1679
|
+
return runPostUpgradeSkillInstall(options, deps);
|
|
1680
|
+
}
|
|
1681
|
+
function runUninstallCommand(deps = {}) {
|
|
1682
|
+
return runPackageManagerCommand(PACKAGE_UNINSTALL_COMMAND, deps);
|
|
1683
|
+
}
|
|
1684
|
+
async function runManagementCommand(command, options, deps = {}) {
|
|
1685
|
+
switch (command) {
|
|
1686
|
+
case "--ver":
|
|
1687
|
+
case "--version":
|
|
1688
|
+
return runVersionCommand(options, deps);
|
|
1689
|
+
case "--upgrade":
|
|
1690
|
+
return runUpgradeCommand(options, deps);
|
|
1691
|
+
case "--uninstall":
|
|
1692
|
+
return runUninstallCommand(deps);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// src/utils/toolProfiles.ts
|
|
1697
|
+
var TOOL_API_PROFILES = {
|
|
1698
|
+
maps_geocode: { tool: "maps_geocode", apis: ["geocoding"], dependsOnTools: [] },
|
|
1699
|
+
maps_reverse_geocode: { tool: "maps_reverse_geocode", apis: ["geocoding"], dependsOnTools: [] },
|
|
1700
|
+
maps_batch_geocode: { tool: "maps_batch_geocode", apis: ["geocoding"], dependsOnTools: ["maps_geocode"] },
|
|
1701
|
+
maps_search_nearby: {
|
|
1702
|
+
tool: "maps_search_nearby",
|
|
1703
|
+
apis: ["places-new", "geocoding"],
|
|
1704
|
+
dependsOnTools: ["maps_geocode"]
|
|
1705
|
+
},
|
|
1706
|
+
maps_search_places: { tool: "maps_search_places", apis: ["places-new"], dependsOnTools: [] },
|
|
1707
|
+
maps_place_details: { tool: "maps_place_details", apis: ["places-new"], dependsOnTools: [] },
|
|
1708
|
+
maps_directions: { tool: "maps_directions", apis: ["routes"], dependsOnTools: [] },
|
|
1709
|
+
maps_distance_matrix: { tool: "maps_distance_matrix", apis: ["routes"], dependsOnTools: [] },
|
|
1710
|
+
maps_elevation: { tool: "maps_elevation", apis: ["elevation"], dependsOnTools: [] },
|
|
1711
|
+
maps_timezone: { tool: "maps_timezone", apis: ["timezone"], dependsOnTools: [] },
|
|
1712
|
+
maps_weather: { tool: "maps_weather", apis: ["weather"], dependsOnTools: [] },
|
|
1713
|
+
maps_air_quality: { tool: "maps_air_quality", apis: ["air-quality"], dependsOnTools: [] },
|
|
1714
|
+
maps_static_map: { tool: "maps_static_map", apis: ["static-maps"], dependsOnTools: [] },
|
|
1715
|
+
maps_explore_area: {
|
|
1716
|
+
tool: "maps_explore_area",
|
|
1717
|
+
apis: ["places-new", "geocoding"],
|
|
1718
|
+
dependsOnTools: ["maps_geocode", "maps_search_nearby", "maps_place_details"]
|
|
1719
|
+
},
|
|
1720
|
+
maps_plan_route: {
|
|
1721
|
+
tool: "maps_plan_route",
|
|
1722
|
+
apis: ["routes", "geocoding", "places-new"],
|
|
1723
|
+
dependsOnTools: ["maps_geocode", "maps_search_places", "maps_directions"]
|
|
1724
|
+
},
|
|
1725
|
+
maps_compare_places: {
|
|
1726
|
+
tool: "maps_compare_places",
|
|
1727
|
+
apis: ["places-new", "routes"],
|
|
1728
|
+
dependsOnTools: ["maps_search_places", "maps_place_details", "maps_distance_matrix"]
|
|
1729
|
+
},
|
|
1730
|
+
maps_search_along_route: {
|
|
1731
|
+
tool: "maps_search_along_route",
|
|
1732
|
+
apis: ["routes", "places-new"],
|
|
1733
|
+
dependsOnTools: ["maps_directions", "maps_search_places"]
|
|
1734
|
+
},
|
|
1735
|
+
maps_local_rank_tracker: {
|
|
1736
|
+
tool: "maps_local_rank_tracker",
|
|
1737
|
+
apis: ["places-new"],
|
|
1738
|
+
dependsOnTools: ["maps_place_details", "maps_search_places"]
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
function describeToolsProfile(toolNames) {
|
|
1742
|
+
const tools = [];
|
|
1743
|
+
const apiOrder = [];
|
|
1744
|
+
const seenApis = /* @__PURE__ */ new Set();
|
|
1745
|
+
const seenTools = /* @__PURE__ */ new Set();
|
|
1746
|
+
for (const name of toolNames) {
|
|
1747
|
+
if (seenTools.has(name)) {
|
|
1748
|
+
continue;
|
|
1749
|
+
}
|
|
1750
|
+
const profile = TOOL_API_PROFILES[name];
|
|
1751
|
+
if (!profile) {
|
|
1752
|
+
continue;
|
|
1753
|
+
}
|
|
1754
|
+
seenTools.add(name);
|
|
1755
|
+
tools.push({
|
|
1756
|
+
tool: profile.tool,
|
|
1757
|
+
apis: [...profile.apis],
|
|
1758
|
+
dependsOnTools: [...profile.dependsOnTools]
|
|
1759
|
+
});
|
|
1760
|
+
for (const api of profile.apis) {
|
|
1761
|
+
if (!seenApis.has(api)) {
|
|
1762
|
+
seenApis.add(api);
|
|
1763
|
+
apiOrder.push(api);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
return { toolCount: tools.length, apis: apiOrder, tools };
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// src/utils/clientSnippets.ts
|
|
1771
|
+
var API_KEY_PLACEHOLDER = "<your-google-maps-api-key>";
|
|
1772
|
+
var SNIPPET_DEFAULT_HOST = "127.0.0.1";
|
|
1773
|
+
var SNIPPET_DEFAULT_PORT = 3e3;
|
|
1774
|
+
var SNIPPET_CLIENT_TARGETS = {
|
|
1775
|
+
generic: "your MCP client configuration file",
|
|
1776
|
+
claude: "Claude Desktop: claude_desktop_config.json",
|
|
1777
|
+
cursor: "Cursor: .cursor/mcp.json",
|
|
1778
|
+
pi: "pi CLI: ~/.pi/agent/mcp.json (user) or .pi/mcp.json (project)"
|
|
1779
|
+
};
|
|
1780
|
+
function renderClientSnippet(options = {}) {
|
|
1781
|
+
const client = options.client ?? "generic";
|
|
1782
|
+
const transport = options.transport ?? "stdio";
|
|
1783
|
+
const serverName = options.serverName ?? "mcp-google-map";
|
|
1784
|
+
const notes = [`Paste this snippet into ${SNIPPET_CLIENT_TARGETS[client]}.`];
|
|
1785
|
+
notes.push("This command prints the snippet only; it never writes or edits configuration files.");
|
|
1786
|
+
let entry;
|
|
1787
|
+
if (transport === "http") {
|
|
1788
|
+
const host = options.host ?? SNIPPET_DEFAULT_HOST;
|
|
1789
|
+
const port = options.port ?? SNIPPET_DEFAULT_PORT;
|
|
1790
|
+
entry = { url: `http://${host}:${port}/mcp` };
|
|
1791
|
+
notes.push(`Start the server first: mcp-google-map --host ${host} --port ${port}.`);
|
|
1792
|
+
} else {
|
|
1793
|
+
const launcher = options.launcher ?? "npx";
|
|
1794
|
+
if (launcher === "node" && !options.commandPath) {
|
|
1795
|
+
throw new Error("--launcher node requires --command <path> pointing at dist/cli.js");
|
|
1796
|
+
}
|
|
1797
|
+
const command = launcher === "npx" ? "npx" : launcher === "bin" ? "mcp-google-map" : "node";
|
|
1798
|
+
const args = launcher === "npx" ? ["-y", "mcp-google-map", "--stdio"] : launcher === "bin" ? ["--stdio"] : [options.commandPath, "--stdio"];
|
|
1799
|
+
if (launcher === "npx") {
|
|
1800
|
+
notes.push("The npx launcher runs the published package; npm must be available on PATH.");
|
|
1801
|
+
}
|
|
1802
|
+
const apiKeyValue = options.envKey ? `\${${options.envKey}}` : API_KEY_PLACEHOLDER;
|
|
1803
|
+
entry = { command, args, env: { GOOGLE_MAPS_API_KEY: apiKeyValue } };
|
|
1804
|
+
if (options.envKey) {
|
|
1805
|
+
notes.push(
|
|
1806
|
+
`Variable references (\${${options.envKey}}) are client-specific: confirm your client expands environment variables before relying on them.`
|
|
1320
1807
|
);
|
|
1321
|
-
|
|
1808
|
+
} else {
|
|
1809
|
+
notes.push(`Replace ${API_KEY_PLACEHOLDER} with your Google Maps API key.`);
|
|
1322
1810
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1811
|
+
}
|
|
1812
|
+
const json = JSON.stringify({ mcpServers: { [serverName]: entry } }, null, 2);
|
|
1813
|
+
return { json, notes };
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
// src/utils/cliTargets.ts
|
|
1817
|
+
import {
|
|
1818
|
+
cpSync,
|
|
1819
|
+
existsSync as existsSync2,
|
|
1820
|
+
mkdirSync as mkdirSync3,
|
|
1821
|
+
readFileSync as readFileSync3,
|
|
1822
|
+
readdirSync,
|
|
1823
|
+
renameSync as renameSync2,
|
|
1824
|
+
rmSync,
|
|
1825
|
+
statSync,
|
|
1826
|
+
writeFileSync as writeFileSync3
|
|
1827
|
+
} from "fs";
|
|
1828
|
+
import { join as join3, relative } from "path";
|
|
1829
|
+
function homeRoot(home) {
|
|
1830
|
+
return home ?? (process.env.HOME ?? process.cwd());
|
|
1831
|
+
}
|
|
1832
|
+
var PI_TARGET = {
|
|
1833
|
+
id: "pi",
|
|
1834
|
+
configFormat: "json",
|
|
1835
|
+
mcpConfigPath: (home) => join3(homeRoot(home), ".pi", "agent", "mcp.json"),
|
|
1836
|
+
skillsDir: (home) => join3(homeRoot(home), ".pi", "agent", "skills")
|
|
1837
|
+
};
|
|
1838
|
+
var OPENCLAW_TARGET = {
|
|
1839
|
+
id: "openclaw",
|
|
1840
|
+
configFormat: "json",
|
|
1841
|
+
mcpConfigPath: (home) => join3(homeRoot(home), ".openclaw", "openclaw.json"),
|
|
1842
|
+
skillsDir: (home) => join3(homeRoot(home), ".openclaw", "workspace", "skills")
|
|
1843
|
+
};
|
|
1844
|
+
var CODEX_TARGET = {
|
|
1845
|
+
id: "codex",
|
|
1846
|
+
configFormat: "toml",
|
|
1847
|
+
mcpConfigPath: (home) => join3(homeRoot(home), ".codex", "config.toml"),
|
|
1848
|
+
skillsDir: (home) => join3(homeRoot(home), ".codex", "skills")
|
|
1849
|
+
};
|
|
1850
|
+
var ANTIGRAVITY_TARGET = {
|
|
1851
|
+
id: "antigravity",
|
|
1852
|
+
configFormat: "json",
|
|
1853
|
+
mcpConfigPath: (home) => join3(homeRoot(home), ".gemini", "settings.json"),
|
|
1854
|
+
skillsDir: (home) => join3(homeRoot(home), ".gemini", "config", "skills")
|
|
1855
|
+
};
|
|
1856
|
+
var CLAUDE_TARGET = {
|
|
1857
|
+
id: "claude",
|
|
1858
|
+
configFormat: "json",
|
|
1859
|
+
mcpConfigPath: (home) => join3(homeRoot(home), ".claude.json"),
|
|
1860
|
+
skillsDir: (home) => join3(homeRoot(home), ".claude", "skills")
|
|
1861
|
+
};
|
|
1862
|
+
var CLI_TARGETS = {
|
|
1863
|
+
pi: PI_TARGET,
|
|
1864
|
+
openclaw: OPENCLAW_TARGET,
|
|
1865
|
+
codex: CODEX_TARGET,
|
|
1866
|
+
antigravity: ANTIGRAVITY_TARGET,
|
|
1867
|
+
claude: CLAUDE_TARGET
|
|
1868
|
+
};
|
|
1869
|
+
function resolveCliTarget(name) {
|
|
1870
|
+
return CLI_TARGETS[name.trim().toLowerCase()];
|
|
1871
|
+
}
|
|
1872
|
+
function supportedCliNames() {
|
|
1873
|
+
return Object.keys(CLI_TARGETS).join(", ");
|
|
1874
|
+
}
|
|
1875
|
+
function readJsonConfig(configPath) {
|
|
1876
|
+
if (!existsSync2(configPath)) return {};
|
|
1877
|
+
const parsed = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1878
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1879
|
+
throw new Error(`configuration is not a JSON object: ${configPath}`);
|
|
1880
|
+
}
|
|
1881
|
+
return parsed;
|
|
1882
|
+
}
|
|
1883
|
+
function writeJsonConfig(configPath, value) {
|
|
1884
|
+
const temporary = `${configPath}.tmp`;
|
|
1885
|
+
mkdirSync3(join3(configPath, ".."), { recursive: true });
|
|
1886
|
+
writeFileSync3(temporary, JSON.stringify(value, null, 2) + "\n", "utf-8");
|
|
1887
|
+
renameSync2(temporary, configPath);
|
|
1888
|
+
}
|
|
1889
|
+
function writeJsonMcpServerEntry(configPath, serverName, entry) {
|
|
1890
|
+
const config = readJsonConfig(configPath);
|
|
1891
|
+
const mcpServers = config.mcpServers ?? {};
|
|
1892
|
+
if (mcpServers === null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
1893
|
+
throw new Error(`mcpServers is not an object: ${configPath}`);
|
|
1894
|
+
}
|
|
1895
|
+
mcpServers[serverName] = entry;
|
|
1896
|
+
config.mcpServers = mcpServers;
|
|
1897
|
+
writeJsonConfig(configPath, config);
|
|
1898
|
+
}
|
|
1899
|
+
function removeJsonMcpServerEntry(configPath, serverName) {
|
|
1900
|
+
if (!existsSync2(configPath)) return false;
|
|
1901
|
+
const config = readJsonConfig(configPath);
|
|
1902
|
+
const mcpServers = config.mcpServers;
|
|
1903
|
+
if (mcpServers === void 0) return false;
|
|
1904
|
+
if (mcpServers === null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
1905
|
+
throw new Error(`mcpServers is not an object: ${configPath}`);
|
|
1906
|
+
}
|
|
1907
|
+
const map = mcpServers;
|
|
1908
|
+
if (!(serverName in map)) return false;
|
|
1909
|
+
delete map[serverName];
|
|
1910
|
+
config.mcpServers = map;
|
|
1911
|
+
writeJsonConfig(configPath, config);
|
|
1912
|
+
return true;
|
|
1913
|
+
}
|
|
1914
|
+
function readJsonMcpServerEntry(configPath, serverName) {
|
|
1915
|
+
try {
|
|
1916
|
+
const config = readJsonConfig(configPath);
|
|
1917
|
+
const mcpServers = config.mcpServers;
|
|
1918
|
+
if (mcpServers === null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
1919
|
+
return void 0;
|
|
1325
1920
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1921
|
+
return mcpServers[serverName];
|
|
1922
|
+
} catch {
|
|
1923
|
+
return void 0;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
function tomlString(value) {
|
|
1927
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1928
|
+
}
|
|
1929
|
+
function tomlSectionHeader(serverName) {
|
|
1930
|
+
return `[mcp_servers.${serverName}]`;
|
|
1931
|
+
}
|
|
1932
|
+
function removeTomlSection(content, serverName) {
|
|
1933
|
+
const lines = content.split("\n");
|
|
1934
|
+
const header = tomlSectionHeader(serverName);
|
|
1935
|
+
const kept = [];
|
|
1936
|
+
let skipping = false;
|
|
1937
|
+
for (const line of lines) {
|
|
1938
|
+
if (skipping) {
|
|
1939
|
+
if (line.trimStart().startsWith("[")) {
|
|
1940
|
+
skipping = false;
|
|
1941
|
+
} else {
|
|
1942
|
+
continue;
|
|
1943
|
+
}
|
|
1944
|
+
} else if (line.trim() === header) {
|
|
1945
|
+
skipping = true;
|
|
1946
|
+
while (kept.length > 0 && kept[kept.length - 1].trim() === "") {
|
|
1947
|
+
kept.pop();
|
|
1948
|
+
}
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
kept.push(line);
|
|
1952
|
+
}
|
|
1953
|
+
while (kept.length > 0 && kept[kept.length - 1].trim() === "") {
|
|
1954
|
+
kept.pop();
|
|
1955
|
+
}
|
|
1956
|
+
return kept.length > 0 ? kept.join("\n") + "\n" : "";
|
|
1957
|
+
}
|
|
1958
|
+
function hasTomlSection(content, serverName) {
|
|
1959
|
+
return content.split("\n").some((line) => line.trim() === tomlSectionHeader(serverName));
|
|
1960
|
+
}
|
|
1961
|
+
function registrationToTomlLines(entry) {
|
|
1962
|
+
const lines = [`command = ${tomlString(entry.command)}`];
|
|
1963
|
+
if (entry.args !== void 0) {
|
|
1964
|
+
lines.push(`args = [${entry.args.map((arg) => tomlString(arg)).join(", ")}]`);
|
|
1965
|
+
}
|
|
1966
|
+
if (entry.env !== void 0) {
|
|
1967
|
+
const pairs = Object.entries(entry.env).map(([key, value]) => `${tomlString(key)} = ${tomlString(value)}`);
|
|
1968
|
+
if (pairs.length > 0) lines.push(`env = { ${pairs.join(", ")} }`);
|
|
1969
|
+
}
|
|
1970
|
+
return lines;
|
|
1971
|
+
}
|
|
1972
|
+
function writeTomlMcpServerSection(configPath, serverName, entry) {
|
|
1973
|
+
const previous = existsSync2(configPath) ? readFileSync3(configPath, "utf-8") : "";
|
|
1974
|
+
const base = removeTomlSection(previous, serverName);
|
|
1975
|
+
const table = [tomlSectionHeader(serverName), ...registrationToTomlLines(entry), ""].join("\n");
|
|
1976
|
+
const content = base.length > 0 ? base.replace(/\n*$/, "\n") + table : table;
|
|
1977
|
+
const temporary = `${configPath}.tmp`;
|
|
1978
|
+
mkdirSync3(join3(configPath, ".."), { recursive: true });
|
|
1979
|
+
writeFileSync3(temporary, content, "utf-8");
|
|
1980
|
+
renameSync2(temporary, configPath);
|
|
1981
|
+
}
|
|
1982
|
+
function removeTomlMcpServerSection(configPath, serverName) {
|
|
1983
|
+
if (!existsSync2(configPath)) return false;
|
|
1984
|
+
const previous = readFileSync3(configPath, "utf-8");
|
|
1985
|
+
if (!hasTomlSection(previous, serverName)) return false;
|
|
1986
|
+
const content = removeTomlSection(previous, serverName);
|
|
1987
|
+
const temporary = `${configPath}.tmp`;
|
|
1988
|
+
writeFileSync3(temporary, content, "utf-8");
|
|
1989
|
+
renameSync2(temporary, configPath);
|
|
1990
|
+
return true;
|
|
1991
|
+
}
|
|
1992
|
+
function hasTomlMcpServerSection(configPath, serverName) {
|
|
1993
|
+
if (!existsSync2(configPath)) return false;
|
|
1994
|
+
return hasTomlSection(readFileSync3(configPath, "utf-8"), serverName);
|
|
1995
|
+
}
|
|
1996
|
+
function listFilesRecursive(root) {
|
|
1997
|
+
if (!existsSync2(root)) return [];
|
|
1998
|
+
const files = [];
|
|
1999
|
+
const walk = (dir) => {
|
|
2000
|
+
for (const entry of readdirSync(dir)) {
|
|
2001
|
+
const full = join3(dir, entry);
|
|
2002
|
+
if (statSync(full).isDirectory()) {
|
|
2003
|
+
walk(full);
|
|
2004
|
+
} else {
|
|
2005
|
+
files.push(relative(root, full).split("\\").join("/"));
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
walk(root);
|
|
2010
|
+
return files;
|
|
2011
|
+
}
|
|
2012
|
+
function syncSkillTree(source, destination) {
|
|
2013
|
+
mkdirSync3(destination, { recursive: true });
|
|
2014
|
+
cpSync(source, destination, { recursive: true, force: true });
|
|
2015
|
+
const expected = new Set(listFilesRecursive(source));
|
|
2016
|
+
const existing = listFilesRecursive(destination);
|
|
2017
|
+
existing.sort((a, b) => b.length - a.length);
|
|
2018
|
+
for (const rel of existing) {
|
|
2019
|
+
if (expected.has(rel)) continue;
|
|
2020
|
+
rmSync(join3(destination, rel), { recursive: true, force: true });
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
function removeSkillDir(skillDir) {
|
|
2024
|
+
rmSync(skillDir, { recursive: true, force: true });
|
|
2025
|
+
}
|
|
2026
|
+
function compareSkillTree(source, destination) {
|
|
2027
|
+
const packaged = listFilesRecursive(source);
|
|
2028
|
+
if (packaged.length === 0) return "MISSING";
|
|
2029
|
+
if (!existsSync2(destination)) return "MISSING";
|
|
2030
|
+
for (const rel of packaged) {
|
|
2031
|
+
const target = join3(destination, ...rel.split("/"));
|
|
2032
|
+
if (!existsSync2(target)) return "MISSING";
|
|
2033
|
+
if (readFileSync3(target).toString("utf-8") !== readFileSync3(join3(source, ...rel.split("/"))).toString("utf-8")) {
|
|
2034
|
+
return "STALE";
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
return "OK";
|
|
2038
|
+
}
|
|
2039
|
+
function isServerRegistered(target, serverName, home) {
|
|
2040
|
+
const configPath = target.mcpConfigPath(home);
|
|
2041
|
+
if (target.configFormat === "toml") {
|
|
2042
|
+
return hasTomlMcpServerSection(configPath, serverName);
|
|
2043
|
+
}
|
|
2044
|
+
return readJsonMcpServerEntry(configPath, serverName) !== void 0;
|
|
2045
|
+
}
|
|
2046
|
+
function applyServerRegistration(target, serverName, entry, home) {
|
|
2047
|
+
const configPath = target.mcpConfigPath(home);
|
|
2048
|
+
if (target.configFormat === "toml") {
|
|
2049
|
+
if (entry === void 0) {
|
|
2050
|
+
removeTomlMcpServerSection(configPath, serverName);
|
|
2051
|
+
} else {
|
|
2052
|
+
writeTomlMcpServerSection(configPath, serverName, entry);
|
|
2053
|
+
}
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
if (entry === void 0) {
|
|
2057
|
+
removeJsonMcpServerEntry(configPath, serverName);
|
|
2058
|
+
} else {
|
|
2059
|
+
writeJsonMcpServerEntry(configPath, serverName, entry);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
// src/utils/cliInstaller.ts
|
|
2064
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
2065
|
+
import { join as join4, resolve as resolve3 } from "path";
|
|
2066
|
+
function buildServerRegistration(apiKey) {
|
|
2067
|
+
const env = {};
|
|
2068
|
+
if (apiKey !== void 0 && apiKey.length > 0) {
|
|
2069
|
+
env.GOOGLE_MAPS_API_KEY = apiKey;
|
|
2070
|
+
}
|
|
2071
|
+
return { command: "npx", args: ["-y", MCP_SERVER_NAME, "--stdio"], env };
|
|
2072
|
+
}
|
|
2073
|
+
function resolveSkillsResourcesRoot(packageRoot) {
|
|
2074
|
+
const candidate = resolve3(packageRoot, "src", "resources", "skills");
|
|
2075
|
+
if (!existsSync3(candidate)) {
|
|
2076
|
+
throw new Error(`skills resources root not found: ${candidate}`);
|
|
2077
|
+
}
|
|
2078
|
+
return candidate;
|
|
2079
|
+
}
|
|
2080
|
+
function loadSkillsManifest(resourcesRoot) {
|
|
2081
|
+
const manifestPath = join4(resourcesRoot, "manifest.json");
|
|
2082
|
+
if (!existsSync3(manifestPath)) {
|
|
2083
|
+
throw new Error(`skills manifest not found: ${manifestPath}`);
|
|
2084
|
+
}
|
|
2085
|
+
let parsed;
|
|
2086
|
+
try {
|
|
2087
|
+
parsed = JSON.parse(readFileSync4(manifestPath, "utf-8"));
|
|
1330
2088
|
} catch (error) {
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
2089
|
+
throw new Error(`skills manifest is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
2090
|
+
}
|
|
2091
|
+
const skills = parsed?.skills;
|
|
2092
|
+
if (!Array.isArray(skills)) {
|
|
2093
|
+
throw new Error(`skills manifest has no skills array: ${manifestPath}`);
|
|
1334
2094
|
}
|
|
2095
|
+
return skills.map((entry) => {
|
|
2096
|
+
const skill = entry;
|
|
2097
|
+
if (typeof skill.id !== "string" || typeof skill.path !== "string") {
|
|
2098
|
+
throw new Error(`skills manifest entry is missing id or path: ${manifestPath}`);
|
|
2099
|
+
}
|
|
2100
|
+
if (!existsSync3(join4(resourcesRoot, skill.path))) {
|
|
2101
|
+
throw new Error(`manifest skill file not found: ${join4(resourcesRoot, skill.path)}`);
|
|
2102
|
+
}
|
|
2103
|
+
return {
|
|
2104
|
+
id: skill.id,
|
|
2105
|
+
name: skill.name ?? skill.id,
|
|
2106
|
+
version: skill.version ?? "",
|
|
2107
|
+
path: skill.path,
|
|
2108
|
+
description: skill.description ?? ""
|
|
2109
|
+
};
|
|
2110
|
+
});
|
|
2111
|
+
}
|
|
2112
|
+
function installEnabledCliResources(options) {
|
|
2113
|
+
const manifest = loadSkillsManifest(options.resourcesRoot);
|
|
2114
|
+
const entry = buildServerRegistration(options.apiKey);
|
|
2115
|
+
const result = { summaries: [], errors: [] };
|
|
2116
|
+
for (const cli of loadCliSettings(options.home).enabled) {
|
|
2117
|
+
const target = CLI_TARGETS[cli];
|
|
2118
|
+
if (target === void 0) {
|
|
2119
|
+
result.errors.push(`Unsupported CLI: ${cli}`);
|
|
2120
|
+
continue;
|
|
2121
|
+
}
|
|
2122
|
+
try {
|
|
2123
|
+
applyServerRegistration(target, MCP_SERVER_NAME, entry, options.home);
|
|
2124
|
+
let skills = 0;
|
|
2125
|
+
for (const skill of manifest) {
|
|
2126
|
+
syncSkillTree(join4(options.resourcesRoot, skill.id), join4(target.skillsDir(options.home), skill.id));
|
|
2127
|
+
skills += 1;
|
|
2128
|
+
}
|
|
2129
|
+
result.summaries.push({ cli: target.id, server: 1, skills });
|
|
2130
|
+
} catch (error) {
|
|
2131
|
+
result.errors.push(`${target.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
return result;
|
|
2135
|
+
}
|
|
2136
|
+
function uninstallEnabledCliResources(options) {
|
|
2137
|
+
const manifest = loadSkillsManifest(options.resourcesRoot);
|
|
2138
|
+
const result = { summaries: [], errors: [] };
|
|
2139
|
+
for (const cli of loadCliSettings(options.home).enabled) {
|
|
2140
|
+
const target = CLI_TARGETS[cli];
|
|
2141
|
+
if (target === void 0) {
|
|
2142
|
+
result.errors.push(`Unsupported CLI: ${cli}`);
|
|
2143
|
+
continue;
|
|
2144
|
+
}
|
|
2145
|
+
try {
|
|
2146
|
+
applyServerRegistration(target, MCP_SERVER_NAME, void 0, options.home);
|
|
2147
|
+
let skills = 0;
|
|
2148
|
+
for (const skill of manifest) {
|
|
2149
|
+
removeSkillDir(join4(target.skillsDir(options.home), skill.id));
|
|
2150
|
+
skills += 1;
|
|
2151
|
+
}
|
|
2152
|
+
result.summaries.push({ cli: target.id, server: 0, skills });
|
|
2153
|
+
} catch (error) {
|
|
2154
|
+
result.errors.push(`${target.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
return result;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
// src/utils/cliShow.ts
|
|
2161
|
+
import { join as join5 } from "path";
|
|
2162
|
+
import { relative as relative2, resolve as resolve4 } from "path";
|
|
2163
|
+
var MAX_CELL_WIDTH = 48;
|
|
2164
|
+
var STATUS_OK = "OK";
|
|
2165
|
+
var STATUS_MISSING = "MISSING";
|
|
2166
|
+
function wrapCell(text, width) {
|
|
2167
|
+
const normalized = String(text).split(/\s+/).filter((word) => word.length > 0);
|
|
2168
|
+
if (normalized.length === 0) return [""];
|
|
2169
|
+
const lines = [];
|
|
2170
|
+
let current = "";
|
|
2171
|
+
for (let word of normalized) {
|
|
2172
|
+
while (word.length > width) {
|
|
2173
|
+
if (current.length > 0) {
|
|
2174
|
+
lines.push(current);
|
|
2175
|
+
current = "";
|
|
2176
|
+
}
|
|
2177
|
+
lines.push(word.slice(0, width));
|
|
2178
|
+
word = word.slice(width);
|
|
2179
|
+
}
|
|
2180
|
+
if (current.length === 0) {
|
|
2181
|
+
current = word;
|
|
2182
|
+
} else if (current.length + 1 + word.length <= width) {
|
|
2183
|
+
current = `${current} ${word}`;
|
|
2184
|
+
} else {
|
|
2185
|
+
lines.push(current);
|
|
2186
|
+
current = word;
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
if (current.length > 0) lines.push(current);
|
|
2190
|
+
return lines.length > 0 ? lines : [""];
|
|
2191
|
+
}
|
|
2192
|
+
function renderAsciiTable(headers, rows, maxWidth = MAX_CELL_WIDTH) {
|
|
2193
|
+
const widths = headers.map((header, index) => {
|
|
2194
|
+
const lengths = [header.length, ...rows.map((row) => (row[index] ?? "").length)];
|
|
2195
|
+
return Math.max(1, Math.min(Math.max(...lengths), Math.max(1, maxWidth)));
|
|
2196
|
+
});
|
|
2197
|
+
const border = "+" + widths.map((width) => "-".repeat(width + 2)).join("+") + "+";
|
|
2198
|
+
const renderRow = (cells) => {
|
|
2199
|
+
const wrapped = cells.map((cell, index) => wrapCell(cell ?? "", widths[index]));
|
|
2200
|
+
const height = Math.max(...wrapped.map((part) => part.length));
|
|
2201
|
+
const lines = [];
|
|
2202
|
+
for (let line = 0; line < height; line += 1) {
|
|
2203
|
+
const parts = wrapped.map((part, index) => (part[line] ?? "").padEnd(widths[index]));
|
|
2204
|
+
lines.push("| " + parts.join(" | ") + " |");
|
|
2205
|
+
}
|
|
2206
|
+
return lines;
|
|
2207
|
+
};
|
|
2208
|
+
const output = [border, ...renderRow(headers), border];
|
|
2209
|
+
for (const row of rows) {
|
|
2210
|
+
output.push(...renderRow(row));
|
|
2211
|
+
output.push(border);
|
|
2212
|
+
}
|
|
2213
|
+
return output.join("\n");
|
|
2214
|
+
}
|
|
2215
|
+
function buildCliRows(home) {
|
|
2216
|
+
const root = home ?? (process.env.HOME ?? process.cwd());
|
|
2217
|
+
const enabled = new Set(loadCliSettings(home).enabled);
|
|
2218
|
+
return Object.keys(CLI_TARGETS).map((id) => {
|
|
2219
|
+
const target = CLI_TARGETS[id];
|
|
2220
|
+
return [
|
|
2221
|
+
target.id,
|
|
2222
|
+
enabled.has(id) ? "yes" : "no",
|
|
2223
|
+
relative2(resolve4(root), resolve4(target.mcpConfigPath(home))).split("\\").join("/"),
|
|
2224
|
+
relative2(resolve4(root), resolve4(target.skillsDir(home))).split("\\").join("/")
|
|
2225
|
+
];
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
function buildInstallRows(home, resourcesRoot) {
|
|
2229
|
+
const enabled = loadCliSettings(home).enabled;
|
|
2230
|
+
const manifest = loadSkillsManifest(resourcesRoot);
|
|
2231
|
+
const rows = [];
|
|
2232
|
+
for (const cli of enabled) {
|
|
2233
|
+
const target = CLI_TARGETS[cli];
|
|
2234
|
+
if (target === void 0) continue;
|
|
2235
|
+
rows.push([
|
|
2236
|
+
"Server",
|
|
2237
|
+
MCP_SERVER_NAME,
|
|
2238
|
+
target.id,
|
|
2239
|
+
"yes",
|
|
2240
|
+
isServerRegistered(target, MCP_SERVER_NAME, home) ? STATUS_OK : STATUS_MISSING
|
|
2241
|
+
]);
|
|
2242
|
+
for (const skill of manifest) {
|
|
2243
|
+
rows.push([
|
|
2244
|
+
"Skill",
|
|
2245
|
+
skill.id,
|
|
2246
|
+
target.id,
|
|
2247
|
+
"yes",
|
|
2248
|
+
compareSkillTree(join5(resourcesRoot, skill.id), join5(target.skillsDir(home), skill.id))
|
|
2249
|
+
]);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
return rows;
|
|
2253
|
+
}
|
|
2254
|
+
function buildCliShowReport(options) {
|
|
2255
|
+
return [
|
|
2256
|
+
"CLI Configuration",
|
|
2257
|
+
renderAsciiTable(["CLI", "Enabled", "MCP Config Path", "Skills Config Path"], buildCliRows(options.home)),
|
|
2258
|
+
"",
|
|
2259
|
+
"Installation Status",
|
|
2260
|
+
renderAsciiTable(["Type", "Name", "CLI", "Enabled", "Installed"], buildInstallRows(options.home, options.resourcesRoot))
|
|
2261
|
+
].join("\n");
|
|
1335
2262
|
}
|
|
1336
2263
|
|
|
1337
2264
|
// src/cli.ts
|
|
1338
2265
|
var __filename = fileURLToPath(import.meta.url);
|
|
1339
|
-
var __dirname =
|
|
1340
|
-
|
|
1341
|
-
|
|
2266
|
+
var __dirname = dirname2(__filename);
|
|
2267
|
+
function extractEnvFileOption(argv) {
|
|
2268
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2269
|
+
const arg = argv[i];
|
|
2270
|
+
if (arg === "--env-file" && i + 1 < argv.length) {
|
|
2271
|
+
return argv[i + 1];
|
|
2272
|
+
}
|
|
2273
|
+
if (arg.startsWith("--env-file=")) {
|
|
2274
|
+
return arg.slice("--env-file=".length);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
return void 0;
|
|
2278
|
+
}
|
|
2279
|
+
function loadEnvironment(envFile) {
|
|
2280
|
+
if (envFile !== void 0) {
|
|
2281
|
+
const result = applyExplicitEnvFile(envFile);
|
|
2282
|
+
if (!result.ok) {
|
|
2283
|
+
process.stderr.write(JSON.stringify({ error: result.error }, null, 2) + "\n");
|
|
2284
|
+
process.exit(1);
|
|
2285
|
+
}
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
dotenvConfig2({ path: resolve5(process.cwd(), ".env") });
|
|
2289
|
+
dotenvConfig2({ path: resolve5(__dirname, "../.env") });
|
|
2290
|
+
}
|
|
1342
2291
|
async function startServer(port, apiKey, host) {
|
|
1343
2292
|
if (port) {
|
|
1344
2293
|
process.env.MCP_SERVER_PORT = port.toString();
|
|
@@ -1355,36 +2304,69 @@ async function startServer(port, apiKey, host) {
|
|
|
1355
2304
|
"\u2139\uFE0F Reminder: enable Places API (New) in https://console.cloud.google.com before using the new Place features."
|
|
1356
2305
|
);
|
|
1357
2306
|
Logger.log("");
|
|
1358
|
-
const
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
2307
|
+
const servers = [];
|
|
2308
|
+
let started = 0;
|
|
2309
|
+
let failed = 0;
|
|
2310
|
+
await Promise.all(
|
|
2311
|
+
config_default.map(async (config) => {
|
|
2312
|
+
const portString = process.env[config.portEnvVar];
|
|
2313
|
+
if (!portString) {
|
|
2314
|
+
Logger.error(`\u26A0\uFE0F [${config.name}] Port environment variable ${config.portEnvVar} not set.`);
|
|
2315
|
+
Logger.log(`\u{1F4A1} Please set ${config.portEnvVar} in your .env file or use --port parameter.`);
|
|
2316
|
+
Logger.log(` Example: ${config.portEnvVar}=3000 or --port 3000`);
|
|
2317
|
+
failed++;
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
const serverPort = Number(portString);
|
|
2321
|
+
if (!Number.isInteger(serverPort) || serverPort < 1 || serverPort > 65535) {
|
|
2322
|
+
Logger.error(
|
|
2323
|
+
`\u274C [${config.name}] Invalid port number "${portString}" defined in ${config.portEnvVar} (expected integer 1-65535).`
|
|
2324
|
+
);
|
|
2325
|
+
failed++;
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
try {
|
|
2329
|
+
const activeTools = filterTools(config.tools);
|
|
2330
|
+
const server = new BaseMcpServer(config.name, activeTools);
|
|
2331
|
+
const serverHost = process.env.MCP_SERVER_HOST || "127.0.0.1";
|
|
2332
|
+
Logger.log(`\u{1F527} [${config.name}] Initializing MCP Server in HTTP mode on ${serverHost}:${serverPort}...`);
|
|
2333
|
+
await server.startHttpServer(serverPort, serverHost);
|
|
2334
|
+
servers.push(server);
|
|
2335
|
+
started++;
|
|
2336
|
+
const displayHost = serverHost === "0.0.0.0" ? "localhost" : serverHost;
|
|
2337
|
+
Logger.log(`\u2705 [${config.name}] MCP Server started successfully!`);
|
|
2338
|
+
Logger.log(` \u{1F310} Endpoint: http://${displayHost}:${serverPort}/mcp`);
|
|
2339
|
+
Logger.log(` \u{1F4DA} Tools: ${activeTools.length} available`);
|
|
2340
|
+
} catch (error) {
|
|
2341
|
+
Logger.error(`\u274C [${config.name}] Failed to start MCP Server on port ${serverPort}:`, error);
|
|
2342
|
+
failed++;
|
|
2343
|
+
}
|
|
2344
|
+
})
|
|
2345
|
+
);
|
|
2346
|
+
if (started > 0) {
|
|
2347
|
+
installShutdownHandlers(servers);
|
|
2348
|
+
}
|
|
1385
2349
|
Logger.log("");
|
|
1386
|
-
Logger.log("\u{1F389} Server initialization completed!");
|
|
2350
|
+
Logger.log(started > 0 ? "\u{1F389} Server initialization completed!" : "\u274C No MCP server instance started.");
|
|
1387
2351
|
Logger.log("\u{1F4A1} Need help? Check the README.md for configuration details.");
|
|
2352
|
+
return { started, failed, servers };
|
|
2353
|
+
}
|
|
2354
|
+
function createShutdownHandler(servers, exitHook = (code) => process.exit(code)) {
|
|
2355
|
+
let invoked = false;
|
|
2356
|
+
return async (signal) => {
|
|
2357
|
+
if (invoked) {
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
2360
|
+
invoked = true;
|
|
2361
|
+
Logger.log(`\u{1F4F6} Received ${signal}; shutting down MCP HTTP server...`);
|
|
2362
|
+
await Promise.allSettled(servers.map((server) => server.stopHttpServer()));
|
|
2363
|
+
exitHook(0);
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
function installShutdownHandlers(servers) {
|
|
2367
|
+
const handler = createShutdownHandler(servers);
|
|
2368
|
+
process.on("SIGINT", () => void handler("SIGINT"));
|
|
2369
|
+
process.on("SIGTERM", () => void handler("SIGTERM"));
|
|
1388
2370
|
}
|
|
1389
2371
|
var EXEC_TOOLS = [
|
|
1390
2372
|
"geocode",
|
|
@@ -1550,13 +2532,58 @@ async function runExecCommand(toolName, params, apiKey, runner = execTool, strea
|
|
|
1550
2532
|
return 1;
|
|
1551
2533
|
}
|
|
1552
2534
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
2535
|
+
var DOCTOR_PROBES = [
|
|
2536
|
+
{ id: "geocoding", name: "geocoding-api", tool: "geocode", params: { address: "Tokyo Tower" } },
|
|
2537
|
+
{ id: "places-new", name: "places-api-new", tool: "search-places", params: { query: "Tokyo Tower" } },
|
|
2538
|
+
{
|
|
2539
|
+
id: "routes",
|
|
2540
|
+
name: "routes-api",
|
|
2541
|
+
tool: "directions",
|
|
2542
|
+
params: { origin: "Tokyo Tower", destination: "Tokyo Station", mode: "walking" }
|
|
2543
|
+
}
|
|
2544
|
+
];
|
|
2545
|
+
var DOCTOR_PROBE_ALIASES = {
|
|
2546
|
+
geocoding: "geocoding",
|
|
2547
|
+
"geocoding-api": "geocoding",
|
|
2548
|
+
"places-new": "places-new",
|
|
2549
|
+
places: "places-new",
|
|
2550
|
+
"places-api-new": "places-new",
|
|
2551
|
+
routes: "routes",
|
|
2552
|
+
"routes-api": "routes"
|
|
2553
|
+
};
|
|
2554
|
+
function parseDoctorProbes(raw) {
|
|
2555
|
+
if (raw === void 0) {
|
|
2556
|
+
return { probes: void 0 };
|
|
2557
|
+
}
|
|
2558
|
+
const items = raw.split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
|
|
2559
|
+
if (items.length === 0) {
|
|
2560
|
+
return { probes: void 0, error: "--probes received no probe names (expected: geocoding, places-new, routes)" };
|
|
2561
|
+
}
|
|
2562
|
+
const probes = [];
|
|
2563
|
+
for (const item of items) {
|
|
2564
|
+
const id = DOCTOR_PROBE_ALIASES[item];
|
|
2565
|
+
if (!id) {
|
|
2566
|
+
return { probes: [], error: `unknown probe "${item}" (expected: geocoding, places-new, routes)` };
|
|
2567
|
+
}
|
|
2568
|
+
if (!probes.includes(id)) {
|
|
2569
|
+
probes.push(id);
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
return { probes };
|
|
2573
|
+
}
|
|
2574
|
+
async function runDoctor(packageVersion, apiKey, live = false, runner = execTool, options = {}) {
|
|
2575
|
+
const nodeVersion = options.nodeVersion ?? process.versions.node;
|
|
2576
|
+
const nodeMajor = Number.parseInt(nodeVersion.split(".")[0] ?? "0", 10);
|
|
2577
|
+
const config = options.config ?? resolveRuntimeConfig(process.env);
|
|
2578
|
+
const allToolNames = options.allToolNames ?? config_default[0].tools.map((tool) => tool.name);
|
|
2579
|
+
const toolsResolution = resolveEnabledToolNames(allToolNames, config.enabledToolsRaw);
|
|
2580
|
+
const diagnostics = describeRuntimeConfig(config);
|
|
2581
|
+
const toolsProfile = describeToolsProfile(toolsResolution.names);
|
|
1555
2582
|
const checks = [
|
|
1556
2583
|
{
|
|
1557
2584
|
name: "node",
|
|
1558
|
-
status: nodeMajor >=
|
|
1559
|
-
detail: `Node.js ${
|
|
2585
|
+
status: nodeMajor >= NODE_ENGINE_FLOOR ? "pass" : "fail",
|
|
2586
|
+
detail: `Node.js ${nodeVersion} detected; version ${NODE_ENGINE_FLOOR} or newer is required (package.json engines).`
|
|
1560
2587
|
},
|
|
1561
2588
|
{
|
|
1562
2589
|
name: "package",
|
|
@@ -1567,6 +2594,21 @@ async function runDoctor(packageVersion, apiKey, live = false, runner = execTool
|
|
|
1567
2594
|
name: "api-key",
|
|
1568
2595
|
status: apiKey ? "pass" : "fail",
|
|
1569
2596
|
detail: apiKey ? "GOOGLE_MAPS_API_KEY is configured (value hidden)." : "GOOGLE_MAPS_API_KEY is not configured. Set it in the environment before making API calls."
|
|
2597
|
+
},
|
|
2598
|
+
{
|
|
2599
|
+
name: "config",
|
|
2600
|
+
status: toolsResolution.fallback ? "fail" : "pass",
|
|
2601
|
+
detail: toolsResolution.fallback ? `GOOGLE_MAPS_ENABLED_TOOLS matched 0 tools; the server falls back to all ${allToolNames.length} tools. Effective configuration: ${formatConfigDiagnostics(diagnostics)}` : `Effective configuration: ${formatConfigDiagnostics(diagnostics)}`
|
|
2602
|
+
},
|
|
2603
|
+
{
|
|
2604
|
+
name: "env-file",
|
|
2605
|
+
status: "pass",
|
|
2606
|
+
detail: config.envFile ? `Explicit environment file loaded: ${config.envFile}` : "No explicit --env-file requested; implicit .env discovery (current directory, then package directory) applies."
|
|
2607
|
+
},
|
|
2608
|
+
{
|
|
2609
|
+
name: "tools",
|
|
2610
|
+
status: "pass",
|
|
2611
|
+
detail: `${toolsProfile.toolCount} tool(s) enabled; Google APIs: ${toolsProfile.apis.join(", ") || "none"}; per-tool: ${toolsProfile.tools.map((tool) => `${tool.tool}[${tool.apis.join("+")}]`).join(", ")}`
|
|
1570
2612
|
}
|
|
1571
2613
|
];
|
|
1572
2614
|
if (!live) {
|
|
@@ -1582,16 +2624,8 @@ async function runDoctor(packageVersion, apiKey, live = false, runner = execTool
|
|
|
1582
2624
|
detail: "Live API checks require GOOGLE_MAPS_API_KEY."
|
|
1583
2625
|
});
|
|
1584
2626
|
} else {
|
|
1585
|
-
const
|
|
1586
|
-
|
|
1587
|
-
{ name: "places-api-new", tool: "search-places", params: { query: "Tokyo Tower" } },
|
|
1588
|
-
{
|
|
1589
|
-
name: "routes-api",
|
|
1590
|
-
tool: "directions",
|
|
1591
|
-
params: { origin: "Tokyo Tower", destination: "Tokyo Station", mode: "walking" }
|
|
1592
|
-
}
|
|
1593
|
-
];
|
|
1594
|
-
for (const check of liveChecks) {
|
|
2627
|
+
const selected = options.probes ? DOCTOR_PROBES.filter((probe) => options.probes.includes(probe.id)) : DOCTOR_PROBES;
|
|
2628
|
+
for (const check of selected) {
|
|
1595
2629
|
try {
|
|
1596
2630
|
const result = await runner(check.tool, check.params, apiKey);
|
|
1597
2631
|
if (isExecFailureResponse(result)) {
|
|
@@ -1623,241 +2657,591 @@ async function runDoctor(packageVersion, apiKey, live = false, runner = execTool
|
|
|
1623
2657
|
checks
|
|
1624
2658
|
};
|
|
1625
2659
|
}
|
|
2660
|
+
async function runSetup(options, deps = {}) {
|
|
2661
|
+
const fsImpl = deps.fs ?? {
|
|
2662
|
+
existsSync: (path) => existsSync4(path),
|
|
2663
|
+
readFileSync: (path, encoding) => readFileSync5(path, encoding),
|
|
2664
|
+
writeFileSync: (path, data, encoding) => writeFileSync4(path, data, encoding)
|
|
2665
|
+
};
|
|
2666
|
+
const nodeVersion = deps.nodeVersion ?? process.versions.node;
|
|
2667
|
+
const nodeMajor = Number.parseInt(nodeVersion.split(".")[0] ?? "0", 10);
|
|
2668
|
+
const config = deps.config ?? resolveRuntimeConfig(process.env);
|
|
2669
|
+
const examplePath = resolve5(options.packageDir, ".env.example");
|
|
2670
|
+
const envPath = resolve5(options.targetDir, ".env");
|
|
2671
|
+
const checks = [
|
|
2672
|
+
{
|
|
2673
|
+
name: "node",
|
|
2674
|
+
status: nodeMajor >= NODE_ENGINE_FLOOR ? "pass" : "fail",
|
|
2675
|
+
detail: `Node.js ${nodeVersion} detected; version ${NODE_ENGINE_FLOOR} or newer is required (package.json engines).`
|
|
2676
|
+
},
|
|
2677
|
+
{
|
|
2678
|
+
name: "env-template",
|
|
2679
|
+
status: fsImpl.existsSync(examplePath) ? "pass" : "fail",
|
|
2680
|
+
detail: fsImpl.existsSync(examplePath) ? `Template found: ${examplePath}` : `Template missing: ${examplePath} (expected next to the installed package).`
|
|
2681
|
+
}
|
|
2682
|
+
];
|
|
2683
|
+
let created = false;
|
|
2684
|
+
if (options.writeEnv) {
|
|
2685
|
+
if (fsImpl.existsSync(envPath)) {
|
|
2686
|
+
checks.push({
|
|
2687
|
+
name: "env-file",
|
|
2688
|
+
status: "pass",
|
|
2689
|
+
detail: `${envPath} already exists; left untouched (no overwrite).`
|
|
2690
|
+
});
|
|
2691
|
+
} else if (!fsImpl.existsSync(examplePath)) {
|
|
2692
|
+
checks.push({
|
|
2693
|
+
name: "env-file",
|
|
2694
|
+
status: "fail",
|
|
2695
|
+
detail: `Cannot create ${envPath}: template ${examplePath} is missing.`
|
|
2696
|
+
});
|
|
2697
|
+
} else {
|
|
2698
|
+
try {
|
|
2699
|
+
fsImpl.writeFileSync(envPath, fsImpl.readFileSync(examplePath, "utf-8"), "utf-8");
|
|
2700
|
+
created = true;
|
|
2701
|
+
checks.push({
|
|
2702
|
+
name: "env-file",
|
|
2703
|
+
status: "pass",
|
|
2704
|
+
detail: `Created ${envPath} from template; edit it and set GOOGLE_MAPS_API_KEY.`
|
|
2705
|
+
});
|
|
2706
|
+
} catch (error) {
|
|
2707
|
+
checks.push({
|
|
2708
|
+
name: "env-file",
|
|
2709
|
+
status: "fail",
|
|
2710
|
+
detail: `Failed to create ${envPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
} else {
|
|
2715
|
+
checks.push({
|
|
2716
|
+
name: "env-file",
|
|
2717
|
+
status: fsImpl.existsSync(envPath) ? "pass" : "skip",
|
|
2718
|
+
detail: fsImpl.existsSync(envPath) ? `${envPath} found.` : `${envPath} not found; rerun with --write-env to create it from the template.`
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
2721
|
+
checks.push({
|
|
2722
|
+
name: "api-key",
|
|
2723
|
+
status: config.apiKey ? "pass" : "fail",
|
|
2724
|
+
detail: config.apiKey ? `GOOGLE_MAPS_API_KEY is configured (source: ${config.apiKeySource}, value hidden).` : "GOOGLE_MAPS_API_KEY is not configured. Set it in the environment or in .env after scaffolding."
|
|
2725
|
+
});
|
|
2726
|
+
return {
|
|
2727
|
+
success: checks.every((check) => check.status !== "fail"),
|
|
2728
|
+
created,
|
|
2729
|
+
checks
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
function runSnippetCommand(options, streams = { stdout: process.stdout, stderr: process.stderr }) {
|
|
2733
|
+
try {
|
|
2734
|
+
const snippet = renderClientSnippet(options);
|
|
2735
|
+
streams.stdout.write(snippet.json + "\n");
|
|
2736
|
+
for (const note of snippet.notes) {
|
|
2737
|
+
streams.stderr.write(`[snippet] ${note}
|
|
2738
|
+
`);
|
|
2739
|
+
}
|
|
2740
|
+
return 0;
|
|
2741
|
+
} catch (error) {
|
|
2742
|
+
streams.stderr.write(
|
|
2743
|
+
JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2) + "\n"
|
|
2744
|
+
);
|
|
2745
|
+
return 1;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
function runEnableDisableCliCommand(name, enable, options = {}) {
|
|
2749
|
+
const streams = options.streams ?? { stdout: process.stdout, stderr: process.stderr };
|
|
2750
|
+
const target = resolveCliTarget(name);
|
|
2751
|
+
if (target === void 0) {
|
|
2752
|
+
streams.stderr.write(
|
|
2753
|
+
JSON.stringify({ error: `Unsupported CLI: ${name}. Supported: ${supportedCliNames()}.` }, null, 2) + "\n"
|
|
2754
|
+
);
|
|
2755
|
+
return 2;
|
|
2756
|
+
}
|
|
2757
|
+
const settings = loadCliSettings(options.home);
|
|
2758
|
+
const already = settings.enabled.includes(target.id);
|
|
2759
|
+
if (enable === already) {
|
|
2760
|
+
streams.stdout.write(
|
|
2761
|
+
JSON.stringify({ cli: target.id, enabled: enable, message: `${target.id} is already ${enable ? "enabled" : "disabled"}.` }, null, 2) + "\n"
|
|
2762
|
+
);
|
|
2763
|
+
return 0;
|
|
2764
|
+
}
|
|
2765
|
+
settings.enabled = enable ? [...settings.enabled, target.id] : settings.enabled.filter((item) => item !== target.id);
|
|
2766
|
+
saveCliSettings(settings, options.home);
|
|
2767
|
+
streams.stdout.write(
|
|
2768
|
+
JSON.stringify({ cli: target.id, enabled: enable, message: `${enable ? "Enabled" : "Disabled"} ${target.id}.` }, null, 2) + "\n"
|
|
2769
|
+
);
|
|
2770
|
+
return 0;
|
|
2771
|
+
}
|
|
2772
|
+
function runInstallUninstallCommand(operation, options) {
|
|
2773
|
+
const streams = options.streams ?? { stdout: process.stdout, stderr: process.stderr };
|
|
2774
|
+
if (loadCliSettings(options.home).enabled.length === 0) {
|
|
2775
|
+
streams.stdout.write(
|
|
2776
|
+
JSON.stringify(
|
|
2777
|
+
{
|
|
2778
|
+
success: true,
|
|
2779
|
+
message: "No CLI enabled. Use 'mcp-google-map enable <cli name>' first.",
|
|
2780
|
+
summaries: [],
|
|
2781
|
+
errors: []
|
|
2782
|
+
},
|
|
2783
|
+
null,
|
|
2784
|
+
2
|
|
2785
|
+
) + "\n"
|
|
2786
|
+
);
|
|
2787
|
+
return 0;
|
|
2788
|
+
}
|
|
2789
|
+
let result;
|
|
2790
|
+
try {
|
|
2791
|
+
result = operation === "install" ? installEnabledCliResources({ home: options.home, apiKey: options.apiKey, resourcesRoot: options.resourcesRoot }) : uninstallEnabledCliResources({ home: options.home, resourcesRoot: options.resourcesRoot });
|
|
2792
|
+
} catch (error) {
|
|
2793
|
+
streams.stderr.write(
|
|
2794
|
+
JSON.stringify({ success: false, errors: [error instanceof Error ? error.message : String(error)] }, null, 2) + "\n"
|
|
2795
|
+
);
|
|
2796
|
+
return 1;
|
|
2797
|
+
}
|
|
2798
|
+
streams.stdout.write(
|
|
2799
|
+
JSON.stringify({ success: result.errors.length === 0, operation, summaries: result.summaries, errors: result.errors }, null, 2) + "\n"
|
|
2800
|
+
);
|
|
2801
|
+
return result.errors.length > 0 ? 1 : 0;
|
|
2802
|
+
}
|
|
2803
|
+
function runShowCommand(options) {
|
|
2804
|
+
const streams = options.streams ?? { stdout: process.stdout, stderr: process.stderr };
|
|
2805
|
+
try {
|
|
2806
|
+
streams.stdout.write(buildCliShowReport(options) + "\n");
|
|
2807
|
+
return 0;
|
|
2808
|
+
} catch (error) {
|
|
2809
|
+
streams.stderr.write(
|
|
2810
|
+
JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2) + "\n"
|
|
2811
|
+
);
|
|
2812
|
+
return 1;
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
1626
2815
|
var isRunDirectly = process.argv[1] && (process.argv[1].endsWith("cli.ts") || process.argv[1].endsWith("cli.js") || process.argv[1].endsWith("mcp-google-map") || process.argv[1].includes("mcp-google-map"));
|
|
1627
2816
|
var isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
1628
2817
|
if (isRunDirectly || isMainModule) {
|
|
2818
|
+
loadEnvironment(extractEnvFileOption(process.argv));
|
|
1629
2819
|
let packageVersion = "0.0.0";
|
|
1630
2820
|
try {
|
|
1631
|
-
const packageJsonPath =
|
|
1632
|
-
const packageJson = JSON.parse(
|
|
2821
|
+
const packageJsonPath = resolve5(__dirname, "../package.json");
|
|
2822
|
+
const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
|
|
1633
2823
|
packageVersion = packageJson.version;
|
|
1634
2824
|
} catch (e) {
|
|
1635
2825
|
packageVersion = "0.0.0";
|
|
1636
2826
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
1656
|
-
process.exitCode = report.success ? 0 : 1;
|
|
1657
|
-
}
|
|
1658
|
-
).command(
|
|
1659
|
-
"exec <tool> [params]",
|
|
1660
|
-
"Execute a tool directly and output JSON",
|
|
1661
|
-
(yargs2) => {
|
|
1662
|
-
return yargs2.positional("tool", {
|
|
1663
|
-
type: "string",
|
|
1664
|
-
describe: `Tool name: ${EXEC_TOOLS.join(", ")}`
|
|
1665
|
-
}).positional("params", {
|
|
2827
|
+
const managementCommand = parseManagementCommand(hideBin(process.argv));
|
|
2828
|
+
if (managementCommand !== void 0) {
|
|
2829
|
+
void runManagementCommand(managementCommand, { currentVersion: packageVersion }).then((code) => {
|
|
2830
|
+
process.exitCode = code;
|
|
2831
|
+
}).catch((error) => {
|
|
2832
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2833
|
+
process.stderr.write(JSON.stringify({ error: message }, null, 2) + "\n");
|
|
2834
|
+
process.exitCode = 1;
|
|
2835
|
+
});
|
|
2836
|
+
} else {
|
|
2837
|
+
yargs(hideBin(process.argv)).command(
|
|
2838
|
+
"doctor",
|
|
2839
|
+
"Check standalone CLI readiness without starting an MCP server",
|
|
2840
|
+
(yargs2) => yargs2.option("live", {
|
|
2841
|
+
type: "boolean",
|
|
2842
|
+
default: false,
|
|
2843
|
+
description: "Make billable test requests to Geocoding, Places (New), and Routes APIs"
|
|
2844
|
+
}).option("probes", {
|
|
1666
2845
|
type: "string",
|
|
1667
|
-
|
|
2846
|
+
description: "Comma-separated subset of live probes: geocoding, places-new, routes (default: all three)"
|
|
1668
2847
|
}).option("apikey", {
|
|
1669
2848
|
alias: "k",
|
|
1670
2849
|
type: "string",
|
|
1671
|
-
description: "Google Maps API key",
|
|
2850
|
+
description: "Google Maps API key (prefer GOOGLE_MAPS_API_KEY to avoid shell history exposure)",
|
|
1672
2851
|
default: process.env.GOOGLE_MAPS_API_KEY
|
|
1673
2852
|
}).example([
|
|
1674
|
-
[
|
|
1675
|
-
[
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
) + "\n"
|
|
1692
|
-
);
|
|
1693
|
-
process.exitCode = 1;
|
|
1694
|
-
return;
|
|
2853
|
+
["$0 doctor", "Check local runtime and API key configuration without network calls"],
|
|
2854
|
+
["$0 doctor --live", "Also verify three Google Maps APIs with billable requests"],
|
|
2855
|
+
["$0 doctor --live --probes geocoding,routes", "Probe only the selected APIs"]
|
|
2856
|
+
]),
|
|
2857
|
+
async (argv) => {
|
|
2858
|
+
const parsedProbes = parseDoctorProbes(argv.probes);
|
|
2859
|
+
if (parsedProbes.error) {
|
|
2860
|
+
process.stderr.write(JSON.stringify({ error: parsedProbes.error }, null, 2) + "\n");
|
|
2861
|
+
process.exitCode = 1;
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
const report = await runDoctor(packageVersion, argv.apikey, argv.live, void 0, {
|
|
2865
|
+
probes: parsedProbes.probes,
|
|
2866
|
+
config: resolveRuntimeConfig(process.env, { envFile: extractEnvFileOption(process.argv) })
|
|
2867
|
+
});
|
|
2868
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
2869
|
+
process.exitCode = report.success ? 0 : 1;
|
|
1695
2870
|
}
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
2871
|
+
).command(
|
|
2872
|
+
"setup",
|
|
2873
|
+
"Non-interactive onboarding: verify local readiness, optionally scaffold .env",
|
|
2874
|
+
(yargs2) => yargs2.option("write-env", {
|
|
2875
|
+
type: "boolean",
|
|
2876
|
+
default: false,
|
|
2877
|
+
description: "Create .env from .env.example in --env-dir when it does not exist (never overwrites)"
|
|
2878
|
+
}).option("env-dir", {
|
|
2879
|
+
type: "string",
|
|
2880
|
+
default: process.cwd(),
|
|
2881
|
+
description: "Directory where .env is looked up / created"
|
|
2882
|
+
}).example([
|
|
2883
|
+
["$0 setup", "Check readiness without writing anything"],
|
|
2884
|
+
["$0 setup --write-env", "Also scaffold .env from the package template"]
|
|
2885
|
+
]),
|
|
2886
|
+
async (argv) => {
|
|
2887
|
+
const report = await runSetup({
|
|
2888
|
+
packageDir: resolve5(__dirname, ".."),
|
|
2889
|
+
targetDir: argv.envDir,
|
|
2890
|
+
writeEnv: argv.writeEnv
|
|
2891
|
+
});
|
|
2892
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
2893
|
+
process.exitCode = report.success ? 0 : 1;
|
|
1702
2894
|
}
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
2895
|
+
).command(
|
|
2896
|
+
"snippet",
|
|
2897
|
+
"Print an MCP client registration snippet (JSON on stdout; never writes files)",
|
|
2898
|
+
(yargs2) => yargs2.option("client", {
|
|
2899
|
+
type: "string",
|
|
2900
|
+
choices: ["generic", "claude", "cursor", "pi"],
|
|
2901
|
+
default: "generic",
|
|
2902
|
+
description: "Target client (selects the documented target file)"
|
|
2903
|
+
}).option("transport", {
|
|
1710
2904
|
type: "string",
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
2905
|
+
choices: ["stdio", "http"],
|
|
2906
|
+
default: "stdio",
|
|
2907
|
+
description: "MCP transport of the snippet"
|
|
2908
|
+
}).option("launcher", {
|
|
1715
2909
|
type: "string",
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
2910
|
+
choices: ["npx", "node", "bin"],
|
|
2911
|
+
default: "npx",
|
|
2912
|
+
description: "stdio launcher command"
|
|
2913
|
+
}).option("command", {
|
|
2914
|
+
type: "string",
|
|
2915
|
+
description: "Absolute path to dist/cli.js (required for --launcher node)"
|
|
2916
|
+
}).option("host", {
|
|
2917
|
+
type: "string",
|
|
2918
|
+
default: "127.0.0.1",
|
|
2919
|
+
description: "Host used in the URL (transport http)"
|
|
2920
|
+
}).option("port", {
|
|
1719
2921
|
type: "number",
|
|
1720
|
-
|
|
1721
|
-
|
|
2922
|
+
default: 3e3,
|
|
2923
|
+
description: "Port used in the URL (transport http)"
|
|
2924
|
+
}).option("env-key", {
|
|
2925
|
+
type: "string",
|
|
2926
|
+
description: "Emit a ${NAME} variable reference instead of a literal placeholder (expansion is client-specific)"
|
|
2927
|
+
}).option("name", {
|
|
2928
|
+
type: "string",
|
|
2929
|
+
default: "mcp-google-map",
|
|
2930
|
+
description: "Server name inside mcpServers"
|
|
2931
|
+
}).example([
|
|
2932
|
+
["$0 snippet --client claude", "stdio snippet using the npx launcher"],
|
|
2933
|
+
["$0 snippet --client pi --launcher node --command /abs/path/dist/cli.js", "stdio snippet with an explicit artifact"],
|
|
2934
|
+
["$0 snippet --transport http --port 3100", "HTTP URL snippet"]
|
|
2935
|
+
]),
|
|
2936
|
+
(argv) => {
|
|
2937
|
+
process.exitCode = runSnippetCommand({
|
|
2938
|
+
client: argv.client,
|
|
2939
|
+
transport: argv.transport,
|
|
2940
|
+
launcher: argv.launcher,
|
|
2941
|
+
commandPath: argv.command,
|
|
2942
|
+
host: argv.host,
|
|
2943
|
+
port: argv.port,
|
|
2944
|
+
envKey: argv.envKey,
|
|
2945
|
+
serverName: argv.name
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
).command(
|
|
2949
|
+
"enable <cli>",
|
|
2950
|
+
"Enable MCP-server and skill installation for a supported CLI",
|
|
2951
|
+
(yargs2) => yargs2.positional("cli", {
|
|
2952
|
+
type: "string",
|
|
2953
|
+
describe: `CLI name: ${supportedCliNames()}`
|
|
2954
|
+
}).option("home", {
|
|
2955
|
+
type: "string",
|
|
2956
|
+
description: "Home-directory override for the settings file (default: user home)"
|
|
2957
|
+
}).example([["$0 enable pi", "Enable pi for install/uninstall/show"]]),
|
|
2958
|
+
(argv) => {
|
|
2959
|
+
process.exitCode = runEnableDisableCliCommand(argv.cli, true, {
|
|
2960
|
+
home: argv.home
|
|
2961
|
+
});
|
|
2962
|
+
}
|
|
2963
|
+
).command(
|
|
2964
|
+
"disable <cli>",
|
|
2965
|
+
"Disable MCP-server and skill installation for a supported CLI",
|
|
2966
|
+
(yargs2) => yargs2.positional("cli", {
|
|
2967
|
+
type: "string",
|
|
2968
|
+
describe: `CLI name: ${supportedCliNames()}`
|
|
2969
|
+
}).option("home", {
|
|
2970
|
+
type: "string",
|
|
2971
|
+
description: "Home-directory override for the settings file (default: user home)"
|
|
2972
|
+
}).example([["$0 disable claude", "Disable claude for install/uninstall/show"]]),
|
|
2973
|
+
(argv) => {
|
|
2974
|
+
process.exitCode = runEnableDisableCliCommand(argv.cli, false, {
|
|
2975
|
+
home: argv.home
|
|
2976
|
+
});
|
|
2977
|
+
}
|
|
2978
|
+
).command(
|
|
2979
|
+
"install",
|
|
2980
|
+
"Register the mcp-google-map stdio server and install packaged skills for every enabled CLI",
|
|
2981
|
+
(yargs2) => yargs2.option("home", {
|
|
2982
|
+
type: "string",
|
|
2983
|
+
description: "Home-directory override for CLI configuration paths (default: user home)"
|
|
1722
2984
|
}).option("apikey", {
|
|
1723
2985
|
alias: "k",
|
|
1724
2986
|
type: "string",
|
|
1725
|
-
description: "Google Maps API key",
|
|
1726
|
-
default: process.env.
|
|
1727
|
-
}).example([
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
if (!argv.apikey) {
|
|
1735
|
-
console.error("Error: GOOGLE_MAPS_API_KEY not set. Use --apikey or set env var.");
|
|
1736
|
-
process.exit(1);
|
|
2987
|
+
description: "Google Maps API key embedded in the registered entry (prefer GOOGLE_MAPS_API_KEY)",
|
|
2988
|
+
default: resolveRuntimeConfig(process.env, { envFile: extractEnvFileOption(process.argv) }).apiKey
|
|
2989
|
+
}).example([["$0 install", "Register the server and skills in every enabled CLI"]]),
|
|
2990
|
+
(argv) => {
|
|
2991
|
+
process.exitCode = runInstallUninstallCommand("install", {
|
|
2992
|
+
home: argv.home,
|
|
2993
|
+
apiKey: argv.apikey,
|
|
2994
|
+
resourcesRoot: resolveSkillsResourcesRoot(resolve5(__dirname, ".."))
|
|
2995
|
+
});
|
|
1737
2996
|
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
}
|
|
1751
|
-
lines = readFileSync(argv.input, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
2997
|
+
).command(
|
|
2998
|
+
"uninstall",
|
|
2999
|
+
"Remove the mcp-google-map server entry and managed skills from every enabled CLI",
|
|
3000
|
+
(yargs2) => yargs2.option("home", {
|
|
3001
|
+
type: "string",
|
|
3002
|
+
description: "Home-directory override for CLI configuration paths (default: user home)"
|
|
3003
|
+
}).example([["$0 uninstall", "Reciprocal removal of install for every enabled CLI"]]),
|
|
3004
|
+
(argv) => {
|
|
3005
|
+
process.exitCode = runInstallUninstallCommand("uninstall", {
|
|
3006
|
+
home: argv.home,
|
|
3007
|
+
resourcesRoot: resolveSkillsResourcesRoot(resolve5(__dirname, ".."))
|
|
3008
|
+
});
|
|
1752
3009
|
}
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
3010
|
+
).command(
|
|
3011
|
+
"show",
|
|
3012
|
+
"Show CLI configuration and installation status tables (human-readable)",
|
|
3013
|
+
(yargs2) => yargs2.option("home", {
|
|
3014
|
+
type: "string",
|
|
3015
|
+
description: "Home-directory override for CLI configuration paths (default: user home)"
|
|
3016
|
+
}).example([["$0 show", "Print CLI Configuration and Installation Status tables"]]),
|
|
3017
|
+
(argv) => {
|
|
3018
|
+
process.exitCode = runShowCommand({
|
|
3019
|
+
home: argv.home,
|
|
3020
|
+
resourcesRoot: resolveSkillsResourcesRoot(resolve5(__dirname, ".."))
|
|
3021
|
+
});
|
|
1756
3022
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
3023
|
+
).command(
|
|
3024
|
+
"exec <tool> [params]",
|
|
3025
|
+
"Execute a tool directly and output JSON",
|
|
3026
|
+
(yargs2) => {
|
|
3027
|
+
return yargs2.positional("tool", {
|
|
3028
|
+
type: "string",
|
|
3029
|
+
describe: `Tool name: ${EXEC_TOOLS.join(", ")}`
|
|
3030
|
+
}).positional("params", {
|
|
3031
|
+
type: "string",
|
|
3032
|
+
describe: "JSON parameters string"
|
|
3033
|
+
}).option("apikey", {
|
|
3034
|
+
alias: "k",
|
|
3035
|
+
type: "string",
|
|
3036
|
+
description: "Google Maps API key",
|
|
3037
|
+
default: process.env.GOOGLE_MAPS_API_KEY
|
|
3038
|
+
}).example([
|
|
3039
|
+
[`$0 exec geocode '{"address":"Tokyo Tower"}'`, "Geocode an address"],
|
|
3040
|
+
[
|
|
3041
|
+
`$0 exec search-nearby '{"center":{"value":"35.68,139.74","isCoordinates":true},"keyword":"restaurant"}'`,
|
|
3042
|
+
"Search nearby"
|
|
3043
|
+
],
|
|
3044
|
+
[`$0 exec search-places '{"query":"ramen in Tokyo"}'`, "Text search"]
|
|
3045
|
+
]);
|
|
3046
|
+
},
|
|
3047
|
+
async (argv) => {
|
|
3048
|
+
if (!argv.apikey) {
|
|
3049
|
+
process.stderr.write(
|
|
3050
|
+
JSON.stringify(
|
|
3051
|
+
{
|
|
3052
|
+
error: "GOOGLE_MAPS_API_KEY not set. Use --apikey or set GOOGLE_MAPS_API_KEY environment variable."
|
|
3053
|
+
},
|
|
3054
|
+
null,
|
|
3055
|
+
2
|
|
3056
|
+
) + "\n"
|
|
3057
|
+
);
|
|
3058
|
+
process.exitCode = 1;
|
|
3059
|
+
return;
|
|
1771
3060
|
}
|
|
1772
|
-
await Promise.all(executing);
|
|
1773
|
-
};
|
|
1774
|
-
const tasks = lines.map((address, index) => async () => {
|
|
1775
3061
|
try {
|
|
1776
|
-
const
|
|
1777
|
-
|
|
3062
|
+
const params = argv.params ? JSON.parse(argv.params) : {};
|
|
3063
|
+
process.exitCode = await runExecCommand(argv.tool, params, argv.apikey);
|
|
1778
3064
|
} catch (error) {
|
|
1779
|
-
|
|
3065
|
+
process.stderr.write(JSON.stringify({ error: error.message }, null, 2) + "\n");
|
|
3066
|
+
process.exitCode = 1;
|
|
1780
3067
|
}
|
|
1781
|
-
completed++;
|
|
1782
|
-
if (!argv.output) return;
|
|
1783
|
-
process.stderr.write(`\r ${completed}/${lines.length} geocoded`);
|
|
1784
|
-
});
|
|
1785
|
-
await semaphore(tasks, concurrency);
|
|
1786
|
-
if (argv.output) {
|
|
1787
|
-
process.stderr.write("\n");
|
|
1788
|
-
}
|
|
1789
|
-
const succeeded = results.filter((r) => r.success).length;
|
|
1790
|
-
const failed = results.filter((r) => !r.success).length;
|
|
1791
|
-
const summary = { total: lines.length, succeeded, failed, results };
|
|
1792
|
-
const json = JSON.stringify(summary, null, 2);
|
|
1793
|
-
if (argv.output) {
|
|
1794
|
-
writeFileSync(argv.output, json, "utf-8");
|
|
1795
|
-
console.error(`Done: ${succeeded}/${lines.length} succeeded. Output: ${argv.output}`);
|
|
1796
|
-
} else {
|
|
1797
|
-
console.log(json);
|
|
1798
3068
|
}
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
},
|
|
1830
|
-
async (argv) => {
|
|
1831
|
-
if (argv.apikey) {
|
|
1832
|
-
process.env.GOOGLE_MAPS_API_KEY = argv.apikey;
|
|
1833
|
-
}
|
|
1834
|
-
void checkForUpdates({ currentVersion: packageVersion }).catch(() => void 0);
|
|
1835
|
-
const tools = filterTools(config_default[0].tools);
|
|
1836
|
-
if (argv.stdio) {
|
|
1837
|
-
const server = new BaseMcpServer(config_default[0].name, tools);
|
|
1838
|
-
await server.startStdio();
|
|
1839
|
-
} else {
|
|
1840
|
-
Logger.log("\u{1F5FA}\uFE0F Google Maps MCP Server");
|
|
1841
|
-
Logger.log(" A Model Context Protocol server for Google Maps services");
|
|
1842
|
-
Logger.log("");
|
|
3069
|
+
).command(
|
|
3070
|
+
"batch-geocode",
|
|
3071
|
+
"Geocode multiple addresses from a file (one address per line)",
|
|
3072
|
+
(yargs2) => {
|
|
3073
|
+
return yargs2.option("input", {
|
|
3074
|
+
alias: "i",
|
|
3075
|
+
type: "string",
|
|
3076
|
+
describe: "Input file path (one address per line). Use - for stdin.",
|
|
3077
|
+
demandOption: true
|
|
3078
|
+
}).option("output", {
|
|
3079
|
+
alias: "o",
|
|
3080
|
+
type: "string",
|
|
3081
|
+
describe: "Output file path (JSON). Defaults to stdout."
|
|
3082
|
+
}).option("concurrency", {
|
|
3083
|
+
alias: "c",
|
|
3084
|
+
type: "number",
|
|
3085
|
+
describe: "Max parallel requests",
|
|
3086
|
+
default: 20
|
|
3087
|
+
}).option("apikey", {
|
|
3088
|
+
alias: "k",
|
|
3089
|
+
type: "string",
|
|
3090
|
+
description: "Google Maps API key",
|
|
3091
|
+
default: process.env.GOOGLE_MAPS_API_KEY
|
|
3092
|
+
}).example([
|
|
3093
|
+
["$0 batch-geocode -i addresses.txt", "Geocode to stdout"],
|
|
3094
|
+
["$0 batch-geocode -i addresses.txt -o results.json", "Geocode to file"],
|
|
3095
|
+
["cat addresses.txt | $0 batch-geocode -i -", "Geocode from stdin"]
|
|
3096
|
+
]);
|
|
3097
|
+
},
|
|
3098
|
+
async (argv) => {
|
|
1843
3099
|
if (!argv.apikey) {
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
3100
|
+
console.error("Error: GOOGLE_MAPS_API_KEY not set. Use --apikey or set env var.");
|
|
3101
|
+
process.exit(1);
|
|
3102
|
+
}
|
|
3103
|
+
let lines;
|
|
3104
|
+
if (argv.input === "-") {
|
|
3105
|
+
const rl = createInterface({ input: process.stdin });
|
|
3106
|
+
lines = [];
|
|
3107
|
+
for await (const line of rl) {
|
|
3108
|
+
const trimmed = line.trim();
|
|
3109
|
+
if (trimmed) lines.push(trimmed);
|
|
3110
|
+
}
|
|
3111
|
+
} else {
|
|
3112
|
+
if (!existsSync4(argv.input)) {
|
|
3113
|
+
console.error(`Error: File not found: ${argv.input}`);
|
|
3114
|
+
process.exit(1);
|
|
3115
|
+
}
|
|
3116
|
+
lines = readFileSync5(argv.input, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
1847
3117
|
}
|
|
1848
|
-
|
|
1849
|
-
|
|
3118
|
+
if (lines.length === 0) {
|
|
3119
|
+
console.error("Error: No addresses found in input.");
|
|
1850
3120
|
process.exit(1);
|
|
3121
|
+
}
|
|
3122
|
+
const searcher = new PlacesSearcher(argv.apikey);
|
|
3123
|
+
const concurrency = Math.min(Math.max(argv.concurrency, 1), 50);
|
|
3124
|
+
const results = [];
|
|
3125
|
+
let completed = 0;
|
|
3126
|
+
const semaphore = async (tasks2, limit) => {
|
|
3127
|
+
const executing = [];
|
|
3128
|
+
for (const task of tasks2) {
|
|
3129
|
+
const p = task().then(() => {
|
|
3130
|
+
executing.splice(executing.indexOf(p), 1);
|
|
3131
|
+
});
|
|
3132
|
+
executing.push(p);
|
|
3133
|
+
if (executing.length >= limit) {
|
|
3134
|
+
await Promise.race(executing);
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
await Promise.all(executing);
|
|
3138
|
+
};
|
|
3139
|
+
const tasks = lines.map((address, index) => async () => {
|
|
3140
|
+
try {
|
|
3141
|
+
const result = await searcher.geocode(address);
|
|
3142
|
+
results[index] = { address, ...result };
|
|
3143
|
+
} catch (error) {
|
|
3144
|
+
results[index] = { address, success: false, error: error.message };
|
|
3145
|
+
}
|
|
3146
|
+
completed++;
|
|
3147
|
+
if (!argv.output) return;
|
|
3148
|
+
process.stderr.write(`\r ${completed}/${lines.length} geocoded`);
|
|
1851
3149
|
});
|
|
3150
|
+
await semaphore(tasks, concurrency);
|
|
3151
|
+
if (argv.output) {
|
|
3152
|
+
process.stderr.write("\n");
|
|
3153
|
+
}
|
|
3154
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
3155
|
+
const failed = results.filter((r) => !r.success).length;
|
|
3156
|
+
const summary = { total: lines.length, succeeded, failed, results };
|
|
3157
|
+
const json = JSON.stringify(summary, null, 2);
|
|
3158
|
+
if (argv.output) {
|
|
3159
|
+
writeFileSync4(argv.output, json, "utf-8");
|
|
3160
|
+
console.error(`Done: ${succeeded}/${lines.length} succeeded. Output: ${argv.output}`);
|
|
3161
|
+
} else {
|
|
3162
|
+
console.log(json);
|
|
3163
|
+
}
|
|
3164
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
1852
3165
|
}
|
|
1853
|
-
|
|
1854
|
-
|
|
3166
|
+
).command(
|
|
3167
|
+
"$0",
|
|
3168
|
+
"Start the MCP server (HTTP by default, --stdio for stdio mode)",
|
|
3169
|
+
(yargs2) => {
|
|
3170
|
+
return yargs2.option("port", {
|
|
3171
|
+
alias: "p",
|
|
3172
|
+
type: "number",
|
|
3173
|
+
description: "Port to run the MCP server on",
|
|
3174
|
+
default: process.env.MCP_SERVER_PORT ? parseInt(process.env.MCP_SERVER_PORT) : 3e3
|
|
3175
|
+
}).option("host", {
|
|
3176
|
+
type: "string",
|
|
3177
|
+
description: "Hostname to bind the server to (default 127.0.0.1 loopback; 0.0.0.0 for all interfaces)",
|
|
3178
|
+
default: process.env.MCP_SERVER_HOST || "127.0.0.1"
|
|
3179
|
+
}).option("apikey", {
|
|
3180
|
+
alias: "k",
|
|
3181
|
+
type: "string",
|
|
3182
|
+
description: "Google Maps API key",
|
|
3183
|
+
default: process.env.GOOGLE_MAPS_API_KEY
|
|
3184
|
+
}).option("stdio", {
|
|
3185
|
+
type: "boolean",
|
|
3186
|
+
description: "Use stdio transport instead of HTTP",
|
|
3187
|
+
default: false
|
|
3188
|
+
}).example([
|
|
3189
|
+
["$0", "Start HTTP server with default settings"],
|
|
3190
|
+
['$0 --port 3000 --apikey "your_api_key"', "Start HTTP with custom port and API key"],
|
|
3191
|
+
["$0 --host 0.0.0.0 --port 3000", "Start HTTP accessible from all interfaces"],
|
|
3192
|
+
["$0 --stdio", "Start in stdio mode (for Claude Desktop, Cursor, etc.)"]
|
|
3193
|
+
]);
|
|
3194
|
+
},
|
|
3195
|
+
async (argv) => {
|
|
3196
|
+
if (argv.apikey) {
|
|
3197
|
+
process.env.GOOGLE_MAPS_API_KEY = argv.apikey;
|
|
3198
|
+
}
|
|
3199
|
+
void checkVersionIdle({ currentVersion: packageVersion }).catch(() => void 0);
|
|
3200
|
+
const tools = filterTools(config_default[0].tools);
|
|
3201
|
+
if (argv.stdio) {
|
|
3202
|
+
const server = new BaseMcpServer(config_default[0].name, tools);
|
|
3203
|
+
await server.startStdio();
|
|
3204
|
+
} else {
|
|
3205
|
+
Logger.log("\u{1F5FA}\uFE0F Google Maps MCP Server");
|
|
3206
|
+
Logger.log(" A Model Context Protocol server for Google Maps services");
|
|
3207
|
+
Logger.log("");
|
|
3208
|
+
if (!argv.apikey) {
|
|
3209
|
+
Logger.log("\u26A0\uFE0F Google Maps API Key not found!");
|
|
3210
|
+
Logger.log(" Please provide --apikey parameter or set GOOGLE_MAPS_API_KEY in your .env file");
|
|
3211
|
+
Logger.log("");
|
|
3212
|
+
}
|
|
3213
|
+
startServer(argv.port, argv.apikey, argv.host).then((outcome) => {
|
|
3214
|
+
if (outcome.started === 0) {
|
|
3215
|
+
process.exit(1);
|
|
3216
|
+
}
|
|
3217
|
+
}).catch((error) => {
|
|
3218
|
+
Logger.error("\u274C Failed to start server:", error);
|
|
3219
|
+
process.exit(1);
|
|
3220
|
+
});
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
).version(packageVersion).alias("version", "v").option("env-file", {
|
|
3224
|
+
type: "string",
|
|
3225
|
+
global: true,
|
|
3226
|
+
description: "Load environment variables from this exact file (error if missing; no implicit .env fallback)"
|
|
3227
|
+
}).help().parse();
|
|
3228
|
+
}
|
|
1855
3229
|
}
|
|
1856
3230
|
export {
|
|
3231
|
+
DOCTOR_PROBES,
|
|
1857
3232
|
EXEC_TOOLS,
|
|
3233
|
+
createShutdownHandler,
|
|
1858
3234
|
execTool,
|
|
3235
|
+
extractEnvFileOption,
|
|
3236
|
+
installShutdownHandlers,
|
|
3237
|
+
parseDoctorProbes,
|
|
1859
3238
|
runDoctor,
|
|
3239
|
+
runEnableDisableCliCommand,
|
|
1860
3240
|
runExecCommand,
|
|
3241
|
+
runInstallUninstallCommand,
|
|
3242
|
+
runSetup,
|
|
3243
|
+
runShowCommand,
|
|
3244
|
+
runSnippetCommand,
|
|
1861
3245
|
startServer
|
|
1862
3246
|
};
|
|
1863
3247
|
//# sourceMappingURL=cli.js.map
|