multicorn-shield 1.3.5 → 1.4.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/CHANGELOG.md CHANGED
@@ -9,6 +9,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  - Bump `version` in `package.json` before publishing to npm.
11
11
 
12
+ ## [1.4.0] - 2026-05-08
13
+
14
+ ### Added
15
+
16
+ - Hosted proxy wizard now prompts for upstream MCP server authentication (header name and value) when the target server requires credentials
17
+ - Helpful examples shown during hosted proxy setup: common MCP server URLs (GitHub, Supabase, Atlassian, Stripe) with links to where to find tokens
18
+ - Upstream auth headers are stored encrypted and forwarded by the proxy to the target MCP server on every request
19
+
20
+ ### Changed
21
+
22
+ - Target MCP server URL prompt now shows common examples instead of a generic placeholder
23
+ - "govern" replaced with "control" in all user-facing copy
24
+ - "upstream auth" replaced with "server credentials" in all user-facing copy
25
+
26
+ ### Fixed
27
+
28
+ - Hosted proxy connections to MCP servers requiring authentication (e.g. GitHub, Supabase) now work end-to-end - previously the proxy forwarded requests without credentials
29
+
30
+ ## [1.3.6] - 2026-05-08
31
+
32
+ ### Fixed
33
+
34
+ - Hosted proxy URLs now embed the API key as a query parameter fallback for MCP clients that don't send static Authorization headers (fixes Cursor, Claude Desktop, and other clients that ignore the headers config during discovery)
35
+
12
36
  ## [1.3.5] - 2026-05-08
13
37
 
14
38
  ### Fixed
@@ -1500,8 +1500,8 @@ async function promptProxyConfig(ask, agentName) {
1500
1500
  while (targetUrl.length === 0) {
1501
1501
  process.stderr.write(
1502
1502
  "\n" + style.bold("Target MCP server URL:") + "\n" + style.dim(
1503
- "The URL of the MCP server you want Shield to protect. Example: https://your-server.example.com/mcp"
1504
- ) + "\n"
1503
+ "This is the URL of the MCP server you want Shield to control. Common examples:"
1504
+ ) + "\n" + style.dim(" GitHub: https://api.githubcopilot.com/mcp/") + "\n" + style.dim(" Supabase: https://mcp.supabase.com/sse") + "\n" + style.dim(" Atlassian: https://mcp.atlassian.com/v1/sse") + "\n" + style.dim(" Stripe: https://mcp.stripe.com/v1/sse") + "\n" + style.dim("Check your MCP server's documentation for the correct URL.") + "\n"
1505
1505
  );
1506
1506
  const input = await ask("URL: ");
1507
1507
  if (input.trim().length === 0) {
@@ -1521,10 +1521,44 @@ async function promptProxyConfig(ask, agentName) {
1521
1521
  targetUrl = input.trim();
1522
1522
  }
1523
1523
  const shortName = normalizeAgentName(agentName) || "shield-mcp";
1524
- return { targetUrl, shortName };
1524
+ process.stderr.write(
1525
+ "\n" + style.bold("Does this MCP server require authentication?") + "\n" + style.dim(
1526
+ "Most MCP servers need a token or API key. Check the server's docs for how to get one:"
1527
+ ) + "\n" + style.dim(" GitHub: Settings > Developer Settings > Personal Access Tokens") + "\n" + style.dim(
1528
+ " Supabase: Project Settings > API > anon or scoped key (service role bypasses RLS; avoid for most MCP)"
1529
+ ) + "\n" + style.dim(" Atlassian: id.atlassian.com > API Tokens") + "\n" + style.dim(" Stripe: Dashboard > Developers > API Keys") + "\n"
1530
+ );
1531
+ const authReply = await ask("(y/N): ");
1532
+ const authNorm = authReply.trim().toLowerCase();
1533
+ const wantsAuth = authNorm === "y" || authNorm === "yes";
1534
+ let upstreamHeaders;
1535
+ if (wantsAuth) {
1536
+ process.stderr.write(
1537
+ "\n" + style.bold("Enter the Authorization header value.") + "\n" + style.dim(" For Bearer tokens: Bearer ghp_xxxxxxxxxxxx") + "\n" + style.dim(" For API keys: Bearer sk-xxxxxxxxxxxx") + "\n"
1538
+ );
1539
+ const headerVal = await ask("Value: ");
1540
+ const trimmed = headerVal.trim();
1541
+ if (trimmed.length > 0) {
1542
+ upstreamHeaders = { Authorization: trimmed };
1543
+ }
1544
+ }
1545
+ return {
1546
+ targetUrl,
1547
+ shortName,
1548
+ ...upstreamHeaders !== void 0 ? { upstreamHeaders } : {}
1549
+ };
1525
1550
  }
1526
- async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverName, platform) {
1551
+ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverName, platform, upstreamHeaders) {
1527
1552
  let response;
1553
+ const body = {
1554
+ server_name: serverName,
1555
+ target_url: targetUrl,
1556
+ platform,
1557
+ agent_name: agentName
1558
+ };
1559
+ if (upstreamHeaders !== void 0 && Object.keys(upstreamHeaders).length > 0) {
1560
+ body["upstream_headers"] = upstreamHeaders;
1561
+ }
1528
1562
  try {
1529
1563
  response = await fetch(`${baseUrl}/api/v1/proxy/config`, {
1530
1564
  method: "POST",
@@ -1532,12 +1566,7 @@ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverNa
1532
1566
  "Content-Type": "application/json",
1533
1567
  "X-Multicorn-Key": apiKey
1534
1568
  },
1535
- body: JSON.stringify({
1536
- server_name: serverName,
1537
- target_url: targetUrl,
1538
- platform,
1539
- agent_name: agentName
1540
- }),
1569
+ body: JSON.stringify(body),
1541
1570
  signal: AbortSignal.timeout(1e4)
1542
1571
  });
1543
1572
  } catch (error) {
@@ -1564,6 +1593,41 @@ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverNa
1564
1593
  const data = envelope["data"];
1565
1594
  return typeof data?.["proxy_url"] === "string" ? data["proxy_url"] : "";
1566
1595
  }
