@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/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: ({ command, args, name, description, transport, url: url2, env }) => jsonContent(addServer({
34662
- command: String(command),
34663
- args: Array.isArray(args) ? args.map(String) : [],
34664
- name: typeof name === "string" ? name : undefined,
34665
- description: typeof description === "string" ? description : undefined,
34666
- transport,
34667
- url: typeof url2 === "string" ? url2 : undefined,
34668
- env: isRecordOfStrings(env) ? env : {}
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: { id: exports_external2.string().describe("Registry server ID") },
34675
- run: async ({ id }) => jsonContent(await installFromRegistry(String(id)))
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: ({ id, name, description, command, args, transport, url: url2 }) => {
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 url2 === "string")
34740
- fields.url = url2;
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: ({ id, name, use_fallback }) => {
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: ({ id, targets }) => {
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
- run: async () => {
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 ({ tool_name, arguments: args }) => {
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(args));
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: { id: exports_external2.string().describe("Server ID") },
34966
- run: async ({ id }) => {
34967
- const serverId = String(id);
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";