@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/README.md +21 -0
- package/bin/index.js +448 -98
- package/bin/mcp.js +263 -47
- package/dist/index.d.ts +3 -2
- package/dist/index.js +286 -53
- package/dist/lib/credentials.d.ts +18 -0
- package/dist/lib/registry.d.ts +4 -2
- package/dist/mcp/index.js +263 -47
- package/dist/types.d.ts +10 -0
- package/package.json +1 -1
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
|
|
16631
|
-
import { join as
|
|
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
|
|
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 (!
|
|
16642
|
+
if (!existsSync7(file))
|
|
16639
16643
|
return null;
|
|
16640
|
-
const data = JSON.parse(
|
|
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 (!
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
25270
|
-
var
|
|
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
|
|
25460
|
+
return SECRET_KEY_PATTERN2.test(key) || SECRET_FLAG_PATTERN.test(key);
|
|
25280
25461
|
}
|
|
25281
25462
|
function isSecretValue(value) {
|
|
25282
|
-
return
|
|
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
|
|
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
|
-
|
|
25645
|
-
|
|
25646
|
-
|
|
25647
|
-
|
|
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
|
-
|
|
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
|
|
26009
|
-
import { join as
|
|
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
|
|
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 =
|
|
26034
|
-
const configPath =
|
|
26035
|
-
if (!
|
|
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 =
|
|
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 =
|
|
26056
|
-
const configPath =
|
|
26057
|
-
if (!
|
|
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 (
|
|
26062
|
-
settings = JSON.parse(
|
|
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(
|
|
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
|
|
26303
|
-
import { join as
|
|
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 =
|
|
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 (!
|
|
26536
|
+
if (!existsSync9(CATALOG_CACHE_PATH))
|
|
26317
26537
|
return null;
|
|
26318
|
-
const parsed = JSON.parse(
|
|
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(
|
|
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
|
|
27002
|
-
import { dirname as dirname3, join as
|
|
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
|
-
|
|
27009
|
-
|
|
27010
|
-
|
|
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 (!
|
|
27233
|
+
if (!existsSync10(candidate))
|
|
27014
27234
|
continue;
|
|
27015
27235
|
try {
|
|
27016
|
-
const pkg = JSON.parse(
|
|
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>;
|
package/dist/lib/registry.d.ts
CHANGED
|
@@ -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;
|