@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/mcp.js
CHANGED
|
@@ -30840,6 +30840,185 @@ function getCachedTools(serverId) {
|
|
|
30840
30840
|
|
|
30841
30841
|
// src/lib/remote.ts
|
|
30842
30842
|
init_config2();
|
|
30843
|
+
|
|
30844
|
+
// src/lib/local-command-consent.ts
|
|
30845
|
+
class LocalCommandConsentError extends Error {
|
|
30846
|
+
review;
|
|
30847
|
+
constructor(message, review) {
|
|
30848
|
+
super(message);
|
|
30849
|
+
this.name = "LocalCommandConsentError";
|
|
30850
|
+
this.review = review;
|
|
30851
|
+
}
|
|
30852
|
+
}
|
|
30853
|
+
var SHELL_COMMANDS = new Set(["bash", "sh", "zsh", "fish", "cmd", "cmd.exe", "powershell", "powershell.exe", "pwsh"]);
|
|
30854
|
+
var DESTRUCTIVE_COMMANDS = new Set([
|
|
30855
|
+
"rm",
|
|
30856
|
+
"dd",
|
|
30857
|
+
"mkfs",
|
|
30858
|
+
"shutdown",
|
|
30859
|
+
"reboot",
|
|
30860
|
+
"poweroff",
|
|
30861
|
+
"halt",
|
|
30862
|
+
"killall",
|
|
30863
|
+
"pkill"
|
|
30864
|
+
]);
|
|
30865
|
+
var SHELL_EVAL_FLAGS = new Set(["-c", "/c", "-Command", "-command", "-EncodedCommand", "-encodedcommand"]);
|
|
30866
|
+
var SECRET_FLAG_PATTERN = /(?:^|[-_])(api[-_]?key|token|secret|password|passwd|credential|auth|private[-_]?key)(?:$|[-_])/i;
|
|
30867
|
+
var SECRET_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
|
|
30868
|
+
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_-]+)$/;
|
|
30869
|
+
var SHELL_META_PATTERN = /[;&|`<>]|\$\(/;
|
|
30870
|
+
function commandBase(command) {
|
|
30871
|
+
return command.trim().split(/[\\/]/).pop()?.toLowerCase() || command.trim().toLowerCase();
|
|
30872
|
+
}
|
|
30873
|
+
function normalizeArgs(args) {
|
|
30874
|
+
return (args ?? []).map((arg) => String(arg));
|
|
30875
|
+
}
|
|
30876
|
+
function isSecretKey(key) {
|
|
30877
|
+
return SECRET_KEY_PATTERN.test(key) || SECRET_FLAG_PATTERN.test(key);
|
|
30878
|
+
}
|
|
30879
|
+
function isSecretValue(value) {
|
|
30880
|
+
return SECRET_VALUE_PATTERN.test(value.trim());
|
|
30881
|
+
}
|
|
30882
|
+
function isSecretAssignment(arg) {
|
|
30883
|
+
const eqIdx = arg.indexOf("=");
|
|
30884
|
+
if (eqIdx <= 0)
|
|
30885
|
+
return false;
|
|
30886
|
+
const key = arg.slice(0, eqIdx);
|
|
30887
|
+
const value = arg.slice(eqIdx + 1);
|
|
30888
|
+
return isSecretKey(key) || isSecretValue(value);
|
|
30889
|
+
}
|
|
30890
|
+
function isSecretArg(args, index) {
|
|
30891
|
+
const arg = args[index] ?? "";
|
|
30892
|
+
const previous = args[index - 1] ?? "";
|
|
30893
|
+
return isSecretAssignment(arg) || isSecretValue(arg) || SECRET_FLAG_PATTERN.test(previous);
|
|
30894
|
+
}
|
|
30895
|
+
function quoteArg(value) {
|
|
30896
|
+
return JSON.stringify(value);
|
|
30897
|
+
}
|
|
30898
|
+
function displayCommand(command, args) {
|
|
30899
|
+
return [quoteArg(command), ...args.map((arg, index) => quoteArg(isSecretArg(args, index) ? "<redacted>" : arg))].join(" ");
|
|
30900
|
+
}
|
|
30901
|
+
function pushRisk(risks, risk) {
|
|
30902
|
+
if (risks.some((existing) => existing.code === risk.code && existing.evidence === risk.evidence))
|
|
30903
|
+
return;
|
|
30904
|
+
risks.push(risk);
|
|
30905
|
+
}
|
|
30906
|
+
function inspectRisks(command, args, env) {
|
|
30907
|
+
const risks = [];
|
|
30908
|
+
const base = commandBase(command);
|
|
30909
|
+
const joined = [command, ...args].join(" ");
|
|
30910
|
+
if (SHELL_COMMANDS.has(base)) {
|
|
30911
|
+
pushRisk(risks, {
|
|
30912
|
+
code: "shell_interpreter",
|
|
30913
|
+
severity: "warning",
|
|
30914
|
+
message: "Command launches a shell interpreter.",
|
|
30915
|
+
evidence: base
|
|
30916
|
+
});
|
|
30917
|
+
if (args.some((arg) => SHELL_EVAL_FLAGS.has(arg))) {
|
|
30918
|
+
pushRisk(risks, {
|
|
30919
|
+
code: "shell_eval",
|
|
30920
|
+
severity: "danger",
|
|
30921
|
+
message: "Shell command evaluates an inline script.",
|
|
30922
|
+
evidence: base
|
|
30923
|
+
});
|
|
30924
|
+
}
|
|
30925
|
+
}
|
|
30926
|
+
if (base === "sudo") {
|
|
30927
|
+
pushRisk(risks, {
|
|
30928
|
+
code: "privilege_escalation",
|
|
30929
|
+
severity: "danger",
|
|
30930
|
+
message: "Command requests elevated privileges.",
|
|
30931
|
+
evidence: base
|
|
30932
|
+
});
|
|
30933
|
+
}
|
|
30934
|
+
if (DESTRUCTIVE_COMMANDS.has(base) || /\brm\s+-[^\s]*[rf][^\s]*\b/.test(joined) || /--no-preserve-root\b/.test(joined)) {
|
|
30935
|
+
pushRisk(risks, {
|
|
30936
|
+
code: "destructive_command",
|
|
30937
|
+
severity: "danger",
|
|
30938
|
+
message: "Command includes a destructive system operation.",
|
|
30939
|
+
evidence: base
|
|
30940
|
+
});
|
|
30941
|
+
}
|
|
30942
|
+
if (/\b(curl|wget)\b[\s\S]*\|[\s\S]*\b(sh|bash|zsh|fish)\b/.test(joined)) {
|
|
30943
|
+
pushRisk(risks, {
|
|
30944
|
+
code: "download_pipe_shell",
|
|
30945
|
+
severity: "danger",
|
|
30946
|
+
message: "Command downloads remote content and pipes it to a shell."
|
|
30947
|
+
});
|
|
30948
|
+
}
|
|
30949
|
+
if ([command, ...args].some((part) => SHELL_META_PATTERN.test(part))) {
|
|
30950
|
+
pushRisk(risks, {
|
|
30951
|
+
code: "shell_metacharacters",
|
|
30952
|
+
severity: "warning",
|
|
30953
|
+
message: "Command or arguments contain shell metacharacters."
|
|
30954
|
+
});
|
|
30955
|
+
}
|
|
30956
|
+
if (args.some((arg, index) => isSecretArg(args, index))) {
|
|
30957
|
+
pushRisk(risks, {
|
|
30958
|
+
code: "inline_secret",
|
|
30959
|
+
severity: "danger",
|
|
30960
|
+
message: "Command arguments appear to contain inline secret material."
|
|
30961
|
+
});
|
|
30962
|
+
}
|
|
30963
|
+
const secretEnvKeys = Object.keys(env).filter(isSecretKey).sort();
|
|
30964
|
+
if (secretEnvKeys.length > 0) {
|
|
30965
|
+
pushRisk(risks, {
|
|
30966
|
+
code: "secret_env",
|
|
30967
|
+
severity: "warning",
|
|
30968
|
+
message: "Environment contains secret-like keys; values are redacted from consent output.",
|
|
30969
|
+
evidence: secretEnvKeys.join(", ")
|
|
30970
|
+
});
|
|
30971
|
+
}
|
|
30972
|
+
return risks;
|
|
30973
|
+
}
|
|
30974
|
+
function inspectLocalCommand(input) {
|
|
30975
|
+
const args = normalizeArgs(input.args);
|
|
30976
|
+
const env = input.env ?? {};
|
|
30977
|
+
const transport = input.transport ?? "stdio";
|
|
30978
|
+
const risks = inspectRisks(input.command, args, env);
|
|
30979
|
+
return {
|
|
30980
|
+
requiresConsent: transport === "stdio",
|
|
30981
|
+
operation: input.operation ?? "launch",
|
|
30982
|
+
command: input.command,
|
|
30983
|
+
args,
|
|
30984
|
+
displayCommand: displayCommand(input.command, args),
|
|
30985
|
+
envKeys: Object.keys(env).sort(),
|
|
30986
|
+
risks,
|
|
30987
|
+
hasDangerousRisk: risks.some((risk) => risk.severity === "danger")
|
|
30988
|
+
};
|
|
30989
|
+
}
|
|
30990
|
+
function formatLocalCommandReview(review) {
|
|
30991
|
+
const lines = [
|
|
30992
|
+
`Command: ${review.displayCommand}`,
|
|
30993
|
+
review.envKeys.length > 0 ? `Env keys: ${review.envKeys.join(", ")}` : "Env keys: <none>"
|
|
30994
|
+
];
|
|
30995
|
+
if (review.risks.length > 0) {
|
|
30996
|
+
lines.push("Risks:");
|
|
30997
|
+
for (const risk of review.risks) {
|
|
30998
|
+
lines.push(`- ${risk.severity}: ${risk.code} - ${risk.message}${risk.evidence ? ` (${risk.evidence})` : ""}`);
|
|
30999
|
+
}
|
|
31000
|
+
} else {
|
|
31001
|
+
lines.push("Risks: none detected");
|
|
31002
|
+
}
|
|
31003
|
+
return lines.join(`
|
|
31004
|
+
`);
|
|
31005
|
+
}
|
|
31006
|
+
function assertLocalCommandConsent(input, consent = {}) {
|
|
31007
|
+
const review = inspectLocalCommand(input);
|
|
31008
|
+
if (!review.requiresConsent)
|
|
31009
|
+
return review;
|
|
31010
|
+
if (consent.approved !== true) {
|
|
31011
|
+
throw new LocalCommandConsentError(`local stdio command approval is required before ${review.operation}.
|
|
31012
|
+
${formatLocalCommandReview(review)}`, review);
|
|
31013
|
+
}
|
|
31014
|
+
if (review.hasDangerousRisk && consent.allowRisky !== true) {
|
|
31015
|
+
throw new LocalCommandConsentError(`risky command approval is required before ${review.operation}.
|
|
31016
|
+
${formatLocalCommandReview(review)}`, review);
|
|
31017
|
+
}
|
|
31018
|
+
return review;
|
|
31019
|
+
}
|
|
31020
|
+
|
|
31021
|
+
// src/lib/remote.ts
|
|
30843
31022
|
function parseRegistryEntry(entry) {
|
|
30844
31023
|
const s = entry.server;
|
|
30845
31024
|
return {
|
|
@@ -30870,7 +31049,7 @@ async function getRegistryServer(id) {
|
|
|
30870
31049
|
const all = entries.map(parseRegistryEntry);
|
|
30871
31050
|
return all.find((s) => s.id === id) || null;
|
|
30872
31051
|
}
|
|
30873
|
-
async function installFromRegistry(id) {
|
|
31052
|
+
async function installFromRegistry(id, options = {}) {
|
|
30874
31053
|
const server = await getRegistryServer(id);
|
|
30875
31054
|
if (!server) {
|
|
30876
31055
|
throw new Error(`Server "${id}" not found in registry`);
|
|
@@ -30890,6 +31069,13 @@ async function installFromRegistry(id) {
|
|
|
30890
31069
|
transport = pkg.transport.type;
|
|
30891
31070
|
}
|
|
30892
31071
|
}
|
|
31072
|
+
assertLocalCommandConsent({
|
|
31073
|
+
command,
|
|
31074
|
+
args,
|
|
31075
|
+
transport,
|
|
31076
|
+
env: {},
|
|
31077
|
+
operation: "register"
|
|
31078
|
+
}, options.localCommandConsent);
|
|
30893
31079
|
return addServer({
|
|
30894
31080
|
name: server.name,
|
|
30895
31081
|
description: server.description,
|
|
@@ -30984,7 +31170,22 @@ function installToGemini(entry) {
|
|
|
30984
31170
|
return { agent: "gemini", success: false, error: err.message };
|
|
30985
31171
|
}
|
|
30986
31172
|
}
|
|
30987
|
-
function installToAgents(entry, targets = ["claude", "codex", "gemini"]) {
|
|
31173
|
+
function installToAgents(entry, targets = ["claude", "codex", "gemini"], options = {}) {
|
|
31174
|
+
try {
|
|
31175
|
+
assertLocalCommandConsent({
|
|
31176
|
+
command: entry.command,
|
|
31177
|
+
args: entry.args,
|
|
31178
|
+
env: entry.env,
|
|
31179
|
+
transport: entry.transport,
|
|
31180
|
+
operation: "install"
|
|
31181
|
+
}, options.localCommandConsent);
|
|
31182
|
+
} catch (err) {
|
|
31183
|
+
return targets.map((target) => ({
|
|
31184
|
+
agent: target,
|
|
31185
|
+
success: false,
|
|
31186
|
+
error: err.message
|
|
31187
|
+
}));
|
|
31188
|
+
}
|
|
30988
31189
|
return targets.map((target) => {
|
|
30989
31190
|
if (target === "claude")
|
|
30990
31191
|
return installToClaude(entry);
|
|
@@ -33401,7 +33602,7 @@ function requireUrl(entry) {
|
|
|
33401
33602
|
throw new Error(`Server "${entry.id}" has an invalid URL: ${entry.url}`);
|
|
33402
33603
|
}
|
|
33403
33604
|
}
|
|
33404
|
-
async function connectToServer(entry) {
|
|
33605
|
+
async function connectToServer(entry, options = {}) {
|
|
33405
33606
|
if (connections.has(entry.id)) {
|
|
33406
33607
|
return connections.get(entry.id);
|
|
33407
33608
|
}
|
|
@@ -33417,6 +33618,13 @@ async function connectToServer(entry) {
|
|
|
33417
33618
|
if (!entry.command?.trim()) {
|
|
33418
33619
|
throw new Error(`Server "${entry.id}" is missing a command`);
|
|
33419
33620
|
}
|
|
33621
|
+
assertLocalCommandConsent({
|
|
33622
|
+
command: entry.command,
|
|
33623
|
+
args: entry.args,
|
|
33624
|
+
env: entry.env,
|
|
33625
|
+
transport: entry.transport,
|
|
33626
|
+
operation: "launch"
|
|
33627
|
+
}, options.localCommandConsent);
|
|
33420
33628
|
transport = new StdioClientTransport({
|
|
33421
33629
|
command: entry.command,
|
|
33422
33630
|
args: entry.args,
|
|
@@ -33533,7 +33741,7 @@ async function callTool(prefixedName, args) {
|
|
|
33533
33741
|
]
|
|
33534
33742
|
};
|
|
33535
33743
|
}
|
|
33536
|
-
async function connectAllEnabled() {
|
|
33744
|
+
async function connectAllEnabled(options = {}) {
|
|
33537
33745
|
const servers = listServers().filter((s) => s.enabled);
|
|
33538
33746
|
const results = [];
|
|
33539
33747
|
let index = 0;
|
|
@@ -33545,7 +33753,7 @@ async function connectAllEnabled() {
|
|
|
33545
33753
|
return;
|
|
33546
33754
|
const server = servers[current];
|
|
33547
33755
|
try {
|
|
33548
|
-
const conn = await connectToServer(server);
|
|
33756
|
+
const conn = await connectToServer(server, options);
|
|
33549
33757
|
results.push(conn);
|
|
33550
33758
|
} catch (err) {
|
|
33551
33759
|
console.error(`Failed to connect to ${server.name}: ${err.message}`);
|
|
@@ -33558,13 +33766,28 @@ async function connectAllEnabled() {
|
|
|
33558
33766
|
|
|
33559
33767
|
// src/lib/doctor.ts
|
|
33560
33768
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
33561
|
-
async function diagnoseServer(server) {
|
|
33769
|
+
async function diagnoseServer(server, options = {}) {
|
|
33562
33770
|
const checks4 = [];
|
|
33771
|
+
let hasLocalConsent = true;
|
|
33563
33772
|
if (server.transport === "stdio") {
|
|
33773
|
+
try {
|
|
33774
|
+
assertLocalCommandConsent({
|
|
33775
|
+
command: server.command,
|
|
33776
|
+
args: server.args,
|
|
33777
|
+
env: server.env,
|
|
33778
|
+
transport: server.transport,
|
|
33779
|
+
operation: "diagnose"
|
|
33780
|
+
}, options.localCommandConsent);
|
|
33781
|
+
} catch (err) {
|
|
33782
|
+
hasLocalConsent = false;
|
|
33783
|
+
checks4.push({ name: "local command consent", pass: false, message: err.message });
|
|
33784
|
+
}
|
|
33564
33785
|
try {
|
|
33565
33786
|
const path = execFileSync2("which", [server.command], { stdio: "pipe" }).toString().trim();
|
|
33566
33787
|
let version2 = "";
|
|
33567
33788
|
try {
|
|
33789
|
+
if (!hasLocalConsent)
|
|
33790
|
+
throw new Error("local stdio command approval is required before version probing");
|
|
33568
33791
|
version2 = execFileSync2(server.command, ["--version"], { stdio: "pipe" }).toString().trim().split(`
|
|
33569
33792
|
`)[0];
|
|
33570
33793
|
} catch {}
|
|
@@ -33595,10 +33818,10 @@ async function diagnoseServer(server) {
|
|
|
33595
33818
|
checks4.push({ name: "URL reachable", pass: false, message: `unreachable: ${err.message}` });
|
|
33596
33819
|
}
|
|
33597
33820
|
}
|
|
33598
|
-
if (server.enabled) {
|
|
33821
|
+
if (server.enabled && hasLocalConsent) {
|
|
33599
33822
|
try {
|
|
33600
33823
|
await Promise.race([
|
|
33601
|
-
connectToServer(server),
|
|
33824
|
+
connectToServer(server, { localCommandConsent: options.localCommandConsent }),
|
|
33602
33825
|
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout after 10s")), 1e4))
|
|
33603
33826
|
]);
|
|
33604
33827
|
await disconnectServer(server.id);
|
|
@@ -33606,8 +33829,10 @@ async function diagnoseServer(server) {
|
|
|
33606
33829
|
} catch (err) {
|
|
33607
33830
|
checks4.push({ name: "connect & list tools", pass: false, message: err.message });
|
|
33608
33831
|
}
|
|
33609
|
-
} else {
|
|
33832
|
+
} else if (!server.enabled) {
|
|
33610
33833
|
checks4.push({ name: "connect & list tools", pass: true, message: "skipped (server disabled)" });
|
|
33834
|
+
} else {
|
|
33835
|
+
checks4.push({ name: "connect & list tools", pass: false, message: "skipped until local stdio command is approved" });
|
|
33611
33836
|
}
|
|
33612
33837
|
return {
|
|
33613
33838
|
server,
|
|
@@ -34606,11 +34831,19 @@ function installProviderProfile(id, options = {}) {
|
|
|
34606
34831
|
if (!command) {
|
|
34607
34832
|
throw new Error(`Provider profile "${id}" does not define an install fallback command`);
|
|
34608
34833
|
}
|
|
34834
|
+
assertLocalCommandConsent({
|
|
34835
|
+
command,
|
|
34836
|
+
args,
|
|
34837
|
+
env: fallback?.env ?? {},
|
|
34838
|
+
transport: useFallback ? "stdio" : profile.transport,
|
|
34839
|
+
operation: "register"
|
|
34840
|
+
}, options.localCommandConsent);
|
|
34609
34841
|
return addServer({
|
|
34610
34842
|
name: options.name ?? profile.displayName,
|
|
34611
34843
|
description: profile.description ?? undefined,
|
|
34612
34844
|
command,
|
|
34613
34845
|
args,
|
|
34846
|
+
env: fallback?.env,
|
|
34614
34847
|
transport: useFallback ? "stdio" : profile.transport,
|
|
34615
34848
|
url: useFallback ? fallback?.url : profile.endpoint ?? undefined,
|
|
34616
34849
|
source: "provider-profile"
|
|
@@ -34632,6 +34865,13 @@ function jsonContent(value) {
|
|
|
34632
34865
|
function errorContent(text) {
|
|
34633
34866
|
return { ...textContent(text), isError: true };
|
|
34634
34867
|
}
|
|
34868
|
+
function localConsent(input) {
|
|
34869
|
+
return {
|
|
34870
|
+
approved: input.allow_local_stdio === true,
|
|
34871
|
+
allowRisky: input.allow_risky_command === true,
|
|
34872
|
+
source: "mcp"
|
|
34873
|
+
};
|
|
34874
|
+
}
|
|
34635
34875
|
function buildMcpTools() {
|
|
34636
34876
|
const definitions = [
|
|
34637
34877
|
{
|
|
@@ -34656,23 +34896,46 @@ function buildMcpTools() {
|
|
|
34656
34896
|
description: exports_external2.string().optional().describe("Description"),
|
|
34657
34897
|
transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("Transport type"),
|
|
34658
34898
|
url: exports_external2.string().optional().describe("URL for remote transports"),
|
|
34659
|
-
env: exports_external2.record(exports_external2.string()).optional().describe("Environment variables")
|
|
34899
|
+
env: exports_external2.record(exports_external2.string()).optional().describe("Environment variables"),
|
|
34900
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering this local stdio command"),
|
|
34901
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve registering risky local command patterns")
|
|
34660
34902
|
},
|
|
34661
|
-
run: (
|
|
34662
|
-
command
|
|
34663
|
-
args
|
|
34664
|
-
|
|
34665
|
-
|
|
34666
|
-
|
|
34667
|
-
|
|
34668
|
-
|
|
34669
|
-
|
|
34903
|
+
run: (input) => {
|
|
34904
|
+
const command = String(input.command);
|
|
34905
|
+
const args = Array.isArray(input.args) ? input.args.map(String) : [];
|
|
34906
|
+
const env = isRecordOfStrings(input.env) ? input.env : {};
|
|
34907
|
+
const transport = input.transport;
|
|
34908
|
+
try {
|
|
34909
|
+
assertLocalCommandConsent({ command, args, env, transport, operation: "register" }, localConsent(input));
|
|
34910
|
+
return jsonContent(addServer({
|
|
34911
|
+
command,
|
|
34912
|
+
args,
|
|
34913
|
+
name: typeof input.name === "string" ? input.name : undefined,
|
|
34914
|
+
description: typeof input.description === "string" ? input.description : undefined,
|
|
34915
|
+
transport,
|
|
34916
|
+
url: typeof input.url === "string" ? input.url : undefined,
|
|
34917
|
+
env
|
|
34918
|
+
}));
|
|
34919
|
+
} catch (err) {
|
|
34920
|
+
return errorContent(err.message);
|
|
34921
|
+
}
|
|
34922
|
+
}
|
|
34670
34923
|
},
|
|
34671
34924
|
{
|
|
34672
34925
|
name: "install_from_registry",
|
|
34673
34926
|
description: "Install an MCP server from the official registry",
|
|
34674
|
-
paramsSchema: {
|
|
34675
|
-
|
|
34927
|
+
paramsSchema: {
|
|
34928
|
+
id: exports_external2.string().describe("Registry server ID"),
|
|
34929
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering registry stdio commands"),
|
|
34930
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve registering risky local command patterns")
|
|
34931
|
+
},
|
|
34932
|
+
run: async (input) => {
|
|
34933
|
+
try {
|
|
34934
|
+
return jsonContent(await installFromRegistry(String(input.id), { localCommandConsent: localConsent(input) }));
|
|
34935
|
+
} catch (err) {
|
|
34936
|
+
return errorContent(err.message);
|
|
34937
|
+
}
|
|
34938
|
+
}
|
|
34676
34939
|
},
|
|
34677
34940
|
{
|
|
34678
34941
|
name: "remove_server",
|
|
@@ -34718,26 +34981,41 @@ function buildMcpTools() {
|
|
|
34718
34981
|
command: exports_external2.string().optional().describe("New command"),
|
|
34719
34982
|
args: exports_external2.array(exports_external2.string()).optional().describe("New args list"),
|
|
34720
34983
|
transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("New transport type"),
|
|
34721
|
-
url: exports_external2.string().optional().describe("New URL for remote transports")
|
|
34984
|
+
url: exports_external2.string().optional().describe("New URL for remote transports"),
|
|
34985
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve updating this local stdio command"),
|
|
34986
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve risky local command patterns")
|
|
34722
34987
|
},
|
|
34723
|
-
run: (
|
|
34724
|
-
const serverId = String(id);
|
|
34988
|
+
run: (input) => {
|
|
34989
|
+
const serverId = String(input.id);
|
|
34725
34990
|
const existing = getServer(serverId);
|
|
34726
34991
|
if (!existing)
|
|
34727
34992
|
return errorContent(`Server "${serverId}" not found.`);
|
|
34728
34993
|
const fields = {};
|
|
34729
|
-
if (typeof name === "string")
|
|
34730
|
-
fields.name = name;
|
|
34731
|
-
if (typeof description === "string")
|
|
34732
|
-
fields.description = description;
|
|
34733
|
-
if (typeof command === "string")
|
|
34734
|
-
fields.command = command;
|
|
34735
|
-
if (Array.isArray(args))
|
|
34736
|
-
fields.args = args.map(String);
|
|
34737
|
-
if (transport === "stdio" || transport === "sse" || transport === "streamable-http")
|
|
34738
|
-
fields.transport = transport;
|
|
34739
|
-
if (typeof
|
|
34740
|
-
fields.url =
|
|
34994
|
+
if (typeof input.name === "string")
|
|
34995
|
+
fields.name = input.name;
|
|
34996
|
+
if (typeof input.description === "string")
|
|
34997
|
+
fields.description = input.description;
|
|
34998
|
+
if (typeof input.command === "string")
|
|
34999
|
+
fields.command = input.command;
|
|
35000
|
+
if (Array.isArray(input.args))
|
|
35001
|
+
fields.args = input.args.map(String);
|
|
35002
|
+
if (input.transport === "stdio" || input.transport === "sse" || input.transport === "streamable-http")
|
|
35003
|
+
fields.transport = input.transport;
|
|
35004
|
+
if (typeof input.url === "string")
|
|
35005
|
+
fields.url = input.url;
|
|
35006
|
+
if (fields.command !== undefined || fields.args !== undefined || fields.transport !== undefined) {
|
|
35007
|
+
try {
|
|
35008
|
+
assertLocalCommandConsent({
|
|
35009
|
+
command: fields.command ?? existing.command,
|
|
35010
|
+
args: fields.args ?? existing.args,
|
|
35011
|
+
env: existing.env,
|
|
35012
|
+
transport: fields.transport ?? existing.transport,
|
|
35013
|
+
operation: "register"
|
|
35014
|
+
}, localConsent(input));
|
|
35015
|
+
} catch (err) {
|
|
35016
|
+
return errorContent(err.message);
|
|
35017
|
+
}
|
|
35018
|
+
}
|
|
34741
35019
|
return jsonContent(redactServerEnv(updateServer(serverId, fields)));
|
|
34742
35020
|
}
|
|
34743
35021
|
},
|
|
@@ -34819,13 +35097,16 @@ function buildMcpTools() {
|
|
|
34819
35097
|
paramsSchema: {
|
|
34820
35098
|
id: exports_external2.string().describe("Provider profile ID"),
|
|
34821
35099
|
name: exports_external2.string().optional().describe("Override registered server name"),
|
|
34822
|
-
use_fallback: exports_external2.boolean().optional().describe("Install the stdio fallback command instead of the direct remote transport")
|
|
35100
|
+
use_fallback: exports_external2.boolean().optional().describe("Install the stdio fallback command instead of the direct remote transport"),
|
|
35101
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering provider stdio fallback commands"),
|
|
35102
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve risky local command patterns")
|
|
34823
35103
|
},
|
|
34824
|
-
run: (
|
|
35104
|
+
run: (input) => {
|
|
34825
35105
|
try {
|
|
34826
|
-
return jsonContent(redactServerEnv(installProviderProfile(String(id), {
|
|
34827
|
-
name: typeof name === "string" ? name : undefined,
|
|
34828
|
-
useFallback: use_fallback === true
|
|
35106
|
+
return jsonContent(redactServerEnv(installProviderProfile(String(input.id), {
|
|
35107
|
+
name: typeof input.name === "string" ? input.name : undefined,
|
|
35108
|
+
useFallback: input.use_fallback === true,
|
|
35109
|
+
localCommandConsent: localConsent(input)
|
|
34829
35110
|
})));
|
|
34830
35111
|
} catch (err) {
|
|
34831
35112
|
return errorContent(err.message);
|
|
@@ -34898,15 +35179,19 @@ function buildMcpTools() {
|
|
|
34898
35179
|
description: "Install a registered MCP server into Claude Code, Codex, and/or Gemini",
|
|
34899
35180
|
paramsSchema: {
|
|
34900
35181
|
id: exports_external2.string().describe("Server ID to install (from list_servers)"),
|
|
34901
|
-
targets: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("Target agents to install into (default: all)")
|
|
35182
|
+
targets: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("Target agents to install into (default: all)"),
|
|
35183
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve installing local stdio commands into local agent configs"),
|
|
35184
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve installing risky local command patterns")
|
|
34902
35185
|
},
|
|
34903
|
-
run: (
|
|
34904
|
-
const serverId = String(id);
|
|
35186
|
+
run: (input) => {
|
|
35187
|
+
const serverId = String(input.id);
|
|
34905
35188
|
const entry = getServer(serverId);
|
|
34906
35189
|
if (!entry)
|
|
34907
35190
|
return errorContent(`Server "${serverId}" not found.`);
|
|
34908
|
-
const agentTargets = Array.isArray(targets) ? targets : undefined;
|
|
34909
|
-
return jsonContent(installToAgents(entry, agentTargets ?? ["claude", "codex", "gemini"]
|
|
35191
|
+
const agentTargets = Array.isArray(input.targets) ? input.targets : undefined;
|
|
35192
|
+
return jsonContent(installToAgents(entry, agentTargets ?? ["claude", "codex", "gemini"], {
|
|
35193
|
+
localCommandConsent: localConsent(input)
|
|
35194
|
+
}));
|
|
34910
35195
|
}
|
|
34911
35196
|
},
|
|
34912
35197
|
{
|
|
@@ -34918,11 +35203,14 @@ function buildMcpTools() {
|
|
|
34918
35203
|
{
|
|
34919
35204
|
name: "connect_and_list_tools",
|
|
34920
35205
|
description: "Connect to all enabled MCP servers and list their available tools",
|
|
34921
|
-
paramsSchema: {
|
|
34922
|
-
|
|
35206
|
+
paramsSchema: {
|
|
35207
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching enabled local stdio commands"),
|
|
35208
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve launching risky local command patterns")
|
|
35209
|
+
},
|
|
35210
|
+
run: async (input) => {
|
|
34923
35211
|
let liveTools = [];
|
|
34924
35212
|
try {
|
|
34925
|
-
await connectAllEnabled();
|
|
35213
|
+
await connectAllEnabled({ localCommandConsent: localConsent(input) });
|
|
34926
35214
|
liveTools = listAllTools();
|
|
34927
35215
|
} finally {
|
|
34928
35216
|
await disconnectAll().catch(() => {
|
|
@@ -34937,11 +35225,13 @@ function buildMcpTools() {
|
|
|
34937
35225
|
description: `Call a tool on a connected upstream MCP server. Tool name format: server_id${TOOL_PREFIX_SEPARATOR}tool_name`,
|
|
34938
35226
|
paramsSchema: {
|
|
34939
35227
|
tool_name: exports_external2.string().describe(`Prefixed tool name (server_id${TOOL_PREFIX_SEPARATOR}tool_name)`),
|
|
34940
|
-
arguments: exports_external2.record(exports_external2.unknown()).optional().describe("Tool arguments as key-value pairs")
|
|
35228
|
+
arguments: exports_external2.record(exports_external2.unknown()).optional().describe("Tool arguments as key-value pairs"),
|
|
35229
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching this local stdio command"),
|
|
35230
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve launching risky local command patterns")
|
|
34941
35231
|
},
|
|
34942
|
-
run: async (
|
|
35232
|
+
run: async (input) => {
|
|
34943
35233
|
try {
|
|
34944
|
-
const toolName = String(tool_name);
|
|
35234
|
+
const toolName = String(input.tool_name);
|
|
34945
35235
|
const sepIdx = toolName.indexOf(TOOL_PREFIX_SEPARATOR);
|
|
34946
35236
|
if (sepIdx === -1)
|
|
34947
35237
|
return errorContent(`Error: Invalid tool name "${toolName}"`);
|
|
@@ -34951,8 +35241,8 @@ function buildMcpTools() {
|
|
|
34951
35241
|
return errorContent(`Error: Server "${serverId}" not found.`);
|
|
34952
35242
|
if (!entry.enabled)
|
|
34953
35243
|
return errorContent(`Error: Server "${serverId}" is disabled.`);
|
|
34954
|
-
await connectToServer(entry);
|
|
34955
|
-
const result = await callTool(toolName, readRecord(
|
|
35244
|
+
await connectToServer(entry, { localCommandConsent: localConsent(input) });
|
|
35245
|
+
const result = await callTool(toolName, readRecord(input.arguments));
|
|
34956
35246
|
return { content: result.content };
|
|
34957
35247
|
} catch (error2) {
|
|
34958
35248
|
return errorContent(`Error: ${error2.message}`);
|
|
@@ -34962,13 +35252,17 @@ function buildMcpTools() {
|
|
|
34962
35252
|
{
|
|
34963
35253
|
name: "diagnose_server",
|
|
34964
35254
|
description: "Run health checks on a registered MCP server",
|
|
34965
|
-
paramsSchema: {
|
|
34966
|
-
|
|
34967
|
-
|
|
35255
|
+
paramsSchema: {
|
|
35256
|
+
id: exports_external2.string().describe("Server ID"),
|
|
35257
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching local stdio diagnostics"),
|
|
35258
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve diagnosing risky local command patterns")
|
|
35259
|
+
},
|
|
35260
|
+
run: async (input) => {
|
|
35261
|
+
const serverId = String(input.id);
|
|
34968
35262
|
const entry = getServer(serverId);
|
|
34969
35263
|
if (!entry)
|
|
34970
35264
|
return errorContent(`Server "${serverId}" not found.`);
|
|
34971
|
-
return jsonContent(await diagnoseServer(entry));
|
|
35265
|
+
return jsonContent(await diagnoseServer(entry, { localCommandConsent: localConsent(input) }));
|
|
34972
35266
|
}
|
|
34973
35267
|
},
|
|
34974
35268
|
{
|
package/dist/index.d.ts
CHANGED
|
@@ -14,5 +14,7 @@ export type { McpSource, AddSourceOptions } from "./types.js";
|
|
|
14
14
|
export { addMachine, upsertMachine, listMachines, getMachine, updateMachine, removeMachine, seedDefaultMachines, DEFAULT_MACHINE_SEEDS, } from "./lib/machines.js";
|
|
15
15
|
export { listHasnaMcpCatalog, runFleetHealthCheck, runFleetInstall } from "./lib/fleet.js";
|
|
16
16
|
export { readPackageVersion } from "./lib/version.js";
|
|
17
|
+
export { assertLocalCommandConsent, formatLocalCommandReview, inspectLocalCommand, LocalCommandConsentError, } from "./lib/local-command-consent.js";
|
|
18
|
+
export type { LocalCommandConsent, LocalCommandInput, LocalCommandOperation, LocalCommandReview, LocalCommandRisk, } from "./lib/local-command-consent.js";
|
|
17
19
|
export { connectToServer, disconnectServer, listAllTools, callTool, refreshTools, disconnectAll, } from "./lib/proxy.js";
|
|
18
20
|
export { getDb, closeDb } from "./lib/db.js";
|