@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/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
|
|
16828
|
-
import { join as
|
|
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
|
|
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 (!
|
|
16839
|
+
if (!existsSync8(file))
|
|
16836
16840
|
return null;
|
|
16837
|
-
const data = JSON.parse(
|
|
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 (!
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
30868
|
-
var
|
|
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
|
|
31026
|
+
return SECRET_KEY_PATTERN2.test(key) || SECRET_FLAG_PATTERN.test(key);
|
|
30878
31027
|
}
|
|
30879
31028
|
function isSecretValue(value) {
|
|
30880
|
-
return
|
|
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
|
|
31105
|
-
import { join as
|
|
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
|
|
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 =
|
|
31130
|
-
const configPath =
|
|
31131
|
-
if (!
|
|
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 =
|
|
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 =
|
|
31152
|
-
const configPath =
|
|
31153
|
-
if (!
|
|
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 (
|
|
31158
|
-
settings = JSON.parse(
|
|
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(
|
|
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
|
|
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
|
-
|
|
33807
|
-
|
|
33808
|
-
|
|
33809
|
-
|
|
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
|
-
|
|
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
|
|
34049
|
-
import { join as
|
|
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 =
|
|
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 (!
|
|
34250
|
+
if (!existsSync10(CATALOG_CACHE_PATH))
|
|
34063
34251
|
return null;
|
|
34064
|
-
const parsed = JSON.parse(
|
|
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(
|
|
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({
|
|
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:
|
|
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";
|