@hasna/mcps 0.0.15 → 0.0.16

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
@@ -9547,6 +9547,7 @@ function getDb() {
9547
9547
  command TEXT NOT NULL,
9548
9548
  args TEXT NOT NULL DEFAULT '[]',
9549
9549
  env TEXT NOT NULL DEFAULT '{}',
9550
+ credential_refs TEXT NOT NULL DEFAULT '{}',
9550
9551
  transport TEXT NOT NULL DEFAULT 'stdio',
9551
9552
  url TEXT,
9552
9553
  source TEXT NOT NULL DEFAULT 'local',
@@ -9573,6 +9574,9 @@ function getDb() {
9573
9574
  try {
9574
9575
  db.exec("ALTER TABLE servers ADD COLUMN last_error TEXT");
9575
9576
  } catch {}
9577
+ try {
9578
+ db.exec("ALTER TABLE servers ADD COLUMN credential_refs TEXT NOT NULL DEFAULT '{}'");
9579
+ } catch {}
9576
9580
  db.exec(`
9577
9581
  CREATE TABLE IF NOT EXISTS sources (
9578
9582
  id TEXT PRIMARY KEY,
@@ -16627,17 +16631,17 @@ __export(exports_sources, {
16627
16631
  clearCache: () => clearCache,
16628
16632
  addSource: () => addSource
16629
16633
  });
16630
- import { mkdirSync as mkdirSync6, existsSync as existsSync6, readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync as readdirSync3, unlinkSync } from "fs";
16631
- import { join as join7 } from "path";
16634
+ import { mkdirSync as mkdirSync6, existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync as readdirSync3, unlinkSync } from "fs";
16635
+ import { join as join8 } from "path";
16632
16636
  function getCacheFile(sourceId) {
16633
- return join7(CACHE_DIR, `${sourceId}.json`);
16637
+ return join8(CACHE_DIR, `${sourceId}.json`);
16634
16638
  }
16635
16639
  function readCache(sourceId) {
16636
16640
  try {
16637
16641
  const file = getCacheFile(sourceId);
16638
- if (!existsSync6(file))
16642
+ if (!existsSync7(file))
16639
16643
  return null;
16640
- const data = JSON.parse(readFileSync2(file, "utf-8"));
16644
+ const data = JSON.parse(readFileSync3(file, "utf-8"));
16641
16645
  return data;
16642
16646
  } catch {
16643
16647
  return null;
@@ -16651,7 +16655,7 @@ function writeCache(sourceId, results) {
16651
16655
  }
16652
16656
  function clearCache(sourceId) {
16653
16657
  try {
16654
- if (!existsSync6(CACHE_DIR))
16658
+ if (!existsSync7(CACHE_DIR))
16655
16659
  return;
16656
16660
  const files = readdirSync3(CACHE_DIR);
16657
16661
  for (const file of files) {
@@ -16659,7 +16663,7 @@ function clearCache(sourceId) {
16659
16663
  continue;
16660
16664
  if (!sourceId || file.startsWith(`${sourceId}.`)) {
16661
16665
  try {
16662
- unlinkSync(join7(CACHE_DIR, file));
16666
+ unlinkSync(join8(CACHE_DIR, file));
16663
16667
  } catch {}
16664
16668
  }
16665
16669
  }
@@ -16905,12 +16909,166 @@ var CACHE_DIR, DEFAULT_TTL_MS;
16905
16909
  var init_sources = __esm(() => {
16906
16910
  init_db();
16907
16911
  init_config2();
16908
- CACHE_DIR = join7(MCPS_DIR, "cache");
16912
+ CACHE_DIR = join8(MCPS_DIR, "cache");
16909
16913
  DEFAULT_TTL_MS = 10 * 60 * 1000;
16910
16914
  });
16911
16915
 
16912
16916
  // src/lib/registry.ts
16913
16917
  init_db();
16918
+
16919
+ // src/lib/credentials.ts
16920
+ init_config2();
16921
+ import { existsSync as existsSync6, readFileSync as readFileSync2 } from "fs";
16922
+ import { join as join7 } from "path";
16923
+
16924
+ class CredentialReferenceError extends Error {
16925
+ constructor(message) {
16926
+ super(message);
16927
+ this.name = "CredentialReferenceError";
16928
+ }
16929
+ }
16930
+ var SECRET_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
16931
+ 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_-]+)$/;
16932
+ var REDACTED_CREDENTIAL_VALUE = "<redacted>";
16933
+ function normalizeKey(key) {
16934
+ return key.trim();
16935
+ }
16936
+ function isRecord(value) {
16937
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16938
+ }
16939
+ function isSecretLikeEnvKey(key) {
16940
+ return SECRET_KEY_PATTERN.test(key);
16941
+ }
16942
+ function isSecretLikeValue(value) {
16943
+ return SECRET_VALUE_PATTERN.test(value.trim());
16944
+ }
16945
+ function normalizeCredentialRef(ref) {
16946
+ const source = ref.source;
16947
+ if (source !== "env" && source !== "local-vault" && source !== "hosted") {
16948
+ throw new CredentialReferenceError(`Unsupported credential reference source: ${String(source)}`);
16949
+ }
16950
+ const name = ref.name?.trim();
16951
+ if (!name) {
16952
+ throw new CredentialReferenceError("Credential reference name is required");
16953
+ }
16954
+ return {
16955
+ source,
16956
+ name,
16957
+ required: ref.required !== false,
16958
+ ...ref.description ? { description: ref.description } : {}
16959
+ };
16960
+ }
16961
+ function normalizeCredentialRefs(refs) {
16962
+ const normalized = {};
16963
+ for (const [rawKey, ref] of Object.entries(refs ?? {})) {
16964
+ const key = normalizeKey(rawKey);
16965
+ if (!key)
16966
+ throw new CredentialReferenceError("Credential reference env key is required");
16967
+ normalized[key] = normalizeCredentialRef(ref);
16968
+ }
16969
+ return normalized;
16970
+ }
16971
+ function parseCredentialRefs(value) {
16972
+ if (!isRecord(value))
16973
+ return {};
16974
+ const refs = {};
16975
+ for (const [key, ref] of Object.entries(value)) {
16976
+ if (!isRecord(ref))
16977
+ continue;
16978
+ const source = ref.source;
16979
+ const name = ref.name;
16980
+ if ((source === "env" || source === "local-vault" || source === "hosted") && typeof name === "string" && name.trim()) {
16981
+ refs[key] = normalizeCredentialRef({
16982
+ source,
16983
+ name,
16984
+ required: typeof ref.required === "boolean" ? ref.required : true,
16985
+ description: typeof ref.description === "string" ? ref.description : undefined
16986
+ });
16987
+ }
16988
+ }
16989
+ return refs;
16990
+ }
16991
+ function normalizeLiteralEnv(env) {
16992
+ const normalized = {};
16993
+ for (const [rawKey, rawValue] of Object.entries(env ?? {})) {
16994
+ const key = normalizeKey(rawKey);
16995
+ if (!key)
16996
+ continue;
16997
+ const value = String(rawValue);
16998
+ if (isSecretLikeEnvKey(key) || isSecretLikeValue(value)) {
16999
+ throw new CredentialReferenceError(`Refusing to store raw secret-like env value for "${key}". Use a credential reference instead.`);
17000
+ }
17001
+ normalized[key] = value;
17002
+ }
17003
+ return normalized;
17004
+ }
17005
+ function redactEnv(env) {
17006
+ const redacted = {};
17007
+ for (const [key, value] of Object.entries(env)) {
17008
+ redacted[key] = isSecretLikeEnvKey(key) || isSecretLikeValue(value) ? REDACTED_CREDENTIAL_VALUE : value;
17009
+ }
17010
+ return redacted;
17011
+ }
17012
+ function redactServerCredentials(server) {
17013
+ return {
17014
+ ...server,
17015
+ env: redactEnv(server.env),
17016
+ credentialRefs: normalizeCredentialRefs(server.credentialRefs)
17017
+ };
17018
+ }
17019
+ function readLocalVault() {
17020
+ const path = process.env.HASNA_MCPS_CREDENTIAL_VAULT_PATH ?? join7(MCPS_DIR, "credentials.local.json");
17021
+ if (!existsSync6(path))
17022
+ return {};
17023
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
17024
+ if (!isRecord(parsed))
17025
+ return {};
17026
+ const values = {};
17027
+ for (const [key, value] of Object.entries(parsed)) {
17028
+ if (typeof value === "string")
17029
+ values[key] = value;
17030
+ }
17031
+ return values;
17032
+ }
17033
+ function resolveCredentialRef(envKey, ref) {
17034
+ if (ref.source === "env") {
17035
+ const value = process.env[ref.name];
17036
+ if (value === undefined && ref.required !== false) {
17037
+ throw new CredentialReferenceError(`Missing required environment credential "${ref.name}" for "${envKey}"`);
17038
+ }
17039
+ return value;
17040
+ }
17041
+ if (ref.source === "local-vault") {
17042
+ const value = readLocalVault()[ref.name];
17043
+ if (value === undefined && ref.required !== false) {
17044
+ throw new CredentialReferenceError(`Missing required local vault credential "${ref.name}" for "${envKey}"`);
17045
+ }
17046
+ return value;
17047
+ }
17048
+ if (ref.required !== false) {
17049
+ throw new CredentialReferenceError(`Hosted credential "${ref.name}" for "${envKey}" cannot be resolved by the local runtime`);
17050
+ }
17051
+ return;
17052
+ }
17053
+ function resolveServerEnv(server) {
17054
+ const resolved = { ...server.env };
17055
+ const refs = normalizeCredentialRefs(server.credentialRefs);
17056
+ for (const [envKey, ref] of Object.entries(refs)) {
17057
+ const value = resolveCredentialRef(envKey, ref);
17058
+ if (value !== undefined)
17059
+ resolved[envKey] = value;
17060
+ }
17061
+ return resolved;
17062
+ }
17063
+ function credentialRefPlaceholders(refs) {
17064
+ const placeholders = {};
17065
+ for (const key of Object.keys(refs ?? {})) {
17066
+ placeholders[key] = REDACTED_CREDENTIAL_VALUE;
17067
+ }
17068
+ return placeholders;
17069
+ }
17070
+
17071
+ // src/lib/registry.ts
16914
17072
  function parseRow(row) {
16915
17073
  return {
16916
17074
  id: row.id,
@@ -16919,6 +17077,7 @@ function parseRow(row) {
16919
17077
  command: row.command,
16920
17078
  args: safeJsonParse(row.args, []),
16921
17079
  env: safeJsonParse(row.env, {}),
17080
+ credentialRefs: parseCredentialRefs(safeJsonParse(row.credential_refs, {})),
16922
17081
  transport: row.transport,
16923
17082
  url: row.url || null,
16924
17083
  source: row.source,
@@ -16986,9 +17145,9 @@ function addServer(opts) {
16986
17145
  if (!id) {
16987
17146
  throw new Error("Unable to generate a valid server ID");
16988
17147
  }
16989
- const row = db2.prepare(`INSERT INTO servers (id, name, description, command, args, env, transport, url, source)
16990
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
16991
- RETURNING *`).get(id, name, opts.description || null, command, JSON.stringify(opts.args || []), JSON.stringify(opts.env || {}), opts.transport || "stdio", opts.url || null, opts.source || "local");
17148
+ const row = db2.prepare(`INSERT INTO servers (id, name, description, command, args, env, credential_refs, transport, url, source)
17149
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
17150
+ RETURNING *`).get(id, name, opts.description || null, command, JSON.stringify(opts.args || []), JSON.stringify(normalizeLiteralEnv(opts.env)), JSON.stringify(normalizeCredentialRefs(opts.credentialRefs)), opts.transport || "stdio", opts.url || null, opts.source || "local");
16992
17151
  return parseRow(row);
16993
17152
  }
16994
17153
  function removeServer(id) {
@@ -17027,7 +17186,11 @@ function updateServer(id, updates) {
17027
17186
  }
17028
17187
  if (updates.env !== undefined) {
17029
17188
  sets.push("env = ?");
17030
- values.push(JSON.stringify(updates.env));
17189
+ values.push(JSON.stringify(normalizeLiteralEnv(updates.env)));
17190
+ }
17191
+ if (updates.credentialRefs !== undefined) {
17192
+ sets.push("credential_refs = ?");
17193
+ values.push(JSON.stringify(normalizeCredentialRefs(updates.credentialRefs)));
17031
17194
  }
17032
17195
  if (updates.transport !== undefined) {
17033
17196
  sets.push("transport = ?");
@@ -17061,7 +17224,7 @@ function setServerEnv(id, key, value) {
17061
17224
  if (!server)
17062
17225
  throw new Error(`Server "${id}" not found`);
17063
17226
  const env = { ...server.env, [key]: value };
17064
- db2.prepare("UPDATE servers SET env = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(env), id);
17227
+ db2.prepare("UPDATE servers SET env = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(normalizeLiteralEnv(env)), id);
17065
17228
  }
17066
17229
  function unsetServerEnv(id, key) {
17067
17230
  const db2 = getDb();
@@ -17072,6 +17235,23 @@ function unsetServerEnv(id, key) {
17072
17235
  delete env[key];
17073
17236
  db2.prepare("UPDATE servers SET env = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(env), id);
17074
17237
  }
17238
+ function setServerCredentialRef(id, key, ref) {
17239
+ const db2 = getDb();
17240
+ const server = getServer(id);
17241
+ if (!server)
17242
+ throw new Error(`Server "${id}" not found`);
17243
+ const credentialRefs = normalizeCredentialRefs({ ...server.credentialRefs ?? {}, [key]: ref });
17244
+ db2.prepare("UPDATE servers SET credential_refs = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(credentialRefs), id);
17245
+ }
17246
+ function unsetServerCredentialRef(id, key) {
17247
+ const db2 = getDb();
17248
+ const server = getServer(id);
17249
+ if (!server)
17250
+ throw new Error(`Server "${id}" not found`);
17251
+ const credentialRefs = { ...server.credentialRefs ?? {} };
17252
+ delete credentialRefs[key];
17253
+ db2.prepare("UPDATE servers SET credential_refs = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(normalizeCredentialRefs(credentialRefs)), id);
17254
+ }
17075
17255
  function cacheTools(serverId, tools) {
17076
17256
  const db2 = getDb();
17077
17257
  const insert = db2.prepare("INSERT INTO tool_cache (server_id, name, description, input_schema) VALUES (?, ?, ?, ?)");
@@ -17111,6 +17291,7 @@ function cloneServer(id, newName) {
17111
17291
  command: server.command,
17112
17292
  args: server.args,
17113
17293
  env: server.env,
17294
+ credentialRefs: server.credentialRefs,
17114
17295
  transport: server.transport,
17115
17296
  url: server.url ?? undefined,
17116
17297
  source: server.source
@@ -25266,8 +25447,8 @@ var DESTRUCTIVE_COMMANDS = new Set([
25266
25447
  ]);
25267
25448
  var SHELL_EVAL_FLAGS = new Set(["-c", "/c", "-Command", "-command", "-EncodedCommand", "-encodedcommand"]);
25268
25449
  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_-]+)$/;
25450
+ var SECRET_KEY_PATTERN2 = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
25451
+ var SECRET_VALUE_PATTERN2 = /^(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
25452
  var SHELL_META_PATTERN = /[;&|`<>]|\$\(/;
25272
25453
  function commandBase(command) {
25273
25454
  return command.trim().split(/[\\/]/).pop()?.toLowerCase() || command.trim().toLowerCase();
@@ -25276,10 +25457,10 @@ function normalizeArgs(args) {
25276
25457
  return (args ?? []).map((arg) => String(arg));
25277
25458
  }
25278
25459
  function isSecretKey(key) {
25279
- return SECRET_KEY_PATTERN.test(key) || SECRET_FLAG_PATTERN.test(key);
25460
+ return SECRET_KEY_PATTERN2.test(key) || SECRET_FLAG_PATTERN.test(key);
25280
25461
  }
25281
25462
  function isSecretValue(value) {
25282
- return SECRET_VALUE_PATTERN.test(value.trim());
25463
+ return SECRET_VALUE_PATTERN2.test(value.trim());
25283
25464
  }
25284
25465
  function isSecretAssignment(arg) {
25285
25466
  const eqIdx = arg.indexOf("=");
@@ -25465,14 +25646,14 @@ async function connectToServer(entry, options = {}) {
25465
25646
  assertLocalCommandConsent({
25466
25647
  command: entry.command,
25467
25648
  args: entry.args,
25468
- env: entry.env,
25649
+ env: { ...entry.env, ...credentialRefPlaceholders(entry.credentialRefs) },
25469
25650
  transport: entry.transport,
25470
25651
  operation: "launch"
25471
25652
  }, options.localCommandConsent);
25472
25653
  transport = new StdioClientTransport({
25473
25654
  command: entry.command,
25474
25655
  args: entry.args,
25475
- env: buildEnv(entry.env)
25656
+ env: buildEnv(resolveServerEnv(entry))
25476
25657
  });
25477
25658
  } else if (entry.transport === "sse") {
25478
25659
  transport = new SSEClientTransport(requireUrl(entry));
@@ -25612,7 +25793,7 @@ async function diagnoseServer(server, options = {}) {
25612
25793
  assertLocalCommandConsent({
25613
25794
  command: server.command,
25614
25795
  args: server.args,
25615
- env: server.env,
25796
+ env: { ...server.env, ...credentialRefPlaceholders(server.credentialRefs) },
25616
25797
  transport: server.transport,
25617
25798
  operation: "diagnose"
25618
25799
  }, options.localCommandConsent);
@@ -25641,12 +25822,29 @@ async function diagnoseServer(server, options = {}) {
25641
25822
  }
25642
25823
  }
25643
25824
  const missingEnv = Object.entries(server.env).filter(([, v]) => !v);
25644
- if (Object.keys(server.env).length === 0) {
25645
- checks3.push({ name: "env vars", pass: true, message: "no env vars required" });
25646
- } else if (missingEnv.length > 0) {
25647
- checks3.push({ name: "env vars", pass: false, message: `missing values for: ${missingEnv.map(([k]) => k).join(", ")}` });
25825
+ const credentialRefCount = Object.keys(server.credentialRefs ?? {}).length;
25826
+ let credentialError = null;
25827
+ try {
25828
+ resolveServerEnv(server);
25829
+ } catch (err) {
25830
+ credentialError = err.message;
25831
+ }
25832
+ if (Object.keys(server.env).length === 0 && credentialRefCount === 0) {
25833
+ checks3.push({ name: "env vars", pass: true, message: "no env vars or credential refs required" });
25834
+ } else if (missingEnv.length > 0 || credentialError) {
25835
+ const parts = [];
25836
+ if (missingEnv.length > 0)
25837
+ parts.push(`missing literal values for: ${missingEnv.map(([k]) => k).join(", ")}`);
25838
+ if (credentialError)
25839
+ parts.push(credentialError);
25840
+ checks3.push({ name: "env vars", pass: false, message: parts.join("; ") });
25648
25841
  } else {
25649
- checks3.push({ name: "env vars", pass: true, message: `${Object.keys(server.env).length} env var(s) set` });
25842
+ const literalCount = Object.keys(server.env).length;
25843
+ checks3.push({
25844
+ name: "env vars",
25845
+ pass: true,
25846
+ message: `${literalCount} literal env var(s), ${credentialRefCount} credential ref(s) available`
25847
+ });
25650
25848
  }
25651
25849
  if (server.transport !== "stdio" && server.url) {
25652
25850
  try {
@@ -26005,8 +26203,8 @@ init_provider_profile_seeds();
26005
26203
 
26006
26204
  // src/lib/install.ts
26007
26205
  import { execFileSync as execFileSync2 } from "child_process";
26008
- import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync7 } from "fs";
26009
- import { join as join8 } from "path";
26206
+ import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync7 } from "fs";
26207
+ import { join as join9 } from "path";
26010
26208
  import { homedir as homedir8 } from "os";
26011
26209
  function installToClaude(entry) {
26012
26210
  try {
@@ -26018,7 +26216,7 @@ function installToClaude(entry) {
26018
26216
  "--scope",
26019
26217
  "user"
26020
26218
  ];
26021
- for (const [k, v] of Object.entries(entry.env)) {
26219
+ for (const [k, v] of Object.entries(assertAgentInstallEnv(entry))) {
26022
26220
  args.push("--env", `${k}=${v}`);
26023
26221
  }
26024
26222
  args.push(entry.id, "--", entry.command, ...entry.args);
@@ -26030,9 +26228,9 @@ function installToClaude(entry) {
26030
26228
  }
26031
26229
  function installToCodex(entry) {
26032
26230
  try {
26033
- const configDir = join8(homedir8(), ".codex");
26034
- const configPath = join8(configDir, "config.toml");
26035
- if (!existsSync7(configDir)) {
26231
+ const configDir = join9(homedir8(), ".codex");
26232
+ const configPath = join9(configDir, "config.toml");
26233
+ if (!existsSync8(configDir)) {
26036
26234
  mkdirSync7(configDir, { recursive: true });
26037
26235
  }
26038
26236
  const block = `
@@ -26040,7 +26238,7 @@ function installToCodex(entry) {
26040
26238
  ` + `command = ${JSON.stringify(entry.command)}
26041
26239
  ` + `args = [${entry.args.map((a) => JSON.stringify(a)).join(", ")}]
26042
26240
  `;
26043
- const existing = existsSync7(configPath) ? readFileSync3(configPath, "utf-8") : "";
26241
+ const existing = existsSync8(configPath) ? readFileSync4(configPath, "utf-8") : "";
26044
26242
  if (existing.includes(`[mcp_servers.${entry.id}]`)) {
26045
26243
  return { agent: "codex", success: true };
26046
26244
  }
@@ -26052,21 +26250,22 @@ function installToCodex(entry) {
26052
26250
  }
26053
26251
  function installToGemini(entry) {
26054
26252
  try {
26055
- const configDir = join8(homedir8(), ".gemini");
26056
- const configPath = join8(configDir, "settings.json");
26057
- if (!existsSync7(configDir)) {
26253
+ const configDir = join9(homedir8(), ".gemini");
26254
+ const configPath = join9(configDir, "settings.json");
26255
+ if (!existsSync8(configDir)) {
26058
26256
  mkdirSync7(configDir, { recursive: true });
26059
26257
  }
26060
26258
  let settings = {};
26061
- if (existsSync7(configPath)) {
26062
- settings = JSON.parse(readFileSync3(configPath, "utf-8"));
26259
+ if (existsSync8(configPath)) {
26260
+ settings = JSON.parse(readFileSync4(configPath, "utf-8"));
26063
26261
  }
26064
26262
  if (!settings.mcpServers)
26065
26263
  settings.mcpServers = {};
26264
+ const env = assertAgentInstallEnv(entry);
26066
26265
  settings.mcpServers[entry.id] = {
26067
26266
  command: entry.command,
26068
26267
  args: entry.args,
26069
- ...Object.keys(entry.env).length > 0 ? { env: entry.env } : {}
26268
+ ...Object.keys(env).length > 0 ? { env } : {}
26070
26269
  };
26071
26270
  writeFileSync3(configPath, JSON.stringify(settings, null, 2), "utf-8");
26072
26271
  return { agent: "gemini", success: true };
@@ -26074,12 +26273,24 @@ function installToGemini(entry) {
26074
26273
  return { agent: "gemini", success: false, error: err.message };
26075
26274
  }
26076
26275
  }
26276
+ function assertAgentInstallEnv(entry) {
26277
+ const refs = entry.credentialRefs ?? {};
26278
+ if (Object.keys(refs).length > 0) {
26279
+ throw new CredentialReferenceError(`Server "${entry.id}" uses credential references; refusing to materialize secrets into local agent config files`);
26280
+ }
26281
+ for (const [key, value] of Object.entries(entry.env)) {
26282
+ if (isSecretLikeEnvKey(key) || isSecretLikeValue(value)) {
26283
+ throw new CredentialReferenceError(`Server "${entry.id}" has legacy raw secret-like env "${key}"; move it to a credential reference before installing to agents`);
26284
+ }
26285
+ }
26286
+ return entry.env;
26287
+ }
26077
26288
  function installToAgents(entry, targets = ["claude", "codex", "gemini"], options = {}) {
26078
26289
  try {
26079
26290
  assertLocalCommandConsent({
26080
26291
  command: entry.command,
26081
26292
  args: entry.args,
26082
- env: entry.env,
26293
+ env: { ...entry.env, ...credentialRefPlaceholders(entry.credentialRefs) },
26083
26294
  transport: entry.transport,
26084
26295
  operation: "install"
26085
26296
  }, options.localCommandConsent);
@@ -26090,6 +26301,15 @@ function installToAgents(entry, targets = ["claude", "codex", "gemini"], options
26090
26301
  error: err.message
26091
26302
  }));
26092
26303
  }
26304
+ try {
26305
+ assertAgentInstallEnv(entry);
26306
+ } catch (err) {
26307
+ return targets.map((target) => ({
26308
+ agent: target,
26309
+ success: false,
26310
+ error: err.message
26311
+ }));
26312
+ }
26093
26313
  return targets.map((target) => {
26094
26314
  if (target === "claude")
26095
26315
  return installToClaude(entry);
@@ -26299,11 +26519,11 @@ function seedDefaultMachines() {
26299
26519
  // src/lib/fleet.ts
26300
26520
  init_config2();
26301
26521
  import { spawn as spawn2 } from "child_process";
26302
- import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
26303
- import { join as join9 } from "path";
26522
+ import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
26523
+ import { join as join10 } from "path";
26304
26524
  var NPM_SEARCH_URL = "https://registry.npmjs.org/-/v1/search";
26305
26525
  var NPM_REGISTRY_URL = "https://registry.npmjs.org";
26306
- var CATALOG_CACHE_PATH = join9(MCPS_DIR, "cache", "hasna-catalog.json");
26526
+ var CATALOG_CACHE_PATH = join10(MCPS_DIR, "cache", "hasna-catalog.json");
26307
26527
  var DEFAULT_CATALOG_CACHE_TTL_MS = 60 * 60 * 1000;
26308
26528
  var DEFAULT_REMOTE_TIMEOUT_MS = 180000;
26309
26529
  var DEFAULT_HANDSHAKE_TIMEOUT_MS = 2500;
@@ -26313,9 +26533,9 @@ function normalizeQueryList(values) {
26313
26533
  }
26314
26534
  function readCatalogCache(maxAgeMs) {
26315
26535
  try {
26316
- if (!existsSync8(CATALOG_CACHE_PATH))
26536
+ if (!existsSync9(CATALOG_CACHE_PATH))
26317
26537
  return null;
26318
- const parsed = JSON.parse(readFileSync4(CATALOG_CACHE_PATH, "utf-8"));
26538
+ const parsed = JSON.parse(readFileSync5(CATALOG_CACHE_PATH, "utf-8"));
26319
26539
  if (!parsed.cachedAt || !Array.isArray(parsed.entries))
26320
26540
  return null;
26321
26541
  if (Date.now() - parsed.cachedAt > maxAgeMs)
@@ -26327,7 +26547,7 @@ function readCatalogCache(maxAgeMs) {
26327
26547
  }
26328
26548
  function writeCatalogCache(entries) {
26329
26549
  try {
26330
- mkdirSync8(join9(MCPS_DIR, "cache"), { recursive: true });
26550
+ mkdirSync8(join10(MCPS_DIR, "cache"), { recursive: true });
26331
26551
  writeFileSync4(CATALOG_CACHE_PATH, JSON.stringify({ cachedAt: Date.now(), entries }, null, 2), "utf-8");
26332
26552
  } catch {}
26333
26553
  }
@@ -26998,22 +27218,22 @@ async function runFleetInstall(options = {}, dependencies = {}) {
26998
27218
  }));
26999
27219
  }
27000
27220
  // src/lib/version.ts
27001
- import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
27002
- import { dirname as dirname3, join as join10 } from "path";
27221
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
27222
+ import { dirname as dirname3, join as join11 } from "path";
27003
27223
  import { fileURLToPath } from "url";
27004
27224
  var FALLBACK_VERSION = "0.0.1";
27005
27225
  function readPackageVersion(moduleUrl, fallback = FALLBACK_VERSION) {
27006
27226
  const baseDir = dirname3(fileURLToPath(moduleUrl));
27007
27227
  const candidates = [
27008
- join10(baseDir, "..", "..", "package.json"),
27009
- join10(baseDir, "..", "package.json"),
27010
- join10(baseDir, "package.json")
27228
+ join11(baseDir, "..", "..", "package.json"),
27229
+ join11(baseDir, "..", "package.json"),
27230
+ join11(baseDir, "package.json")
27011
27231
  ];
27012
27232
  for (const candidate of candidates) {
27013
- if (!existsSync9(candidate))
27233
+ if (!existsSync10(candidate))
27014
27234
  continue;
27015
27235
  try {
27016
- const pkg = JSON.parse(readFileSync5(candidate, "utf-8"));
27236
+ const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
27017
27237
  if (pkg.version)
27018
27238
  return pkg.version;
27019
27239
  } catch {}
@@ -27029,19 +27249,27 @@ export {
27029
27249
  updateServer,
27030
27250
  updateMachine,
27031
27251
  unsetServerEnv,
27252
+ unsetServerCredentialRef,
27032
27253
  setServerEnv,
27254
+ setServerCredentialRef,
27033
27255
  seedDefaultProviderProfiles,
27034
27256
  seedDefaultMachines,
27035
27257
  searchRegistry,
27036
27258
  searchProviderProfiles,
27037
27259
  runFleetInstall,
27038
27260
  runFleetHealthCheck,
27261
+ resolveServerEnv,
27039
27262
  removeSource,
27040
27263
  removeServer,
27041
27264
  removeProviderProfile,
27042
27265
  removeMachine,
27043
27266
  refreshTools,
27267
+ redactServerCredentials,
27268
+ redactEnv,
27044
27269
  readPackageVersion,
27270
+ normalizeLiteralEnv,
27271
+ normalizeCredentialRefs,
27272
+ normalizeCredentialRef,
27045
27273
  listSources,
27046
27274
  listServers,
27047
27275
  listProviderProfiles,
@@ -27049,6 +27277,8 @@ export {
27049
27277
  listHasnaMcpCatalog,
27050
27278
  listAwesomeServers,
27051
27279
  listAllTools,
27280
+ isSecretLikeValue,
27281
+ isSecretLikeEnvKey,
27052
27282
  installToAgents,
27053
27283
  installProviderProfile,
27054
27284
  installFromRegistry,
@@ -27072,6 +27302,7 @@ export {
27072
27302
  disableServer,
27073
27303
  disableProviderProfile,
27074
27304
  diagnoseServer,
27305
+ credentialRefPlaceholders,
27075
27306
  connectToServer,
27076
27307
  closeDb,
27077
27308
  cloneServer,
@@ -27080,7 +27311,9 @@ export {
27080
27311
  addSource,
27081
27312
  addServer,
27082
27313
  addMachine,
27314
+ REDACTED_CREDENTIAL_VALUE,
27083
27315
  LocalCommandConsentError,
27084
27316
  DEFAULT_PROVIDER_PROFILE_SEEDS,
27085
- DEFAULT_MACHINE_SEEDS
27317
+ DEFAULT_MACHINE_SEEDS,
27318
+ CredentialReferenceError
27086
27319
  };
@@ -0,0 +1,18 @@
1
+ import type { CredentialReference, CredentialReferenceMap, McpServerEntry } from "../types.js";
2
+ export declare class CredentialReferenceError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare const REDACTED_CREDENTIAL_VALUE = "<redacted>";
6
+ export declare function isSecretLikeEnvKey(key: string): boolean;
7
+ export declare function isSecretLikeValue(value: string): boolean;
8
+ export declare function normalizeCredentialRef(ref: CredentialReference): CredentialReference;
9
+ export declare function normalizeCredentialRefs(refs: CredentialReferenceMap | undefined): CredentialReferenceMap;
10
+ export declare function parseCredentialRefs(value: unknown): CredentialReferenceMap;
11
+ export declare function normalizeLiteralEnv(env: Record<string, string> | undefined): Record<string, string>;
12
+ export declare function redactEnv(env: Record<string, string>): Record<string, string>;
13
+ export declare function redactServerCredentials<T extends {
14
+ env: Record<string, string>;
15
+ credentialRefs?: CredentialReferenceMap;
16
+ }>(server: T): T;
17
+ export declare function resolveServerEnv(server: McpServerEntry): Record<string, string>;
18
+ export declare function credentialRefPlaceholders(refs: CredentialReferenceMap | undefined): Record<string, string>;
@@ -1,13 +1,15 @@
1
- import type { McpServerEntry, AddServerOptions } from "../types.js";
1
+ import type { CredentialReference, McpServerEntry, AddServerOptions } from "../types.js";
2
2
  export declare function addServer(opts: AddServerOptions): McpServerEntry;
3
3
  export declare function removeServer(id: string): void;
4
4
  export declare function listServers(): McpServerEntry[];
5
5
  export declare function getServer(id: string): McpServerEntry | null;
6
- export declare function updateServer(id: string, updates: Partial<Pick<McpServerEntry, "name" | "description" | "command" | "args" | "env" | "transport" | "url" | "enabled">>): McpServerEntry;
6
+ export declare function updateServer(id: string, updates: Partial<Pick<McpServerEntry, "name" | "description" | "command" | "args" | "env" | "credentialRefs" | "transport" | "url" | "enabled">>): McpServerEntry;
7
7
  export declare function enableServer(id: string): McpServerEntry;
8
8
  export declare function disableServer(id: string): McpServerEntry;
9
9
  export declare function setServerEnv(id: string, key: string, value: string): void;
10
10
  export declare function unsetServerEnv(id: string, key: string): void;
11
+ export declare function setServerCredentialRef(id: string, key: string, ref: CredentialReference): void;
12
+ export declare function unsetServerCredentialRef(id: string, key: string): void;
11
13
  export declare function cacheTools(serverId: string, tools: Array<{
12
14
  name: string;
13
15
  description: string;