multicorn-shield 0.13.0 → 1.0.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 +21 -13
- package/README.md +5 -5
- package/dist/multicorn-proxy.js +302 -133
- package/dist/multicorn-shield.js +3091 -31
- package/dist/openclaw-plugin/multicorn-shield.js +2 -2
- package/dist/proxy.cjs +1 -1
- package/dist/proxy.js +1 -1
- package/dist/shield-extension.js +1 -1
- package/package.json +3 -3
- package/plugins/windsurf/README.md +2 -2
package/dist/multicorn-proxy.js
CHANGED
|
@@ -1,31 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync, statSync } from 'fs';
|
|
3
|
-
import { mkdir, writeFile,
|
|
4
|
-
import {
|
|
3
|
+
import { readFile, mkdir, writeFile, copyFile, chmod, unlink } from 'fs/promises';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { createInterface } from 'readline';
|
|
8
|
-
import { spawn } from 'child_process';
|
|
9
8
|
import { createHash } from 'crypto';
|
|
9
|
+
import { spawn } from 'child_process';
|
|
10
10
|
import 'stream';
|
|
11
11
|
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
dim: (s) => `\x1B[2m${s}\x1B[0m`
|
|
12
|
+
var __defProp = Object.defineProperty;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __esm = (fn, res) => function __init() {
|
|
15
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
16
|
+
};
|
|
17
|
+
var __export = (target, all) => {
|
|
18
|
+
for (var name in all)
|
|
19
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
21
20
|
};
|
|
22
|
-
var BANNER = [
|
|
23
|
-
" \u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588 \u2588 \u2588\u2588\u2584 ",
|
|
24
|
-
" \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588",
|
|
25
|
-
" \u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588",
|
|
26
|
-
" \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588",
|
|
27
|
-
" \u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2580 "
|
|
28
|
-
].map((line) => style.violet(line)).join("\n");
|
|
29
21
|
function withSpinner(message) {
|
|
30
22
|
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
31
23
|
let i = 0;
|
|
@@ -43,12 +35,6 @@ function withSpinner(message) {
|
|
|
43
35
|
}
|
|
44
36
|
};
|
|
45
37
|
}
|
|
46
|
-
var NativePluginPrerequisiteMissingError = class extends Error {
|
|
47
|
-
constructor() {
|
|
48
|
-
super("Native plugin prerequisites not met");
|
|
49
|
-
this.name = "NativePluginPrerequisiteMissingError";
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
38
|
function isExistingDirectory(path) {
|
|
53
39
|
try {
|
|
54
40
|
if (!existsSync(path)) return false;
|
|
@@ -60,10 +46,6 @@ function isExistingDirectory(path) {
|
|
|
60
46
|
function nativePluginSkippedSaveNote(wizardCommand, productName) {
|
|
61
47
|
return "\n" + style.dim("Your agent config has been saved. Run ") + style.cyan(wizardCommand) + style.dim(` again after installing ${productName} to complete hook setup.`) + "\n";
|
|
62
48
|
}
|
|
63
|
-
var CONFIG_DIR = join(homedir(), ".multicorn");
|
|
64
|
-
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
65
|
-
var OPENCLAW_CONFIG_PATH = join(homedir(), ".openclaw", "openclaw.json");
|
|
66
|
-
var ANSI_PATTERN = new RegExp(String.fromCharCode(27) + "\\[[0-9;]*[a-zA-Z]", "g");
|
|
67
49
|
function stripAnsi(str) {
|
|
68
50
|
return str.replace(ANSI_PATTERN, "");
|
|
69
51
|
}
|
|
@@ -207,7 +189,6 @@ async function saveConfig(config) {
|
|
|
207
189
|
mode: 384
|
|
208
190
|
});
|
|
209
191
|
}
|
|
210
|
-
var OPENCLAW_MIN_VERSION = "2026.2.26";
|
|
211
192
|
async function detectOpenClaw() {
|
|
212
193
|
let raw;
|
|
213
194
|
try {
|
|
@@ -373,7 +354,8 @@ async function isCursorConnected() {
|
|
|
373
354
|
const url = rec["url"];
|
|
374
355
|
if (typeof url === "string" && url.includes("multicorn")) return true;
|
|
375
356
|
const args = rec["args"];
|
|
376
|
-
if (Array.isArray(args) && args.includes("multicorn-proxy"))
|
|
357
|
+
if (Array.isArray(args) && (args.includes("multicorn-shield") || args.includes("multicorn-proxy")))
|
|
358
|
+
return true;
|
|
377
359
|
}
|
|
378
360
|
return false;
|
|
379
361
|
} catch (err) {
|
|
@@ -452,7 +434,7 @@ async function installWindsurfNativeHooks() {
|
|
|
452
434
|
"Open Windsurf at least once so this folder exists, or install from:\n " + style.cyan("https://windsurf.com/download") + "\n\n"
|
|
453
435
|
);
|
|
454
436
|
process.stderr.write("Then run this wizard again:\n");
|
|
455
|
-
process.stderr.write(" " + style.cyan("npx multicorn-
|
|
437
|
+
process.stderr.write(" " + style.cyan("npx multicorn-shield init") + "\n");
|
|
456
438
|
throw new NativePluginPrerequisiteMissingError();
|
|
457
439
|
}
|
|
458
440
|
const installDir = getWindsurfHooksInstallDir();
|
|
@@ -770,35 +752,6 @@ function getClaudeDesktopConfigPath() {
|
|
|
770
752
|
);
|
|
771
753
|
}
|
|
772
754
|
}
|
|
773
|
-
var PLATFORM_LABELS = [
|
|
774
|
-
"OpenClaw",
|
|
775
|
-
"Claude Code",
|
|
776
|
-
"Cursor",
|
|
777
|
-
"Windsurf",
|
|
778
|
-
"Cline",
|
|
779
|
-
"Claude Desktop",
|
|
780
|
-
"Gemini CLI",
|
|
781
|
-
"Local MCP / Other"
|
|
782
|
-
];
|
|
783
|
-
var PLATFORM_BY_SELECTION = {
|
|
784
|
-
1: "openclaw",
|
|
785
|
-
2: "claude-code",
|
|
786
|
-
3: "cursor",
|
|
787
|
-
4: "windsurf",
|
|
788
|
-
5: "cline",
|
|
789
|
-
6: "claude-desktop",
|
|
790
|
-
7: "gemini-cli",
|
|
791
|
-
8: "other-mcp"
|
|
792
|
-
};
|
|
793
|
-
var DEFAULT_AGENT_NAMES = {
|
|
794
|
-
openclaw: "my-openclaw-agent",
|
|
795
|
-
"claude-code": "my-claude-code-agent",
|
|
796
|
-
cursor: "my-cursor-agent",
|
|
797
|
-
windsurf: "my-windsurf-agent",
|
|
798
|
-
cline: "my-cline-agent",
|
|
799
|
-
"claude-desktop": "my-claude-desktop-agent",
|
|
800
|
-
"gemini-cli": "my-gemini-cli-agent"
|
|
801
|
-
};
|
|
802
755
|
async function promptPlatformSelection(ask) {
|
|
803
756
|
process.stderr.write(
|
|
804
757
|
"\n" + style.bold(style.violet("Which platform are you connecting?")) + "\n"
|
|
@@ -817,7 +770,7 @@ async function promptPlatformSelection(ask) {
|
|
|
817
770
|
);
|
|
818
771
|
}
|
|
819
772
|
process.stderr.write(
|
|
820
|
-
style.dim(" Pick 8 if you want to wrap a local MCP server with multicorn-
|
|
773
|
+
style.dim(" Pick 8 if you want to wrap a local MCP server with multicorn-shield --wrap.") + "\n"
|
|
821
774
|
);
|
|
822
775
|
let selection = 0;
|
|
823
776
|
while (selection === 0) {
|
|
@@ -1036,7 +989,6 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
1036
989
|
);
|
|
1037
990
|
}
|
|
1038
991
|
}
|
|
1039
|
-
var DEFAULT_SHIELD_API_BASE_URL = "https://api.multicorn.ai";
|
|
1040
992
|
async function runInit(explicitBaseUrl) {
|
|
1041
993
|
if (!process.stdin.isTTY) {
|
|
1042
994
|
process.stderr.write(
|
|
@@ -1140,7 +1092,7 @@ async function runInit(explicitBaseUrl) {
|
|
|
1140
1092
|
);
|
|
1141
1093
|
process.stderr.write(
|
|
1142
1094
|
"\n" + style.bold("Try it:") + " " + style.cyan(
|
|
1143
|
-
"npx multicorn-
|
|
1095
|
+
"npx multicorn-shield --wrap npx @modelcontextprotocol/server-filesystem /tmp"
|
|
1144
1096
|
) + "\n"
|
|
1145
1097
|
);
|
|
1146
1098
|
} catch (error) {
|
|
@@ -1190,7 +1142,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
1190
1142
|
}
|
|
1191
1143
|
if (detection.status === "not-found") {
|
|
1192
1144
|
process.stderr.write(
|
|
1193
|
-
style.red("\u2717") + " OpenClaw is not installed. Install OpenClaw first, then run npx multicorn-
|
|
1145
|
+
style.red("\u2717") + " OpenClaw is not installed. Install OpenClaw first, then run npx multicorn-shield init again.\n"
|
|
1194
1146
|
);
|
|
1195
1147
|
rl.close();
|
|
1196
1148
|
return null;
|
|
@@ -1306,7 +1258,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
1306
1258
|
} catch (error) {
|
|
1307
1259
|
if (error instanceof NativePluginPrerequisiteMissingError) {
|
|
1308
1260
|
postSaveNativeSkipNote = nativePluginSkippedSaveNote(
|
|
1309
|
-
"npx multicorn-
|
|
1261
|
+
"npx multicorn-shield init",
|
|
1310
1262
|
"Windsurf"
|
|
1311
1263
|
);
|
|
1312
1264
|
configuredAgents.push({
|
|
@@ -1689,22 +1641,96 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
1689
1641
|
}
|
|
1690
1642
|
return lastConfig;
|
|
1691
1643
|
}
|
|
1644
|
+
var style, BANNER, NativePluginPrerequisiteMissingError, CONFIG_DIR, CONFIG_PATH, OPENCLAW_CONFIG_PATH, ANSI_PATTERN, OPENCLAW_MIN_VERSION, PLATFORM_LABELS, PLATFORM_BY_SELECTION, DEFAULT_AGENT_NAMES, DEFAULT_SHIELD_API_BASE_URL;
|
|
1645
|
+
var init_config = __esm({
|
|
1646
|
+
"src/proxy/config.ts"() {
|
|
1647
|
+
style = {
|
|
1648
|
+
violet: (s) => `\x1B[38;2;124;58;237m${s}\x1B[0m`,
|
|
1649
|
+
violetLight: (s) => `\x1B[38;2;167;139;250m${s}\x1B[0m`,
|
|
1650
|
+
green: (s) => `\x1B[38;2;34;197;94m${s}\x1B[0m`,
|
|
1651
|
+
yellow: (s) => `\x1B[38;2;245;158;11m${s}\x1B[0m`,
|
|
1652
|
+
red: (s) => `\x1B[38;2;239;68;68m${s}\x1B[0m`,
|
|
1653
|
+
cyan: (s) => `\x1B[38;2;6;182;212m${s}\x1B[0m`,
|
|
1654
|
+
bold: (s) => `\x1B[1m${s}\x1B[0m`,
|
|
1655
|
+
dim: (s) => `\x1B[2m${s}\x1B[0m`
|
|
1656
|
+
};
|
|
1657
|
+
BANNER = [
|
|
1658
|
+
" \u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588 \u2588 \u2588\u2588\u2584 ",
|
|
1659
|
+
" \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588",
|
|
1660
|
+
" \u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588",
|
|
1661
|
+
" \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588",
|
|
1662
|
+
" \u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2580 "
|
|
1663
|
+
].map((line) => style.violet(line)).join("\n");
|
|
1664
|
+
NativePluginPrerequisiteMissingError = class extends Error {
|
|
1665
|
+
constructor() {
|
|
1666
|
+
super("Native plugin prerequisites not met");
|
|
1667
|
+
this.name = "NativePluginPrerequisiteMissingError";
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
CONFIG_DIR = join(homedir(), ".multicorn");
|
|
1671
|
+
CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
1672
|
+
OPENCLAW_CONFIG_PATH = join(homedir(), ".openclaw", "openclaw.json");
|
|
1673
|
+
ANSI_PATTERN = new RegExp(String.fromCharCode(27) + "\\[[0-9;]*[a-zA-Z]", "g");
|
|
1674
|
+
OPENCLAW_MIN_VERSION = "2026.2.26";
|
|
1675
|
+
PLATFORM_LABELS = [
|
|
1676
|
+
"OpenClaw",
|
|
1677
|
+
"Claude Code",
|
|
1678
|
+
"Cursor",
|
|
1679
|
+
"Windsurf",
|
|
1680
|
+
"Cline",
|
|
1681
|
+
"Claude Desktop",
|
|
1682
|
+
"Gemini CLI",
|
|
1683
|
+
"Local MCP / Other"
|
|
1684
|
+
];
|
|
1685
|
+
PLATFORM_BY_SELECTION = {
|
|
1686
|
+
1: "openclaw",
|
|
1687
|
+
2: "claude-code",
|
|
1688
|
+
3: "cursor",
|
|
1689
|
+
4: "windsurf",
|
|
1690
|
+
5: "cline",
|
|
1691
|
+
6: "claude-desktop",
|
|
1692
|
+
7: "gemini-cli",
|
|
1693
|
+
8: "other-mcp"
|
|
1694
|
+
};
|
|
1695
|
+
DEFAULT_AGENT_NAMES = {
|
|
1696
|
+
openclaw: "my-openclaw-agent",
|
|
1697
|
+
"claude-code": "my-claude-code-agent",
|
|
1698
|
+
cursor: "my-cursor-agent",
|
|
1699
|
+
windsurf: "my-windsurf-agent",
|
|
1700
|
+
cline: "my-cline-agent",
|
|
1701
|
+
"claude-desktop": "my-claude-desktop-agent",
|
|
1702
|
+
"gemini-cli": "my-gemini-cli-agent"
|
|
1703
|
+
};
|
|
1704
|
+
DEFAULT_SHIELD_API_BASE_URL = "https://api.multicorn.ai";
|
|
1705
|
+
}
|
|
1706
|
+
});
|
|
1692
1707
|
|
|
1693
1708
|
// src/types/index.ts
|
|
1694
|
-
var PERMISSION_LEVELS
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1709
|
+
var PERMISSION_LEVELS;
|
|
1710
|
+
var init_types = __esm({
|
|
1711
|
+
"src/types/index.ts"() {
|
|
1712
|
+
PERMISSION_LEVELS = {
|
|
1713
|
+
Read: "read",
|
|
1714
|
+
Write: "write",
|
|
1715
|
+
Execute: "execute",
|
|
1716
|
+
Publish: "publish",
|
|
1717
|
+
Create: "create"
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1701
1721
|
|
|
1702
1722
|
// src/scopes/scope-parser.ts
|
|
1703
|
-
var VALID_PERMISSION_LEVELS = new Set(Object.values(PERMISSION_LEVELS));
|
|
1704
|
-
[...VALID_PERMISSION_LEVELS].join(", ");
|
|
1705
1723
|
function formatScope(scope) {
|
|
1706
1724
|
return `${scope.permissionLevel}:${scope.service}`;
|
|
1707
1725
|
}
|
|
1726
|
+
var VALID_PERMISSION_LEVELS;
|
|
1727
|
+
var init_scope_parser = __esm({
|
|
1728
|
+
"src/scopes/scope-parser.ts"() {
|
|
1729
|
+
init_types();
|
|
1730
|
+
VALID_PERMISSION_LEVELS = new Set(Object.values(PERMISSION_LEVELS));
|
|
1731
|
+
[...VALID_PERMISSION_LEVELS].join(", ");
|
|
1732
|
+
}
|
|
1733
|
+
});
|
|
1708
1734
|
|
|
1709
1735
|
// src/scopes/scope-validator.ts
|
|
1710
1736
|
function validateScopeAccess(grantedScopes, requested) {
|
|
@@ -1732,6 +1758,11 @@ function hasScope(grantedScopes, requested) {
|
|
|
1732
1758
|
(granted) => granted.service === requested.service && granted.permissionLevel === requested.permissionLevel
|
|
1733
1759
|
);
|
|
1734
1760
|
}
|
|
1761
|
+
var init_scope_validator = __esm({
|
|
1762
|
+
"src/scopes/scope-validator.ts"() {
|
|
1763
|
+
init_scope_parser();
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1735
1766
|
|
|
1736
1767
|
// src/logger/action-logger.ts
|
|
1737
1768
|
function createActionLogger(config) {
|
|
@@ -1905,6 +1936,10 @@ function createActionLogger(config) {
|
|
|
1905
1936
|
function sleep(ms) {
|
|
1906
1937
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1907
1938
|
}
|
|
1939
|
+
var init_action_logger = __esm({
|
|
1940
|
+
"src/logger/action-logger.ts"() {
|
|
1941
|
+
}
|
|
1942
|
+
});
|
|
1908
1943
|
|
|
1909
1944
|
// src/spending/spending-checker.ts
|
|
1910
1945
|
function createSpendingChecker(config) {
|
|
@@ -2037,13 +2072,12 @@ function validateLimits(limits) {
|
|
|
2037
2072
|
function dollarsToCents(dollars) {
|
|
2038
2073
|
return Math.round(dollars * 100);
|
|
2039
2074
|
}
|
|
2075
|
+
var init_spending_checker = __esm({
|
|
2076
|
+
"src/spending/spending-checker.ts"() {
|
|
2077
|
+
}
|
|
2078
|
+
});
|
|
2040
2079
|
|
|
2041
2080
|
// src/proxy/interceptor.ts
|
|
2042
|
-
var BLOCKED_ERROR_CODE = -32e3;
|
|
2043
|
-
var SPENDING_BLOCKED_ERROR_CODE = -32001;
|
|
2044
|
-
var INTERNAL_ERROR_CODE = -32002;
|
|
2045
|
-
var SERVICE_UNREACHABLE_ERROR_CODE = -32003;
|
|
2046
|
-
var AUTH_ERROR_CODE = -32004;
|
|
2047
2081
|
function parseJsonRpcLine(line) {
|
|
2048
2082
|
const trimmed = line.trim();
|
|
2049
2083
|
if (trimmed.length === 0) return null;
|
|
@@ -2111,7 +2145,7 @@ function buildServiceUnreachableResponse(id, dashboardUrl) {
|
|
|
2111
2145
|
};
|
|
2112
2146
|
}
|
|
2113
2147
|
function buildAuthErrorResponse(id) {
|
|
2114
|
-
const message = "Action blocked: Shield API key is invalid or has been revoked. Run npx multicorn-
|
|
2148
|
+
const message = "Action blocked: Shield API key is invalid or has been revoked. Run npx multicorn-shield init to reconfigure.";
|
|
2115
2149
|
return {
|
|
2116
2150
|
jsonrpc: "2.0",
|
|
2117
2151
|
id,
|
|
@@ -2143,9 +2177,16 @@ function capitalize(str) {
|
|
|
2143
2177
|
const first = str[0];
|
|
2144
2178
|
return first !== void 0 ? first.toUpperCase() + str.slice(1) : str;
|
|
2145
2179
|
}
|
|
2146
|
-
var
|
|
2147
|
-
var
|
|
2148
|
-
|
|
2180
|
+
var BLOCKED_ERROR_CODE, SPENDING_BLOCKED_ERROR_CODE, INTERNAL_ERROR_CODE, SERVICE_UNREACHABLE_ERROR_CODE, AUTH_ERROR_CODE;
|
|
2181
|
+
var init_interceptor = __esm({
|
|
2182
|
+
"src/proxy/interceptor.ts"() {
|
|
2183
|
+
BLOCKED_ERROR_CODE = -32e3;
|
|
2184
|
+
SPENDING_BLOCKED_ERROR_CODE = -32001;
|
|
2185
|
+
INTERNAL_ERROR_CODE = -32002;
|
|
2186
|
+
SERVICE_UNREACHABLE_ERROR_CODE = -32003;
|
|
2187
|
+
AUTH_ERROR_CODE = -32004;
|
|
2188
|
+
}
|
|
2189
|
+
});
|
|
2149
2190
|
function cacheKey(agentName, apiKey) {
|
|
2150
2191
|
return createHash("sha256").update(`${agentName}:${apiKey}`).digest("hex").slice(0, 16);
|
|
2151
2192
|
}
|
|
@@ -2216,10 +2257,14 @@ async function saveCachedScopes(agentName, agentId, scopes, apiKey) {
|
|
|
2216
2257
|
function isScopesCacheFile(value) {
|
|
2217
2258
|
return typeof value === "object" && value !== null;
|
|
2218
2259
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2260
|
+
var MULTICORN_DIR, SCOPES_PATH, CACHE_META_PATH;
|
|
2261
|
+
var init_scope_cache = __esm({
|
|
2262
|
+
"src/openclaw/scope-cache.ts"() {
|
|
2263
|
+
MULTICORN_DIR = join(homedir(), ".multicorn");
|
|
2264
|
+
SCOPES_PATH = join(MULTICORN_DIR, "scopes.json");
|
|
2265
|
+
CACHE_META_PATH = join(MULTICORN_DIR, "cache-meta.json");
|
|
2266
|
+
}
|
|
2267
|
+
});
|
|
2223
2268
|
function deriveDashboardUrl(baseUrl) {
|
|
2224
2269
|
try {
|
|
2225
2270
|
const url = new URL(baseUrl);
|
|
@@ -2244,13 +2289,6 @@ function deriveDashboardUrl(baseUrl) {
|
|
|
2244
2289
|
return "https://app.multicorn.ai";
|
|
2245
2290
|
}
|
|
2246
2291
|
}
|
|
2247
|
-
var ShieldAuthError = class _ShieldAuthError extends Error {
|
|
2248
|
-
constructor(message) {
|
|
2249
|
-
super(message);
|
|
2250
|
-
this.name = "ShieldAuthError";
|
|
2251
|
-
Object.setPrototypeOf(this, _ShieldAuthError.prototype);
|
|
2252
|
-
}
|
|
2253
|
-
};
|
|
2254
2292
|
async function findAgentByName(agentName, apiKey, baseUrl) {
|
|
2255
2293
|
let response;
|
|
2256
2294
|
try {
|
|
@@ -2443,13 +2481,25 @@ function isPermissionShape(value) {
|
|
|
2443
2481
|
const obj = value;
|
|
2444
2482
|
return typeof obj["service"] === "string" && typeof obj["read"] === "boolean" && typeof obj["write"] === "boolean" && typeof obj["execute"] === "boolean" && (obj["revoked_at"] === null || obj["revoked_at"] === void 0 || typeof obj["revoked_at"] === "string");
|
|
2445
2483
|
}
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2484
|
+
var CONSENT_POLL_INTERVAL_MS, CONSENT_POLL_TIMEOUT_MS, ShieldAuthError;
|
|
2485
|
+
var init_consent = __esm({
|
|
2486
|
+
"src/proxy/consent.ts"() {
|
|
2487
|
+
init_scope_cache();
|
|
2488
|
+
CONSENT_POLL_INTERVAL_MS = 3e3;
|
|
2489
|
+
CONSENT_POLL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2490
|
+
ShieldAuthError = class _ShieldAuthError extends Error {
|
|
2491
|
+
constructor(message) {
|
|
2492
|
+
super(message);
|
|
2493
|
+
this.name = "ShieldAuthError";
|
|
2494
|
+
Object.setPrototypeOf(this, _ShieldAuthError.prototype);
|
|
2495
|
+
}
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
});
|
|
2449
2499
|
function createProxyServer(config) {
|
|
2450
2500
|
if (!config.baseUrl.startsWith("https://") && !config.baseUrl.startsWith("http://localhost") && !config.baseUrl.startsWith("http://127.0.0.1")) {
|
|
2451
2501
|
throw new Error(
|
|
2452
|
-
`[multicorn-
|
|
2502
|
+
`[multicorn-shield] Base URL must use HTTPS. Received: "${config.baseUrl}". Use https:// or http://localhost for local development.`
|
|
2453
2503
|
);
|
|
2454
2504
|
}
|
|
2455
2505
|
let child = null;
|
|
@@ -2546,7 +2596,7 @@ function createProxyServer(config) {
|
|
|
2546
2596
|
if (actionLogger !== null) {
|
|
2547
2597
|
if (!config.agentName || config.agentName.trim().length === 0) {
|
|
2548
2598
|
process.stderr.write(
|
|
2549
|
-
"[multicorn-
|
|
2599
|
+
"[multicorn-shield] Cannot log action: agent name not resolved\n"
|
|
2550
2600
|
);
|
|
2551
2601
|
} else {
|
|
2552
2602
|
config.logger.debug("Logging blocked action (post-consent).", {
|
|
@@ -2576,7 +2626,7 @@ function createProxyServer(config) {
|
|
|
2576
2626
|
if (actionLogger !== null) {
|
|
2577
2627
|
if (!config.agentName || config.agentName.trim().length === 0) {
|
|
2578
2628
|
process.stderr.write(
|
|
2579
|
-
"[multicorn-
|
|
2629
|
+
"[multicorn-shield] Cannot log action: agent name not resolved\n"
|
|
2580
2630
|
);
|
|
2581
2631
|
} else {
|
|
2582
2632
|
config.logger.debug("Logging blocked action (spending).", {
|
|
@@ -2605,7 +2655,7 @@ function createProxyServer(config) {
|
|
|
2605
2655
|
}
|
|
2606
2656
|
if (actionLogger !== null) {
|
|
2607
2657
|
if (!config.agentName || config.agentName.trim().length === 0) {
|
|
2608
|
-
process.stderr.write("[multicorn-
|
|
2658
|
+
process.stderr.write("[multicorn-shield] Cannot log action: agent name not resolved\n");
|
|
2609
2659
|
} else {
|
|
2610
2660
|
config.logger.debug("Logging approved action.", {
|
|
2611
2661
|
agent: config.agentName,
|
|
@@ -2687,7 +2737,7 @@ function createProxyServer(config) {
|
|
|
2687
2737
|
agent: config.agentName
|
|
2688
2738
|
});
|
|
2689
2739
|
process.stderr.write(
|
|
2690
|
-
"\nError: API key was rejected by the Multicorn service.\nCheck your key at https://app.multicorn.ai/settings#api-keys or run `npx multicorn-
|
|
2740
|
+
"\nError: API key was rejected by the Multicorn service.\nCheck your key at https://app.multicorn.ai/settings#api-keys or run `npx multicorn-shield init` to reconfigure.\n\n"
|
|
2691
2741
|
);
|
|
2692
2742
|
throw new Error("API key was rejected by the Multicorn service.");
|
|
2693
2743
|
}
|
|
@@ -2760,12 +2810,17 @@ function extractCostCents(args) {
|
|
|
2760
2810
|
if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return 0;
|
|
2761
2811
|
return dollarsToCents(amount);
|
|
2762
2812
|
}
|
|
2763
|
-
var
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2813
|
+
var DEFAULT_SCOPE_REFRESH_INTERVAL_MS;
|
|
2814
|
+
var init_proxy = __esm({
|
|
2815
|
+
"src/proxy/index.ts"() {
|
|
2816
|
+
init_scope_validator();
|
|
2817
|
+
init_action_logger();
|
|
2818
|
+
init_spending_checker();
|
|
2819
|
+
init_interceptor();
|
|
2820
|
+
init_consent();
|
|
2821
|
+
DEFAULT_SCOPE_REFRESH_INTERVAL_MS = 6e4;
|
|
2822
|
+
}
|
|
2823
|
+
});
|
|
2769
2824
|
function createLogger(level, output = process.stderr) {
|
|
2770
2825
|
const minLevel = LOG_LEVELS[level];
|
|
2771
2826
|
function write(logLevel, msg, data) {
|
|
@@ -2796,8 +2851,93 @@ function createLogger(level, output = process.stderr) {
|
|
|
2796
2851
|
function isValidLogLevel(value) {
|
|
2797
2852
|
return typeof value === "string" && Object.hasOwn(LOG_LEVELS, value);
|
|
2798
2853
|
}
|
|
2854
|
+
var LOG_LEVELS;
|
|
2855
|
+
var init_logger = __esm({
|
|
2856
|
+
"src/proxy/logger.ts"() {
|
|
2857
|
+
LOG_LEVELS = {
|
|
2858
|
+
debug: 0,
|
|
2859
|
+
info: 1,
|
|
2860
|
+
warn: 2,
|
|
2861
|
+
error: 3
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
});
|
|
2865
|
+
function getExtensionBackupPath() {
|
|
2866
|
+
return join(homedir(), ".multicorn", EXTENSION_BACKUP_FILENAME);
|
|
2867
|
+
}
|
|
2868
|
+
function isRecord(value) {
|
|
2869
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2870
|
+
}
|
|
2871
|
+
async function readExtensionBackup() {
|
|
2872
|
+
try {
|
|
2873
|
+
const raw = await readFile(getExtensionBackupPath(), "utf8");
|
|
2874
|
+
const parsed = JSON.parse(raw);
|
|
2875
|
+
if (!isRecord(parsed)) return null;
|
|
2876
|
+
if (parsed["version"] !== 1) return null;
|
|
2877
|
+
if (typeof parsed["createdAt"] !== "string") return null;
|
|
2878
|
+
if (typeof parsed["claudeDesktopConfigPath"] !== "string") return null;
|
|
2879
|
+
const mcpServers = parsed["mcpServers"];
|
|
2880
|
+
if (!isRecord(mcpServers)) return null;
|
|
2881
|
+
return {
|
|
2882
|
+
version: 1,
|
|
2883
|
+
createdAt: parsed["createdAt"],
|
|
2884
|
+
claudeDesktopConfigPath: parsed["claudeDesktopConfigPath"],
|
|
2885
|
+
mcpServers
|
|
2886
|
+
};
|
|
2887
|
+
} catch {
|
|
2888
|
+
return null;
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
var EXTENSION_BACKUP_FILENAME;
|
|
2892
|
+
var init_config_reader = __esm({
|
|
2893
|
+
"src/extension/config-reader.ts"() {
|
|
2894
|
+
EXTENSION_BACKUP_FILENAME = "extension-backup.json";
|
|
2895
|
+
}
|
|
2896
|
+
});
|
|
2897
|
+
function isErrnoException2(e) {
|
|
2898
|
+
return typeof e === "object" && e !== null && "code" in e;
|
|
2899
|
+
}
|
|
2900
|
+
function isRecord2(value) {
|
|
2901
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2902
|
+
}
|
|
2903
|
+
async function restoreClaudeDesktopMcpFromBackup() {
|
|
2904
|
+
const backup = await readExtensionBackup();
|
|
2905
|
+
if (backup === null) {
|
|
2906
|
+
throw new Error(
|
|
2907
|
+
"No Shield extension backup found. Expected ~/.multicorn/extension-backup.json from a previous Shield Desktop Extension session."
|
|
2908
|
+
);
|
|
2909
|
+
}
|
|
2910
|
+
const configPath = getClaudeDesktopConfigPath();
|
|
2911
|
+
let root = {};
|
|
2912
|
+
try {
|
|
2913
|
+
const raw = await readFile(configPath, "utf8");
|
|
2914
|
+
const parsed = JSON.parse(raw);
|
|
2915
|
+
if (isRecord2(parsed)) {
|
|
2916
|
+
root = parsed;
|
|
2917
|
+
}
|
|
2918
|
+
} catch (error) {
|
|
2919
|
+
if (!isErrnoException2(error) || error.code !== "ENOENT") {
|
|
2920
|
+
throw error;
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
root["mcpServers"] = backup.mcpServers;
|
|
2924
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
2925
|
+
await writeFile(configPath, JSON.stringify(root, null, 2) + "\n", { encoding: "utf8" });
|
|
2926
|
+
}
|
|
2927
|
+
var init_restore = __esm({
|
|
2928
|
+
"src/extension/restore.ts"() {
|
|
2929
|
+
init_config();
|
|
2930
|
+
init_config_reader();
|
|
2931
|
+
}
|
|
2932
|
+
});
|
|
2799
2933
|
|
|
2800
|
-
// bin/multicorn-
|
|
2934
|
+
// bin/multicorn-shield.ts
|
|
2935
|
+
var multicorn_shield_exports = {};
|
|
2936
|
+
__export(multicorn_shield_exports, {
|
|
2937
|
+
parseArgs: () => parseArgs,
|
|
2938
|
+
resolveWrapConfig: () => resolveWrapConfig,
|
|
2939
|
+
runCli: () => runCli
|
|
2940
|
+
});
|
|
2801
2941
|
function parseArgs(argv) {
|
|
2802
2942
|
const args = argv.slice(2);
|
|
2803
2943
|
let subcommand = "help";
|
|
@@ -2820,7 +2960,7 @@ function parseArgs(argv) {
|
|
|
2820
2960
|
const name = args[i + 1];
|
|
2821
2961
|
if (name === void 0 || name.startsWith("-")) {
|
|
2822
2962
|
process.stderr.write("Error: delete-agent requires an agent name.\n");
|
|
2823
|
-
process.stderr.write("Example: npx multicorn-
|
|
2963
|
+
process.stderr.write("Example: npx multicorn-shield delete-agent my-agent\n");
|
|
2824
2964
|
process.exit(1);
|
|
2825
2965
|
}
|
|
2826
2966
|
deleteAgentName = name;
|
|
@@ -2872,7 +3012,7 @@ function parseArgs(argv) {
|
|
|
2872
3012
|
}
|
|
2873
3013
|
if (remaining.length === 0) {
|
|
2874
3014
|
process.stderr.write("Error: --wrap requires a command to run.\n");
|
|
2875
|
-
process.stderr.write("Example: npx multicorn-
|
|
3015
|
+
process.stderr.write("Example: npx multicorn-shield --wrap my-mcp-server\n");
|
|
2876
3016
|
process.exit(1);
|
|
2877
3017
|
}
|
|
2878
3018
|
wrapCommand = remaining[0] ?? "";
|
|
@@ -2925,19 +3065,22 @@ function parseArgs(argv) {
|
|
|
2925
3065
|
function printHelp() {
|
|
2926
3066
|
process.stderr.write(
|
|
2927
3067
|
[
|
|
2928
|
-
"multicorn-
|
|
3068
|
+
"multicorn-shield: MCP permission proxy and Shield setup",
|
|
2929
3069
|
"",
|
|
2930
3070
|
"Usage:",
|
|
2931
|
-
" npx multicorn-
|
|
3071
|
+
" npx multicorn-shield init",
|
|
2932
3072
|
" Interactive setup. Saves API key to ~/.multicorn/config.json.",
|
|
2933
3073
|
"",
|
|
2934
|
-
" npx multicorn-
|
|
3074
|
+
" npx multicorn-shield restore",
|
|
3075
|
+
" Restore MCP servers in claude_desktop_config.json from the Shield extension backup.",
|
|
3076
|
+
"",
|
|
3077
|
+
" npx multicorn-shield agents",
|
|
2935
3078
|
" List configured agents and show which is the default.",
|
|
2936
3079
|
"",
|
|
2937
|
-
" npx multicorn-
|
|
3080
|
+
" npx multicorn-shield delete-agent <name>",
|
|
2938
3081
|
" Remove a saved agent.",
|
|
2939
3082
|
"",
|
|
2940
|
-
" npx multicorn-
|
|
3083
|
+
" npx multicorn-shield --wrap <command> [args...]",
|
|
2941
3084
|
" Start <command> as an MCP server and proxy all tool calls through",
|
|
2942
3085
|
" Shield's permission layer.",
|
|
2943
3086
|
"",
|
|
@@ -2949,14 +3092,22 @@ function printHelp() {
|
|
|
2949
3092
|
" --agent-name <name> Override agent name derived from the wrapped command",
|
|
2950
3093
|
"",
|
|
2951
3094
|
"Examples:",
|
|
2952
|
-
" npx multicorn-
|
|
2953
|
-
" npx multicorn-
|
|
2954
|
-
" npx multicorn-
|
|
3095
|
+
" npx multicorn-shield init",
|
|
3096
|
+
" npx multicorn-shield --wrap npx @modelcontextprotocol/server-filesystem /tmp",
|
|
3097
|
+
" npx multicorn-shield --wrap my-mcp-server --log-level debug",
|
|
2955
3098
|
""
|
|
2956
3099
|
].join("\n")
|
|
2957
3100
|
);
|
|
2958
3101
|
}
|
|
2959
|
-
async function
|
|
3102
|
+
async function runCli() {
|
|
3103
|
+
const first = process.argv[2];
|
|
3104
|
+
if (first === "restore") {
|
|
3105
|
+
await restoreClaudeDesktopMcpFromBackup();
|
|
3106
|
+
process.stderr.write(
|
|
3107
|
+
"Restored MCP server entries from ~/.multicorn/extension-backup.json into Claude Desktop config.\nRestart Claude Desktop to apply changes.\n"
|
|
3108
|
+
);
|
|
3109
|
+
return;
|
|
3110
|
+
}
|
|
2960
3111
|
const cli = parseArgs(process.argv);
|
|
2961
3112
|
const logger = createLogger(cli.logLevel);
|
|
2962
3113
|
if (cli.subcommand === "help") {
|
|
@@ -2971,13 +3122,13 @@ async function main() {
|
|
|
2971
3122
|
const config2 = await loadConfig();
|
|
2972
3123
|
if (config2 === null) {
|
|
2973
3124
|
process.stderr.write(
|
|
2974
|
-
"No config found. Run `npx multicorn-
|
|
3125
|
+
"No config found. Run `npx multicorn-shield init` to set up your API key.\n"
|
|
2975
3126
|
);
|
|
2976
3127
|
process.exit(1);
|
|
2977
3128
|
}
|
|
2978
3129
|
const agents = collectAgentsFromConfig(config2);
|
|
2979
3130
|
if (agents.length === 0) {
|
|
2980
|
-
process.stderr.write("No agents configured. Run `npx multicorn-
|
|
3131
|
+
process.stderr.write("No agents configured. Run `npx multicorn-shield init` to add one.\n");
|
|
2981
3132
|
process.exit(0);
|
|
2982
3133
|
}
|
|
2983
3134
|
const def = config2.defaultAgent;
|
|
@@ -3067,7 +3218,7 @@ async function resolveWrapConfig(cli, logger) {
|
|
|
3067
3218
|
return config;
|
|
3068
3219
|
}
|
|
3069
3220
|
process.stderr.write(
|
|
3070
|
-
"No API key found. Provide one via the --api-key flag, the MULTICORN_API_KEY environment variable, or run `npx multicorn-
|
|
3221
|
+
"No API key found. Provide one via the --api-key flag, the MULTICORN_API_KEY environment variable, or run `npx multicorn-shield init` to set up a config file.\n"
|
|
3071
3222
|
);
|
|
3072
3223
|
process.exit(1);
|
|
3073
3224
|
}
|
|
@@ -3095,14 +3246,32 @@ function deriveAgentName(command) {
|
|
|
3095
3246
|
const base = command.split("/").pop() ?? command;
|
|
3096
3247
|
return base.replace(/\.[cm]?[jt]s$/, "");
|
|
3097
3248
|
}
|
|
3098
|
-
var isDirectRun
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3249
|
+
var isDirectRun;
|
|
3250
|
+
var init_multicorn_shield = __esm({
|
|
3251
|
+
"bin/multicorn-shield.ts"() {
|
|
3252
|
+
init_config();
|
|
3253
|
+
init_proxy();
|
|
3254
|
+
init_logger();
|
|
3255
|
+
init_consent();
|
|
3256
|
+
init_restore();
|
|
3257
|
+
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-shield.js") || import.meta.url.endsWith("/multicorn-shield.ts"));
|
|
3258
|
+
if (isDirectRun && process.env["VITEST"] === void 0) {
|
|
3259
|
+
runCli().catch((error) => {
|
|
3260
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3261
|
+
process.stderr.write(`Fatal error: ${message}
|
|
3103
3262
|
`);
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
}
|
|
3263
|
+
process.exit(1);
|
|
3264
|
+
});
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
});
|
|
3107
3268
|
|
|
3108
|
-
|
|
3269
|
+
// bin/multicorn-proxy.ts
|
|
3270
|
+
process.stderr.write("Warning: multicorn-proxy is deprecated. Use multicorn-shield instead.\n");
|
|
3271
|
+
var { runCli: runCli2 } = await Promise.resolve().then(() => (init_multicorn_shield(), multicorn_shield_exports));
|
|
3272
|
+
void runCli2().catch((error) => {
|
|
3273
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3274
|
+
process.stderr.write(`Fatal error: ${message}
|
|
3275
|
+
`);
|
|
3276
|
+
process.exit(1);
|
|
3277
|
+
});
|