@levr-one/cli 0.7.0 → 0.7.1
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.
|
@@ -1044,6 +1044,11 @@ const KNOWN_MCP_URLS = {
|
|
|
1044
1044
|
* deployments) derives `<api-url>/v1/mcp`.
|
|
1045
1045
|
*/
|
|
1046
1046
|
function resolveMcpUrl(flagUrl) {
|
|
1047
|
+
const resolved = resolveRaw(flagUrl);
|
|
1048
|
+
assertUsableMcpUrl(resolved.url, resolved.source);
|
|
1049
|
+
return resolved;
|
|
1050
|
+
}
|
|
1051
|
+
function resolveRaw(flagUrl) {
|
|
1047
1052
|
if (flagUrl) return {
|
|
1048
1053
|
url: stripSlash(flagUrl),
|
|
1049
1054
|
source: "flag"
|
|
@@ -1059,6 +1064,41 @@ function resolveMcpUrl(flagUrl) {
|
|
|
1059
1064
|
source: `derived:${apiUrl}`
|
|
1060
1065
|
};
|
|
1061
1066
|
}
|
|
1067
|
+
/** Name the input the user has to change, not our internal source tag. */
|
|
1068
|
+
function sourceLabel(source) {
|
|
1069
|
+
if (source === "flag") return "--url";
|
|
1070
|
+
if (source === "env:LEVR_MCP_URL") return "LEVR_MCP_URL";
|
|
1071
|
+
return `the API URL (${source.slice(8)})`;
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Reject a URL that must not reach a client config or a spawned CLI (ENG-4159).
|
|
1075
|
+
*
|
|
1076
|
+
* Three things this stops:
|
|
1077
|
+
*
|
|
1078
|
+
* 1. **No scheme.** `claude mcp add --transport http --scope user levr <value>`
|
|
1079
|
+
* is spawned with fixed argv since ENG-4156, so a value like `--foo` is read
|
|
1080
|
+
* by that CLI as a FLAG rather than as its URL operand — argument injection.
|
|
1081
|
+
* Requiring a parseable absolute URL removes the shape entirely.
|
|
1082
|
+
* 2. **A non-http(s) scheme.** `file:` / `javascript:` is never a valid MCP
|
|
1083
|
+
* endpoint, but would be written verbatim into every client's config.
|
|
1084
|
+
* 3. **Embedded credentials.** `https://user:pass@host` would be written into
|
|
1085
|
+
* a config file — and since ENG-4151 a config file can be `--scope project`,
|
|
1086
|
+
* which the user is told to COMMIT. That turns a bad URL into a secret in
|
|
1087
|
+
* git history. MCP authenticates via OAuth in the browser; credentials in
|
|
1088
|
+
* the URL are never needed.
|
|
1089
|
+
*/
|
|
1090
|
+
function assertUsableMcpUrl(url, source) {
|
|
1091
|
+
const from = sourceLabel(source);
|
|
1092
|
+
let parsed;
|
|
1093
|
+
try {
|
|
1094
|
+
parsed = new URL(url);
|
|
1095
|
+
} catch {
|
|
1096
|
+
throw new Error(`Invalid MCP URL from ${from}: ${JSON.stringify(url)} is not a URL. Expected an absolute http(s) URL, e.g. https://ai.levr.one/api/v1/mcp`);
|
|
1097
|
+
}
|
|
1098
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`Invalid MCP URL from ${from}: ${JSON.stringify(url)} uses "${parsed.protocol}" — only http and https are supported.`);
|
|
1099
|
+
if (!parsed.hostname) throw new Error(`Invalid MCP URL from ${from}: ${JSON.stringify(url)} has no host.`);
|
|
1100
|
+
if (parsed.username || parsed.password) throw new Error(`Invalid MCP URL from ${from}: ${JSON.stringify(url)} embeds credentials. They would be written into client config files — including project-scoped ones you are told to commit. Levr authenticates in the browser; remove the user:password@ prefix.`);
|
|
1101
|
+
}
|
|
1062
1102
|
function knownMcpUrl(apiUrl) {
|
|
1063
1103
|
try {
|
|
1064
1104
|
return KNOWN_MCP_URLS[new URL(apiUrl).host];
|
|
@@ -1268,7 +1308,15 @@ const defaultDeps = {
|
|
|
1268
1308
|
install: defaultInstall
|
|
1269
1309
|
};
|
|
1270
1310
|
async function mcpAddHandler(flags) {
|
|
1271
|
-
|
|
1311
|
+
let url;
|
|
1312
|
+
let source;
|
|
1313
|
+
try {
|
|
1314
|
+
({url, source} = resolveMcpUrl(flags.url));
|
|
1315
|
+
} catch (err) {
|
|
1316
|
+
this.logger.error(err instanceof Error ? err.message : "Could not resolve the MCP URL.");
|
|
1317
|
+
this.process.exitCode = 1;
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1272
1320
|
const clients = (flags.client ?? []).flatMap((c) => c.split(",").map((s) => s.trim()).filter(Boolean));
|
|
1273
1321
|
const options = {
|
|
1274
1322
|
all: flags.all,
|
package/dist/cli.js
CHANGED
|
@@ -172,7 +172,7 @@ Examples:
|
|
|
172
172
|
aliases: { y: "yes" }
|
|
173
173
|
},
|
|
174
174
|
loader: async () => {
|
|
175
|
-
const { mcpAddHandler } = await import("./addHandler-
|
|
175
|
+
const { mcpAddHandler } = await import("./addHandler-Brr4ndcR.js");
|
|
176
176
|
return mcpAddHandler;
|
|
177
177
|
}
|
|
178
178
|
});
|
|
@@ -551,7 +551,7 @@ Examples:
|
|
|
551
551
|
|
|
552
552
|
//#endregion
|
|
553
553
|
//#region package.json
|
|
554
|
-
var version = "0.7.
|
|
554
|
+
var version = "0.7.1";
|
|
555
555
|
|
|
556
556
|
//#endregion
|
|
557
557
|
//#region src/app.ts
|