@hasna/mcps 0.0.13 → 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 CHANGED
@@ -11944,6 +11944,85 @@ var init_config2 = __esm(() => {
11944
11944
  DB_PATH = process.env.HASNA_MCPS_DB_PATH ?? process.env.MCPS_DB_PATH ?? join5(MCPS_DIR, "registry.db");
11945
11945
  });
11946
11946
 
11947
+ // src/lib/provider-profile-seeds.ts
11948
+ var DEFAULT_PROVIDER_PROFILE_SEEDS;
11949
+ var init_provider_profile_seeds = __esm(() => {
11950
+ DEFAULT_PROVIDER_PROFILE_SEEDS = [
11951
+ {
11952
+ id: "notion",
11953
+ displayName: "Notion",
11954
+ description: "Connect a Notion workspace so agents can search, read, create, and update workspace content.",
11955
+ endpoint: "https://mcp.notion.com/mcp",
11956
+ transport: "streamable-http",
11957
+ fallbackEndpoints: [
11958
+ {
11959
+ transport: "sse",
11960
+ url: "https://mcp.notion.com/sse",
11961
+ notes: "Fallback for clients that do not support Streamable HTTP."
11962
+ }
11963
+ ],
11964
+ authType: "oauth2",
11965
+ authMetadata: {
11966
+ oauthVersion: "2.0",
11967
+ pkce: true,
11968
+ dynamicClientRegistration: true,
11969
+ bearerToken: "none",
11970
+ notes: "Remote Notion MCP uses OAuth with PKCE. Bearer-token authentication is only appropriate for self-hosted/local fallback deployments."
11971
+ },
11972
+ tokenMode: "workspace",
11973
+ installFallback: {
11974
+ command: "npx",
11975
+ args: ["-y", "mcp-remote", "https://mcp.notion.com/sse", "--transport", "sse-only"],
11976
+ packageName: "mcp-remote",
11977
+ url: "https://mcp.notion.com/sse"
11978
+ },
11979
+ docsUrl: "https://developers.notion.com/guides/mcp/build-mcp-client",
11980
+ safety: {
11981
+ requiresApproval: true,
11982
+ dataClasses: ["workspace_content", "pages", "databases", "comments"],
11983
+ notes: "Connected agents operate with the authorizing user's workspace access. Human confirmation is recommended for write-capable workflows."
11984
+ },
11985
+ provenance: {
11986
+ source: "curated",
11987
+ sourceUrl: "https://developers.notion.com/guides/mcp/build-mcp-client",
11988
+ verifiedAt: "2026-05-10"
11989
+ }
11990
+ },
11991
+ {
11992
+ id: "linear",
11993
+ displayName: "Linear",
11994
+ description: "Connect Linear so agents can find, create, and update issues, projects, comments, and related workspace objects.",
11995
+ endpoint: "https://mcp.linear.app/mcp",
11996
+ transport: "streamable-http",
11997
+ authType: "oauth2",
11998
+ authMetadata: {
11999
+ oauthVersion: "2.1",
12000
+ dynamicClientRegistration: true,
12001
+ bearerToken: "optional",
12002
+ notes: "Linear supports interactive OAuth 2.1 with dynamic client registration and optional Authorization: Bearer tokens for OAuth tokens or API keys."
12003
+ },
12004
+ tokenMode: "workspace",
12005
+ installFallback: {
12006
+ command: "npx",
12007
+ args: ["-y", "mcp-remote", "https://mcp.linear.app/mcp"],
12008
+ packageName: "mcp-remote",
12009
+ url: "https://mcp.linear.app/mcp"
12010
+ },
12011
+ docsUrl: "https://linear.app/docs/mcp",
12012
+ safety: {
12013
+ requiresApproval: true,
12014
+ dataClasses: ["issues", "projects", "comments", "teams", "users"],
12015
+ notes: "Linear tools can create and update workspace objects, so write actions should be policy-gated by the platform."
12016
+ },
12017
+ provenance: {
12018
+ source: "curated",
12019
+ sourceUrl: "https://linear.app/docs/mcp",
12020
+ verifiedAt: "2026-05-10"
12021
+ }
12022
+ }
12023
+ ];
12024
+ });
12025
+
11947
12026
  // src/lib/db.ts
11948
12027
  import { mkdirSync as mkdirSync5 } from "fs";
