@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/bin/mcp.js CHANGED
@@ -16673,6 +16673,7 @@ function getDb() {
16673
16673
  command TEXT NOT NULL,
16674
16674
  args TEXT NOT NULL DEFAULT '[]',
16675
16675
  env TEXT NOT NULL DEFAULT '{}',
16676
+ credential_refs TEXT NOT NULL DEFAULT '{}',
16676
16677
  transport TEXT NOT NULL DEFAULT 'stdio',
16677
16678
  url TEXT,
16678
16679
  source TEXT NOT NULL DEFAULT 'local',
@@ -16699,6 +16700,9 @@ function getDb() {
16699
16700
  try {
16700
16701
  db.exec("ALTER TABLE servers ADD COLUMN last_error TEXT");
16701
16702
  } catch {}
16703
+ try {
16704
+ db.exec("ALTER TABLE servers ADD COLUMN credential_refs TEXT NOT NULL DEFAULT '{}'");
16705
+ } catch {}
16702
16706
  db.exec(`
16703
16707
  CREATE TABLE IF NOT EXISTS sources (
16704
16708
  id TEXT PRIMARY KEY,
@@ -16824,17 +16828,17 @@ __export(exports_sources, {
16824
16828
  clearCache: () => clearCache,
16825
16829
  addSource: () => addSource
16826
16830
  });
16827
- import { mkdirSync as mkdirSync6, existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync as readdirSync3, unlinkSync } from "fs";
16828
- import { join as join8 } from "path";
16831
+ import { mkdirSync as mkdirSync6, existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync2, readdirSync as readdirSync3, unlinkSync } from "fs";
16832
+ import { join as join9 } from "path";
16829
16833
  function getCacheFile(sourceId) {
16830
- return join8(CACHE_DIR, `${sourceId}.json`);
16834
+ return join9(CACHE_DIR, `${sourceId}.json`);
16831
16835
  }
16832
16836
  function readCache(sourceId) {
16833
16837
  try {
16834
16838
  const file = getCacheFile(sourceId);
16835
- if (!existsSync7(file))
16839
+ if (!existsSync8(file))
16836
16840
  return null;
16837
- const data = JSON.parse(readFileSync3(file, "utf-8"));
16841
+ const data = JSON.parse(readFileSync4(file, "utf-8"));
16838
16842
  return data;
16839
16843
  } catch {
16840
16844
  return null;
@@ -16848,7 +16852,7 @@ function writeCache(sourceId, results) {
16848
16852
  }
16849
16853
  function clearCache(sourceId) {
16850
16854
  try {
16851
- if (!existsSync7(CACHE_DIR))
16855
+ if (!existsSync8(CACHE_DIR))
16852
16856
  return;
16853
16857
  const files = readdirSync3(CACHE_DIR);
16854
16858
  for (const file of files) {
@@ -16856,7 +16860,7 @@ function clearCache(sourceId) {
16856
16860
  continue;
16857
16861
  if (!sourceId || file.startsWith(`${sourceId}.`)) {
16858
16862
  try {
16859
- unlinkSync(join8(CACHE_DIR, file));
16863
+ unlinkSync(join9(CACHE_DIR, file));
16860
16864
  } catch {}
16861
16865
  }
16862
16866
  }
@@ -17102,7 +17106,7 @@ var CACHE_DIR, DEFAULT_TTL_MS;
17102
17106
  var init_sources = __esm(() => {
17103
17107
  init_db();
17104
17108
  init_config2();
17105
- CACHE_DIR = join8(MCPS_DIR, "cache");
17109
+ CACHE_DIR = join9(MCPS_DIR, "cache");
17106
17110
  DEFAULT_TTL_MS = 10 * 60 * 1000;
17107
17111
  });
17108
17112
 
@@ -30660,6 +30664,146 @@ function readPackageVersion(moduleUrl, fallback = FALLBACK_VERSION) {
30660
30664
 
30661
30665
  // src/lib/registry.ts
30662
30666
  init_db();
30667
+
30668
+ // src/lib/credentials.ts
30669
+ init_config2();
30670
+ import { existsSync as existsSync7, readFileSync as readFileSync3 } from "fs";
30671
+ import { join as join8 } from "path";
30672
+
30673
+ class CredentialReferenceError extends Error {
30674
+ constructor(message) {
30675
+ super(message);
30676
+ this.name = "CredentialReferenceError";
30677
+ }
30678
+ }
30679
+ var SECRET_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
30680
+ 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_-]+)$/;
30681
+ var REDACTED_CREDENTIAL_VALUE = "<redacted>";
30682
+ function normalizeKey(key) {
30683
+ return key.trim();
30684
+ }
30685
+ function isRecord(value) {
30686
+ return typeof value === "object" && value !== null && !Array.isArray(value);
30687
+ }
30688
+ function isSecretLikeEnvKey(key) {
30689
+ return SECRET_KEY_PATTERN.test(key);
30690
+ }
30691
+ function isSecretLikeValue(value) {
30692
+ return SECRET_VALUE_PATTERN.test(value.trim());
30693
+ }
30694
+ function normalizeCredentialRef(ref) {
30695
+ const source = ref.source;
30696
+ if (source !== "env" && source !== "local-vault" && source !== "hosted") {
30697
+ throw new CredentialReferenceError(`Unsupported credential reference source: ${String(source)}`);
30698
+ }
30699
+ const name = ref.name?.trim();
30700
+ if (!name) {
30701
+ throw new CredentialReferenceError("Credential reference name is required");
30702
+ }
30703
+ return {
30704
+ source,
30705
+ name,
30706
+ required: ref.required !== false,
30707
+ ...ref.description ? { description: ref.description } : {}
30708
+ };
30709
+ }
30710
+ function normalizeCredentialRefs(refs) {
30711
+ const normalized = {};
30712
+ for (const [rawKey, ref] of Object.entries(refs ?? {})) {
30713
+ const key = normalizeKey(rawKey);
30714
+ if (!key)
30715
+ throw new CredentialReferenceError("Credential reference env key is required");
30716
+ normalized[key] = normalizeCredentialRef(ref);
30717
+ }
30718
+ return normalized;
30719
+ }
30720
+ function parseCredentialRefs(value) {
30721
+ if (!isRecord(value))
30722
+ return {};
30723
+ const refs = {};
30724
+ for (const [key, ref] of Object.entries(value)) {
30725
+ if (!isRecord(ref))
30726
+ continue;
30727
+ const source = ref.source;
30728
+ const name = ref.name;
30729
+ if ((source === "env" || source === "local-vault" || source === "hosted") && typeof name === "string" && name.trim()) {
30730
+ refs[key] = normalizeCredentialRef({
30731
+ source,
30732
+ name,
30733
+ required: typeof ref.required === "boolean" ? ref.required : true,
30734
+ description: typeof ref.description === "string" ? ref.description : undefined
30735
+ });
30736
+ }
30737
+ }
30738
+ return refs;
30739
+ }
30740
+ function normalizeLiteralEnv(env) {
30741
+ const normalized = {};
30742
+ for (const [rawKey, rawValue] of Object.entries(env ?? {})) {
30743
+ const key = normalizeKey(rawKey);
30744
+ if (!key)
30745
+ continue;
30746
+ const value = String(rawValue);
30747
+ if (isSecretLikeEnvKey(key) || isSecretLikeValue(value)) {
30748
+ throw new CredentialReferenceError(`Refusing to store raw secret-like env value for "${key}". Use a credential reference instead.`);
30749
+ }
30750
+ normalized[key] = value;
30751
+ }
30752
+ return normalized;
30753
+ }
30754
+ function readLocalVault() {
30755
+ const path = process.env.HASNA_MCPS_CREDENTIAL_VAULT_PATH ?? join8(MCPS_DIR, "credentials.local.json");
30756
+ if (!existsSync7(path))
30757
+ return {};
30758
+ const parsed = JSON.parse(readFileSync3(path, "utf-8"));
30759
+ if (!isRecord(parsed))
30760
+ return {};
30761
+ const values = {};
30762
+ for (const [key, value] of Object.entries(parsed)) {
30763
+ if (typeof value === "string")
30764
+ values[key] = value;
30765
+ }
30766
+ return values;
30767
+ }
30768
+ function resolveCredentialRef(envKey, ref) {
30769
+ if (ref.source === "env") {
30770
+ const value = process.env[ref.name];
30771
+ if (value === undefined && ref.required !== false) {
30772
+ throw new CredentialReferenceError(`Missing required environment credential "${ref.name}" for "${envKey}"`);
30773
+ }
30774
+ return value;
30775
+ }
30776
+ if (ref.source === "local-vault") {
30777
+ const value = readLocalVault()[ref.name];
30778
+ if (value === undefined && ref.required !== false) {
30779
+ throw new CredentialReferenceError(`Missing required local vault credential "${ref.name}" for "${envKey}"`);
30780
+ }
30781
+ return value;
30782
+ }
30783
+ if (ref.required !== false) {
30784
+ throw new CredentialReferenceError(`Hosted credential "${ref.name}" for "${envKey}" cannot be resolved by the local runtime`);
30785
+ }
30786
+ return;
30787
+ }
30788
+ function resolveServerEnv(server) {
30789
+ const resolved = { ...server.env };
30790
+ const refs = normalizeCredentialRefs(server.credentialRefs);
30791
+ for (const [envKey, ref] of Object.entries(refs)) {
30792
+ const value = resolveCredentialRef(envKey, ref);
30793
+ if (value !== undefined)
30794
+ resolved[envKey] = value;
30795
+ }
30796
+ return resolved;
30797
+ }
30798
+ function credentialRefPlaceholders(refs) {
30799
+ const placeholders = {};
30800
+ for (const key of Object.keys(refs ?? {})) {
30801
+ placeholders[key] = REDACTED_CREDENTIAL_VALUE;
30802
+ }
30803
+ return placeholders;
30804
+ }
30805
+
30806
+ // src/lib/registry.ts
30663
30807
  function parseRow(row) {
30664
30808
  return {
30665
30809
  id: row.id,
@@ -30668,6 +30812,7 @@ function parseRow(row) {
30668
30812
  command: row.command,
30669
30813
  args: safeJsonParse(row.args, []),
30670
30814
  env: safeJsonParse(row.env, {}),
30815
+ credentialRefs: parseCredentialRefs(safeJsonParse(row.credential_refs, {})),
30671
30816
  transport: row.transport,
30672
30817
  url: row.url || null,
30673
30818
  source: row.source,
@@ -30735,9 +30880,9 @@ function addServer(opts) {
30735
30880
  if (!id) {
30736
30881
  throw new Error("Unable to generate a valid server ID");
30737
30882
  }
30738
- const row = db2.prepare(`INSERT INTO servers (id, name, description, command, args, env, transport, url, source)
30739
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
30740
- 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");
30883
+ const row = db2.prepare(`INSERT INTO servers (id, name, description, command, args, env, credential_refs, transport, url, source)
30884
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
30885
+ 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");
30741
30886
  return parseRow(row);
30742
30887
  }
30743
30888
  function removeServer(id) {
@@ -30776,7 +30921,11 @@ function updateServer(id, updates) {
30776
30921
  }
30777
30922
  if (updates.env !== undefined) {
30778
30923
  sets.push("env = ?");
30779
- values.push(JSON.stringify(updates.env));
30924
+ values.push(JSON.stringify(normalizeLiteralEnv(updates.env)));
30925
+ }
30926
+ if (updates.credentialRefs !== undefined) {
30927
+ sets.push("credential_refs = ?");
30928
+ values.push(JSON.stringify(normalizeCredentialRefs(updates.credentialRefs)));
30780
30929
  }
30781
30930
  if (updates.transport !== undefined) {
30782
30931
  sets.push("transport = ?");
@@ -30864,8 +31013,8 @@ var DESTRUCTIVE_COMMANDS = new Set([
30864
31013
  ]);
30865
31014
  var SHELL_EVAL_FLAGS = new Set(["-c", "/c", "-Command", "-command", "-EncodedCommand", "-encodedcommand"]);
30866
31015
  var SECRET_FLAG_PATTERN = /(?:^|[-_])(api[-_]?key|token|secret|password|passwd|credential|auth|private[-_]?key)(?:$|[-_])/i;
30867
- var SECRET_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
30868
- var SECRET_VALUE_PATTERN = /^(sk_(?:live|test)_[A-Za-z0-9_]+|ghp_[A-Za-z0-9_]+|github_pat_[A-Za-z0-9_]+|xox[baprs]-[A-Za-z0-9-]+|AIza[A-Za-z0-9_-]{20,}|eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)$/;
31016
+ var SECRET_KEY_PATTERN2 = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
31017
+ 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_-]+)$/;
30869
31018
  var SHELL_META_PATTERN = /[;&|`<>]|\$\(/;
30870
31019
  function commandBase(command) {
30871
31020
  return command.trim().split(/[\\/]/).pop()?.toLowerCase() || command.trim().toLowerCase();
@@ -30874,10 +31023,10 @@ function normalizeArgs(args) {
30874
31023
  return (args ?? []).map((arg) => String(arg));
30875
31024
  }
30876
31025
  function isSecretKey(key) {
30877
- return SECRET_KEY_PATTERN.test(key) || SECRET_FLAG_PATTERN.test(key);
31026
+ return SECRET_KEY_PATTERN2.test(key) || SECRET_FLAG_PATTERN.test(key);
30878
31027
  }
30879
31028
  function isSecretValue(value) {
30880
- return SECRET_VALUE_PATTERN.test(value.trim());
31029
+ return SECRET_VALUE_PATTERN2.test(value.trim());
30881
31030
  }
30882
31031
  function isSecretAssignment(arg) {
30883
31032
  const eqIdx = arg.indexOf("=");
@@ -31101,8 +31250,8 @@ init_sources();
31101
31250
 
31102
31251
  // src/lib/install.ts
31103
31252
  import { execFileSync } from "child_process";
31104
- import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync7 } from "fs";
31105
- import { join as join9 } from "path";
31253
+ import { existsSync as existsSync9, readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync7 } from "fs";
31254
+ import { join as join10 } from "path";
31106
31255
  import { homedir as homedir8 } from "os";
31107
31256
  function installToClaude(entry) {
31108
31257
  try {
@@ -31114,7 +31263,7 @@ function installToClaude(entry) {
31114
31263
  "--scope",
31115
31264
  "user"
31116
31265
  ];
31117
- for (const [k, v] of Object.entries(entry.env)) {
31266
+ for (const [k, v] of Object.entries(assertAgentInstallEnv(entry))) {
31118
31267
  args.push("--env", `${k}=${v}`);
31119
31268
  }
31120
31269
  args.push(entry.id, "--", entry.command, ...entry.args);
@@ -31126,9 +31275,9 @@ function installToClaude(entry) {
31126
31275
  }
31127
31276
  function installToCodex(entry) {
31128
31277
  try {
31129
- const configDir = join9(homedir8(), ".codex");
31130
- const configPath = join9(configDir, "config.toml");
31131
- if (!existsSync8(configDir)) {
31278
+ const configDir = join10(homedir8(), ".codex");
31279
+ const configPath = join10(configDir, "config.toml");
31280
+ if (!existsSync9(configDir)) {
31132
31281
  mkdirSync7(configDir, { recursive: true });
31133
31282
  }
31134
31283
  const block = `
@@ -31136,7 +31285,7 @@ function installToCodex(entry) {
31136
31285
  ` + `command = ${JSON.stringify(entry.command)}
31137
31286
  ` + `args = [${entry.args.map((a) => JSON.stringify(a)).join(", ")}]
31138
31287
  `;
31139
- const existing = existsSync8(configPath) ? readFileSync4(configPath, "utf-8") : "";
31288
+ const existing = existsSync9(configPath) ? readFileSync5(configPath, "utf-8") : "";
31140
31289
  if (existing.includes(`[mcp_servers.${entry.id}]`)) {
31141
31290
  return { agent: "codex", success: true };
31142
31291
  }
@@ -31148,21 +31297,22 @@ function installToCodex(entry) {
31148
31297
  }
31149
31298
  function installToGemini(entry) {
31150
31299
  try {
31151
- const configDir = join9(homedir8(), ".gemini");
31152
- const configPath = join9(configDir, "settings.json");
31153
- if (!existsSync8(configDir)) {
31300
+ const configDir = join10(homedir8(), ".gemini");
31301
+ const configPath = join10(configDir, "settings.json");
31302
+ if (!existsSync9(configDir)) {
31154
31303
  mkdirSync7(configDir, { recursive: true });
31155
31304
  }
31156
31305
  let settings = {};
31157
- if (existsSync8(configPath)) {
31158
- settings = JSON.parse(readFileSync4(configPath, "utf-8"));
31306
+ if (existsSync9(configPath)) {
31307
+ settings = JSON.parse(readFileSync5(configPath, "utf-8"));
31159
31308
  }
31160
31309
  if (!settings.mcpServers)
31161
31310
  settings.mcpServers = {};
31311
+ const env = assertAgentInstallEnv(entry);
31162
31312
  settings.mcpServers[entry.id] = {
31163
31313
  command: entry.command,
31164
31314
  args: entry.args,
31165
- ...Object.keys(entry.env).length > 0 ? { env: entry.env } : {}
31315
+ ...Object.keys(env).length > 0 ? { env } : {}
31166
31316
  };
31167
31317
  writeFileSync3(configPath, JSON.stringify(settings, null, 2), "utf-8");
31168
31318
  return { agent: "gemini", success: true };
@@ -31170,12 +31320,24 @@ function installToGemini(entry) {
31170
31320
  return { agent: "gemini", success: false, error: err.message };
31171
31321
  }
31172
31322
  }
31323
+ function assertAgentInstallEnv(entry) {
31324
+ const refs = entry.credentialRefs ?? {};
31325
+ if (Object.keys(refs).length > 0) {
31326
+ throw new CredentialReferenceError(`Server "${entry.id}" uses credential references; refusing to materialize secrets into local agent config files`);
31327
+ }
31328
+ for (const [key, value] of Object.entries(entry.env)) {
31329
+ if (isSecretLikeEnvKey(key) || isSecretLikeValue(value)) {
31330
+ throw new CredentialReferenceError(`Server "${entry.id}" has legacy raw secret-like env "${key}"; move it to a credential reference before installing to agents`);
31331
+ }
31332
+ }
31333
+ return entry.env;
31334
+ }
31173
31335
  function installToAgents(entry, targets = ["claude", "codex", "gemini"], options = {}) {
31174
31336
  try {
31175
31337
  assertLocalCommandConsent({
31176
31338
  command: entry.command,
31177
31339
  args: entry.args,
31178
- env: entry.env,
31340
+ env: { ...entry.env, ...credentialRefPlaceholders(entry.credentialRefs) },
31179
31341
  transport: entry.transport,
31180
31342
  operation: "install"
31181
31343
  }, options.localCommandConsent);
@@ -31186,6 +31348,15 @@ function installToAgents(entry, targets = ["claude", "codex", "gemini"], options
31186
31348
  error: err.message
31187
31349
  }));
31188
31350
  }
31351
+ try {
31352
+ assertAgentInstallEnv(entry);
31353
+ } catch (err) {
31354
+ return targets.map((target) => ({
31355
+ agent: target,
31356
+ success: false,
31357
+ error: err.message
31358
+ }));
31359
+ }
31189
31360
  return targets.map((target) => {
31190
31361
  if (target === "claude")
31191
31362
  return installToClaude(entry);
@@ -33621,14 +33792,14 @@ async function connectToServer(entry, options = {}) {
33621
33792
  assertLocalCommandConsent({
33622
33793
  command: entry.command,
33623
33794
  args: entry.args,
33624
- env: entry.env,
33795
+ env: { ...entry.env, ...credentialRefPlaceholders(entry.credentialRefs) },
33625
33796
  transport: entry.transport,
33626
33797
  operation: "launch"
33627
33798
  }, options.localCommandConsent);
33628
33799
  transport = new StdioClientTransport({
33629
33800
  command: entry.command,
33630
33801
  args: entry.args,
33631
- env: buildEnv(entry.env)
33802
+ env: buildEnv(resolveServerEnv(entry))
33632
33803
  });
33633
33804
  } else if (entry.transport === "sse") {
33634
33805
  transport = new SSEClientTransport(requireUrl(entry));
@@ -33774,7 +33945,7 @@ async function diagnoseServer(server, options = {}) {
33774
33945
  assertLocalCommandConsent({
33775
33946
  command: server.command,
33776
33947
  args: server.args,
33777
- env: server.env,
33948
+ env: { ...server.env, ...credentialRefPlaceholders(server.credentialRefs) },
33778
33949
  transport: server.transport,
33779
33950
  operation: "diagnose"
33780
33951
  }, options.localCommandConsent);
@@ -33803,12 +33974,29 @@ async function diagnoseServer(server, options = {}) {
33803
33974
  }
33804
33975
  }
33805
33976
  const missingEnv = Object.entries(server.env).filter(([, v]) => !v);
33806
- if (Object.keys(server.env).length === 0) {
33807
- checks4.push({ name: "env vars", pass: true, message: "no env vars required" });
33808
- } else if (missingEnv.length > 0) {
33809
- checks4.push({ name: "env vars", pass: false, message: `missing values for: ${missingEnv.map(([k]) => k).join(", ")}` });
33977
+ const credentialRefCount = Object.keys(server.credentialRefs ?? {}).length;
33978
+ let credentialError = null;
33979
+ try {
33980
+ resolveServerEnv(server);
33981
+ } catch (err) {
33982
+ credentialError = err.message;
33983
+ }
33984
+ if (Object.keys(server.env).length === 0 && credentialRefCount === 0) {
33985
+ checks4.push({ name: "env vars", pass: true, message: "no env vars or credential refs required" });
33986
+ } else if (missingEnv.length > 0 || credentialError) {
33987
+ const parts = [];
33988
+ if (missingEnv.length > 0)
33989
+ parts.push(`missing literal values for: ${missingEnv.map(([k]) => k).join(", ")}`);
33990
+ if (credentialError)
33991
+ parts.push(credentialError);
33992
+ checks4.push({ name: "env vars", pass: false, message: parts.join("; ") });
33810
33993
  } else {
33811
- checks4.push({ name: "env vars", pass: true, message: `${Object.keys(server.env).length} env var(s) set` });
33994
+ const literalCount = Object.keys(server.env).length;
33995
+ checks4.push({
33996
+ name: "env vars",
33997
+ pass: true,
33998
+ message: `${literalCount} literal env var(s), ${credentialRefCount} credential ref(s) available`
33999
+ });
33812
34000
  }
33813
34001
  if (server.transport !== "stdio" && server.url) {
33814
34002
  try {
@@ -34045,11 +34233,11 @@ function seedDefaultMachines() {
34045
34233
  // src/lib/fleet.ts
34046
34234
  init_config2();
34047
34235
  import { spawn as spawn2 } from "child_process";
34048
- import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
34049
- import { join as join10 } from "path";
34236
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
34237
+ import { join as join11 } from "path";
34050
34238
  var NPM_SEARCH_URL = "https://registry.npmjs.org/-/v1/search";
34051
34239
  var NPM_REGISTRY_URL = "https://registry.npmjs.org";
34052
- var CATALOG_CACHE_PATH = join10(MCPS_DIR, "cache", "hasna-catalog.json");
34240
+ var CATALOG_CACHE_PATH = join11(MCPS_DIR, "cache", "hasna-catalog.json");
34053
34241
  var DEFAULT_CATALOG_CACHE_TTL_MS = 60 * 60 * 1000;
34054
34242
  var DEFAULT_REMOTE_TIMEOUT_MS = 180000;
34055
34243
  var DEFAULT_HANDSHAKE_TIMEOUT_MS = 2500;
@@ -34059,9 +34247,9 @@ function normalizeQueryList(values) {
34059
34247
  }
34060
34248
  function readCatalogCache(maxAgeMs) {
34061
34249
  try {
34062
- if (!existsSync9(CATALOG_CACHE_PATH))
34250
+ if (!existsSync10(CATALOG_CACHE_PATH))
34063
34251
  return null;
34064
- const parsed = JSON.parse(readFileSync5(CATALOG_CACHE_PATH, "utf-8"));
34252
+ const parsed = JSON.parse(readFileSync6(CATALOG_CACHE_PATH, "utf-8"));
34065
34253
  if (!parsed.cachedAt || !Array.isArray(parsed.entries))
34066
34254
  return null;
34067
34255
  if (Date.now() - parsed.cachedAt > maxAgeMs)
@@ -34073,7 +34261,7 @@ function readCatalogCache(maxAgeMs) {
34073
34261
  }
34074
34262
  function writeCatalogCache(entries) {
34075
34263
  try {
34076
- mkdirSync8(join10(MCPS_DIR, "cache"), { recursive: true });
34264
+ mkdirSync8(join11(MCPS_DIR, "cache"), { recursive: true });
34077
34265
  writeFileSync4(CATALOG_CACHE_PATH, JSON.stringify({ cachedAt: Date.now(), entries }, null, 2), "utf-8");
34078
34266
  } catch {}
34079
34267
  }
@@ -34872,6 +35060,9 @@ function localConsent(input) {
34872
35060
  source: "mcp"
34873
35061
  };
34874
35062
  }
35063
+ function readCredentialRefs(input) {
35064
+ return normalizeCredentialRefs(input.credential_refs ?? input.credentialRefs);
35065
+ }
34875
35066
  function buildMcpTools() {
34876
35067
  const definitions = [
34877
35068
  {
@@ -34897,6 +35088,12 @@ function buildMcpTools() {
34897
35088
  transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("Transport type"),
34898
35089
  url: exports_external2.string().optional().describe("URL for remote transports"),
34899
35090
  env: exports_external2.record(exports_external2.string()).optional().describe("Environment variables"),
35091
+ credential_refs: exports_external2.record(exports_external2.object({
35092
+ source: exports_external2.enum(["env", "local-vault", "hosted"]),
35093
+ name: exports_external2.string(),
35094
+ required: exports_external2.boolean().optional(),
35095
+ description: exports_external2.string().optional()
35096
+ })).optional().describe("Credential references by server env key"),
34900
35097
  allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering this local stdio command"),
34901
35098
  allow_risky_command: exports_external2.boolean().optional().describe("Approve registering risky local command patterns")
34902
35099
  },
@@ -34904,9 +35101,16 @@ function buildMcpTools() {
34904
35101
  const command = String(input.command);
34905
35102
  const args = Array.isArray(input.args) ? input.args.map(String) : [];
34906
35103
  const env = isRecordOfStrings(input.env) ? input.env : {};
35104
+ const credentialRefs = readCredentialRefs(input);
34907
35105
  const transport = input.transport;
34908
35106
  try {
34909
- assertLocalCommandConsent({ command, args, env, transport, operation: "register" }, localConsent(input));
35107
+ assertLocalCommandConsent({
35108
+ command,
35109
+ args,
35110
+ env: { ...env, ...Object.fromEntries(Object.keys(credentialRefs).map((key) => [key, "<credential-ref>"])) },
35111
+ transport,
35112
+ operation: "register"
35113
+ }, localConsent(input));
34910
35114
  return jsonContent(addServer({
34911
35115
  command,
34912
35116
  args,
@@ -34914,7 +35118,8 @@ function buildMcpTools() {
34914
35118
  description: typeof input.description === "string" ? input.description : undefined,
34915
35119
  transport,
34916
35120
  url: typeof input.url === "string" ? input.url : undefined,
34917
- env
35121
+ env,
35122
+ credentialRefs
34918
35123
  }));
34919
35124
  } catch (err) {
34920
35125
  return errorContent(err.message);
@@ -34982,6 +35187,12 @@ function buildMcpTools() {
34982
35187
  args: exports_external2.array(exports_external2.string()).optional().describe("New args list"),
34983
35188
  transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("New transport type"),
34984
35189
  url: exports_external2.string().optional().describe("New URL for remote transports"),
35190
+ credential_refs: exports_external2.record(exports_external2.object({
35191
+ source: exports_external2.enum(["env", "local-vault", "hosted"]),
35192
+ name: exports_external2.string(),
35193
+ required: exports_external2.boolean().optional(),
35194
+ description: exports_external2.string().optional()
35195
+ })).optional().describe("Credential references by server env key"),
34985
35196
  allow_local_stdio: exports_external2.boolean().optional().describe("Approve updating this local stdio command"),
34986
35197
  allow_risky_command: exports_external2.boolean().optional().describe("Approve risky local command patterns")
34987
35198
  },
@@ -34999,6 +35210,8 @@ function buildMcpTools() {
34999
35210
  fields.command = input.command;
35000
35211
  if (Array.isArray(input.args))
35001
35212
  fields.args = input.args.map(String);
35213
+ if (input.credential_refs !== undefined || input.credentialRefs !== undefined)
35214
+ fields.credentialRefs = readCredentialRefs(input);
35002
35215
  if (input.transport === "stdio" || input.transport === "sse" || input.transport === "streamable-http")
35003
35216
  fields.transport = input.transport;
35004
35217
  if (typeof input.url === "string")
@@ -35008,7 +35221,10 @@ function buildMcpTools() {
35008
35221
  assertLocalCommandConsent({
35009
35222
  command: fields.command ?? existing.command,
35010
35223
  args: fields.args ?? existing.args,
35011
- env: existing.env,
35224
+ env: {
35225
+ ...existing.env,
35226
+ ...Object.fromEntries(Object.keys(fields.credentialRefs ?? existing.credentialRefs ?? {}).map((key) => [key, "<credential-ref>"]))
35227
+ },
35012
35228
  transport: fields.transport ?? existing.transport,
35013
35229
  operation: "register"
35014
35230
  }, localConsent(input));
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export type { McpServerEntry, AddServerOptions, McpTool, RegistryServer, ConnectedServer, FinderResult, MachineEntry, AddMachineOptions, MachinePlatform, MachineArch, MachineInstaller, HasnaMcpCatalogEntry, MachinePackageHealth, FleetHealthReport, FleetInstallPackageResult, FleetInstallReport, ProviderAuthMetadata, ProviderEndpointFallback, ProviderInstallFallback, ProviderProfile, ProviderProfileAuthType, ProviderProfileBearerTokenMode, ProviderProfileSource, ProviderProfileTokenMode, ProviderProfileTransport, ProviderSafetyMetadata, ProviderSourceProvenance, InstallProviderProfileOptions, UpsertProviderProfileOptions, } from "./types.js";
2
- export { addServer, removeServer, listServers, getServer, updateServer, enableServer, disableServer, getToolCounts, getCachedTools, setServerEnv, unsetServerEnv, cloneServer, } from "./lib/registry.js";
1
+ export type { McpServerEntry, AddServerOptions, McpTool, RegistryServer, ConnectedServer, FinderResult, MachineEntry, AddMachineOptions, MachinePlatform, MachineArch, MachineInstaller, HasnaMcpCatalogEntry, MachinePackageHealth, FleetHealthReport, FleetInstallPackageResult, FleetInstallReport, ProviderAuthMetadata, ProviderEndpointFallback, ProviderInstallFallback, ProviderProfile, ProviderProfileAuthType, ProviderProfileBearerTokenMode, ProviderProfileSource, ProviderProfileTokenMode, ProviderProfileTransport, ProviderSafetyMetadata, ProviderSourceProvenance, InstallProviderProfileOptions, UpsertProviderProfileOptions, CredentialReference, CredentialReferenceMap, CredentialReferenceSource, } from "./types.js";
2
+ export { addServer, removeServer, listServers, getServer, updateServer, enableServer, disableServer, getToolCounts, getCachedTools, setServerEnv, setServerCredentialRef, unsetServerEnv, unsetServerCredentialRef, cloneServer, } from "./lib/registry.js";
3
3
  export { diagnoseServer } from "./lib/doctor.js";
4
4
  export type { DoctorReport, DoctorCheck } from "./lib/doctor.js";
5
5
  export { searchRegistry, getRegistryServer, installFromRegistry } from "./lib/remote.js";
@@ -15,6 +15,7 @@ export { addMachine, upsertMachine, listMachines, getMachine, updateMachine, rem
15
15
  export { listHasnaMcpCatalog, runFleetHealthCheck, runFleetInstall } from "./lib/fleet.js";
16
16
  export { readPackageVersion } from "./lib/version.js";
17
17
  export { assertLocalCommandConsent, formatLocalCommandReview, inspectLocalCommand, LocalCommandConsentError, } from "./lib/local-command-consent.js";
18
+ export { CredentialReferenceError, REDACTED_CREDENTIAL_VALUE, credentialRefPlaceholders, isSecretLikeEnvKey, isSecretLikeValue, normalizeCredentialRefs, normalizeCredentialRef, normalizeLiteralEnv, redactEnv, redactServerCredentials, resolveServerEnv, } from "./lib/credentials.js";
18
19
  export type { LocalCommandConsent, LocalCommandInput, LocalCommandOperation, LocalCommandReview, LocalCommandRisk, } from "./lib/local-command-consent.js";
19
20
  export { connectToServer, disconnectServer, listAllTools, callTool, refreshTools, disconnectAll, } from "./lib/proxy.js";
20
21
  export { getDb, closeDb } from "./lib/db.js";