@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/dist/index.js CHANGED
@@ -9451,6 +9451,85 @@ var init_config2 = __esm(() => {
9451
9451
  DB_PATH = process.env.HASNA_MCPS_DB_PATH ?? process.env.MCPS_DB_PATH ?? join5(MCPS_DIR, "registry.db");
9452
9452
  });
9453
9453
 
9454
+ // src/lib/provider-profile-seeds.ts
9455
+ var DEFAULT_PROVIDER_PROFILE_SEEDS;
9456
+ var init_provider_profile_seeds = __esm(() => {
9457
+ DEFAULT_PROVIDER_PROFILE_SEEDS = [
9458
+ {
9459
+ id: "notion",
9460
+ displayName: "Notion",
9461
+ description: "Connect a Notion workspace so agents can search, read, create, and update workspace content.",
9462
+ endpoint: "https://mcp.notion.com/mcp",
9463
+ transport: "streamable-http",
9464
+ fallbackEndpoints: [
9465
+ {
9466
+ transport: "sse",
9467
+ url: "https://mcp.notion.com/sse",
9468
+ notes: "Fallback for clients that do not support Streamable HTTP."
9469
+ }
9470
+ ],
9471
+ authType: "oauth2",
9472
+ authMetadata: {
9473
+ oauthVersion: "2.0",
9474
+ pkce: true,
9475
+ dynamicClientRegistration: true,
9476
+ bearerToken: "none",
9477
+ notes: "Remote Notion MCP uses OAuth with PKCE. Bearer-token authentication is only appropriate for self-hosted/local fallback deployments."
9478
+ },
9479
+ tokenMode: "workspace",
9480
+ installFallback: {
9481
+ command: "npx",
9482
+ args: ["-y", "mcp-remote", "https://mcp.notion.com/sse", "--transport", "sse-only"],
9483
+ packageName: "mcp-remote",
9484
+ url: "https://mcp.notion.com/sse"
9485
+ },
9486
+ docsUrl: "https://developers.notion.com/guides/mcp/build-mcp-client",
9487
+ safety: {
9488
+ requiresApproval: true,
9489
+ dataClasses: ["workspace_content", "pages", "databases", "comments"],
9490
+ notes: "Connected agents operate with the authorizing user's workspace access. Human confirmation is recommended for write-capable workflows."
9491
+ },
9492
+ provenance: {
9493
+ source: "curated",
9494
+ sourceUrl: "https://developers.notion.com/guides/mcp/build-mcp-client",
9495
+ verifiedAt: "2026-05-10"
9496
+ }
9497
+ },
9498
+ {
9499
+ id: "linear",
9500
+ displayName: "Linear",
9501
+ description: "Connect Linear so agents can find, create, and update issues, projects, comments, and related workspace objects.",
9502
+ endpoint: "https://mcp.linear.app/mcp",
9503
+ transport: "streamable-http",
9504
+ authType: "oauth2",
9505
+ authMetadata: {
9506
+ oauthVersion: "2.1",
9507
+ dynamicClientRegistration: true,
9508
+ bearerToken: "optional",
9509
+ notes: "Linear supports interactive OAuth 2.1 with dynamic client registration and optional Authorization: Bearer tokens for OAuth tokens or API keys."
9510
+ },
9511
+ tokenMode: "workspace",
9512
+ installFallback: {
9513
+ command: "npx",
9514
+ args: ["-y", "mcp-remote", "https://mcp.linear.app/mcp"],
9515
+ packageName: "mcp-remote",
9516
+ url: "https://mcp.linear.app/mcp"
9517
+ },
9518
+ docsUrl: "https://linear.app/docs/mcp",
9519
+ safety: {
9520
+ requiresApproval: true,
9521
+ dataClasses: ["issues", "projects", "comments", "teams", "users"],
9522
+ notes: "Linear tools can create and update workspace objects, so write actions should be policy-gated by the platform."
9523
+ },
9524
+ provenance: {
9525
+ source: "curated",
9526
+ sourceUrl: "https://linear.app/docs/mcp",
9527
+ verifiedAt: "2026-05-10"
9528
+ }
9529
+ }
9530
+ ];
9531
+ });
9532
+
9454
9533
  // src/lib/db.ts
9455
9534
  import { mkdirSync as mkdirSync5 } from "fs";
