mcp-google-map 0.3.0 → 0.5.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 +5 -1
- package/README.md +126 -81
- 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 +1345 -88
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/docs/CONFIGURATION.md +20 -12
- package/docs/TOOLS.md +1 -1
- package/package.json +9 -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,185 @@
|
|
|
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
|
+
function isAutoUpdateDisabled(env = process.env) {
|
|
23
|
+
const raw = env[AUTO_UPDATE_DISABLED_ENV];
|
|
24
|
+
return typeof raw === "string" && /^(1|true|yes)$/i.test(raw.trim());
|
|
25
|
+
}
|
|
26
|
+
function isNewerVersion(candidate, current) {
|
|
27
|
+
const toSegments = (version) => version.trim().split(".").map((segment) => /^\d+$/.test(segment) ? Number(segment) : Number.NaN);
|
|
28
|
+
const candidateSegments = toSegments(candidate);
|
|
29
|
+
const currentSegments = toSegments(current);
|
|
30
|
+
if (candidateSegments.some((segment) => !Number.isInteger(segment)) || currentSegments.some((segment) => !Number.isInteger(segment))) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const length = Math.max(candidateSegments.length, currentSegments.length);
|
|
34
|
+
for (let index = 0; index < length; index += 1) {
|
|
35
|
+
const candidatePart = candidateSegments[index] ?? 0;
|
|
36
|
+
const currentPart = currentSegments[index] ?? 0;
|
|
37
|
+
if (candidatePart !== currentPart) {
|
|
38
|
+
return candidatePart > currentPart;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
async function checkForUpdates(deps = {}) {
|
|
44
|
+
const logger = deps.logger ?? Logger;
|
|
45
|
+
const env = deps.env ?? process.env;
|
|
46
|
+
try {
|
|
47
|
+
if (isAutoUpdateDisabled(env)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const currentVersion = deps.currentVersion;
|
|
51
|
+
if (!currentVersion) {
|
|
52
|
+
logger.error(
|
|
53
|
+
`\u26A0\uFE0F [${DEFAULT_PACKAGE_NAME}] Automatic update check skipped: running package version is unknown.`
|
|
54
|
+
);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const packageName = deps.packageName ?? DEFAULT_PACKAGE_NAME;
|
|
58
|
+
const registryBaseUrl = deps.registryBaseUrl ?? DEFAULT_REGISTRY_BASE_URL;
|
|
59
|
+
const timeoutMs = deps.timeoutMs ?? DEFAULT_UPDATE_CHECK_TIMEOUT_MS;
|
|
60
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
61
|
+
const response = await fetchImpl(`${registryBaseUrl}/${packageName}/latest`, {
|
|
62
|
+
headers: { accept: "application/vnd.npm.install-v1+json" },
|
|
63
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
64
|
+
});
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
logger.error(
|
|
67
|
+
`\u26A0\uFE0F [${DEFAULT_PACKAGE_NAME}] Automatic update check failed: registry responded with HTTP ${response.status}.`
|
|
68
|
+
);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const payload = await response.json();
|
|
72
|
+
if (typeof payload?.version !== "string") {
|
|
73
|
+
logger.error(
|
|
74
|
+
`\u26A0\uFE0F [${DEFAULT_PACKAGE_NAME}] Automatic update check failed: registry response has no version field.`
|
|
75
|
+
);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
if (!isNewerVersion(payload.version, currentVersion)) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
logger.log(
|
|
82
|
+
`\u26A0\uFE0F Update available: ${packageName} ${currentVersion} \u2192 ${payload.version}. Upgrade with: npm install -g ${packageName}@latest (or run npx -y ${packageName}@latest).`
|
|
83
|
+
);
|
|
84
|
+
return { currentVersion, latestVersion: payload.version };
|
|
85
|
+
} catch (error) {
|
|
86
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
87
|
+
logger.error(`\u26A0\uFE0F [${DEFAULT_PACKAGE_NAME}] Automatic update check failed: ${message}`);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/utils/configResolver.ts
|
|
93
|
+
var NODE_ENGINE_FLOOR = 22;
|
|
94
|
+
var DEFAULT_HTTP_HOST = "127.0.0.1";
|
|
95
|
+
var DEFAULT_HTTP_PORT = 3e3;
|
|
96
|
+
function resolveRuntimeConfig(env = process.env, overrides = {}) {
|
|
97
|
+
const envPort = env.MCP_SERVER_PORT?.trim() || void 0;
|
|
98
|
+
let port;
|
|
99
|
+
let portRaw;
|
|
100
|
+
let portSource;
|
|
101
|
+
if (overrides.port !== void 0) {
|
|
102
|
+
port = overrides.port;
|
|
103
|
+
portRaw = String(overrides.port);
|
|
104
|
+
portSource = "flag";
|
|
105
|
+
} else if (envPort !== void 0) {
|
|
106
|
+
const parsed = Number(envPort);
|
|
107
|
+
port = Number.isFinite(parsed) ? parsed : void 0;
|
|
108
|
+
portRaw = envPort;
|
|
109
|
+
portSource = "environment";
|
|
110
|
+
} else {
|
|
111
|
+
port = DEFAULT_HTTP_PORT;
|
|
112
|
+
portRaw = void 0;
|
|
113
|
+
portSource = "default";
|
|
114
|
+
}
|
|
115
|
+
const apiKey = overrides.apiKey ?? env.GOOGLE_MAPS_API_KEY;
|
|
116
|
+
const enabledToolsRaw = env.GOOGLE_MAPS_ENABLED_TOOLS?.trim() || void 0;
|
|
117
|
+
return {
|
|
118
|
+
apiKey,
|
|
119
|
+
apiKeySource: overrides.apiKey !== void 0 ? "flag" : apiKey ? "environment" : "none",
|
|
120
|
+
host: overrides.host ?? env.MCP_SERVER_HOST ?? DEFAULT_HTTP_HOST,
|
|
121
|
+
hostSource: overrides.host !== void 0 ? "flag" : env.MCP_SERVER_HOST ? "environment" : "default",
|
|
122
|
+
port,
|
|
123
|
+
portRaw,
|
|
124
|
+
portSource,
|
|
125
|
+
enabledToolsRaw,
|
|
126
|
+
enabledToolsSource: enabledToolsRaw ? "environment" : "default",
|
|
127
|
+
autoUpdateDisabled: isAutoUpdateDisabled(env),
|
|
128
|
+
envFile: overrides.envFile
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function maskSecret(value) {
|
|
132
|
+
if (!value) {
|
|
133
|
+
return "unset";
|
|
134
|
+
}
|
|
135
|
+
if (value.length <= 8) {
|
|
136
|
+
return "****";
|
|
137
|
+
}
|
|
138
|
+
return `${value.slice(0, 4)}****${value.slice(-4)}`;
|
|
139
|
+
}
|
|
140
|
+
function describeRuntimeConfig(config) {
|
|
141
|
+
return {
|
|
142
|
+
apiKey: { value: maskSecret(config.apiKey), source: config.apiKeySource },
|
|
143
|
+
httpHost: { value: config.host, source: config.hostSource },
|
|
144
|
+
httpPort: {
|
|
145
|
+
value: config.port ?? null,
|
|
146
|
+
raw: config.portRaw ?? null,
|
|
147
|
+
source: config.portSource
|
|
148
|
+
},
|
|
149
|
+
enabledTools: { value: config.enabledToolsRaw ?? "*", source: config.enabledToolsSource },
|
|
150
|
+
autoUpdateCheck: { enabled: !config.autoUpdateDisabled },
|
|
151
|
+
envFile: { value: config.envFile ?? null }
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function formatConfigDiagnostics(diagnostics) {
|
|
155
|
+
return [
|
|
156
|
+
`host=${diagnostics.httpHost.value} (${diagnostics.httpHost.source})`,
|
|
157
|
+
`port=${diagnostics.httpPort.value ?? "invalid"} (raw: ${diagnostics.httpPort.raw ?? "n/a"}, ${diagnostics.httpPort.source})`,
|
|
158
|
+
`apiKey=${diagnostics.apiKey.value} (${diagnostics.apiKey.source})`,
|
|
159
|
+
`enabledTools=${diagnostics.enabledTools.value} (${diagnostics.enabledTools.source})`,
|
|
160
|
+
`updateCheck=${diagnostics.autoUpdateCheck.enabled ? "enabled" : "disabled"}`
|
|
161
|
+
].join("; ");
|
|
162
|
+
}
|
|
163
|
+
function applyExplicitEnvFile(path) {
|
|
164
|
+
if (!existsSync(path)) {
|
|
165
|
+
return { ok: false, error: `env file not found: ${path}` };
|
|
166
|
+
}
|
|
167
|
+
dotenvConfig({ path });
|
|
168
|
+
return { ok: true };
|
|
169
|
+
}
|
|
170
|
+
function resolveEnabledToolNames(allToolNames, raw) {
|
|
171
|
+
const trimmed = raw?.trim();
|
|
172
|
+
if (!trimmed || trimmed === "*") {
|
|
173
|
+
return { names: [...allToolNames], matched: allToolNames.length, fallback: false, requested: [] };
|
|
174
|
+
}
|
|
175
|
+
const requested = trimmed.split(",").map((item) => item.trim()).filter(Boolean);
|
|
176
|
+
const enabled = new Set(requested);
|
|
177
|
+
const names = allToolNames.filter((name) => enabled.has(name));
|
|
178
|
+
if (names.length === 0) {
|
|
179
|
+
return { names: [...allToolNames], matched: 0, fallback: true, requested };
|
|
180
|
+
}
|
|
181
|
+
return { names, matched: names.length, fallback: false, requested };
|
|
182
|
+
}
|
|
183
|
+
|
|
13
184
|
// src/tools/maps/searchNearby.ts
|
|
14
185
|
import { z } from "zod";
|
|
15
186
|
|
|
@@ -1023,18 +1194,19 @@ var serverConfigs = [
|
|
|
1023
1194
|
}
|
|
1024
1195
|
];
|
|
1025
1196
|
function filterTools(tools) {
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
raw.split(",").map((s) => s.trim()).filter(Boolean)
|
|
1197
|
+
const resolution = resolveEnabledToolNames(
|
|
1198
|
+
tools.map((tool) => tool.name),
|
|
1199
|
+
process.env.GOOGLE_MAPS_ENABLED_TOOLS
|
|
1030
1200
|
);
|
|
1031
|
-
|
|
1032
|
-
if (filtered.length === 0) {
|
|
1201
|
+
if (resolution.fallback) {
|
|
1033
1202
|
Logger.error(`GOOGLE_MAPS_ENABLED_TOOLS matched 0 tools. Available: ${tools.map((t) => t.name).join(", ")}`);
|
|
1034
1203
|
return tools;
|
|
1035
1204
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1205
|
+
if (resolution.matched !== tools.length) {
|
|
1206
|
+
Logger.log(`GOOGLE_MAPS_ENABLED_TOOLS: ${resolution.matched}/${tools.length} tools active`);
|
|
1207
|
+
}
|
|
1208
|
+
const enabled = new Set(resolution.names);
|
|
1209
|
+
return tools.filter((tool) => enabled.has(tool.name));
|
|
1038
1210
|
}
|
|
1039
1211
|
var config_default = serverConfigs;
|
|
1040
1212
|
|
|
@@ -1140,7 +1312,20 @@ var BaseMcpServer = class {
|
|
|
1140
1312
|
};
|
|
1141
1313
|
Logger.log(`${this.serverName} connected and ready to process requests`);
|
|
1142
1314
|
}
|
|
1143
|
-
|
|
1315
|
+
/**
|
|
1316
|
+
* @brief Starts the Streamable HTTP listener and resolves once it is accepting connections.
|
|
1317
|
+
* @details Builds the Express app with the `/mcp` POST/GET/DELETE handlers,
|
|
1318
|
+
* then wraps `app.listen` in a Promise that resolves from the listen callback
|
|
1319
|
+
* and rejects on the server `error` event (e.g. EADDRINUSE), so callers get
|
|
1320
|
+
* a reliable startup outcome instead of an unhandled error. Default host is
|
|
1321
|
+
* loopback `127.0.0.1` (REQ-008); pass `0.0.0.0` to bind all interfaces.
|
|
1322
|
+
* @param port {number} TCP port to listen on (1-65535).
|
|
1323
|
+
* @param host {string} Bind address; defaults to `127.0.0.1`.
|
|
1324
|
+
* @return {Promise<void>} Resolves when listening; rejects with the listen error.
|
|
1325
|
+
* @throws {Error} Rejects when the HTTP listener emits an error (port in use, permission denied).
|
|
1326
|
+
* @satisfies REQ-008, REQ-095
|
|
1327
|
+
*/
|
|
1328
|
+
async startHttpServer(port, host = "127.0.0.1") {
|
|
1144
1329
|
const app = express();
|
|
1145
1330
|
app.use(express.json());
|
|
1146
1331
|
app.post("/mcp", async (req, res) => {
|
|
@@ -1212,42 +1397,53 @@ var BaseMcpServer = class {
|
|
|
1212
1397
|
app.get("/mcp", handleSessionRequest);
|
|
1213
1398
|
app.delete("/mcp", handleSessionRequest);
|
|
1214
1399
|
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1400
|
+
await new Promise((resolve6, reject) => {
|
|
1401
|
+
this.httpServer = app.listen(port, host, () => {
|
|
1402
|
+
Logger.log(`[${this.serverName}] HTTP server listening on ${host}:${port}`);
|
|
1403
|
+
Logger.log(`[${this.serverName}] MCP endpoint available at http://${displayHost}:${port}/mcp`);
|
|
1404
|
+
resolve6();
|
|
1405
|
+
});
|
|
1406
|
+
this.httpServer.on("error", (error) => reject(error));
|
|
1218
1407
|
});
|
|
1219
1408
|
}
|
|
1220
1409
|
async startStdio() {
|
|
1221
1410
|
const transport = new StdioServerTransport();
|
|
1222
1411
|
await this.connect(transport);
|
|
1223
1412
|
}
|
|
1413
|
+
/**
|
|
1414
|
+
* @brief Stops the HTTP listener and closes every session transport in order.
|
|
1415
|
+
* @details Idempotent: when no listener exists it logs and resolves. Otherwise
|
|
1416
|
+
* it detaches the server reference, clears the session map, awaits
|
|
1417
|
+
* `transport.close()` for every session (errors tolerated via allSettled),
|
|
1418
|
+
* calls `closeAllConnections` when available, and finally closes the socket.
|
|
1419
|
+
* Wired to SIGINT/SIGTERM through `installShutdownHandlers` (REQ-096).
|
|
1420
|
+
* @return {Promise<void>} Resolves after the socket closes; rejects when `server.close` errors.
|
|
1421
|
+
* @throws {Error} Rejects with the `server.close` error.
|
|
1422
|
+
* @satisfies REQ-096
|
|
1423
|
+
*/
|
|
1224
1424
|
async stopHttpServer() {
|
|
1225
|
-
|
|
1425
|
+
const server = this.httpServer;
|
|
1426
|
+
if (!server) {
|
|
1226
1427
|
Logger.error(`[${this.serverName}] HTTP server is not running or already stopped.`);
|
|
1227
1428
|
return;
|
|
1228
1429
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1430
|
+
this.httpServer = null;
|
|
1431
|
+
const closingSessions = Object.values(this.sessions);
|
|
1432
|
+
this.sessions = {};
|
|
1433
|
+
await Promise.allSettled(closingSessions.map((context) => context.transport.close()));
|
|
1434
|
+
if (closingSessions.length > 0) {
|
|
1435
|
+
Logger.log(`[${this.serverName}] All transports closed.`);
|
|
1436
|
+
}
|
|
1437
|
+
return new Promise((resolve6, reject) => {
|
|
1438
|
+
server.closeAllConnections?.();
|
|
1439
|
+
server.close((err) => {
|
|
1231
1440
|
if (err) {
|
|
1232
1441
|
Logger.error(`[${this.serverName}] Error stopping HTTP server:`, err);
|
|
1233
1442
|
reject(err);
|
|
1234
1443
|
return;
|
|
1235
1444
|
}
|
|
1236
1445
|
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
|
-
});
|
|
1446
|
+
resolve6();
|
|
1251
1447
|
});
|
|
1252
1448
|
});
|
|
1253
1449
|
}
|
|
@@ -1256,12 +1452,648 @@ var BaseMcpServer = class {
|
|
|
1256
1452
|
// src/cli.ts
|
|
1257
1453
|
import { fileURLToPath } from "url";
|
|
1258
1454
|
import { dirname } from "path";
|
|
1259
|
-
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
1455
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
|
|
1260
1456
|
import { createInterface } from "readline";
|
|
1457
|
+
|
|
1458
|
+
// src/utils/toolProfiles.ts
|
|
1459
|
+
var TOOL_API_PROFILES = {
|
|
1460
|
+
maps_geocode: { tool: "maps_geocode", apis: ["geocoding"], dependsOnTools: [] },
|
|
1461
|
+
maps_reverse_geocode: { tool: "maps_reverse_geocode", apis: ["geocoding"], dependsOnTools: [] },
|
|
1462
|
+
maps_batch_geocode: { tool: "maps_batch_geocode", apis: ["geocoding"], dependsOnTools: ["maps_geocode"] },
|
|
1463
|
+
maps_search_nearby: {
|
|
1464
|
+
tool: "maps_search_nearby",
|
|
1465
|
+
apis: ["places-new", "geocoding"],
|
|
1466
|
+
dependsOnTools: ["maps_geocode"]
|
|
1467
|
+
},
|
|
1468
|
+
maps_search_places: { tool: "maps_search_places", apis: ["places-new"], dependsOnTools: [] },
|
|
1469
|
+
maps_place_details: { tool: "maps_place_details", apis: ["places-new"], dependsOnTools: [] },
|
|
1470
|
+
maps_directions: { tool: "maps_directions", apis: ["routes"], dependsOnTools: [] },
|
|
1471
|
+
maps_distance_matrix: { tool: "maps_distance_matrix", apis: ["routes"], dependsOnTools: [] },
|
|
1472
|
+
maps_elevation: { tool: "maps_elevation", apis: ["elevation"], dependsOnTools: [] },
|
|
1473
|
+
maps_timezone: { tool: "maps_timezone", apis: ["timezone"], dependsOnTools: [] },
|
|
1474
|
+
maps_weather: { tool: "maps_weather", apis: ["weather"], dependsOnTools: [] },
|
|
1475
|
+
maps_air_quality: { tool: "maps_air_quality", apis: ["air-quality"], dependsOnTools: [] },
|
|
1476
|
+
maps_static_map: { tool: "maps_static_map", apis: ["static-maps"], dependsOnTools: [] },
|
|
1477
|
+
maps_explore_area: {
|
|
1478
|
+
tool: "maps_explore_area",
|
|
1479
|
+
apis: ["places-new", "geocoding"],
|
|
1480
|
+
dependsOnTools: ["maps_geocode", "maps_search_nearby", "maps_place_details"]
|
|
1481
|
+
},
|
|
1482
|
+
maps_plan_route: {
|
|
1483
|
+
tool: "maps_plan_route",
|
|
1484
|
+
apis: ["routes", "geocoding", "places-new"],
|
|
1485
|
+
dependsOnTools: ["maps_geocode", "maps_search_places", "maps_directions"]
|
|
1486
|
+
},
|
|
1487
|
+
maps_compare_places: {
|
|
1488
|
+
tool: "maps_compare_places",
|
|
1489
|
+
apis: ["places-new", "routes"],
|
|
1490
|
+
dependsOnTools: ["maps_search_places", "maps_place_details", "maps_distance_matrix"]
|
|
1491
|
+
},
|
|
1492
|
+
maps_search_along_route: {
|
|
1493
|
+
tool: "maps_search_along_route",
|
|
1494
|
+
apis: ["routes", "places-new"],
|
|
1495
|
+
dependsOnTools: ["maps_directions", "maps_search_places"]
|
|
1496
|
+
},
|
|
1497
|
+
maps_local_rank_tracker: {
|
|
1498
|
+
tool: "maps_local_rank_tracker",
|
|
1499
|
+
apis: ["places-new"],
|
|
1500
|
+
dependsOnTools: ["maps_place_details", "maps_search_places"]
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
function describeToolsProfile(toolNames) {
|
|
1504
|
+
const tools = [];
|
|
1505
|
+
const apiOrder = [];
|
|
1506
|
+
const seenApis = /* @__PURE__ */ new Set();
|
|
1507
|
+
const seenTools = /* @__PURE__ */ new Set();
|
|
1508
|
+
for (const name of toolNames) {
|
|
1509
|
+
if (seenTools.has(name)) {
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
const profile = TOOL_API_PROFILES[name];
|
|
1513
|
+
if (!profile) {
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
seenTools.add(name);
|
|
1517
|
+
tools.push({
|
|
1518
|
+
tool: profile.tool,
|
|
1519
|
+
apis: [...profile.apis],
|
|
1520
|
+
dependsOnTools: [...profile.dependsOnTools]
|
|
1521
|
+
});
|
|
1522
|
+
for (const api of profile.apis) {
|
|
1523
|
+
if (!seenApis.has(api)) {
|
|
1524
|
+
seenApis.add(api);
|
|
1525
|
+
apiOrder.push(api);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
return { toolCount: tools.length, apis: apiOrder, tools };
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// src/utils/clientSnippets.ts
|
|
1533
|
+
var API_KEY_PLACEHOLDER = "<your-google-maps-api-key>";
|
|
1534
|
+
var SNIPPET_DEFAULT_HOST = "127.0.0.1";
|
|
1535
|
+
var SNIPPET_DEFAULT_PORT = 3e3;
|
|
1536
|
+
var SNIPPET_CLIENT_TARGETS = {
|
|
1537
|
+
generic: "your MCP client configuration file",
|
|
1538
|
+
claude: "Claude Desktop: claude_desktop_config.json",
|
|
1539
|
+
cursor: "Cursor: .cursor/mcp.json",
|
|
1540
|
+
pi: "pi CLI: ~/.pi/agent/mcp.json (user) or .pi/mcp.json (project)"
|
|
1541
|
+
};
|
|
1542
|
+
function renderClientSnippet(options = {}) {
|
|
1543
|
+
const client = options.client ?? "generic";
|
|
1544
|
+
const transport = options.transport ?? "stdio";
|
|
1545
|
+
const serverName = options.serverName ?? "mcp-google-map";
|
|
1546
|
+
const notes = [`Paste this snippet into ${SNIPPET_CLIENT_TARGETS[client]}.`];
|
|
1547
|
+
notes.push("This command prints the snippet only; it never writes or edits configuration files.");
|
|
1548
|
+
let entry;
|
|
1549
|
+
if (transport === "http") {
|
|
1550
|
+
const host = options.host ?? SNIPPET_DEFAULT_HOST;
|
|
1551
|
+
const port = options.port ?? SNIPPET_DEFAULT_PORT;
|
|
1552
|
+
entry = { url: `http://${host}:${port}/mcp` };
|
|
1553
|
+
notes.push(`Start the server first: mcp-google-map --host ${host} --port ${port}.`);
|
|
1554
|
+
} else {
|
|
1555
|
+
const launcher = options.launcher ?? "npx";
|
|
1556
|
+
if (launcher === "node" && !options.commandPath) {
|
|
1557
|
+
throw new Error("--launcher node requires --command <path> pointing at dist/cli.js");
|
|
1558
|
+
}
|
|
1559
|
+
const command = launcher === "npx" ? "npx" : launcher === "bin" ? "mcp-google-map" : "node";
|
|
1560
|
+
const args = launcher === "npx" ? ["-y", "mcp-google-map", "--stdio"] : launcher === "bin" ? ["--stdio"] : [options.commandPath, "--stdio"];
|
|
1561
|
+
if (launcher === "npx") {
|
|
1562
|
+
notes.push("The npx launcher runs the published package; npm must be available on PATH.");
|
|
1563
|
+
}
|
|
1564
|
+
const apiKeyValue = options.envKey ? `\${${options.envKey}}` : API_KEY_PLACEHOLDER;
|
|
1565
|
+
entry = { command, args, env: { GOOGLE_MAPS_API_KEY: apiKeyValue } };
|
|
1566
|
+
if (options.envKey) {
|
|
1567
|
+
notes.push(
|
|
1568
|
+
`Variable references (\${${options.envKey}}) are client-specific: confirm your client expands environment variables before relying on them.`
|
|
1569
|
+
);
|
|
1570
|
+
} else {
|
|
1571
|
+
notes.push(`Replace ${API_KEY_PLACEHOLDER} with your Google Maps API key.`);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
const json = JSON.stringify({ mcpServers: { [serverName]: entry } }, null, 2);
|
|
1575
|
+
return { json, notes };
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// src/utils/cliSettings.ts
|
|
1579
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
1580
|
+
import { join, resolve } from "path";
|
|
1581
|
+
var SETTINGS_PROGRAM_NAME = "mcp-google-map";
|
|
1582
|
+
var CLI_SETTINGS_RELATIVE = [".config", SETTINGS_PROGRAM_NAME, "config.json"];
|
|
1583
|
+
var MCP_SERVER_NAME = "mcp-google-map";
|
|
1584
|
+
function resolveCliSettingsPath(home) {
|
|
1585
|
+
const root = home ?? (process.env.HOME ?? process.cwd());
|
|
1586
|
+
return resolve(root, ...CLI_SETTINGS_RELATIVE);
|
|
1587
|
+
}
|
|
1588
|
+
function uniqueStrings(value) {
|
|
1589
|
+
if (!Array.isArray(value)) return [];
|
|
1590
|
+
const result = [];
|
|
1591
|
+
for (const item of value) {
|
|
1592
|
+
if (typeof item === "string" && item.length > 0 && !result.includes(item)) {
|
|
1593
|
+
result.push(item);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
return result;
|
|
1597
|
+
}
|
|
1598
|
+
function loadCliSettings(home) {
|
|
1599
|
+
try {
|
|
1600
|
+
const raw = JSON.parse(readFileSync(resolveCliSettingsPath(home), "utf-8"));
|
|
1601
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1602
|
+
return { enabled: [] };
|
|
1603
|
+
}
|
|
1604
|
+
return { enabled: uniqueStrings(raw.enabled) };
|
|
1605
|
+
} catch {
|
|
1606
|
+
return { enabled: [] };
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
function saveCliSettings(settings, home) {
|
|
1610
|
+
const path = resolveCliSettingsPath(home);
|
|
1611
|
+
const temporary = `${path}.tmp`;
|
|
1612
|
+
try {
|
|
1613
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
1614
|
+
writeFileSync(temporary, JSON.stringify({ enabled: uniqueStrings(settings.enabled) }, null, 2) + "\n", "utf-8");
|
|
1615
|
+
renameSync(temporary, path);
|
|
1616
|
+
} catch (error) {
|
|
1617
|
+
throw new Error(`settings write failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1618
|
+
}
|
|
1619
|
+
return path;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// src/utils/cliTargets.ts
|
|
1623
|
+
import {
|
|
1624
|
+
cpSync,
|
|
1625
|
+
existsSync as existsSync2,
|
|
1626
|
+
mkdirSync as mkdirSync2,
|
|
1627
|
+
readFileSync as readFileSync2,
|
|
1628
|
+
readdirSync,
|
|
1629
|
+
renameSync as renameSync2,
|
|
1630
|
+
rmSync,
|
|
1631
|
+
statSync,
|
|
1632
|
+
writeFileSync as writeFileSync2
|
|
1633
|
+
} from "fs";
|
|
1634
|
+
import { join as join2, relative } from "path";
|
|
1635
|
+
function homeRoot(home) {
|
|
1636
|
+
return home ?? (process.env.HOME ?? process.cwd());
|
|
1637
|
+
}
|
|
1638
|
+
var PI_TARGET = {
|
|
1639
|
+
id: "pi",
|
|
1640
|
+
configFormat: "json",
|
|
1641
|
+
mcpConfigPath: (home) => join2(homeRoot(home), ".pi", "agent", "mcp.json"),
|
|
1642
|
+
skillsDir: (home) => join2(homeRoot(home), ".pi", "agent", "skills")
|
|
1643
|
+
};
|
|
1644
|
+
var OPENCLAW_TARGET = {
|
|
1645
|
+
id: "openclaw",
|
|
1646
|
+
configFormat: "json",
|
|
1647
|
+
mcpConfigPath: (home) => join2(homeRoot(home), ".openclaw", "openclaw.json"),
|
|
1648
|
+
skillsDir: (home) => join2(homeRoot(home), ".openclaw", "workspace", "skills")
|
|
1649
|
+
};
|
|
1650
|
+
var CODEX_TARGET = {
|
|
1651
|
+
id: "codex",
|
|
1652
|
+
configFormat: "toml",
|
|
1653
|
+
mcpConfigPath: (home) => join2(homeRoot(home), ".codex", "config.toml"),
|
|
1654
|
+
skillsDir: (home) => join2(homeRoot(home), ".codex", "skills")
|
|
1655
|
+
};
|
|
1656
|
+
var ANTIGRAVITY_TARGET = {
|
|
1657
|
+
id: "antigravity",
|
|
1658
|
+
configFormat: "json",
|
|
1659
|
+
mcpConfigPath: (home) => join2(homeRoot(home), ".gemini", "settings.json"),
|
|
1660
|
+
skillsDir: (home) => join2(homeRoot(home), ".gemini", "config", "skills")
|
|
1661
|
+
};
|
|
1662
|
+
var CLAUDE_TARGET = {
|
|
1663
|
+
id: "claude",
|
|
1664
|
+
configFormat: "json",
|
|
1665
|
+
mcpConfigPath: (home) => join2(homeRoot(home), ".claude.json"),
|
|
1666
|
+
skillsDir: (home) => join2(homeRoot(home), ".claude", "skills")
|
|
1667
|
+
};
|
|
1668
|
+
var CLI_TARGETS = {
|
|
1669
|
+
pi: PI_TARGET,
|
|
1670
|
+
openclaw: OPENCLAW_TARGET,
|
|
1671
|
+
codex: CODEX_TARGET,
|
|
1672
|
+
antigravity: ANTIGRAVITY_TARGET,
|
|
1673
|
+
claude: CLAUDE_TARGET
|
|
1674
|
+
};
|
|
1675
|
+
function resolveCliTarget(name) {
|
|
1676
|
+
return CLI_TARGETS[name.trim().toLowerCase()];
|
|
1677
|
+
}
|
|
1678
|
+
function supportedCliNames() {
|
|
1679
|
+
return Object.keys(CLI_TARGETS).join(", ");
|
|
1680
|
+
}
|
|
1681
|
+
function readJsonConfig(configPath) {
|
|
1682
|
+
if (!existsSync2(configPath)) return {};
|
|
1683
|
+
const parsed = JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
1684
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1685
|
+
throw new Error(`configuration is not a JSON object: ${configPath}`);
|
|
1686
|
+
}
|
|
1687
|
+
return parsed;
|
|
1688
|
+
}
|
|
1689
|
+
function writeJsonConfig(configPath, value) {
|
|
1690
|
+
const temporary = `${configPath}.tmp`;
|
|
1691
|
+
mkdirSync2(join2(configPath, ".."), { recursive: true });
|
|
1692
|
+
writeFileSync2(temporary, JSON.stringify(value, null, 2) + "\n", "utf-8");
|
|
1693
|
+
renameSync2(temporary, configPath);
|
|
1694
|
+
}
|
|
1695
|
+
function writeJsonMcpServerEntry(configPath, serverName, entry) {
|
|
1696
|
+
const config = readJsonConfig(configPath);
|
|
1697
|
+
const mcpServers = config.mcpServers ?? {};
|
|
1698
|
+
if (mcpServers === null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
1699
|
+
throw new Error(`mcpServers is not an object: ${configPath}`);
|
|
1700
|
+
}
|
|
1701
|
+
mcpServers[serverName] = entry;
|
|
1702
|
+
config.mcpServers = mcpServers;
|
|
1703
|
+
writeJsonConfig(configPath, config);
|
|
1704
|
+
}
|
|
1705
|
+
function removeJsonMcpServerEntry(configPath, serverName) {
|
|
1706
|
+
if (!existsSync2(configPath)) return false;
|
|
1707
|
+
const config = readJsonConfig(configPath);
|
|
1708
|
+
const mcpServers = config.mcpServers;
|
|
1709
|
+
if (mcpServers === void 0) return false;
|
|
1710
|
+
if (mcpServers === null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
1711
|
+
throw new Error(`mcpServers is not an object: ${configPath}`);
|
|
1712
|
+
}
|
|
1713
|
+
const map = mcpServers;
|
|
1714
|
+
if (!(serverName in map)) return false;
|
|
1715
|
+
delete map[serverName];
|
|
1716
|
+
config.mcpServers = map;
|
|
1717
|
+
writeJsonConfig(configPath, config);
|
|
1718
|
+
return true;
|
|
1719
|
+
}
|
|
1720
|
+
function readJsonMcpServerEntry(configPath, serverName) {
|
|
1721
|
+
try {
|
|
1722
|
+
const config = readJsonConfig(configPath);
|
|
1723
|
+
const mcpServers = config.mcpServers;
|
|
1724
|
+
if (mcpServers === null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
1725
|
+
return void 0;
|
|
1726
|
+
}
|
|
1727
|
+
return mcpServers[serverName];
|
|
1728
|
+
} catch {
|
|
1729
|
+
return void 0;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
function tomlString(value) {
|
|
1733
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1734
|
+
}
|
|
1735
|
+
function tomlSectionHeader(serverName) {
|
|
1736
|
+
return `[mcp_servers.${serverName}]`;
|
|
1737
|
+
}
|
|
1738
|
+
function removeTomlSection(content, serverName) {
|
|
1739
|
+
const lines = content.split("\n");
|
|
1740
|
+
const header = tomlSectionHeader(serverName);
|
|
1741
|
+
const kept = [];
|
|
1742
|
+
let skipping = false;
|
|
1743
|
+
for (const line of lines) {
|
|
1744
|
+
if (skipping) {
|
|
1745
|
+
if (line.trimStart().startsWith("[")) {
|
|
1746
|
+
skipping = false;
|
|
1747
|
+
} else {
|
|
1748
|
+
continue;
|
|
1749
|
+
}
|
|
1750
|
+
} else if (line.trim() === header) {
|
|
1751
|
+
skipping = true;
|
|
1752
|
+
while (kept.length > 0 && kept[kept.length - 1].trim() === "") {
|
|
1753
|
+
kept.pop();
|
|
1754
|
+
}
|
|
1755
|
+
continue;
|
|
1756
|
+
}
|
|
1757
|
+
kept.push(line);
|
|
1758
|
+
}
|
|
1759
|
+
while (kept.length > 0 && kept[kept.length - 1].trim() === "") {
|
|
1760
|
+
kept.pop();
|
|
1761
|
+
}
|
|
1762
|
+
return kept.length > 0 ? kept.join("\n") + "\n" : "";
|
|
1763
|
+
}
|
|
1764
|
+
function hasTomlSection(content, serverName) {
|
|
1765
|
+
return content.split("\n").some((line) => line.trim() === tomlSectionHeader(serverName));
|
|
1766
|
+
}
|
|
1767
|
+
function registrationToTomlLines(entry) {
|
|
1768
|
+
const lines = [`command = ${tomlString(entry.command)}`];
|
|
1769
|
+
if (entry.args !== void 0) {
|
|
1770
|
+
lines.push(`args = [${entry.args.map((arg) => tomlString(arg)).join(", ")}]`);
|
|
1771
|
+
}
|
|
1772
|
+
if (entry.env !== void 0) {
|
|
1773
|
+
const pairs = Object.entries(entry.env).map(([key, value]) => `${tomlString(key)} = ${tomlString(value)}`);
|
|
1774
|
+
if (pairs.length > 0) lines.push(`env = { ${pairs.join(", ")} }`);
|
|
1775
|
+
}
|
|
1776
|
+
return lines;
|
|
1777
|
+
}
|
|
1778
|
+
function writeTomlMcpServerSection(configPath, serverName, entry) {
|
|
1779
|
+
const previous = existsSync2(configPath) ? readFileSync2(configPath, "utf-8") : "";
|
|
1780
|
+
const base = removeTomlSection(previous, serverName);
|
|
1781
|
+
const table = [tomlSectionHeader(serverName), ...registrationToTomlLines(entry), ""].join("\n");
|
|
1782
|
+
const content = base.length > 0 ? base.replace(/\n*$/, "\n") + table : table;
|
|
1783
|
+
const temporary = `${configPath}.tmp`;
|
|
1784
|
+
mkdirSync2(join2(configPath, ".."), { recursive: true });
|
|
1785
|
+
writeFileSync2(temporary, content, "utf-8");
|
|
1786
|
+
renameSync2(temporary, configPath);
|
|
1787
|
+
}
|
|
1788
|
+
function removeTomlMcpServerSection(configPath, serverName) {
|
|
1789
|
+
if (!existsSync2(configPath)) return false;
|
|
1790
|
+
const previous = readFileSync2(configPath, "utf-8");
|
|
1791
|
+
if (!hasTomlSection(previous, serverName)) return false;
|
|
1792
|
+
const content = removeTomlSection(previous, serverName);
|
|
1793
|
+
const temporary = `${configPath}.tmp`;
|
|
1794
|
+
writeFileSync2(temporary, content, "utf-8");
|
|
1795
|
+
renameSync2(temporary, configPath);
|
|
1796
|
+
return true;
|
|
1797
|
+
}
|
|
1798
|
+
function hasTomlMcpServerSection(configPath, serverName) {
|
|
1799
|
+
if (!existsSync2(configPath)) return false;
|
|
1800
|
+
return hasTomlSection(readFileSync2(configPath, "utf-8"), serverName);
|
|
1801
|
+
}
|
|
1802
|
+
function listFilesRecursive(root) {
|
|
1803
|
+
if (!existsSync2(root)) return [];
|
|
1804
|
+
const files = [];
|
|
1805
|
+
const walk = (dir) => {
|
|
1806
|
+
for (const entry of readdirSync(dir)) {
|
|
1807
|
+
const full = join2(dir, entry);
|
|
1808
|
+
if (statSync(full).isDirectory()) {
|
|
1809
|
+
walk(full);
|
|
1810
|
+
} else {
|
|
1811
|
+
files.push(relative(root, full).split("\\").join("/"));
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
};
|
|
1815
|
+
walk(root);
|
|
1816
|
+
return files;
|
|
1817
|
+
}
|
|
1818
|
+
function syncSkillTree(source, destination) {
|
|
1819
|
+
mkdirSync2(destination, { recursive: true });
|
|
1820
|
+
cpSync(source, destination, { recursive: true, force: true });
|
|
1821
|
+
const expected = new Set(listFilesRecursive(source));
|
|
1822
|
+
const existing = listFilesRecursive(destination);
|
|
1823
|
+
existing.sort((a, b) => b.length - a.length);
|
|
1824
|
+
for (const rel of existing) {
|
|
1825
|
+
if (expected.has(rel)) continue;
|
|
1826
|
+
rmSync(join2(destination, rel), { recursive: true, force: true });
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
function removeSkillDir(skillDir) {
|
|
1830
|
+
rmSync(skillDir, { recursive: true, force: true });
|
|
1831
|
+
}
|
|
1832
|
+
function compareSkillTree(source, destination) {
|
|
1833
|
+
const packaged = listFilesRecursive(source);
|
|
1834
|
+
if (packaged.length === 0) return "MISSING";
|
|
1835
|
+
if (!existsSync2(destination)) return "MISSING";
|
|
1836
|
+
for (const rel of packaged) {
|
|
1837
|
+
const target = join2(destination, ...rel.split("/"));
|
|
1838
|
+
if (!existsSync2(target)) return "MISSING";
|
|
1839
|
+
if (readFileSync2(target).toString("utf-8") !== readFileSync2(join2(source, ...rel.split("/"))).toString("utf-8")) {
|
|
1840
|
+
return "STALE";
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
return "OK";
|
|
1844
|
+
}
|
|
1845
|
+
function isServerRegistered(target, serverName, home) {
|
|
1846
|
+
const configPath = target.mcpConfigPath(home);
|
|
1847
|
+
if (target.configFormat === "toml") {
|
|
1848
|
+
return hasTomlMcpServerSection(configPath, serverName);
|
|
1849
|
+
}
|
|
1850
|
+
return readJsonMcpServerEntry(configPath, serverName) !== void 0;
|
|
1851
|
+
}
|
|
1852
|
+
function applyServerRegistration(target, serverName, entry, home) {
|
|
1853
|
+
const configPath = target.mcpConfigPath(home);
|
|
1854
|
+
if (target.configFormat === "toml") {
|
|
1855
|
+
if (entry === void 0) {
|
|
1856
|
+
removeTomlMcpServerSection(configPath, serverName);
|
|
1857
|
+
} else {
|
|
1858
|
+
writeTomlMcpServerSection(configPath, serverName, entry);
|
|
1859
|
+
}
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
if (entry === void 0) {
|
|
1863
|
+
removeJsonMcpServerEntry(configPath, serverName);
|
|
1864
|
+
} else {
|
|
1865
|
+
writeJsonMcpServerEntry(configPath, serverName, entry);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
// src/utils/cliInstaller.ts
|
|
1870
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
1871
|
+
import { join as join3, resolve as resolve3 } from "path";
|
|
1872
|
+
function buildServerRegistration(apiKey) {
|
|
1873
|
+
const env = {};
|
|
1874
|
+
if (apiKey !== void 0 && apiKey.length > 0) {
|
|
1875
|
+
env.GOOGLE_MAPS_API_KEY = apiKey;
|
|
1876
|
+
}
|
|
1877
|
+
return { command: "npx", args: ["-y", MCP_SERVER_NAME, "--stdio"], env };
|
|
1878
|
+
}
|
|
1879
|
+
function resolveSkillsResourcesRoot(packageRoot) {
|
|
1880
|
+
const candidate = resolve3(packageRoot, "src", "resources", "skills");
|
|
1881
|
+
if (!existsSync3(candidate)) {
|
|
1882
|
+
throw new Error(`skills resources root not found: ${candidate}`);
|
|
1883
|
+
}
|
|
1884
|
+
return candidate;
|
|
1885
|
+
}
|
|
1886
|
+
function loadSkillsManifest(resourcesRoot) {
|
|
1887
|
+
const manifestPath = join3(resourcesRoot, "manifest.json");
|
|
1888
|
+
if (!existsSync3(manifestPath)) {
|
|
1889
|
+
throw new Error(`skills manifest not found: ${manifestPath}`);
|
|
1890
|
+
}
|
|
1891
|
+
let parsed;
|
|
1892
|
+
try {
|
|
1893
|
+
parsed = JSON.parse(readFileSync3(manifestPath, "utf-8"));
|
|
1894
|
+
} catch (error) {
|
|
1895
|
+
throw new Error(`skills manifest is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
1896
|
+
}
|
|
1897
|
+
const skills = parsed?.skills;
|
|
1898
|
+
if (!Array.isArray(skills)) {
|
|
1899
|
+
throw new Error(`skills manifest has no skills array: ${manifestPath}`);
|
|
1900
|
+
}
|
|
1901
|
+
return skills.map((entry) => {
|
|
1902
|
+
const skill = entry;
|
|
1903
|
+
if (typeof skill.id !== "string" || typeof skill.path !== "string") {
|
|
1904
|
+
throw new Error(`skills manifest entry is missing id or path: ${manifestPath}`);
|
|
1905
|
+
}
|
|
1906
|
+
if (!existsSync3(join3(resourcesRoot, skill.path))) {
|
|
1907
|
+
throw new Error(`manifest skill file not found: ${join3(resourcesRoot, skill.path)}`);
|
|
1908
|
+
}
|
|
1909
|
+
return {
|
|
1910
|
+
id: skill.id,
|
|
1911
|
+
name: skill.name ?? skill.id,
|
|
1912
|
+
version: skill.version ?? "",
|
|
1913
|
+
path: skill.path,
|
|
1914
|
+
description: skill.description ?? ""
|
|
1915
|
+
};
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
function installEnabledCliResources(options) {
|
|
1919
|
+
const manifest = loadSkillsManifest(options.resourcesRoot);
|
|
1920
|
+
const entry = buildServerRegistration(options.apiKey);
|
|
1921
|
+
const result = { summaries: [], errors: [] };
|
|
1922
|
+
for (const cli of loadCliSettings(options.home).enabled) {
|
|
1923
|
+
const target = CLI_TARGETS[cli];
|
|
1924
|
+
if (target === void 0) {
|
|
1925
|
+
result.errors.push(`Unsupported CLI: ${cli}`);
|
|
1926
|
+
continue;
|
|
1927
|
+
}
|
|
1928
|
+
try {
|
|
1929
|
+
applyServerRegistration(target, MCP_SERVER_NAME, entry, options.home);
|
|
1930
|
+
let skills = 0;
|
|
1931
|
+
for (const skill of manifest) {
|
|
1932
|
+
syncSkillTree(join3(options.resourcesRoot, skill.id), join3(target.skillsDir(options.home), skill.id));
|
|
1933
|
+
skills += 1;
|
|
1934
|
+
}
|
|
1935
|
+
result.summaries.push({ cli: target.id, server: 1, skills });
|
|
1936
|
+
} catch (error) {
|
|
1937
|
+
result.errors.push(`${target.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
return result;
|
|
1941
|
+
}
|
|
1942
|
+
function uninstallEnabledCliResources(options) {
|
|
1943
|
+
const manifest = loadSkillsManifest(options.resourcesRoot);
|
|
1944
|
+
const result = { summaries: [], errors: [] };
|
|
1945
|
+
for (const cli of loadCliSettings(options.home).enabled) {
|
|
1946
|
+
const target = CLI_TARGETS[cli];
|
|
1947
|
+
if (target === void 0) {
|
|
1948
|
+
result.errors.push(`Unsupported CLI: ${cli}`);
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
try {
|
|
1952
|
+
applyServerRegistration(target, MCP_SERVER_NAME, void 0, options.home);
|
|
1953
|
+
let skills = 0;
|
|
1954
|
+
for (const skill of manifest) {
|
|
1955
|
+
removeSkillDir(join3(target.skillsDir(options.home), skill.id));
|
|
1956
|
+
skills += 1;
|
|
1957
|
+
}
|
|
1958
|
+
result.summaries.push({ cli: target.id, server: 0, skills });
|
|
1959
|
+
} catch (error) {
|
|
1960
|
+
result.errors.push(`${target.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
return result;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// src/utils/cliShow.ts
|
|
1967
|
+
import { join as join4 } from "path";
|
|
1968
|
+
import { relative as relative2, resolve as resolve4 } from "path";
|
|
1969
|
+
var MAX_CELL_WIDTH = 48;
|
|
1970
|
+
var STATUS_OK = "OK";
|
|
1971
|
+
var STATUS_MISSING = "MISSING";
|
|
1972
|
+
function wrapCell(text, width) {
|
|
1973
|
+
const normalized = String(text).split(/\s+/).filter((word) => word.length > 0);
|
|
1974
|
+
if (normalized.length === 0) return [""];
|
|
1975
|
+
const lines = [];
|
|
1976
|
+
let current = "";
|
|
1977
|
+
for (let word of normalized) {
|
|
1978
|
+
while (word.length > width) {
|
|
1979
|
+
if (current.length > 0) {
|
|
1980
|
+
lines.push(current);
|
|
1981
|
+
current = "";
|
|
1982
|
+
}
|
|
1983
|
+
lines.push(word.slice(0, width));
|
|
1984
|
+
word = word.slice(width);
|
|
1985
|
+
}
|
|
1986
|
+
if (current.length === 0) {
|
|
1987
|
+
current = word;
|
|
1988
|
+
} else if (current.length + 1 + word.length <= width) {
|
|
1989
|
+
current = `${current} ${word}`;
|
|
1990
|
+
} else {
|
|
1991
|
+
lines.push(current);
|
|
1992
|
+
current = word;
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
if (current.length > 0) lines.push(current);
|
|
1996
|
+
return lines.length > 0 ? lines : [""];
|
|
1997
|
+
}
|
|
1998
|
+
function renderAsciiTable(headers, rows, maxWidth = MAX_CELL_WIDTH) {
|
|
1999
|
+
const widths = headers.map((header, index) => {
|
|
2000
|
+
const lengths = [header.length, ...rows.map((row) => (row[index] ?? "").length)];
|
|
2001
|
+
return Math.max(1, Math.min(Math.max(...lengths), Math.max(1, maxWidth)));
|
|
2002
|
+
});
|
|
2003
|
+
const border = "+" + widths.map((width) => "-".repeat(width + 2)).join("+") + "+";
|
|
2004
|
+
const renderRow = (cells) => {
|
|
2005
|
+
const wrapped = cells.map((cell, index) => wrapCell(cell ?? "", widths[index]));
|
|
2006
|
+
const height = Math.max(...wrapped.map((part) => part.length));
|
|
2007
|
+
const lines = [];
|
|
2008
|
+
for (let line = 0; line < height; line += 1) {
|
|
2009
|
+
const parts = wrapped.map((part, index) => (part[line] ?? "").padEnd(widths[index]));
|
|
2010
|
+
lines.push("| " + parts.join(" | ") + " |");
|
|
2011
|
+
}
|
|
2012
|
+
return lines;
|
|
2013
|
+
};
|
|
2014
|
+
const output = [border, ...renderRow(headers), border];
|
|
2015
|
+
for (const row of rows) {
|
|
2016
|
+
output.push(...renderRow(row));
|
|
2017
|
+
output.push(border);
|
|
2018
|
+
}
|
|
2019
|
+
return output.join("\n");
|
|
2020
|
+
}
|
|
2021
|
+
function buildCliRows(home) {
|
|
2022
|
+
const root = home ?? (process.env.HOME ?? process.cwd());
|
|
2023
|
+
const enabled = new Set(loadCliSettings(home).enabled);
|
|
2024
|
+
return Object.keys(CLI_TARGETS).map((id) => {
|
|
2025
|
+
const target = CLI_TARGETS[id];
|
|
2026
|
+
return [
|
|
2027
|
+
target.id,
|
|
2028
|
+
enabled.has(id) ? "yes" : "no",
|
|
2029
|
+
relative2(resolve4(root), resolve4(target.mcpConfigPath(home))).split("\\").join("/"),
|
|
2030
|
+
relative2(resolve4(root), resolve4(target.skillsDir(home))).split("\\").join("/")
|
|
2031
|
+
];
|
|
2032
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
function buildInstallRows(home, resourcesRoot) {
|
|
2035
|
+
const enabled = loadCliSettings(home).enabled;
|
|
2036
|
+
const manifest = loadSkillsManifest(resourcesRoot);
|
|
2037
|
+
const rows = [];
|
|
2038
|
+
for (const cli of enabled) {
|
|
2039
|
+
const target = CLI_TARGETS[cli];
|
|
2040
|
+
if (target === void 0) continue;
|
|
2041
|
+
rows.push([
|
|
2042
|
+
"Server",
|
|
2043
|
+
MCP_SERVER_NAME,
|
|
2044
|
+
target.id,
|
|
2045
|
+
"yes",
|
|
2046
|
+
isServerRegistered(target, MCP_SERVER_NAME, home) ? STATUS_OK : STATUS_MISSING
|
|
2047
|
+
]);
|
|
2048
|
+
for (const skill of manifest) {
|
|
2049
|
+
rows.push([
|
|
2050
|
+
"Skill",
|
|
2051
|
+
skill.id,
|
|
2052
|
+
target.id,
|
|
2053
|
+
"yes",
|
|
2054
|
+
compareSkillTree(join4(resourcesRoot, skill.id), join4(target.skillsDir(home), skill.id))
|
|
2055
|
+
]);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
return rows;
|
|
2059
|
+
}
|
|
2060
|
+
function buildCliShowReport(options) {
|
|
2061
|
+
return [
|
|
2062
|
+
"CLI Configuration",
|
|
2063
|
+
renderAsciiTable(["CLI", "Enabled", "MCP Config Path", "Skills Config Path"], buildCliRows(options.home)),
|
|
2064
|
+
"",
|
|
2065
|
+
"Installation Status",
|
|
2066
|
+
renderAsciiTable(["Type", "Name", "CLI", "Enabled", "Installed"], buildInstallRows(options.home, options.resourcesRoot))
|
|
2067
|
+
].join("\n");
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
// src/cli.ts
|
|
1261
2071
|
var __filename = fileURLToPath(import.meta.url);
|
|
1262
2072
|
var __dirname = dirname(__filename);
|
|
1263
|
-
|
|
1264
|
-
|
|
2073
|
+
function extractEnvFileOption(argv) {
|
|
2074
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2075
|
+
const arg = argv[i];
|
|
2076
|
+
if (arg === "--env-file" && i + 1 < argv.length) {
|
|
2077
|
+
return argv[i + 1];
|
|
2078
|
+
}
|
|
2079
|
+
if (arg.startsWith("--env-file=")) {
|
|
2080
|
+
return arg.slice("--env-file=".length);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
return void 0;
|
|
2084
|
+
}
|
|
2085
|
+
function loadEnvironment(envFile) {
|
|
2086
|
+
if (envFile !== void 0) {
|
|
2087
|
+
const result = applyExplicitEnvFile(envFile);
|
|
2088
|
+
if (!result.ok) {
|
|
2089
|
+
process.stderr.write(JSON.stringify({ error: result.error }, null, 2) + "\n");
|
|
2090
|
+
process.exit(1);
|
|
2091
|
+
}
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
dotenvConfig2({ path: resolve5(process.cwd(), ".env") });
|
|
2095
|
+
dotenvConfig2({ path: resolve5(__dirname, "../.env") });
|
|
2096
|
+
}
|
|
1265
2097
|
async function startServer(port, apiKey, host) {
|
|
1266
2098
|
if (port) {
|
|
1267
2099
|
process.env.MCP_SERVER_PORT = port.toString();
|
|
@@ -1278,36 +2110,69 @@ async function startServer(port, apiKey, host) {
|
|
|
1278
2110
|
"\u2139\uFE0F Reminder: enable Places API (New) in https://console.cloud.google.com before using the new Place features."
|
|
1279
2111
|
);
|
|
1280
2112
|
Logger.log("");
|
|
1281
|
-
const
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
2113
|
+
const servers = [];
|
|
2114
|
+
let started = 0;
|
|
2115
|
+
let failed = 0;
|
|
2116
|
+
await Promise.all(
|
|
2117
|
+
config_default.map(async (config) => {
|
|
2118
|
+
const portString = process.env[config.portEnvVar];
|
|
2119
|
+
if (!portString) {
|
|
2120
|
+
Logger.error(`\u26A0\uFE0F [${config.name}] Port environment variable ${config.portEnvVar} not set.`);
|
|
2121
|
+
Logger.log(`\u{1F4A1} Please set ${config.portEnvVar} in your .env file or use --port parameter.`);
|
|
2122
|
+
Logger.log(` Example: ${config.portEnvVar}=3000 or --port 3000`);
|
|
2123
|
+
failed++;
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
const serverPort = Number(portString);
|
|
2127
|
+
if (!Number.isInteger(serverPort) || serverPort < 1 || serverPort > 65535) {
|
|
2128
|
+
Logger.error(
|
|
2129
|
+
`\u274C [${config.name}] Invalid port number "${portString}" defined in ${config.portEnvVar} (expected integer 1-65535).`
|
|
2130
|
+
);
|
|
2131
|
+
failed++;
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
try {
|
|
2135
|
+
const activeTools = filterTools(config.tools);
|
|
2136
|
+
const server = new BaseMcpServer(config.name, activeTools);
|
|
2137
|
+
const serverHost = process.env.MCP_SERVER_HOST || "127.0.0.1";
|
|
2138
|
+
Logger.log(`\u{1F527} [${config.name}] Initializing MCP Server in HTTP mode on ${serverHost}:${serverPort}...`);
|
|
2139
|
+
await server.startHttpServer(serverPort, serverHost);
|
|
2140
|
+
servers.push(server);
|
|
2141
|
+
started++;
|
|
2142
|
+
const displayHost = serverHost === "0.0.0.0" ? "localhost" : serverHost;
|
|
2143
|
+
Logger.log(`\u2705 [${config.name}] MCP Server started successfully!`);
|
|
2144
|
+
Logger.log(` \u{1F310} Endpoint: http://${displayHost}:${serverPort}/mcp`);
|
|
2145
|
+
Logger.log(` \u{1F4DA} Tools: ${activeTools.length} available`);
|
|
2146
|
+
} catch (error) {
|
|
2147
|
+
Logger.error(`\u274C [${config.name}] Failed to start MCP Server on port ${serverPort}:`, error);
|
|
2148
|
+
failed++;
|
|
2149
|
+
}
|
|
2150
|
+
})
|
|
2151
|
+
);
|
|
2152
|
+
if (started > 0) {
|
|
2153
|
+
installShutdownHandlers(servers);
|
|
2154
|
+
}
|
|
1308
2155
|
Logger.log("");
|
|
1309
|
-
Logger.log("\u{1F389} Server initialization completed!");
|
|
2156
|
+
Logger.log(started > 0 ? "\u{1F389} Server initialization completed!" : "\u274C No MCP server instance started.");
|
|
1310
2157
|
Logger.log("\u{1F4A1} Need help? Check the README.md for configuration details.");
|
|
2158
|
+
return { started, failed, servers };
|
|
2159
|
+
}
|
|
2160
|
+
function createShutdownHandler(servers, exitHook = (code) => process.exit(code)) {
|
|
2161
|
+
let invoked = false;
|
|
2162
|
+
return async (signal) => {
|
|
2163
|
+
if (invoked) {
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
invoked = true;
|
|
2167
|
+
Logger.log(`\u{1F4F6} Received ${signal}; shutting down MCP HTTP server...`);
|
|
2168
|
+
await Promise.allSettled(servers.map((server) => server.stopHttpServer()));
|
|
2169
|
+
exitHook(0);
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
function installShutdownHandlers(servers) {
|
|
2173
|
+
const handler = createShutdownHandler(servers);
|
|
2174
|
+
process.on("SIGINT", () => void handler("SIGINT"));
|
|
2175
|
+
process.on("SIGTERM", () => void handler("SIGTERM"));
|
|
1311
2176
|
}
|
|
1312
2177
|
var EXEC_TOOLS = [
|
|
1313
2178
|
"geocode",
|
|
@@ -1473,13 +2338,58 @@ async function runExecCommand(toolName, params, apiKey, runner = execTool, strea
|
|
|
1473
2338
|
return 1;
|
|
1474
2339
|
}
|
|
1475
2340
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
2341
|
+
var DOCTOR_PROBES = [
|
|
2342
|
+
{ id: "geocoding", name: "geocoding-api", tool: "geocode", params: { address: "Tokyo Tower" } },
|
|
2343
|
+
{ id: "places-new", name: "places-api-new", tool: "search-places", params: { query: "Tokyo Tower" } },
|
|
2344
|
+
{
|
|
2345
|
+
id: "routes",
|
|
2346
|
+
name: "routes-api",
|
|
2347
|
+
tool: "directions",
|
|
2348
|
+
params: { origin: "Tokyo Tower", destination: "Tokyo Station", mode: "walking" }
|
|
2349
|
+
}
|
|
2350
|
+
];
|
|
2351
|
+
var DOCTOR_PROBE_ALIASES = {
|
|
2352
|
+
geocoding: "geocoding",
|
|
2353
|
+
"geocoding-api": "geocoding",
|
|
2354
|
+
"places-new": "places-new",
|
|
2355
|
+
places: "places-new",
|
|
2356
|
+
"places-api-new": "places-new",
|
|
2357
|
+
routes: "routes",
|
|
2358
|
+
"routes-api": "routes"
|
|
2359
|
+
};
|
|
2360
|
+
function parseDoctorProbes(raw) {
|
|
2361
|
+
if (raw === void 0) {
|
|
2362
|
+
return { probes: void 0 };
|
|
2363
|
+
}
|
|
2364
|
+
const items = raw.split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
|
|
2365
|
+
if (items.length === 0) {
|
|
2366
|
+
return { probes: void 0, error: "--probes received no probe names (expected: geocoding, places-new, routes)" };
|
|
2367
|
+
}
|
|
2368
|
+
const probes = [];
|
|
2369
|
+
for (const item of items) {
|
|
2370
|
+
const id = DOCTOR_PROBE_ALIASES[item];
|
|
2371
|
+
if (!id) {
|
|
2372
|
+
return { probes: [], error: `unknown probe "${item}" (expected: geocoding, places-new, routes)` };
|
|
2373
|
+
}
|
|
2374
|
+
if (!probes.includes(id)) {
|
|
2375
|
+
probes.push(id);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
return { probes };
|
|
2379
|
+
}
|
|
2380
|
+
async function runDoctor(packageVersion, apiKey, live = false, runner = execTool, options = {}) {
|
|
2381
|
+
const nodeVersion = options.nodeVersion ?? process.versions.node;
|
|
2382
|
+
const nodeMajor = Number.parseInt(nodeVersion.split(".")[0] ?? "0", 10);
|
|
2383
|
+
const config = options.config ?? resolveRuntimeConfig(process.env);
|
|
2384
|
+
const allToolNames = options.allToolNames ?? config_default[0].tools.map((tool) => tool.name);
|
|
2385
|
+
const toolsResolution = resolveEnabledToolNames(allToolNames, config.enabledToolsRaw);
|
|
2386
|
+
const diagnostics = describeRuntimeConfig(config);
|
|
2387
|
+
const toolsProfile = describeToolsProfile(toolsResolution.names);
|
|
1478
2388
|
const checks = [
|
|
1479
2389
|
{
|
|
1480
2390
|
name: "node",
|
|
1481
|
-
status: nodeMajor >=
|
|
1482
|
-
detail: `Node.js ${
|
|
2391
|
+
status: nodeMajor >= NODE_ENGINE_FLOOR ? "pass" : "fail",
|
|
2392
|
+
detail: `Node.js ${nodeVersion} detected; version ${NODE_ENGINE_FLOOR} or newer is required (package.json engines).`
|
|
1483
2393
|
},
|
|
1484
2394
|
{
|
|
1485
2395
|
name: "package",
|
|
@@ -1490,6 +2400,21 @@ async function runDoctor(packageVersion, apiKey, live = false, runner = execTool
|
|
|
1490
2400
|
name: "api-key",
|
|
1491
2401
|
status: apiKey ? "pass" : "fail",
|
|
1492
2402
|
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."
|
|
2403
|
+
},
|
|
2404
|
+
{
|
|
2405
|
+
name: "config",
|
|
2406
|
+
status: toolsResolution.fallback ? "fail" : "pass",
|
|
2407
|
+
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)}`
|
|
2408
|
+
},
|
|
2409
|
+
{
|
|
2410
|
+
name: "env-file",
|
|
2411
|
+
status: "pass",
|
|
2412
|
+
detail: config.envFile ? `Explicit environment file loaded: ${config.envFile}` : "No explicit --env-file requested; implicit .env discovery (current directory, then package directory) applies."
|
|
2413
|
+
},
|
|
2414
|
+
{
|
|
2415
|
+
name: "tools",
|
|
2416
|
+
status: "pass",
|
|
2417
|
+
detail: `${toolsProfile.toolCount} tool(s) enabled; Google APIs: ${toolsProfile.apis.join(", ") || "none"}; per-tool: ${toolsProfile.tools.map((tool) => `${tool.tool}[${tool.apis.join("+")}]`).join(", ")}`
|
|
1493
2418
|
}
|
|
1494
2419
|
];
|
|
1495
2420
|
if (!live) {
|
|
@@ -1505,16 +2430,8 @@ async function runDoctor(packageVersion, apiKey, live = false, runner = execTool
|
|
|
1505
2430
|
detail: "Live API checks require GOOGLE_MAPS_API_KEY."
|
|
1506
2431
|
});
|
|
1507
2432
|
} else {
|
|
1508
|
-
const
|
|
1509
|
-
|
|
1510
|
-
{ name: "places-api-new", tool: "search-places", params: { query: "Tokyo Tower" } },
|
|
1511
|
-
{
|
|
1512
|
-
name: "routes-api",
|
|
1513
|
-
tool: "directions",
|
|
1514
|
-
params: { origin: "Tokyo Tower", destination: "Tokyo Station", mode: "walking" }
|
|
1515
|
-
}
|
|
1516
|
-
];
|
|
1517
|
-
for (const check of liveChecks) {
|
|
2433
|
+
const selected = options.probes ? DOCTOR_PROBES.filter((probe) => options.probes.includes(probe.id)) : DOCTOR_PROBES;
|
|
2434
|
+
for (const check of selected) {
|
|
1518
2435
|
try {
|
|
1519
2436
|
const result = await runner(check.tool, check.params, apiKey);
|
|
1520
2437
|
if (isExecFailureResponse(result)) {
|
|
@@ -1546,13 +2463,169 @@ async function runDoctor(packageVersion, apiKey, live = false, runner = execTool
|
|
|
1546
2463
|
checks
|
|
1547
2464
|
};
|
|
1548
2465
|
}
|
|
2466
|
+
async function runSetup(options, deps = {}) {
|
|
2467
|
+
const fsImpl = deps.fs ?? {
|
|
2468
|
+
existsSync: (path) => existsSync4(path),
|
|
2469
|
+
readFileSync: (path, encoding) => readFileSync4(path, encoding),
|
|
2470
|
+
writeFileSync: (path, data, encoding) => writeFileSync3(path, data, encoding)
|
|
2471
|
+
};
|
|
2472
|
+
const nodeVersion = deps.nodeVersion ?? process.versions.node;
|
|
2473
|
+
const nodeMajor = Number.parseInt(nodeVersion.split(".")[0] ?? "0", 10);
|
|
2474
|
+
const config = deps.config ?? resolveRuntimeConfig(process.env);
|
|
2475
|
+
const examplePath = resolve5(options.packageDir, ".env.example");
|
|
2476
|
+
const envPath = resolve5(options.targetDir, ".env");
|
|
2477
|
+
const checks = [
|
|
2478
|
+
{
|
|
2479
|
+
name: "node",
|
|
2480
|
+
status: nodeMajor >= NODE_ENGINE_FLOOR ? "pass" : "fail",
|
|
2481
|
+
detail: `Node.js ${nodeVersion} detected; version ${NODE_ENGINE_FLOOR} or newer is required (package.json engines).`
|
|
2482
|
+
},
|
|
2483
|
+
{
|
|
2484
|
+
name: "env-template",
|
|
2485
|
+
status: fsImpl.existsSync(examplePath) ? "pass" : "fail",
|
|
2486
|
+
detail: fsImpl.existsSync(examplePath) ? `Template found: ${examplePath}` : `Template missing: ${examplePath} (expected next to the installed package).`
|
|
2487
|
+
}
|
|
2488
|
+
];
|
|
2489
|
+
let created = false;
|
|
2490
|
+
if (options.writeEnv) {
|
|
2491
|
+
if (fsImpl.existsSync(envPath)) {
|
|
2492
|
+
checks.push({
|
|
2493
|
+
name: "env-file",
|
|
2494
|
+
status: "pass",
|
|
2495
|
+
detail: `${envPath} already exists; left untouched (no overwrite).`
|
|
2496
|
+
});
|
|
2497
|
+
} else if (!fsImpl.existsSync(examplePath)) {
|
|
2498
|
+
checks.push({
|
|
2499
|
+
name: "env-file",
|
|
2500
|
+
status: "fail",
|
|
2501
|
+
detail: `Cannot create ${envPath}: template ${examplePath} is missing.`
|
|
2502
|
+
});
|
|
2503
|
+
} else {
|
|
2504
|
+
try {
|
|
2505
|
+
fsImpl.writeFileSync(envPath, fsImpl.readFileSync(examplePath, "utf-8"), "utf-8");
|
|
2506
|
+
created = true;
|
|
2507
|
+
checks.push({
|
|
2508
|
+
name: "env-file",
|
|
2509
|
+
status: "pass",
|
|
2510
|
+
detail: `Created ${envPath} from template; edit it and set GOOGLE_MAPS_API_KEY.`
|
|
2511
|
+
});
|
|
2512
|
+
} catch (error) {
|
|
2513
|
+
checks.push({
|
|
2514
|
+
name: "env-file",
|
|
2515
|
+
status: "fail",
|
|
2516
|
+
detail: `Failed to create ${envPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
} else {
|
|
2521
|
+
checks.push({
|
|
2522
|
+
name: "env-file",
|
|
2523
|
+
status: fsImpl.existsSync(envPath) ? "pass" : "skip",
|
|
2524
|
+
detail: fsImpl.existsSync(envPath) ? `${envPath} found.` : `${envPath} not found; rerun with --write-env to create it from the template.`
|
|
2525
|
+
});
|
|
2526
|
+
}
|
|
2527
|
+
checks.push({
|
|
2528
|
+
name: "api-key",
|
|
2529
|
+
status: config.apiKey ? "pass" : "fail",
|
|
2530
|
+
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."
|
|
2531
|
+
});
|
|
2532
|
+
return {
|
|
2533
|
+
success: checks.every((check) => check.status !== "fail"),
|
|
2534
|
+
created,
|
|
2535
|
+
checks
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
function runSnippetCommand(options, streams = { stdout: process.stdout, stderr: process.stderr }) {
|
|
2539
|
+
try {
|
|
2540
|
+
const snippet = renderClientSnippet(options);
|
|
2541
|
+
streams.stdout.write(snippet.json + "\n");
|
|
2542
|
+
for (const note of snippet.notes) {
|
|
2543
|
+
streams.stderr.write(`[snippet] ${note}
|
|
2544
|
+
`);
|
|
2545
|
+
}
|
|
2546
|
+
return 0;
|
|
2547
|
+
} catch (error) {
|
|
2548
|
+
streams.stderr.write(
|
|
2549
|
+
JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2) + "\n"
|
|
2550
|
+
);
|
|
2551
|
+
return 1;
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
function runEnableDisableCliCommand(name, enable, options = {}) {
|
|
2555
|
+
const streams = options.streams ?? { stdout: process.stdout, stderr: process.stderr };
|
|
2556
|
+
const target = resolveCliTarget(name);
|
|
2557
|
+
if (target === void 0) {
|
|
2558
|
+
streams.stderr.write(
|
|
2559
|
+
JSON.stringify({ error: `Unsupported CLI: ${name}. Supported: ${supportedCliNames()}.` }, null, 2) + "\n"
|
|
2560
|
+
);
|
|
2561
|
+
return 2;
|
|
2562
|
+
}
|
|
2563
|
+
const settings = loadCliSettings(options.home);
|
|
2564
|
+
const already = settings.enabled.includes(target.id);
|
|
2565
|
+
if (enable === already) {
|
|
2566
|
+
streams.stdout.write(
|
|
2567
|
+
JSON.stringify({ cli: target.id, enabled: enable, message: `${target.id} is already ${enable ? "enabled" : "disabled"}.` }, null, 2) + "\n"
|
|
2568
|
+
);
|
|
2569
|
+
return 0;
|
|
2570
|
+
}
|
|
2571
|
+
settings.enabled = enable ? [...settings.enabled, target.id] : settings.enabled.filter((item) => item !== target.id);
|
|
2572
|
+
saveCliSettings(settings, options.home);
|
|
2573
|
+
streams.stdout.write(
|
|
2574
|
+
JSON.stringify({ cli: target.id, enabled: enable, message: `${enable ? "Enabled" : "Disabled"} ${target.id}.` }, null, 2) + "\n"
|
|
2575
|
+
);
|
|
2576
|
+
return 0;
|
|
2577
|
+
}
|
|
2578
|
+
function runInstallUninstallCommand(operation, options) {
|
|
2579
|
+
const streams = options.streams ?? { stdout: process.stdout, stderr: process.stderr };
|
|
2580
|
+
if (loadCliSettings(options.home).enabled.length === 0) {
|
|
2581
|
+
streams.stdout.write(
|
|
2582
|
+
JSON.stringify(
|
|
2583
|
+
{
|
|
2584
|
+
success: true,
|
|
2585
|
+
message: "No CLI enabled. Use 'mcp-google-map enable <cli name>' first.",
|
|
2586
|
+
summaries: [],
|
|
2587
|
+
errors: []
|
|
2588
|
+
},
|
|
2589
|
+
null,
|
|
2590
|
+
2
|
|
2591
|
+
) + "\n"
|
|
2592
|
+
);
|
|
2593
|
+
return 0;
|
|
2594
|
+
}
|
|
2595
|
+
let result;
|
|
2596
|
+
try {
|
|
2597
|
+
result = operation === "install" ? installEnabledCliResources({ home: options.home, apiKey: options.apiKey, resourcesRoot: options.resourcesRoot }) : uninstallEnabledCliResources({ home: options.home, resourcesRoot: options.resourcesRoot });
|
|
2598
|
+
} catch (error) {
|
|
2599
|
+
streams.stderr.write(
|
|
2600
|
+
JSON.stringify({ success: false, errors: [error instanceof Error ? error.message : String(error)] }, null, 2) + "\n"
|
|
2601
|
+
);
|
|
2602
|
+
return 1;
|
|
2603
|
+
}
|
|
2604
|
+
streams.stdout.write(
|
|
2605
|
+
JSON.stringify({ success: result.errors.length === 0, operation, summaries: result.summaries, errors: result.errors }, null, 2) + "\n"
|
|
2606
|
+
);
|
|
2607
|
+
return result.errors.length > 0 ? 1 : 0;
|
|
2608
|
+
}
|
|
2609
|
+
function runShowCommand(options) {
|
|
2610
|
+
const streams = options.streams ?? { stdout: process.stdout, stderr: process.stderr };
|
|
2611
|
+
try {
|
|
2612
|
+
streams.stdout.write(buildCliShowReport(options) + "\n");
|
|
2613
|
+
return 0;
|
|
2614
|
+
} catch (error) {
|
|
2615
|
+
streams.stderr.write(
|
|
2616
|
+
JSON.stringify({ error: error instanceof Error ? error.message : String(error) }, null, 2) + "\n"
|
|
2617
|
+
);
|
|
2618
|
+
return 1;
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
1549
2621
|
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"));
|
|
1550
2622
|
var isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
1551
2623
|
if (isRunDirectly || isMainModule) {
|
|
2624
|
+
loadEnvironment(extractEnvFileOption(process.argv));
|
|
1552
2625
|
let packageVersion = "0.0.0";
|
|
1553
2626
|
try {
|
|
1554
|
-
const packageJsonPath =
|
|
1555
|
-
const packageJson = JSON.parse(
|
|
2627
|
+
const packageJsonPath = resolve5(__dirname, "../package.json");
|
|
2628
|
+
const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
|
|
1556
2629
|
packageVersion = packageJson.version;
|
|
1557
2630
|
} catch (e) {
|
|
1558
2631
|
packageVersion = "0.0.0";
|
|
@@ -1564,6 +2637,9 @@ if (isRunDirectly || isMainModule) {
|
|
|
1564
2637
|
type: "boolean",
|
|
1565
2638
|
default: false,
|
|
1566
2639
|
description: "Make billable test requests to Geocoding, Places (New), and Routes APIs"
|
|
2640
|
+
}).option("probes", {
|
|
2641
|
+
type: "string",
|
|
2642
|
+
description: "Comma-separated subset of live probes: geocoding, places-new, routes (default: all three)"
|
|
1567
2643
|
}).option("apikey", {
|
|
1568
2644
|
alias: "k",
|
|
1569
2645
|
type: "string",
|
|
@@ -1571,13 +2647,175 @@ if (isRunDirectly || isMainModule) {
|
|
|
1571
2647
|
default: process.env.GOOGLE_MAPS_API_KEY
|
|
1572
2648
|
}).example([
|
|
1573
2649
|
["$0 doctor", "Check local runtime and API key configuration without network calls"],
|
|
1574
|
-
["$0 doctor --live", "Also verify three Google Maps APIs with billable requests"]
|
|
2650
|
+
["$0 doctor --live", "Also verify three Google Maps APIs with billable requests"],
|
|
2651
|
+
["$0 doctor --live --probes geocoding,routes", "Probe only the selected APIs"]
|
|
1575
2652
|
]),
|
|
1576
2653
|
async (argv) => {
|
|
1577
|
-
const
|
|
2654
|
+
const parsedProbes = parseDoctorProbes(argv.probes);
|
|
2655
|
+
if (parsedProbes.error) {
|
|
2656
|
+
process.stderr.write(JSON.stringify({ error: parsedProbes.error }, null, 2) + "\n");
|
|
2657
|
+
process.exitCode = 1;
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
const report = await runDoctor(packageVersion, argv.apikey, argv.live, void 0, {
|
|
2661
|
+
probes: parsedProbes.probes,
|
|
2662
|
+
config: resolveRuntimeConfig(process.env, { envFile: extractEnvFileOption(process.argv) })
|
|
2663
|
+
});
|
|
1578
2664
|
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
1579
2665
|
process.exitCode = report.success ? 0 : 1;
|
|
1580
2666
|
}
|
|
2667
|
+
).command(
|
|
2668
|
+
"setup",
|
|
2669
|
+
"Non-interactive onboarding: verify local readiness, optionally scaffold .env",
|
|
2670
|
+
(yargs2) => yargs2.option("write-env", {
|
|
2671
|
+
type: "boolean",
|
|
2672
|
+
default: false,
|
|
2673
|
+
description: "Create .env from .env.example in --env-dir when it does not exist (never overwrites)"
|
|
2674
|
+
}).option("env-dir", {
|
|
2675
|
+
type: "string",
|
|
2676
|
+
default: process.cwd(),
|
|
2677
|
+
description: "Directory where .env is looked up / created"
|
|
2678
|
+
}).example([
|
|
2679
|
+
["$0 setup", "Check readiness without writing anything"],
|
|
2680
|
+
["$0 setup --write-env", "Also scaffold .env from the package template"]
|
|
2681
|
+
]),
|
|
2682
|
+
async (argv) => {
|
|
2683
|
+
const report = await runSetup({
|
|
2684
|
+
packageDir: resolve5(__dirname, ".."),
|
|
2685
|
+
targetDir: argv.envDir,
|
|
2686
|
+
writeEnv: argv.writeEnv
|
|
2687
|
+
});
|
|
2688
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
2689
|
+
process.exitCode = report.success ? 0 : 1;
|
|
2690
|
+
}
|
|
2691
|
+
).command(
|
|
2692
|
+
"snippet",
|
|
2693
|
+
"Print an MCP client registration snippet (JSON on stdout; never writes files)",
|
|
2694
|
+
(yargs2) => yargs2.option("client", {
|
|
2695
|
+
type: "string",
|
|
2696
|
+
choices: ["generic", "claude", "cursor", "pi"],
|
|
2697
|
+
default: "generic",
|
|
2698
|
+
description: "Target client (selects the documented target file)"
|
|
2699
|
+
}).option("transport", {
|
|
2700
|
+
type: "string",
|
|
2701
|
+
choices: ["stdio", "http"],
|
|
2702
|
+
default: "stdio",
|
|
2703
|
+
description: "MCP transport of the snippet"
|
|
2704
|
+
}).option("launcher", {
|
|
2705
|
+
type: "string",
|
|
2706
|
+
choices: ["npx", "node", "bin"],
|
|
2707
|
+
default: "npx",
|
|
2708
|
+
description: "stdio launcher command"
|
|
2709
|
+
}).option("command", {
|
|
2710
|
+
type: "string",
|
|
2711
|
+
description: "Absolute path to dist/cli.js (required for --launcher node)"
|
|
2712
|
+
}).option("host", {
|
|
2713
|
+
type: "string",
|
|
2714
|
+
default: "127.0.0.1",
|
|
2715
|
+
description: "Host used in the URL (transport http)"
|
|
2716
|
+
}).option("port", {
|
|
2717
|
+
type: "number",
|
|
2718
|
+
default: 3e3,
|
|
2719
|
+
description: "Port used in the URL (transport http)"
|
|
2720
|
+
}).option("env-key", {
|
|
2721
|
+
type: "string",
|
|
2722
|
+
description: "Emit a ${NAME} variable reference instead of a literal placeholder (expansion is client-specific)"
|
|
2723
|
+
}).option("name", {
|
|
2724
|
+
type: "string",
|
|
2725
|
+
default: "mcp-google-map",
|
|
2726
|
+
description: "Server name inside mcpServers"
|
|
2727
|
+
}).example([
|
|
2728
|
+
["$0 snippet --client claude", "stdio snippet using the npx launcher"],
|
|
2729
|
+
["$0 snippet --client pi --launcher node --command /abs/path/dist/cli.js", "stdio snippet with an explicit artifact"],
|
|
2730
|
+
["$0 snippet --transport http --port 3100", "HTTP URL snippet"]
|
|
2731
|
+
]),
|
|
2732
|
+
(argv) => {
|
|
2733
|
+
process.exitCode = runSnippetCommand({
|
|
2734
|
+
client: argv.client,
|
|
2735
|
+
transport: argv.transport,
|
|
2736
|
+
launcher: argv.launcher,
|
|
2737
|
+
commandPath: argv.command,
|
|
2738
|
+
host: argv.host,
|
|
2739
|
+
port: argv.port,
|
|
2740
|
+
envKey: argv.envKey,
|
|
2741
|
+
serverName: argv.name
|
|
2742
|
+
});
|
|
2743
|
+
}
|
|
2744
|
+
).command(
|
|
2745
|
+
"enable <cli>",
|
|
2746
|
+
"Enable MCP-server and skill installation for a supported CLI",
|
|
2747
|
+
(yargs2) => yargs2.positional("cli", {
|
|
2748
|
+
type: "string",
|
|
2749
|
+
describe: `CLI name: ${supportedCliNames()}`
|
|
2750
|
+
}).option("home", {
|
|
2751
|
+
type: "string",
|
|
2752
|
+
description: "Home-directory override for the settings file (default: user home)"
|
|
2753
|
+
}).example([["$0 enable pi", "Enable pi for install/uninstall/show"]]),
|
|
2754
|
+
(argv) => {
|
|
2755
|
+
process.exitCode = runEnableDisableCliCommand(argv.cli, true, {
|
|
2756
|
+
home: argv.home
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
).command(
|
|
2760
|
+
"disable <cli>",
|
|
2761
|
+
"Disable MCP-server and skill installation for a supported CLI",
|
|
2762
|
+
(yargs2) => yargs2.positional("cli", {
|
|
2763
|
+
type: "string",
|
|
2764
|
+
describe: `CLI name: ${supportedCliNames()}`
|
|
2765
|
+
}).option("home", {
|
|
2766
|
+
type: "string",
|
|
2767
|
+
description: "Home-directory override for the settings file (default: user home)"
|
|
2768
|
+
}).example([["$0 disable claude", "Disable claude for install/uninstall/show"]]),
|
|
2769
|
+
(argv) => {
|
|
2770
|
+
process.exitCode = runEnableDisableCliCommand(argv.cli, false, {
|
|
2771
|
+
home: argv.home
|
|
2772
|
+
});
|
|
2773
|
+
}
|
|
2774
|
+
).command(
|
|
2775
|
+
"install",
|
|
2776
|
+
"Register the mcp-google-map stdio server and install packaged skills for every enabled CLI",
|
|
2777
|
+
(yargs2) => yargs2.option("home", {
|
|
2778
|
+
type: "string",
|
|
2779
|
+
description: "Home-directory override for CLI configuration paths (default: user home)"
|
|
2780
|
+
}).option("apikey", {
|
|
2781
|
+
alias: "k",
|
|
2782
|
+
type: "string",
|
|
2783
|
+
description: "Google Maps API key embedded in the registered entry (prefer GOOGLE_MAPS_API_KEY)",
|
|
2784
|
+
default: resolveRuntimeConfig(process.env, { envFile: extractEnvFileOption(process.argv) }).apiKey
|
|
2785
|
+
}).example([["$0 install", "Register the server and skills in every enabled CLI"]]),
|
|
2786
|
+
(argv) => {
|
|
2787
|
+
process.exitCode = runInstallUninstallCommand("install", {
|
|
2788
|
+
home: argv.home,
|
|
2789
|
+
apiKey: argv.apikey,
|
|
2790
|
+
resourcesRoot: resolveSkillsResourcesRoot(resolve5(__dirname, ".."))
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
).command(
|
|
2794
|
+
"uninstall",
|
|
2795
|
+
"Remove the mcp-google-map server entry and managed skills from every enabled CLI",
|
|
2796
|
+
(yargs2) => yargs2.option("home", {
|
|
2797
|
+
type: "string",
|
|
2798
|
+
description: "Home-directory override for CLI configuration paths (default: user home)"
|
|
2799
|
+
}).example([["$0 uninstall", "Reciprocal removal of install for every enabled CLI"]]),
|
|
2800
|
+
(argv) => {
|
|
2801
|
+
process.exitCode = runInstallUninstallCommand("uninstall", {
|
|
2802
|
+
home: argv.home,
|
|
2803
|
+
resourcesRoot: resolveSkillsResourcesRoot(resolve5(__dirname, ".."))
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
2806
|
+
).command(
|
|
2807
|
+
"show",
|
|
2808
|
+
"Show CLI configuration and installation status tables (human-readable)",
|
|
2809
|
+
(yargs2) => yargs2.option("home", {
|
|
2810
|
+
type: "string",
|
|
2811
|
+
description: "Home-directory override for CLI configuration paths (default: user home)"
|
|
2812
|
+
}).example([["$0 show", "Print CLI Configuration and Installation Status tables"]]),
|
|
2813
|
+
(argv) => {
|
|
2814
|
+
process.exitCode = runShowCommand({
|
|
2815
|
+
home: argv.home,
|
|
2816
|
+
resourcesRoot: resolveSkillsResourcesRoot(resolve5(__dirname, ".."))
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
1581
2819
|
).command(
|
|
1582
2820
|
"exec <tool> [params]",
|
|
1583
2821
|
"Execute a tool directly and output JSON",
|
|
@@ -1667,11 +2905,11 @@ if (isRunDirectly || isMainModule) {
|
|
|
1667
2905
|
if (trimmed) lines.push(trimmed);
|
|
1668
2906
|
}
|
|
1669
2907
|
} else {
|
|
1670
|
-
if (!
|
|
2908
|
+
if (!existsSync4(argv.input)) {
|
|
1671
2909
|
console.error(`Error: File not found: ${argv.input}`);
|
|
1672
2910
|
process.exit(1);
|
|
1673
2911
|
}
|
|
1674
|
-
lines =
|
|
2912
|
+
lines = readFileSync4(argv.input, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
1675
2913
|
}
|
|
1676
2914
|
if (lines.length === 0) {
|
|
1677
2915
|
console.error("Error: No addresses found in input.");
|
|
@@ -1714,7 +2952,7 @@ if (isRunDirectly || isMainModule) {
|
|
|
1714
2952
|
const summary = { total: lines.length, succeeded, failed, results };
|
|
1715
2953
|
const json = JSON.stringify(summary, null, 2);
|
|
1716
2954
|
if (argv.output) {
|
|
1717
|
-
|
|
2955
|
+
writeFileSync3(argv.output, json, "utf-8");
|
|
1718
2956
|
console.error(`Done: ${succeeded}/${lines.length} succeeded. Output: ${argv.output}`);
|
|
1719
2957
|
} else {
|
|
1720
2958
|
console.log(json);
|
|
@@ -1732,8 +2970,8 @@ if (isRunDirectly || isMainModule) {
|
|
|
1732
2970
|
default: process.env.MCP_SERVER_PORT ? parseInt(process.env.MCP_SERVER_PORT) : 3e3
|
|
1733
2971
|
}).option("host", {
|
|
1734
2972
|
type: "string",
|
|
1735
|
-
description: "Hostname to bind the server to (
|
|
1736
|
-
default: process.env.MCP_SERVER_HOST || "
|
|
2973
|
+
description: "Hostname to bind the server to (default 127.0.0.1 loopback; 0.0.0.0 for all interfaces)",
|
|
2974
|
+
default: process.env.MCP_SERVER_HOST || "127.0.0.1"
|
|
1737
2975
|
}).option("apikey", {
|
|
1738
2976
|
alias: "k",
|
|
1739
2977
|
type: "string",
|
|
@@ -1754,6 +2992,7 @@ if (isRunDirectly || isMainModule) {
|
|
|
1754
2992
|
if (argv.apikey) {
|
|
1755
2993
|
process.env.GOOGLE_MAPS_API_KEY = argv.apikey;
|
|
1756
2994
|
}
|
|
2995
|
+
void checkForUpdates({ currentVersion: packageVersion }).catch(() => void 0);
|
|
1757
2996
|
const tools = filterTools(config_default[0].tools);
|
|
1758
2997
|
if (argv.stdio) {
|
|
1759
2998
|
const server = new BaseMcpServer(config_default[0].name, tools);
|
|
@@ -1767,19 +3006,37 @@ if (isRunDirectly || isMainModule) {
|
|
|
1767
3006
|
Logger.log(" Please provide --apikey parameter or set GOOGLE_MAPS_API_KEY in your .env file");
|
|
1768
3007
|
Logger.log("");
|
|
1769
3008
|
}
|
|
1770
|
-
startServer(argv.port, argv.apikey, argv.host).
|
|
3009
|
+
startServer(argv.port, argv.apikey, argv.host).then((outcome) => {
|
|
3010
|
+
if (outcome.started === 0) {
|
|
3011
|
+
process.exit(1);
|
|
3012
|
+
}
|
|
3013
|
+
}).catch((error) => {
|
|
1771
3014
|
Logger.error("\u274C Failed to start server:", error);
|
|
1772
3015
|
process.exit(1);
|
|
1773
3016
|
});
|
|
1774
3017
|
}
|
|
1775
3018
|
}
|
|
1776
|
-
).version(packageVersion).alias("version", "v").
|
|
3019
|
+
).version(packageVersion).alias("version", "v").option("env-file", {
|
|
3020
|
+
type: "string",
|
|
3021
|
+
global: true,
|
|
3022
|
+
description: "Load environment variables from this exact file (error if missing; no implicit .env fallback)"
|
|
3023
|
+
}).help().parse();
|
|
1777
3024
|
}
|
|
1778
3025
|
export {
|
|
3026
|
+
DOCTOR_PROBES,
|
|
1779
3027
|
EXEC_TOOLS,
|
|
3028
|
+
createShutdownHandler,
|
|
1780
3029
|
execTool,
|
|
3030
|
+
extractEnvFileOption,
|
|
3031
|
+
installShutdownHandlers,
|
|
3032
|
+
parseDoctorProbes,
|
|
1781
3033
|
runDoctor,
|
|
3034
|
+
runEnableDisableCliCommand,
|
|
1782
3035
|
runExecCommand,
|
|
3036
|
+
runInstallUninstallCommand,
|
|
3037
|
+
runSetup,
|
|
3038
|
+
runShowCommand,
|
|
3039
|
+
runSnippetCommand,
|
|
1783
3040
|
startServer
|
|
1784
3041
|
};
|
|
1785
3042
|
//# sourceMappingURL=cli.js.map
|