multicorn-shield 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +11 -7
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +11 -7
- package/dist/multicorn-proxy.js +236 -95
- package/dist/shield-extension.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2106,7 +2106,7 @@ var MulticornShield = class {
|
|
|
2106
2106
|
*
|
|
2107
2107
|
* @example
|
|
2108
2108
|
* ```ts
|
|
2109
|
-
* const shield = new MulticornShield({ apiKey: '
|
|
2109
|
+
* const shield = new MulticornShield({ apiKey: 'mcs_live_abc123...' });
|
|
2110
2110
|
* ```
|
|
2111
2111
|
*/
|
|
2112
2112
|
constructor(config) {
|
|
@@ -2463,16 +2463,20 @@ function validateBaseUrl(baseUrl) {
|
|
|
2463
2463
|
);
|
|
2464
2464
|
}
|
|
2465
2465
|
}
|
|
2466
|
+
var PLACEHOLDER_KEYS = /* @__PURE__ */ new Set(["mcs_your_key_here"]);
|
|
2467
|
+
var INVALID_KEY_MESSAGE = "Invalid Multicorn Shield API key. Get your key at https://app.multicorn.ai/settings";
|
|
2466
2468
|
function validateApiKey(apiKey) {
|
|
2469
|
+
if (typeof apiKey !== "string" || apiKey.length === 0) {
|
|
2470
|
+
throw new Error(INVALID_KEY_MESSAGE);
|
|
2471
|
+
}
|
|
2467
2472
|
if (!apiKey.startsWith(API_KEY_PREFIX)) {
|
|
2468
|
-
throw new Error(
|
|
2469
|
-
`[MulticornShield] Invalid API key format. Keys must start with "${API_KEY_PREFIX}". Find your API key in the Multicorn dashboard under Settings \u2192 API Keys.`
|
|
2470
|
-
);
|
|
2473
|
+
throw new Error(INVALID_KEY_MESSAGE);
|
|
2471
2474
|
}
|
|
2472
2475
|
if (apiKey.length < MIN_API_KEY_LENGTH) {
|
|
2473
|
-
throw new Error(
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
+
throw new Error(INVALID_KEY_MESSAGE);
|
|
2477
|
+
}
|
|
2478
|
+
if (PLACEHOLDER_KEYS.has(apiKey)) {
|
|
2479
|
+
throw new Error(INVALID_KEY_MESSAGE);
|
|
2476
2480
|
}
|
|
2477
2481
|
}
|
|
2478
2482
|
|
package/dist/index.d.cts
CHANGED
|
@@ -1902,7 +1902,7 @@ declare function createMcpAdapter(config: McpAdapterConfig): McpAdapter;
|
|
|
1902
1902
|
* @example
|
|
1903
1903
|
* ```ts
|
|
1904
1904
|
* const shield = new MulticornShield({
|
|
1905
|
-
* apiKey: '
|
|
1905
|
+
* apiKey: 'mcs_live_abc123...',
|
|
1906
1906
|
* baseUrl: 'https://api.multicorn.ai',
|
|
1907
1907
|
* timeout: 5000,
|
|
1908
1908
|
* });
|
|
@@ -1938,7 +1938,7 @@ interface MulticornShieldConfig {
|
|
|
1938
1938
|
* @example
|
|
1939
1939
|
* ```ts
|
|
1940
1940
|
* const shield = new MulticornShield({
|
|
1941
|
-
* apiKey: '
|
|
1941
|
+
* apiKey: 'mcs_live_abc123...',
|
|
1942
1942
|
* onError: (err) => myLogger.warn(err.message),
|
|
1943
1943
|
* });
|
|
1944
1944
|
* ```
|
|
@@ -2052,7 +2052,7 @@ declare class MulticornShield {
|
|
|
2052
2052
|
*
|
|
2053
2053
|
* @example
|
|
2054
2054
|
* ```ts
|
|
2055
|
-
* const shield = new MulticornShield({ apiKey: '
|
|
2055
|
+
* const shield = new MulticornShield({ apiKey: 'mcs_live_abc123...' });
|
|
2056
2056
|
* ```
|
|
2057
2057
|
*/
|
|
2058
2058
|
constructor(config: MulticornShieldConfig);
|
package/dist/index.d.ts
CHANGED
|
@@ -1902,7 +1902,7 @@ declare function createMcpAdapter(config: McpAdapterConfig): McpAdapter;
|
|
|
1902
1902
|
* @example
|
|
1903
1903
|
* ```ts
|
|
1904
1904
|
* const shield = new MulticornShield({
|
|
1905
|
-
* apiKey: '
|
|
1905
|
+
* apiKey: 'mcs_live_abc123...',
|
|
1906
1906
|
* baseUrl: 'https://api.multicorn.ai',
|
|
1907
1907
|
* timeout: 5000,
|
|
1908
1908
|
* });
|
|
@@ -1938,7 +1938,7 @@ interface MulticornShieldConfig {
|
|
|
1938
1938
|
* @example
|
|
1939
1939
|
* ```ts
|
|
1940
1940
|
* const shield = new MulticornShield({
|
|
1941
|
-
* apiKey: '
|
|
1941
|
+
* apiKey: 'mcs_live_abc123...',
|
|
1942
1942
|
* onError: (err) => myLogger.warn(err.message),
|
|
1943
1943
|
* });
|
|
1944
1944
|
* ```
|
|
@@ -2052,7 +2052,7 @@ declare class MulticornShield {
|
|
|
2052
2052
|
*
|
|
2053
2053
|
* @example
|
|
2054
2054
|
* ```ts
|
|
2055
|
-
* const shield = new MulticornShield({ apiKey: '
|
|
2055
|
+
* const shield = new MulticornShield({ apiKey: 'mcs_live_abc123...' });
|
|
2056
2056
|
* ```
|
|
2057
2057
|
*/
|
|
2058
2058
|
constructor(config: MulticornShieldConfig);
|
package/dist/index.js
CHANGED
|
@@ -2104,7 +2104,7 @@ var MulticornShield = class {
|
|
|
2104
2104
|
*
|
|
2105
2105
|
* @example
|
|
2106
2106
|
* ```ts
|
|
2107
|
-
* const shield = new MulticornShield({ apiKey: '
|
|
2107
|
+
* const shield = new MulticornShield({ apiKey: 'mcs_live_abc123...' });
|
|
2108
2108
|
* ```
|
|
2109
2109
|
*/
|
|
2110
2110
|
constructor(config) {
|
|
@@ -2461,16 +2461,20 @@ function validateBaseUrl(baseUrl) {
|
|
|
2461
2461
|
);
|
|
2462
2462
|
}
|
|
2463
2463
|
}
|
|
2464
|
+
var PLACEHOLDER_KEYS = /* @__PURE__ */ new Set(["mcs_your_key_here"]);
|
|
2465
|
+
var INVALID_KEY_MESSAGE = "Invalid Multicorn Shield API key. Get your key at https://app.multicorn.ai/settings";
|
|
2464
2466
|
function validateApiKey(apiKey) {
|
|
2467
|
+
if (typeof apiKey !== "string" || apiKey.length === 0) {
|
|
2468
|
+
throw new Error(INVALID_KEY_MESSAGE);
|
|
2469
|
+
}
|
|
2465
2470
|
if (!apiKey.startsWith(API_KEY_PREFIX)) {
|
|
2466
|
-
throw new Error(
|
|
2467
|
-
`[MulticornShield] Invalid API key format. Keys must start with "${API_KEY_PREFIX}". Find your API key in the Multicorn dashboard under Settings \u2192 API Keys.`
|
|
2468
|
-
);
|
|
2471
|
+
throw new Error(INVALID_KEY_MESSAGE);
|
|
2469
2472
|
}
|
|
2470
2473
|
if (apiKey.length < MIN_API_KEY_LENGTH) {
|
|
2471
|
-
throw new Error(
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
+
throw new Error(INVALID_KEY_MESSAGE);
|
|
2475
|
+
}
|
|
2476
|
+
if (PLACEHOLDER_KEYS.has(apiKey)) {
|
|
2477
|
+
throw new Error(INVALID_KEY_MESSAGE);
|
|
2474
2478
|
}
|
|
2475
2479
|
}
|
|
2476
2480
|
|
package/dist/multicorn-proxy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
|
-
import {
|
|
3
|
+
import { mkdir, writeFile, readFile, unlink } from 'fs/promises';
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
6
|
import { createInterface } from 'readline';
|
|
@@ -94,31 +94,68 @@ function collectAgentsFromConfig(cfg) {
|
|
|
94
94
|
}
|
|
95
95
|
return [];
|
|
96
96
|
}
|
|
97
|
-
async function
|
|
97
|
+
async function parseConfigFile() {
|
|
98
98
|
try {
|
|
99
99
|
const raw = await readFile(CONFIG_PATH, "utf8");
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
100
|
+
try {
|
|
101
|
+
return { kind: "ok", value: JSON.parse(raw) };
|
|
102
|
+
} catch {
|
|
103
|
+
return { kind: "parseError" };
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
107
|
+
return { kind: "missing" };
|
|
108
|
+
}
|
|
109
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
110
|
+
return { kind: "readError", message };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function isAllowedShieldApiBaseUrl(url) {
|
|
114
|
+
return url.startsWith("https://") || url.startsWith("http://localhost") || url.startsWith("http://127.0.0.1");
|
|
115
|
+
}
|
|
116
|
+
async function loadConfig() {
|
|
117
|
+
const result = await parseConfigFile();
|
|
118
|
+
if (result.kind !== "ok") return null;
|
|
119
|
+
const parsed = result.value;
|
|
120
|
+
if (!isProxyConfig(parsed)) return null;
|
|
121
|
+
const obj = parsed;
|
|
122
|
+
const agentNameRaw = obj["agentName"];
|
|
123
|
+
const agentsRaw = obj["agents"];
|
|
124
|
+
const hasNonEmptyAgents = Array.isArray(agentsRaw) && agentsRaw.length > 0 && agentsRaw.every((e) => isAgentEntry(e));
|
|
125
|
+
const needsMigrate = typeof agentNameRaw === "string" && agentNameRaw.length > 0 && !hasNonEmptyAgents;
|
|
126
|
+
if (!needsMigrate) {
|
|
127
|
+
return parsed;
|
|
128
|
+
}
|
|
129
|
+
const platform = typeof obj["platform"] === "string" && obj["platform"].length > 0 ? obj["platform"] : "unknown";
|
|
130
|
+
const next = { ...obj };
|
|
131
|
+
delete next["agentName"];
|
|
132
|
+
delete next["platform"];
|
|
133
|
+
next["agents"] = [{ name: agentNameRaw, platform }];
|
|
134
|
+
next["defaultAgent"] = agentNameRaw;
|
|
135
|
+
const migrated = next;
|
|
136
|
+
await saveConfig(migrated);
|
|
137
|
+
return migrated;
|
|
138
|
+
}
|
|
139
|
+
async function readBaseUrlFromConfig() {
|
|
140
|
+
const result = await parseConfigFile();
|
|
141
|
+
if (result.kind === "missing") return void 0;
|
|
142
|
+
if (result.kind === "readError") {
|
|
143
|
+
process.stderr.write(
|
|
144
|
+
style.yellow(`Warning: could not read base URL from config file: ${result.message}`) + "\n"
|
|
145
|
+
);
|
|
146
|
+
return void 0;
|
|
121
147
|
}
|
|
148
|
+
if (result.kind === "parseError") {
|
|
149
|
+
process.stderr.write(
|
|
150
|
+
style.yellow("Warning: could not parse ~/.multicorn/config.json as JSON.") + "\n"
|
|
151
|
+
);
|
|
152
|
+
return void 0;
|
|
153
|
+
}
|
|
154
|
+
const parsed = result.value;
|
|
155
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
156
|
+
const u = parsed["baseUrl"];
|
|
157
|
+
if (typeof u !== "string" || u.length === 0) return void 0;
|
|
158
|
+
return u;
|
|
122
159
|
}
|
|
123
160
|
async function deleteAgentByName(name) {
|
|
124
161
|
const config = await loadConfig();
|
|
@@ -329,11 +366,12 @@ async function isCursorConnected() {
|
|
|
329
366
|
return false;
|
|
330
367
|
}
|
|
331
368
|
}
|
|
332
|
-
var PLATFORM_LABELS = ["OpenClaw", "Claude Code", "Cursor"];
|
|
369
|
+
var PLATFORM_LABELS = ["OpenClaw", "Claude Code", "Cursor", "Local MCP / Other"];
|
|
333
370
|
var PLATFORM_BY_SELECTION = {
|
|
334
371
|
1: "openclaw",
|
|
335
372
|
2: "claude-code",
|
|
336
|
-
3: "cursor"
|
|
373
|
+
3: "cursor",
|
|
374
|
+
4: "other-mcp"
|
|
337
375
|
};
|
|
338
376
|
var DEFAULT_AGENT_NAMES = {
|
|
339
377
|
openclaw: "my-openclaw-agent",
|
|
@@ -350,17 +388,20 @@ async function promptPlatformSelection(ask) {
|
|
|
350
388
|
await isCursorConnected()
|
|
351
389
|
];
|
|
352
390
|
for (let i = 0; i < PLATFORM_LABELS.length; i++) {
|
|
353
|
-
const marker = connectedFlags[i] ? " " + style.
|
|
391
|
+
const marker = i < connectedFlags.length && connectedFlags[i] ? " " + style.dim("\u25CF detected locally") : "";
|
|
354
392
|
process.stderr.write(
|
|
355
393
|
` ${style.violet(String(i + 1))}. ${PLATFORM_LABELS[i] ?? ""}${marker}
|
|
356
394
|
`
|
|
357
395
|
);
|
|
358
396
|
}
|
|
397
|
+
process.stderr.write(
|
|
398
|
+
style.dim(" Pick 4 if you want to wrap a local MCP server with multicorn-proxy --wrap.") + "\n"
|
|
399
|
+
);
|
|
359
400
|
let selection = 0;
|
|
360
401
|
while (selection === 0) {
|
|
361
|
-
const input = await ask("Select (1-
|
|
402
|
+
const input = await ask("Select (1-4): ");
|
|
362
403
|
const num = parseInt(input.trim(), 10);
|
|
363
|
-
if (num >= 1 && num <=
|
|
404
|
+
if (num >= 1 && num <= 4) {
|
|
364
405
|
selection = num;
|
|
365
406
|
}
|
|
366
407
|
}
|
|
@@ -490,7 +531,7 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
490
531
|
if (platform !== "cursor") {
|
|
491
532
|
process.stderr.write(
|
|
492
533
|
style.dim(
|
|
493
|
-
"Replace YOUR_SHIELD_API_KEY with your API key. Find it in Settings > API keys at https://app.multicorn.ai/settings
|
|
534
|
+
"Replace YOUR_SHIELD_API_KEY with your API key. Find it in Settings > API keys at https://app.multicorn.ai/settings#api-keys"
|
|
494
535
|
) + "\n"
|
|
495
536
|
);
|
|
496
537
|
}
|
|
@@ -507,7 +548,8 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
507
548
|
);
|
|
508
549
|
}
|
|
509
550
|
}
|
|
510
|
-
|
|
551
|
+
var DEFAULT_SHIELD_API_BASE_URL = "https://api.multicorn.ai";
|
|
552
|
+
async function runInit(explicitBaseUrl) {
|
|
511
553
|
if (!process.stdin.isTTY) {
|
|
512
554
|
process.stderr.write(
|
|
513
555
|
style.red("Error: interactive terminal required. Cannot run init with piped input.") + "\n"
|
|
@@ -522,19 +564,32 @@ async function runInit(baseUrl = "https://api.multicorn.ai") {
|
|
|
522
564
|
process.stderr.write(style.dim("Agent governance for the AI era") + "\n\n");
|
|
523
565
|
process.stderr.write(style.bold(style.violet("Multicorn Shield proxy setup")) + "\n\n");
|
|
524
566
|
process.stderr.write(
|
|
525
|
-
style.dim("Get your API key at https://app.multicorn.ai/settings
|
|
567
|
+
style.dim("Get your API key at https://app.multicorn.ai/settings#api-keys") + "\n\n"
|
|
526
568
|
);
|
|
527
569
|
const existing = await loadConfig().catch(() => null);
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
570
|
+
let resolvedBaseUrl;
|
|
571
|
+
if (explicitBaseUrl !== void 0 && explicitBaseUrl.trim().length > 0) {
|
|
572
|
+
resolvedBaseUrl = explicitBaseUrl.trim();
|
|
573
|
+
} else if (existing !== null && existing.baseUrl.length > 0) {
|
|
574
|
+
resolvedBaseUrl = existing.baseUrl;
|
|
575
|
+
} else {
|
|
576
|
+
const fromFile = await readBaseUrlFromConfig();
|
|
577
|
+
if (fromFile !== void 0 && fromFile.length > 0) {
|
|
578
|
+
resolvedBaseUrl = fromFile;
|
|
531
579
|
} else {
|
|
532
580
|
const envBaseUrl = process.env["MULTICORN_BASE_URL"];
|
|
533
|
-
|
|
534
|
-
baseUrl = envBaseUrl;
|
|
535
|
-
}
|
|
581
|
+
resolvedBaseUrl = envBaseUrl !== void 0 && envBaseUrl.trim().length > 0 ? envBaseUrl.trim() : DEFAULT_SHIELD_API_BASE_URL;
|
|
536
582
|
}
|
|
537
583
|
}
|
|
584
|
+
if (!isAllowedShieldApiBaseUrl(resolvedBaseUrl)) {
|
|
585
|
+
process.stderr.write(
|
|
586
|
+
style.red(
|
|
587
|
+
"Base URL must use HTTPS (or http://localhost for local development). Received a non-HTTPS URL from config. Use --base-url to override."
|
|
588
|
+
) + "\n"
|
|
589
|
+
);
|
|
590
|
+
rl.close();
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
538
593
|
let apiKey = "";
|
|
539
594
|
if (existing !== null && existing.apiKey.startsWith("mcs_") && existing.apiKey.length >= 8) {
|
|
540
595
|
const masked = "mcs_..." + existing.apiKey.slice(-4);
|
|
@@ -554,7 +609,7 @@ async function runInit(baseUrl = "https://api.multicorn.ai") {
|
|
|
554
609
|
const spinner = withSpinner("Validating key...");
|
|
555
610
|
let result;
|
|
556
611
|
try {
|
|
557
|
-
result = await validateApiKey(key,
|
|
612
|
+
result = await validateApiKey(key, resolvedBaseUrl);
|
|
558
613
|
} catch (error) {
|
|
559
614
|
spinner.stop(false, "Validation failed");
|
|
560
615
|
throw error;
|
|
@@ -566,18 +621,11 @@ async function runInit(baseUrl = "https://api.multicorn.ai") {
|
|
|
566
621
|
spinner.stop(true, "Key validated");
|
|
567
622
|
apiKey = key;
|
|
568
623
|
}
|
|
569
|
-
if (!baseUrl.startsWith("https://") && !baseUrl.startsWith("http://localhost") && !baseUrl.startsWith("http://127.0.0.1")) {
|
|
570
|
-
process.stderr.write(
|
|
571
|
-
style.red(`\u2717 Shield API base URL must use HTTPS. Got: ${baseUrl}`) + "\n"
|
|
572
|
-
);
|
|
573
|
-
rl.close();
|
|
574
|
-
return null;
|
|
575
|
-
}
|
|
576
624
|
const configuredAgents = [];
|
|
577
625
|
let currentAgents = collectAgentsFromConfig(existing);
|
|
578
626
|
let lastConfig = {
|
|
579
627
|
apiKey,
|
|
580
|
-
baseUrl,
|
|
628
|
+
baseUrl: resolvedBaseUrl,
|
|
581
629
|
...currentAgents.length > 0 ? {
|
|
582
630
|
agents: currentAgents,
|
|
583
631
|
defaultAgent: existing !== null && typeof existing.defaultAgent === "string" && existing.defaultAgent.length > 0 ? existing.defaultAgent : currentAgents[currentAgents.length - 1]?.name ?? ""
|
|
@@ -588,6 +636,40 @@ async function runInit(baseUrl = "https://api.multicorn.ai") {
|
|
|
588
636
|
const selection = await promptPlatformSelection(ask);
|
|
589
637
|
const selectedPlatform = PLATFORM_BY_SELECTION[selection] ?? "cursor";
|
|
590
638
|
const selectedLabel = PLATFORM_LABELS[selection - 1] ?? "Cursor";
|
|
639
|
+
if (selection === 4) {
|
|
640
|
+
const raw = existing !== null ? { ...existing } : {};
|
|
641
|
+
raw["apiKey"] = apiKey;
|
|
642
|
+
raw["baseUrl"] = resolvedBaseUrl;
|
|
643
|
+
delete raw["agentName"];
|
|
644
|
+
delete raw["platform"];
|
|
645
|
+
lastConfig = raw;
|
|
646
|
+
try {
|
|
647
|
+
await saveConfig(lastConfig);
|
|
648
|
+
process.stderr.write(
|
|
649
|
+
style.green("\u2713") + ` Config saved to ${style.cyan(CONFIG_PATH)}
|
|
650
|
+
`
|
|
651
|
+
);
|
|
652
|
+
process.stderr.write(
|
|
653
|
+
"\n" + style.bold("Try it:") + " " + style.cyan(
|
|
654
|
+
"npx multicorn-proxy --wrap npx @modelcontextprotocol/server-filesystem /tmp"
|
|
655
|
+
) + "\n"
|
|
656
|
+
);
|
|
657
|
+
} catch (error) {
|
|
658
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
659
|
+
process.stderr.write(style.red(`Failed to save config: ${detail}`) + "\n");
|
|
660
|
+
}
|
|
661
|
+
configuredAgents.push({
|
|
662
|
+
selection,
|
|
663
|
+
platform: selectedPlatform,
|
|
664
|
+
platformLabel: selectedLabel,
|
|
665
|
+
agentName: ""
|
|
666
|
+
});
|
|
667
|
+
const another2 = await ask("\nConnect another agent? (Y/n) ");
|
|
668
|
+
if (another2.trim().toLowerCase() === "n") {
|
|
669
|
+
configuring = false;
|
|
670
|
+
}
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
591
673
|
const existingForPlatform = currentAgents.find((a) => a.platform === selectedPlatform);
|
|
592
674
|
if (existingForPlatform !== void 0) {
|
|
593
675
|
process.stderr.write(
|
|
@@ -657,7 +739,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
657
739
|
}
|
|
658
740
|
const spinner = withSpinner("Updating OpenClaw config...");
|
|
659
741
|
try {
|
|
660
|
-
const result = await updateOpenClawConfigIfPresent(apiKey,
|
|
742
|
+
const result = await updateOpenClawConfigIfPresent(apiKey, resolvedBaseUrl, agentName);
|
|
661
743
|
if (result === "not-found") {
|
|
662
744
|
spinner.stop(false, "OpenClaw config disappeared unexpectedly.");
|
|
663
745
|
rl.close();
|
|
@@ -712,7 +794,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
712
794
|
const spinner = withSpinner("Creating proxy config...");
|
|
713
795
|
try {
|
|
714
796
|
proxyUrl = await createProxyConfig(
|
|
715
|
-
|
|
797
|
+
resolvedBaseUrl,
|
|
716
798
|
apiKey,
|
|
717
799
|
agentName,
|
|
718
800
|
targetUrl,
|
|
@@ -750,7 +832,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
750
832
|
currentAgents.push({ name: agentName, platform: selectedPlatform });
|
|
751
833
|
const raw = existing !== null ? { ...existing } : {};
|
|
752
834
|
raw["apiKey"] = apiKey;
|
|
753
|
-
raw["baseUrl"] =
|
|
835
|
+
raw["baseUrl"] = resolvedBaseUrl;
|
|
754
836
|
raw["agents"] = currentAgents;
|
|
755
837
|
raw["defaultAgent"] = agentName;
|
|
756
838
|
delete raw["agentName"];
|
|
@@ -776,14 +858,15 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
776
858
|
if (configuredAgents.length > 0) {
|
|
777
859
|
process.stderr.write("\n" + style.bold(style.violet("Setup complete")) + "\n\n");
|
|
778
860
|
for (const agent of configuredAgents) {
|
|
861
|
+
const namePart = agent.agentName.length > 0 ? ` - ${style.cyan(agent.agentName)}` : "";
|
|
862
|
+
const urlPart = agent.proxyUrl != null ? ` ${style.dim(`(${agent.proxyUrl})`)}` : "";
|
|
779
863
|
process.stderr.write(
|
|
780
|
-
` ${style.green("\u2713")} ${agent.platformLabel}
|
|
864
|
+
` ${style.green("\u2713")} ${agent.platformLabel}${namePart}${urlPart}
|
|
781
865
|
`
|
|
782
866
|
);
|
|
783
867
|
}
|
|
784
868
|
process.stderr.write("\n");
|
|
785
869
|
const configuredPlatforms = new Set(configuredAgents.map((a) => a.platform));
|
|
786
|
-
process.stderr.write("\n" + style.bold(style.violet("Next steps")) + "\n");
|
|
787
870
|
const blocks = [];
|
|
788
871
|
if (configuredPlatforms.has("openclaw")) {
|
|
789
872
|
blocks.push(
|
|
@@ -805,12 +888,10 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
805
888
|
"\n" + style.bold("To complete your Cursor setup:") + "\n \u2192 Restart Cursor to pick up MCP config changes\n"
|
|
806
889
|
);
|
|
807
890
|
}
|
|
808
|
-
if (
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
);
|
|
891
|
+
if (blocks.length > 0) {
|
|
892
|
+
process.stderr.write("\n" + style.bold(style.violet("Next steps")) + "\n");
|
|
893
|
+
process.stderr.write(blocks.join("") + "\n");
|
|
812
894
|
}
|
|
813
|
-
process.stderr.write(blocks.join("") + "\n");
|
|
814
895
|
}
|
|
815
896
|
return lastConfig;
|
|
816
897
|
}
|
|
@@ -1807,6 +1888,15 @@ function createProxyServer(config) {
|
|
|
1807
1888
|
agentId = agentRecord.id;
|
|
1808
1889
|
grantedScopes = agentRecord.scopes;
|
|
1809
1890
|
authInvalid = agentRecord.authInvalid === true;
|
|
1891
|
+
if (authInvalid) {
|
|
1892
|
+
config.logger.error("API key rejected by the Multicorn service.", {
|
|
1893
|
+
agent: config.agentName
|
|
1894
|
+
});
|
|
1895
|
+
process.stderr.write(
|
|
1896
|
+
"\nError: API key was rejected by the Multicorn service.\nCheck your key at https://app.multicorn.ai/settings#api-keys or run `npx multicorn-proxy init` to reconfigure.\n\n"
|
|
1897
|
+
);
|
|
1898
|
+
throw new Error("API key was rejected by the Multicorn service.");
|
|
1899
|
+
}
|
|
1810
1900
|
config.logger.info("Agent resolved.", {
|
|
1811
1901
|
agent: config.agentName,
|
|
1812
1902
|
id: agentId,
|
|
@@ -1920,10 +2010,11 @@ function parseArgs(argv) {
|
|
|
1920
2010
|
let wrapCommand = "";
|
|
1921
2011
|
let wrapArgs = [];
|
|
1922
2012
|
let logLevel = "info";
|
|
1923
|
-
let baseUrl =
|
|
2013
|
+
let baseUrl = void 0;
|
|
1924
2014
|
let dashboardUrl = "";
|
|
1925
2015
|
let agentName = "";
|
|
1926
2016
|
let deleteAgentName = "";
|
|
2017
|
+
let apiKey = void 0;
|
|
1927
2018
|
for (let i = 0; i < args.length; i++) {
|
|
1928
2019
|
const arg = args[i];
|
|
1929
2020
|
if (arg === "init") {
|
|
@@ -1942,46 +2033,56 @@ function parseArgs(argv) {
|
|
|
1942
2033
|
i++;
|
|
1943
2034
|
} else if (arg === "--wrap") {
|
|
1944
2035
|
subcommand = "wrap";
|
|
1945
|
-
const
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
for (let j = 0; j < wrapArgs.length; j++) {
|
|
1955
|
-
const token = wrapArgs[j];
|
|
2036
|
+
const tail = args.slice(i + 1);
|
|
2037
|
+
const remaining = [];
|
|
2038
|
+
for (let j = 0; j < tail.length; j++) {
|
|
2039
|
+
const token = tail[j];
|
|
2040
|
+
if (token === void 0) continue;
|
|
2041
|
+
if (remaining.length > 0) {
|
|
2042
|
+
remaining.push(token);
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
1956
2045
|
if (token === "--agent-name") {
|
|
1957
|
-
const value =
|
|
2046
|
+
const value = tail[j + 1];
|
|
1958
2047
|
if (value !== void 0) {
|
|
1959
2048
|
agentName = value;
|
|
1960
2049
|
j++;
|
|
1961
2050
|
}
|
|
1962
2051
|
} else if (token === "--log-level") {
|
|
1963
|
-
const value =
|
|
2052
|
+
const value = tail[j + 1];
|
|
1964
2053
|
if (value !== void 0 && isValidLogLevel(value)) {
|
|
1965
2054
|
logLevel = value;
|
|
1966
2055
|
j++;
|
|
1967
2056
|
}
|
|
1968
2057
|
} else if (token === "--base-url") {
|
|
1969
|
-
const value =
|
|
2058
|
+
const value = tail[j + 1];
|
|
1970
2059
|
if (value !== void 0) {
|
|
1971
2060
|
baseUrl = value;
|
|
1972
2061
|
j++;
|
|
1973
2062
|
}
|
|
1974
2063
|
} else if (token === "--dashboard-url") {
|
|
1975
|
-
const value =
|
|
2064
|
+
const value = tail[j + 1];
|
|
1976
2065
|
if (value !== void 0) {
|
|
1977
2066
|
dashboardUrl = value;
|
|
1978
2067
|
j++;
|
|
1979
2068
|
}
|
|
1980
|
-
} else if (token
|
|
1981
|
-
|
|
2069
|
+
} else if (token === "--api-key") {
|
|
2070
|
+
const value = tail[j + 1];
|
|
2071
|
+
if (value !== void 0) {
|
|
2072
|
+
apiKey = value;
|
|
2073
|
+
j++;
|
|
2074
|
+
}
|
|
2075
|
+
} else {
|
|
2076
|
+
remaining.push(token);
|
|
1982
2077
|
}
|
|
1983
2078
|
}
|
|
1984
|
-
|
|
2079
|
+
if (remaining.length === 0) {
|
|
2080
|
+
process.stderr.write("Error: --wrap requires a command to run.\n");
|
|
2081
|
+
process.stderr.write("Example: npx multicorn-proxy --wrap my-mcp-server\n");
|
|
2082
|
+
process.exit(1);
|
|
2083
|
+
}
|
|
2084
|
+
wrapCommand = remaining[0] ?? "";
|
|
2085
|
+
wrapArgs = remaining.slice(1);
|
|
1985
2086
|
break;
|
|
1986
2087
|
} else if (arg === "--log-level") {
|
|
1987
2088
|
const next = args[i + 1];
|
|
@@ -2007,6 +2108,12 @@ function parseArgs(argv) {
|
|
|
2007
2108
|
agentName = next;
|
|
2008
2109
|
i++;
|
|
2009
2110
|
}
|
|
2111
|
+
} else if (arg === "--api-key") {
|
|
2112
|
+
const next = args[i + 1];
|
|
2113
|
+
if (next !== void 0) {
|
|
2114
|
+
apiKey = next;
|
|
2115
|
+
i++;
|
|
2116
|
+
}
|
|
2010
2117
|
}
|
|
2011
2118
|
}
|
|
2012
2119
|
return {
|
|
@@ -2017,7 +2124,8 @@ function parseArgs(argv) {
|
|
|
2017
2124
|
baseUrl,
|
|
2018
2125
|
dashboardUrl,
|
|
2019
2126
|
agentName,
|
|
2020
|
-
deleteAgentName
|
|
2127
|
+
deleteAgentName,
|
|
2128
|
+
apiKey
|
|
2021
2129
|
};
|
|
2022
2130
|
}
|
|
2023
2131
|
function printHelp() {
|
|
@@ -2040,6 +2148,7 @@ function printHelp() {
|
|
|
2040
2148
|
" Shield's permission layer.",
|
|
2041
2149
|
"",
|
|
2042
2150
|
"Options:",
|
|
2151
|
+
" --api-key <key> Multicorn API key (overrides MULTICORN_API_KEY env var and config file)",
|
|
2043
2152
|
" --log-level <level> Log level: debug | info | warn | error (default: info)",
|
|
2044
2153
|
" --base-url <url> Multicorn API base URL (default: https://api.multicorn.ai)",
|
|
2045
2154
|
" --dashboard-url <url> Dashboard URL for consent page (default: derived from --base-url)",
|
|
@@ -2098,23 +2207,25 @@ async function main() {
|
|
|
2098
2207
|
`);
|
|
2099
2208
|
return;
|
|
2100
2209
|
}
|
|
2101
|
-
if (
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2210
|
+
if (cli.baseUrl !== void 0 && cli.baseUrl.length > 0) {
|
|
2211
|
+
if (!isAllowedShieldApiBaseUrl(cli.baseUrl)) {
|
|
2212
|
+
process.stderr.write(
|
|
2213
|
+
"Error: --base-url must use HTTPS. Received a non-HTTPS URL.\nUse https:// or http://localhost for local development.\n"
|
|
2214
|
+
);
|
|
2215
|
+
process.exit(1);
|
|
2216
|
+
}
|
|
2108
2217
|
}
|
|
2109
|
-
const config = await
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2218
|
+
const config = await resolveWrapConfig(cli, logger);
|
|
2219
|
+
const finalBaseUrl = cli.baseUrl !== void 0 && cli.baseUrl.length > 0 ? cli.baseUrl : config.baseUrl;
|
|
2220
|
+
if (cli.baseUrl === void 0 || cli.baseUrl.length === 0) {
|
|
2221
|
+
if (!isAllowedShieldApiBaseUrl(finalBaseUrl)) {
|
|
2222
|
+
process.stderr.write(
|
|
2223
|
+
"Error: --base-url must use HTTPS. Received a non-HTTPS URL.\nUse https:// or http://localhost for local development.\n"
|
|
2224
|
+
);
|
|
2225
|
+
process.exit(1);
|
|
2226
|
+
}
|
|
2115
2227
|
}
|
|
2116
2228
|
const agentName = resolveWrapAgentName(cli, config);
|
|
2117
|
-
const finalBaseUrl = cli.baseUrl !== "https://api.multicorn.ai" ? cli.baseUrl : config.baseUrl;
|
|
2118
2229
|
const finalDashboardUrl = cli.dashboardUrl !== "" ? cli.dashboardUrl : deriveDashboardUrl(finalBaseUrl);
|
|
2119
2230
|
const legacyPlatform = config.platform;
|
|
2120
2231
|
const platformForServer = typeof legacyPlatform === "string" && legacyPlatform.length > 0 ? legacyPlatform : "other-mcp";
|
|
@@ -2141,6 +2252,31 @@ Use https:// or http://localhost for local development.
|
|
|
2141
2252
|
});
|
|
2142
2253
|
await proxy.start();
|
|
2143
2254
|
}
|
|
2255
|
+
async function resolveWrapConfig(cli, logger) {
|
|
2256
|
+
if (cli.apiKey !== void 0 && cli.apiKey.length > 0) {
|
|
2257
|
+
logger.debug("Using API key from --api-key flag.");
|
|
2258
|
+
return {
|
|
2259
|
+
apiKey: cli.apiKey,
|
|
2260
|
+
baseUrl: cli.baseUrl ?? DEFAULT_SHIELD_API_BASE_URL
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
const envKey = process.env["MULTICORN_API_KEY"];
|
|
2264
|
+
if (typeof envKey === "string" && envKey.length > 0) {
|
|
2265
|
+
logger.debug("Using API key from MULTICORN_API_KEY environment variable.");
|
|
2266
|
+
return {
|
|
2267
|
+
apiKey: envKey,
|
|
2268
|
+
baseUrl: cli.baseUrl ?? DEFAULT_SHIELD_API_BASE_URL
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
const config = await loadConfig();
|
|
2272
|
+
if (config !== null) {
|
|
2273
|
+
return config;
|
|
2274
|
+
}
|
|
2275
|
+
process.stderr.write(
|
|
2276
|
+
"No API key found. Provide one via the --api-key flag, the MULTICORN_API_KEY environment variable, or run `npx multicorn-proxy init` to set up a config file.\n"
|
|
2277
|
+
);
|
|
2278
|
+
process.exit(1);
|
|
2279
|
+
}
|
|
2144
2280
|
function resolveWrapAgentName(cli, config) {
|
|
2145
2281
|
if (cli.agentName.length > 0) {
|
|
2146
2282
|
return cli.agentName;
|
|
@@ -2165,9 +2301,14 @@ function deriveAgentName(command) {
|
|
|
2165
2301
|
const base = command.split("/").pop() ?? command;
|
|
2166
2302
|
return base.replace(/\.[cm]?[jt]s$/, "");
|
|
2167
2303
|
}
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2304
|
+
var isDirectRun = process.argv[1] !== void 0 && (import.meta.url.endsWith(process.argv[1]) || import.meta.url === `file://${process.argv[1]}` || import.meta.url.endsWith("/multicorn-proxy.js") || import.meta.url.endsWith("/multicorn-proxy.ts"));
|
|
2305
|
+
if (isDirectRun && process.env["VITEST"] === void 0) {
|
|
2306
|
+
main().catch((error) => {
|
|
2307
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2308
|
+
process.stderr.write(`Fatal error: ${message}
|
|
2171
2309
|
`);
|
|
2172
|
-
|
|
2173
|
-
});
|
|
2310
|
+
process.exit(1);
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
export { parseArgs, resolveWrapConfig };
|
package/dist/shield-extension.js
CHANGED
|
@@ -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.7.0"};
|
|
22362
22362
|
|
|
22363
22363
|
// src/package-meta.ts
|
|
22364
22364
|
var PACKAGE_VERSION = package_default.version;
|