multicorn-shield 0.6.1 → 0.7.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/dist/index.cjs CHANGED
@@ -2106,7 +2106,7 @@ var MulticornShield = class {
2106
2106
  *
2107
2107
  * @example
2108
2108
  * ```ts
2109
- * const shield = new MulticornShield({ apiKey: 'mcs_your_key_here' });
2109
+ * const shield = new MulticornShield({ apiKey: 'mcs_live_abc123...' });
2110
2110
  * ```
2111
2111
  */
2112
2112
  constructor(config) {
@@ -2463,16 +2463,20 @@ function validateBaseUrl(baseUrl) {
2463
2463
  );
2464
2464
  }
2465
2465
  }
2466
+ var PLACEHOLDER_KEYS = /* @__PURE__ */ new Set(["mcs_your_key_here"]);
2467
+ var INVALID_KEY_MESSAGE = "Invalid Multicorn Shield API key. Get your key at https://app.multicorn.ai/settings";
2466
2468
  function validateApiKey(apiKey) {
2469
+ if (typeof apiKey !== "string" || apiKey.length === 0) {
2470
+ throw new Error(INVALID_KEY_MESSAGE);
2471
+ }
2467
2472
  if (!apiKey.startsWith(API_KEY_PREFIX)) {
2468
- throw new Error(
2469
- `[MulticornShield] Invalid API key format. Keys must start with "${API_KEY_PREFIX}". Find your API key in the Multicorn dashboard under Settings \u2192 API Keys.`
2470
- );
2473
+ throw new Error(INVALID_KEY_MESSAGE);
2471
2474
  }
2472
2475
  if (apiKey.length < MIN_API_KEY_LENGTH) {
2473
- throw new Error(
2474
- `[MulticornShield] API key is too short (${String(apiKey.length)} characters). Minimum length is ${String(MIN_API_KEY_LENGTH)} characters. Find your API key in the Multicorn dashboard under Settings \u2192 API Keys.`
2475
- );
2476
+ throw new Error(INVALID_KEY_MESSAGE);
2477
+ }
2478
+ if (PLACEHOLDER_KEYS.has(apiKey)) {
2479
+ throw new Error(INVALID_KEY_MESSAGE);
2476
2480
  }
2477
2481
  }
2478
2482
 
package/dist/index.d.cts CHANGED
@@ -1902,7 +1902,7 @@ declare function createMcpAdapter(config: McpAdapterConfig): McpAdapter;
1902
1902
  * @example
1903
1903
  * ```ts
1904
1904
  * const shield = new MulticornShield({
1905
- * apiKey: 'mcs_your_key_here',
1905
+ * apiKey: 'mcs_live_abc123...',
1906
1906
  * baseUrl: 'https://api.multicorn.ai',
1907
1907
  * timeout: 5000,
1908
1908
  * });
@@ -1938,7 +1938,7 @@ interface MulticornShieldConfig {
1938
1938
  * @example
1939
1939
  * ```ts
1940
1940
  * const shield = new MulticornShield({
1941
- * apiKey: 'mcs_your_key_here',
1941
+ * apiKey: 'mcs_live_abc123...',
1942
1942
  * onError: (err) => myLogger.warn(err.message),
1943
1943
  * });
1944
1944
  * ```
@@ -2052,7 +2052,7 @@ declare class MulticornShield {
2052
2052
  *
2053
2053
  * @example
2054
2054
  * ```ts
2055
- * const shield = new MulticornShield({ apiKey: 'mcs_your_key_here' });
2055
+ * const shield = new MulticornShield({ apiKey: 'mcs_live_abc123...' });
2056
2056
  * ```
2057
2057
  */
2058
2058
  constructor(config: MulticornShieldConfig);
package/dist/index.d.ts CHANGED
@@ -1902,7 +1902,7 @@ declare function createMcpAdapter(config: McpAdapterConfig): McpAdapter;
1902
1902
  * @example
1903
1903
  * ```ts
1904
1904
  * const shield = new MulticornShield({
1905
- * apiKey: 'mcs_your_key_here',
1905
+ * apiKey: 'mcs_live_abc123...',
1906
1906
  * baseUrl: 'https://api.multicorn.ai',
1907
1907
  * timeout: 5000,
1908
1908
  * });
