multicorn-shield 0.4.0 → 0.6.1
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/README.md +4 -2
- package/dist/multicorn-proxy.js +455 -47
- package/dist/openclaw-plugin/multicorn-shield.js +12 -1
- package/dist/shield-extension.js +22 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-

|
|
2
2
|
|
|
3
3
|
# Multicorn Shield
|
|
4
4
|
|
|
@@ -38,6 +38,8 @@ npm install -g multicorn-shield
|
|
|
38
38
|
npx multicorn-proxy init
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
The init wizard supports multiple agents. Run it again to add agents on different platforms (OpenClaw, Claude Code, Cursor) without losing existing config. Use `npx multicorn-proxy agents` to see configured agents.
|
|
42
|
+
|
|
41
43
|
**Step 3: Wrap your MCP server**
|
|
42
44
|
|
|
43
45
|
```bash
|
|
@@ -194,7 +196,7 @@ With the dashboard you can:
|
|
|
194
196
|
The dashboard works with both the SDK integration and the MCP proxy. No extra setup needed.
|
|
195
197
|
|
|
196
198
|
<p align="center">
|
|
197
|
-
<img src="https://multicorn.ai/images/
|
|
199
|
+
<img src="https://multicorn.ai/images/og-image-shield.png" alt="Dashboard overview showing total actions, blocked count, spend, and live activity feed" width="800" />
|
|
198
200
|
</p>
|
|
199
201
|
|
|
200
202
|
<p align="center">
|
package/dist/multicorn-proxy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
|
-
import { readFile,
|
|
3
|
+
import { readFile, writeFile, mkdir, unlink } from 'fs/promises';
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
6
|
import { createInterface } from 'readline';
|
|
@@ -52,21 +52,99 @@ function stripAnsi(str) {
|
|
|
52
52
|
function normalizeAgentName(raw) {
|
|
53
53
|
return raw.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "").slice(0, 50);
|
|
54
54
|
}
|
|
55
|
+
function isErrnoException(e) {
|
|
56
|
+
return typeof e === "object" && e !== null && "code" in e;
|
|
57
|
+
}
|
|
55
58
|
function isProxyConfig(value) {
|
|
56
59
|
if (typeof value !== "object" || value === null) return false;
|
|
57
60
|
const obj = value;
|
|
58
61
|
return typeof obj["apiKey"] === "string" && typeof obj["baseUrl"] === "string";
|
|
59
62
|
}
|
|
63
|
+
function isAgentEntry(value) {
|
|
64
|
+
if (typeof value !== "object" || value === null) return false;
|
|
65
|
+
const o = value;
|
|
66
|
+
return typeof o["name"] === "string" && typeof o["platform"] === "string";
|
|
67
|
+
}
|
|
68
|
+
function getAgentByPlatform(config, platform) {
|
|
69
|
+
const list = config.agents;
|
|
70
|
+
if (list === void 0 || list.length === 0) return void 0;
|
|
71
|
+
return list.find((a) => a.platform === platform);
|
|
72
|
+
}
|
|
73
|
+
function getDefaultAgent(config) {
|
|
74
|
+
const list = config.agents;
|
|
75
|
+
if (list === void 0 || list.length === 0) return void 0;
|
|
76
|
+
const defName = config.defaultAgent;
|
|
77
|
+
if (typeof defName === "string" && defName.length > 0) {
|
|
78
|
+
const match = list.find((a) => a.name === defName);
|
|
79
|
+
if (match !== void 0) return match;
|
|
80
|
+
}
|
|
81
|
+
return list[0];
|
|
82
|
+
}
|
|
83
|
+
function collectAgentsFromConfig(cfg) {
|
|
84
|
+
if (cfg === null) return [];
|
|
85
|
+
if (cfg.agents !== void 0 && cfg.agents.length > 0) {
|
|
86
|
+
return cfg.agents.map((a) => ({ name: a.name, platform: a.platform }));
|
|
87
|
+
}
|
|
88
|
+
const raw = cfg;
|
|
89
|
+
const legacyName = raw["agentName"];
|
|
90
|
+
const legacyPlatform = raw["platform"];
|
|
91
|
+
if (typeof legacyName === "string" && legacyName.length > 0) {
|
|
92
|
+
const plat = typeof legacyPlatform === "string" && legacyPlatform.length > 0 ? legacyPlatform : "unknown";
|
|
93
|
+
return [{ name: legacyName, platform: plat }];
|
|
94
|
+
}
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
60
97
|
async function loadConfig() {
|
|
61
98
|
try {
|
|
62
99
|
const raw = await readFile(CONFIG_PATH, "utf8");
|
|
63
100
|
const parsed = JSON.parse(raw);
|
|
64
101
|
if (!isProxyConfig(parsed)) return null;
|
|
65
|
-
|
|
102
|
+
const obj = parsed;
|
|
103
|
+
const agentNameRaw = obj["agentName"];
|
|
104
|
+
const agentsRaw = obj["agents"];
|
|
105
|
+
const hasNonEmptyAgents = Array.isArray(agentsRaw) && agentsRaw.length > 0 && agentsRaw.every((e) => isAgentEntry(e));
|
|
106
|
+
const needsMigrate = typeof agentNameRaw === "string" && agentNameRaw.length > 0 && !hasNonEmptyAgents;
|
|
107
|
+
if (!needsMigrate) {
|
|
108
|
+
return parsed;
|
|
109
|
+
}
|
|
110
|
+
const platform = typeof obj["platform"] === "string" && obj["platform"].length > 0 ? obj["platform"] : "unknown";
|
|
111
|
+
const next = { ...obj };
|
|
112
|
+
delete next["agentName"];
|
|
113
|
+
delete next["platform"];
|
|
114
|
+
next["agents"] = [{ name: agentNameRaw, platform }];
|
|
115
|
+
next["defaultAgent"] = agentNameRaw;
|
|
116
|
+
const migrated = next;
|
|
117
|
+
await saveConfig(migrated);
|
|
118
|
+
return migrated;
|
|
66
119
|
} catch {
|
|
67
120
|
return null;
|
|
68
121
|
}
|
|
69
122
|
}
|
|
123
|
+
async function deleteAgentByName(name) {
|
|
124
|
+
const config = await loadConfig();
|
|
125
|
+
if (config === null) return false;
|
|
126
|
+
const agents = collectAgentsFromConfig(config);
|
|
127
|
+
const idx = agents.findIndex((a) => a.name === name);
|
|
128
|
+
if (idx === -1) return false;
|
|
129
|
+
const nextAgents = agents.filter((_, i) => i !== idx);
|
|
130
|
+
let defaultAgent = config.defaultAgent;
|
|
131
|
+
if (defaultAgent === name) {
|
|
132
|
+
defaultAgent = void 0;
|
|
133
|
+
}
|
|
134
|
+
const raw = { ...config };
|
|
135
|
+
if (nextAgents.length > 0) {
|
|
136
|
+
raw["agents"] = nextAgents;
|
|
137
|
+
} else {
|
|
138
|
+
delete raw["agents"];
|
|
139
|
+
}
|
|
140
|
+
if (defaultAgent !== void 0 && defaultAgent.length > 0) {
|
|
141
|
+
raw["defaultAgent"] = defaultAgent;
|
|
142
|
+
} else {
|
|
143
|
+
delete raw["defaultAgent"];
|
|
144
|
+
}
|
|
145
|
+
await saveConfig(raw);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
70
148
|
async function saveConfig(config) {
|
|
71
149
|
await mkdir(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
72
150
|
await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", {
|
|
@@ -74,6 +152,111 @@ async function saveConfig(config) {
|
|
|
74
152
|
mode: 384
|
|
75
153
|
});
|
|
76
154
|
}
|
|
155
|
+
var OPENCLAW_MIN_VERSION = "2026.2.26";
|
|
156
|
+
async function detectOpenClaw() {
|
|
157
|
+
let raw;
|
|
158
|
+
try {
|
|
159
|
+
raw = await readFile(OPENCLAW_CONFIG_PATH, "utf8");
|
|
160
|
+
} catch (e) {
|
|
161
|
+
if (isErrnoException(e) && e.code === "ENOENT") {
|
|
162
|
+
return { status: "not-found", version: null };
|
|
163
|
+
}
|
|
164
|
+
throw e;
|
|
165
|
+
}
|
|
166
|
+
let obj;
|
|
167
|
+
try {
|
|
168
|
+
obj = JSON.parse(raw);
|
|
169
|
+
} catch {
|
|
170
|
+
return { status: "parse-error", version: null };
|
|
171
|
+
}
|
|
172
|
+
const meta = obj["meta"];
|
|
173
|
+
if (typeof meta === "object" && meta !== null) {
|
|
174
|
+
const v = meta["lastTouchedVersion"];
|
|
175
|
+
if (typeof v === "string" && v.length > 0) {
|
|
176
|
+
return { status: "detected", version: v };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return { status: "detected", version: null };
|
|
180
|
+
}
|
|
181
|
+
function isVersionAtLeast(version, minimum) {
|
|
182
|
+
const vParts = version.split(".").map(Number);
|
|
183
|
+
const mParts = minimum.split(".").map(Number);
|
|
184
|
+
const len = Math.max(vParts.length, mParts.length);
|
|
185
|
+
for (let i = 0; i < len; i++) {
|
|
186
|
+
const v = vParts[i] ?? 0;
|
|
187
|
+
const m = mParts[i] ?? 0;
|
|
188
|
+
if (Number.isNaN(v) || Number.isNaN(m)) return false;
|
|
189
|
+
if (v > m) return true;
|
|
190
|
+
if (v < m) return false;
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
async function updateOpenClawConfigIfPresent(apiKey, baseUrl, agentName) {
|
|
195
|
+
let raw;
|
|
196
|
+
try {
|
|
197
|
+
raw = await readFile(OPENCLAW_CONFIG_PATH, "utf8");
|
|
198
|
+
} catch (e) {
|
|
199
|
+
if (isErrnoException(e) && e.code === "ENOENT") {
|
|
200
|
+
return "not-found";
|
|
201
|
+
}
|
|
202
|
+
throw e;
|
|
203
|
+
}
|
|
204
|
+
let obj;
|
|
205
|
+
try {
|
|
206
|
+
obj = JSON.parse(raw);
|
|
207
|
+
} catch {
|
|
208
|
+
return "parse-error";
|
|
209
|
+
}
|
|
210
|
+
let hooks = obj["hooks"];
|
|
211
|
+
if (hooks === void 0 || typeof hooks !== "object") {
|
|
212
|
+
hooks = {};
|
|
213
|
+
obj["hooks"] = hooks;
|
|
214
|
+
}
|
|
215
|
+
let internal = hooks["internal"];
|
|
216
|
+
if (internal === void 0 || typeof internal !== "object") {
|
|
217
|
+
internal = { enabled: true, entries: {} };
|
|
218
|
+
hooks["internal"] = internal;
|
|
219
|
+
}
|
|
220
|
+
let entries = internal["entries"];
|
|
221
|
+
if (entries === void 0 || typeof entries !== "object") {
|
|
222
|
+
entries = {};
|
|
223
|
+
internal["entries"] = entries;
|
|
224
|
+
}
|
|
225
|
+
let shield = entries["multicorn-shield"];
|
|
226
|
+
if (shield === void 0 || typeof shield !== "object") {
|
|
227
|
+
shield = { enabled: true, env: {} };
|
|
228
|
+
entries["multicorn-shield"] = shield;
|
|
229
|
+
}
|
|
230
|
+
let env = shield["env"];
|
|
231
|
+
if (env === void 0 || typeof env !== "object") {
|
|
232
|
+
env = {};
|
|
233
|
+
shield["env"] = env;
|
|
234
|
+
}
|
|
235
|
+
env["MULTICORN_API_KEY"] = apiKey;
|
|
236
|
+
env["MULTICORN_BASE_URL"] = baseUrl;
|
|
237
|
+
if (agentName !== void 0) {
|
|
238
|
+
env["MULTICORN_AGENT_NAME"] = agentName;
|
|
239
|
+
const agentsList = obj["agents"];
|
|
240
|
+
const list = agentsList?.["list"];
|
|
241
|
+
if (Array.isArray(list) && list.length > 0) {
|
|
242
|
+
const first = list[0];
|
|
243
|
+
if (first["id"] !== agentName) {
|
|
244
|
+
first["id"] = agentName;
|
|
245
|
+
first["name"] = agentName;
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
if (agentsList !== void 0 && typeof agentsList === "object") {
|
|
249
|
+
agentsList["list"] = [{ id: agentName, name: agentName }];
|
|
250
|
+
} else {
|
|
251
|
+
obj["agents"] = { list: [{ id: agentName, name: agentName }] };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
await writeFile(OPENCLAW_CONFIG_PATH, JSON.stringify(obj, null, 2) + "\n", {
|
|
256
|
+
encoding: "utf8"
|
|
257
|
+
});
|
|
258
|
+
return "updated";
|
|
259
|
+
}
|
|
77
260
|
async function validateApiKey(apiKey, baseUrl) {
|
|
78
261
|
try {
|
|
79
262
|
const response = await fetch(`${baseUrl}/api/v1/agents`, {
|
|
@@ -280,14 +463,15 @@ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverNa
|
|
|
280
463
|
const data = envelope["data"];
|
|
281
464
|
return typeof data?.["proxy_url"] === "string" ? data["proxy_url"] : "";
|
|
282
465
|
}
|
|
283
|
-
function printPlatformSnippet(platform, routingToken, shortName) {
|
|
466
|
+
function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
467
|
+
const authHeader = platform === "cursor" ? `Bearer ${apiKey}` : "Bearer YOUR_SHIELD_API_KEY";
|
|
284
468
|
const mcpSnippet = JSON.stringify(
|
|
285
469
|
{
|
|
286
470
|
mcpServers: {
|
|
287
471
|
[shortName]: {
|
|
288
472
|
url: routingToken,
|
|
289
473
|
headers: {
|
|
290
|
-
Authorization:
|
|
474
|
+
Authorization: authHeader
|
|
291
475
|
}
|
|
292
476
|
}
|
|
293
477
|
}
|
|
@@ -303,11 +487,13 @@ function printPlatformSnippet(platform, routingToken, shortName) {
|
|
|
303
487
|
process.stderr.write("\n" + style.dim("Add this to ~/.cursor/mcp.json:") + "\n\n");
|
|
304
488
|
}
|
|
305
489
|
process.stderr.write(style.cyan(mcpSnippet) + "\n\n");
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
490
|
+
if (platform !== "cursor") {
|
|
491
|
+
process.stderr.write(
|
|
492
|
+
style.dim(
|
|
493
|
+
"Replace YOUR_SHIELD_API_KEY with your API key. Find it in Settings > API keys at https://app.multicorn.ai/settings/api-keys"
|
|
494
|
+
) + "\n"
|
|
495
|
+
);
|
|
496
|
+
}
|
|
311
497
|
if (platform === "cursor") {
|
|
312
498
|
process.stderr.write(
|
|
313
499
|
style.dim(
|
|
@@ -321,27 +507,7 @@ function printPlatformSnippet(platform, routingToken, shortName) {
|
|
|
321
507
|
);
|
|
322
508
|
}
|
|
323
509
|
}
|
|
324
|
-
function
|
|
325
|
-
process.stderr.write("\n" + style.green("\u2713") + " Agent registered!\n");
|
|
326
|
-
process.stderr.write(
|
|
327
|
-
"\nTo connect this agent, add the Multicorn Shield plugin to your OpenClaw agent:\n"
|
|
328
|
-
);
|
|
329
|
-
process.stderr.write("\n " + style.cyan("openclaw plugins add multicorn-shield") + "\n");
|
|
330
|
-
process.stderr.write(
|
|
331
|
-
"\nThen start your agent. Shield will monitor and protect tool calls automatically.\n"
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
function printClaudeCodeInstructions() {
|
|
335
|
-
process.stderr.write("\n" + style.green("\u2713") + " Agent registered!\n");
|
|
336
|
-
process.stderr.write(
|
|
337
|
-
"\nTo connect this agent, install the Multicorn Shield plugin in Claude Code:\n"
|
|
338
|
-
);
|
|
339
|
-
process.stderr.write("\n " + style.cyan("claude plugins install multicorn-shield") + "\n");
|
|
340
|
-
process.stderr.write(
|
|
341
|
-
"\nThen start a new Claude Code session. Shield will monitor and protect tool calls automatically.\n"
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
async function runInit(baseUrl = "https://api.multicorn.ai", platform) {
|
|
510
|
+
async function runInit(baseUrl = "https://api.multicorn.ai") {
|
|
345
511
|
if (!process.stdin.isTTY) {
|
|
346
512
|
process.stderr.write(
|
|
347
513
|
style.red("Error: interactive terminal required. Cannot run init with piped input.") + "\n"
|
|
@@ -408,23 +574,136 @@ async function runInit(baseUrl = "https://api.multicorn.ai", platform) {
|
|
|
408
574
|
return null;
|
|
409
575
|
}
|
|
410
576
|
const configuredAgents = [];
|
|
577
|
+
let currentAgents = collectAgentsFromConfig(existing);
|
|
411
578
|
let lastConfig = {
|
|
412
579
|
apiKey,
|
|
413
580
|
baseUrl,
|
|
414
|
-
...{
|
|
581
|
+
...currentAgents.length > 0 ? {
|
|
582
|
+
agents: currentAgents,
|
|
583
|
+
defaultAgent: existing !== null && typeof existing.defaultAgent === "string" && existing.defaultAgent.length > 0 ? existing.defaultAgent : currentAgents[currentAgents.length - 1]?.name ?? ""
|
|
584
|
+
} : {}
|
|
415
585
|
};
|
|
416
586
|
let configuring = true;
|
|
417
587
|
while (configuring) {
|
|
418
588
|
const selection = await promptPlatformSelection(ask);
|
|
419
589
|
const selectedPlatform = PLATFORM_BY_SELECTION[selection] ?? "cursor";
|
|
420
590
|
const selectedLabel = PLATFORM_LABELS[selection - 1] ?? "Cursor";
|
|
591
|
+
const existingForPlatform = currentAgents.find((a) => a.platform === selectedPlatform);
|
|
592
|
+
if (existingForPlatform !== void 0) {
|
|
593
|
+
process.stderr.write(
|
|
594
|
+
`
|
|
595
|
+
An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.name)}
|
|
596
|
+
`
|
|
597
|
+
);
|
|
598
|
+
const replace = await ask("Replace it? (Y/n) ");
|
|
599
|
+
if (replace.trim().toLowerCase() === "n") {
|
|
600
|
+
const another2 = await ask("\nConnect another agent? (Y/n) ");
|
|
601
|
+
if (another2.trim().toLowerCase() === "n") {
|
|
602
|
+
configuring = false;
|
|
603
|
+
}
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
421
607
|
const agentName = await promptAgentName(ask, selectedPlatform);
|
|
608
|
+
let setupSucceeded = false;
|
|
422
609
|
if (selection === 1) {
|
|
423
|
-
|
|
424
|
-
|
|
610
|
+
let detection;
|
|
611
|
+
try {
|
|
612
|
+
detection = await detectOpenClaw();
|
|
613
|
+
} catch (error) {
|
|
614
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
615
|
+
process.stderr.write(style.red("\u2717") + ` Failed to read OpenClaw config: ${detail}
|
|
616
|
+
`);
|
|
617
|
+
rl.close();
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
if (detection.status === "not-found") {
|
|
621
|
+
process.stderr.write(
|
|
622
|
+
style.red("\u2717") + " OpenClaw is not installed. Install OpenClaw first, then run npx multicorn-proxy init again.\n"
|
|
623
|
+
);
|
|
624
|
+
rl.close();
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
if (detection.status === "parse-error") {
|
|
628
|
+
process.stderr.write(
|
|
629
|
+
style.red("\u2717") + " Could not update OpenClaw config. Set MULTICORN_API_KEY in ~/.openclaw/openclaw.json manually.\n"
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
if (detection.status === "detected") {
|
|
633
|
+
if (detection.version !== null) {
|
|
634
|
+
process.stderr.write(
|
|
635
|
+
style.green("\u2713") + ` OpenClaw detected ${style.dim(`(${detection.version})`)}
|
|
636
|
+
`
|
|
637
|
+
);
|
|
638
|
+
if (isVersionAtLeast(detection.version, OPENCLAW_MIN_VERSION)) {
|
|
639
|
+
process.stderr.write(
|
|
640
|
+
style.green("\u2713") + " " + style.green("Version compatible") + "\n"
|
|
641
|
+
);
|
|
642
|
+
} else {
|
|
643
|
+
process.stderr.write(
|
|
644
|
+
style.yellow("\u26A0") + ` Shield has been tested with OpenClaw ${style.cyan(OPENCLAW_MIN_VERSION)} and above. Your version (${detection.version}) may work but is untested. We recommend upgrading to at least ${style.cyan(OPENCLAW_MIN_VERSION)}.
|
|
645
|
+
`
|
|
646
|
+
);
|
|
647
|
+
const answer = await ask("Continue anyway? (y/N) ");
|
|
648
|
+
if (answer.trim().toLowerCase() !== "y") {
|
|
649
|
+
rl.close();
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
} else {
|
|
654
|
+
process.stderr.write(
|
|
655
|
+
style.yellow("\u26A0") + " Could not detect OpenClaw version. Continuing anyway.\n"
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
const spinner = withSpinner("Updating OpenClaw config...");
|
|
659
|
+
try {
|
|
660
|
+
const result = await updateOpenClawConfigIfPresent(apiKey, baseUrl, agentName);
|
|
661
|
+
if (result === "not-found") {
|
|
662
|
+
spinner.stop(false, "OpenClaw config disappeared unexpectedly.");
|
|
663
|
+
rl.close();
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
if (result === "parse-error") {
|
|
667
|
+
spinner.stop(
|
|
668
|
+
false,
|
|
669
|
+
"Could not update OpenClaw config. Set MULTICORN_API_KEY in ~/.openclaw/openclaw.json manually."
|
|
670
|
+
);
|
|
671
|
+
} else {
|
|
672
|
+
spinner.stop(
|
|
673
|
+
true,
|
|
674
|
+
"OpenClaw config updated at " + style.cyan("~/.openclaw/openclaw.json")
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
} catch (error) {
|
|
678
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
679
|
+
spinner.stop(false, `Failed to update OpenClaw config: ${detail}`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
configuredAgents.push({
|
|
683
|
+
selection,
|
|
684
|
+
platform: selectedPlatform,
|
|
685
|
+
platformLabel: selectedLabel,
|
|
686
|
+
agentName
|
|
687
|
+
});
|
|
688
|
+
setupSucceeded = true;
|
|
425
689
|
} else if (selection === 2) {
|
|
426
|
-
|
|
427
|
-
|
|
690
|
+
process.stderr.write("\nTo connect Claude Code to Shield:\n\n");
|
|
691
|
+
process.stderr.write(
|
|
692
|
+
" " + style.bold("Step 1") + " - Add the Multicorn marketplace:\n " + style.cyan("claude plugin marketplace add Multicorn-AI/multicorn-shield") + "\n\n"
|
|
693
|
+
);
|
|
694
|
+
process.stderr.write(
|
|
695
|
+
" " + style.bold("Step 2") + " - Install the plugin:\n " + style.cyan("claude plugin install multicorn-shield@multicorn-shield") + "\n\n"
|
|
696
|
+
);
|
|
697
|
+
process.stderr.write(
|
|
698
|
+
style.dim("Requires Claude Code to be installed. Get it at https://code.claude.com") + "\n"
|
|
699
|
+
);
|
|
700
|
+
configuredAgents.push({
|
|
701
|
+
selection,
|
|
702
|
+
platform: selectedPlatform,
|
|
703
|
+
platformLabel: selectedLabel,
|
|
704
|
+
agentName
|
|
705
|
+
});
|
|
706
|
+
setupSucceeded = true;
|
|
428
707
|
} else {
|
|
429
708
|
const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
|
|
430
709
|
let proxyUrl = "";
|
|
@@ -454,23 +733,39 @@ async function runInit(baseUrl = "https://api.multicorn.ai", platform) {
|
|
|
454
733
|
if (created && proxyUrl.length > 0) {
|
|
455
734
|
process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
|
|
456
735
|
process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
|
|
457
|
-
printPlatformSnippet(selectedPlatform, proxyUrl, shortName);
|
|
736
|
+
printPlatformSnippet(selectedPlatform, proxyUrl, shortName, apiKey);
|
|
458
737
|
configuredAgents.push({
|
|
738
|
+
selection,
|
|
739
|
+
platform: selectedPlatform,
|
|
459
740
|
platformLabel: selectedLabel,
|
|
460
741
|
agentName,
|
|
461
742
|
shortName,
|
|
462
743
|
proxyUrl
|
|
463
744
|
});
|
|
745
|
+
setupSucceeded = true;
|
|
464
746
|
}
|
|
465
747
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
748
|
+
if (setupSucceeded) {
|
|
749
|
+
currentAgents = currentAgents.filter((a) => a.platform !== selectedPlatform);
|
|
750
|
+
currentAgents.push({ name: agentName, platform: selectedPlatform });
|
|
751
|
+
const raw = existing !== null ? { ...existing } : {};
|
|
752
|
+
raw["apiKey"] = apiKey;
|
|
753
|
+
raw["baseUrl"] = baseUrl;
|
|
754
|
+
raw["agents"] = currentAgents;
|
|
755
|
+
raw["defaultAgent"] = agentName;
|
|
756
|
+
delete raw["agentName"];
|
|
757
|
+
delete raw["platform"];
|
|
758
|
+
lastConfig = raw;
|
|
759
|
+
try {
|
|
760
|
+
await saveConfig(lastConfig);
|
|
761
|
+
process.stderr.write(
|
|
762
|
+
style.green("\u2713") + ` Config saved to ${style.cyan(CONFIG_PATH)}
|
|
763
|
+
`
|
|
764
|
+
);
|
|
765
|
+
} catch (error) {
|
|
766
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
767
|
+
process.stderr.write(style.red(`Failed to save config: ${detail}`) + "\n");
|
|
768
|
+
}
|
|
474
769
|
}
|
|
475
770
|
const another = await ask("\nConnect another agent? (Y/n) ");
|
|
476
771
|
if (another.trim().toLowerCase() === "n") {
|
|
@@ -487,6 +782,35 @@ async function runInit(baseUrl = "https://api.multicorn.ai", platform) {
|
|
|
487
782
|
);
|
|
488
783
|
}
|
|
489
784
|
process.stderr.write("\n");
|
|
785
|
+
const configuredPlatforms = new Set(configuredAgents.map((a) => a.platform));
|
|
786
|
+
process.stderr.write("\n" + style.bold(style.violet("Next steps")) + "\n");
|
|
787
|
+
const blocks = [];
|
|
788
|
+
if (configuredPlatforms.has("openclaw")) {
|
|
789
|
+
blocks.push(
|
|
790
|
+
"\n" + style.bold("To complete your OpenClaw setup:") + "\n \u2192 Restart your gateway: " + style.cyan("openclaw gateway restart") + "\n \u2192 Start a session: " + style.cyan("openclaw tui") + "\n"
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
if (configuredPlatforms.has("claude-code")) {
|
|
794
|
+
blocks.push(
|
|
795
|
+
"\n" + style.bold("To complete your Claude Code setup:") + "\n \u2192 Add marketplace: " + style.cyan("claude plugin marketplace add Multicorn-AI/multicorn-shield") + "\n \u2192 Install plugin: " + style.cyan("claude plugin install multicorn-shield@multicorn-shield") + "\n"
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
if (configuredPlatforms.has("claude-desktop")) {
|
|
799
|
+
blocks.push(
|
|
800
|
+
"\n" + style.bold("To complete your Claude Desktop setup:") + "\n \u2192 Restart Claude Desktop to pick up config changes\n"
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
if (configuredPlatforms.has("cursor")) {
|
|
804
|
+
blocks.push(
|
|
805
|
+
"\n" + style.bold("To complete your Cursor setup:") + "\n \u2192 Restart Cursor to pick up MCP config changes\n"
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
if (configuredPlatforms.has("other-mcp")) {
|
|
809
|
+
blocks.push(
|
|
810
|
+
"\n" + style.bold("To complete your Other MCP Agent setup:") + "\n \u2192 Start your agent with: " + style.cyan("npx multicorn-proxy --wrap <your-server> --agent-name <name>") + "\n"
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
process.stderr.write(blocks.join("") + "\n");
|
|
490
814
|
}
|
|
491
815
|
return lastConfig;
|
|
492
816
|
}
|
|
@@ -1599,10 +1923,23 @@ function parseArgs(argv) {
|
|
|
1599
1923
|
let baseUrl = "https://api.multicorn.ai";
|
|
1600
1924
|
let dashboardUrl = "";
|
|
1601
1925
|
let agentName = "";
|
|
1926
|
+
let deleteAgentName = "";
|
|
1602
1927
|
for (let i = 0; i < args.length; i++) {
|
|
1603
1928
|
const arg = args[i];
|
|
1604
1929
|
if (arg === "init") {
|
|
1605
1930
|
subcommand = "init";
|
|
1931
|
+
} else if (arg === "agents") {
|
|
1932
|
+
subcommand = "agents";
|
|
1933
|
+
} else if (arg === "delete-agent") {
|
|
1934
|
+
subcommand = "delete-agent";
|
|
1935
|
+
const name = args[i + 1];
|
|
1936
|
+
if (name === void 0 || name.startsWith("-")) {
|
|
1937
|
+
process.stderr.write("Error: delete-agent requires an agent name.\n");
|
|
1938
|
+
process.stderr.write("Example: npx multicorn-proxy delete-agent my-agent\n");
|
|
1939
|
+
process.exit(1);
|
|
1940
|
+
}
|
|
1941
|
+
deleteAgentName = name;
|
|
1942
|
+
i++;
|
|
1606
1943
|
} else if (arg === "--wrap") {
|
|
1607
1944
|
subcommand = "wrap";
|
|
1608
1945
|
const next = args[i + 1];
|
|
@@ -1672,7 +2009,16 @@ function parseArgs(argv) {
|
|
|
1672
2009
|
}
|
|
1673
2010
|
}
|
|
1674
2011
|
}
|
|
1675
|
-
return {
|
|
2012
|
+
return {
|
|
2013
|
+
subcommand,
|
|
2014
|
+
wrapCommand,
|
|
2015
|
+
wrapArgs,
|
|
2016
|
+
logLevel,
|
|
2017
|
+
baseUrl,
|
|
2018
|
+
dashboardUrl,
|
|
2019
|
+
agentName,
|
|
2020
|
+
deleteAgentName
|
|
2021
|
+
};
|
|
1676
2022
|
}
|
|
1677
2023
|
function printHelp() {
|
|
1678
2024
|
process.stderr.write(
|
|
@@ -1683,6 +2029,12 @@ function printHelp() {
|
|
|
1683
2029
|
" npx multicorn-proxy init",
|
|
1684
2030
|
" Interactive setup. Saves API key to ~/.multicorn/config.json.",
|
|
1685
2031
|
"",
|
|
2032
|
+
" npx multicorn-proxy agents",
|
|
2033
|
+
" List configured agents and show which is the default.",
|
|
2034
|
+
"",
|
|
2035
|
+
" npx multicorn-proxy delete-agent <name>",
|
|
2036
|
+
" Remove a saved agent.",
|
|
2037
|
+
"",
|
|
1686
2038
|
" npx multicorn-proxy --wrap <command> [args...]",
|
|
1687
2039
|
" Start <command> as an MCP server and proxy all tool calls through",
|
|
1688
2040
|
" Shield's permission layer.",
|
|
@@ -1712,6 +2064,40 @@ async function main() {
|
|
|
1712
2064
|
await runInit(cli.baseUrl);
|
|
1713
2065
|
return;
|
|
1714
2066
|
}
|
|
2067
|
+
if (cli.subcommand === "agents") {
|
|
2068
|
+
const config2 = await loadConfig();
|
|
2069
|
+
if (config2 === null) {
|
|
2070
|
+
process.stderr.write(
|
|
2071
|
+
"No config found. Run `npx multicorn-proxy init` to set up your API key.\n"
|
|
2072
|
+
);
|
|
2073
|
+
process.exit(1);
|
|
2074
|
+
}
|
|
2075
|
+
const agents = collectAgentsFromConfig(config2);
|
|
2076
|
+
if (agents.length === 0) {
|
|
2077
|
+
process.stderr.write("No agents configured. Run `npx multicorn-proxy init` to add one.\n");
|
|
2078
|
+
process.exit(0);
|
|
2079
|
+
}
|
|
2080
|
+
const def = config2.defaultAgent;
|
|
2081
|
+
process.stdout.write("Configured agents:\n");
|
|
2082
|
+
for (const a of agents) {
|
|
2083
|
+
const mark = a.name === def ? " (default)" : "";
|
|
2084
|
+
process.stdout.write(`${a.name} (${a.platform})${mark}
|
|
2085
|
+
`);
|
|
2086
|
+
}
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
if (cli.subcommand === "delete-agent") {
|
|
2090
|
+
const safeName = cli.deleteAgentName.replace(/[^\x20-\x7E]/g, "");
|
|
2091
|
+
const ok = await deleteAgentByName(cli.deleteAgentName);
|
|
2092
|
+
if (!ok) {
|
|
2093
|
+
process.stderr.write(`No agent named "${safeName}" in config.
|
|
2094
|
+
`);
|
|
2095
|
+
process.exit(1);
|
|
2096
|
+
}
|
|
2097
|
+
process.stdout.write(`Removed agent "${safeName}".
|
|
2098
|
+
`);
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
1715
2101
|
if (!cli.baseUrl.startsWith("https://") && !cli.baseUrl.startsWith("http://localhost") && !cli.baseUrl.startsWith("http://127.0.0.1")) {
|
|
1716
2102
|
process.stderr.write(
|
|
1717
2103
|
`Error: --base-url must use HTTPS. Received: "${cli.baseUrl}"
|
|
@@ -1727,9 +2113,11 @@ Use https:// or http://localhost for local development.
|
|
|
1727
2113
|
);
|
|
1728
2114
|
process.exit(1);
|
|
1729
2115
|
}
|
|
1730
|
-
const agentName = cli
|
|
2116
|
+
const agentName = resolveWrapAgentName(cli, config);
|
|
1731
2117
|
const finalBaseUrl = cli.baseUrl !== "https://api.multicorn.ai" ? cli.baseUrl : config.baseUrl;
|
|
1732
2118
|
const finalDashboardUrl = cli.dashboardUrl !== "" ? cli.dashboardUrl : deriveDashboardUrl(finalBaseUrl);
|
|
2119
|
+
const legacyPlatform = config.platform;
|
|
2120
|
+
const platformForServer = typeof legacyPlatform === "string" && legacyPlatform.length > 0 ? legacyPlatform : "other-mcp";
|
|
1733
2121
|
const proxy = createProxyServer({
|
|
1734
2122
|
command: cli.wrapCommand,
|
|
1735
2123
|
commandArgs: cli.wrapArgs,
|
|
@@ -1738,7 +2126,7 @@ Use https:// or http://localhost for local development.
|
|
|
1738
2126
|
baseUrl: finalBaseUrl,
|
|
1739
2127
|
dashboardUrl: finalDashboardUrl,
|
|
1740
2128
|
logger,
|
|
1741
|
-
platform:
|
|
2129
|
+
platform: platformForServer
|
|
1742
2130
|
});
|
|
1743
2131
|
async function shutdown() {
|
|
1744
2132
|
logger.info("Shutting down.");
|
|
@@ -1753,6 +2141,26 @@ Use https:// or http://localhost for local development.
|
|
|
1753
2141
|
});
|
|
1754
2142
|
await proxy.start();
|
|
1755
2143
|
}
|
|
2144
|
+
function resolveWrapAgentName(cli, config) {
|
|
2145
|
+
if (cli.agentName.length > 0) {
|
|
2146
|
+
return cli.agentName;
|
|
2147
|
+
}
|
|
2148
|
+
const legacyPlatform = config.platform;
|
|
2149
|
+
const legacyAgentName = config.agentName;
|
|
2150
|
+
const platformKey = typeof legacyPlatform === "string" && legacyPlatform.length > 0 ? legacyPlatform : "other-mcp";
|
|
2151
|
+
const fromPlatform = getAgentByPlatform(config, platformKey);
|
|
2152
|
+
if (fromPlatform !== void 0) {
|
|
2153
|
+
return fromPlatform.name;
|
|
2154
|
+
}
|
|
2155
|
+
const fallbackDefault = getDefaultAgent(config);
|
|
2156
|
+
if (fallbackDefault !== void 0) {
|
|
2157
|
+
return fallbackDefault.name;
|
|
2158
|
+
}
|
|
2159
|
+
if (typeof legacyAgentName === "string" && legacyAgentName.length > 0) {
|
|
2160
|
+
return legacyAgentName;
|
|
2161
|
+
}
|
|
2162
|
+
return deriveAgentName(cli.wrapCommand);
|
|
2163
|
+
}
|
|
1756
2164
|
function deriveAgentName(command) {
|
|
1757
2165
|
const base = command.split("/").pop() ?? command;
|
|
1758
2166
|
return base.replace(/\.[cm]?[jt]s$/, "");
|
|
@@ -492,11 +492,22 @@ function loadMulticornConfig() {
|
|
|
492
492
|
return null;
|
|
493
493
|
}
|
|
494
494
|
}
|
|
495
|
+
function agentNameFromOpenclawPlatform(cfg) {
|
|
496
|
+
if (cfg === null) return void 0;
|
|
497
|
+
const list = cfg.agents;
|
|
498
|
+
if (!Array.isArray(list)) return void 0;
|
|
499
|
+
for (const e of list) {
|
|
500
|
+
if (typeof e === "object" && e !== null && "platform" in e && "name" in e && e.platform === "openclaw" && typeof e.name === "string") {
|
|
501
|
+
return e.name;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return void 0;
|
|
505
|
+
}
|
|
495
506
|
function readConfig() {
|
|
496
507
|
const pc = pluginConfig ?? {};
|
|
497
508
|
const resolvedApiKey = asString(cachedMulticornConfig?.apiKey) ?? asString(process.env["MULTICORN_API_KEY"]) ?? "";
|
|
498
509
|
const resolvedBaseUrl = asString(cachedMulticornConfig?.baseUrl) ?? asString(process.env["MULTICORN_BASE_URL"]) ?? "https://api.multicorn.ai";
|
|
499
|
-
const agentName = asString(pc["agentName"]) ?? process.env["MULTICORN_AGENT_NAME"] ?? asString(cachedMulticornConfig?.agentName) ?? null;
|
|
510
|
+
const agentName = asString(pc["agentName"]) ?? process.env["MULTICORN_AGENT_NAME"] ?? agentNameFromOpenclawPlatform(cachedMulticornConfig) ?? asString(cachedMulticornConfig?.agentName) ?? null;
|
|
500
511
|
const failMode = "closed";
|
|
501
512
|
return { apiKey: resolvedApiKey, baseUrl: resolvedBaseUrl, agentName, failMode };
|
|
502
513
|
}
|
package/dist/shield-extension.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { mkdirSync, appendFileSync } from 'fs';
|
|
2
|
+
import { readFileSync, mkdirSync, appendFileSync } from 'fs';
|
|
3
3
|
import { readFile, mkdir, writeFile, unlink } from 'fs/promises';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
import { join } from 'path';
|
|
@@ -22358,7 +22358,7 @@ async function writeExtensionBackup(claudeDesktopConfigPath, mcpServers) {
|
|
|
22358
22358
|
|
|
22359
22359
|
// package.json
|
|
22360
22360
|
var package_default = {
|
|
22361
|
-
version: "0.
|
|
22361
|
+
version: "0.6.1"};
|
|
22362
22362
|
|
|
22363
22363
|
// src/package-meta.ts
|
|
22364
22364
|
var PACKAGE_VERSION = package_default.version;
|
|
@@ -23522,8 +23522,27 @@ function readBaseUrl() {
|
|
|
23522
23522
|
return raw;
|
|
23523
23523
|
}
|
|
23524
23524
|
function readAgentName() {
|
|
23525
|
+
try {
|
|
23526
|
+
const rawFile = readFileSync(getMulticornConfigPath(), "utf8");
|
|
23527
|
+
const obj = JSON.parse(rawFile);
|
|
23528
|
+
const agents = obj["agents"];
|
|
23529
|
+
if (Array.isArray(agents)) {
|
|
23530
|
+
for (const e of agents) {
|
|
23531
|
+
if (typeof e === "object" && e !== null && e.platform === "claude-desktop" && typeof e.name === "string") {
|
|
23532
|
+
const n = e.name.trim();
|
|
23533
|
+
if (n.length > 0) {
|
|
23534
|
+
return n;
|
|
23535
|
+
}
|
|
23536
|
+
}
|
|
23537
|
+
}
|
|
23538
|
+
}
|
|
23539
|
+
} catch {
|
|
23540
|
+
}
|
|
23525
23541
|
const raw = process.env["MULTICORN_AGENT_NAME"]?.trim();
|
|
23526
|
-
|
|
23542
|
+
if (raw !== void 0 && raw.length > 0 && !raw.startsWith("${")) {
|
|
23543
|
+
return raw;
|
|
23544
|
+
}
|
|
23545
|
+
return "claude-desktop-shield";
|
|
23527
23546
|
}
|
|
23528
23547
|
function readLogLevel() {
|
|
23529
23548
|
const raw = process.env["MULTICORN_LOG_LEVEL"]?.trim();
|