11949
12028
  function getDb() {
@@ -12029,6 +12108,50 @@ function getDb() {
12029
12108
  ('github-mcp-topic', 'GitHub MCP Topic', 'github-topic', 'https://api.github.com/search/repositories', 'GitHub repositories tagged with mcp-server topic')
12030
12109
  `);
12031
12110
  }
12111
+ db.exec(`
12112
+ CREATE TABLE IF NOT EXISTS provider_profiles (
12113
+ id TEXT PRIMARY KEY,
12114
+ display_name TEXT NOT NULL,
12115
+ description TEXT,
12116
+ endpoint TEXT,
12117
+ transport TEXT NOT NULL,
12118
+ fallback_endpoints TEXT NOT NULL DEFAULT '[]',
12119
+ auth_type TEXT NOT NULL,
12120
+ auth_metadata TEXT NOT NULL DEFAULT '{}',
12121
+ scopes TEXT NOT NULL DEFAULT '[]',
12122
+ token_mode TEXT NOT NULL DEFAULT 'none',
12123
+ install_fallback TEXT NOT NULL DEFAULT '{}',
12124
+ docs_url TEXT,
12125
+ safety TEXT NOT NULL DEFAULT '{}',
12126
+ provenance TEXT NOT NULL DEFAULT '{"source":"manual"}',
12127
+ enabled INTEGER NOT NULL DEFAULT 1,
12128
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
12129
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
12130
+ )
12131
+ `);
12132
+ try {
12133
+ db.exec("ALTER TABLE provider_profiles ADD COLUMN fallback_endpoints TEXT NOT NULL DEFAULT '[]'");
12134
+ } catch {}
12135
+ try {
12136
+ db.exec("ALTER TABLE provider_profiles ADD COLUMN auth_metadata TEXT NOT NULL DEFAULT '{}'");
12137
+ } catch {}
12138
+ db.exec("CREATE INDEX IF NOT EXISTS idx_provider_profiles_enabled ON provider_profiles(enabled)");
12139
+ const providerProfileCount = db.query("SELECT COUNT(*) as c FROM provider_profiles").get().c;
12140
+ if (providerProfileCount === 0) {
12141
+ const insertProviderProfile = db.prepare(`
12142
+ INSERT OR IGNORE INTO provider_profiles (
12143
+ id, display_name, description, endpoint, transport, fallback_endpoints,
12144
+ auth_type, auth_metadata, scopes, token_mode, install_fallback,
12145
+ docs_url, safety, provenance, enabled
12146
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
12147
+ `);
12148
+ const run = db.transaction(() => {
12149
+ for (const profile of DEFAULT_PROVIDER_PROFILE_SEEDS) {
12150
+ insertProviderProfile.run(profile.id, profile.displayName, profile.description ?? null, profile.endpoint ?? null, profile.transport, JSON.stringify(profile.fallbackEndpoints ?? []), profile.authType, JSON.stringify(profile.authMetadata ?? {}), JSON.stringify(profile.scopes ?? []), profile.tokenMode ?? "none", JSON.stringify(profile.installFallback ?? null), profile.docsUrl ?? null, JSON.stringify(profile.safety ?? {}), JSON.stringify(profile.provenance), profile.enabled === false ? 0 : 1);
12151
+ }
12152
+ });
12153
+ run();
12154
+ }
12032
12155
  db.exec(`
12033
12156
  CREATE TABLE IF NOT EXISTS feedback (
12034
12157
  id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
@@ -12059,6 +12182,7 @@ var db = null, _adapter = null;
12059
12182
  var init_db = __esm(() => {
12060
12183
  init_dist();
12061
12184
  init_config2();
12185
+ init_provider_profile_seeds();
12062
12186
  });
12063
12187
 
12064
12188
  // node_modules/ajv/dist/compile/codegen/code.js
@@ -19288,12 +19412,12 @@ var init_sources = __esm(() => {
19288
19412
  var require_package = __commonJS((exports, module) => {
19289
19413
  module.exports = {
19290
19414
  name: "@hasna/mcps",
19291
- version: "0.0.13",
19415
+ version: "0.0.15",
19292
19416
  description: "Meta-MCP registry & CLI \u2014 discover, manage, and proxy MCP servers",
19293
19417
  type: "module",
19294
19418
  repository: {
19295
19419
  type: "git",
19296
- url: "https://github.com/hasna/mcps.git"
19420
+ url: "git+https://github.com/hasna/mcps.git"
19297
19421
  },
19298
19422
  main: "dist/index.js",
19299
19423
  types: "dist/index.d.ts",
@@ -35467,6 +35591,185 @@ class StreamableHTTPClientTransport {
35467
35591
  // src/lib/proxy.ts
35468
35592
  init_config2();
35469
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
35470
35773
  var connections = new Map;
35471
35774
  var inflightConnections = new Map;
35472
35775
  var CONNECT_CONCURRENCY = 4;
@@ -35493,7 +35796,7 @@ function requireUrl(entry) {
35493
35796
  throw new Error(`Server "${entry.id}" has an invalid URL: ${entry.url}`);
35494
35797
  }
35495
35798
  }
35496
- async function connectToServer(entry) {
35799
+ async function connectToServer(entry, options = {}) {
35497
35800
  if (connections.has(entry.id)) {
35498
35801
  return connections.get(entry.id);
35499
35802
  }
@@ -35509,6 +35812,13 @@ async function connectToServer(entry) {
35509
35812
  if (!entry.command?.trim()) {
35510
35813
  throw new Error(`Server "${entry.id}" is missing a command`);
35511
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);
35512
35822
  transport = new StdioClientTransport({
35513
35823
  command: entry.command,
35514
35824
  args: entry.args,
@@ -35625,7 +35935,7 @@ async function callTool(prefixedName, args) {
35625
35935
  ]
35626
35936
  };
35627
35937
  }
35628
- async function connectAllEnabled() {
35938
+ async function connectAllEnabled(options = {}) {
35629
35939
  const servers = listServers().filter((s) => s.enabled);
35630
35940
  const results = [];
35631
35941
  let index = 0;
@@ -35637,7 +35947,7 @@ async function connectAllEnabled() {
35637
35947
  return;
35638
35948
  const server = servers[current];
35639
35949
  try {
35640
- const conn = await connectToServer(server);
35950
+ const conn = await connectToServer(server, options);
35641
35951
  results.push(conn);
35642
35952
  } catch (err) {
35643
35953
  console.error(`Failed to connect to ${server.name}: ${err.message}`);
@@ -35649,13 +35959,28 @@ async function connectAllEnabled() {
35649
35959
  }
35650
35960
 
35651
35961
  // src/lib/doctor.ts
35652
- async function diagnoseServer(server) {
35962
+ async function diagnoseServer(server, options = {}) {
35653
35963
  const checks4 = [];
35964
+ let hasLocalConsent = true;
35654
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
+ }
35655
35978
  try {
35656
35979
  const path = execFileSync("which", [server.command], { stdio: "pipe" }).toString().trim();
35657
35980
  let version2 = "";
35658
35981
  try {
35982
+ if (!hasLocalConsent)
35983
+ throw new Error("local stdio command approval is required before version probing");
35659
35984
  version2 = execFileSync(server.command, ["--version"], { stdio: "pipe" }).toString().trim().split(`
35660
35985
  `)[0];
35661
35986
  } catch {}
@@ -35686,10 +36011,10 @@ async function diagnoseServer(server) {
35686
36011
  checks4.push({ name: "URL reachable", pass: false, message: `unreachable: ${err.message}` });
35687
36012
  }
35688
36013
  }
35689
- if (server.enabled) {
36014
+ if (server.enabled && hasLocalConsent) {
35690
36015
  try {
35691
36016
  await Promise.race([
35692
- connectToServer(server),
36017
+ connectToServer(server, { localCommandConsent: options.localCommandConsent }),
35693
36018
  new Promise((_, reject) => setTimeout(() => reject(new Error("timeout after 10s")), 1e4))
35694
36019
  ]);
35695
36020
  await disconnectServer(server.id);
@@ -35697,8 +36022,10 @@ async function diagnoseServer(server) {
35697
36022
  } catch (err) {
35698
36023
  checks4.push({ name: "connect & list tools", pass: false, message: err.message });
35699
36024
  }
35700
- } else {
36025
+ } else if (!server.enabled) {
35701
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" });
35702
36029
  }
35703
36030
  return {
35704
36031
  server,
@@ -35739,7 +36066,7 @@ async function getRegistryServer(id) {
35739
36066
  const all = entries.map(parseRegistryEntry);
35740
36067
  return all.find((s) => s.id === id) || null;
35741
36068
  }
35742
- async function installFromRegistry(id) {
36069
+ async function installFromRegistry(id, options = {}) {
35743
36070
  const server = await getRegistryServer(id);
35744
36071
  if (!server) {
35745
36072
  throw new Error(`Server "${id}" not found in registry`);
@@ -35759,6 +36086,13 @@ async function installFromRegistry(id) {
35759
36086
  transport = pkg.transport.type;
35760
36087
  }
35761
36088
  }
36089
+ assertLocalCommandConsent({
36090
+ command,
36091
+ args,
36092
+ transport,
36093
+ env: {},
36094
+ operation: "register"
36095
+ }, options.localCommandConsent);
35762
36096
  return addServer({
35763
36097
  name: server.name,
35764
36098
  description: server.description,
@@ -35853,7 +36187,22 @@ function installToGemini(entry) {
35853
36187
  return { agent: "gemini", success: false, error: err.message };
35854
36188
  }
35855
36189
  }
35856
- 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
+ }
35857
36206
  return targets.map((target) => {
35858
36207
  if (target === "claude")
35859
36208
  return installToClaude(entry);
@@ -36767,6 +37116,112 @@ async function runFleetInstall(options = {}, dependencies = {}) {
36767
37116
  }));
36768
37117
  }
36769
37118
 
37119
+ // src/lib/provider-profiles.ts
37120
+ init_db();
37121
+ init_provider_profile_seeds();
37122
+ var TRANSPORTS = new Set(["stdio", "sse", "streamable-http"]);
37123
+ var AUTH_TYPES = new Set(["none", "oauth2", "api_key", "bearer_token", "custom"]);
37124
+ var TOKEN_MODES = new Set(["none", "user", "workspace", "service"]);
37125
+ var BEARER_TOKEN_MODES = new Set(["none", "optional", "required"]);
37126
+ var PROVENANCE_SOURCES = new Set(["curated", "official-registry", "npm", "github", "manual"]);
37127
+ function safeJsonParse2(value, fallback) {
37128
+ if (typeof value !== "string")
37129
+ return fallback;
37130
+ try {
37131
+ return JSON.parse(value);
37132
+ } catch {
37133
+ return fallback;
37134
+ }
37135
+ }
37136
+ function normalizeId(id) {
37137
+ const normalized = id.trim().toLowerCase();
37138
+ if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(normalized)) {
37139
+ throw new Error("Provider profile id must be lowercase kebab-case");
37140
+ }
37141
+ return normalized;
37142
+ }
37143
+ function parseRow3(row) {
37144
+ const installFallback = safeJsonParse2(row.install_fallback, null);
37145
+ return {
37146
+ id: row.id,
37147
+ displayName: row.display_name,
37148
+ description: row.description || null,
37149
+ endpoint: row.endpoint || null,
37150
+ transport: row.transport,
37151
+ fallbackEndpoints: safeJsonParse2(row.fallback_endpoints, []),
37152
+ authType: row.auth_type,
37153
+ authMetadata: safeJsonParse2(row.auth_metadata, {}),
37154
+ scopes: safeJsonParse2(row.scopes, []),
37155
+ tokenMode: row.token_mode,
37156
+ installFallback,
37157
+ docsUrl: row.docs_url || null,
37158
+ safety: safeJsonParse2(row.safety, {}),
37159
+ provenance: safeJsonParse2(row.provenance, { source: "manual" }),
37160
+ enabled: row.enabled === 1 || row.enabled === true,
37161
+ created_at: row.created_at,
37162
+ updated_at: row.updated_at
37163
+ };
37164
+ }
37165
+ function listProviderProfiles(options = {}) {
37166
+ const db2 = getDb();
37167
+ const sql = options.enabledOnly ? "SELECT * FROM provider_profiles WHERE enabled = 1 ORDER BY display_name" : "SELECT * FROM provider_profiles ORDER BY display_name";
37168
+ return db2.prepare(sql).all().map(parseRow3);
37169
+ }
37170
+ function searchProviderProfiles(query, options = {}) {
37171
+ const normalizedQuery = query.trim().toLowerCase();
37172
+ if (!normalizedQuery)
37173
+ return listProviderProfiles(options);
37174
+ return listProviderProfiles(options).filter((profile) => {
37175
+ const searchable = [
37176
+ profile.id,
37177
+ profile.displayName,
37178
+ profile.description ?? "",
37179
+ profile.endpoint ?? "",
37180
+ profile.docsUrl ?? "",
37181
+ profile.provenance.sourceUrl ?? "",
37182
+ profile.provenance.packageName ?? ""
37183
+ ].join(`
37184
+ `).toLowerCase();
37185
+ return searchable.includes(normalizedQuery);
37186
+ });
37187
+ }
37188
+ function getProviderProfile(id) {
37189
+ const db2 = getDb();
37190
+ const row = db2.prepare("SELECT * FROM provider_profiles WHERE id = ?").get(normalizeId(id));
37191
+ return row ? parseRow3(row) : null;
37192
+ }
37193
+ function installProviderProfile(id, options = {}) {
37194
+ const profile = getProviderProfile(id);
37195
+ if (!profile)
37196
+ throw new Error(`Provider profile "${id}" not found`);
37197
+ if (!profile.enabled)
37198
+ throw new Error(`Provider profile "${id}" is disabled`);
37199
+ const fallback = profile.installFallback;
37200
+ const useFallback = options.useFallback || !profile.endpoint;
37201
+ const command = useFallback ? fallback?.command : fallback?.command ?? "npx";
37202
+ const args = useFallback ? fallback?.args ?? [] : fallback?.args ?? [];
37203
+ if (!command) {
37204
+ throw new Error(`Provider profile "${id}" does not define an install fallback command`);
37205
+ }
37206
+ assertLocalCommandConsent({
37207
+ command,
37208
+ args,
37209
+ env: fallback?.env ?? {},
37210
+ transport: useFallback ? "stdio" : profile.transport,
37211
+ operation: "register"
37212
+ }, options.localCommandConsent);
37213
+ return addServer({
37214
+ name: options.name ?? profile.displayName,
37215
+ description: profile.description ?? undefined,
37216
+ command,
37217
+ args,
37218
+ env: fallback?.env,
37219
+ transport: useFallback ? "stdio" : profile.transport,
37220
+ url: useFallback ? fallback?.url : profile.endpoint ?? undefined,
37221
+ source: "provider-profile"
37222
+ });
37223
+ }
37224
+
36770
37225
  // src/cli/index.tsx
36771
37226
  import * as readline from "readline";
36772
37227
 
@@ -38035,6 +38490,13 @@ function jsonContent(value) {
38035
38490
  function errorContent(text) {
38036
38491
  return { ...textContent(text), isError: true };
38037
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
+ }
38038
38500
  function buildMcpTools() {
38039
38501
  const definitions = [
38040
38502
  {
@@ -38059,23 +38521,46 @@ function buildMcpTools() {
38059
38521
  description: exports_external2.string().optional().describe("Description"),
38060
38522
  transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("Transport type"),
38061
38523
  url: exports_external2.string().optional().describe("URL for remote transports"),
38062
- 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")
38063
38527
  },
38064
- run: ({ command, args, name, description, transport, url: url2, env }) => jsonContent(addServer({
38065
- command: String(command),
38066
- args: Array.isArray(args) ? args.map(String) : [],
38067
- name: typeof name === "string" ? name : undefined,
38068
- description: typeof description === "string" ? description : undefined,
38069
- transport,
38070
- url: typeof url2 === "string" ? url2 : undefined,
38071
- env: isRecordOfStrings(env) ? env : {}
38072
- }))
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
+ }
38073
38548
  },
38074
38549
  {
38075
38550
  name: "install_from_registry",
38076
38551
  description: "Install an MCP server from the official registry",
38077
- paramsSchema: { id: exports_external2.string().describe("Registry server ID") },
38078
- run: async ({ id }) => jsonContent(await installFromRegistry(String(id)))
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
+ }
38079
38564
  },
38080
38565
  {
38081
38566
  name: "remove_server",
@@ -38121,26 +38606,41 @@ function buildMcpTools() {
38121
38606
  command: exports_external2.string().optional().describe("New command"),
38122
38607
  args: exports_external2.array(exports_external2.string()).optional().describe("New args list"),
38123
38608
  transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("New transport type"),
38124
- 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")
38125
38612
  },
38126
- run: ({ id, name, description, command, args, transport, url: url2 }) => {
38127
- const serverId = String(id);
38613
+ run: (input) => {
38614
+ const serverId = String(input.id);
38128
38615
  const existing = getServer(serverId);
38129
38616
  if (!existing)
38130
38617
  return errorContent(`Server "${serverId}" not found.`);
38131
38618
  const fields = {};
38132
- if (typeof name === "string")
38133
- fields.name = name;
38134
- if (typeof description === "string")
38135
- fields.description = description;
38136
- if (typeof command === "string")
38137
- fields.command = command;
38138
- if (Array.isArray(args))
38139
- fields.args = args.map(String);
38140
- if (transport === "stdio" || transport === "sse" || transport === "streamable-http")
38141
- fields.transport = transport;
38142
- if (typeof url2 === "string")
38143
- fields.url = url2;
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
+ }
38144
38644
  return jsonContent(redactServerEnv(updateServer(serverId, fields)));
38145
38645
  }
38146
38646
  },
@@ -38186,6 +38686,58 @@ function buildMcpTools() {
38186
38686
  limit: typeof limit === "number" ? limit : undefined
38187
38687
  }))
38188
38688
  },
38689
+ {
38690
+ name: "list_provider_profiles",
38691
+ description: "List curated provider profiles for hosted/common MCP integrations such as Notion and Linear.",
38692
+ paramsSchema: {
38693
+ enabled_only: exports_external2.boolean().optional().describe("Only include enabled provider profiles")
38694
+ },
38695
+ run: ({ enabled_only }) => jsonContent(listProviderProfiles({ enabledOnly: enabled_only === true }))
38696
+ },
38697
+ {
38698
+ name: "search_provider_profiles",
38699
+ description: "Search curated provider profiles separately from raw MCP registry/source search.",
38700
+ paramsSchema: {
38701
+ query: exports_external2.string().describe("Search query such as 'notion', 'linear', or an endpoint URL"),
38702
+ enabled_only: exports_external2.boolean().optional().describe("Only include enabled provider profiles")
38703
+ },
38704
+ run: ({ query, enabled_only }) => jsonContent(searchProviderProfiles(String(query), { enabledOnly: enabled_only === true }))
38705
+ },
38706
+ {
38707
+ name: "get_provider_profile",
38708
+ description: "Get one curated provider profile by ID.",
38709
+ paramsSchema: {
38710
+ id: exports_external2.string().describe("Provider profile ID")
38711
+ },
38712
+ run: ({ id }) => {
38713
+ const profile = getProviderProfile(String(id));
38714
+ if (!profile)
38715
+ return errorContent(`Provider profile "${String(id)}" not found.`);
38716
+ return jsonContent(profile);
38717
+ }
38718
+ },
38719
+ {
38720
+ name: "install_provider_profile",
38721
+ description: "Register a curated provider profile as an MCP server.",
38722
+ paramsSchema: {
38723
+ id: exports_external2.string().describe("Provider profile ID"),
38724
+ name: exports_external2.string().optional().describe("Override registered server name"),
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")
38728
+ },
38729
+ run: (input) => {
38730
+ try {
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)
38735
+ })));
38736
+ } catch (err) {
38737
+ return errorContent(err.message);
38738
+ }
38739
+ }
38740
+ },
38189
38741
  {
38190
38742
  name: "list_sources",
38191
38743
  description: "List all configured search sources for finding MCP servers",
@@ -38252,15 +38804,19 @@ function buildMcpTools() {
38252
38804
  description: "Install a registered MCP server into Claude Code, Codex, and/or Gemini",
38253
38805
  paramsSchema: {
38254
38806
  id: exports_external2.string().describe("Server ID to install (from list_servers)"),
38255
- 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")
38256
38810
  },
38257
- run: ({ id, targets }) => {
38258
- const serverId = String(id);
38811
+ run: (input) => {
38812
+ const serverId = String(input.id);
38259
38813
  const entry = getServer(serverId);
38260
38814
  if (!entry)
38261
38815
  return errorContent(`Server "${serverId}" not found.`);
38262
- const agentTargets = Array.isArray(targets) ? targets : undefined;
38263
- 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
+ }));
38264
38820
  }
38265
38821
  },
38266
38822
  {
@@ -38272,11 +38828,14 @@ function buildMcpTools() {
38272
38828
  {
38273
38829
  name: "connect_and_list_tools",
38274
38830
  description: "Connect to all enabled MCP servers and list their available tools",
38275
- paramsSchema: {},
38276
- run: async () => {
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) => {
38277
38836
  let liveTools = [];
38278
38837
  try {
38279
- await connectAllEnabled();
38838
+ await connectAllEnabled({ localCommandConsent: localConsent(input) });
38280
38839
  liveTools = listAllTools();
38281
38840
  } finally {
38282
38841
  await disconnectAll().catch(() => {
@@ -38291,11 +38850,13 @@ function buildMcpTools() {
38291
38850
  description: `Call a tool on a connected upstream MCP server. Tool name format: server_id${TOOL_PREFIX_SEPARATOR}tool_name`,
38292
38851
  paramsSchema: {
38293
38852
  tool_name: exports_external2.string().describe(`Prefixed tool name (server_id${TOOL_PREFIX_SEPARATOR}tool_name)`),
38294
- 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")
38295
38856
  },
38296
- run: async ({ tool_name, arguments: args }) => {
38857
+ run: async (input) => {
38297
38858
  try {
38298
- const toolName = String(tool_name);
38859
+ const toolName = String(input.tool_name);
38299
38860
  const sepIdx = toolName.indexOf(TOOL_PREFIX_SEPARATOR);
38300
38861
  if (sepIdx === -1)
38301
38862
  return errorContent(`Error: Invalid tool name "${toolName}"`);
@@ -38305,8 +38866,8 @@ function buildMcpTools() {
38305
38866
  return errorContent(`Error: Server "${serverId}" not found.`);
38306
38867
  if (!entry.enabled)
38307
38868
  return errorContent(`Error: Server "${serverId}" is disabled.`);
38308
- await connectToServer(entry);
38309
- const result = await callTool(toolName, readRecord(args));
38869
+ await connectToServer(entry, { localCommandConsent: localConsent(input) });
38870
+ const result = await callTool(toolName, readRecord(input.arguments));
38310
38871
  return { content: result.content };
38311
38872
  } catch (error2) {
38312
38873
  return errorContent(`Error: ${error2.message}`);
@@ -38316,13 +38877,17 @@ function buildMcpTools() {
38316
38877
  {
38317
38878
  name: "diagnose_server",
38318
38879
  description: "Run health checks on a registered MCP server",
38319
- paramsSchema: { id: exports_external2.string().describe("Server ID") },
38320
- run: async ({ id }) => {
38321
- const serverId = String(id);
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);
38322
38887
  const entry = getServer(serverId);
38323
38888
  if (!entry)
38324
38889
  return errorContent(`Server "${serverId}" not found.`);
38325
- return jsonContent(await diagnoseServer(entry));
38890
+ return jsonContent(await diagnoseServer(entry, { localCommandConsent: localConsent(input) }));
38326
38891
  }
38327
38892
  },
38328
38893
  {
@@ -38691,6 +39256,20 @@ function isValidId(id) {
38691
39256
  return /^[a-z0-9-]+$/.test(id);
38692
39257
  }
38693
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
+ }
38694
39273
  function isLoopbackHost(hostname3) {
38695
39274
  return hostname3 === "127.0.0.1" || hostname3 === "localhost" || hostname3 === "::1";
38696
39275
  }
@@ -38837,6 +39416,11 @@ Dashboard not found at: ${dashboardDir}`);
38837
39416
  return json({ error: "Invalid 'args' format" }, 400, port);
38838
39417
  }
38839
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
+ }
38840
39424
  const entry = addServer({
38841
39425
  name: body.name,
38842
39426
  command,
@@ -38935,6 +39519,19 @@ Dashboard not found at: ${dashboardDir}`);
38935
39519
  }
38936
39520
  fields.args = body.args;
38937
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
+ }
38938
39535
  const updated = updateServer(id, fields);
38939
39536
  return json(redactServer(updated), 200, port);
38940
39537
  } catch (e) {
@@ -39010,7 +39607,7 @@ Dashboard not found at: ${dashboardDir}`);
39010
39607
  }
39011
39608
  if (!body.tool || typeof body.tool !== "string")
39012
39609
  return json({ error: "Missing 'tool'" }, 400, port);
39013
- await connectToServer(entry);
39610
+ await connectToServer(entry, { localCommandConsent: consentFromInput(body) });
39014
39611
  const toolName = `${id}__${body.tool}`;
39015
39612
  const result = await callTool(toolName, body.args || {});
39016
39613
  await disconnectServer(id).catch(() => {
@@ -39021,6 +39618,9 @@ Dashboard not found at: ${dashboardDir}`);
39021
39618
  await disconnectServer(id).catch(() => {
39022
39619
  return;
39023
39620
  });
39621
+ if (e instanceof LocalCommandConsentError) {
39622
+ return json({ error: e.message }, 400, port);
39623
+ }
39024
39624
  return json({ error: e instanceof Error ? e.message : "Failed to call tool" }, 500, port);
39025
39625
  }
39026
39626
  }
@@ -39033,7 +39633,7 @@ Dashboard not found at: ${dashboardDir}`);
39033
39633
  if (!entry)
39034
39634
  return json({ error: `Server '${id}' not found` }, 404, port);
39035
39635
  try {
39036
- const report = await diagnoseServer(entry);
39636
+ const report = await diagnoseServer(entry, { localCommandConsent: consentFromSearchParams(url2.searchParams) });
39037
39637
  return json(report, 200, port);
39038
39638
  } catch (e) {
39039
39639
  return json({ error: e instanceof Error ? e.message : "Failed to diagnose server" }, 500, port);
@@ -39688,6 +40288,13 @@ function ServerDetail({ server, onSelectTool, onBack }) {
39688
40288
  const [tools2, setTools] = useState3([]);
39689
40289
  const [loading, setLoading] = useState3(false);
39690
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
+ });
39691
40298
  const cachedTools = getCachedTools(server.id);
39692
40299
  const cachedKey = cachedTools.map((t) => `${t.name}|${t.description}|${JSON.stringify(t.input_schema)}`).join(";");
39693
40300
  useEffect3(() => {
@@ -39708,7 +40315,9 @@ function ServerDetail({ server, onSelectTool, onBack }) {
39708
40315
  setLoading(true);
39709
40316
  setError(null);
39710
40317
  try {
39711
- const conn = await connectToServer(server);
40318
+ const conn = await connectToServer(server, {
40319
+ localCommandConsent: { approved: true, source: "tui" }
40320
+ });
39712
40321
  setTools(conn.tools);
39713
40322
  } catch (err) {
39714
40323
  setError(err.message);
@@ -39767,6 +40376,21 @@ function ServerDetail({ server, onSelectTool, onBack }) {
39767
40376
  server.args.join(" ")
39768
40377
  ]
39769
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),
39770
40394
  server.description && /* @__PURE__ */ jsxDEV2(Text5, {
39771
40395
  dimColor: true,
39772
40396
  children: server.description
@@ -39935,7 +40559,9 @@ function SearchView({ onBack }) {
39935
40559
  setInstalling(item.value);
39936
40560
  setMessage(null);
39937
40561
  try {
39938
- const server = await installFromRegistry(item.value);
40562
+ const server = await installFromRegistry(item.value, {
40563
+ localCommandConsent: { approved: true, source: "tui" }
40564
+ });
39939
40565
  setMessage(`Installed: ${server.name} [${server.id}]`);
39940
40566
  } catch (err) {
39941
40567
  setMessage(`Install failed: ${err.message}`);
@@ -40046,7 +40672,9 @@ function ToolCall({ server, tool, onBack }) {
40046
40672
  setError(null);
40047
40673
  setResult(null);
40048
40674
  try {
40049
- await connectToServer(server);
40675
+ await connectToServer(server, {
40676
+ localCommandConsent: { approved: true, source: "tui" }
40677
+ });
40050
40678
  const prefixed = `${server.id}${TOOL_PREFIX_SEPARATOR}${tool.name}`;
40051
40679
  const res = await callTool(prefixed, args);
40052
40680
  const text = res.content.map((c) => c.text).join(`
@@ -40266,6 +40894,46 @@ var MACHINE_PLATFORMS = ["linux", "darwin", "unknown"];
40266
40894
  var MACHINE_ARCHES = ["arm64", "x64", "unknown"];
40267
40895
  var MACHINE_INSTALLERS = ["auto", "bun", "npm"];
40268
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
+ }
40919
+ function printProviderProfile(profile) {
40920
+ const status = profile.enabled ? chalk2.green("enabled") : chalk2.red("disabled");
40921
+ console.log(` ${chalk2.bold(profile.displayName)} ${chalk2.dim(`[${profile.id}]`)} \u2014 ${chalk2.dim(profile.transport)} \u2014 ${status}`);
40922
+ if (profile.description)
40923
+ console.log(` ${chalk2.dim(profile.description)}`);
40924
+ if (profile.endpoint)
40925
+ console.log(` ${chalk2.cyan(profile.endpoint)}`);
40926
+ if (profile.authMetadata.bearerToken === "optional") {
40927
+ console.log(` ${chalk2.dim("Auth: OAuth with optional bearer token/API key support")}`);
40928
+ } else if (profile.authMetadata.pkce || profile.authMetadata.dynamicClientRegistration) {
40929
+ const parts = [
40930
+ profile.authMetadata.oauthVersion ? `OAuth ${profile.authMetadata.oauthVersion}` : "OAuth",
40931
+ profile.authMetadata.pkce ? "PKCE" : null,
40932
+ profile.authMetadata.dynamicClientRegistration ? "dynamic client registration" : null
40933
+ ].filter(Boolean);
40934
+ console.log(` ${chalk2.dim(`Auth: ${parts.join(", ")}`)}`);
40935
+ }
40936
+ }
40269
40937
  function printJson(value) {
40270
40938
  console.log(JSON.stringify(value, null, 2));
40271
40939
  }
@@ -40438,6 +41106,86 @@ ${results.length} result(s). Use \`mcps add --from-registry <id>\` to install.`)
40438
41106
  closeDb();
40439
41107
  }
40440
41108
  });
41109
+ var providersCmd = program2.command("providers").description("Discover and install curated MCP provider profiles");
41110
+ providersCmd.command("list").description("List curated provider profiles").option("--json", "Output as JSON").option("--enabled-only", "Only include enabled profiles").action((opts) => {
41111
+ const profiles = listProviderProfiles({ enabledOnly: opts.enabledOnly === true });
41112
+ if (opts.json) {
41113
+ printJson(profiles);
41114
+ closeDb();
41115
+ return;
41116
+ }
41117
+ if (profiles.length === 0) {
41118
+ console.log(chalk2.dim("No curated provider profiles available."));
41119
+ closeDb();
41120
+ return;
41121
+ }
41122
+ for (const profile of profiles)
41123
+ printProviderProfile(profile);
41124
+ closeDb();
41125
+ });
41126
+ providersCmd.command("search").argument("<query>", "Search query").description("Search curated provider profiles").option("--json", "Output as JSON").option("--enabled-only", "Only include enabled profiles").action((query, opts) => {
41127
+ const profiles = searchProviderProfiles(query, { enabledOnly: opts.enabledOnly === true });
41128
+ if (opts.json) {
41129
+ printJson(profiles);
41130
+ closeDb();
41131
+ return;
41132
+ }
41133
+ if (profiles.length === 0) {
41134
+ console.log(chalk2.dim("No curated provider profiles found."));
41135
+ closeDb();
41136
+ return;
41137
+ }
41138
+ for (const profile of profiles)
41139
+ printProviderProfile(profile);
41140
+ console.log(chalk2.dim(`
41141
+ ${profiles.length} provider profile(s). Use \`mcps providers install <id>\` to register one.`));
41142
+ closeDb();
41143
+ });
41144
+ providersCmd.command("info").argument("<id>", "Provider profile ID").description("Show a curated provider profile").option("--json", "Output as JSON").action((id, opts) => {
41145
+ const profile = getProviderProfile(id);
41146
+ if (!profile) {
41147
+ console.error(chalk2.red(`Provider profile "${id}" not found.`));
41148
+ closeDb();
41149
+ process.exit(1);
41150
+ }
41151
+ if (opts.json) {
41152
+ printJson(profile);
41153
+ closeDb();
41154
+ return;
41155
+ }
41156
+ printProviderProfile(profile);
41157
+ if (profile.fallbackEndpoints.length > 0) {
41158
+ console.log(chalk2.bold(" Fallback endpoints:"));
41159
+ for (const fallback of profile.fallbackEndpoints) {
41160
+ console.log(` ${fallback.transport}: ${chalk2.cyan(fallback.url)}`);
41161
+ }
41162
+ }
41163
+ if (profile.docsUrl)
41164
+ console.log(` Docs: ${chalk2.cyan(profile.docsUrl)}`);
41165
+ closeDb();
41166
+ });
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) => {
41168
+ try {
41169
+ const server = installProviderProfile(id, {
41170
+ name: opts.name,
41171
+ useFallback: opts.fallback === true,
41172
+ localCommandConsent: localConsentFromOptions(opts)
41173
+ });
41174
+ if (opts.json) {
41175
+ printJson(server);
41176
+ } else {
41177
+ console.log(chalk2.green(`Installed provider profile: ${server.name} [${server.id}]`));
41178
+ console.log(chalk2.dim(` Transport: ${server.transport}`));
41179
+ if (server.url)
41180
+ console.log(chalk2.dim(` URL: ${server.url}`));
41181
+ }
41182
+ closeDb();
41183
+ } catch (err) {
41184
+ console.error(chalk2.red(`Failed to install provider profile: ${err.message}`));
41185
+ closeDb();
41186
+ process.exit(1);
41187
+ }
41188
+ });
40441
41189
  async function promptReadline(rl, question) {
40442
41190
  return new Promise((resolve2) => rl.question(question, resolve2));
40443
41191
  }
@@ -40452,11 +41200,13 @@ function detectSourceType(url2) {
40452
41200
  return "mcp-registry";
40453
41201
  return null;
40454
41202
  }
40455
- 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) => {
40456
41204
  try {
40457
41205
  if (opts.fromRegistry) {
40458
41206
  console.log(chalk2.dim(`Installing "${opts.fromRegistry}" from registry...`));
40459
- const server2 = await installFromRegistry(opts.fromRegistry);
41207
+ const server2 = await installFromRegistry(opts.fromRegistry, {
41208
+ localCommandConsent: localConsentFromOptions(opts)
41209
+ });
40460
41210
  console.log(chalk2.green(`Added server: ${server2.name} [${server2.id}]`));
40461
41211
  console.log(chalk2.dim(` ${server2.command} ${server2.args.join(" ")}`));
40462
41212
  closeDb();
@@ -40495,6 +41245,13 @@ Server to add:`));
40495
41245
  console.log(` Name: ${wizardName}`);
40496
41246
  if (Object.keys(env).length)
40497
41247
  console.log(` Env: ${Object.keys(env).join(", ")}`);
41248
+ printLocalCommandReviewIfNeeded({
41249
+ command: wizardCommand,
41250
+ args: wizardArgs,
41251
+ env,
41252
+ transport,
41253
+ operation: "register"
41254
+ });
40498
41255
  const confirm = await new Promise((resolve2) => {
40499
41256
  const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
40500
41257
  rl2.question(chalk2.bold("Add this server? [Y/n]: "), (ans) => {
@@ -40507,6 +41264,13 @@ Server to add:`));
40507
41264
  closeDb();
40508
41265
  return;
40509
41266
  }
41267
+ assertLocalCommandConsent({
41268
+ command: wizardCommand,
41269
+ args: wizardArgs,
41270
+ env,
41271
+ transport,
41272
+ operation: "register"
41273
+ }, localConsentFromOptions(opts, true));
40510
41274
  const server2 = addServer({
40511
41275
  command: wizardCommand,
40512
41276
  args: wizardArgs,
@@ -40543,6 +41307,13 @@ Server to add:`));
40543
41307
  envMap[key] = rest.join("=");
40544
41308
  }
40545
41309
  }
41310
+ assertCliLocalCommandConsent({
41311
+ command,
41312
+ args,
41313
+ env: envMap,
41314
+ transport: opts.transport,
41315
+ operation: "register"
41316
+ }, opts);
40546
41317
  const server = addServer({
40547
41318
  name: opts.name,
40548
41319
  description: opts.description,
@@ -40565,29 +41336,44 @@ Server to add:`));
40565
41336
  }
40566
41337
  closeDb();
40567
41338
  });
40568
- 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) => {
40569
- const server = getServer(id);
40570
- if (!server) {
40571
- console.error(chalk2.red(`Server "${id}" not found.`));
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}`));
40572
41374
  closeDb();
40573
41375
  process.exit(1);
40574
41376
  }
40575
- const fields = {};
40576
- if (opts.name !== undefined)
40577
- fields.name = opts.name;
40578
- if (opts.description !== undefined)
40579
- fields.description = opts.description;
40580
- if (opts.command !== undefined)
40581
- fields.command = opts.command;
40582
- if (opts.args !== undefined)
40583
- fields.args = opts.args;
40584
- if (opts.transport !== undefined)
40585
- fields.transport = opts.transport;
40586
- if (opts.url !== undefined)
40587
- fields.url = opts.url;
40588
- const updated = updateServer(id, fields);
40589
- console.log(chalk2.green(`Updated server: ${updated.name} [${updated.id}]`));
40590
- closeDb();
40591
41377
  });
40592
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) => {
40593
41379
  try {
@@ -40634,10 +41420,10 @@ program2.command("disable").argument("<id>", "Server ID to disable").description
40634
41420
  console.log(chalk2.yellow(`Disabled server: ${server.name}`));
40635
41421
  closeDb();
40636
41422
  });
40637
- 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) => {
40638
41424
  if (opts.connect) {
40639
41425
  console.log(chalk2.dim("Connecting to enabled servers..."));
40640
- await connectAllEnabled();
41426
+ await connectAllEnabled({ localCommandConsent: localConsentFromOptions(opts) });
40641
41427
  const tools2 = listAllTools();
40642
41428
  if (tools2.length === 0) {
40643
41429
  console.log(chalk2.dim("No tools available."));
@@ -40687,7 +41473,7 @@ ${total} tool(s) total.`));
40687
41473
  }
40688
41474
  closeDb();
40689
41475
  });
40690
- 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) => {
40691
41477
  let args = {};
40692
41478
  if (opts.json) {
40693
41479
  try {
@@ -40710,7 +41496,7 @@ program2.command("call").argument("<tool>", "Tool name (server_id__tool_name)").
40710
41496
  let exitCode = 0;
40711
41497
  try {
40712
41498
  console.log(chalk2.dim(`Connecting to servers...`));
40713
- await connectAllEnabled();
41499
+ await connectAllEnabled({ localCommandConsent: localConsentFromOptions(opts) });
40714
41500
  console.log(chalk2.dim(`Calling ${tool}...`));
40715
41501
  const result = await callTool(tool, args);
40716
41502
  for (const c of result.content) {
@@ -40778,7 +41564,7 @@ program2.command("status").description("Show registry stats").option("--json", "
40778
41564
  console.log(` Tools: ${totalTools} (cached)`);
40779
41565
  closeDb();
40780
41566
  });
40781
- 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) => {
40782
41568
  const { execFileSync: execFileSync22 } = await import("child_process");
40783
41569
  const servers = id ? [getServer(id)].filter(Boolean) : listServers();
40784
41570
  if (servers.length === 0) {
@@ -40790,7 +41576,7 @@ program2.command("doctor").argument("[id]", "Server ID to check (omit to check a
40790
41576
  for (const server of servers) {
40791
41577
  console.log(chalk2.bold(`
40792
41578
  ${server.name} [${server.id}]`));
40793
- const report = await diagnoseServer(server);
41579
+ const report = await diagnoseServer(server, { localCommandConsent: localConsentFromOptions(opts) });
40794
41580
  for (const check2 of report.checks) {
40795
41581
  const icon = check2.pass ? chalk2.green("\u2713") : chalk2.red("\u2717");
40796
41582
  console.log(` ${icon} ${check2.name}: ${chalk2.dim(check2.message)}`);
@@ -40816,7 +41602,7 @@ ${server.name} [${server.id}]`));
40816
41602
  closeDb();
40817
41603
  });
40818
41604
  program2.command("completion").argument("<shell>", "Shell type: bash, zsh, fish").description("Generate shell completion script").action((shell) => {
40819
- const commands = ["list", "search", "find", "add", "remove", "enable", "disable", "info", "status", "tools", "call", "doctor", "install", "machines", "fleet", "export", "import", "env", "sources", "clone", "update-server", "serve", "update", "mcp", "completion"];
41605
+ const commands = ["list", "search", "providers", "find", "add", "remove", "enable", "disable", "info", "status", "tools", "call", "doctor", "install", "machines", "fleet", "export", "import", "env", "sources", "clone", "update-server", "serve", "update", "mcp", "completion"];
40820
41606
  if (shell === "bash") {
40821
41607
  console.log(`# Add to ~/.bashrc: eval "$(mcps completion bash)"
40822
41608
  _mcps_complete() {
@@ -40876,7 +41662,7 @@ program2.command("update").description("Update mcps to the latest version").acti
40876
41662
  process.exit(1);
40877
41663
  }
40878
41664
  });
40879
- 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) => {
40880
41666
  try {
40881
41667
  if (opts.awesome) {
40882
41668
  console.log(chalk2.dim("Fetching curated awesome-mcp-servers list..."));
@@ -40986,6 +41772,14 @@ Enter number to install (1-${results.length}), or 0 to cancel: `), resolve2);
40986
41772
  return;
40987
41773
  }
40988
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);
40989
41783
  const server = addServer({
40990
41784
  command: "npx",
40991
41785
  args: ["-y", pkg],
@@ -40993,7 +41787,7 @@ Enter number to install (1-${results.length}), or 0 to cancel: `), resolve2);
40993
41787
  description: chosen.description,
40994
41788
  transport: "stdio"
40995
41789
  });
40996
- const results2 = installToAgents(server, ["claude", "codex", "gemini"]);
41790
+ const results2 = installToAgents(server, ["claude", "codex", "gemini"], { localCommandConsent });
40997
41791
  for (const r of results2) {
40998
41792
  if (r.success) {
40999
41793
  console.log(chalk2.green(` \u2713 ${r.agent}`));
@@ -41138,7 +41932,7 @@ sourcesCmd.command("test").argument("<id>", "Source ID to test").description("Te
41138
41932
  }
41139
41933
  closeDb();
41140
41934
  });
41141
- 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) => {
41142
41936
  const targets = [];
41143
41937
  if (opts.to) {
41144
41938
  for (const t of opts.to) {
@@ -41164,7 +41958,9 @@ program2.command("install").argument("[id]", "Server ID (from `mcps list`) to in
41164
41958
  if (opts.fromRegistry) {
41165
41959
  console.log(chalk2.dim(`Installing "${opts.fromRegistry}" from registry...`));
41166
41960
  try {
41167
- server = await installFromRegistry(opts.fromRegistry);
41961
+ server = await installFromRegistry(opts.fromRegistry, {
41962
+ localCommandConsent: localConsentFromOptions(opts)
41963
+ });
41168
41964
  console.log(chalk2.green(`Added server: ${server.name} [${server.id}]`));
41169
41965
  } catch (err) {
41170
41966
  console.error(chalk2.red(`Failed to install from registry: ${err.message}`));
@@ -41173,6 +41969,13 @@ program2.command("install").argument("[id]", "Server ID (from `mcps list`) to in
41173
41969
  }
41174
41970
  } else if (opts.npm) {
41175
41971
  const pkg = opts.npm;
41972
+ assertCliLocalCommandConsent({
41973
+ command: "npx",
41974
+ args: ["-y", pkg],
41975
+ env: {},
41976
+ transport: "stdio",
41977
+ operation: "install"
41978
+ }, opts);
41176
41979
  server = addServer({ command: "npx", args: ["-y", pkg], name: pkg, transport: "stdio" });
41177
41980
  console.log(chalk2.green(`Added server: ${server.name} [${server.id}]`));
41178
41981
  } else {
@@ -41189,7 +41992,7 @@ program2.command("install").argument("[id]", "Server ID (from `mcps list`) to in
41189
41992
  }
41190
41993
  }
41191
41994
  console.log(chalk2.dim(`Installing "${server.name}" to: ${targets.join(", ")}...`));
41192
- const results = installToAgents(server, targets);
41995
+ const results = installToAgents(server, targets, { localCommandConsent: localConsentFromOptions(opts) });
41193
41996
  for (const r of results) {
41194
41997
  if (r.success) {
41195
41998
  console.log(chalk2.green(` \u2713 ${r.agent}`));