@@ -1938,7 +1938,7 @@ interface MulticornShieldConfig {
1938
1938
  * @example
1939
1939
  * ```ts
1940
1940
  * const shield = new MulticornShield({
1941
- * apiKey: 'mcs_your_key_here',
1941
+ * apiKey: 'mcs_live_abc123...',
1942
1942
  * onError: (err) => myLogger.warn(err.message),
1943
1943
  * });
1944
1944
  * ```
@@ -2052,7 +2052,7 @@ declare class MulticornShield {
2052
2052
  *
2053
2053
  * @example
2054
2054
  * ```ts
2055
- * const shield = new MulticornShield({ apiKey: 'mcs_your_key_here' });
2055
+ * const shield = new MulticornShield({ apiKey: 'mcs_live_abc123...' });
2056
2056
  * ```
2057
2057
  */
2058
2058
  constructor(config: MulticornShieldConfig);
package/dist/index.js CHANGED
@@ -2104,7 +2104,7 @@ var MulticornShield = class {
2104
2104
  *
2105
2105
  * @example
2106
2106
  * ```ts
2107
- * const shield = new MulticornShield({ apiKey: 'mcs_your_key_here' });
2107
+ * const shield = new MulticornShield({ apiKey: 'mcs_live_abc123...' });
2108
2108
  * ```
2109
2109
  */
2110
2110
  constructor(config) {
@@ -2461,16 +2461,20 @@ function validateBaseUrl(baseUrl) {
2461
2461
  );
2462
2462
  }
2463
2463
  }
2464
+ var PLACEHOLDER_KEYS = /* @__PURE__ */ new Set(["mcs_your_key_here"]);
2465
+ var INVALID_KEY_MESSAGE = "Invalid Multicorn Shield API key. Get your key at https://app.multicorn.ai/settings";
2464
2466
  function validateApiKey(apiKey) {
2467
+ if (typeof apiKey !== "string" || apiKey.length === 0) {
2468
+ throw new Error(INVALID_KEY_MESSAGE);
2469
+ }
2465
2470
  if (!apiKey.startsWith(API_KEY_PREFIX)) {
2466
- throw new Error(
2467
- `[MulticornShield] Invalid API key format. Keys must start with "${API_KEY_PREFIX}". Find your API key in the Multicorn dashboard under Settings \u2192 API Keys.`
2468
- );
2471
+ throw new Error(INVALID_KEY_MESSAGE);
2469
2472
  }
2470
2473
  if (apiKey.length < MIN_API_KEY_LENGTH) {
2471
- throw new Error(
2472
- `[MulticornShield] API key is too short (${String(apiKey.length)} characters). Minimum length is ${String(MIN_API_KEY_LENGTH)} characters. Find your API key in the Multicorn dashboard under Settings \u2192 API Keys.`
2473
- );
2474
+ throw new Error(INVALID_KEY_MESSAGE);
2475
+ }
2476
+ if (PLACEHOLDER_KEYS.has(apiKey)) {
2477
+ throw new Error(INVALID_KEY_MESSAGE);
2474
2478
  }
2475
2479
  }
2476
2480
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { existsSync } from 'fs';
3
- import { readFile, writeFile, mkdir, unlink } from 'fs/promises';
3
+ import { mkdir, writeFile, readFile, unlink } from 'fs/promises';
4
4
  import { join } from 'path';
5
5
  import { homedir } from 'os';
6
6
  import { createInterface } from 'readline';
@@ -94,31 +94,68 @@ function collectAgentsFromConfig(cfg) {
94
94
  }
95
95
  return [];
96
96
  }
97
- async function loadConfig() {
97
+ async function parseConfigFile() {
98
98
  try {
99
99
  const raw = await readFile(CONFIG_PATH, "utf8");
100
- const parsed = JSON.parse(raw);
101
- if (!isProxyConfig(parsed)) return null;
102
- const obj = parsed;
103
- const agentNameRaw = obj["agentName"];
104
- const agentsRaw = obj["agents"];
105
- const hasNonEmptyAgents = Array.isArray(agentsRaw) && agentsRaw.length > 0 && agentsRaw.every((e) => isAgentEntry(e));
106
- const needsMigrate = typeof agentNameRaw === "string" && agentNameRaw.length > 0 && !hasNonEmptyAgents;
107
- if (!needsMigrate) {
108
- return parsed;
109
- }
110
- const platform = typeof obj["platform"] === "string" && obj["platform"].length > 0 ? obj["platform"] : "unknown";
111
- const next = { ...obj };
112
- delete next["agentName"];
113
- delete next["platform"];
114
- next["agents"] = [{ name: agentNameRaw, platform }];
115
- next["defaultAgent"] = agentNameRaw;
116
- const migrated = next;
117
- await saveConfig(migrated);
118
- return migrated;
119
- } catch {
120
- return null;
100
+ try {
101
+ return { kind: "ok", value: JSON.parse(raw) };
102
+ } catch {
103
+ return { kind: "parseError" };
104
+ }
105
+ } catch (error) {
106
+ if (isErrnoException(error) && error.code === "ENOENT") {
107
+ return { kind: "missing" };
108
+ }
109
+ const message = error instanceof Error ? error.message : String(error);
110
+ return { kind: "readError", message };
111
+ }
112
+ }
113
+ function isAllowedShieldApiBaseUrl(url) {
114
+ return url.startsWith("https://") || url.startsWith("http://localhost") || url.startsWith("http://127.0.0.1");
115
+ }
116
+ async function loadConfig() {
117
+ const result = await parseConfigFile();
118
+ if (result.kind !== "ok") return null;
119
+ const parsed = result.value;
120
+ if (!isProxyConfig(parsed)) return null;
121
+ const obj = parsed;
122
+ const agentNameRaw = obj["agentName"];
123
+ const agentsRaw = obj["agents"];
124
+ const hasNonEmptyAgents = Array.isArray(agentsRaw) && agentsRaw.length > 0 && agentsRaw.every((e) => isAgentEntry(e));
125
+ const needsMigrate = typeof agentNameRaw === "string" && agentNameRaw.length > 0 && !hasNonEmptyAgents;
126
+ if (!needsMigrate) {
127
+ return parsed;
128
+ }
129
+ const platform = typeof obj["platform"] === "string" && obj["platform"].length > 0 ? obj["platform"] : "unknown";
130
+ const next = { ...obj };
131
+ delete next["agentName"];
132
+ delete next["platform"];
133
+ next["agents"] = [{ name: agentNameRaw, platform }];
134
+ next["defaultAgent"] = agentNameRaw;
135
+ const migrated = next;
136
+ await saveConfig(migrated);
137
+ return migrated;
138
+ }
139
+ async function readBaseUrlFromConfig() {
140
+ const result = await parseConfigFile();
141
+ if (result.kind === "missing") return void 0;
142
+ if (result.kind === "readError") {
143
+ process.stderr.write(
144
+ style.yellow(`Warning: could not read base URL from config file: ${result.message}`) + "\n"
145
+ );
146
+ return void 0;
121
147
  }
148
+ if (result.kind === "parseError") {
149
+ process.stderr.write(
150
+ style.yellow("Warning: could not parse ~/.multicorn/config.json as JSON.") + "\n"
151
+ );
152
+ return void 0;
153
+ }
154
+ const parsed = result.value;
155
+ if (typeof parsed !== "object" || parsed === null) return void 0;
156
+ const u = parsed["baseUrl"];
157
+ if (typeof u !== "string" || u.length === 0) return void 0;
158
+ return u;
122
159
  }
123
160
  async function deleteAgentByName(name) {
124
161
  const config = await loadConfig();
@@ -329,11 +366,12 @@ async function isCursorConnected() {
329
366
  return false;
330
367
  }
331
368
  }
332
- var PLATFORM_LABELS = ["OpenClaw", "Claude Code", "Cursor"];
369
+ var PLATFORM_LABELS = ["OpenClaw", "Claude Code", "Cursor", "Local MCP / Other"];
333
370
  var PLATFORM_BY_SELECTION = {
334
371
  1: "openclaw",
335
372
  2: "claude-code",
336
- 3: "cursor"
373
+ 3: "cursor",
374
+ 4: "other-mcp"
337
375
  };
338
376
  var DEFAULT_AGENT_NAMES = {
339
377
  openclaw: "my-openclaw-agent",
@@ -350,17 +388,20 @@ async function promptPlatformSelection(ask) {
350
388
  await isCursorConnected()
351
389
  ];
352
390
  for (let i = 0; i < PLATFORM_LABELS.length; i++) {
353
- const marker = connectedFlags[i] ? " " + style.green("\u2713") + style.dim(" connected") : "";
391
+ const marker = i < connectedFlags.length && connectedFlags[i] ? " " + style.dim("\u25CF detected locally") : "";
354
392
  process.stderr.write(
355
393
  ` ${style.violet(String(i + 1))}. ${PLATFORM_LABELS[i] ?? ""}${marker}
356
394
  `
357
395
  );
358
396
  }
397
+ process.stderr.write(
398
+ style.dim(" Pick 4 if you want to wrap a local MCP server with multicorn-proxy --wrap.") + "\n"
399
+ );
359
400
  let selection = 0;
360
401
  while (selection === 0) {
361
- const input = await ask("Select (1-3): ");
402
+ const input = await ask("Select (1-4): ");
362
403
  const num = parseInt(input.trim(), 10);
363
- if (num >= 1 && num <= 3) {
404
+ if (num >= 1 && num <= 4) {
364
405
  selection = num;
365
406
  }
366
407
  }
@@ -490,7 +531,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
490
531
  if (platform !== "cursor") {
491
532
  process.stderr.write(
492
533
  style.dim(
493
- "Replace YOUR_SHIELD_API_KEY with your API key. Find it in Settings > API keys at https://app.multicorn.ai/settings/api-keys"
534
+ "Replace YOUR_SHIELD_API_KEY with your API key. Find it in Settings > API keys at https://app.multicorn.ai/settings#api-keys"
494
535
  ) + "\n"
495
536
  );
496
537
  }
@@ -507,7 +548,8 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
507
548
  );
508
549
  }
509
550
  }
510
- async function runInit(baseUrl = "https://api.multicorn.ai") {
551
+ var DEFAULT_SHIELD_API_BASE_URL = "https://api.multicorn.ai";
552
+ async function runInit(explicitBaseUrl) {
511
553
  if (!process.stdin.isTTY) {
512
554
  process.stderr.write(
513
555
  style.red("Error: interactive terminal required. Cannot run init with piped input.") + "\n"
@@ -522,19 +564,32 @@ async function runInit(baseUrl = "https://api.multicorn.ai") {
522
564
  process.stderr.write(style.dim("Agent governance for the AI era") + "\n\n");
523
565
  process.stderr.write(style.bold(style.violet("Multicorn Shield proxy setup")) + "\n\n");
524
566
  process.stderr.write(
525
- style.dim("Get your API key at https://app.multicorn.ai/settings/api-keys") + "\n\n"
567
+ style.dim("Get your API key at https://app.multicorn.ai/settings#api-keys") + "\n\n"
526
568
  );
527
569
  const existing = await loadConfig().catch(() => null);
528
- if (baseUrl === "https://api.multicorn.ai") {
529
- if (existing !== null && existing.baseUrl.length > 0) {
530
- baseUrl = existing.baseUrl;
570
+ let resolvedBaseUrl;
571
+ if (explicitBaseUrl !== void 0 && explicitBaseUrl.trim().length > 0) {
572
+ resolvedBaseUrl = explicitBaseUrl.trim();
573
+ } else if (existing !== null && existing.baseUrl.length > 0) {
574
+ resolvedBaseUrl = existing.baseUrl;
575
+ } else {
576
+ const fromFile = await readBaseUrlFromConfig();
577
+ if (fromFile !== void 0 && fromFile.length > 0) {
578
+ resolvedBaseUrl = fromFile;
531
579
  } else {
532
580
  const envBaseUrl = process.env["MULTICORN_BASE_URL"];
533
- if (envBaseUrl !== void 0 && envBaseUrl.length > 0) {
534
- baseUrl = envBaseUrl;
535
- }
581
+ resolvedBaseUrl = envBaseUrl !== void 0 && envBaseUrl.trim().length > 0 ? envBaseUrl.trim() : DEFAULT_SHIELD_API_BASE_URL;
536
582
  }
537
583
  }
584
+ if (!isAllowedShieldApiBaseUrl(resolvedBaseUrl)) {
585
+ process.stderr.write(
586
+ style.red(
587
+ "Base URL must use HTTPS (or http://localhost for local development). Received a non-HTTPS URL from config. Use --base-url to override."
588
+ ) + "\n"
589
+ );
590
+ rl.close();
591
+ return null;
592
+ }
538
593
  let apiKey = "";
539
594
  if (existing !== null && existing.apiKey.startsWith("mcs_") && existing.apiKey.length >= 8) {
540
595
  const masked = "mcs_..." + existing.apiKey.slice(-4);
@@ -554,7 +609,7 @@ async function runInit(baseUrl = "https://api.multicorn.ai") {
554
609
  const spinner = withSpinner("Validating key...");
555
610
  let result;
556
611
  try {
557
- result = await validateApiKey(key, baseUrl);
612
+ result = await validateApiKey(key, resolvedBaseUrl);
558
613
  } catch (error) {
559
614
  spinner.stop(false, "Validation failed");
560
615
  throw error;
@@ -566,18 +621,11 @@ async function runInit(baseUrl = "https://api.multicorn.ai") {
566
621
  spinner.stop(true, "Key validated");
567
622
  apiKey = key;
568
623
  }
569
- if (!baseUrl.startsWith("https://") && !baseUrl.startsWith("http://localhost") && !baseUrl.startsWith("http://127.0.0.1")) {
570
- process.stderr.write(
571
- style.red(`\u2717 Shield API base URL must use HTTPS. Got: ${baseUrl}`) + "\n"
572
- );
573
- rl.close();
574
- return null;
575
- }
576
624
  const configuredAgents = [];
577
625
  let currentAgents = collectAgentsFromConfig(existing);
578
626
  let lastConfig = {
579
627
  apiKey,
580
- baseUrl,
628
+ baseUrl: resolvedBaseUrl,
581
629
  ...currentAgents.length > 0 ? {
582
630
  agents: currentAgents,
583
631
  defaultAgent: existing !== null && typeof existing.defaultAgent === "string" && existing.defaultAgent.length > 0 ? existing.defaultAgent : currentAgents[currentAgents.length - 1]?.name ?? ""
@@ -588,6 +636,40 @@ async function runInit(baseUrl = "https://api.multicorn.ai") {
588
636
  const selection = await promptPlatformSelection(ask);
589
637
  const selectedPlatform = PLATFORM_BY_SELECTION[selection] ?? "cursor";
590
638
  const selectedLabel = PLATFORM_LABELS[selection - 1] ?? "Cursor";
639
+ if (selection === 4) {
640
+ const raw = existing !== null ? { ...existing } : {};
641
+ raw["apiKey"] = apiKey;
642
+ raw["baseUrl"] = resolvedBaseUrl;
643
+ delete raw["agentName"];
644
+ delete raw["platform"];
645
+ lastConfig = raw;
646
+ try {
647
+ await saveConfig(lastConfig);
648
+ process.stderr.write(
649
+ style.green("\u2713") + ` Config saved to ${style.cyan(CONFIG_PATH)}
650
+ `
651
+ );
652
+ process.stderr.write(
653
+ "\n" + style.bold("Try it:") + " " + style.cyan(
654
+ "npx multicorn-proxy --wrap npx @modelcontextprotocol/server-filesystem /tmp"
655
+ ) + "\n"
656
+ );
657
+ } catch (error) {
658
+ const detail = error instanceof Error ? error.message : String(error);
659
+ process.stderr.write(style.red(`Failed to save config: ${detail}`) + "\n");
660
+ }
661
+ configuredAgents.push({
662
+ selection,
663
+ platform: selectedPlatform,
664
+ platformLabel: selectedLabel,
665
+ agentName: ""
666
+ });
667
+ const another2 = await ask("\nConnect another agent? (Y/n) ");
668
+ if (another2.trim().toLowerCase() === "n") {
669
+ configuring = false;
670
+ }
671
+ continue;
672
+ }
591
673
  const existingForPlatform = currentAgents.find((a) => a.platform === selectedPlatform);
592
674
  if (existingForPlatform !== void 0) {
593
675
  process.stderr.write(
@@ -657,7 +739,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
657
739
  }
658
740
  const spinner = withSpinner("Updating OpenClaw config...");
659
741
  try {
660
- const result = await updateOpenClawConfigIfPresent(apiKey, baseUrl, agentName);
742
+ const result = await updateOpenClawConfigIfPresent(apiKey, resolvedBaseUrl, agentName);
661
743
  if (result === "not-found") {
662
744
  spinner.stop(false, "OpenClaw config disappeared unexpectedly.");
663
745
  rl.close();
@@ -712,7 +794,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
712
794
  const spinner = withSpinner("Creating proxy config...");
713
795
  try {
714
796
  proxyUrl = await createProxyConfig(
715
- baseUrl,
797
+ resolvedBaseUrl,
716
798
  apiKey,
717
799
  agentName,
718
800
  targetUrl,
@@ -750,7 +832,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
750
832
  currentAgents.push({ name: agentName, platform: selectedPlatform });
751
833
  const raw = existing !== null ? { ...existing } : {};
752
834
  raw["apiKey"] = apiKey;
753
- raw["baseUrl"] = baseUrl;
835
+ raw["baseUrl"] = resolvedBaseUrl;
754
836
  raw["agents"] = currentAgents;
755
837
  raw["defaultAgent"] = agentName;
756
838
  delete raw["agentName"];
@@ -776,14 +858,15 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
776
858
  if (configuredAgents.length > 0) {
777
859
  process.stderr.write("\n" + style.bold(style.violet("Setup complete")) + "\n\n");
778
860
  for (const agent of configuredAgents) {
861
+ const namePart = agent.agentName.length > 0 ? ` - ${style.cyan(agent.agentName)}` : "";
862
+ const urlPart = agent.proxyUrl != null ? ` ${style.dim(`(${agent.proxyUrl})`)}` : "";
779
863
  process.stderr.write(
780
- ` ${style.green("\u2713")} ${agent.platformLabel} - ${style.cyan(agent.agentName)}${agent.proxyUrl != null ? ` ${style.dim(`(${agent.proxyUrl})`)}` : ""}
864
+ ` ${style.green("\u2713")} ${agent.platformLabel}${namePart}${urlPart}
781
865
  `
782
866
  );
783
867
  }
784
868
  process.stderr.write("\n");
785
869
  const configuredPlatforms = new Set(configuredAgents.map((a) => a.platform));
786
- process.stderr.write("\n" + style.bold(style.violet("Next steps")) + "\n");
787
870
  const blocks = [];
788
871
  if (configuredPlatforms.has("openclaw")) {
789
872
  blocks.push(
@@ -805,12 +888,10 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
805
888
  "\n" + style.bold("To complete your Cursor setup:") + "\n \u2192 Restart Cursor to pick up MCP config changes\n"
806
889
  );
807
890
  }
808
- if (configuredPlatforms.has("other-mcp")) {
809
- blocks.push(
810
- "\n" + style.bold("To complete your Other MCP Agent setup:") + "\n \u2192 Start your agent with: " + style.cyan("npx multicorn-proxy --wrap <your-server> --agent-name <name>") + "\n"
811
- );
891
+ if (blocks.length > 0) {
892
+ process.stderr.write("\n" + style.bold(style.violet("Next steps")) + "\n");
893
+ process.stderr.write(blocks.join("") + "\n");
812
894
  }
813
- process.stderr.write(blocks.join("") + "\n");
814
895
  }
815
896
  return lastConfig;
816
897
  }
@@ -1807,6 +1888,15 @@ function createProxyServer(config) {
1807
1888
  agentId = agentRecord.id;
1808
1889
  grantedScopes = agentRecord.scopes;
1809
1890
  authInvalid = agentRecord.authInvalid === true;
1891
+ if (authInvalid) {
1892
+ config.logger.error("API key rejected by the Multicorn service.", {
1893
+ agent: config.agentName
1894
+ });
1895
+ process.stderr.write(
1896
+ "\nError: API key was rejected by the Multicorn service.\nCheck your key at https://app.multicorn.ai/settings#api-keys or run `npx multicorn-proxy init` to reconfigure.\n\n"
1897
+ );
1898
+ throw new Error("API key was rejected by the Multicorn service.");
1899
+ }
1810
1900
  config.logger.info("Agent resolved.", {
1811
1901
  agent: config.agentName,
1812
1902
  id: agentId,
@@ -1920,10 +2010,11 @@ function parseArgs(argv) {
1920
2010
  let wrapCommand = "";
1921
2011
  let wrapArgs = [];
1922
2012
  let logLevel = "info";
1923
- let baseUrl = "https://api.multicorn.ai";
2013
+ let baseUrl = void 0;
1924
2014
  let dashboardUrl = "";
1925
2015
  let agentName = "";
1926
2016
  let deleteAgentName = "";
2017
+ let apiKey = void 0;
1927
2018
  for (let i = 0; i < args.length; i++) {
1928
2019
  const arg = args[i];
1929
2020
  if (arg === "init") {
@@ -1942,46 +2033,56 @@ function parseArgs(argv) {
1942
2033
  i++;
1943
2034
  } else if (arg === "--wrap") {
1944
2035
  subcommand = "wrap";
1945
- const next = args[i + 1];
1946
- if (next === void 0 || next.startsWith("-")) {
1947
- process.stderr.write("Error: --wrap requires a command to run.\n");
1948
- process.stderr.write("Example: npx multicorn-proxy --wrap my-mcp-server\n");
1949
- process.exit(1);
1950
- }
1951
- wrapCommand = next;
1952
- wrapArgs = args.slice(i + 2);
1953
- const cleaned = [];
1954
- for (let j = 0; j < wrapArgs.length; j++) {
1955
- const token = wrapArgs[j];
2036
+ const tail = args.slice(i + 1);
2037
+ const remaining = [];
2038
+ for (let j = 0; j < tail.length; j++) {
2039
+ const token = tail[j];
2040
+ if (token === void 0) continue;
2041
+ if (remaining.length > 0) {
2042
+ remaining.push(token);
2043
+ continue;
2044
+ }
1956
2045
  if (token === "--agent-name") {
1957
- const value = wrapArgs[j + 1];
2046
+ const value = tail[j + 1];
1958
2047
  if (value !== void 0) {
1959
2048
  agentName = value;
1960
2049
  j++;
1961
2050
  }
1962
2051
  } else if (token === "--log-level") {
1963
- const value = wrapArgs[j + 1];
2052
+ const value = tail[j + 1];
1964
2053
  if (value !== void 0 && isValidLogLevel(value)) {
1965
2054
  logLevel = value;
1966
2055
  j++;
1967
2056
  }
1968
2057
  } else if (token === "--base-url") {
1969
- const value = wrapArgs[j + 1];
2058
+ const value = tail[j + 1];
1970
2059
  if (value !== void 0) {
1971
2060
  baseUrl = value;
1972
2061
  j++;
1973
2062
  }
1974
2063
  } else if (token === "--dashboard-url") {
1975
- const value = wrapArgs[j + 1];
2064
+ const value = tail[j + 1];
1976
2065
  if (value !== void 0) {
1977
2066
  dashboardUrl = value;
1978
2067
  j++;
1979
2068
  }
1980
- } else if (token !== void 0) {
1981
- cleaned.push(token);
2069
+ } else if (token === "--api-key") {
2070
+ const value = tail[j + 1];
2071
+ if (value !== void 0) {
2072
+ apiKey = value;
2073
+ j++;
2074
+ }
2075
+ } else {
2076
+ remaining.push(token);
1982
2077
  }
1983
2078
  }
1984
- wrapArgs = cleaned;
2079
+ if (remaining.length === 0) {
2080
+ process.stderr.write("Error: --wrap requires a command to run.\n");
2081
+ process.stderr.write("Example: npx multicorn-proxy --wrap my-mcp-server\n");
2082
+ process.exit(1);
2083
+ }
2084
+ wrapCommand = remaining[0] ?? "";
2085
+ wrapArgs = remaining.slice(1);
1985
2086
  break;
1986
2087
  } else if (arg === "--log-level") {
1987
2088
  const next = args[i + 1];
@@ -2007,6 +2108,12 @@ function parseArgs(argv) {
2007
2108
  agentName = next;
2008
2109
  i++;
2009
2110
  }
2111
+ } else if (arg === "--api-key") {
2112
+ const next = args[i + 1];
2113
+ if (next !== void 0) {
2114
+ apiKey = next;
2115
+ i++;
2116
+ }
2010
2117
  }
2011
2118
  }
2012
2119
  return {
@@ -2017,7 +2124,8 @@ function parseArgs(argv) {
2017
2124
  baseUrl,
2018
2125
  dashboardUrl,
2019
2126
  agentName,
2020
- deleteAgentName
2127
+ deleteAgentName,
2128
+ apiKey
2021
2129
  };
2022
2130
  }
2023
2131
  function printHelp() {
@@ -2040,6 +2148,7 @@ function printHelp() {
2040
2148
  " Shield's permission layer.",
2041
2149
  "",
2042
2150
  "Options:",
2151
+ " --api-key <key> Multicorn API key (overrides MULTICORN_API_KEY env var and config file)",
2043
2152
  " --log-level <level> Log level: debug | info | warn | error (default: info)",
2044
2153
  " --base-url <url> Multicorn API base URL (default: https://api.multicorn.ai)",
2045
2154
  " --dashboard-url <url> Dashboard URL for consent page (default: derived from --base-url)",
@@ -2098,23 +2207,25 @@ async function main() {
2098
2207
  `);
2099
2208
  return;
2100
2209
  }
2101
- if (!cli.baseUrl.startsWith("https://") && !cli.baseUrl.startsWith("http://localhost") && !cli.baseUrl.startsWith("http://127.0.0.1")) {
2102
- process.stderr.write(
2103
- `Error: --base-url must use HTTPS. Received: "${cli.baseUrl}"
2104
- Use https:// or http://localhost for local development.
2105
- `
2106
- );
2107
- process.exit(1);
2210
+ if (cli.baseUrl !== void 0 && cli.baseUrl.length > 0) {
2211
+ if (!isAllowedShieldApiBaseUrl(cli.baseUrl)) {
2212
+ process.stderr.write(
2213
+ "Error: --base-url must use HTTPS. Received a non-HTTPS URL.\nUse https:// or http://localhost for local development.\n"
2214
+ );
2215
+ process.exit(1);
2216
+ }
2108
2217
  }
2109
- const config = await loadConfig();
2110
- if (config === null) {
2111
- process.stderr.write(
2112
- "No config found. Run `npx multicorn-proxy init` to set up your API key.\n"
2113
- );
2114
- process.exit(1);
2218
+ const config = await resolveWrapConfig(cli, logger);
2219
+ const finalBaseUrl = cli.baseUrl !== void 0 && cli.baseUrl.length > 0 ? cli.baseUrl : config.baseUrl;
2220
+ if (cli.baseUrl === void 0 || cli.baseUrl.length === 0) {
2221
+ if (!isAllowedShieldApiBaseUrl(finalBaseUrl)) {
2222
+ process.stderr.write(
2223
+ "Error: --base-url must use HTTPS. Received a non-HTTPS URL.\nUse https:// or http://localhost for local development.\n"
2224
+ );
2225
+ process.exit(1);
2226
+ }
2115
2227
  }
2116
2228
  const agentName = resolveWrapAgentName(cli, config);
2117
- const finalBaseUrl = cli.baseUrl !== "https://api.multicorn.ai" ? cli.baseUrl : config.baseUrl;
2118
2229
  const finalDashboardUrl = cli.dashboardUrl !== "" ? cli.dashboardUrl : deriveDashboardUrl(finalBaseUrl);
2119
2230
  const legacyPlatform = config.platform;
2120
2231
  const platformForServer = typeof legacyPlatform === "string" && legacyPlatform.length > 0 ? legacyPlatform : "other-mcp";
@@ -2141,6 +2252,31 @@ Use https:// or http://localhost for local development.
2141
2252
  });
2142
2253
  await proxy.start();
2143
2254
  }
2255
+ async function resolveWrapConfig(cli, logger) {
2256
+ if (cli.apiKey !== void 0 && cli.apiKey.length > 0) {
2257
+ logger.debug("Using API key from --api-key flag.");
2258
+ return {
2259
+ apiKey: cli.apiKey,
2260
+ baseUrl: cli.baseUrl ?? DEFAULT_SHIELD_API_BASE_URL
2261
+ };
2262
+ }
2263
+ const envKey = process.env["MULTICORN_API_KEY"];
2264
+ if (typeof envKey === "string" && envKey.length > 0) {
2265
+ logger.debug("Using API key from MULTICORN_API_KEY environment variable.");
2266
+ return {
2267
+ apiKey: envKey,
2268
+ baseUrl: cli.baseUrl ?? DEFAULT_SHIELD_API_BASE_URL
2269
+ };
2270
+ }
2271
+ const config = await loadConfig();
2272
+ if (config !== null) {
2273
+ return config;
2274
+ }
2275
+ process.stderr.write(
2276
+ "No API key found. Provide one via the --api-key flag, the MULTICORN_API_KEY environment variable, or run `npx multicorn-proxy init` to set up a config file.\n"
2277
+ );
2278
+ process.exit(1);
2279
+ }
2144
2280
  function resolveWrapAgentName(cli, config) {
2145
2281
  if (cli.agentName.length > 0) {
2146
2282
  return cli.agentName;
@@ -2165,9 +2301,14 @@ function deriveAgentName(command) {
2165
2301
  const base = command.split("/").pop() ?? command;
2166
2302
  return base.replace(/\.[cm]?[jt]s$/, "");
2167
2303
  }
2168
- main().catch((error) => {
2169
- const message = error instanceof Error ? error.message : String(error);
2170
- process.stderr.write(`Fatal error: ${message}
2304
+ var isDirectRun = process.argv[1] !== void 0 && (import.meta.url.endsWith(process.argv[1]) || import.meta.url === `file://${process.argv[1]}` || import.meta.url.endsWith("/multicorn-proxy.js") || import.meta.url.endsWith("/multicorn-proxy.ts"));
2305
+ if (isDirectRun && process.env["VITEST"] === void 0) {
2306
+ main().catch((error) => {
2307
+ const message = error instanceof Error ? error.message : String(error);
2308
+ process.stderr.write(`Fatal error: ${message}
2171
2309
  `);
2172
- process.exit(1);
2173
- });
2310
+ process.exit(1);
2311
+ });
2312
+ }
2313
+
2314
+ export { parseArgs, resolveWrapConfig };
@@ -22358,7 +22358,7 @@ async function writeExtensionBackup(claudeDesktopConfigPath, mcpServers) {
22358
22358
 
22359
22359
  // package.json
22360
22360
  var package_default = {
22361
- version: "0.6.1"};
22361
+ version: "0.7.0"};
22362
22362
 
22363
22363
  // src/package-meta.ts
22364
22364
  var PACKAGE_VERSION = package_default.version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multicorn-shield",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "The control layer for AI agents: permissions, consent, spending limits, and audit logging.",
5
5
  "license": "MIT",
6
6
  "type": "module",