9456
9535
  function getDb() {
@@ -9536,6 +9615,50 @@ function getDb() {
9536
9615
  ('github-mcp-topic', 'GitHub MCP Topic', 'github-topic', 'https://api.github.com/search/repositories', 'GitHub repositories tagged with mcp-server topic')
9537
9616
  `);
9538
9617
  }
9618
+ db.exec(`
9619
+ CREATE TABLE IF NOT EXISTS provider_profiles (
9620
+ id TEXT PRIMARY KEY,
9621
+ display_name TEXT NOT NULL,
9622
+ description TEXT,
9623
+ endpoint TEXT,
9624
+ transport TEXT NOT NULL,
9625
+ fallback_endpoints TEXT NOT NULL DEFAULT '[]',
9626
+ auth_type TEXT NOT NULL,
9627
+ auth_metadata TEXT NOT NULL DEFAULT '{}',
9628
+ scopes TEXT NOT NULL DEFAULT '[]',
9629
+ token_mode TEXT NOT NULL DEFAULT 'none',
9630
+ install_fallback TEXT NOT NULL DEFAULT '{}',
9631
+ docs_url TEXT,
9632
+ safety TEXT NOT NULL DEFAULT '{}',
9633
+ provenance TEXT NOT NULL DEFAULT '{"source":"manual"}',
9634
+ enabled INTEGER NOT NULL DEFAULT 1,
9635
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
9636
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
9637
+ )
9638
+ `);
9639
+ try {
9640
+ db.exec("ALTER TABLE provider_profiles ADD COLUMN fallback_endpoints TEXT NOT NULL DEFAULT '[]'");
9641
+ } catch {}
9642
+ try {
9643
+ db.exec("ALTER TABLE provider_profiles ADD COLUMN auth_metadata TEXT NOT NULL DEFAULT '{}'");
9644
+ } catch {}
9645
+ db.exec("CREATE INDEX IF NOT EXISTS idx_provider_profiles_enabled ON provider_profiles(enabled)");
9646
+ const providerProfileCount = db.query("SELECT COUNT(*) as c FROM provider_profiles").get().c;
9647
+ if (providerProfileCount === 0) {
9648
+ const insertProviderProfile = db.prepare(`
9649
+ INSERT OR IGNORE INTO provider_profiles (
9650
+ id, display_name, description, endpoint, transport, fallback_endpoints,
9651
+ auth_type, auth_metadata, scopes, token_mode, install_fallback,
9652
+ docs_url, safety, provenance, enabled
9653
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9654
+ `);
9655
+ const run = db.transaction(() => {
9656
+ for (const profile of DEFAULT_PROVIDER_PROFILE_SEEDS) {
9657
+ 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);
9658
+ }
9659
+ });
9660
+ run();
9661
+ }
9539
9662
  db.exec(`
9540
9663
  CREATE TABLE IF NOT EXISTS feedback (
9541
9664
  id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
@@ -9560,6 +9683,7 @@ var db = null, _adapter = null;
9560
9683
  var init_db = __esm(() => {
9561
9684
  init_dist();
9562
9685
  init_config2();
9686
+ init_provider_profile_seeds();
9563
9687
  });
9564
9688
 
9565
9689
  // node_modules/ajv/dist/compile/codegen/code.js
@@ -25118,6 +25242,185 @@ class StreamableHTTPClientTransport {
25118
25242
  // src/lib/proxy.ts
25119
25243
  init_config2();
25120
25244
  init_db();
25245
+
25246
+ // src/lib/local-command-consent.ts
25247
+ class LocalCommandConsentError extends Error {
25248
+ review;
25249
+ constructor(message, review) {
25250
+ super(message);
25251
+ this.name = "LocalCommandConsentError";
25252
+ this.review = review;
25253
+ }
25254
+ }
25255
+ var SHELL_COMMANDS = new Set(["bash", "sh", "zsh", "fish", "cmd", "cmd.exe", "powershell", "powershell.exe", "pwsh"]);
25256
+ var DESTRUCTIVE_COMMANDS = new Set([
25257
+ "rm",
25258
+ "dd",
25259
+ "mkfs",
25260
+ "shutdown",
25261
+ "reboot",
25262
+ "poweroff",
25263
+ "halt",
25264
+ "killall",
25265
+ "pkill"
25266
+ ]);
25267
+ var SHELL_EVAL_FLAGS = new Set(["-c", "/c", "-Command", "-command", "-EncodedCommand", "-encodedcommand"]);
25268
+ var SECRET_FLAG_PATTERN = /(?:^|[-_])(api[-_]?key|token|secret|password|passwd|credential|auth|private[-_]?key)(?:$|[-_])/i;
25269
+ var SECRET_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
25270
+ 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_-]+)$/;
25271
+ var SHELL_META_PATTERN = /[;&|`<>]|\$\(/;
25272
+ function commandBase(command) {
25273
+ return command.trim().split(/[\\/]/).pop()?.toLowerCase() || command.trim().toLowerCase();
25274
+ }
25275
+ function normalizeArgs(args) {
25276
+ return (args ?? []).map((arg) => String(arg));
25277
+ }
25278
+ function isSecretKey(key) {
25279
+ return SECRET_KEY_PATTERN.test(key) || SECRET_FLAG_PATTERN.test(key);
25280
+ }
25281
+ function isSecretValue(value) {
25282
+ return SECRET_VALUE_PATTERN.test(value.trim());
25283
+ }
25284
+ function isSecretAssignment(arg) {
25285
+ const eqIdx = arg.indexOf("=");
25286
+ if (eqIdx <= 0)
25287
+ return false;
25288
+ const key = arg.slice(0, eqIdx);
25289
+ const value = arg.slice(eqIdx + 1);
25290
+ return isSecretKey(key) || isSecretValue(value);
25291
+ }
25292
+ function isSecretArg(args, index) {
25293
+ const arg = args[index] ?? "";
25294
+ const previous = args[index - 1] ?? "";
25295
+ return isSecretAssignment(arg) || isSecretValue(arg) || SECRET_FLAG_PATTERN.test(previous);
25296
+ }
25297
+ function quoteArg(value) {
25298
+ return JSON.stringify(value);
25299
+ }
25300
+ function displayCommand(command, args) {
25301
+ return [quoteArg(command), ...args.map((arg, index) => quoteArg(isSecretArg(args, index) ? "<redacted>" : arg))].join(" ");
25302
+ }
25303
+ function pushRisk(risks, risk) {
25304
+ if (risks.some((existing) => existing.code === risk.code && existing.evidence === risk.evidence))
25305
+ return;
25306
+ risks.push(risk);
25307
+ }
25308
+ function inspectRisks(command, args, env) {
25309
+ const risks = [];
25310
+ const base = commandBase(command);
25311
+ const joined = [command, ...args].join(" ");
25312
+ if (SHELL_COMMANDS.has(base)) {
25313
+ pushRisk(risks, {
25314
+ code: "shell_interpreter",
25315
+ severity: "warning",
25316
+ message: "Command launches a shell interpreter.",
25317
+ evidence: base
25318
+ });
25319
+ if (args.some((arg) => SHELL_EVAL_FLAGS.has(arg))) {
25320
+ pushRisk(risks, {
25321
+ code: "shell_eval",
25322
+ severity: "danger",
25323
+ message: "Shell command evaluates an inline script.",
25324
+ evidence: base
25325
+ });
25326
+ }
25327
+ }
25328
+ if (base === "sudo") {
25329
+ pushRisk(risks, {
25330
+ code: "privilege_escalation",
25331
+ severity: "danger",
25332
+ message: "Command requests elevated privileges.",
25333
+ evidence: base
25334
+ });
25335
+ }
25336
+ if (DESTRUCTIVE_COMMANDS.has(base) || /\brm\s+-[^\s]*[rf][^\s]*\b/.test(joined) || /--no-preserve-root\b/.test(joined)) {
25337
+ pushRisk(risks, {
25338
+ code: "destructive_command",
25339
+ severity: "danger",
25340
+ message: "Command includes a destructive system operation.",
25341
+ evidence: base
25342
+ });
25343
+ }
25344
+ if (/\b(curl|wget)\b[\s\S]*\|[\s\S]*\b(sh|bash|zsh|fish)\b/.test(joined)) {
25345
+ pushRisk(risks, {
25346
+ code: "download_pipe_shell",
25347
+ severity: "danger",
25348
+ message: "Command downloads remote content and pipes it to a shell."
25349
+ });
25350
+ }
25351
+ if ([command, ...args].some((part) => SHELL_META_PATTERN.test(part))) {
25352
+ pushRisk(risks, {
25353
+ code: "shell_metacharacters",
25354
+ severity: "warning",
25355
+ message: "Command or arguments contain shell metacharacters."
25356
+ });
25357
+ }
25358
+ if (args.some((arg, index) => isSecretArg(args, index))) {
25359
+ pushRisk(risks, {
25360
+ code: "inline_secret",
25361
+ severity: "danger",
25362
+ message: "Command arguments appear to contain inline secret material."
25363
+ });
25364
+ }
25365
+ const secretEnvKeys = Object.keys(env).filter(isSecretKey).sort();
25366
+ if (secretEnvKeys.length > 0) {
25367
+ pushRisk(risks, {
25368
+ code: "secret_env",
25369
+ severity: "warning",
25370
+ message: "Environment contains secret-like keys; values are redacted from consent output.",
25371
+ evidence: secretEnvKeys.join(", ")
25372
+ });
25373
+ }
25374
+ return risks;
25375
+ }
25376
+ function inspectLocalCommand(input) {
25377
+ const args = normalizeArgs(input.args);
25378
+ const env = input.env ?? {};
25379
+ const transport = input.transport ?? "stdio";
25380
+ const risks = inspectRisks(input.command, args, env);
25381
+ return {
25382
+ requiresConsent: transport === "stdio",
25383
+ operation: input.operation ?? "launch",
25384
+ command: input.command,
25385
+ args,
25386
+ displayCommand: displayCommand(input.command, args),
25387
+ envKeys: Object.keys(env).sort(),
25388
+ risks,
25389
+ hasDangerousRisk: risks.some((risk) => risk.severity === "danger")
25390
+ };
25391
+ }
25392
+ function formatLocalCommandReview(review) {
25393
+ const lines = [
25394
+ `Command: ${review.displayCommand}`,
25395
+ review.envKeys.length > 0 ? `Env keys: ${review.envKeys.join(", ")}` : "Env keys: <none>"
25396
+ ];
25397
+ if (review.risks.length > 0) {
25398
+ lines.push("Risks:");
25399
+ for (const risk of review.risks) {
25400
+ lines.push(`- ${risk.severity}: ${risk.code} - ${risk.message}${risk.evidence ? ` (${risk.evidence})` : ""}`);
25401
+ }
25402
+ } else {
25403
+ lines.push("Risks: none detected");
25404
+ }
25405
+ return lines.join(`
25406
+ `);
25407
+ }
25408
+ function assertLocalCommandConsent(input, consent = {}) {
25409
+ const review = inspectLocalCommand(input);
25410
+ if (!review.requiresConsent)
25411
+ return review;
25412
+ if (consent.approved !== true) {
25413
+ throw new LocalCommandConsentError(`local stdio command approval is required before ${review.operation}.
25414
+ ${formatLocalCommandReview(review)}`, review);
25415
+ }
25416
+ if (review.hasDangerousRisk && consent.allowRisky !== true) {
25417
+ throw new LocalCommandConsentError(`risky command approval is required before ${review.operation}.
25418
+ ${formatLocalCommandReview(review)}`, review);
25419
+ }
25420
+ return review;
25421
+ }
25422
+
25423
+ // src/lib/proxy.ts
25121
25424
  var connections = new Map;
25122
25425
  var inflightConnections = new Map;
25123
25426
  function buildEnv(extra) {
@@ -25143,7 +25446,7 @@ function requireUrl(entry) {
25143
25446
  throw new Error(`Server "${entry.id}" has an invalid URL: ${entry.url}`);
25144
25447
  }
25145
25448
  }
25146
- async function connectToServer(entry) {
25449
+ async function connectToServer(entry, options = {}) {
25147
25450
  if (connections.has(entry.id)) {
25148
25451
  return connections.get(entry.id);
25149
25452
  }
@@ -25159,6 +25462,13 @@ async function connectToServer(entry) {
25159
25462
  if (!entry.command?.trim()) {
25160
25463
  throw new Error(`Server "${entry.id}" is missing a command`);
25161
25464
  }
25465
+ assertLocalCommandConsent({
25466
+ command: entry.command,
25467
+ args: entry.args,
25468
+ env: entry.env,
25469
+ transport: entry.transport,
25470
+ operation: "launch"
25471
+ }, options.localCommandConsent);
25162
25472
  transport = new StdioClientTransport({
25163
25473
  command: entry.command,
25164
25474
  args: entry.args,
@@ -25294,13 +25604,28 @@ async function refreshTools(id) {
25294
25604
  }
25295
25605
 
25296
25606
  // src/lib/doctor.ts
25297
- async function diagnoseServer(server) {
25607
+ async function diagnoseServer(server, options = {}) {
25298
25608
  const checks3 = [];
25609
+ let hasLocalConsent = true;
25299
25610
  if (server.transport === "stdio") {
25611
+ try {
25612
+ assertLocalCommandConsent({
25613
+ command: server.command,
25614
+ args: server.args,
25615
+ env: server.env,
25616
+ transport: server.transport,
25617
+ operation: "diagnose"
25618
+ }, options.localCommandConsent);
25619
+ } catch (err) {
25620
+ hasLocalConsent = false;
25621
+ checks3.push({ name: "local command consent", pass: false, message: err.message });
25622
+ }
25300
25623
  try {
25301
25624
  const path = execFileSync("which", [server.command], { stdio: "pipe" }).toString().trim();
25302
25625
  let version2 = "";
25303
25626
  try {
25627
+ if (!hasLocalConsent)
25628
+ throw new Error("local stdio command approval is required before version probing");
25304
25629
  version2 = execFileSync(server.command, ["--version"], { stdio: "pipe" }).toString().trim().split(`
25305
25630
  `)[0];
25306
25631
  } catch {}
@@ -25331,10 +25656,10 @@ async function diagnoseServer(server) {
25331
25656
  checks3.push({ name: "URL reachable", pass: false, message: `unreachable: ${err.message}` });
25332
25657
  }
25333
25658
  }
25334
- if (server.enabled) {
25659
+ if (server.enabled && hasLocalConsent) {
25335
25660
  try {
25336
25661
  await Promise.race([
25337
- connectToServer(server),
25662
+ connectToServer(server, { localCommandConsent: options.localCommandConsent }),
25338
25663
  new Promise((_, reject) => setTimeout(() => reject(new Error("timeout after 10s")), 1e4))
25339
25664
  ]);
25340
25665
  await disconnectServer(server.id);
@@ -25342,8 +25667,10 @@ async function diagnoseServer(server) {
25342
25667
  } catch (err) {
25343
25668
  checks3.push({ name: "connect & list tools", pass: false, message: err.message });
25344
25669
  }
25345
- } else {
25670
+ } else if (!server.enabled) {
25346
25671
  checks3.push({ name: "connect & list tools", pass: true, message: "skipped (server disabled)" });
25672
+ } else {
25673
+ checks3.push({ name: "connect & list tools", pass: false, message: "skipped until local stdio command is approved" });
25347
25674
  }
25348
25675
  return {
25349
25676
  server,
@@ -25383,7 +25710,7 @@ async function getRegistryServer(id) {
25383
25710
  const all = entries.map(parseRegistryEntry);
25384
25711
  return all.find((s) => s.id === id) || null;
25385
25712
  }
25386
- async function installFromRegistry(id) {
25713
+ async function installFromRegistry(id, options = {}) {
25387
25714
  const server = await getRegistryServer(id);
25388
25715
  if (!server) {
25389
25716
  throw new Error(`Server "${id}" not found in registry`);
@@ -25403,6 +25730,13 @@ async function installFromRegistry(id) {
25403
25730
  transport = pkg.transport.type;
25404
25731
  }
25405
25732
  }
25733
+ assertLocalCommandConsent({
25734
+ command,
25735
+ args,
25736
+ transport,
25737
+ env: {},
25738
+ operation: "register"
25739
+ }, options.localCommandConsent);
25406
25740
  return addServer({
25407
25741
  name: server.name,
25408
25742
  description: server.description,
@@ -25425,6 +25759,250 @@ async function listAwesomeServers() {
25425
25759
  // src/index.ts
25426
25760
  init_sources();
25427
25761
 
25762
+ // src/lib/provider-profiles.ts
25763
+ init_db();
25764
+ init_provider_profile_seeds();
25765
+ var TRANSPORTS = new Set(["stdio", "sse", "streamable-http"]);
25766
+ var AUTH_TYPES = new Set(["none", "oauth2", "api_key", "bearer_token", "custom"]);
25767
+ var TOKEN_MODES = new Set(["none", "user", "workspace", "service"]);
25768
+ var BEARER_TOKEN_MODES = new Set(["none", "optional", "required"]);
25769
+ var PROVENANCE_SOURCES = new Set(["curated", "official-registry", "npm", "github", "manual"]);
25770
+ function safeJsonParse2(value, fallback) {
25771
+ if (typeof value !== "string")
25772
+ return fallback;
25773
+ try {
25774
+ return JSON.parse(value);
25775
+ } catch {
25776
+ return fallback;
25777
+ }
25778
+ }
25779
+ function normalizeString(value) {
25780
+ const trimmed = value?.trim();
25781
+ return trimmed ? trimmed : null;
25782
+ }
25783
+ function normalizeId(id) {
25784
+ const normalized = id.trim().toLowerCase();
25785
+ if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(normalized)) {
25786
+ throw new Error("Provider profile id must be lowercase kebab-case");
25787
+ }
25788
+ return normalized;
25789
+ }
25790
+ function normalizeScopes(scopes) {
25791
+ const seen = new Set;
25792
+ const normalized = [];
25793
+ for (const scope of scopes ?? []) {
25794
+ const trimmed = scope.trim();
25795
+ if (!trimmed || seen.has(trimmed))
25796
+ continue;
25797
+ seen.add(trimmed);
25798
+ normalized.push(trimmed);
25799
+ }
25800
+ return normalized;
25801
+ }
25802
+ function assertKnown(value, allowed, label) {
25803
+ if (!allowed.has(value))
25804
+ throw new Error(`Unknown provider profile ${label}: ${value}`);
25805
+ return value;
25806
+ }
25807
+ function normalizeProvenance(provenance) {
25808
+ const source = assertKnown(provenance.source, PROVENANCE_SOURCES, "provenance source");
25809
+ return {
25810
+ source,
25811
+ sourceUrl: normalizeString(provenance.sourceUrl) ?? undefined,
25812
+ repositoryUrl: normalizeString(provenance.repositoryUrl) ?? undefined,
25813
+ packageName: normalizeString(provenance.packageName) ?? undefined,
25814
+ verifiedAt: normalizeString(provenance.verifiedAt) ?? undefined
25815
+ };
25816
+ }
25817
+ function normalizeInstallFallback(fallback) {
25818
+ if (!fallback)
25819
+ return null;
25820
+ const normalized = {
25821
+ command: normalizeString(fallback.command) ?? undefined,
25822
+ args: Array.isArray(fallback.args) ? fallback.args.map((arg) => arg.trim()).filter(Boolean) : undefined,
25823
+ env: fallback.env && Object.keys(fallback.env).length > 0 ? fallback.env : undefined,
25824
+ packageName: normalizeString(fallback.packageName) ?? undefined,
25825
+ registryId: normalizeString(fallback.registryId) ?? undefined,
25826
+ url: normalizeString(fallback.url) ?? undefined
25827
+ };
25828
+ return Object.values(normalized).some((value) => value !== undefined) ? normalized : null;
25829
+ }
25830
+ function normalizeFallbackEndpoints(fallbacks) {
25831
+ const seen = new Set;
25832
+ const normalized = [];
25833
+ for (const fallback of fallbacks ?? []) {
25834
+ const transport = assertKnown(fallback.transport, TRANSPORTS, "fallback transport");
25835
+ const url2 = normalizeString(fallback.url);
25836
+ if (!url2)
25837
+ continue;
25838
+ const key = `${transport}:${url2}`;
25839
+ if (seen.has(key))
25840
+ continue;
25841
+ seen.add(key);
25842
+ normalized.push({
25843
+ transport,
25844
+ url: url2,
25845
+ notes: normalizeString(fallback.notes) ?? undefined
25846
+ });
25847
+ }
25848
+ return normalized;
25849
+ }
25850
+ function normalizeAuthMetadata(authMetadata) {
25851
+ const bearerToken = authMetadata?.bearerToken ? assertKnown(authMetadata.bearerToken, BEARER_TOKEN_MODES, "bearer token mode") : undefined;
25852
+ return {
25853
+ oauthVersion: authMetadata?.oauthVersion,
25854
+ pkce: authMetadata?.pkce,
25855
+ dynamicClientRegistration: authMetadata?.dynamicClientRegistration,
25856
+ bearerToken,
25857
+ notes: normalizeString(authMetadata?.notes) ?? undefined
25858
+ };
25859
+ }
25860
+ function parseRow2(row) {
25861
+ const installFallback = safeJsonParse2(row.install_fallback, null);
25862
+ return {
25863
+ id: row.id,
25864
+ displayName: row.display_name,
25865
+ description: row.description || null,
25866
+ endpoint: row.endpoint || null,
25867
+ transport: row.transport,
25868
+ fallbackEndpoints: safeJsonParse2(row.fallback_endpoints, []),
25869
+ authType: row.auth_type,
25870
+ authMetadata: safeJsonParse2(row.auth_metadata, {}),
25871
+ scopes: safeJsonParse2(row.scopes, []),
25872
+ tokenMode: row.token_mode,
25873
+ installFallback,
25874
+ docsUrl: row.docs_url || null,
25875
+ safety: safeJsonParse2(row.safety, {}),
25876
+ provenance: safeJsonParse2(row.provenance, { source: "manual" }),
25877
+ enabled: row.enabled === 1 || row.enabled === true,
25878
+ created_at: row.created_at,
25879
+ updated_at: row.updated_at
25880
+ };
25881
+ }
25882
+ function upsertProviderProfile(opts) {
25883
+ const db2 = getDb();
25884
+ const id = normalizeId(opts.id);
25885
+ const displayName = normalizeString(opts.displayName);
25886
+ if (!displayName)
25887
+ throw new Error("Provider profile displayName is required");
25888
+ const transport = assertKnown(opts.transport, TRANSPORTS, "transport");
25889
+ const fallbackEndpoints = normalizeFallbackEndpoints(opts.fallbackEndpoints);
25890
+ const authType = assertKnown(opts.authType, AUTH_TYPES, "auth type");
25891
+ const authMetadata = normalizeAuthMetadata(opts.authMetadata);
25892
+ const tokenMode = assertKnown(opts.tokenMode ?? "none", TOKEN_MODES, "token mode");
25893
+ const scopes = normalizeScopes(opts.scopes);
25894
+ const installFallback = normalizeInstallFallback(opts.installFallback);
25895
+ const provenance = normalizeProvenance(opts.provenance);
25896
+ const safety = opts.safety ?? {};
25897
+ const enabled = opts.enabled === false ? 0 : 1;
25898
+ const row = db2.prepare(`INSERT INTO provider_profiles (
25899
+ id, display_name, description, endpoint, transport, fallback_endpoints, auth_type, auth_metadata, scopes,
25900
+ token_mode, install_fallback, docs_url, safety, provenance, enabled
25901
+ )
25902
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
25903
+ ON CONFLICT(id) DO UPDATE SET
25904
+ display_name = excluded.display_name,
25905
+ description = excluded.description,
25906
+ endpoint = excluded.endpoint,
25907
+ transport = excluded.transport,
25908
+ fallback_endpoints = excluded.fallback_endpoints,
25909
+ auth_type = excluded.auth_type,
25910
+ auth_metadata = excluded.auth_metadata,
25911
+ scopes = excluded.scopes,
25912
+ token_mode = excluded.token_mode,
25913
+ install_fallback = excluded.install_fallback,
25914
+ docs_url = excluded.docs_url,
25915
+ safety = excluded.safety,
25916
+ provenance = excluded.provenance,
25917
+ enabled = excluded.enabled,
25918
+ updated_at = datetime('now')
25919
+ RETURNING *`).get(id, displayName, normalizeString(opts.description), normalizeString(opts.endpoint), transport, JSON.stringify(fallbackEndpoints), authType, JSON.stringify(authMetadata), JSON.stringify(scopes), tokenMode, JSON.stringify(installFallback), normalizeString(opts.docsUrl), JSON.stringify(safety), JSON.stringify(provenance), enabled);
25920
+ return parseRow2(row);
25921
+ }
25922
+ function listProviderProfiles(options = {}) {
25923
+ const db2 = getDb();
25924
+ const sql = options.enabledOnly ? "SELECT * FROM provider_profiles WHERE enabled = 1 ORDER BY display_name" : "SELECT * FROM provider_profiles ORDER BY display_name";
25925
+ return db2.prepare(sql).all().map(parseRow2);
25926
+ }
25927
+ function searchProviderProfiles(query, options = {}) {
25928
+ const normalizedQuery = query.trim().toLowerCase();
25929
+ if (!normalizedQuery)
25930
+ return listProviderProfiles(options);
25931
+ return listProviderProfiles(options).filter((profile) => {
25932
+ const searchable = [
25933
+ profile.id,
25934
+ profile.displayName,
25935
+ profile.description ?? "",
25936
+ profile.endpoint ?? "",
25937
+ profile.docsUrl ?? "",
25938
+ profile.provenance.sourceUrl ?? "",
25939
+ profile.provenance.packageName ?? ""
25940
+ ].join(`
25941
+ `).toLowerCase();
25942
+ return searchable.includes(normalizedQuery);
25943
+ });
25944
+ }
25945
+ function getProviderProfile(id) {
25946
+ const db2 = getDb();
25947
+ const row = db2.prepare("SELECT * FROM provider_profiles WHERE id = ?").get(normalizeId(id));
25948
+ return row ? parseRow2(row) : null;
25949
+ }
25950
+ function removeProviderProfile(id) {
25951
+ const db2 = getDb();
25952
+ db2.prepare("DELETE FROM provider_profiles WHERE id = ?").run(normalizeId(id));
25953
+ }
25954
+ function enableProviderProfile(id) {
25955
+ return setProviderProfileEnabled(id, true);
25956
+ }
25957
+ function disableProviderProfile(id) {
25958
+ return setProviderProfileEnabled(id, false);
25959
+ }
25960
+ function seedDefaultProviderProfiles() {
25961
+ return DEFAULT_PROVIDER_PROFILE_SEEDS.map((profile) => upsertProviderProfile(profile));
25962
+ }
25963
+ function installProviderProfile(id, options = {}) {
25964
+ const profile = getProviderProfile(id);
25965
+ if (!profile)
25966
+ throw new Error(`Provider profile "${id}" not found`);
25967
+ if (!profile.enabled)
25968
+ throw new Error(`Provider profile "${id}" is disabled`);
25969
+ const fallback = profile.installFallback;
25970
+ const useFallback = options.useFallback || !profile.endpoint;
25971
+ const command = useFallback ? fallback?.command : fallback?.command ?? "npx";
25972
+ const args = useFallback ? fallback?.args ?? [] : fallback?.args ?? [];
25973
+ if (!command) {
25974
+ throw new Error(`Provider profile "${id}" does not define an install fallback command`);
25975
+ }
25976
+ assertLocalCommandConsent({
25977
+ command,
25978
+ args,
25979
+ env: fallback?.env ?? {},
25980
+ transport: useFallback ? "stdio" : profile.transport,
25981
+ operation: "register"
25982
+ }, options.localCommandConsent);
25983
+ return addServer({
25984
+ name: options.name ?? profile.displayName,
25985
+ description: profile.description ?? undefined,
25986
+ command,
25987
+ args,
25988
+ env: fallback?.env,
25989
+ transport: useFallback ? "stdio" : profile.transport,
25990
+ url: useFallback ? fallback?.url : profile.endpoint ?? undefined,
25991
+ source: "provider-profile"
25992
+ });
25993
+ }
25994
+ function setProviderProfileEnabled(id, enabled) {
25995
+ const db2 = getDb();
25996
+ const row = db2.prepare("UPDATE provider_profiles SET enabled = ?, updated_at = datetime('now') WHERE id = ? RETURNING *").get(enabled ? 1 : 0, normalizeId(id));
25997
+ if (!row) {
25998
+ throw new Error(`Provider profile "${id}" not found`);
25999
+ }
26000
+ return parseRow2(row);
26001
+ }
26002
+
26003
+ // src/index.ts
26004
+ init_provider_profile_seeds();
26005
+
25428
26006
  // src/lib/install.ts
25429
26007
  import { execFileSync as execFileSync2 } from "child_process";
25430
26008
  import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync7 } from "fs";
@@ -25496,7 +26074,22 @@ function installToGemini(entry) {
25496
26074
  return { agent: "gemini", success: false, error: err.message };
25497
26075
  }
25498
26076
  }
25499
- function installToAgents(entry, targets = ["claude", "codex", "gemini"]) {
26077
+ function installToAgents(entry, targets = ["claude", "codex", "gemini"], options = {}) {
26078
+ try {
26079
+ assertLocalCommandConsent({
26080
+ command: entry.command,
26081
+ args: entry.args,
26082
+ env: entry.env,
26083
+ transport: entry.transport,
26084
+ operation: "install"
26085
+ }, options.localCommandConsent);
26086
+ } catch (err) {
26087
+ return targets.map((target) => ({
26088
+ agent: target,
26089
+ success: false,
26090
+ error: err.message
26091
+ }));
26092
+ }
25500
26093
  return targets.map((target) => {
25501
26094
  if (target === "claude")
25502
26095
  return installToClaude(entry);
@@ -25510,7 +26103,7 @@ function installToAgents(entry, targets = ["claude", "codex", "gemini"]) {
25510
26103
  // src/lib/machines.ts
25511
26104
  init_db();
25512
26105
  import { userInfo } from "os";
25513
- function parseRow2(row) {
26106
+ function parseRow3(row) {
25514
26107
  return {
25515
26108
  id: row.id,
25516
26109
  name: row.name,
@@ -25553,12 +26146,12 @@ function toSqlBool(value, fallback = true) {
25553
26146
  function listMachines() {
25554
26147
  const db2 = getDb();
25555
26148
  const rows = db2.prepare("SELECT * FROM machines ORDER BY name ASC").all();
25556
- return rows.map(parseRow2);
26149
+ return rows.map(parseRow3);
25557
26150
  }
25558
26151
  function getMachine(id) {
25559
26152
  const db2 = getDb();
25560
26153
  const row = db2.prepare("SELECT * FROM machines WHERE id = ?").get(id);
25561
- return row ? parseRow2(row) : null;
26154
+ return row ? parseRow3(row) : null;
25562
26155
  }
25563
26156
  function addMachine(opts) {
25564
26157
  const db2 = getDb();
@@ -25573,7 +26166,7 @@ function addMachine(opts) {
25573
26166
  id, name, host, username, port, platform, arch, bun_path, npm_path, installer, ssh_key_path, enabled
25574
26167
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
25575
26168
  RETURNING *`).get(id, name, host, normalizeText(opts.username) ?? resolveDefaultUser(), opts.port ?? 22, opts.platform ?? "unknown", opts.arch ?? "unknown", normalizeText(opts.bun_path) ?? null, normalizeText(opts.npm_path) ?? null, opts.installer ?? "auto", normalizeText(opts.ssh_key_path) ?? null, toSqlBool(opts.enabled, true));
25576
- return parseRow2(row);
26169
+ return parseRow3(row);
25577
26170
  }
25578
26171
  function upsertMachine(opts) {
25579
26172
  const db2 = getDb();
@@ -25601,7 +26194,7 @@ function upsertMachine(opts) {
25601
26194
  enabled = excluded.enabled,
25602
26195
  updated_at = datetime('now')
25603
26196
  RETURNING *`).get(id, name, host, normalizeText(opts.username) ?? resolveDefaultUser(), opts.port ?? 22, opts.platform ?? "unknown", opts.arch ?? "unknown", normalizeText(opts.bun_path) ?? null, normalizeText(opts.npm_path) ?? null, opts.installer ?? "auto", normalizeText(opts.ssh_key_path) ?? null, toSqlBool(opts.enabled, true));
25604
- return parseRow2(row);
26197
+ return parseRow3(row);
25605
26198
  }
25606
26199
  function updateMachine(id, updates) {
25607
26200
  const db2 = getDb();
@@ -25664,7 +26257,7 @@ function updateMachine(id, updates) {
25664
26257
  const row = db2.prepare(`UPDATE machines SET ${sets.join(", ")} WHERE id = ? RETURNING *`).get(...values);
25665
26258
  if (!row)
25666
26259
  throw new Error(`Machine "${id}" not found`);
25667
- return parseRow2(row);
26260
+ return parseRow3(row);
25668
26261
  }
25669
26262
  function removeMachine(id) {
25670
26263
  const db2 = getDb();
@@ -26431,49 +27024,63 @@ function readPackageVersion(moduleUrl, fallback = FALLBACK_VERSION) {
26431
27024
  // src/index.ts
26432
27025
  init_db();
26433
27026
  export {
27027
+ upsertProviderProfile,
26434
27028
  upsertMachine,
26435
27029
  updateServer,
26436
27030
  updateMachine,
26437
27031
  unsetServerEnv,
26438
27032
  setServerEnv,
27033
+ seedDefaultProviderProfiles,
26439
27034
  seedDefaultMachines,
26440
27035
  searchRegistry,
27036
+ searchProviderProfiles,
26441
27037
  runFleetInstall,
26442
27038
  runFleetHealthCheck,
26443
27039
  removeSource,
26444
27040
  removeServer,
27041
+ removeProviderProfile,
26445
27042
  removeMachine,
26446
27043
  refreshTools,
26447
27044
  readPackageVersion,
26448
27045
  listSources,
26449
27046
  listServers,
27047
+ listProviderProfiles,
26450
27048
  listMachines,
26451
27049
  listHasnaMcpCatalog,
26452
27050
  listAwesomeServers,
26453
27051
  listAllTools,
26454
27052
  installToAgents,
27053
+ installProviderProfile,
26455
27054
  installFromRegistry,
27055
+ inspectLocalCommand,
26456
27056
  getToolCounts,
26457
27057
  getSource,
26458
27058
  getServer,
26459
27059
  getRegistryServer,
27060
+ getProviderProfile,
26460
27061
  getMachine,
26461
27062
  getDb,
26462
27063
  getCachedTools,
27064
+ formatLocalCommandReview,
26463
27065
  findServers,
26464
27066
  enableSource,
26465
27067
  enableServer,
27068
+ enableProviderProfile,
26466
27069
  disconnectServer,
26467
27070
  disconnectAll,
26468
27071
  disableSource,
26469
27072
  disableServer,
27073
+ disableProviderProfile,
26470
27074
  diagnoseServer,
26471
27075
  connectToServer,
26472
27076
  closeDb,
26473
27077
  cloneServer,
26474
27078
  callTool,
27079
+ assertLocalCommandConsent,
26475
27080
  addSource,
26476
27081
  addServer,
26477
27082
  addMachine,
27083
+ LocalCommandConsentError,
27084
+ DEFAULT_PROVIDER_PROFILE_SEEDS,
26478
27085
  DEFAULT_MACHINE_SEEDS
26479
27086
  };