multicorn-shield 1.4.1 → 1.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/CHANGELOG.md +37 -0
- package/dist/multicorn-proxy.js +504 -90
- package/dist/multicorn-shield.js +323 -90
- package/dist/proxy.cjs +6 -2
- package/dist/proxy.d.cts +1 -1
- package/dist/proxy.d.ts +1 -1
- package/dist/proxy.js +6 -2
- package/dist/shield-extension.js +9 -4
- package/package.json +2 -1
package/dist/multicorn-shield.js
CHANGED
|
@@ -6,6 +6,7 @@ import { homedir } from 'os';
|
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import { createInterface } from 'readline';
|
|
9
|
+
import { parse, stringify } from 'yaml';
|
|
9
10
|
import { spawn } from 'child_process';
|
|
10
11
|
import { createHash } from 'crypto';
|
|
11
12
|
import 'stream';
|
|
@@ -1360,9 +1361,6 @@ function getClineMcpSettingsPath() {
|
|
|
1360
1361
|
);
|
|
1361
1362
|
}
|
|
1362
1363
|
}
|
|
1363
|
-
function getContinueConfigJsonPath() {
|
|
1364
|
-
return join(homedir(), ".continue", "config.json");
|
|
1365
|
-
}
|
|
1366
1364
|
var INIT_WIZARD_PLATFORM_REGISTRY = [
|
|
1367
1365
|
{ slug: "openclaw", displayName: "OpenClaw", section: "native" },
|
|
1368
1366
|
{ slug: "claude-code", displayName: "Claude Code", section: "native" },
|
|
@@ -1483,6 +1481,12 @@ async function promptWindsurfIntegrationMode(ask) {
|
|
|
1483
1481
|
return choice === 1 ? "native" : "hosted";
|
|
1484
1482
|
}
|
|
1485
1483
|
async function arrowSelect(options, ask, fallbackLabel) {
|
|
1484
|
+
if (options.length === 1) {
|
|
1485
|
+
const only = options[0] ?? "";
|
|
1486
|
+
process.stderr.write(`${style.violet("\u276F")} ${style.cyan(only)}
|
|
1487
|
+
`);
|
|
1488
|
+
return 0;
|
|
1489
|
+
}
|
|
1486
1490
|
const canRaw = process.stdin.isTTY && typeof process.stdin.setRawMode === "function";
|
|
1487
1491
|
if (!canRaw) {
|
|
1488
1492
|
for (let i = 0; i < options.length; i++) {
|
|
@@ -1535,7 +1539,7 @@ async function arrowSelect(options, ask, fallbackLabel) {
|
|
|
1535
1539
|
cleanup();
|
|
1536
1540
|
clearLines();
|
|
1537
1541
|
const chosen = options.at(idx);
|
|
1538
|
-
if (chosen !== void 0) {
|
|
1542
|
+
if (chosen !== void 0 && options.length > 1) {
|
|
1539
1543
|
process.stderr.write(`${style.violet("\u276F")} ${style.cyan(chosen)}
|
|
1540
1544
|
`);
|
|
1541
1545
|
}
|
|
@@ -1724,13 +1728,56 @@ function writeMcpAddedLine(shortName, filePath) {
|
|
|
1724
1728
|
style.green("\u2713") + ' MCP server "' + shortName + '" added to ' + style.cyan(filePath) + "\n"
|
|
1725
1729
|
);
|
|
1726
1730
|
}
|
|
1727
|
-
|
|
1731
|
+
function sanitiseYamlValue(value) {
|
|
1732
|
+
if (value.length === 0) {
|
|
1733
|
+
return "''";
|
|
1734
|
+
}
|
|
1735
|
+
const needsQuoting = /[:#\n{}]/.test(value) || value.includes("[") || value.includes("]") || value !== value.trim() || value.includes("'");
|
|
1736
|
+
if (!needsQuoting) {
|
|
1737
|
+
return value;
|
|
1738
|
+
}
|
|
1739
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
1740
|
+
}
|
|
1741
|
+
function gitignoreLikelyCoversPath(relPosixPath, gitignoreBody) {
|
|
1742
|
+
const norm = relPosixPath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1743
|
+
const lines = gitignoreBody.split(/\r?\n/);
|
|
1744
|
+
for (const raw of lines) {
|
|
1745
|
+
const line = raw.trim();
|
|
1746
|
+
if (!line || line.startsWith("#") || line.startsWith("!")) continue;
|
|
1747
|
+
const pat = line.replace(/^\//, "");
|
|
1748
|
+
if (pat === norm || pat === `./${norm}`) return true;
|
|
1749
|
+
if (!pat.includes("*")) {
|
|
1750
|
+
if (pat.endsWith("/")) {
|
|
1751
|
+
const dir = pat.slice(0, -1);
|
|
1752
|
+
if (norm === dir || norm.startsWith(`${dir}/`)) return true;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return false;
|
|
1757
|
+
}
|
|
1758
|
+
async function warnIfApiKeyFileNotGitignored(workspaceRoot, relativePosixPath) {
|
|
1759
|
+
const gitignorePath = join(workspaceRoot, ".gitignore");
|
|
1760
|
+
let content;
|
|
1761
|
+
try {
|
|
1762
|
+
content = await readFile(gitignorePath, "utf8");
|
|
1763
|
+
} catch (e) {
|
|
1764
|
+
if (isErrnoException(e) && e.code === "ENOENT") return;
|
|
1765
|
+
throw e;
|
|
1766
|
+
}
|
|
1767
|
+
const norm = relativePosixPath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1768
|
+
if (gitignoreLikelyCoversPath(norm, content)) return;
|
|
1769
|
+
process.stderr.write(
|
|
1770
|
+
style.yellow("\u26A0") + " Config contains your API key. Add " + style.cyan(norm) + " to .gitignore to avoid committing credentials.\n"
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
async function mergeTopLevelKeyedJsonFile(filePath, topLevelKey, shortName, entry, options) {
|
|
1728
1774
|
let root = {};
|
|
1729
1775
|
try {
|
|
1730
1776
|
const raw = await readFile(filePath, "utf8");
|
|
1777
|
+
const toParse = options.stripJsonComments ? raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "") : raw;
|
|
1731
1778
|
let parsed;
|
|
1732
1779
|
try {
|
|
1733
|
-
parsed = JSON.parse(
|
|
1780
|
+
parsed = JSON.parse(toParse);
|
|
1734
1781
|
} catch {
|
|
1735
1782
|
return "parse-error";
|
|
1736
1783
|
}
|
|
@@ -1746,15 +1793,25 @@ async function mergeMcpServersObjectStyle(filePath, shortName, entry) {
|
|
|
1746
1793
|
throw e;
|
|
1747
1794
|
}
|
|
1748
1795
|
}
|
|
1749
|
-
const
|
|
1750
|
-
const
|
|
1751
|
-
|
|
1752
|
-
|
|
1796
|
+
const bucketRaw = root[topLevelKey];
|
|
1797
|
+
const bucket = typeof bucketRaw === "object" && bucketRaw !== null && !Array.isArray(bucketRaw) ? { ...bucketRaw } : {};
|
|
1798
|
+
if (options.onExisting === "skip" && bucket[shortName] !== void 0) {
|
|
1799
|
+
return "unchanged";
|
|
1800
|
+
}
|
|
1801
|
+
bucket[shortName] = entry;
|
|
1802
|
+
root[topLevelKey] = bucket;
|
|
1753
1803
|
await mkdir(dirname(filePath), { recursive: true });
|
|
1754
1804
|
await writeFile(filePath, JSON.stringify(root, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
1755
1805
|
writeMcpAddedLine(shortName, filePath);
|
|
1756
1806
|
return "ok";
|
|
1757
1807
|
}
|
|
1808
|
+
async function mergeMcpServersObjectStyle(filePath, shortName, entry) {
|
|
1809
|
+
const result = await mergeTopLevelKeyedJsonFile(filePath, "mcpServers", shortName, entry, {
|
|
1810
|
+
stripJsonComments: false,
|
|
1811
|
+
onExisting: "overwrite"
|
|
1812
|
+
});
|
|
1813
|
+
return result === "parse-error" ? "parse-error" : "ok";
|
|
1814
|
+
}
|
|
1758
1815
|
async function mergeClaudeDesktopHostedMcpRemote(shortName, proxyUrl, apiKey) {
|
|
1759
1816
|
const entry = {
|
|
1760
1817
|
command: "npx",
|
|
@@ -1762,8 +1819,30 @@ async function mergeClaudeDesktopHostedMcpRemote(shortName, proxyUrl, apiKey) {
|
|
|
1762
1819
|
};
|
|
1763
1820
|
return mergeMcpServersObjectStyle(getClaudeDesktopConfigPath(), shortName, entry);
|
|
1764
1821
|
}
|
|
1765
|
-
async function mergeContinueHostedMcp(shortName, proxyUrl, apiKey) {
|
|
1766
|
-
const
|
|
1822
|
+
async function mergeContinueHostedMcp(workspacePath, shortName, proxyUrl, apiKey) {
|
|
1823
|
+
const dir = join(workspacePath, ".continue", "mcpServers");
|
|
1824
|
+
const filePath = join(dir, `${shortName}.yaml`);
|
|
1825
|
+
const sn = sanitiseYamlValue(shortName);
|
|
1826
|
+
const urlEsc = sanitiseYamlValue(proxyUrl);
|
|
1827
|
+
const authEsc = sanitiseYamlValue(`Bearer ${apiKey}`);
|
|
1828
|
+
const yaml = `name: ${sn}
|
|
1829
|
+
version: 0.0.1
|
|
1830
|
+
schema: v1
|
|
1831
|
+
mcpServers:
|
|
1832
|
+
- name: ${sn}
|
|
1833
|
+
type: streamable-http
|
|
1834
|
+
url: ${urlEsc}
|
|
1835
|
+
headers:
|
|
1836
|
+
Authorization: ${authEsc}
|
|
1837
|
+
`;
|
|
1838
|
+
await mkdir(dir, { recursive: true });
|
|
1839
|
+
await writeFile(filePath, yaml, SECRET_JSON_FILE_OPTIONS);
|
|
1840
|
+
writeMcpAddedLine(shortName, filePath);
|
|
1841
|
+
await warnIfApiKeyFileNotGitignored(workspacePath, `.continue/mcpServers/${shortName}.yaml`);
|
|
1842
|
+
return "ok";
|
|
1843
|
+
}
|
|
1844
|
+
async function mergeCopilotVscodeMcp(workspacePath, shortName, proxyUrl, apiKey) {
|
|
1845
|
+
const filePath = join(workspacePath, ".vscode", "mcp.json");
|
|
1767
1846
|
let root = {};
|
|
1768
1847
|
try {
|
|
1769
1848
|
const raw = await readFile(filePath, "utf8");
|
|
@@ -1785,43 +1864,49 @@ async function mergeContinueHostedMcp(shortName, proxyUrl, apiKey) {
|
|
|
1785
1864
|
throw e;
|
|
1786
1865
|
}
|
|
1787
1866
|
}
|
|
1788
|
-
const
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
return "parse-error";
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
const servers = Array.isArray(rawServers) ? rawServers.map((s) => ({ ...s })) : [];
|
|
1797
|
-
const entry = {
|
|
1798
|
-
name: shortName,
|
|
1799
|
-
type: "streamable-http",
|
|
1867
|
+
const serversRaw = root["servers"];
|
|
1868
|
+
const servers = typeof serversRaw === "object" && serversRaw !== null && !Array.isArray(serversRaw) ? { ...serversRaw } : {};
|
|
1869
|
+
const existed = servers[shortName] !== void 0;
|
|
1870
|
+
servers[shortName] = {
|
|
1871
|
+
type: "http",
|
|
1800
1872
|
url: proxyUrl,
|
|
1801
|
-
headers: {
|
|
1802
|
-
Authorization: `Bearer ${apiKey}`
|
|
1803
|
-
}
|
|
1873
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1804
1874
|
};
|
|
1805
|
-
|
|
1806
|
-
if (idx >= 0) {
|
|
1807
|
-
servers[idx] = entry;
|
|
1808
|
-
} else {
|
|
1809
|
-
servers.push(entry);
|
|
1810
|
-
}
|
|
1811
|
-
root["mcpServers"] = servers;
|
|
1875
|
+
root["servers"] = servers;
|
|
1812
1876
|
await mkdir(dirname(filePath), { recursive: true });
|
|
1813
1877
|
await writeFile(filePath, JSON.stringify(root, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
1814
|
-
|
|
1878
|
+
if (existed) {
|
|
1879
|
+
process.stderr.write(
|
|
1880
|
+
style.dim(`Updated existing server entry for ${shortName} in .vscode/mcp.json`) + "\n"
|
|
1881
|
+
);
|
|
1882
|
+
} else {
|
|
1883
|
+
writeMcpAddedLine(shortName, filePath);
|
|
1884
|
+
}
|
|
1885
|
+
await warnIfApiKeyFileNotGitignored(workspacePath, ".vscode/mcp.json");
|
|
1815
1886
|
return "ok";
|
|
1816
1887
|
}
|
|
1817
1888
|
async function mergeKiloCodeProjectMcp(workspacePath, shortName, proxyUrl, apiKey) {
|
|
1818
|
-
const filePath = join(workspacePath, ".
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1889
|
+
const filePath = join(workspacePath, ".kilo", "kilo.jsonc");
|
|
1890
|
+
const result = await mergeTopLevelKeyedJsonFile(
|
|
1891
|
+
filePath,
|
|
1892
|
+
"mcp",
|
|
1893
|
+
shortName,
|
|
1894
|
+
{
|
|
1895
|
+
type: "remote",
|
|
1896
|
+
url: proxyUrl,
|
|
1897
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
1898
|
+
enabled: true
|
|
1899
|
+
},
|
|
1900
|
+
{
|
|
1901
|
+
stripJsonComments: true,
|
|
1902
|
+
onExisting: "skip"
|
|
1823
1903
|
}
|
|
1824
|
-
|
|
1904
|
+
);
|
|
1905
|
+
if (result === "parse-error") return "parse-error";
|
|
1906
|
+
if (result === "ok") {
|
|
1907
|
+
await warnIfApiKeyFileNotGitignored(workspacePath, ".kilo/kilo.jsonc");
|
|
1908
|
+
}
|
|
1909
|
+
return "ok";
|
|
1825
1910
|
}
|
|
1826
1911
|
function printHostedProxyJsonParseWarning(filePath) {
|
|
1827
1912
|
process.stderr.write(
|
|
@@ -1862,7 +1947,7 @@ function printHostedProxyPostWriteHints(platform, shortName) {
|
|
|
1862
1947
|
}
|
|
1863
1948
|
if (platform === "kilo-code") {
|
|
1864
1949
|
process.stderr.write(
|
|
1865
|
-
style.dim("Restart Kilo Code or reload the window so it picks up .
|
|
1950
|
+
style.dim("Restart Kilo Code or reload the window so it picks up .kilo/kilo.jsonc.") + "\n"
|
|
1866
1951
|
);
|
|
1867
1952
|
}
|
|
1868
1953
|
}
|
|
@@ -1879,15 +1964,40 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
|
|
|
1879
1964
|
return;
|
|
1880
1965
|
}
|
|
1881
1966
|
if (platform === "github-copilot") {
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1967
|
+
try {
|
|
1968
|
+
const result = await mergeCopilotVscodeMcp(
|
|
1969
|
+
workspacePath,
|
|
1970
|
+
shortName,
|
|
1971
|
+
proxyUrlWithKeyWhenNeeded,
|
|
1972
|
+
apiKey
|
|
1973
|
+
);
|
|
1974
|
+
if (result === "ok") {
|
|
1975
|
+
printHostedProxyPostWriteHints(platform, shortName);
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
printHostedProxyJsonParseWarning(join(workspacePath, ".vscode", "mcp.json"));
|
|
1979
|
+
} catch (err) {
|
|
1980
|
+
process.stderr.write(
|
|
1981
|
+
`${style.yellow("!")} Could not auto-write config: ${err instanceof Error ? err.message : String(err)}
|
|
1982
|
+
`
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1887
1985
|
printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
|
|
1888
1986
|
return;
|
|
1889
1987
|
}
|
|
1890
1988
|
if (platform === "goose") {
|
|
1989
|
+
try {
|
|
1990
|
+
const result = await mergeGooseConfig(shortName, proxyUrlWithKeyWhenNeeded, apiKey);
|
|
1991
|
+
if (result === "ok") {
|
|
1992
|
+
printHostedProxyPostWriteHints(platform, shortName);
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
} catch (err) {
|
|
1996
|
+
process.stderr.write(
|
|
1997
|
+
`${style.yellow("!")} Could not auto-write config: ${err instanceof Error ? err.message : String(err)}
|
|
1998
|
+
`
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
1891
2001
|
printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
|
|
1892
2002
|
return;
|
|
1893
2003
|
}
|
|
@@ -1934,12 +2044,19 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
|
|
|
1934
2044
|
apiKey
|
|
1935
2045
|
);
|
|
1936
2046
|
if (result === "parse-error") {
|
|
1937
|
-
printHostedProxyJsonParseWarning(join(workspacePath, ".
|
|
2047
|
+
printHostedProxyJsonParseWarning(join(workspacePath, ".kilo", "kilo.jsonc"));
|
|
1938
2048
|
}
|
|
1939
2049
|
} else if (platform === "continue-dev") {
|
|
1940
|
-
result = await mergeContinueHostedMcp(
|
|
2050
|
+
result = await mergeContinueHostedMcp(
|
|
2051
|
+
workspacePath,
|
|
2052
|
+
shortName,
|
|
2053
|
+
proxyUrlWithKeyWhenNeeded,
|
|
2054
|
+
apiKey
|
|
2055
|
+
);
|
|
1941
2056
|
if (result === "parse-error") {
|
|
1942
|
-
printHostedProxyJsonParseWarning(
|
|
2057
|
+
printHostedProxyJsonParseWarning(
|
|
2058
|
+
join(workspacePath, ".continue", "mcpServers", `${shortName}.yaml`)
|
|
2059
|
+
);
|
|
1943
2060
|
}
|
|
1944
2061
|
} else {
|
|
1945
2062
|
result = await mergeMcpServersObjectStyle(getCursorMcpJsonPath(), shortName, {
|
|
@@ -1963,17 +2080,96 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
|
|
|
1963
2080
|
}
|
|
1964
2081
|
printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
|
|
1965
2082
|
}
|
|
1966
|
-
function
|
|
1967
|
-
|
|
1968
|
-
|
|
2083
|
+
function printGooseConfigYamlParseErrorToStderr() {
|
|
2084
|
+
process.stderr.write(
|
|
2085
|
+
style.yellow("!") + " Could not parse ~/.config/goose/config.yaml - check for syntax errors or invalid YAML\n"
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
function gooseExtensionYaml(shortName, proxyUrl, bearerHeader) {
|
|
2089
|
+
const sn = sanitiseYamlValue(shortName);
|
|
2090
|
+
const urlEsc = sanitiseYamlValue(proxyUrl);
|
|
2091
|
+
const authEsc = sanitiseYamlValue(bearerHeader);
|
|
2092
|
+
return ` ${sn}:
|
|
2093
|
+
enabled: true
|
|
1969
2094
|
type: streamable_http
|
|
1970
|
-
|
|
2095
|
+
name: ${sn}
|
|
2096
|
+
description: ''
|
|
2097
|
+
uri: ${urlEsc}
|
|
2098
|
+
envs: {}
|
|
2099
|
+
env_keys: []
|
|
1971
2100
|
headers:
|
|
1972
|
-
Authorization: ${
|
|
1973
|
-
enabled: true
|
|
2101
|
+
Authorization: ${authEsc}
|
|
1974
2102
|
timeout: 300
|
|
2103
|
+
socket: null
|
|
2104
|
+
bundled: null
|
|
2105
|
+
available_tools: []
|
|
1975
2106
|
`;
|
|
1976
2107
|
}
|
|
2108
|
+
function gooseHostedProxyYaml(shortName, proxyUrl, bearerHeader) {
|
|
2109
|
+
return `extensions:
|
|
2110
|
+
` + gooseExtensionYaml(shortName, proxyUrl, bearerHeader);
|
|
2111
|
+
}
|
|
2112
|
+
function isYamlPlainObject(value) {
|
|
2113
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2114
|
+
}
|
|
2115
|
+
async function mergeGooseConfig(shortName, proxyUrl, apiKey) {
|
|
2116
|
+
const filePath = join(homedir(), ".config", "goose", "config.yaml");
|
|
2117
|
+
const bearerHeader = `Bearer ${apiKey}`;
|
|
2118
|
+
let content = "";
|
|
2119
|
+
try {
|
|
2120
|
+
content = await readFile(filePath, "utf8");
|
|
2121
|
+
} catch (e) {
|
|
2122
|
+
if (isErrnoException(e) && e.code === "ENOENT") {
|
|
2123
|
+
content = "";
|
|
2124
|
+
} else {
|
|
2125
|
+
throw e;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
let root;
|
|
2129
|
+
try {
|
|
2130
|
+
const data = content.trim().length === 0 ? {} : parse(content);
|
|
2131
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
2132
|
+
printGooseConfigYamlParseErrorToStderr();
|
|
2133
|
+
return "parse-error";
|
|
2134
|
+
}
|
|
2135
|
+
root = data;
|
|
2136
|
+
} catch {
|
|
2137
|
+
printGooseConfigYamlParseErrorToStderr();
|
|
2138
|
+
return "parse-error";
|
|
2139
|
+
}
|
|
2140
|
+
const extensionsRaw = root["extensions"];
|
|
2141
|
+
let extensions;
|
|
2142
|
+
if (isYamlPlainObject(extensionsRaw)) {
|
|
2143
|
+
extensions = { ...extensionsRaw };
|
|
2144
|
+
} else if (extensionsRaw === void 0) {
|
|
2145
|
+
extensions = {};
|
|
2146
|
+
} else {
|
|
2147
|
+
printGooseConfigYamlParseErrorToStderr();
|
|
2148
|
+
return "parse-error";
|
|
2149
|
+
}
|
|
2150
|
+
extensions[shortName] = {
|
|
2151
|
+
enabled: true,
|
|
2152
|
+
type: "streamable_http",
|
|
2153
|
+
name: shortName,
|
|
2154
|
+
description: "",
|
|
2155
|
+
uri: proxyUrl,
|
|
2156
|
+
envs: {},
|
|
2157
|
+
env_keys: [],
|
|
2158
|
+
headers: { Authorization: bearerHeader },
|
|
2159
|
+
timeout: 300,
|
|
2160
|
+
socket: null,
|
|
2161
|
+
bundled: null,
|
|
2162
|
+
available_tools: []
|
|
2163
|
+
};
|
|
2164
|
+
root["extensions"] = extensions;
|
|
2165
|
+
const out = stringify(root, { indent: 2, lineWidth: 0 });
|
|
2166
|
+
const body = out.endsWith("\n") ? out : `${out}
|
|
2167
|
+
`;
|
|
2168
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
2169
|
+
await writeFile(filePath, body, SECRET_JSON_FILE_OPTIONS);
|
|
2170
|
+
writeMcpAddedLine(shortName, filePath);
|
|
2171
|
+
return "ok";
|
|
2172
|
+
}
|
|
1977
2173
|
function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
1978
2174
|
const hostedInlinePlatforms = /* @__PURE__ */ new Set([
|
|
1979
2175
|
"cursor",
|
|
@@ -1993,14 +2189,12 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
1993
2189
|
if (platform === "github-copilot") {
|
|
1994
2190
|
snippetText = JSON.stringify(
|
|
1995
2191
|
{
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
Authorization: authHeader
|
|
2003
|
-
}
|
|
2192
|
+
servers: {
|
|
2193
|
+
[shortName]: {
|
|
2194
|
+
type: "http",
|
|
2195
|
+
url: urlInSnippet,
|
|
2196
|
+
headers: {
|
|
2197
|
+
Authorization: authHeader
|
|
2004
2198
|
}
|
|
2005
2199
|
}
|
|
2006
2200
|
}
|
|
@@ -2039,18 +2233,32 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
2039
2233
|
2
|
|
2040
2234
|
);
|
|
2041
2235
|
} else if (platform === "continue-dev") {
|
|
2236
|
+
const sn = sanitiseYamlValue(shortName);
|
|
2237
|
+
const urlEsc = sanitiseYamlValue(urlInSnippet);
|
|
2238
|
+
const authEsc = sanitiseYamlValue(authHeader);
|
|
2239
|
+
snippetText = `name: ${sn}
|
|
2240
|
+
version: 0.0.1
|
|
2241
|
+
schema: v1
|
|
2242
|
+
mcpServers:
|
|
2243
|
+
- name: ${sn}
|
|
2244
|
+
type: streamable-http
|
|
2245
|
+
url: ${urlEsc}
|
|
2246
|
+
headers:
|
|
2247
|
+
Authorization: ${authEsc}
|
|
2248
|
+
`;
|
|
2249
|
+
} else if (platform === "kilo-code") {
|
|
2042
2250
|
snippetText = JSON.stringify(
|
|
2043
2251
|
{
|
|
2044
|
-
|
|
2045
|
-
{
|
|
2046
|
-
|
|
2047
|
-
type: "streamable-http",
|
|
2252
|
+
mcp: {
|
|
2253
|
+
[shortName]: {
|
|
2254
|
+
type: "remote",
|
|
2048
2255
|
url: urlInSnippet,
|
|
2049
2256
|
headers: {
|
|
2050
2257
|
Authorization: authHeader
|
|
2051
|
-
}
|
|
2258
|
+
},
|
|
2259
|
+
enabled: true
|
|
2052
2260
|
}
|
|
2053
|
-
|
|
2261
|
+
}
|
|
2054
2262
|
},
|
|
2055
2263
|
null,
|
|
2056
2264
|
2
|
|
@@ -2090,16 +2298,18 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
2090
2298
|
);
|
|
2091
2299
|
} else if (platform === "kilo-code") {
|
|
2092
2300
|
process.stderr.write(
|
|
2093
|
-
"\n" + style.dim(`Add this to ${join(resolve(process.cwd()), ".
|
|
2301
|
+
"\n" + style.dim(`Add this to ${join(resolve(process.cwd()), ".kilo", "kilo.jsonc")}:`) + "\n\n"
|
|
2094
2302
|
);
|
|
2095
2303
|
} else if (platform === "github-copilot") {
|
|
2096
2304
|
process.stderr.write(
|
|
2097
2305
|
"\n" + style.dim(
|
|
2098
|
-
"
|
|
2306
|
+
"Create .vscode/mcp.json in your workspace root (create the .vscode folder if it does not exist). After saving, reload VS Code and confirm the server appears in Copilot Agent mode under Tools."
|
|
2099
2307
|
) + "\n\n"
|
|
2100
2308
|
);
|
|
2101
2309
|
} else if (platform === "continue-dev") {
|
|
2102
|
-
process.stderr.write(
|
|
2310
|
+
process.stderr.write(
|
|
2311
|
+
"\n" + style.dim(`Save this as .continue/mcpServers/${shortName}.yaml in your workspace root.`) + "\n\n"
|
|
2312
|
+
);
|
|
2103
2313
|
} else if (platform === "goose") {
|
|
2104
2314
|
process.stderr.write(
|
|
2105
2315
|
"\n" + style.dim("Add this to ~/.config/goose/config.yaml under the extensions key.") + "\n\n"
|
|
@@ -2276,7 +2486,7 @@ async function runInit(explicitBaseUrl, options) {
|
|
|
2276
2486
|
}
|
|
2277
2487
|
await warnIfInstalledShieldIsOutdated();
|
|
2278
2488
|
const configuredAgents = [];
|
|
2279
|
-
let currentAgents = collectAgentsFromConfig(existing);
|
|
2489
|
+
let currentAgents = mergeAgentsForUniqueNames(collectAgentsFromConfig(existing));
|
|
2280
2490
|
let lastConfig = {
|
|
2281
2491
|
apiKey,
|
|
2282
2492
|
baseUrl: resolvedBaseUrl,
|
|
@@ -2894,6 +3104,14 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2894
3104
|
}
|
|
2895
3105
|
rl.close();
|
|
2896
3106
|
if (configuredAgents.length > 0) {
|
|
3107
|
+
let mcpPromptLabel2 = function(platformSlug) {
|
|
3108
|
+
const rows = configuredAgents.filter((a) => a.platform === platformSlug);
|
|
3109
|
+
const last = rows[rows.length - 1];
|
|
3110
|
+
if (last === void 0) return "shield-mcp";
|
|
3111
|
+
const s = typeof last.shortName === "string" ? last.shortName.trim() : "";
|
|
3112
|
+
if (s.length > 0) return s;
|
|
3113
|
+
return last.agentName.trim().length > 0 ? last.agentName.trim() : "shield-mcp";
|
|
3114
|
+
};
|
|
2897
3115
|
process.stderr.write("\n" + style.bold(style.violet("Setup complete")) + "\n\n");
|
|
2898
3116
|
for (const agent of configuredAgents) {
|
|
2899
3117
|
const namePart = agent.agentName.length > 0 ? ` - ${style.cyan(agent.agentName)}` : "";
|
|
@@ -2905,14 +3123,6 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2905
3123
|
}
|
|
2906
3124
|
process.stderr.write("\n");
|
|
2907
3125
|
const configuredPlatforms = new Set(configuredAgents.map((a) => a.platform));
|
|
2908
|
-
const cursorMcpPromptLabel = (() => {
|
|
2909
|
-
const rows = configuredAgents.filter((a) => a.platform === "cursor");
|
|
2910
|
-
const last = rows[rows.length - 1];
|
|
2911
|
-
if (last === void 0) return "shield-mcp";
|
|
2912
|
-
const s = typeof last.shortName === "string" ? last.shortName.trim() : "";
|
|
2913
|
-
if (s.length > 0) return s;
|
|
2914
|
-
return last.agentName.trim().length > 0 ? last.agentName.trim() : "shield-mcp";
|
|
2915
|
-
})();
|
|
2916
3126
|
const blocks = [];
|
|
2917
3127
|
if (configuredPlatforms.has("openclaw")) {
|
|
2918
3128
|
blocks.push(
|
|
@@ -2925,33 +3135,39 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2925
3135
|
);
|
|
2926
3136
|
}
|
|
2927
3137
|
if (configuredPlatforms.has("claude-desktop")) {
|
|
3138
|
+
const cdLabel = mcpPromptLabel2("claude-desktop");
|
|
2928
3139
|
blocks.push(
|
|
2929
|
-
"\n" + style.bold("Claude Desktop") +
|
|
3140
|
+
"\n" + style.bold("Claude Desktop") + '\n \u2192 Restart Claude Desktop to load the updated configuration\n \u2192 Confirm connection: click your profile (bottom-left) \u2192 Settings \u2192 Developer\n Check that "' + cdLabel + '" shows a green "running" status\n \u2192 Try it: paste this into Claude Desktop:\n "Use the ' + cdLabel + ' MCP server to list my GitHub repositories"\n'
|
|
2930
3141
|
);
|
|
2931
3142
|
}
|
|
2932
3143
|
if (configuredPlatforms.has("cursor")) {
|
|
3144
|
+
const cursorLabel = mcpPromptLabel2("cursor");
|
|
2933
3145
|
blocks.push(
|
|
2934
|
-
"\n" + style.bold("Cursor") + "\n \u2192 If needed, download Cursor from " + style.cyan("https://www.cursor.com/downloads") + '\n \u2192 Restart Cursor so it loads the MCP server\n \u2192
|
|
3146
|
+
"\n" + style.bold("Cursor") + "\n \u2192 If needed, download Cursor from " + style.cyan("https://www.cursor.com/downloads") + '\n \u2192 Restart Cursor so it loads the MCP server\n \u2192 Confirm connection: open Settings \u2192 Tools & MCPs\n Check that "' + cursorLabel + '" shows a green status indicator\n \u2192 Try it: paste this into Cursor:\n "Use the ' + cursorLabel + ' MCP server to list my GitHub repositories"\n'
|
|
2935
3147
|
);
|
|
2936
3148
|
}
|
|
2937
3149
|
if (configuredPlatforms.has("kilo-code")) {
|
|
3150
|
+
const kiloLabel = mcpPromptLabel2("kilo-code");
|
|
2938
3151
|
blocks.push(
|
|
2939
|
-
"\n" + style.bold("Kilo Code") +
|
|
3152
|
+
"\n" + style.bold("Kilo Code") + '\n \u2192 Restart the editor or reload the window if the MCP server does not appear\n \u2192 Confirm connection: Settings \u2192 Agent Behaviour \u2192 MCP Servers\n \u2192 Try it: paste this into Kilo Code:\n "Use the ' + kiloLabel + ' MCP server to list my GitHub repositories"\n'
|
|
2940
3153
|
);
|
|
2941
3154
|
}
|
|
2942
3155
|
if (configuredPlatforms.has("github-copilot")) {
|
|
3156
|
+
const copilotLabel = mcpPromptLabel2("github-copilot");
|
|
2943
3157
|
blocks.push(
|
|
2944
|
-
"\n" + style.bold("GitHub Copilot") +
|
|
3158
|
+
"\n" + style.bold("GitHub Copilot") + '\n \u2192 Reload the editor window if the MCP server does not appear\n \u2192 Confirm connection: open Copilot chat in Agent mode and confirm the server appears under Tools\n \u2192 Try it: paste this into GitHub Copilot:\n "Use the ' + copilotLabel + ' MCP server to list my GitHub repositories"\n'
|
|
2945
3159
|
);
|
|
2946
3160
|
}
|
|
2947
3161
|
if (configuredPlatforms.has("continue-dev")) {
|
|
3162
|
+
const continueLabel = mcpPromptLabel2("continue-dev");
|
|
2948
3163
|
blocks.push(
|
|
2949
|
-
"\n" + style.bold("Continue") + "\n \u2192 If needed, install Continue from " + style.cyan("https://docs.continue.dev/ide-extensions/install") +
|
|
3164
|
+
"\n" + style.bold("Continue") + "\n \u2192 If needed, install Continue from " + style.cyan("https://docs.continue.dev/ide-extensions/install") + '\n \u2192 Reload VS Code and open Continue agent mode\n \u2192 Confirm connection: Settings \u2192 Tools \u2192 MCP Servers\n \u2192 Try it: paste this into Continue:\n "Use the ' + continueLabel + ' MCP server to list my GitHub repositories"\n'
|
|
2950
3165
|
);
|
|
2951
3166
|
}
|
|
2952
3167
|
if (configuredPlatforms.has("goose")) {
|
|
3168
|
+
const gooseLabel = mcpPromptLabel2("goose");
|
|
2953
3169
|
blocks.push(
|
|
2954
|
-
"\n" + style.bold("Goose") +
|
|
3170
|
+
"\n" + style.bold("Goose") + '\n \u2192 Start a new Goose session after updating config\n \u2192 Confirm connection: check the Extensions page in the sidebar\n \u2192 Try it: paste this into Goose:\n "Use the ' + gooseLabel + ' MCP server to list my GitHub repositories"\n'
|
|
2955
3171
|
);
|
|
2956
3172
|
}
|
|
2957
3173
|
const windsurfNativeConfigured = configuredAgents.some(
|
|
@@ -3397,9 +3613,13 @@ function extractToolCallParams(request) {
|
|
|
3397
3613
|
if (typeof args !== "object" || args === null) return null;
|
|
3398
3614
|
return { name, arguments: args };
|
|
3399
3615
|
}
|
|
3400
|
-
function buildBlockedResponse(id, service,
|
|
3616
|
+
function buildBlockedResponse(id, service, _permissionLevel, dashboardUrl) {
|
|
3401
3617
|
const displayService = capitalize(service);
|
|
3402
|
-
const message = `Action blocked by
|
|
3618
|
+
const message = `Action blocked by Shield
|
|
3619
|
+
|
|
3620
|
+
This agent cannot use ${displayService}.
|
|
3621
|
+
|
|
3622
|
+
Configure permissions: ${dashboardUrl}`;
|
|
3403
3623
|
return {
|
|
3404
3624
|
jsonrpc: "2.0",
|
|
3405
3625
|
id,
|
|
@@ -3901,6 +4121,13 @@ async function restoreClaudeDesktopMcpFromBackup() {
|
|
|
3901
4121
|
});
|
|
3902
4122
|
}
|
|
3903
4123
|
|
|
4124
|
+
// package.json
|
|
4125
|
+
var package_default = {
|
|
4126
|
+
version: "1.7.0"};
|
|
4127
|
+
|
|
4128
|
+
// src/package-meta.ts
|
|
4129
|
+
var PACKAGE_VERSION = package_default.version;
|
|
4130
|
+
|
|
3904
4131
|
// bin/multicorn-shield.ts
|
|
3905
4132
|
function parseArgs(argv) {
|
|
3906
4133
|
const args = argv.slice(2);
|
|
@@ -4053,6 +4280,7 @@ function printHelp() {
|
|
|
4053
4280
|
" Shield's permission layer.",
|
|
4054
4281
|
"",
|
|
4055
4282
|
"Options:",
|
|
4283
|
+
" --version, -v Print version and exit",
|
|
4056
4284
|
" --verbose, --debug Print extra diagnostics during init (menu selection, agent counts)",
|
|
4057
4285
|
" --api-key <key> Multicorn API key (overrides MULTICORN_API_KEY env var and config file)",
|
|
4058
4286
|
" --log-level <level> Log level: debug | info | warn | error (default: info)",
|
|
@@ -4077,6 +4305,11 @@ async function runCli() {
|
|
|
4077
4305
|
);
|
|
4078
4306
|
return;
|
|
4079
4307
|
}
|
|
4308
|
+
if (first === "--version" || first === "-v") {
|
|
4309
|
+
process.stdout.write(`${PACKAGE_VERSION}
|
|
4310
|
+
`);
|
|
4311
|
+
process.exit(0);
|
|
4312
|
+
}
|
|
4080
4313
|
const cli = parseArgs(process.argv);
|
|
4081
4314
|
const logger = createLogger(cli.logLevel);
|
|
4082
4315
|
if (cli.subcommand === "help") {
|
package/dist/proxy.cjs
CHANGED
|
@@ -30,9 +30,13 @@ function extractToolCallParams(request) {
|
|
|
30
30
|
if (typeof args !== "object" || args === null) return null;
|
|
31
31
|
return { name, arguments: args };
|
|
32
32
|
}
|
|
33
|
-
function buildBlockedResponse(id, service,
|
|
33
|
+
function buildBlockedResponse(id, service, _permissionLevel, dashboardUrl) {
|
|
34
34
|
const displayService = capitalize(service);
|
|
35
|
-
const message = `Action blocked by
|
|
35
|
+
const message = `Action blocked by Shield
|
|
36
|
+
|
|
37
|
+
This agent cannot use ${displayService}.
|
|
38
|
+
|
|
39
|
+
Configure permissions: ${dashboardUrl}`;
|
|
36
40
|
return {
|
|
37
41
|
jsonrpc: "2.0",
|
|
38
42
|
id,
|
package/dist/proxy.d.cts
CHANGED
|
@@ -31,7 +31,7 @@ interface ToolCallParams {
|
|
|
31
31
|
}
|
|
32
32
|
declare function parseJsonRpcLine(line: string): JsonRpcRequest | null;
|
|
33
33
|
declare function extractToolCallParams(request: JsonRpcRequest): ToolCallParams | null;
|
|
34
|
-
declare function buildBlockedResponse(id: string | number | null, service: string,
|
|
34
|
+
declare function buildBlockedResponse(id: string | number | null, service: string, _permissionLevel: string, dashboardUrl: string): JsonRpcResponse;
|
|
35
35
|
declare function buildSpendingBlockedResponse(id: string | number | null, reason: string, dashboardUrl: string): JsonRpcResponse;
|
|
36
36
|
/**
|
|
37
37
|
* Internal error: Shield could not verify permissions due to an exception.
|