1596
+ function shouldEmbedKeyInHostedProxyUrl(platform) {
1597
+ return HOSTED_PROXY_PLATFORMS_WITH_URL_KEY.has(platform);
1598
+ }
1599
+ function hostedProxyUrlWithKeyParam(proxyUrl, apiKey) {
1600
+ if (apiKey.length === 0) {
1601
+ process.stderr.write(
1602
+ style.yellow("\u26A0") + " Could not add key to proxy URL: API key is empty; using URL without key query parameter.\n"
1603
+ );
1604
+ return proxyUrl;
1605
+ }
1606
+ try {
1607
+ const u = new URL(proxyUrl);
1608
+ u.searchParams.set("key", apiKey);
1609
+ return u.toString();
1610
+ } catch (err) {
1611
+ const detail = err instanceof Error ? err.message : String(err);
1612
+ process.stderr.write(
1613
+ style.yellow("\u26A0") + " Could not parse proxy URL to append key query parameter; using URL unchanged. " + style.dim(detail) + "\n"
1614
+ );
1615
+ return proxyUrl;
1616
+ }
1617
+ }
1618
+ function formatHostedProxyUrlForStderr(platform, proxyUrl, apiKey) {
1619
+ if (!shouldEmbedKeyInHostedProxyUrl(platform) || apiKey.length === 0) {
1620
+ return proxyUrl;
1621
+ }
1622
+ try {
1623
+ const u = new URL(proxyUrl);
1624
+ const redactedLabel = apiKey.length <= 4 ? "****" : `mcs_...${apiKey.slice(-4)}`;
1625
+ u.searchParams.set("key", redactedLabel);
1626
+ return u.toString();
1627
+ } catch {
1628
+ return proxyUrl;
1629
+ }
1630
+ }
1567
1631
  function writeMcpAddedLine(shortName, filePath) {
1568
1632
  process.stderr.write(
1569
1633
  style.green("\u2713") + ' MCP server "' + shortName + '" added to ' + style.cyan(filePath) + "\n"
@@ -1713,8 +1777,9 @@ function printHostedProxyPostWriteHints(platform, shortName) {
1713
1777
  }
1714
1778
  async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey, workspacePath) {
1715
1779
  const authHeader = `Bearer ${apiKey}`;
1780
+ const proxyUrlWithKeyWhenNeeded = shouldEmbedKeyInHostedProxyUrl(platform) ? hostedProxyUrlWithKeyParam(proxyUrl, apiKey) : proxyUrl;
1716
1781
  if (platform === "gemini-cli") {
1717
- await mergeGeminiHostedMcpServersIntoSettings(shortName, proxyUrl, apiKey);
1782
+ await mergeGeminiHostedMcpServersIntoSettings(shortName, proxyUrlWithKeyWhenNeeded, apiKey);
1718
1783
  process.stderr.write(
1719
1784
  style.dim(
1720
1785
  "For project-specific config, copy the mcpServers entry into .gemini/settings.json in your project root. Restart Gemini CLI if it is already running."
@@ -1739,20 +1804,24 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
1739
1804
  let result = "parse-error";
1740
1805
  if (platform === "cursor") {
1741
1806
  result = await mergeMcpServersObjectStyle(getCursorMcpJsonPath(), shortName, {
1742
- url: proxyUrl,
1807
+ url: proxyUrlWithKeyWhenNeeded,
1743
1808
  headers: { Authorization: authHeader }
1744
1809
  });
1745
1810
  if (result === "parse-error") {
1746
1811
  printHostedProxyJsonParseWarning(getCursorMcpJsonPath());
1747
1812
  }
1748
1813
  } else if (platform === "claude-desktop") {
1749
- result = await mergeClaudeDesktopHostedMcpRemote(shortName, proxyUrl, apiKey);
1814
+ result = await mergeClaudeDesktopHostedMcpRemote(
1815
+ shortName,
1816
+ proxyUrlWithKeyWhenNeeded,
1817
+ apiKey
1818
+ );
1750
1819
  if (result === "parse-error") {
1751
1820
  printHostedProxyJsonParseWarning(getClaudeDesktopConfigPath());
1752
1821
  }
1753
1822
  } else if (platform === "windsurf") {
1754
1823
  result = await mergeMcpServersObjectStyle(getWindsurfMcpConfigPath(), shortName, {
1755
- serverUrl: proxyUrl,
1824
+ serverUrl: proxyUrlWithKeyWhenNeeded,
1756
1825
  headers: { Authorization: authHeader }
1757
1826
  });
1758
1827
  if (result === "parse-error") {
@@ -1760,25 +1829,30 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
1760
1829
  }
1761
1830
  } else if (platform === "cline") {
1762
1831
  result = await mergeMcpServersObjectStyle(getClineMcpSettingsPath(), shortName, {
1763
- url: proxyUrl,
1832
+ url: proxyUrlWithKeyWhenNeeded,
1764
1833
  headers: { Authorization: authHeader }
1765
1834
  });
1766
1835
  if (result === "parse-error") {
1767
1836
  printHostedProxyJsonParseWarning(getClineMcpSettingsPath());
1768
1837
  }
1769
1838
  } else if (platform === "kilo-code") {
1770
- result = await mergeKiloCodeProjectMcp(workspacePath, shortName, proxyUrl, apiKey);
1839
+ result = await mergeKiloCodeProjectMcp(
1840
+ workspacePath,
1841
+ shortName,
1842
+ proxyUrlWithKeyWhenNeeded,
1843
+ apiKey
1844
+ );
1771
1845
  if (result === "parse-error") {
1772
1846
  printHostedProxyJsonParseWarning(join(workspacePath, ".kilocode", "mcp.json"));
1773
1847
  }
1774
1848
  } else if (platform === "continue-dev") {
1775
- result = await mergeContinueHostedMcp(shortName, proxyUrl, apiKey);
1849
+ result = await mergeContinueHostedMcp(shortName, proxyUrlWithKeyWhenNeeded, apiKey);
1776
1850
  if (result === "parse-error") {
1777
1851
  printHostedProxyJsonParseWarning(getContinueConfigJsonPath());
1778
1852
  }
1779
1853
  } else {
1780
1854
  result = await mergeMcpServersObjectStyle(getCursorMcpJsonPath(), shortName, {
1781
- url: proxyUrl,
1855
+ url: proxyUrlWithKeyWhenNeeded,
1782
1856
  headers: { Authorization: authHeader }
1783
1857
  });
1784
1858
  if (result === "parse-error") {
@@ -1823,6 +1897,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1823
1897
  ]);
1824
1898
  const usesInlineKey = hostedInlinePlatforms.has(platform);
1825
1899
  const authHeader = usesInlineKey ? `Bearer ${apiKey}` : "Bearer YOUR_SHIELD_API_KEY";
1900
+ const urlInSnippet = usesInlineKey && shouldEmbedKeyInHostedProxyUrl(platform) ? hostedProxyUrlWithKeyParam(routingToken, apiKey) : routingToken;
1826
1901
  let snippetText;
1827
1902
  if (platform === "github-copilot") {
1828
1903
  snippetText = JSON.stringify(
@@ -1831,7 +1906,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1831
1906
  servers: {
1832
1907
  [shortName]: {
1833
1908
  type: "http",
1834
- url: routingToken,
1909
+ url: urlInSnippet,
1835
1910
  headers: {
1836
1911
  Authorization: authHeader
1837
1912
  }
@@ -1843,13 +1918,13 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1843
1918
  2
1844
1919
  );
1845
1920
  } else if (platform === "goose") {
1846
- snippetText = gooseHostedProxyYaml(shortName, routingToken, authHeader);
1921
+ snippetText = gooseHostedProxyYaml(shortName, urlInSnippet, authHeader);
1847
1922
  } else if (platform === "gemini-cli") {
1848
1923
  snippetText = JSON.stringify(
1849
1924
  {
1850
1925
  mcpServers: {
1851
1926
  [shortName]: {
1852
- httpUrl: routingToken,
1927
+ httpUrl: urlInSnippet,
1853
1928
  headers: {
1854
1929
  Authorization: authHeader
1855
1930
  }
@@ -1865,7 +1940,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1865
1940
  mcpServers: {
1866
1941
  [shortName]: {
1867
1942
  command: "npx",
1868
- args: ["-y", "mcp-remote", routingToken, "--header", `Authorization: ${authHeader}`]
1943
+ args: ["-y", "mcp-remote", urlInSnippet, "--header", `Authorization: ${authHeader}`]
1869
1944
  }
1870
1945
  }
1871
1946
  },
@@ -1879,7 +1954,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1879
1954
  {
1880
1955
  name: shortName,
1881
1956
  type: "streamable-http",
1882
- url: routingToken,
1957
+ url: urlInSnippet,
1883
1958
  headers: {
1884
1959
  Authorization: authHeader
1885
1960
  }
@@ -1895,7 +1970,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1895
1970
  {
1896
1971
  mcpServers: {
1897
1972
  [shortName]: {
1898
- [urlKey]: routingToken,
1973
+ [urlKey]: urlInSnippet,
1899
1974
  headers: {
1900
1975
  Authorization: authHeader
1901
1976
  }
@@ -2399,7 +2474,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2399
2474
  }
2400
2475
  }
2401
2476
  } else {
2402
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2477
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2403
2478
  let proxyUrl = "";
2404
2479
  let created = false;
2405
2480
  while (!created) {
@@ -2411,7 +2486,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2411
2486
  agentName,
2412
2487
  targetUrl,
2413
2488
  shortName,
2414
- selectedPlatform
2489
+ selectedPlatform,
2490
+ upstreamHeaders
2415
2491
  );
2416
2492
  spinner.stop(true, "Proxy config created!");
2417
2493
  created = true;
@@ -2426,7 +2502,9 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2426
2502
  }
2427
2503
  if (created && proxyUrl.length > 0) {
2428
2504
  process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
2429
- process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
2505
+ process.stderr.write(
2506
+ " " + style.cyan(formatHostedProxyUrlForStderr(selectedPlatform, proxyUrl, apiKey)) + "\n"
2507
+ );
2430
2508
  await applyHostedProxyMcpConfig(
2431
2509
  selectedPlatform,
2432
2510
  proxyUrl,
@@ -2490,7 +2568,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2490
2568
  }
2491
2569
  }
2492
2570
  } else {
2493
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2571
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2494
2572
  let proxyUrl = "";
2495
2573
  let created = false;
2496
2574
  while (!created) {
@@ -2502,7 +2580,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2502
2580
  agentName,
2503
2581
  targetUrl,
2504
2582
  shortName,
2505
- selectedPlatform
2583
+ selectedPlatform,
2584
+ upstreamHeaders
2506
2585
  );
2507
2586
  spinner.stop(true, "Proxy config created!");
2508
2587
  created = true;
@@ -2517,7 +2596,9 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2517
2596
  }
2518
2597
  if (created && proxyUrl.length > 0) {
2519
2598
  process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
2520
- process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
2599
+ process.stderr.write(
2600
+ " " + style.cyan(formatHostedProxyUrlForStderr(selectedPlatform, proxyUrl, apiKey)) + "\n"
2601
+ );
2521
2602
  await applyHostedProxyMcpConfig(
2522
2603
  selectedPlatform,
2523
2604
  proxyUrl,
@@ -2575,7 +2656,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2575
2656
  }
2576
2657
  }
2577
2658
  } else {
2578
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2659
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2579
2660
  let proxyUrl = "";
2580
2661
  let created = false;
2581
2662
  while (!created) {
@@ -2587,7 +2668,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2587
2668
  agentName,
2588
2669
  targetUrl,
2589
2670
  shortName,
2590
- selectedPlatform
2671
+ selectedPlatform,
2672
+ upstreamHeaders
2591
2673
  );
2592
2674
  spinner.stop(true, "Proxy config created!");
2593
2675
  created = true;
@@ -2602,7 +2684,9 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2602
2684
  }
2603
2685
  if (created && proxyUrl.length > 0) {
2604
2686
  process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
2605
- process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
2687
+ process.stderr.write(
2688
+ " " + style.cyan(formatHostedProxyUrlForStderr(selectedPlatform, proxyUrl, apiKey)) + "\n"
2689
+ );
2606
2690
  await applyHostedProxyMcpConfig(
2607
2691
  selectedPlatform,
2608
2692
  proxyUrl,
@@ -2623,7 +2707,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2623
2707
  }
2624
2708
  }
2625
2709
  } else {
2626
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2710
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2627
2711
  let proxyUrl = "";
2628
2712
  let created = false;
2629
2713
  while (!created) {
@@ -2635,7 +2719,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2635
2719
  agentName,
2636
2720
  targetUrl,
2637
2721
  shortName,
2638
- selectedPlatform
2722
+ selectedPlatform,
2723
+ upstreamHeaders
2639
2724
  );
2640
2725
  spinner.stop(true, "Proxy config created!");
2641
2726
  created = true;
@@ -2650,7 +2735,9 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2650
2735
  }
2651
2736
  if (created && proxyUrl.length > 0) {
2652
2737
  process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
2653
- process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
2738
+ process.stderr.write(
2739
+ " " + style.cyan(formatHostedProxyUrlForStderr(selectedPlatform, proxyUrl, apiKey)) + "\n"
2740
+ );
2654
2741
  await applyHostedProxyMcpConfig(
2655
2742
  selectedPlatform,
2656
2743
  proxyUrl,
@@ -2831,7 +2918,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2831
2918
  }
2832
2919
  return lastConfig;
2833
2920
  }
2834
- var SECRET_JSON_FILE_OPTIONS, style, BANNER, NativePluginPrerequisiteMissingError, CONFIG_DIR, CONFIG_PATH, OPENCLAW_CONFIG_PATH, ANSI_PATTERN, OPENCLAW_MIN_VERSION, INIT_WIZARD_PLATFORM_REGISTRY, INIT_WIZARD_MENU_SECTIONS, INIT_WIZARD_SELECTION_MAX, PLATFORM_BY_SELECTION, DEFAULT_SHIELD_API_BASE_URL;
2921
+ var SECRET_JSON_FILE_OPTIONS, style, BANNER, NativePluginPrerequisiteMissingError, CONFIG_DIR, CONFIG_PATH, OPENCLAW_CONFIG_PATH, ANSI_PATTERN, OPENCLAW_MIN_VERSION, INIT_WIZARD_PLATFORM_REGISTRY, INIT_WIZARD_MENU_SECTIONS, INIT_WIZARD_SELECTION_MAX, PLATFORM_BY_SELECTION, HOSTED_PROXY_PLATFORMS_WITH_URL_KEY, DEFAULT_SHIELD_API_BASE_URL;
2835
2922
  var init_config = __esm({
2836
2923
  "src/proxy/config.ts"() {
2837
2924
  init_consent();
@@ -2922,6 +3009,14 @@ var init_config = __esm({
2922
3009
  PLATFORM_BY_SELECTION = Object.fromEntries(
2923
3010
  INIT_WIZARD_PLATFORM_REGISTRY.map((e, i) => [i + 1, e.slug])
2924
3011
  );
3012
+ HOSTED_PROXY_PLATFORMS_WITH_URL_KEY = /* @__PURE__ */ new Set([
3013
+ "cursor",
3014
+ "claude-desktop",
3015
+ "github-copilot",
3016
+ "kilo-code",
3017
+ "continue-dev",
3018
+ "goose"
3019
+ ]);
2925
3020
  DEFAULT_SHIELD_API_BASE_URL = "https://api.multicorn.ai";
2926
3021
  }
2927
3022
  });
@@ -1571,8 +1571,8 @@ async function promptProxyConfig(ask, agentName) {
1571
1571
  while (targetUrl.length === 0) {
1572
1572
  process.stderr.write(
1573
1573
  "\n" + style.bold("Target MCP server URL:") + "\n" + style.dim(
1574
- "The URL of the MCP server you want Shield to protect. Example: https://your-server.example.com/mcp"
1575
- ) + "\n"
1574
+ "This is the URL of the MCP server you want Shield to control. Common examples:"
1575
+ ) + "\n" + style.dim(" GitHub: https://api.githubcopilot.com/mcp/") + "\n" + style.dim(" Supabase: https://mcp.supabase.com/sse") + "\n" + style.dim(" Atlassian: https://mcp.atlassian.com/v1/sse") + "\n" + style.dim(" Stripe: https://mcp.stripe.com/v1/sse") + "\n" + style.dim("Check your MCP server's documentation for the correct URL.") + "\n"
1576
1576
  );
1577
1577
  const input = await ask("URL: ");
1578
1578
  if (input.trim().length === 0) {
@@ -1592,10 +1592,44 @@ async function promptProxyConfig(ask, agentName) {
1592
1592
  targetUrl = input.trim();
1593
1593
  }
1594
1594
  const shortName = normalizeAgentName(agentName) || "shield-mcp";
1595
- return { targetUrl, shortName };
1595
+ process.stderr.write(
1596
+ "\n" + style.bold("Does this MCP server require authentication?") + "\n" + style.dim(
1597
+ "Most MCP servers need a token or API key. Check the server's docs for how to get one:"
1598
+ ) + "\n" + style.dim(" GitHub: Settings > Developer Settings > Personal Access Tokens") + "\n" + style.dim(
1599
+ " Supabase: Project Settings > API > anon or scoped key (service role bypasses RLS; avoid for most MCP)"
1600
+ ) + "\n" + style.dim(" Atlassian: id.atlassian.com > API Tokens") + "\n" + style.dim(" Stripe: Dashboard > Developers > API Keys") + "\n"
1601
+ );
1602
+ const authReply = await ask("(y/N): ");
1603
+ const authNorm = authReply.trim().toLowerCase();
1604
+ const wantsAuth = authNorm === "y" || authNorm === "yes";
1605
+ let upstreamHeaders;
1606
+ if (wantsAuth) {
1607
+ process.stderr.write(
1608
+ "\n" + style.bold("Enter the Authorization header value.") + "\n" + style.dim(" For Bearer tokens: Bearer ghp_xxxxxxxxxxxx") + "\n" + style.dim(" For API keys: Bearer sk-xxxxxxxxxxxx") + "\n"
1609
+ );
1610
+ const headerVal = await ask("Value: ");
1611
+ const trimmed = headerVal.trim();
1612
+ if (trimmed.length > 0) {
1613
+ upstreamHeaders = { Authorization: trimmed };
1614
+ }
1615
+ }
1616
+ return {
1617
+ targetUrl,
1618
+ shortName,
1619
+ ...upstreamHeaders !== void 0 ? { upstreamHeaders } : {}
1620
+ };
1596
1621
  }
1597
- async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverName, platform) {
1622
+ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverName, platform, upstreamHeaders) {
1598
1623
  let response;
1624
+ const body = {
1625
+ server_name: serverName,
1626
+ target_url: targetUrl,
1627
+ platform,
1628
+ agent_name: agentName
1629
+ };
1630
+ if (upstreamHeaders !== void 0 && Object.keys(upstreamHeaders).length > 0) {
1631
+ body["upstream_headers"] = upstreamHeaders;
1632
+ }
1599
1633
  try {
1600
1634
  response = await fetch(`${baseUrl}/api/v1/proxy/config`, {
1601
1635
  method: "POST",
@@ -1603,12 +1637,7 @@ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverNa
1603
1637
  "Content-Type": "application/json",
1604
1638
  "X-Multicorn-Key": apiKey
1605
1639
  },
1606
- body: JSON.stringify({
1607
- server_name: serverName,
1608
- target_url: targetUrl,
1609
- platform,
1610
- agent_name: agentName
1611
- }),
1640
+ body: JSON.stringify(body),
1612
1641
  signal: AbortSignal.timeout(1e4)
1613
1642
  });
1614
1643
  } catch (error) {
@@ -1635,6 +1664,49 @@ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverNa
1635
1664
  const data = envelope["data"];
1636
1665
  return typeof data?.["proxy_url"] === "string" ? data["proxy_url"] : "";
1637
1666
  }
1667
+ var HOSTED_PROXY_PLATFORMS_WITH_URL_KEY = /* @__PURE__ */ new Set([
1668
+ "cursor",
1669
+ "claude-desktop",
1670
+ "github-copilot",
1671
+ "kilo-code",
1672
+ "continue-dev",
1673
+ "goose"
1674
+ ]);
1675
+ function shouldEmbedKeyInHostedProxyUrl(platform) {
1676
+ return HOSTED_PROXY_PLATFORMS_WITH_URL_KEY.has(platform);
1677
+ }
1678
+ function hostedProxyUrlWithKeyParam(proxyUrl, apiKey) {
1679
+ if (apiKey.length === 0) {
1680
+ process.stderr.write(
1681
+ style.yellow("\u26A0") + " Could not add key to proxy URL: API key is empty; using URL without key query parameter.\n"
1682
+ );
1683
+ return proxyUrl;
1684
+ }
1685
+ try {
1686
+ const u = new URL(proxyUrl);
1687
+ u.searchParams.set("key", apiKey);
1688
+ return u.toString();
1689
+ } catch (err) {
1690
+ const detail = err instanceof Error ? err.message : String(err);
1691
+ process.stderr.write(
1692
+ style.yellow("\u26A0") + " Could not parse proxy URL to append key query parameter; using URL unchanged. " + style.dim(detail) + "\n"
1693
+ );
1694
+ return proxyUrl;
1695
+ }
1696
+ }
1697
+ function formatHostedProxyUrlForStderr(platform, proxyUrl, apiKey) {
1698
+ if (!shouldEmbedKeyInHostedProxyUrl(platform) || apiKey.length === 0) {
1699
+ return proxyUrl;
1700
+ }
1701
+ try {
1702
+ const u = new URL(proxyUrl);
1703
+ const redactedLabel = apiKey.length <= 4 ? "****" : `mcs_...${apiKey.slice(-4)}`;
1704
+ u.searchParams.set("key", redactedLabel);
1705
+ return u.toString();
1706
+ } catch {
1707
+ return proxyUrl;
1708
+ }
1709
+ }
1638
1710
  function writeMcpAddedLine(shortName, filePath) {
1639
1711
  process.stderr.write(
1640
1712
  style.green("\u2713") + ' MCP server "' + shortName + '" added to ' + style.cyan(filePath) + "\n"
@@ -1784,8 +1856,9 @@ function printHostedProxyPostWriteHints(platform, shortName) {
1784
1856
  }
1785
1857
  async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey, workspacePath) {
1786
1858
  const authHeader = `Bearer ${apiKey}`;
1859
+ const proxyUrlWithKeyWhenNeeded = shouldEmbedKeyInHostedProxyUrl(platform) ? hostedProxyUrlWithKeyParam(proxyUrl, apiKey) : proxyUrl;
1787
1860
  if (platform === "gemini-cli") {
1788
- await mergeGeminiHostedMcpServersIntoSettings(shortName, proxyUrl, apiKey);
1861
+ await mergeGeminiHostedMcpServersIntoSettings(shortName, proxyUrlWithKeyWhenNeeded, apiKey);
1789
1862
  process.stderr.write(
1790
1863
  style.dim(
1791
1864
  "For project-specific config, copy the mcpServers entry into .gemini/settings.json in your project root. Restart Gemini CLI if it is already running."
@@ -1810,20 +1883,24 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
1810
1883
  let result = "parse-error";
1811
1884
  if (platform === "cursor") {
1812
1885
  result = await mergeMcpServersObjectStyle(getCursorMcpJsonPath(), shortName, {
1813
- url: proxyUrl,
1886
+ url: proxyUrlWithKeyWhenNeeded,
1814
1887
  headers: { Authorization: authHeader }
1815
1888
  });
1816
1889
  if (result === "parse-error") {
1817
1890
  printHostedProxyJsonParseWarning(getCursorMcpJsonPath());
1818
1891
  }
1819
1892
  } else if (platform === "claude-desktop") {
1820
- result = await mergeClaudeDesktopHostedMcpRemote(shortName, proxyUrl, apiKey);
1893
+ result = await mergeClaudeDesktopHostedMcpRemote(
1894
+ shortName,
1895
+ proxyUrlWithKeyWhenNeeded,
1896
+ apiKey
1897
+ );
1821
1898
  if (result === "parse-error") {
1822
1899
  printHostedProxyJsonParseWarning(getClaudeDesktopConfigPath());
1823
1900
  }
1824
1901
  } else if (platform === "windsurf") {
1825
1902
  result = await mergeMcpServersObjectStyle(getWindsurfMcpConfigPath(), shortName, {
1826
- serverUrl: proxyUrl,
1903
+ serverUrl: proxyUrlWithKeyWhenNeeded,
1827
1904
  headers: { Authorization: authHeader }
1828
1905
  });
1829
1906
  if (result === "parse-error") {
@@ -1831,25 +1908,30 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
1831
1908
  }
1832
1909
  } else if (platform === "cline") {
1833
1910
  result = await mergeMcpServersObjectStyle(getClineMcpSettingsPath(), shortName, {
1834
- url: proxyUrl,
1911
+ url: proxyUrlWithKeyWhenNeeded,
1835
1912
  headers: { Authorization: authHeader }
1836
1913
  });
1837
1914
  if (result === "parse-error") {
1838
1915
  printHostedProxyJsonParseWarning(getClineMcpSettingsPath());
1839
1916
  }
1840
1917
  } else if (platform === "kilo-code") {
1841
- result = await mergeKiloCodeProjectMcp(workspacePath, shortName, proxyUrl, apiKey);
1918
+ result = await mergeKiloCodeProjectMcp(
1919
+ workspacePath,
1920
+ shortName,
1921
+ proxyUrlWithKeyWhenNeeded,
1922
+ apiKey
1923
+ );
1842
1924
  if (result === "parse-error") {
1843
1925
  printHostedProxyJsonParseWarning(join(workspacePath, ".kilocode", "mcp.json"));
1844
1926
  }
1845
1927
  } else if (platform === "continue-dev") {
1846
- result = await mergeContinueHostedMcp(shortName, proxyUrl, apiKey);
1928
+ result = await mergeContinueHostedMcp(shortName, proxyUrlWithKeyWhenNeeded, apiKey);
1847
1929
  if (result === "parse-error") {
1848
1930
  printHostedProxyJsonParseWarning(getContinueConfigJsonPath());
1849
1931
  }
1850
1932
  } else {
1851
1933
  result = await mergeMcpServersObjectStyle(getCursorMcpJsonPath(), shortName, {
1852
- url: proxyUrl,
1934
+ url: proxyUrlWithKeyWhenNeeded,
1853
1935
  headers: { Authorization: authHeader }
1854
1936
  });
1855
1937
  if (result === "parse-error") {
@@ -1894,6 +1976,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1894
1976
  ]);
1895
1977
  const usesInlineKey = hostedInlinePlatforms.has(platform);
1896
1978
  const authHeader = usesInlineKey ? `Bearer ${apiKey}` : "Bearer YOUR_SHIELD_API_KEY";
1979
+ const urlInSnippet = usesInlineKey && shouldEmbedKeyInHostedProxyUrl(platform) ? hostedProxyUrlWithKeyParam(routingToken, apiKey) : routingToken;
1897
1980
  let snippetText;
1898
1981
  if (platform === "github-copilot") {
1899
1982
  snippetText = JSON.stringify(
@@ -1902,7 +1985,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1902
1985
  servers: {
1903
1986
  [shortName]: {
1904
1987
  type: "http",
1905
- url: routingToken,
1988
+ url: urlInSnippet,
1906
1989
  headers: {
1907
1990
  Authorization: authHeader
1908
1991
  }
@@ -1914,13 +1997,13 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1914
1997
  2
1915
1998
  );
1916
1999
  } else if (platform === "goose") {
1917
- snippetText = gooseHostedProxyYaml(shortName, routingToken, authHeader);
2000
+ snippetText = gooseHostedProxyYaml(shortName, urlInSnippet, authHeader);
1918
2001
  } else if (platform === "gemini-cli") {
1919
2002
  snippetText = JSON.stringify(
1920
2003
  {
1921
2004
  mcpServers: {
1922
2005
  [shortName]: {
1923
- httpUrl: routingToken,
2006
+ httpUrl: urlInSnippet,
1924
2007
  headers: {
1925
2008
  Authorization: authHeader
1926
2009
  }
@@ -1936,7 +2019,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1936
2019
  mcpServers: {
1937
2020
  [shortName]: {
1938
2021
  command: "npx",
1939
- args: ["-y", "mcp-remote", routingToken, "--header", `Authorization: ${authHeader}`]
2022
+ args: ["-y", "mcp-remote", urlInSnippet, "--header", `Authorization: ${authHeader}`]
1940
2023
  }
1941
2024
  }
1942
2025
  },
@@ -1950,7 +2033,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1950
2033
  {
1951
2034
  name: shortName,
1952
2035
  type: "streamable-http",
1953
- url: routingToken,
2036
+ url: urlInSnippet,
1954
2037
  headers: {
1955
2038
  Authorization: authHeader
1956
2039
  }
@@ -1966,7 +2049,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1966
2049
  {
1967
2050
  mcpServers: {
1968
2051
  [shortName]: {
1969
- [urlKey]: routingToken,
2052
+ [urlKey]: urlInSnippet,
1970
2053
  headers: {
1971
2054
  Authorization: authHeader
1972
2055
  }
@@ -2471,7 +2554,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2471
2554
  }
2472
2555
  }
2473
2556
  } else {
2474
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2557
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2475
2558
  let proxyUrl = "";
2476
2559
  let created = false;
2477
2560
  while (!created) {
@@ -2483,7 +2566,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2483
2566
  agentName,
2484
2567
  targetUrl,
2485
2568
  shortName,
2486
- selectedPlatform
2569
+ selectedPlatform,
2570
+ upstreamHeaders
2487
2571
  );
2488
2572
  spinner.stop(true, "Proxy config created!");
2489
2573
  created = true;
@@ -2498,7 +2582,9 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2498
2582
  }
2499
2583
  if (created && proxyUrl.length > 0) {
2500
2584
  process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
2501
- process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
2585
+ process.stderr.write(
2586
+ " " + style.cyan(formatHostedProxyUrlForStderr(selectedPlatform, proxyUrl, apiKey)) + "\n"
2587
+ );
2502
2588
  await applyHostedProxyMcpConfig(
2503
2589
  selectedPlatform,
2504
2590
  proxyUrl,
@@ -2562,7 +2648,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2562
2648
  }
2563
2649
  }
2564
2650
  } else {
2565
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2651
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2566
2652
  let proxyUrl = "";
2567
2653
  let created = false;
2568
2654
  while (!created) {
@@ -2574,7 +2660,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2574
2660
  agentName,
2575
2661
  targetUrl,
2576
2662
  shortName,
2577
- selectedPlatform
2663
+ selectedPlatform,
2664
+ upstreamHeaders
2578
2665
  );
2579
2666
  spinner.stop(true, "Proxy config created!");
2580
2667
  created = true;
@@ -2589,7 +2676,9 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2589
2676
  }
2590
2677
  if (created && proxyUrl.length > 0) {
2591
2678
  process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
2592
- process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
2679
+ process.stderr.write(
2680
+ " " + style.cyan(formatHostedProxyUrlForStderr(selectedPlatform, proxyUrl, apiKey)) + "\n"
2681
+ );
2593
2682
  await applyHostedProxyMcpConfig(
2594
2683
  selectedPlatform,
2595
2684
  proxyUrl,
@@ -2647,7 +2736,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2647
2736
  }
2648
2737
  }
2649
2738
  } else {
2650
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2739
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2651
2740
  let proxyUrl = "";
2652
2741
  let created = false;
2653
2742
  while (!created) {
@@ -2659,7 +2748,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2659
2748
  agentName,
2660
2749
  targetUrl,
2661
2750
  shortName,
2662
- selectedPlatform
2751
+ selectedPlatform,
2752
+ upstreamHeaders
2663
2753
  );
2664
2754
  spinner.stop(true, "Proxy config created!");
2665
2755
  created = true;
@@ -2674,7 +2764,9 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2674
2764
  }
2675
2765
  if (created && proxyUrl.length > 0) {
2676
2766
  process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
2677
- process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
2767
+ process.stderr.write(
2768
+ " " + style.cyan(formatHostedProxyUrlForStderr(selectedPlatform, proxyUrl, apiKey)) + "\n"
2769
+ );
2678
2770
  await applyHostedProxyMcpConfig(
2679
2771
  selectedPlatform,
2680
2772
  proxyUrl,
@@ -2695,7 +2787,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2695
2787
  }
2696
2788
  }
2697
2789
  } else {
2698
- const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
2790
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
2699
2791
  let proxyUrl = "";
2700
2792
  let created = false;
2701
2793
  while (!created) {
@@ -2707,7 +2799,8 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2707
2799
  agentName,
2708
2800
  targetUrl,
2709
2801
  shortName,
2710
- selectedPlatform
2802
+ selectedPlatform,
2803
+ upstreamHeaders
2711
2804
  );
2712
2805
  spinner.stop(true, "Proxy config created!");
2713
2806
  created = true;
@@ -2722,7 +2815,9 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2722
2815
  }
2723
2816
  if (created && proxyUrl.length > 0) {
2724
2817
  process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
2725
- process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
2818
+ process.stderr.write(
2819
+ " " + style.cyan(formatHostedProxyUrlForStderr(selectedPlatform, proxyUrl, apiKey)) + "\n"
2820
+ );
2726
2821
  await applyHostedProxyMcpConfig(
2727
2822
  selectedPlatform,
2728
2823
  proxyUrl,
@@ -22417,7 +22417,7 @@ async function writeExtensionBackup(claudeDesktopConfigPath, mcpServers) {
22417
22417
 
22418
22418
  // package.json
22419
22419
  var package_default = {
22420
- version: "1.3.5"};
22420
+ version: "1.4.0"};
22421
22421
 
22422
22422
  // src/package-meta.ts
22423
22423
  var PACKAGE_VERSION = package_default.version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multicorn-shield",
3
- "version": "1.3.5",
3
+ "version": "1.4.0",
4
4
  "description": "The control layer for AI agents: permissions, consent, spending limits, and audit logging.",
5
5
  "license": "MIT",
6
6
  "author": "Multicorn AI Pty Ltd",