@hasna/mcps 0.0.14 → 0.0.15
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/bin/index.js +534 -99
- package/bin/mcp.js +352 -58
- package/dist/index.d.ts +2 -0
- package/dist/index.js +244 -7
- package/dist/lib/doctor.d.ts +4 -1
- package/dist/lib/install.d.ts +5 -1
- package/dist/lib/local-command-consent.d.ts +38 -0
- package/dist/lib/proxy.d.ts +6 -2
- package/dist/lib/remote.d.ts +4 -1
- package/dist/mcp/index.js +352 -58
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -19412,7 +19412,7 @@ var init_sources = __esm(() => {
|
|
|
19412
19412
|
var require_package = __commonJS((exports, module) => {
|
|
19413
19413
|
module.exports = {
|
|
19414
19414
|
name: "@hasna/mcps",
|
|
19415
|
-
version: "0.0.
|
|
19415
|
+
version: "0.0.15",
|
|
19416
19416
|
description: "Meta-MCP registry & CLI \u2014 discover, manage, and proxy MCP servers",
|
|
19417
19417
|
type: "module",
|
|
19418
19418
|
repository: {
|
|
@@ -35591,6 +35591,185 @@ class StreamableHTTPClientTransport {
|
|
|
35591
35591
|
// src/lib/proxy.ts
|
|
35592
35592
|
init_config2();
|
|
35593
35593
|
init_db();
|
|
35594
|
+
|
|
35595
|
+
// src/lib/local-command-consent.ts
|
|
35596
|
+
class LocalCommandConsentError extends Error {
|
|
35597
|
+
review;
|
|
35598
|
+
constructor(message, review) {
|
|
35599
|
+
super(message);
|
|
35600
|
+
this.name = "LocalCommandConsentError";
|
|
35601
|
+
this.review = review;
|
|
35602
|
+
}
|
|
35603
|
+
}
|
|
35604
|
+
var SHELL_COMMANDS = new Set(["bash", "sh", "zsh", "fish", "cmd", "cmd.exe", "powershell", "powershell.exe", "pwsh"]);
|
|
35605
|
+
var DESTRUCTIVE_COMMANDS = new Set([
|
|
35606
|
+
"rm",
|
|
35607
|
+
"dd",
|
|
35608
|
+
"mkfs",
|
|
35609
|
+
"shutdown",
|
|
35610
|
+
"reboot",
|
|
35611
|
+
"poweroff",
|
|
35612
|
+
"halt",
|
|
35613
|
+
"killall",
|
|
35614
|
+
"pkill"
|
|
35615
|
+
]);
|
|
35616
|
+
var SHELL_EVAL_FLAGS = new Set(["-c", "/c", "-Command", "-command", "-EncodedCommand", "-encodedcommand"]);
|
|
35617
|
+
var SECRET_FLAG_PATTERN = /(?:^|[-_])(api[-_]?key|token|secret|password|passwd|credential|auth|private[-_]?key)(?:$|[-_])/i;
|
|
35618
|
+
var SECRET_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
|
|
35619
|
+
var SECRET_VALUE_PATTERN = /^(sk_(?:live|test)_[A-Za-z0-9_]+|ghp_[A-Za-z0-9_]+|github_pat_[A-Za-z0-9_]+|xox[baprs]-[A-Za-z0-9-]+|AIza[A-Za-z0-9_-]{20,}|eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)$/;
|
|
35620
|
+
var SHELL_META_PATTERN = /[;&|`<>]|\$\(/;
|
|
35621
|
+
function commandBase(command) {
|
|
35622
|
+
return command.trim().split(/[\\/]/).pop()?.toLowerCase() || command.trim().toLowerCase();
|
|
35623
|
+
}
|
|
35624
|
+
function normalizeArgs(args) {
|
|
35625
|
+
return (args ?? []).map((arg) => String(arg));
|
|
35626
|
+
}
|
|
35627
|
+
function isSecretKey(key) {
|
|
35628
|
+
return SECRET_KEY_PATTERN.test(key) || SECRET_FLAG_PATTERN.test(key);
|
|
35629
|
+
}
|
|
35630
|
+
function isSecretValue(value) {
|
|
35631
|
+
return SECRET_VALUE_PATTERN.test(value.trim());
|
|
35632
|
+
}
|
|
35633
|
+
function isSecretAssignment(arg) {
|
|
35634
|
+
const eqIdx = arg.indexOf("=");
|
|
35635
|
+
if (eqIdx <= 0)
|
|
35636
|
+
return false;
|
|
35637
|
+
const key = arg.slice(0, eqIdx);
|
|
35638
|
+
const value = arg.slice(eqIdx + 1);
|
|
35639
|
+
return isSecretKey(key) || isSecretValue(value);
|
|
35640
|
+
}
|
|
35641
|
+
function isSecretArg(args, index) {
|
|
35642
|
+
const arg = args[index] ?? "";
|
|
35643
|
+
const previous = args[index - 1] ?? "";
|
|
35644
|
+
return isSecretAssignment(arg) || isSecretValue(arg) || SECRET_FLAG_PATTERN.test(previous);
|
|
35645
|
+
}
|
|
35646
|
+
function quoteArg(value) {
|
|
35647
|
+
return JSON.stringify(value);
|
|
35648
|
+
}
|
|
35649
|
+
function displayCommand(command, args) {
|
|
35650
|
+
return [quoteArg(command), ...args.map((arg, index) => quoteArg(isSecretArg(args, index) ? "<redacted>" : arg))].join(" ");
|
|
35651
|
+
}
|
|
35652
|
+
function pushRisk(risks, risk) {
|
|
35653
|
+
if (risks.some((existing) => existing.code === risk.code && existing.evidence === risk.evidence))
|
|
35654
|
+
return;
|
|
35655
|
+
risks.push(risk);
|
|
35656
|
+
}
|
|
35657
|
+
function inspectRisks(command, args, env) {
|
|
35658
|
+
const risks = [];
|
|
35659
|
+
const base = commandBase(command);
|
|
35660
|
+
const joined = [command, ...args].join(" ");
|
|
35661
|
+
if (SHELL_COMMANDS.has(base)) {
|
|
35662
|
+
pushRisk(risks, {
|
|
35663
|
+
code: "shell_interpreter",
|
|
35664
|
+
severity: "warning",
|
|
35665
|
+
message: "Command launches a shell interpreter.",
|
|
35666
|
+
evidence: base
|
|
35667
|
+
});
|
|
35668
|
+
if (args.some((arg) => SHELL_EVAL_FLAGS.has(arg))) {
|
|
35669
|
+
pushRisk(risks, {
|
|
35670
|
+
code: "shell_eval",
|
|
35671
|
+
severity: "danger",
|
|
35672
|
+
message: "Shell command evaluates an inline script.",
|
|
35673
|
+
evidence: base
|
|
35674
|
+
});
|
|
35675
|
+
}
|
|
35676
|
+
}
|
|
35677
|
+
if (base === "sudo") {
|
|
35678
|
+
pushRisk(risks, {
|
|
35679
|
+
code: "privilege_escalation",
|
|
35680
|
+
severity: "danger",
|
|
35681
|
+
message: "Command requests elevated privileges.",
|
|
35682
|
+
evidence: base
|
|
35683
|
+
});
|
|
35684
|
+
}
|
|
35685
|
+
if (DESTRUCTIVE_COMMANDS.has(base) || /\brm\s+-[^\s]*[rf][^\s]*\b/.test(joined) || /--no-preserve-root\b/.test(joined)) {
|
|
35686
|
+
pushRisk(risks, {
|
|
35687
|
+
code: "destructive_command",
|
|
35688
|
+
severity: "danger",
|
|
35689
|
+
message: "Command includes a destructive system operation.",
|
|
35690
|
+
evidence: base
|
|
35691
|
+
});
|
|
35692
|
+
}
|
|
35693
|
+
if (/\b(curl|wget)\b[\s\S]*\|[\s\S]*\b(sh|bash|zsh|fish)\b/.test(joined)) {
|
|
35694
|
+
pushRisk(risks, {
|
|
35695
|
+
code: "download_pipe_shell",
|
|
35696
|
+
severity: "danger",
|
|
35697
|
+
message: "Command downloads remote content and pipes it to a shell."
|
|
35698
|
+
});
|
|
35699
|
+
}
|
|
35700
|
+
if ([command, ...args].some((part) => SHELL_META_PATTERN.test(part))) {
|
|
35701
|
+
pushRisk(risks, {
|
|
35702
|
+
code: "shell_metacharacters",
|
|
35703
|
+
severity: "warning",
|
|
35704
|
+
message: "Command or arguments contain shell metacharacters."
|
|
35705
|
+
});
|
|
35706
|
+
}
|
|
35707
|
+
if (args.some((arg, index) => isSecretArg(args, index))) {
|
|
35708
|
+
pushRisk(risks, {
|
|
35709
|
+
code: "inline_secret",
|
|
35710
|
+
severity: "danger",
|
|
35711
|
+
message: "Command arguments appear to contain inline secret material."
|
|
35712
|
+
});
|
|
35713
|
+
}
|
|
35714
|
+
const secretEnvKeys = Object.keys(env).filter(isSecretKey).sort();
|
|
35715
|
+
if (secretEnvKeys.length > 0) {
|
|
35716
|
+
pushRisk(risks, {
|
|
35717
|
+
code: "secret_env",
|
|
35718
|
+
severity: "warning",
|
|
35719
|
+
message: "Environment contains secret-like keys; values are redacted from consent output.",
|
|
35720
|
+
evidence: secretEnvKeys.join(", ")
|
|
35721
|
+
});
|
|
35722
|
+
}
|
|
35723
|
+
return risks;
|
|
35724
|
+
}
|
|
35725
|
+
function inspectLocalCommand(input) {
|
|
35726
|
+
const args = normalizeArgs(input.args);
|
|
35727
|
+
const env = input.env ?? {};
|
|
35728
|
+
const transport = input.transport ?? "stdio";
|
|
35729
|
+
const risks = inspectRisks(input.command, args, env);
|
|
35730
|
+
return {
|
|
35731
|
+
requiresConsent: transport === "stdio",
|
|
35732
|
+
operation: input.operation ?? "launch",
|
|
35733
|
+
command: input.command,
|
|
35734
|
+
args,
|
|
35735
|
+
displayCommand: displayCommand(input.command, args),
|
|
35736
|
+
envKeys: Object.keys(env).sort(),
|
|
35737
|
+
risks,
|
|
35738
|
+
hasDangerousRisk: risks.some((risk) => risk.severity === "danger")
|
|
35739
|
+
};
|
|
35740
|
+
}
|
|
35741
|
+
function formatLocalCommandReview(review) {
|
|
35742
|
+
const lines = [
|
|
35743
|
+
`Command: ${review.displayCommand}`,
|
|
35744
|
+
review.envKeys.length > 0 ? `Env keys: ${review.envKeys.join(", ")}` : "Env keys: <none>"
|
|
35745
|
+
];
|
|
35746
|
+
if (review.risks.length > 0) {
|
|
35747
|
+
lines.push("Risks:");
|
|
35748
|
+
for (const risk of review.risks) {
|
|
35749
|
+
lines.push(`- ${risk.severity}: ${risk.code} - ${risk.message}${risk.evidence ? ` (${risk.evidence})` : ""}`);
|
|
35750
|
+
}
|
|
35751
|
+
} else {
|
|
35752
|
+
lines.push("Risks: none detected");
|
|
35753
|
+
}
|
|
35754
|
+
return lines.join(`
|
|
35755
|
+
`);
|
|
35756
|
+
}
|
|
35757
|
+
function assertLocalCommandConsent(input, consent = {}) {
|
|
35758
|
+
const review = inspectLocalCommand(input);
|
|
35759
|
+
if (!review.requiresConsent)
|
|
35760
|
+
return review;
|
|
35761
|
+
if (consent.approved !== true) {
|
|
35762
|
+
throw new LocalCommandConsentError(`local stdio command approval is required before ${review.operation}.
|
|
35763
|
+
${formatLocalCommandReview(review)}`, review);
|
|
35764
|
+
}
|
|
35765
|
+
if (review.hasDangerousRisk && consent.allowRisky !== true) {
|
|
35766
|
+
throw new LocalCommandConsentError(`risky command approval is required before ${review.operation}.
|
|
35767
|
+
${formatLocalCommandReview(review)}`, review);
|
|
35768
|
+
}
|
|
35769
|
+
return review;
|
|
35770
|
+
}
|
|
35771
|
+
|
|
35772
|
+
// src/lib/proxy.ts
|
|
35594
35773
|
var connections = new Map;
|
|
35595
35774
|
var inflightConnections = new Map;
|
|
35596
35775
|
var CONNECT_CONCURRENCY = 4;
|
|
@@ -35617,7 +35796,7 @@ function requireUrl(entry) {
|
|
|
35617
35796
|
throw new Error(`Server "${entry.id}" has an invalid URL: ${entry.url}`);
|
|
35618
35797
|
}
|
|
35619
35798
|
}
|
|
35620
|
-
async function connectToServer(entry) {
|
|
35799
|
+
async function connectToServer(entry, options = {}) {
|
|
35621
35800
|
if (connections.has(entry.id)) {
|
|
35622
35801
|
return connections.get(entry.id);
|
|
35623
35802
|
}
|
|
@@ -35633,6 +35812,13 @@ async function connectToServer(entry) {
|
|
|
35633
35812
|
if (!entry.command?.trim()) {
|
|
35634
35813
|
throw new Error(`Server "${entry.id}" is missing a command`);
|
|
35635
35814
|
}
|
|
35815
|
+
assertLocalCommandConsent({
|
|
35816
|
+
command: entry.command,
|
|
35817
|
+
args: entry.args,
|
|
35818
|
+
env: entry.env,
|
|
35819
|
+
transport: entry.transport,
|
|
35820
|
+
operation: "launch"
|
|
35821
|
+
}, options.localCommandConsent);
|
|
35636
35822
|
transport = new StdioClientTransport({
|
|
35637
35823
|
command: entry.command,
|
|
35638
35824
|
args: entry.args,
|
|
@@ -35749,7 +35935,7 @@ async function callTool(prefixedName, args) {
|
|
|
35749
35935
|
]
|
|
35750
35936
|
};
|
|
35751
35937
|
}
|
|
35752
|
-
async function connectAllEnabled() {
|
|
35938
|
+
async function connectAllEnabled(options = {}) {
|
|
35753
35939
|
const servers = listServers().filter((s) => s.enabled);
|
|
35754
35940
|
const results = [];
|
|
35755
35941
|
let index = 0;
|
|
@@ -35761,7 +35947,7 @@ async function connectAllEnabled() {
|
|
|
35761
35947
|
return;
|
|
35762
35948
|
const server = servers[current];
|
|
35763
35949
|
try {
|
|
35764
|
-
const conn = await connectToServer(server);
|
|
35950
|
+
const conn = await connectToServer(server, options);
|
|
35765
35951
|
results.push(conn);
|
|
35766
35952
|
} catch (err) {
|
|
35767
35953
|
console.error(`Failed to connect to ${server.name}: ${err.message}`);
|
|
@@ -35773,13 +35959,28 @@ async function connectAllEnabled() {
|
|
|
35773
35959
|
}
|
|
35774
35960
|
|
|
35775
35961
|
// src/lib/doctor.ts
|
|
35776
|
-
async function diagnoseServer(server) {
|
|
35962
|
+
async function diagnoseServer(server, options = {}) {
|
|
35777
35963
|
const checks4 = [];
|
|
35964
|
+
let hasLocalConsent = true;
|
|
35778
35965
|
if (server.transport === "stdio") {
|
|
35966
|
+
try {
|
|
35967
|
+
assertLocalCommandConsent({
|
|
35968
|
+
command: server.command,
|
|
35969
|
+
args: server.args,
|
|
35970
|
+
env: server.env,
|
|
35971
|
+
transport: server.transport,
|
|
35972
|
+
operation: "diagnose"
|
|
35973
|
+
}, options.localCommandConsent);
|
|
35974
|
+
} catch (err) {
|
|
35975
|
+
hasLocalConsent = false;
|
|
35976
|
+
checks4.push({ name: "local command consent", pass: false, message: err.message });
|
|
35977
|
+
}
|
|
35779
35978
|
try {
|
|
35780
35979
|
const path = execFileSync("which", [server.command], { stdio: "pipe" }).toString().trim();
|
|
35781
35980
|
let version2 = "";
|
|
35782
35981
|
try {
|
|
35982
|
+
if (!hasLocalConsent)
|
|
35983
|
+
throw new Error("local stdio command approval is required before version probing");
|
|
35783
35984
|
version2 = execFileSync(server.command, ["--version"], { stdio: "pipe" }).toString().trim().split(`
|
|
35784
35985
|
`)[0];
|
|
35785
35986
|
} catch {}
|
|
@@ -35810,10 +36011,10 @@ async function diagnoseServer(server) {
|
|
|
35810
36011
|
checks4.push({ name: "URL reachable", pass: false, message: `unreachable: ${err.message}` });
|
|
35811
36012
|
}
|
|
35812
36013
|
}
|
|
35813
|
-
if (server.enabled) {
|
|
36014
|
+
if (server.enabled && hasLocalConsent) {
|
|
35814
36015
|
try {
|
|
35815
36016
|
await Promise.race([
|
|
35816
|
-
connectToServer(server),
|
|
36017
|
+
connectToServer(server, { localCommandConsent: options.localCommandConsent }),
|
|
35817
36018
|
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout after 10s")), 1e4))
|
|
35818
36019
|
]);
|
|
35819
36020
|
await disconnectServer(server.id);
|
|
@@ -35821,8 +36022,10 @@ async function diagnoseServer(server) {
|
|
|
35821
36022
|
} catch (err) {
|
|
35822
36023
|
checks4.push({ name: "connect & list tools", pass: false, message: err.message });
|
|
35823
36024
|
}
|
|
35824
|
-
} else {
|
|
36025
|
+
} else if (!server.enabled) {
|
|
35825
36026
|
checks4.push({ name: "connect & list tools", pass: true, message: "skipped (server disabled)" });
|
|
36027
|
+
} else {
|
|
36028
|
+
checks4.push({ name: "connect & list tools", pass: false, message: "skipped until local stdio command is approved" });
|
|
35826
36029
|
}
|
|
35827
36030
|
return {
|
|
35828
36031
|
server,
|
|
@@ -35863,7 +36066,7 @@ async function getRegistryServer(id) {
|
|
|
35863
36066
|
const all = entries.map(parseRegistryEntry);
|
|
35864
36067
|
return all.find((s) => s.id === id) || null;
|
|
35865
36068
|
}
|
|
35866
|
-
async function installFromRegistry(id) {
|
|
36069
|
+
async function installFromRegistry(id, options = {}) {
|
|
35867
36070
|
const server = await getRegistryServer(id);
|
|
35868
36071
|
if (!server) {
|
|
35869
36072
|
throw new Error(`Server "${id}" not found in registry`);
|
|
@@ -35883,6 +36086,13 @@ async function installFromRegistry(id) {
|
|
|
35883
36086
|
transport = pkg.transport.type;
|
|
35884
36087
|
}
|
|
35885
36088
|
}
|
|
36089
|
+
assertLocalCommandConsent({
|
|
36090
|
+
command,
|
|
36091
|
+
args,
|
|
36092
|
+
transport,
|
|
36093
|
+
env: {},
|
|
36094
|
+
operation: "register"
|
|
36095
|
+
}, options.localCommandConsent);
|
|
35886
36096
|
return addServer({
|
|
35887
36097
|
name: server.name,
|
|
35888
36098
|
description: server.description,
|
|
@@ -35977,7 +36187,22 @@ function installToGemini(entry) {
|
|
|
35977
36187
|
return { agent: "gemini", success: false, error: err.message };
|
|
35978
36188
|
}
|
|
35979
36189
|
}
|
|
35980
|
-
function installToAgents(entry, targets = ["claude", "codex", "gemini"]) {
|
|
36190
|
+
function installToAgents(entry, targets = ["claude", "codex", "gemini"], options = {}) {
|
|
36191
|
+
try {
|
|
36192
|
+
assertLocalCommandConsent({
|
|
36193
|
+
command: entry.command,
|
|
36194
|
+
args: entry.args,
|
|
36195
|
+
env: entry.env,
|
|
36196
|
+
transport: entry.transport,
|
|
36197
|
+
operation: "install"
|
|
36198
|
+
}, options.localCommandConsent);
|
|
36199
|
+
} catch (err) {
|
|
36200
|
+
return targets.map((target) => ({
|
|
36201
|
+
agent: target,
|
|
36202
|
+
success: false,
|
|
36203
|
+
error: err.message
|
|
36204
|
+
}));
|
|
36205
|
+
}
|
|
35981
36206
|
return targets.map((target) => {
|
|
35982
36207
|
if (target === "claude")
|
|
35983
36208
|
return installToClaude(entry);
|
|
@@ -36978,11 +37203,19 @@ function installProviderProfile(id, options = {}) {
|
|
|
36978
37203
|
if (!command) {
|
|
36979
37204
|
throw new Error(`Provider profile "${id}" does not define an install fallback command`);
|
|
36980
37205
|
}
|
|
37206
|
+
assertLocalCommandConsent({
|
|
37207
|
+
command,
|
|
37208
|
+
args,
|
|
37209
|
+
env: fallback?.env ?? {},
|
|
37210
|
+
transport: useFallback ? "stdio" : profile.transport,
|
|
37211
|
+
operation: "register"
|
|
37212
|
+
}, options.localCommandConsent);
|
|
36981
37213
|
return addServer({
|
|
36982
37214
|
name: options.name ?? profile.displayName,
|
|
36983
37215
|
description: profile.description ?? undefined,
|
|
36984
37216
|
command,
|
|
36985
37217
|
args,
|
|
37218
|
+
env: fallback?.env,
|
|
36986
37219
|
transport: useFallback ? "stdio" : profile.transport,
|
|
36987
37220
|
url: useFallback ? fallback?.url : profile.endpoint ?? undefined,
|
|
36988
37221
|
source: "provider-profile"
|
|
@@ -38257,6 +38490,13 @@ function jsonContent(value) {
|
|
|
38257
38490
|
function errorContent(text) {
|
|
38258
38491
|
return { ...textContent(text), isError: true };
|
|
38259
38492
|
}
|
|
38493
|
+
function localConsent(input) {
|
|
38494
|
+
return {
|
|
38495
|
+
approved: input.allow_local_stdio === true,
|
|
38496
|
+
allowRisky: input.allow_risky_command === true,
|
|
38497
|
+
source: "mcp"
|
|
38498
|
+
};
|
|
38499
|
+
}
|
|
38260
38500
|
function buildMcpTools() {
|
|
38261
38501
|
const definitions = [
|
|
38262
38502
|
{
|
|
@@ -38281,23 +38521,46 @@ function buildMcpTools() {
|
|
|
38281
38521
|
description: exports_external2.string().optional().describe("Description"),
|
|
38282
38522
|
transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("Transport type"),
|
|
38283
38523
|
url: exports_external2.string().optional().describe("URL for remote transports"),
|
|
38284
|
-
env: exports_external2.record(exports_external2.string()).optional().describe("Environment variables")
|
|
38524
|
+
env: exports_external2.record(exports_external2.string()).optional().describe("Environment variables"),
|
|
38525
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering this local stdio command"),
|
|
38526
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve registering risky local command patterns")
|
|
38285
38527
|
},
|
|
38286
|
-
run: (
|
|
38287
|
-
command
|
|
38288
|
-
args
|
|
38289
|
-
|
|
38290
|
-
|
|
38291
|
-
|
|
38292
|
-
|
|
38293
|
-
|
|
38294
|
-
|
|
38528
|
+
run: (input) => {
|
|
38529
|
+
const command = String(input.command);
|
|
38530
|
+
const args = Array.isArray(input.args) ? input.args.map(String) : [];
|
|
38531
|
+
const env = isRecordOfStrings(input.env) ? input.env : {};
|
|
38532
|
+
const transport = input.transport;
|
|
38533
|
+
try {
|
|
38534
|
+
assertLocalCommandConsent({ command, args, env, transport, operation: "register" }, localConsent(input));
|
|
38535
|
+
return jsonContent(addServer({
|
|
38536
|
+
command,
|
|
38537
|
+
args,
|
|
38538
|
+
name: typeof input.name === "string" ? input.name : undefined,
|
|
38539
|
+
description: typeof input.description === "string" ? input.description : undefined,
|
|
38540
|
+
transport,
|
|
38541
|
+
url: typeof input.url === "string" ? input.url : undefined,
|
|
38542
|
+
env
|
|
38543
|
+
}));
|
|
38544
|
+
} catch (err) {
|
|
38545
|
+
return errorContent(err.message);
|
|
38546
|
+
}
|
|
38547
|
+
}
|
|
38295
38548
|
},
|
|
38296
38549
|
{
|
|
38297
38550
|
name: "install_from_registry",
|
|
38298
38551
|
description: "Install an MCP server from the official registry",
|
|
38299
|
-
paramsSchema: {
|
|
38300
|
-
|
|
38552
|
+
paramsSchema: {
|
|
38553
|
+
id: exports_external2.string().describe("Registry server ID"),
|
|
38554
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering registry stdio commands"),
|
|
38555
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve registering risky local command patterns")
|
|
38556
|
+
},
|
|
38557
|
+
run: async (input) => {
|
|
38558
|
+
try {
|
|
38559
|
+
return jsonContent(await installFromRegistry(String(input.id), { localCommandConsent: localConsent(input) }));
|
|
38560
|
+
} catch (err) {
|
|
38561
|
+
return errorContent(err.message);
|
|
38562
|
+
}
|
|
38563
|
+
}
|
|
38301
38564
|
},
|
|
38302
38565
|
{
|
|
38303
38566
|
name: "remove_server",
|
|
@@ -38343,26 +38606,41 @@ function buildMcpTools() {
|
|
|
38343
38606
|
command: exports_external2.string().optional().describe("New command"),
|
|
38344
38607
|
args: exports_external2.array(exports_external2.string()).optional().describe("New args list"),
|
|
38345
38608
|
transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("New transport type"),
|
|
38346
|
-
url: exports_external2.string().optional().describe("New URL for remote transports")
|
|
38609
|
+
url: exports_external2.string().optional().describe("New URL for remote transports"),
|
|
38610
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve updating this local stdio command"),
|
|
38611
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve risky local command patterns")
|
|
38347
38612
|
},
|
|
38348
|
-
run: (
|
|
38349
|
-
const serverId = String(id);
|
|
38613
|
+
run: (input) => {
|
|
38614
|
+
const serverId = String(input.id);
|
|
38350
38615
|
const existing = getServer(serverId);
|
|
38351
38616
|
if (!existing)
|
|
38352
38617
|
return errorContent(`Server "${serverId}" not found.`);
|
|
38353
38618
|
const fields = {};
|
|
38354
|
-
if (typeof name === "string")
|
|
38355
|
-
fields.name = name;
|
|
38356
|
-
if (typeof description === "string")
|
|
38357
|
-
fields.description = description;
|
|
38358
|
-
if (typeof command === "string")
|
|
38359
|
-
fields.command = command;
|
|
38360
|
-
if (Array.isArray(args))
|
|
38361
|
-
fields.args = args.map(String);
|
|
38362
|
-
if (transport === "stdio" || transport === "sse" || transport === "streamable-http")
|
|
38363
|
-
fields.transport = transport;
|
|
38364
|
-
if (typeof
|
|
38365
|
-
fields.url =
|
|
38619
|
+
if (typeof input.name === "string")
|
|
38620
|
+
fields.name = input.name;
|
|
38621
|
+
if (typeof input.description === "string")
|
|
38622
|
+
fields.description = input.description;
|
|
38623
|
+
if (typeof input.command === "string")
|
|
38624
|
+
fields.command = input.command;
|
|
38625
|
+
if (Array.isArray(input.args))
|
|
38626
|
+
fields.args = input.args.map(String);
|
|
38627
|
+
if (input.transport === "stdio" || input.transport === "sse" || input.transport === "streamable-http")
|
|
38628
|
+
fields.transport = input.transport;
|
|
38629
|
+
if (typeof input.url === "string")
|
|
38630
|
+
fields.url = input.url;
|
|
38631
|
+
if (fields.command !== undefined || fields.args !== undefined || fields.transport !== undefined) {
|
|
38632
|
+
try {
|
|
38633
|
+
assertLocalCommandConsent({
|
|
38634
|
+
command: fields.command ?? existing.command,
|
|
38635
|
+
args: fields.args ?? existing.args,
|
|
38636
|
+
env: existing.env,
|
|
38637
|
+
transport: fields.transport ?? existing.transport,
|
|
38638
|
+
operation: "register"
|
|
38639
|
+
}, localConsent(input));
|
|
38640
|
+
} catch (err) {
|
|
38641
|
+
return errorContent(err.message);
|
|
38642
|
+
}
|
|
38643
|
+
}
|
|
38366
38644
|
return jsonContent(redactServerEnv(updateServer(serverId, fields)));
|
|
38367
38645
|
}
|
|
38368
38646
|
},
|
|
@@ -38444,13 +38722,16 @@ function buildMcpTools() {
|
|
|
38444
38722
|
paramsSchema: {
|
|
38445
38723
|
id: exports_external2.string().describe("Provider profile ID"),
|
|
38446
38724
|
name: exports_external2.string().optional().describe("Override registered server name"),
|
|
38447
|
-
use_fallback: exports_external2.boolean().optional().describe("Install the stdio fallback command instead of the direct remote transport")
|
|
38725
|
+
use_fallback: exports_external2.boolean().optional().describe("Install the stdio fallback command instead of the direct remote transport"),
|
|
38726
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering provider stdio fallback commands"),
|
|
38727
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve risky local command patterns")
|
|
38448
38728
|
},
|
|
38449
|
-
run: (
|
|
38729
|
+
run: (input) => {
|
|
38450
38730
|
try {
|
|
38451
|
-
return jsonContent(redactServerEnv(installProviderProfile(String(id), {
|
|
38452
|
-
name: typeof name === "string" ? name : undefined,
|
|
38453
|
-
useFallback: use_fallback === true
|
|
38731
|
+
return jsonContent(redactServerEnv(installProviderProfile(String(input.id), {
|
|
38732
|
+
name: typeof input.name === "string" ? input.name : undefined,
|
|
38733
|
+
useFallback: input.use_fallback === true,
|
|
38734
|
+
localCommandConsent: localConsent(input)
|
|
38454
38735
|
})));
|
|
38455
38736
|
} catch (err) {
|
|
38456
38737
|
return errorContent(err.message);
|
|
@@ -38523,15 +38804,19 @@ function buildMcpTools() {
|
|
|
38523
38804
|
description: "Install a registered MCP server into Claude Code, Codex, and/or Gemini",
|
|
38524
38805
|
paramsSchema: {
|
|
38525
38806
|
id: exports_external2.string().describe("Server ID to install (from list_servers)"),
|
|
38526
|
-
targets: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("Target agents to install into (default: all)")
|
|
38807
|
+
targets: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("Target agents to install into (default: all)"),
|
|
38808
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve installing local stdio commands into local agent configs"),
|
|
38809
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve installing risky local command patterns")
|
|
38527
38810
|
},
|
|
38528
|
-
run: (
|
|
38529
|
-
const serverId = String(id);
|
|
38811
|
+
run: (input) => {
|
|
38812
|
+
const serverId = String(input.id);
|
|
38530
38813
|
const entry = getServer(serverId);
|
|
38531
38814
|
if (!entry)
|
|
38532
38815
|
return errorContent(`Server "${serverId}" not found.`);
|
|
38533
|
-
const agentTargets = Array.isArray(targets) ? targets : undefined;
|
|
38534
|
-
return jsonContent(installToAgents(entry, agentTargets ?? ["claude", "codex", "gemini"]
|
|
38816
|
+
const agentTargets = Array.isArray(input.targets) ? input.targets : undefined;
|
|
38817
|
+
return jsonContent(installToAgents(entry, agentTargets ?? ["claude", "codex", "gemini"], {
|
|
38818
|
+
localCommandConsent: localConsent(input)
|
|
38819
|
+
}));
|
|
38535
38820
|
}
|
|
38536
38821
|
},
|
|
38537
38822
|
{
|
|
@@ -38543,11 +38828,14 @@ function buildMcpTools() {
|
|
|
38543
38828
|
{
|
|
38544
38829
|
name: "connect_and_list_tools",
|
|
38545
38830
|
description: "Connect to all enabled MCP servers and list their available tools",
|
|
38546
|
-
paramsSchema: {
|
|
38547
|
-
|
|
38831
|
+
paramsSchema: {
|
|
38832
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching enabled local stdio commands"),
|
|
38833
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve launching risky local command patterns")
|
|
38834
|
+
},
|
|
38835
|
+
run: async (input) => {
|
|
38548
38836
|
let liveTools = [];
|
|
38549
38837
|
try {
|
|
38550
|
-
await connectAllEnabled();
|
|
38838
|
+
await connectAllEnabled({ localCommandConsent: localConsent(input) });
|
|
38551
38839
|
liveTools = listAllTools();
|
|
38552
38840
|
} finally {
|
|
38553
38841
|
await disconnectAll().catch(() => {
|
|
@@ -38562,11 +38850,13 @@ function buildMcpTools() {
|
|
|
38562
38850
|
description: `Call a tool on a connected upstream MCP server. Tool name format: server_id${TOOL_PREFIX_SEPARATOR}tool_name`,
|
|
38563
38851
|
paramsSchema: {
|
|
38564
38852
|
tool_name: exports_external2.string().describe(`Prefixed tool name (server_id${TOOL_PREFIX_SEPARATOR}tool_name)`),
|
|
38565
|
-
arguments: exports_external2.record(exports_external2.unknown()).optional().describe("Tool arguments as key-value pairs")
|
|
38853
|
+
arguments: exports_external2.record(exports_external2.unknown()).optional().describe("Tool arguments as key-value pairs"),
|
|
38854
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching this local stdio command"),
|
|
38855
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve launching risky local command patterns")
|
|
38566
38856
|
},
|
|
38567
|
-
run: async (
|
|
38857
|
+
run: async (input) => {
|
|
38568
38858
|
try {
|
|
38569
|
-
const toolName = String(tool_name);
|
|
38859
|
+
const toolName = String(input.tool_name);
|
|
38570
38860
|
const sepIdx = toolName.indexOf(TOOL_PREFIX_SEPARATOR);
|
|
38571
38861
|
if (sepIdx === -1)
|
|
38572
38862
|
return errorContent(`Error: Invalid tool name "${toolName}"`);
|
|
@@ -38576,8 +38866,8 @@ function buildMcpTools() {
|
|
|
38576
38866
|
return errorContent(`Error: Server "${serverId}" not found.`);
|
|
38577
38867
|
if (!entry.enabled)
|
|
38578
38868
|
return errorContent(`Error: Server "${serverId}" is disabled.`);
|
|
38579
|
-
await connectToServer(entry);
|
|
38580
|
-
const result = await callTool(toolName, readRecord(
|
|
38869
|
+
await connectToServer(entry, { localCommandConsent: localConsent(input) });
|
|
38870
|
+
const result = await callTool(toolName, readRecord(input.arguments));
|
|
38581
38871
|
return { content: result.content };
|
|
38582
38872
|
} catch (error2) {
|
|
38583
38873
|
return errorContent(`Error: ${error2.message}`);
|
|
@@ -38587,13 +38877,17 @@ function buildMcpTools() {
|
|
|
38587
38877
|
{
|
|
38588
38878
|
name: "diagnose_server",
|
|
38589
38879
|
description: "Run health checks on a registered MCP server",
|
|
38590
|
-
paramsSchema: {
|
|
38591
|
-
|
|
38592
|
-
|
|
38880
|
+
paramsSchema: {
|
|
38881
|
+
id: exports_external2.string().describe("Server ID"),
|
|
38882
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching local stdio diagnostics"),
|
|
38883
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve diagnosing risky local command patterns")
|
|
38884
|
+
},
|
|
38885
|
+
run: async (input) => {
|
|
38886
|
+
const serverId = String(input.id);
|
|
38593
38887
|
const entry = getServer(serverId);
|
|
38594
38888
|
if (!entry)
|
|
38595
38889
|
return errorContent(`Server "${serverId}" not found.`);
|
|
38596
|
-
return jsonContent(await diagnoseServer(entry));
|
|
38890
|
+
return jsonContent(await diagnoseServer(entry, { localCommandConsent: localConsent(input) }));
|
|
38597
38891
|
}
|
|
38598
38892
|
},
|
|
38599
38893
|
{
|
|
@@ -38962,6 +39256,20 @@ function isValidId(id) {
|
|
|
38962
39256
|
return /^[a-z0-9-]+$/.test(id);
|
|
38963
39257
|
}
|
|
38964
39258
|
var MAX_BODY_SIZE = 1024 * 1024;
|
|
39259
|
+
function consentFromInput(input) {
|
|
39260
|
+
return {
|
|
39261
|
+
approved: input.allow_local_stdio === true || input.allowLocalStdio === true,
|
|
39262
|
+
allowRisky: input.allow_risky_command === true || input.allowRiskyCommand === true,
|
|
39263
|
+
source: "api"
|
|
39264
|
+
};
|
|
39265
|
+
}
|
|
39266
|
+
function consentFromSearchParams(params) {
|
|
39267
|
+
return {
|
|
39268
|
+
approved: params.get("allow_local_stdio") === "1" || params.get("allow_local_stdio") === "true",
|
|
39269
|
+
allowRisky: params.get("allow_risky_command") === "1" || params.get("allow_risky_command") === "true",
|
|
39270
|
+
source: "api"
|
|
39271
|
+
};
|
|
39272
|
+
}
|
|
38965
39273
|
function isLoopbackHost(hostname3) {
|
|
38966
39274
|
return hostname3 === "127.0.0.1" || hostname3 === "localhost" || hostname3 === "::1";
|
|
38967
39275
|
}
|
|
@@ -39108,6 +39416,11 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39108
39416
|
return json({ error: "Invalid 'args' format" }, 400, port);
|
|
39109
39417
|
}
|
|
39110
39418
|
const args = body.args || [];
|
|
39419
|
+
try {
|
|
39420
|
+
assertLocalCommandConsent({ command, args, env: {}, transport, operation: "register" }, consentFromInput(body));
|
|
39421
|
+
} catch (err) {
|
|
39422
|
+
return json({ error: err.message }, 400, port);
|
|
39423
|
+
}
|
|
39111
39424
|
const entry = addServer({
|
|
39112
39425
|
name: body.name,
|
|
39113
39426
|
command,
|
|
@@ -39206,6 +39519,19 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39206
39519
|
}
|
|
39207
39520
|
fields.args = body.args;
|
|
39208
39521
|
}
|
|
39522
|
+
if (fields.command !== undefined || fields.args !== undefined || fields.transport !== undefined) {
|
|
39523
|
+
try {
|
|
39524
|
+
assertLocalCommandConsent({
|
|
39525
|
+
command: fields.command ?? existing.command,
|
|
39526
|
+
args: fields.args ?? existing.args,
|
|
39527
|
+
env: existing.env,
|
|
39528
|
+
transport: fields.transport ?? existing.transport,
|
|
39529
|
+
operation: "register"
|
|
39530
|
+
}, consentFromInput(body));
|
|
39531
|
+
} catch (err) {
|
|
39532
|
+
return json({ error: err.message }, 400, port);
|
|
39533
|
+
}
|
|
39534
|
+
}
|
|
39209
39535
|
const updated = updateServer(id, fields);
|
|
39210
39536
|
return json(redactServer(updated), 200, port);
|
|
39211
39537
|
} catch (e) {
|
|
@@ -39281,7 +39607,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39281
39607
|
}
|
|
39282
39608
|
if (!body.tool || typeof body.tool !== "string")
|
|
39283
39609
|
return json({ error: "Missing 'tool'" }, 400, port);
|
|
39284
|
-
await connectToServer(entry);
|
|
39610
|
+
await connectToServer(entry, { localCommandConsent: consentFromInput(body) });
|
|
39285
39611
|
const toolName = `${id}__${body.tool}`;
|
|
39286
39612
|
const result = await callTool(toolName, body.args || {});
|
|
39287
39613
|
await disconnectServer(id).catch(() => {
|
|
@@ -39292,6 +39618,9 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39292
39618
|
await disconnectServer(id).catch(() => {
|
|
39293
39619
|
return;
|
|
39294
39620
|
});
|
|
39621
|
+
if (e instanceof LocalCommandConsentError) {
|
|
39622
|
+
return json({ error: e.message }, 400, port);
|
|
39623
|
+
}
|
|
39295
39624
|
return json({ error: e instanceof Error ? e.message : "Failed to call tool" }, 500, port);
|
|
39296
39625
|
}
|
|
39297
39626
|
}
|
|
@@ -39304,7 +39633,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39304
39633
|
if (!entry)
|
|
39305
39634
|
return json({ error: `Server '${id}' not found` }, 404, port);
|
|
39306
39635
|
try {
|
|
39307
|
-
const report = await diagnoseServer(entry);
|
|
39636
|
+
const report = await diagnoseServer(entry, { localCommandConsent: consentFromSearchParams(url2.searchParams) });
|
|
39308
39637
|
return json(report, 200, port);
|
|
39309
39638
|
} catch (e) {
|
|
39310
39639
|
return json({ error: e instanceof Error ? e.message : "Failed to diagnose server" }, 500, port);
|
|
@@ -39959,6 +40288,13 @@ function ServerDetail({ server, onSelectTool, onBack }) {
|
|
|
39959
40288
|
const [tools2, setTools] = useState3([]);
|
|
39960
40289
|
const [loading, setLoading] = useState3(false);
|
|
39961
40290
|
const [error2, setError] = useState3(null);
|
|
40291
|
+
const commandReview = inspectLocalCommand({
|
|
40292
|
+
command: server.command,
|
|
40293
|
+
args: server.args,
|
|
40294
|
+
env: server.env,
|
|
40295
|
+
transport: server.transport,
|
|
40296
|
+
operation: "launch"
|
|
40297
|
+
});
|
|
39962
40298
|
const cachedTools = getCachedTools(server.id);
|
|
39963
40299
|
const cachedKey = cachedTools.map((t) => `${t.name}|${t.description}|${JSON.stringify(t.input_schema)}`).join(";");
|
|
39964
40300
|
useEffect3(() => {
|
|
@@ -39979,7 +40315,9 @@ function ServerDetail({ server, onSelectTool, onBack }) {
|
|
|
39979
40315
|
setLoading(true);
|
|
39980
40316
|
setError(null);
|
|
39981
40317
|
try {
|
|
39982
|
-
const conn = await connectToServer(server
|
|
40318
|
+
const conn = await connectToServer(server, {
|
|
40319
|
+
localCommandConsent: { approved: true, source: "tui" }
|
|
40320
|
+
});
|
|
39983
40321
|
setTools(conn.tools);
|
|
39984
40322
|
} catch (err) {
|
|
39985
40323
|
setError(err.message);
|
|
@@ -40038,6 +40376,21 @@ function ServerDetail({ server, onSelectTool, onBack }) {
|
|
|
40038
40376
|
server.args.join(" ")
|
|
40039
40377
|
]
|
|
40040
40378
|
}, undefined, true, undefined, this),
|
|
40379
|
+
commandReview.requiresConsent && /* @__PURE__ */ jsxDEV2(Box4, {
|
|
40380
|
+
marginTop: 1,
|
|
40381
|
+
flexDirection: "column",
|
|
40382
|
+
children: [
|
|
40383
|
+
/* @__PURE__ */ jsxDEV2(Text5, {
|
|
40384
|
+
color: commandReview.hasDangerousRisk ? "red" : "yellow",
|
|
40385
|
+
children: "Local stdio command review"
|
|
40386
|
+
}, undefined, false, undefined, this),
|
|
40387
|
+
formatLocalCommandReview(commandReview).split(`
|
|
40388
|
+
`).map((line, index) => /* @__PURE__ */ jsxDEV2(Text5, {
|
|
40389
|
+
dimColor: true,
|
|
40390
|
+
children: line
|
|
40391
|
+
}, `${index}:${line}`, false, undefined, this))
|
|
40392
|
+
]
|
|
40393
|
+
}, undefined, true, undefined, this),
|
|
40041
40394
|
server.description && /* @__PURE__ */ jsxDEV2(Text5, {
|
|
40042
40395
|
dimColor: true,
|
|
40043
40396
|
children: server.description
|
|
@@ -40206,7 +40559,9 @@ function SearchView({ onBack }) {
|
|
|
40206
40559
|
setInstalling(item.value);
|
|
40207
40560
|
setMessage(null);
|
|
40208
40561
|
try {
|
|
40209
|
-
const server = await installFromRegistry(item.value
|
|
40562
|
+
const server = await installFromRegistry(item.value, {
|
|
40563
|
+
localCommandConsent: { approved: true, source: "tui" }
|
|
40564
|
+
});
|
|
40210
40565
|
setMessage(`Installed: ${server.name} [${server.id}]`);
|
|
40211
40566
|
} catch (err) {
|
|
40212
40567
|
setMessage(`Install failed: ${err.message}`);
|
|
@@ -40317,7 +40672,9 @@ function ToolCall({ server, tool, onBack }) {
|
|
|
40317
40672
|
setError(null);
|
|
40318
40673
|
setResult(null);
|
|
40319
40674
|
try {
|
|
40320
|
-
await connectToServer(server
|
|
40675
|
+
await connectToServer(server, {
|
|
40676
|
+
localCommandConsent: { approved: true, source: "tui" }
|
|
40677
|
+
});
|
|
40321
40678
|
const prefixed = `${server.id}${TOOL_PREFIX_SEPARATOR}${tool.name}`;
|
|
40322
40679
|
const res = await callTool(prefixed, args);
|
|
40323
40680
|
const text = res.content.map((c) => c.text).join(`
|
|
@@ -40537,6 +40894,28 @@ var MACHINE_PLATFORMS = ["linux", "darwin", "unknown"];
|
|
|
40537
40894
|
var MACHINE_ARCHES = ["arm64", "x64", "unknown"];
|
|
40538
40895
|
var MACHINE_INSTALLERS = ["auto", "bun", "npm"];
|
|
40539
40896
|
var FLEET_INSTALL_MODES = ["missing", "missing-or-outdated", "all"];
|
|
40897
|
+
function localConsentFromOptions(opts, approved = false) {
|
|
40898
|
+
return {
|
|
40899
|
+
approved: approved || opts.yes === true || opts.allowLocalStdio === true,
|
|
40900
|
+
allowRisky: opts.allowRiskyCommand === true,
|
|
40901
|
+
source: "cli"
|
|
40902
|
+
};
|
|
40903
|
+
}
|
|
40904
|
+
function printLocalCommandReviewIfNeeded(input) {
|
|
40905
|
+
const review = inspectLocalCommand(input);
|
|
40906
|
+
if (!review.requiresConsent)
|
|
40907
|
+
return;
|
|
40908
|
+
console.log(chalk2.dim(formatLocalCommandReview(review)));
|
|
40909
|
+
}
|
|
40910
|
+
function assertCliLocalCommandConsent(input, opts, approved = false) {
|
|
40911
|
+
const consent = localConsentFromOptions(opts, approved);
|
|
40912
|
+
const review = inspectLocalCommand(input);
|
|
40913
|
+
if (review.requiresConsent && consent.approved === true && (!review.hasDangerousRisk || consent.allowRisky === true)) {
|
|
40914
|
+
console.log(chalk2.dim(formatLocalCommandReview(review)));
|
|
40915
|
+
}
|
|
40916
|
+
assertLocalCommandConsent(input, consent);
|
|
40917
|
+
return consent;
|
|
40918
|
+
}
|
|
40540
40919
|
function printProviderProfile(profile) {
|
|
40541
40920
|
const status = profile.enabled ? chalk2.green("enabled") : chalk2.red("disabled");
|
|
40542
40921
|
console.log(` ${chalk2.bold(profile.displayName)} ${chalk2.dim(`[${profile.id}]`)} \u2014 ${chalk2.dim(profile.transport)} \u2014 ${status}`);
|
|
@@ -40785,11 +41164,12 @@ providersCmd.command("info").argument("<id>", "Provider profile ID").description
|
|
|
40785
41164
|
console.log(` Docs: ${chalk2.cyan(profile.docsUrl)}`);
|
|
40786
41165
|
closeDb();
|
|
40787
41166
|
});
|
|
40788
|
-
providersCmd.command("install").argument("<id>", "Provider profile ID").description("Register a curated provider profile as an MCP server").option("--name <name>", "Override registered server name").option("--fallback", "Install the stdio fallback command instead of direct remote transport").option("--json", "Output as JSON").action((id, opts) => {
|
|
41167
|
+
providersCmd.command("install").argument("<id>", "Provider profile ID").description("Register a curated provider profile as an MCP server").option("--name <name>", "Override registered server name").option("--fallback", "Install the stdio fallback command instead of direct remote transport").option("--yes", "Approve local stdio fallback commands").option("--allow-local-stdio", "Approve local stdio fallback commands").option("--allow-risky-command", "Approve high-risk local command patterns").option("--json", "Output as JSON").action((id, opts) => {
|
|
40789
41168
|
try {
|
|
40790
41169
|
const server = installProviderProfile(id, {
|
|
40791
41170
|
name: opts.name,
|
|
40792
|
-
useFallback: opts.fallback === true
|
|
41171
|
+
useFallback: opts.fallback === true,
|
|
41172
|
+
localCommandConsent: localConsentFromOptions(opts)
|
|
40793
41173
|
});
|
|
40794
41174
|
if (opts.json) {
|
|
40795
41175
|
printJson(server);
|
|
@@ -40820,11 +41200,13 @@ function detectSourceType(url2) {
|
|
|
40820
41200
|
return "mcp-registry";
|
|
40821
41201
|
return null;
|
|
40822
41202
|
}
|
|
40823
|
-
program2.command("add").passThroughOptions().argument("[command]", "Command to run the MCP server").argument("[args...]", "Arguments for the command").option("--name <name>", "Display name for the server").option("--description <desc>", "Description").option("--from-registry <id>", "Install from official registry by ID").option("--transport <type>", "Transport type: stdio, sse, streamable-http", "stdio").option("--url <url>", "URL for remote transports").option("--env <pairs...>", "Environment variables as KEY=VALUE pairs").option("--wizard", "Interactive setup wizard").option("--force", "Register even if duplicate command exists").description("Add a local MCP server").action(async (command, args, opts) => {
|
|
41203
|
+
program2.command("add").passThroughOptions().argument("[command]", "Command to run the MCP server").argument("[args...]", "Arguments for the command").option("--name <name>", "Display name for the server").option("--description <desc>", "Description").option("--from-registry <id>", "Install from official registry by ID").option("--transport <type>", "Transport type: stdio, sse, streamable-http", "stdio").option("--url <url>", "URL for remote transports").option("--env <pairs...>", "Environment variables as KEY=VALUE pairs").option("--wizard", "Interactive setup wizard").option("--force", "Register even if duplicate command exists").option("--yes", "Approve registering local stdio commands").option("--allow-local-stdio", "Approve registering local stdio commands").option("--allow-risky-command", "Approve high-risk local command patterns").description("Add a local MCP server").action(async (command, args, opts) => {
|
|
40824
41204
|
try {
|
|
40825
41205
|
if (opts.fromRegistry) {
|
|
40826
41206
|
console.log(chalk2.dim(`Installing "${opts.fromRegistry}" from registry...`));
|
|
40827
|
-
const server2 = await installFromRegistry(opts.fromRegistry
|
|
41207
|
+
const server2 = await installFromRegistry(opts.fromRegistry, {
|
|
41208
|
+
localCommandConsent: localConsentFromOptions(opts)
|
|
41209
|
+
});
|
|
40828
41210
|
console.log(chalk2.green(`Added server: ${server2.name} [${server2.id}]`));
|
|
40829
41211
|
console.log(chalk2.dim(` ${server2.command} ${server2.args.join(" ")}`));
|
|
40830
41212
|
closeDb();
|
|
@@ -40863,6 +41245,13 @@ Server to add:`));
|
|
|
40863
41245
|
console.log(` Name: ${wizardName}`);
|
|
40864
41246
|
if (Object.keys(env).length)
|
|
40865
41247
|
console.log(` Env: ${Object.keys(env).join(", ")}`);
|
|
41248
|
+
printLocalCommandReviewIfNeeded({
|
|
41249
|
+
command: wizardCommand,
|
|
41250
|
+
args: wizardArgs,
|
|
41251
|
+
env,
|
|
41252
|
+
transport,
|
|
41253
|
+
operation: "register"
|
|
41254
|
+
});
|
|
40866
41255
|
const confirm = await new Promise((resolve2) => {
|
|
40867
41256
|
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
40868
41257
|
rl2.question(chalk2.bold("Add this server? [Y/n]: "), (ans) => {
|
|
@@ -40875,6 +41264,13 @@ Server to add:`));
|
|
|
40875
41264
|
closeDb();
|
|
40876
41265
|
return;
|
|
40877
41266
|
}
|
|
41267
|
+
assertLocalCommandConsent({
|
|
41268
|
+
command: wizardCommand,
|
|
41269
|
+
args: wizardArgs,
|
|
41270
|
+
env,
|
|
41271
|
+
transport,
|
|
41272
|
+
operation: "register"
|
|
41273
|
+
}, localConsentFromOptions(opts, true));
|
|
40878
41274
|
const server2 = addServer({
|
|
40879
41275
|
command: wizardCommand,
|
|
40880
41276
|
args: wizardArgs,
|
|
@@ -40911,6 +41307,13 @@ Server to add:`));
|
|
|
40911
41307
|
envMap[key] = rest.join("=");
|
|
40912
41308
|
}
|
|
40913
41309
|
}
|
|
41310
|
+
assertCliLocalCommandConsent({
|
|
41311
|
+
command,
|
|
41312
|
+
args,
|
|
41313
|
+
env: envMap,
|
|
41314
|
+
transport: opts.transport,
|
|
41315
|
+
operation: "register"
|
|
41316
|
+
}, opts);
|
|
40914
41317
|
const server = addServer({
|
|
40915
41318
|
name: opts.name,
|
|
40916
41319
|
description: opts.description,
|
|
@@ -40933,29 +41336,44 @@ Server to add:`));
|
|
|
40933
41336
|
}
|
|
40934
41337
|
closeDb();
|
|
40935
41338
|
});
|
|
40936
|
-
program2.command("update-server").argument("<id>", "Server ID to update").description("Update fields of a registered server").option("--name <name>", "New display name").option("--description <desc>", "New description").option("--command <cmd>", "New command").option("--args <args...>", "New args list").option("--transport <type>", "New transport type").option("--url <url>", "New URL").action((id, opts) => {
|
|
40937
|
-
|
|
40938
|
-
|
|
40939
|
-
|
|
41339
|
+
program2.command("update-server").argument("<id>", "Server ID to update").description("Update fields of a registered server").option("--name <name>", "New display name").option("--description <desc>", "New description").option("--command <cmd>", "New command").option("--args <args...>", "New args list").option("--transport <type>", "New transport type").option("--url <url>", "New URL").option("--yes", "Approve updated local stdio commands").option("--allow-local-stdio", "Approve updated local stdio commands").option("--allow-risky-command", "Approve high-risk local command patterns").action((id, opts) => {
|
|
41340
|
+
try {
|
|
41341
|
+
const server = getServer(id);
|
|
41342
|
+
if (!server) {
|
|
41343
|
+
console.error(chalk2.red(`Server "${id}" not found.`));
|
|
41344
|
+
closeDb();
|
|
41345
|
+
process.exit(1);
|
|
41346
|
+
}
|
|
41347
|
+
const fields = {};
|
|
41348
|
+
if (opts.name !== undefined)
|
|
41349
|
+
fields.name = opts.name;
|
|
41350
|
+
if (opts.description !== undefined)
|
|
41351
|
+
fields.description = opts.description;
|
|
41352
|
+
if (opts.command !== undefined)
|
|
41353
|
+
fields.command = opts.command;
|
|
41354
|
+
if (opts.args !== undefined)
|
|
41355
|
+
fields.args = opts.args;
|
|
41356
|
+
if (opts.transport !== undefined)
|
|
41357
|
+
fields.transport = opts.transport;
|
|
41358
|
+
if (opts.url !== undefined)
|
|
41359
|
+
fields.url = opts.url;
|
|
41360
|
+
if (fields.command !== undefined || fields.args !== undefined || fields.transport !== undefined) {
|
|
41361
|
+
assertCliLocalCommandConsent({
|
|
41362
|
+
command: fields.command ?? server.command,
|
|
41363
|
+
args: fields.args ?? server.args,
|
|
41364
|
+
env: server.env,
|
|
41365
|
+
transport: fields.transport ?? server.transport,
|
|
41366
|
+
operation: "register"
|
|
41367
|
+
}, opts);
|
|
41368
|
+
}
|
|
41369
|
+
const updated = updateServer(id, fields);
|
|
41370
|
+
console.log(chalk2.green(`Updated server: ${updated.name} [${updated.id}]`));
|
|
41371
|
+
closeDb();
|
|
41372
|
+
} catch (err) {
|
|
41373
|
+
console.error(chalk2.red(`Failed to update server: ${err.message}`));
|
|
40940
41374
|
closeDb();
|
|
40941
41375
|
process.exit(1);
|
|
40942
41376
|
}
|
|
40943
|
-
const fields = {};
|
|
40944
|
-
if (opts.name !== undefined)
|
|
40945
|
-
fields.name = opts.name;
|
|
40946
|
-
if (opts.description !== undefined)
|
|
40947
|
-
fields.description = opts.description;
|
|
40948
|
-
if (opts.command !== undefined)
|
|
40949
|
-
fields.command = opts.command;
|
|
40950
|
-
if (opts.args !== undefined)
|
|
40951
|
-
fields.args = opts.args;
|
|
40952
|
-
if (opts.transport !== undefined)
|
|
40953
|
-
fields.transport = opts.transport;
|
|
40954
|
-
if (opts.url !== undefined)
|
|
40955
|
-
fields.url = opts.url;
|
|
40956
|
-
const updated = updateServer(id, fields);
|
|
40957
|
-
console.log(chalk2.green(`Updated server: ${updated.name} [${updated.id}]`));
|
|
40958
|
-
closeDb();
|
|
40959
41377
|
});
|
|
40960
41378
|
program2.command("clone").argument("<id>", "Server ID to clone").argument("<new-name>", "Name for the cloned server").description("Clone a server with a new name").action((id, newName) => {
|
|
40961
41379
|
try {
|
|
@@ -41002,10 +41420,10 @@ program2.command("disable").argument("<id>", "Server ID to disable").description
|
|
|
41002
41420
|
console.log(chalk2.yellow(`Disabled server: ${server.name}`));
|
|
41003
41421
|
closeDb();
|
|
41004
41422
|
});
|
|
41005
|
-
program2.command("tools").argument("[server-id]", "Optional server ID to filter by").description("List tools (all or per server)").option("--connect", "Connect to servers to fetch live tools").action(async (serverId, opts) => {
|
|
41423
|
+
program2.command("tools").argument("[server-id]", "Optional server ID to filter by").description("List tools (all or per server)").option("--connect", "Connect to servers to fetch live tools").option("--yes", "Approve launching local stdio commands when --connect is used").option("--allow-local-stdio", "Approve launching local stdio commands when --connect is used").option("--allow-risky-command", "Approve high-risk local command patterns").action(async (serverId, opts) => {
|
|
41006
41424
|
if (opts.connect) {
|
|
41007
41425
|
console.log(chalk2.dim("Connecting to enabled servers..."));
|
|
41008
|
-
await connectAllEnabled();
|
|
41426
|
+
await connectAllEnabled({ localCommandConsent: localConsentFromOptions(opts) });
|
|
41009
41427
|
const tools2 = listAllTools();
|
|
41010
41428
|
if (tools2.length === 0) {
|
|
41011
41429
|
console.log(chalk2.dim("No tools available."));
|
|
@@ -41055,7 +41473,7 @@ ${total} tool(s) total.`));
|
|
|
41055
41473
|
}
|
|
41056
41474
|
closeDb();
|
|
41057
41475
|
});
|
|
41058
|
-
program2.command("call").argument("<tool>", "Tool name (server_id__tool_name)").option("--arg <pairs...>", "Arguments as key=value pairs").option("--json <json>", "Arguments as JSON string").description("Call a tool directly").action(async (tool, opts) => {
|
|
41476
|
+
program2.command("call").argument("<tool>", "Tool name (server_id__tool_name)").option("--arg <pairs...>", "Arguments as key=value pairs").option("--json <json>", "Arguments as JSON string").option("--yes", "Approve launching local stdio commands").option("--allow-local-stdio", "Approve launching local stdio commands").option("--allow-risky-command", "Approve high-risk local command patterns").description("Call a tool directly").action(async (tool, opts) => {
|
|
41059
41477
|
let args = {};
|
|
41060
41478
|
if (opts.json) {
|
|
41061
41479
|
try {
|
|
@@ -41078,7 +41496,7 @@ program2.command("call").argument("<tool>", "Tool name (server_id__tool_name)").
|
|
|
41078
41496
|
let exitCode = 0;
|
|
41079
41497
|
try {
|
|
41080
41498
|
console.log(chalk2.dim(`Connecting to servers...`));
|
|
41081
|
-
await connectAllEnabled();
|
|
41499
|
+
await connectAllEnabled({ localCommandConsent: localConsentFromOptions(opts) });
|
|
41082
41500
|
console.log(chalk2.dim(`Calling ${tool}...`));
|
|
41083
41501
|
const result = await callTool(tool, args);
|
|
41084
41502
|
for (const c of result.content) {
|
|
@@ -41146,7 +41564,7 @@ program2.command("status").description("Show registry stats").option("--json", "
|
|
|
41146
41564
|
console.log(` Tools: ${totalTools} (cached)`);
|
|
41147
41565
|
closeDb();
|
|
41148
41566
|
});
|
|
41149
|
-
program2.command("doctor").argument("[id]", "Server ID to check (omit to check all)").description("Diagnose server health \u2014 checks PATH, env vars, connectivity").option("--fix", "Attempt to fix issues automatically").action(async (id, opts) => {
|
|
41567
|
+
program2.command("doctor").argument("[id]", "Server ID to check (omit to check all)").description("Diagnose server health \u2014 checks PATH, env vars, connectivity").option("--fix", "Attempt to fix issues automatically").option("--yes", "Approve probing and launching local stdio commands").option("--allow-local-stdio", "Approve probing and launching local stdio commands").option("--allow-risky-command", "Approve high-risk local command patterns").action(async (id, opts) => {
|
|
41150
41568
|
const { execFileSync: execFileSync22 } = await import("child_process");
|
|
41151
41569
|
const servers = id ? [getServer(id)].filter(Boolean) : listServers();
|
|
41152
41570
|
if (servers.length === 0) {
|
|
@@ -41158,7 +41576,7 @@ program2.command("doctor").argument("[id]", "Server ID to check (omit to check a
|
|
|
41158
41576
|
for (const server of servers) {
|
|
41159
41577
|
console.log(chalk2.bold(`
|
|
41160
41578
|
${server.name} [${server.id}]`));
|
|
41161
|
-
const report = await diagnoseServer(server);
|
|
41579
|
+
const report = await diagnoseServer(server, { localCommandConsent: localConsentFromOptions(opts) });
|
|
41162
41580
|
for (const check2 of report.checks) {
|
|
41163
41581
|
const icon = check2.pass ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
41164
41582
|
console.log(` ${icon} ${check2.name}: ${chalk2.dim(check2.message)}`);
|
|
@@ -41244,7 +41662,7 @@ program2.command("update").description("Update mcps to the latest version").acti
|
|
|
41244
41662
|
process.exit(1);
|
|
41245
41663
|
}
|
|
41246
41664
|
});
|
|
41247
|
-
program2.command("find").argument("[query]", "Search query (omit to list all from awesome list)").description("Find MCP servers across npm, GitHub, official registry, and awesome lists").option("--source <sources...>", "Source IDs to search (see `mcps sources list`)").option("--limit <n>", "Max results per source", "20").option("--awesome", "List curated servers from punkpeye/awesome-mcp-servers").option("--json", "Output as JSON").option("--install", "After showing results, prompt to select one and install it").option("--yes", "Auto-install without prompting (only when there is exactly 1 result)").option("--no-cache", "Bypass source cache and fetch fresh results").action(async (query, opts) => {
|
|
41665
|
+
program2.command("find").argument("[query]", "Search query (omit to list all from awesome list)").description("Find MCP servers across npm, GitHub, official registry, and awesome lists").option("--source <sources...>", "Source IDs to search (see `mcps sources list`)").option("--limit <n>", "Max results per source", "20").option("--awesome", "List curated servers from punkpeye/awesome-mcp-servers").option("--json", "Output as JSON").option("--install", "After showing results, prompt to select one and install it").option("--yes", "Auto-install without prompting (only when there is exactly 1 result)").option("--allow-risky-command", "Approve high-risk local command patterns").option("--no-cache", "Bypass source cache and fetch fresh results").action(async (query, opts) => {
|
|
41248
41666
|
try {
|
|
41249
41667
|
if (opts.awesome) {
|
|
41250
41668
|
console.log(chalk2.dim("Fetching curated awesome-mcp-servers list..."));
|
|
@@ -41354,6 +41772,14 @@ Enter number to install (1-${results.length}), or 0 to cancel: `), resolve2);
|
|
|
41354
41772
|
return;
|
|
41355
41773
|
}
|
|
41356
41774
|
console.log(chalk2.dim(`Installing ${chosen.name}...`));
|
|
41775
|
+
const localCommandInput = {
|
|
41776
|
+
command: "npx",
|
|
41777
|
+
args: ["-y", pkg],
|
|
41778
|
+
env: {},
|
|
41779
|
+
transport: "stdio",
|
|
41780
|
+
operation: "install"
|
|
41781
|
+
};
|
|
41782
|
+
const localCommandConsent = assertCliLocalCommandConsent(localCommandInput, opts, true);
|
|
41357
41783
|
const server = addServer({
|
|
41358
41784
|
command: "npx",
|
|
41359
41785
|
args: ["-y", pkg],
|
|
@@ -41361,7 +41787,7 @@ Enter number to install (1-${results.length}), or 0 to cancel: `), resolve2);
|
|
|
41361
41787
|
description: chosen.description,
|
|
41362
41788
|
transport: "stdio"
|
|
41363
41789
|
});
|
|
41364
|
-
const results2 = installToAgents(server, ["claude", "codex", "gemini"]);
|
|
41790
|
+
const results2 = installToAgents(server, ["claude", "codex", "gemini"], { localCommandConsent });
|
|
41365
41791
|
for (const r of results2) {
|
|
41366
41792
|
if (r.success) {
|
|
41367
41793
|
console.log(chalk2.green(` \u2713 ${r.agent}`));
|
|
@@ -41506,7 +41932,7 @@ sourcesCmd.command("test").argument("<id>", "Source ID to test").description("Te
|
|
|
41506
41932
|
}
|
|
41507
41933
|
closeDb();
|
|
41508
41934
|
});
|
|
41509
|
-
program2.command("install").argument("[id]", "Server ID (from `mcps list`) to install into AI agents").description("Install a registered MCP server into Claude Code, Codex, and/or Gemini").option("--claude", "Install to Claude Code").option("--codex", "Install to Codex").option("--gemini", "Install to Gemini").option("--all", "Install to all agents (default if none specified)").option("--to <agents...>", "Agents to install to: claude, codex, gemini").option("--from-registry <id>", "Add from official registry and install in one step").option("--npm <package>", "Add an npm package as a server and install in one step").action(async (id, opts) => {
|
|
41935
|
+
program2.command("install").argument("[id]", "Server ID (from `mcps list`) to install into AI agents").description("Install a registered MCP server into Claude Code, Codex, and/or Gemini").option("--claude", "Install to Claude Code").option("--codex", "Install to Codex").option("--gemini", "Install to Gemini").option("--all", "Install to all agents (default if none specified)").option("--to <agents...>", "Agents to install to: claude, codex, gemini").option("--from-registry <id>", "Add from official registry and install in one step").option("--npm <package>", "Add an npm package as a server and install in one step").option("--yes", "Approve installing local stdio commands into agents").option("--allow-local-stdio", "Approve installing local stdio commands into agents").option("--allow-risky-command", "Approve high-risk local command patterns").action(async (id, opts) => {
|
|
41510
41936
|
const targets = [];
|
|
41511
41937
|
if (opts.to) {
|
|
41512
41938
|
for (const t of opts.to) {
|
|
@@ -41532,7 +41958,9 @@ program2.command("install").argument("[id]", "Server ID (from `mcps list`) to in
|
|
|
41532
41958
|
if (opts.fromRegistry) {
|
|
41533
41959
|
console.log(chalk2.dim(`Installing "${opts.fromRegistry}" from registry...`));
|
|
41534
41960
|
try {
|
|
41535
|
-
server = await installFromRegistry(opts.fromRegistry
|
|
41961
|
+
server = await installFromRegistry(opts.fromRegistry, {
|
|
41962
|
+
localCommandConsent: localConsentFromOptions(opts)
|
|
41963
|
+
});
|
|
41536
41964
|
console.log(chalk2.green(`Added server: ${server.name} [${server.id}]`));
|
|
41537
41965
|
} catch (err) {
|
|
41538
41966
|
console.error(chalk2.red(`Failed to install from registry: ${err.message}`));
|
|
@@ -41541,6 +41969,13 @@ program2.command("install").argument("[id]", "Server ID (from `mcps list`) to in
|
|
|
41541
41969
|
}
|
|
41542
41970
|
} else if (opts.npm) {
|
|
41543
41971
|
const pkg = opts.npm;
|
|
41972
|
+
assertCliLocalCommandConsent({
|
|
41973
|
+
command: "npx",
|
|
41974
|
+
args: ["-y", pkg],
|
|
41975
|
+
env: {},
|
|
41976
|
+
transport: "stdio",
|
|
41977
|
+
operation: "install"
|
|
41978
|
+
}, opts);
|
|
41544
41979
|
server = addServer({ command: "npx", args: ["-y", pkg], name: pkg, transport: "stdio" });
|
|
41545
41980
|
console.log(chalk2.green(`Added server: ${server.name} [${server.id}]`));
|
|
41546
41981
|
} else {
|
|
@@ -41557,7 +41992,7 @@ program2.command("install").argument("[id]", "Server ID (from `mcps list`) to in
|
|
|
41557
41992
|
}
|
|
41558
41993
|
}
|
|
41559
41994
|
console.log(chalk2.dim(`Installing "${server.name}" to: ${targets.join(", ")}...`));
|
|
41560
|
-
const results = installToAgents(server, targets);
|
|
41995
|
+
const results = installToAgents(server, targets, { localCommandConsent: localConsentFromOptions(opts) });
|
|
41561
41996
|
for (const r of results) {
|
|
41562
41997
|
if (r.success) {
|
|
41563
41998
|
console.log(chalk2.green(` \u2713 ${r.agent}`));
|