@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
- const { url, source } = resolveMcpUrl(flags.url);
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-7QMKiy7o.js");
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.0";
554
+ var version = "0.7.1";
555
555
 
556
556
  //#endregion
557
557
  //#region src/app.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levr-one/cli",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "The command-line interface for Levr",
5
5
  "type": "module",
6
6
  "license": "MIT",