multicorn-shield 0.6.2 → 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';
@@ -366,11 +366,12 @@ async function isCursorConnected() {
366
366
  return false;
367
367
  }
368
368
  }
369
- var PLATFORM_LABELS = ["OpenClaw", "Claude Code", "Cursor"];
369
+ var PLATFORM_LABELS = ["OpenClaw", "Claude Code", "Cursor", "Local MCP / Other"];
370
370
  var PLATFORM_BY_SELECTION = {
371
371
  1: "openclaw",
372
372
  2: "claude-code",
373
- 3: "cursor"
373
+ 3: "cursor",
374
+ 4: "other-mcp"
374
375
  };
375
376
  var DEFAULT_AGENT_NAMES = {
376
377
  openclaw: "my-openclaw-agent",
@@ -387,17 +388,20 @@ async function promptPlatformSelection(ask) {
387
388
  await isCursorConnected()
388
389
  ];
389
390
  for (let i = 0; i < PLATFORM_LABELS.length; i++) {
390
- const marker = connectedFlags[i] ? " " + style.green("\u2713") + style.dim(" connected") : "";
391
+ const marker = i < connectedFlags.length && connectedFlags[i] ? " " + style.dim("\u25CF detected locally") : "";
391
392
  process.stderr.write(
392
393
  ` ${style.violet(String(i + 1))}. ${PLATFORM_LABELS[i] ?? ""}${marker}
393
394
  `
394
395
  );
395
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
+ );
396
400
  let selection = 0;
397
401
  while (selection === 0) {
398
- const input = await ask("Select (1-3): ");
402
+ const input = await ask("Select (1-4): ");
399
403
  const num = parseInt(input.trim(), 10);
400
- if (num >= 1 && num <= 3) {
404
+ if (num >= 1 && num <= 4) {
401
405
  selection = num;
402
406
  }
403
407
  }
@@ -527,7 +531,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
527
531
  if (platform !== "cursor") {
528
532
  process.stderr.write(
529
533
  style.dim(
530
- "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"
531
535
  ) + "\n"
532
536
  );
533
537
  }
@@ -560,7 +564,7 @@ async function runInit(explicitBaseUrl) {
560
564
  process.stderr.write(style.dim("Agent governance for the AI era") + "\n\n");
561
565
  process.stderr.write(style.bold(style.violet("Multicorn Shield proxy setup")) + "\n\n");
562
566
  process.stderr.write(
563
- 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"
564
568
  );
565
569
  const existing = await loadConfig().catch(() => null);
566
570
  let resolvedBaseUrl;
@@ -632,6 +636,40 @@ async function runInit(explicitBaseUrl) {
632
636
  const selection = await promptPlatformSelection(ask);
633
637
  const selectedPlatform = PLATFORM_BY_SELECTION[selection] ?? "cursor";
634
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
+ }
635
673
  const existingForPlatform = currentAgents.find((a) => a.platform === selectedPlatform);
636
674
  if (existingForPlatform !== void 0) {
637
675
  process.stderr.write(
@@ -820,14 +858,15 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
820
858
  if (configuredAgents.length > 0) {
821
859
  process.stderr.write("\n" + style.bold(style.violet("Setup complete")) + "\n\n");
822
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})`)}` : "";
823
863
  process.stderr.write(
824
- ` ${style.green("\u2713")} ${agent.platformLabel} - ${style.cyan(agent.agentName)}${agent.proxyUrl != null ? ` ${style.dim(`(${agent.proxyUrl})`)}` : ""}
864
+ ` ${style.green("\u2713")} ${agent.platformLabel}${namePart}${urlPart}
825
865
  `
826
866
  );
827
867
  }
828
868
  process.stderr.write("\n");
829
869
  const configuredPlatforms = new Set(configuredAgents.map((a) => a.platform));
830
- process.stderr.write("\n" + style.bold(style.violet("Next steps")) + "\n");
831
870
  const blocks = [];
832
871
  if (configuredPlatforms.has("openclaw")) {
833
872
  blocks.push(
@@ -849,12 +888,10 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
849
888
  "\n" + style.bold("To complete your Cursor setup:") + "\n \u2192 Restart Cursor to pick up MCP config changes\n"
850
889
  );
851
890
  }
852
- if (configuredPlatforms.has("other-mcp")) {
853
- blocks.push(
854
- "\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"
855
- );
891
+ if (blocks.length > 0) {
892
+ process.stderr.write("\n" + style.bold(style.violet("Next steps")) + "\n");
893
+ process.stderr.write(blocks.join("") + "\n");
856
894
  }
857
- process.stderr.write(blocks.join("") + "\n");
858
895
  }
859
896
  return lastConfig;
860
897
  }
@@ -1851,6 +1888,15 @@ function createProxyServer(config) {
1851
1888
  agentId = agentRecord.id;
1852
1889
  grantedScopes = agentRecord.scopes;
1853
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
+ }
1854
1900
  config.logger.info("Agent resolved.", {
1855
1901
  agent: config.agentName,
1856
1902
  id: agentId,
@@ -1968,6 +2014,7 @@ function parseArgs(argv) {
1968
2014
  let dashboardUrl = "";
1969
2015
  let agentName = "";
1970
2016
  let deleteAgentName = "";
2017
+ let apiKey = void 0;
1971
2018
  for (let i = 0; i < args.length; i++) {
1972
2019
  const arg = args[i];
1973
2020
  if (arg === "init") {
@@ -1986,46 +2033,56 @@ function parseArgs(argv) {
1986
2033
  i++;
1987
2034
  } else if (arg === "--wrap") {
1988
2035
  subcommand = "wrap";
1989
- const next = args[i + 1];
1990
- if (next === void 0 || next.startsWith("-")) {
1991
- process.stderr.write("Error: --wrap requires a command to run.\n");
1992
- process.stderr.write("Example: npx multicorn-proxy --wrap my-mcp-server\n");
1993
- process.exit(1);
1994
- }
1995
- wrapCommand = next;
1996
- wrapArgs = args.slice(i + 2);
1997
- const cleaned = [];
1998
- for (let j = 0; j < wrapArgs.length; j++) {
1999
- 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
+ }
2000
2045
  if (token === "--agent-name") {
2001
- const value = wrapArgs[j + 1];
2046
+ const value = tail[j + 1];
2002
2047
  if (value !== void 0) {
2003
2048
  agentName = value;
2004
2049
  j++;
2005
2050
  }
2006
2051
  } else if (token === "--log-level") {
2007
- const value = wrapArgs[j + 1];
2052
+ const value = tail[j + 1];
2008
2053
  if (value !== void 0 && isValidLogLevel(value)) {
2009
2054
  logLevel = value;
2010
2055
  j++;
2011
2056
  }
2012
2057
  } else if (token === "--base-url") {
2013
- const value = wrapArgs[j + 1];
2058
+ const value = tail[j + 1];
2014
2059
  if (value !== void 0) {
2015
2060
  baseUrl = value;
2016
2061
  j++;
2017
2062
  }
2018
2063
  } else if (token === "--dashboard-url") {
2019
- const value = wrapArgs[j + 1];
2064
+ const value = tail[j + 1];
2020
2065
  if (value !== void 0) {
2021
2066
  dashboardUrl = value;
2022
2067
  j++;
2023
2068
  }
2024
- } else if (token !== void 0) {
2025
- 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);
2026
2077
  }
2027
2078
  }
2028
- 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);
2029
2086
  break;
2030
2087
  } else if (arg === "--log-level") {
2031
2088
  const next = args[i + 1];
@@ -2051,6 +2108,12 @@ function parseArgs(argv) {
2051
2108
  agentName = next;
2052
2109
  i++;
2053
2110
  }
2111
+ } else if (arg === "--api-key") {
2112
+ const next = args[i + 1];
2113
+ if (next !== void 0) {
2114
+ apiKey = next;
2115
+ i++;
2116
+ }
2054
2117
  }
2055
2118
  }
2056
2119
  return {
@@ -2061,7 +2124,8 @@ function parseArgs(argv) {
2061
2124
  baseUrl,
2062
2125
  dashboardUrl,
2063
2126
  agentName,
2064
- deleteAgentName
2127
+ deleteAgentName,
2128
+ apiKey
2065
2129
  };
2066
2130
  }
2067
2131
  function printHelp() {
@@ -2084,6 +2148,7 @@ function printHelp() {
2084
2148
  " Shield's permission layer.",
2085
2149
  "",
2086
2150
  "Options:",
2151
+ " --api-key <key> Multicorn API key (overrides MULTICORN_API_KEY env var and config file)",
2087
2152
  " --log-level <level> Log level: debug | info | warn | error (default: info)",
2088
2153
  " --base-url <url> Multicorn API base URL (default: https://api.multicorn.ai)",
2089
2154
  " --dashboard-url <url> Dashboard URL for consent page (default: derived from --base-url)",
@@ -2150,13 +2215,7 @@ async function main() {
2150
2215
  process.exit(1);
2151
2216
  }
2152
2217
  }
2153
- const config = await loadConfig();
2154
- if (config === null) {
2155
- process.stderr.write(
2156
- "No config found. Run `npx multicorn-proxy init` to set up your API key.\n"
2157
- );
2158
- process.exit(1);
2159
- }
2218
+ const config = await resolveWrapConfig(cli, logger);
2160
2219
  const finalBaseUrl = cli.baseUrl !== void 0 && cli.baseUrl.length > 0 ? cli.baseUrl : config.baseUrl;
2161
2220
  if (cli.baseUrl === void 0 || cli.baseUrl.length === 0) {
2162
2221
  if (!isAllowedShieldApiBaseUrl(finalBaseUrl)) {
@@ -2193,6 +2252,31 @@ async function main() {
2193
2252
  });
2194
2253
  await proxy.start();
2195
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
+ }
2196
2280
  function resolveWrapAgentName(cli, config) {
2197
2281
  if (cli.agentName.length > 0) {
2198
2282
  return cli.agentName;
@@ -2217,9 +2301,14 @@ function deriveAgentName(command) {
2217
2301
  const base = command.split("/").pop() ?? command;
2218
2302
  return base.replace(/\.[cm]?[jt]s$/, "");
2219
2303
  }
2220
- main().catch((error) => {
2221
- const message = error instanceof Error ? error.message : String(error);
2222
- 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}
2223
2309
  `);
2224
- process.exit(1);
2225
- });
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.2"};
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.2",
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",