@hasna/mcps 0.0.14 → 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 +967 -182
- package/bin/mcp.js +605 -95
- package/dist/index.d.ts +5 -2
- package/dist/index.js +523 -53
- package/dist/lib/credentials.d.ts +18 -0
- package/dist/lib/doctor.d.ts +4 -1
- package/dist/lib/install.d.ts +5 -1
- package/dist/lib/local-command-consent.d.ts +38 -0
- package/dist/lib/proxy.d.ts +6 -2
- package/dist/lib/registry.d.ts +4 -2
- package/dist/lib/remote.d.ts +4 -1
- package/dist/mcp/index.js +605 -95
- package/dist/types.d.ts +15 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -12040,6 +12040,7 @@ function getDb() {
|
|
|
12040
12040
|
command TEXT NOT NULL,
|
|
12041
12041
|
args TEXT NOT NULL DEFAULT '[]',
|
|
12042
12042
|
env TEXT NOT NULL DEFAULT '{}',
|
|
12043
|
+
credential_refs TEXT NOT NULL DEFAULT '{}',
|
|
12043
12044
|
transport TEXT NOT NULL DEFAULT 'stdio',
|
|
12044
12045
|
url TEXT,
|
|
12045
12046
|
source TEXT NOT NULL DEFAULT 'local',
|
|
@@ -12066,6 +12067,9 @@ function getDb() {
|
|
|
12066
12067
|
try {
|
|
12067
12068
|
db.exec("ALTER TABLE servers ADD COLUMN last_error TEXT");
|
|
12068
12069
|
} catch {}
|
|
12070
|
+
try {
|
|
12071
|
+
db.exec("ALTER TABLE servers ADD COLUMN credential_refs TEXT NOT NULL DEFAULT '{}'");
|
|
12072
|
+
} catch {}
|
|
12069
12073
|
db.exec(`
|
|
12070
12074
|
CREATE TABLE IF NOT EXISTS sources (
|
|
12071
12075
|
id TEXT PRIMARY KEY,
|
|
@@ -19126,17 +19130,17 @@ __export(exports_sources, {
|
|
|
19126
19130
|
clearCache: () => clearCache,
|
|
19127
19131
|
addSource: () => addSource
|
|
19128
19132
|
});
|
|
19129
|
-
import { mkdirSync as mkdirSync6, existsSync as
|
|
19130
|
-
import { join as
|
|
19133
|
+
import { mkdirSync as mkdirSync6, existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync as readdirSync3, unlinkSync } from "fs";
|
|
19134
|
+
import { join as join8 } from "path";
|
|
19131
19135
|
function getCacheFile(sourceId) {
|
|
19132
|
-
return
|
|
19136
|
+
return join8(CACHE_DIR, `${sourceId}.json`);
|
|
19133
19137
|
}
|
|
19134
19138
|
function readCache(sourceId) {
|
|
19135
19139
|
try {
|
|
19136
19140
|
const file = getCacheFile(sourceId);
|
|
19137
|
-
if (!
|
|
19141
|
+
if (!existsSync7(file))
|
|
19138
19142
|
return null;
|
|
19139
|
-
const data = JSON.parse(
|
|
19143
|
+
const data = JSON.parse(readFileSync3(file, "utf-8"));
|
|
19140
19144
|
return data;
|
|
19141
19145
|
} catch {
|
|
19142
19146
|
return null;
|
|
@@ -19150,7 +19154,7 @@ function writeCache(sourceId, results) {
|
|
|
19150
19154
|
}
|
|
19151
19155
|
function clearCache(sourceId) {
|
|
19152
19156
|
try {
|
|
19153
|
-
if (!
|
|
19157
|
+
if (!existsSync7(CACHE_DIR))
|
|
19154
19158
|
return;
|
|
19155
19159
|
const files = readdirSync3(CACHE_DIR);
|
|
19156
19160
|
for (const file of files) {
|
|
@@ -19158,7 +19162,7 @@ function clearCache(sourceId) {
|
|
|
19158
19162
|
continue;
|
|
19159
19163
|
if (!sourceId || file.startsWith(`${sourceId}.`)) {
|
|
19160
19164
|
try {
|
|
19161
|
-
unlinkSync(
|
|
19165
|
+
unlinkSync(join8(CACHE_DIR, file));
|
|
19162
19166
|
} catch {}
|
|
19163
19167
|
}
|
|
19164
19168
|
}
|
|
@@ -19404,7 +19408,7 @@ var CACHE_DIR, DEFAULT_TTL_MS;
|
|
|
19404
19408
|
var init_sources = __esm(() => {
|
|
19405
19409
|
init_db();
|
|
19406
19410
|
init_config2();
|
|
19407
|
-
CACHE_DIR =
|
|
19411
|
+
CACHE_DIR = join8(MCPS_DIR, "cache");
|
|
19408
19412
|
DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
19409
19413
|
});
|
|
19410
19414
|
|
|
@@ -19412,7 +19416,7 @@ var init_sources = __esm(() => {
|
|
|
19412
19416
|
var require_package = __commonJS((exports, module) => {
|
|
19413
19417
|
module.exports = {
|
|
19414
19418
|
name: "@hasna/mcps",
|
|
19415
|
-
version: "0.0.
|
|
19419
|
+
version: "0.0.16",
|
|
19416
19420
|
description: "Meta-MCP registry & CLI \u2014 discover, manage, and proxy MCP servers",
|
|
19417
19421
|
type: "module",
|
|
19418
19422
|
repository: {
|
|
@@ -21142,10 +21146,164 @@ var {
|
|
|
21142
21146
|
import React10 from "react";
|
|
21143
21147
|
import { render } from "ink";
|
|
21144
21148
|
import chalk2 from "chalk";
|
|
21145
|
-
import { readFileSync as
|
|
21149
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
21146
21150
|
|
|
21147
21151
|
// src/lib/registry.ts
|
|
21148
21152
|
init_db();
|
|
21153
|
+
|
|
21154
|
+
// src/lib/credentials.ts
|
|
21155
|
+
init_config2();
|
|
21156
|
+
import { existsSync as existsSync6, readFileSync as readFileSync2 } from "fs";
|
|
21157
|
+
import { join as join7 } from "path";
|
|
21158
|
+
|
|
21159
|
+
class CredentialReferenceError extends Error {
|
|
21160
|
+
constructor(message) {
|
|
21161
|
+
super(message);
|
|
21162
|
+
this.name = "CredentialReferenceError";
|
|
21163
|
+
}
|
|
21164
|
+
}
|
|
21165
|
+
var SECRET_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
|
|
21166
|
+
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_-]+)$/;
|
|
21167
|
+
var REDACTED_CREDENTIAL_VALUE = "<redacted>";
|
|
21168
|
+
function normalizeKey(key) {
|
|
21169
|
+
return key.trim();
|
|
21170
|
+
}
|
|
21171
|
+
function isRecord(value) {
|
|
21172
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21173
|
+
}
|
|
21174
|
+
function isSecretLikeEnvKey(key) {
|
|
21175
|
+
return SECRET_KEY_PATTERN.test(key);
|
|
21176
|
+
}
|
|
21177
|
+
function isSecretLikeValue(value) {
|
|
21178
|
+
return SECRET_VALUE_PATTERN.test(value.trim());
|
|
21179
|
+
}
|
|
21180
|
+
function normalizeCredentialRef(ref) {
|
|
21181
|
+
const source = ref.source;
|
|
21182
|
+
if (source !== "env" && source !== "local-vault" && source !== "hosted") {
|
|
21183
|
+
throw new CredentialReferenceError(`Unsupported credential reference source: ${String(source)}`);
|
|
21184
|
+
}
|
|
21185
|
+
const name = ref.name?.trim();
|
|
21186
|
+
if (!name) {
|
|
21187
|
+
throw new CredentialReferenceError("Credential reference name is required");
|
|
21188
|
+
}
|
|
21189
|
+
return {
|
|
21190
|
+
source,
|
|
21191
|
+
name,
|
|
21192
|
+
required: ref.required !== false,
|
|
21193
|
+
...ref.description ? { description: ref.description } : {}
|
|
21194
|
+
};
|
|
21195
|
+
}
|
|
21196
|
+
function normalizeCredentialRefs(refs) {
|
|
21197
|
+
const normalized = {};
|
|
21198
|
+
for (const [rawKey, ref] of Object.entries(refs ?? {})) {
|
|
21199
|
+
const key = normalizeKey(rawKey);
|
|
21200
|
+
if (!key)
|
|
21201
|
+
throw new CredentialReferenceError("Credential reference env key is required");
|
|
21202
|
+
normalized[key] = normalizeCredentialRef(ref);
|
|
21203
|
+
}
|
|
21204
|
+
return normalized;
|
|
21205
|
+
}
|
|
21206
|
+
function parseCredentialRefs(value) {
|
|
21207
|
+
if (!isRecord(value))
|
|
21208
|
+
return {};
|
|
21209
|
+
const refs = {};
|
|
21210
|
+
for (const [key, ref] of Object.entries(value)) {
|
|
21211
|
+
if (!isRecord(ref))
|
|
21212
|
+
continue;
|
|
21213
|
+
const source = ref.source;
|
|
21214
|
+
const name = ref.name;
|
|
21215
|
+
if ((source === "env" || source === "local-vault" || source === "hosted") && typeof name === "string" && name.trim()) {
|
|
21216
|
+
refs[key] = normalizeCredentialRef({
|
|
21217
|
+
source,
|
|
21218
|
+
name,
|
|
21219
|
+
required: typeof ref.required === "boolean" ? ref.required : true,
|
|
21220
|
+
description: typeof ref.description === "string" ? ref.description : undefined
|
|
21221
|
+
});
|
|
21222
|
+
}
|
|
21223
|
+
}
|
|
21224
|
+
return refs;
|
|
21225
|
+
}
|
|
21226
|
+
function normalizeLiteralEnv(env) {
|
|
21227
|
+
const normalized = {};
|
|
21228
|
+
for (const [rawKey, rawValue] of Object.entries(env ?? {})) {
|
|
21229
|
+
const key = normalizeKey(rawKey);
|
|
21230
|
+
if (!key)
|
|
21231
|
+
continue;
|
|
21232
|
+
const value = String(rawValue);
|
|
21233
|
+
if (isSecretLikeEnvKey(key) || isSecretLikeValue(value)) {
|
|
21234
|
+
throw new CredentialReferenceError(`Refusing to store raw secret-like env value for "${key}". Use a credential reference instead.`);
|
|
21235
|
+
}
|
|
21236
|
+
normalized[key] = value;
|
|
21237
|
+
}
|
|
21238
|
+
return normalized;
|
|
21239
|
+
}
|
|
21240
|
+
function redactEnv(env) {
|
|
21241
|
+
const redacted = {};
|
|
21242
|
+
for (const [key, value] of Object.entries(env)) {
|
|
21243
|
+
redacted[key] = isSecretLikeEnvKey(key) || isSecretLikeValue(value) ? REDACTED_CREDENTIAL_VALUE : value;
|
|
21244
|
+
}
|
|
21245
|
+
return redacted;
|
|
21246
|
+
}
|
|
21247
|
+
function redactServerCredentials(server) {
|
|
21248
|
+
return {
|
|
21249
|
+
...server,
|
|
21250
|
+
env: redactEnv(server.env),
|
|
21251
|
+
credentialRefs: normalizeCredentialRefs(server.credentialRefs)
|
|
21252
|
+
};
|
|
21253
|
+
}
|
|
21254
|
+
function readLocalVault() {
|
|
21255
|
+
const path = process.env.HASNA_MCPS_CREDENTIAL_VAULT_PATH ?? join7(MCPS_DIR, "credentials.local.json");
|
|
21256
|
+
if (!existsSync6(path))
|
|
21257
|
+
return {};
|
|
21258
|
+
const parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
21259
|
+
if (!isRecord(parsed))
|
|
21260
|
+
return {};
|
|
21261
|
+
const values = {};
|
|
21262
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
21263
|
+
if (typeof value === "string")
|
|
21264
|
+
values[key] = value;
|
|
21265
|
+
}
|
|
21266
|
+
return values;
|
|
21267
|
+
}
|
|
21268
|
+
function resolveCredentialRef(envKey, ref) {
|
|
21269
|
+
if (ref.source === "env") {
|
|
21270
|
+
const value = process.env[ref.name];
|
|
21271
|
+
if (value === undefined && ref.required !== false) {
|
|
21272
|
+
throw new CredentialReferenceError(`Missing required environment credential "${ref.name}" for "${envKey}"`);
|
|
21273
|
+
}
|
|
21274
|
+
return value;
|
|
21275
|
+
}
|
|
21276
|
+
if (ref.source === "local-vault") {
|
|
21277
|
+
const value = readLocalVault()[ref.name];
|
|
21278
|
+
if (value === undefined && ref.required !== false) {
|
|
21279
|
+
throw new CredentialReferenceError(`Missing required local vault credential "${ref.name}" for "${envKey}"`);
|
|
21280
|
+
}
|
|
21281
|
+
return value;
|
|
21282
|
+
}
|
|
21283
|
+
if (ref.required !== false) {
|
|
21284
|
+
throw new CredentialReferenceError(`Hosted credential "${ref.name}" for "${envKey}" cannot be resolved by the local runtime`);
|
|
21285
|
+
}
|
|
21286
|
+
return;
|
|
21287
|
+
}
|
|
21288
|
+
function resolveServerEnv(server) {
|
|
21289
|
+
const resolved = { ...server.env };
|
|
21290
|
+
const refs = normalizeCredentialRefs(server.credentialRefs);
|
|
21291
|
+
for (const [envKey, ref] of Object.entries(refs)) {
|
|
21292
|
+
const value = resolveCredentialRef(envKey, ref);
|
|
21293
|
+
if (value !== undefined)
|
|
21294
|
+
resolved[envKey] = value;
|
|
21295
|
+
}
|
|
21296
|
+
return resolved;
|
|
21297
|
+
}
|
|
21298
|
+
function credentialRefPlaceholders(refs) {
|
|
21299
|
+
const placeholders = {};
|
|
21300
|
+
for (const key of Object.keys(refs ?? {})) {
|
|
21301
|
+
placeholders[key] = REDACTED_CREDENTIAL_VALUE;
|
|
21302
|
+
}
|
|
21303
|
+
return placeholders;
|
|
21304
|
+
}
|
|
21305
|
+
|
|
21306
|
+
// src/lib/registry.ts
|
|
21149
21307
|
function parseRow(row) {
|
|
21150
21308
|
return {
|
|
21151
21309
|
id: row.id,
|
|
@@ -21154,6 +21312,7 @@ function parseRow(row) {
|
|
|
21154
21312
|
command: row.command,
|
|
21155
21313
|
args: safeJsonParse(row.args, []),
|
|
21156
21314
|
env: safeJsonParse(row.env, {}),
|
|
21315
|
+
credentialRefs: parseCredentialRefs(safeJsonParse(row.credential_refs, {})),
|
|
21157
21316
|
transport: row.transport,
|
|
21158
21317
|
url: row.url || null,
|
|
21159
21318
|
source: row.source,
|
|
@@ -21221,9 +21380,9 @@ function addServer(opts) {
|
|
|
21221
21380
|
if (!id) {
|
|
21222
21381
|
throw new Error("Unable to generate a valid server ID");
|
|
21223
21382
|
}
|
|
21224
|
-
const row = db2.prepare(`INSERT INTO servers (id, name, description, command, args, env, transport, url, source)
|
|
21225
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
21226
|
-
RETURNING *`).get(id, name, opts.description || null, command, JSON.stringify(opts.args || []), JSON.stringify(opts.env
|
|
21383
|
+
const row = db2.prepare(`INSERT INTO servers (id, name, description, command, args, env, credential_refs, transport, url, source)
|
|
21384
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
21385
|
+
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");
|
|
21227
21386
|
return parseRow(row);
|
|
21228
21387
|
}
|
|
21229
21388
|
function removeServer(id) {
|
|
@@ -21262,7 +21421,11 @@ function updateServer(id, updates) {
|
|
|
21262
21421
|
}
|
|
21263
21422
|
if (updates.env !== undefined) {
|
|
21264
21423
|
sets.push("env = ?");
|
|
21265
|
-
values.push(JSON.stringify(updates.env));
|
|
21424
|
+
values.push(JSON.stringify(normalizeLiteralEnv(updates.env)));
|
|
21425
|
+
}
|
|
21426
|
+
if (updates.credentialRefs !== undefined) {
|
|
21427
|
+
sets.push("credential_refs = ?");
|
|
21428
|
+
values.push(JSON.stringify(normalizeCredentialRefs(updates.credentialRefs)));
|
|
21266
21429
|
}
|
|
21267
21430
|
if (updates.transport !== undefined) {
|
|
21268
21431
|
sets.push("transport = ?");
|
|
@@ -21296,7 +21459,7 @@ function setServerEnv(id, key, value) {
|
|
|
21296
21459
|
if (!server)
|
|
21297
21460
|
throw new Error(`Server "${id}" not found`);
|
|
21298
21461
|
const env = { ...server.env, [key]: value };
|
|
21299
|
-
db2.prepare("UPDATE servers SET env = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(env), id);
|
|
21462
|
+
db2.prepare("UPDATE servers SET env = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(normalizeLiteralEnv(env)), id);
|
|
21300
21463
|
}
|
|
21301
21464
|
function unsetServerEnv(id, key) {
|
|
21302
21465
|
const db2 = getDb();
|
|
@@ -21307,6 +21470,23 @@ function unsetServerEnv(id, key) {
|
|
|
21307
21470
|
delete env[key];
|
|
21308
21471
|
db2.prepare("UPDATE servers SET env = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(env), id);
|
|
21309
21472
|
}
|
|
21473
|
+
function setServerCredentialRef(id, key, ref) {
|
|
21474
|
+
const db2 = getDb();
|
|
21475
|
+
const server = getServer(id);
|
|
21476
|
+
if (!server)
|
|
21477
|
+
throw new Error(`Server "${id}" not found`);
|
|
21478
|
+
const credentialRefs = normalizeCredentialRefs({ ...server.credentialRefs ?? {}, [key]: ref });
|
|
21479
|
+
db2.prepare("UPDATE servers SET credential_refs = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(credentialRefs), id);
|
|
21480
|
+
}
|
|
21481
|
+
function unsetServerCredentialRef(id, key) {
|
|
21482
|
+
const db2 = getDb();
|
|
21483
|
+
const server = getServer(id);
|
|
21484
|
+
if (!server)
|
|
21485
|
+
throw new Error(`Server "${id}" not found`);
|
|
21486
|
+
const credentialRefs = { ...server.credentialRefs ?? {} };
|
|
21487
|
+
delete credentialRefs[key];
|
|
21488
|
+
db2.prepare("UPDATE servers SET credential_refs = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(normalizeCredentialRefs(credentialRefs)), id);
|
|
21489
|
+
}
|
|
21310
21490
|
function cacheTools(serverId, tools) {
|
|
21311
21491
|
const db2 = getDb();
|
|
21312
21492
|
const insert = db2.prepare("INSERT INTO tool_cache (server_id, name, description, input_schema) VALUES (?, ?, ?, ?)");
|
|
@@ -21346,6 +21526,7 @@ function cloneServer(id, newName) {
|
|
|
21346
21526
|
command: server.command,
|
|
21347
21527
|
args: server.args,
|
|
21348
21528
|
env: server.env,
|
|
21529
|
+
credentialRefs: server.credentialRefs,
|
|
21349
21530
|
transport: server.transport,
|
|
21350
21531
|
url: server.url ?? undefined,
|
|
21351
21532
|
source: server.source
|
|
@@ -35591,6 +35772,185 @@ class StreamableHTTPClientTransport {
|
|
|
35591
35772
|
// src/lib/proxy.ts
|
|
35592
35773
|
init_config2();
|
|
35593
35774
|
init_db();
|
|
35775
|
+
|
|
35776
|
+
// src/lib/local-command-consent.ts
|
|
35777
|
+
class LocalCommandConsentError extends Error {
|
|
35778
|
+
review;
|
|
35779
|
+
constructor(message, review) {
|
|
35780
|
+
super(message);
|
|
35781
|
+
this.name = "LocalCommandConsentError";
|
|
35782
|
+
this.review = review;
|
|
35783
|
+
}
|
|
35784
|
+
}
|
|
35785
|
+
var SHELL_COMMANDS = new Set(["bash", "sh", "zsh", "fish", "cmd", "cmd.exe", "powershell", "powershell.exe", "pwsh"]);
|
|
35786
|
+
var DESTRUCTIVE_COMMANDS = new Set([
|
|
35787
|
+
"rm",
|
|
35788
|
+
"dd",
|
|
35789
|
+
"mkfs",
|
|
35790
|
+
"shutdown",
|
|
35791
|
+
"reboot",
|
|
35792
|
+
"poweroff",
|
|
35793
|
+
"halt",
|
|
35794
|
+
"killall",
|
|
35795
|
+
"pkill"
|
|
35796
|
+
]);
|
|
35797
|
+
var SHELL_EVAL_FLAGS = new Set(["-c", "/c", "-Command", "-command", "-EncodedCommand", "-encodedcommand"]);
|
|
35798
|
+
var SECRET_FLAG_PATTERN = /(?:^|[-_])(api[-_]?key|token|secret|password|passwd|credential|auth|private[-_]?key)(?:$|[-_])/i;
|
|
35799
|
+
var SECRET_KEY_PATTERN2 = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
|
|
35800
|
+
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_-]+)$/;
|
|
35801
|
+
var SHELL_META_PATTERN = /[;&|`<>]|\$\(/;
|
|
35802
|
+
function commandBase(command) {
|
|
35803
|
+
return command.trim().split(/[\\/]/).pop()?.toLowerCase() || command.trim().toLowerCase();
|
|
35804
|
+
}
|
|
35805
|
+
function normalizeArgs(args) {
|
|
35806
|
+
return (args ?? []).map((arg) => String(arg));
|
|
35807
|
+
}
|
|
35808
|
+
function isSecretKey(key) {
|
|
35809
|
+
return SECRET_KEY_PATTERN2.test(key) || SECRET_FLAG_PATTERN.test(key);
|
|
35810
|
+
}
|
|
35811
|
+
function isSecretValue(value) {
|
|
35812
|
+
return SECRET_VALUE_PATTERN2.test(value.trim());
|
|
35813
|
+
}
|
|
35814
|
+
function isSecretAssignment(arg) {
|
|
35815
|
+
const eqIdx = arg.indexOf("=");
|
|
35816
|
+
if (eqIdx <= 0)
|
|
35817
|
+
return false;
|
|
35818
|
+
const key = arg.slice(0, eqIdx);
|
|
35819
|
+
const value = arg.slice(eqIdx + 1);
|
|
35820
|
+
return isSecretKey(key) || isSecretValue(value);
|
|
35821
|
+
}
|
|
35822
|
+
function isSecretArg(args, index) {
|
|
35823
|
+
const arg = args[index] ?? "";
|
|
35824
|
+
const previous = args[index - 1] ?? "";
|
|
35825
|
+
return isSecretAssignment(arg) || isSecretValue(arg) || SECRET_FLAG_PATTERN.test(previous);
|
|
35826
|
+
}
|
|
35827
|
+
function quoteArg(value) {
|
|
35828
|
+
return JSON.stringify(value);
|
|
35829
|
+
}
|
|
35830
|
+
function displayCommand(command, args) {
|
|
35831
|
+
return [quoteArg(command), ...args.map((arg, index) => quoteArg(isSecretArg(args, index) ? "<redacted>" : arg))].join(" ");
|
|
35832
|
+
}
|
|
35833
|
+
function pushRisk(risks, risk) {
|
|
35834
|
+
if (risks.some((existing) => existing.code === risk.code && existing.evidence === risk.evidence))
|
|
35835
|
+
return;
|
|
35836
|
+
risks.push(risk);
|
|
35837
|
+
}
|
|
35838
|
+
function inspectRisks(command, args, env) {
|
|
35839
|
+
const risks = [];
|
|
35840
|
+
const base = commandBase(command);
|
|
35841
|
+
const joined = [command, ...args].join(" ");
|
|
35842
|
+
if (SHELL_COMMANDS.has(base)) {
|
|
35843
|
+
pushRisk(risks, {
|
|
35844
|
+
code: "shell_interpreter",
|
|
35845
|
+
severity: "warning",
|
|
35846
|
+
message: "Command launches a shell interpreter.",
|
|
35847
|
+
evidence: base
|
|
35848
|
+
});
|
|
35849
|
+
if (args.some((arg) => SHELL_EVAL_FLAGS.has(arg))) {
|
|
35850
|
+
pushRisk(risks, {
|
|
35851
|
+
code: "shell_eval",
|
|
35852
|
+
severity: "danger",
|
|
35853
|
+
message: "Shell command evaluates an inline script.",
|
|
35854
|
+
evidence: base
|
|
35855
|
+
});
|
|
35856
|
+
}
|
|
35857
|
+
}
|
|
35858
|
+
if (base === "sudo") {
|
|
35859
|
+
pushRisk(risks, {
|
|
35860
|
+
code: "privilege_escalation",
|
|
35861
|
+
severity: "danger",
|
|
35862
|
+
message: "Command requests elevated privileges.",
|
|
35863
|
+
evidence: base
|
|
35864
|
+
});
|
|
35865
|
+
}
|
|
35866
|
+
if (DESTRUCTIVE_COMMANDS.has(base) || /\brm\s+-[^\s]*[rf][^\s]*\b/.test(joined) || /--no-preserve-root\b/.test(joined)) {
|
|
35867
|
+
pushRisk(risks, {
|
|
35868
|
+
code: "destructive_command",
|
|
35869
|
+
severity: "danger",
|
|
35870
|
+
message: "Command includes a destructive system operation.",
|
|
35871
|
+
evidence: base
|
|
35872
|
+
});
|
|
35873
|
+
}
|
|
35874
|
+
if (/\b(curl|wget)\b[\s\S]*\|[\s\S]*\b(sh|bash|zsh|fish)\b/.test(joined)) {
|
|
35875
|
+
pushRisk(risks, {
|
|
35876
|
+
code: "download_pipe_shell",
|
|
35877
|
+
severity: "danger",
|
|
35878
|
+
message: "Command downloads remote content and pipes it to a shell."
|
|
35879
|
+
});
|
|
35880
|
+
}
|
|
35881
|
+
if ([command, ...args].some((part) => SHELL_META_PATTERN.test(part))) {
|
|
35882
|
+
pushRisk(risks, {
|
|
35883
|
+
code: "shell_metacharacters",
|
|
35884
|
+
severity: "warning",
|
|
35885
|
+
message: "Command or arguments contain shell metacharacters."
|
|
35886
|
+
});
|
|
35887
|
+
}
|
|
35888
|
+
if (args.some((arg, index) => isSecretArg(args, index))) {
|
|
35889
|
+
pushRisk(risks, {
|
|
35890
|
+
code: "inline_secret",
|
|
35891
|
+
severity: "danger",
|
|
35892
|
+
message: "Command arguments appear to contain inline secret material."
|
|
35893
|
+
});
|
|
35894
|
+
}
|
|
35895
|
+
const secretEnvKeys = Object.keys(env).filter(isSecretKey).sort();
|
|
35896
|
+
if (secretEnvKeys.length > 0) {
|
|
35897
|
+
pushRisk(risks, {
|
|
35898
|
+
code: "secret_env",
|
|
35899
|
+
severity: "warning",
|
|
35900
|
+
message: "Environment contains secret-like keys; values are redacted from consent output.",
|
|
35901
|
+
evidence: secretEnvKeys.join(", ")
|
|
35902
|
+
});
|
|
35903
|
+
}
|
|
35904
|
+
return risks;
|
|
35905
|
+
}
|
|
35906
|
+
function inspectLocalCommand(input) {
|
|
35907
|
+
const args = normalizeArgs(input.args);
|
|
35908
|
+
const env = input.env ?? {};
|
|
35909
|
+
const transport = input.transport ?? "stdio";
|
|
35910
|
+
const risks = inspectRisks(input.command, args, env);
|
|
35911
|
+
return {
|
|
35912
|
+
requiresConsent: transport === "stdio",
|
|
35913
|
+
operation: input.operation ?? "launch",
|
|
35914
|
+
command: input.command,
|
|
35915
|
+
args,
|
|
35916
|
+
displayCommand: displayCommand(input.command, args),
|
|
35917
|
+
envKeys: Object.keys(env).sort(),
|
|
35918
|
+
risks,
|
|
35919
|
+
hasDangerousRisk: risks.some((risk) => risk.severity === "danger")
|
|
35920
|
+
};
|
|
35921
|
+
}
|
|
35922
|
+
function formatLocalCommandReview(review) {
|
|
35923
|
+
const lines = [
|
|
35924
|
+
`Command: ${review.displayCommand}`,
|
|
35925
|
+
review.envKeys.length > 0 ? `Env keys: ${review.envKeys.join(", ")}` : "Env keys: <none>"
|
|
35926
|
+
];
|
|
35927
|
+
if (review.risks.length > 0) {
|
|
35928
|
+
lines.push("Risks:");
|
|
35929
|
+
for (const risk of review.risks) {
|
|
35930
|
+
lines.push(`- ${risk.severity}: ${risk.code} - ${risk.message}${risk.evidence ? ` (${risk.evidence})` : ""}`);
|
|
35931
|
+
}
|
|
35932
|
+
} else {
|
|
35933
|
+
lines.push("Risks: none detected");
|
|
35934
|
+
}
|
|
35935
|
+
return lines.join(`
|
|
35936
|
+
`);
|
|
35937
|
+
}
|
|
35938
|
+
function assertLocalCommandConsent(input, consent = {}) {
|
|
35939
|
+
const review = inspectLocalCommand(input);
|
|
35940
|
+
if (!review.requiresConsent)
|
|
35941
|
+
return review;
|
|
35942
|
+
if (consent.approved !== true) {
|
|
35943
|
+
throw new LocalCommandConsentError(`local stdio command approval is required before ${review.operation}.
|
|
35944
|
+
${formatLocalCommandReview(review)}`, review);
|
|
35945
|
+
}
|
|
35946
|
+
if (review.hasDangerousRisk && consent.allowRisky !== true) {
|
|
35947
|
+
throw new LocalCommandConsentError(`risky command approval is required before ${review.operation}.
|
|
35948
|
+
${formatLocalCommandReview(review)}`, review);
|
|
35949
|
+
}
|
|
35950
|
+
return review;
|
|
35951
|
+
}
|
|
35952
|
+
|
|
35953
|
+
// src/lib/proxy.ts
|
|
35594
35954
|
var connections = new Map;
|
|
35595
35955
|
var inflightConnections = new Map;
|
|
35596
35956
|
var CONNECT_CONCURRENCY = 4;
|
|
@@ -35617,7 +35977,7 @@ function requireUrl(entry) {
|
|
|
35617
35977
|
throw new Error(`Server "${entry.id}" has an invalid URL: ${entry.url}`);
|
|
35618
35978
|
}
|
|
35619
35979
|
}
|
|
35620
|
-
async function connectToServer(entry) {
|
|
35980
|
+
async function connectToServer(entry, options = {}) {
|
|
35621
35981
|
if (connections.has(entry.id)) {
|
|
35622
35982
|
return connections.get(entry.id);
|
|
35623
35983
|
}
|
|
@@ -35633,10 +35993,17 @@ async function connectToServer(entry) {
|
|
|
35633
35993
|
if (!entry.command?.trim()) {
|
|
35634
35994
|
throw new Error(`Server "${entry.id}" is missing a command`);
|
|
35635
35995
|
}
|
|
35996
|
+
assertLocalCommandConsent({
|
|
35997
|
+
command: entry.command,
|
|
35998
|
+
args: entry.args,
|
|
35999
|
+
env: { ...entry.env, ...credentialRefPlaceholders(entry.credentialRefs) },
|
|
36000
|
+
transport: entry.transport,
|
|
36001
|
+
operation: "launch"
|
|
36002
|
+
}, options.localCommandConsent);
|
|
35636
36003
|
transport = new StdioClientTransport({
|
|
35637
36004
|
command: entry.command,
|
|
35638
36005
|
args: entry.args,
|
|
35639
|
-
env: buildEnv(entry
|
|
36006
|
+
env: buildEnv(resolveServerEnv(entry))
|
|
35640
36007
|
});
|
|
35641
36008
|
} else if (entry.transport === "sse") {
|
|
35642
36009
|
transport = new SSEClientTransport(requireUrl(entry));
|
|
@@ -35749,7 +36116,7 @@ async function callTool(prefixedName, args) {
|
|
|
35749
36116
|
]
|
|
35750
36117
|
};
|
|
35751
36118
|
}
|
|
35752
|
-
async function connectAllEnabled() {
|
|
36119
|
+
async function connectAllEnabled(options = {}) {
|
|
35753
36120
|
const servers = listServers().filter((s) => s.enabled);
|
|
35754
36121
|
const results = [];
|
|
35755
36122
|
let index = 0;
|
|
@@ -35761,7 +36128,7 @@ async function connectAllEnabled() {
|
|
|
35761
36128
|
return;
|
|
35762
36129
|
const server = servers[current];
|
|
35763
36130
|
try {
|
|
35764
|
-
const conn = await connectToServer(server);
|
|
36131
|
+
const conn = await connectToServer(server, options);
|
|
35765
36132
|
results.push(conn);
|
|
35766
36133
|
} catch (err) {
|
|
35767
36134
|
console.error(`Failed to connect to ${server.name}: ${err.message}`);
|
|
@@ -35773,13 +36140,28 @@ async function connectAllEnabled() {
|
|
|
35773
36140
|
}
|
|
35774
36141
|
|
|
35775
36142
|
// src/lib/doctor.ts
|
|
35776
|
-
async function diagnoseServer(server) {
|
|
36143
|
+
async function diagnoseServer(server, options = {}) {
|
|
35777
36144
|
const checks4 = [];
|
|
36145
|
+
let hasLocalConsent = true;
|
|
35778
36146
|
if (server.transport === "stdio") {
|
|
36147
|
+
try {
|
|
36148
|
+
assertLocalCommandConsent({
|
|
36149
|
+
command: server.command,
|
|
36150
|
+
args: server.args,
|
|
36151
|
+
env: { ...server.env, ...credentialRefPlaceholders(server.credentialRefs) },
|
|
36152
|
+
transport: server.transport,
|
|
36153
|
+
operation: "diagnose"
|
|
36154
|
+
}, options.localCommandConsent);
|
|
36155
|
+
} catch (err) {
|
|
36156
|
+
hasLocalConsent = false;
|
|
36157
|
+
checks4.push({ name: "local command consent", pass: false, message: err.message });
|
|
36158
|
+
}
|
|
35779
36159
|
try {
|
|
35780
36160
|
const path = execFileSync("which", [server.command], { stdio: "pipe" }).toString().trim();
|
|
35781
36161
|
let version2 = "";
|
|
35782
36162
|
try {
|
|
36163
|
+
if (!hasLocalConsent)
|
|
36164
|
+
throw new Error("local stdio command approval is required before version probing");
|
|
35783
36165
|
version2 = execFileSync(server.command, ["--version"], { stdio: "pipe" }).toString().trim().split(`
|
|
35784
36166
|
`)[0];
|
|
35785
36167
|
} catch {}
|
|
@@ -35795,12 +36177,29 @@ async function diagnoseServer(server) {
|
|
|
35795
36177
|
}
|
|
35796
36178
|
}
|
|
35797
36179
|
const missingEnv = Object.entries(server.env).filter(([, v]) => !v);
|
|
35798
|
-
|
|
35799
|
-
|
|
35800
|
-
|
|
35801
|
-
|
|
36180
|
+
const credentialRefCount = Object.keys(server.credentialRefs ?? {}).length;
|
|
36181
|
+
let credentialError = null;
|
|
36182
|
+
try {
|
|
36183
|
+
resolveServerEnv(server);
|
|
36184
|
+
} catch (err) {
|
|
36185
|
+
credentialError = err.message;
|
|
36186
|
+
}
|
|
36187
|
+
if (Object.keys(server.env).length === 0 && credentialRefCount === 0) {
|
|
36188
|
+
checks4.push({ name: "env vars", pass: true, message: "no env vars or credential refs required" });
|
|
36189
|
+
} else if (missingEnv.length > 0 || credentialError) {
|
|
36190
|
+
const parts = [];
|
|
36191
|
+
if (missingEnv.length > 0)
|
|
36192
|
+
parts.push(`missing literal values for: ${missingEnv.map(([k]) => k).join(", ")}`);
|
|
36193
|
+
if (credentialError)
|
|
36194
|
+
parts.push(credentialError);
|
|
36195
|
+
checks4.push({ name: "env vars", pass: false, message: parts.join("; ") });
|
|
35802
36196
|
} else {
|
|
35803
|
-
|
|
36197
|
+
const literalCount = Object.keys(server.env).length;
|
|
36198
|
+
checks4.push({
|
|
36199
|
+
name: "env vars",
|
|
36200
|
+
pass: true,
|
|
36201
|
+
message: `${literalCount} literal env var(s), ${credentialRefCount} credential ref(s) available`
|
|
36202
|
+
});
|
|
35804
36203
|
}
|
|
35805
36204
|
if (server.transport !== "stdio" && server.url) {
|
|
35806
36205
|
try {
|
|
@@ -35810,10 +36209,10 @@ async function diagnoseServer(server) {
|
|
|
35810
36209
|
checks4.push({ name: "URL reachable", pass: false, message: `unreachable: ${err.message}` });
|
|
35811
36210
|
}
|
|
35812
36211
|
}
|
|
35813
|
-
if (server.enabled) {
|
|
36212
|
+
if (server.enabled && hasLocalConsent) {
|
|
35814
36213
|
try {
|
|
35815
36214
|
await Promise.race([
|
|
35816
|
-
connectToServer(server),
|
|
36215
|
+
connectToServer(server, { localCommandConsent: options.localCommandConsent }),
|
|
35817
36216
|
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout after 10s")), 1e4))
|
|
35818
36217
|
]);
|
|
35819
36218
|
await disconnectServer(server.id);
|
|
@@ -35821,8 +36220,10 @@ async function diagnoseServer(server) {
|
|
|
35821
36220
|
} catch (err) {
|
|
35822
36221
|
checks4.push({ name: "connect & list tools", pass: false, message: err.message });
|
|
35823
36222
|
}
|
|
35824
|
-
} else {
|
|
36223
|
+
} else if (!server.enabled) {
|
|
35825
36224
|
checks4.push({ name: "connect & list tools", pass: true, message: "skipped (server disabled)" });
|
|
36225
|
+
} else {
|
|
36226
|
+
checks4.push({ name: "connect & list tools", pass: false, message: "skipped until local stdio command is approved" });
|
|
35826
36227
|
}
|
|
35827
36228
|
return {
|
|
35828
36229
|
server,
|
|
@@ -35863,7 +36264,7 @@ async function getRegistryServer(id) {
|
|
|
35863
36264
|
const all = entries.map(parseRegistryEntry);
|
|
35864
36265
|
return all.find((s) => s.id === id) || null;
|
|
35865
36266
|
}
|
|
35866
|
-
async function installFromRegistry(id) {
|
|
36267
|
+
async function installFromRegistry(id, options = {}) {
|
|
35867
36268
|
const server = await getRegistryServer(id);
|
|
35868
36269
|
if (!server) {
|
|
35869
36270
|
throw new Error(`Server "${id}" not found in registry`);
|
|
@@ -35883,6 +36284,13 @@ async function installFromRegistry(id) {
|
|
|
35883
36284
|
transport = pkg.transport.type;
|
|
35884
36285
|
}
|
|
35885
36286
|
}
|
|
36287
|
+
assertLocalCommandConsent({
|
|
36288
|
+
command,
|
|
36289
|
+
args,
|
|
36290
|
+
transport,
|
|
36291
|
+
env: {},
|
|
36292
|
+
operation: "register"
|
|
36293
|
+
}, options.localCommandConsent);
|
|
35886
36294
|
return addServer({
|
|
35887
36295
|
name: server.name,
|
|
35888
36296
|
description: server.description,
|
|
@@ -35908,8 +36316,8 @@ init_sources();
|
|
|
35908
36316
|
|
|
35909
36317
|
// src/lib/install.ts
|
|
35910
36318
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
35911
|
-
import { existsSync as
|
|
35912
|
-
import { join as
|
|
36319
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync7 } from "fs";
|
|
36320
|
+
import { join as join9 } from "path";
|
|
35913
36321
|
import { homedir as homedir8 } from "os";
|
|
35914
36322
|
function installToClaude(entry) {
|
|
35915
36323
|
try {
|
|
@@ -35921,7 +36329,7 @@ function installToClaude(entry) {
|
|
|
35921
36329
|
"--scope",
|
|
35922
36330
|
"user"
|
|
35923
36331
|
];
|
|
35924
|
-
for (const [k, v] of Object.entries(entry
|
|
36332
|
+
for (const [k, v] of Object.entries(assertAgentInstallEnv(entry))) {
|
|
35925
36333
|
args.push("--env", `${k}=${v}`);
|
|
35926
36334
|
}
|
|
35927
36335
|
args.push(entry.id, "--", entry.command, ...entry.args);
|
|
@@ -35933,9 +36341,9 @@ function installToClaude(entry) {
|
|
|
35933
36341
|
}
|
|
35934
36342
|
function installToCodex(entry) {
|
|
35935
36343
|
try {
|
|
35936
|
-
const configDir =
|
|
35937
|
-
const configPath =
|
|
35938
|
-
if (!
|
|
36344
|
+
const configDir = join9(homedir8(), ".codex");
|
|
36345
|
+
const configPath = join9(configDir, "config.toml");
|
|
36346
|
+
if (!existsSync8(configDir)) {
|
|
35939
36347
|
mkdirSync7(configDir, { recursive: true });
|
|
35940
36348
|
}
|
|
35941
36349
|
const block = `
|
|
@@ -35943,7 +36351,7 @@ function installToCodex(entry) {
|
|
|
35943
36351
|
` + `command = ${JSON.stringify(entry.command)}
|
|
35944
36352
|
` + `args = [${entry.args.map((a) => JSON.stringify(a)).join(", ")}]
|
|
35945
36353
|
`;
|
|
35946
|
-
const existing =
|
|
36354
|
+
const existing = existsSync8(configPath) ? readFileSync4(configPath, "utf-8") : "";
|
|
35947
36355
|
if (existing.includes(`[mcp_servers.${entry.id}]`)) {
|
|
35948
36356
|
return { agent: "codex", success: true };
|
|
35949
36357
|
}
|
|
@@ -35955,21 +36363,22 @@ function installToCodex(entry) {
|
|
|
35955
36363
|
}
|
|
35956
36364
|
function installToGemini(entry) {
|
|
35957
36365
|
try {
|
|
35958
|
-
const configDir =
|
|
35959
|
-
const configPath =
|
|
35960
|
-
if (!
|
|
36366
|
+
const configDir = join9(homedir8(), ".gemini");
|
|
36367
|
+
const configPath = join9(configDir, "settings.json");
|
|
36368
|
+
if (!existsSync8(configDir)) {
|
|
35961
36369
|
mkdirSync7(configDir, { recursive: true });
|
|
35962
36370
|
}
|
|
35963
36371
|
let settings = {};
|
|
35964
|
-
if (
|
|
35965
|
-
settings = JSON.parse(
|
|
36372
|
+
if (existsSync8(configPath)) {
|
|
36373
|
+
settings = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
35966
36374
|
}
|
|
35967
36375
|
if (!settings.mcpServers)
|
|
35968
36376
|
settings.mcpServers = {};
|
|
36377
|
+
const env = assertAgentInstallEnv(entry);
|
|
35969
36378
|
settings.mcpServers[entry.id] = {
|
|
35970
36379
|
command: entry.command,
|
|
35971
36380
|
args: entry.args,
|
|
35972
|
-
...Object.keys(
|
|
36381
|
+
...Object.keys(env).length > 0 ? { env } : {}
|
|
35973
36382
|
};
|
|
35974
36383
|
writeFileSync3(configPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
35975
36384
|
return { agent: "gemini", success: true };
|
|
@@ -35977,7 +36386,43 @@ function installToGemini(entry) {
|
|
|
35977
36386
|
return { agent: "gemini", success: false, error: err.message };
|
|
35978
36387
|
}
|
|
35979
36388
|
}
|
|
35980
|
-
function
|
|
36389
|
+
function assertAgentInstallEnv(entry) {
|
|
36390
|
+
const refs = entry.credentialRefs ?? {};
|
|
36391
|
+
if (Object.keys(refs).length > 0) {
|
|
36392
|
+
throw new CredentialReferenceError(`Server "${entry.id}" uses credential references; refusing to materialize secrets into local agent config files`);
|
|
36393
|
+
}
|
|
36394
|
+
for (const [key, value] of Object.entries(entry.env)) {
|
|
36395
|
+
if (isSecretLikeEnvKey(key) || isSecretLikeValue(value)) {
|
|
36396
|
+
throw new CredentialReferenceError(`Server "${entry.id}" has legacy raw secret-like env "${key}"; move it to a credential reference before installing to agents`);
|
|
36397
|
+
}
|
|
36398
|
+
}
|
|
36399
|
+
return entry.env;
|
|
36400
|
+
}
|
|
36401
|
+
function installToAgents(entry, targets = ["claude", "codex", "gemini"], options = {}) {
|
|
36402
|
+
try {
|
|
36403
|
+
assertLocalCommandConsent({
|
|
36404
|
+
command: entry.command,
|
|
36405
|
+
args: entry.args,
|
|
36406
|
+
env: { ...entry.env, ...credentialRefPlaceholders(entry.credentialRefs) },
|
|
36407
|
+
transport: entry.transport,
|
|
36408
|
+
operation: "install"
|
|
36409
|
+
}, options.localCommandConsent);
|
|
36410
|
+
} catch (err) {
|
|
36411
|
+
return targets.map((target) => ({
|
|
36412
|
+
agent: target,
|
|
36413
|
+
success: false,
|
|
36414
|
+
error: err.message
|
|
36415
|
+
}));
|
|
36416
|
+
}
|
|
36417
|
+
try {
|
|
36418
|
+
assertAgentInstallEnv(entry);
|
|
36419
|
+
} catch (err) {
|
|
36420
|
+
return targets.map((target) => ({
|
|
36421
|
+
agent: target,
|
|
36422
|
+
success: false,
|
|
36423
|
+
error: err.message
|
|
36424
|
+
}));
|
|
36425
|
+
}
|
|
35981
36426
|
return targets.map((target) => {
|
|
35982
36427
|
if (target === "claude")
|
|
35983
36428
|
return installToClaude(entry);
|
|
@@ -36192,11 +36637,11 @@ function seedDefaultMachines() {
|
|
|
36192
36637
|
// src/lib/fleet.ts
|
|
36193
36638
|
init_config2();
|
|
36194
36639
|
import { spawn as spawn2 } from "child_process";
|
|
36195
|
-
import { existsSync as
|
|
36196
|
-
import { join as
|
|
36640
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
36641
|
+
import { join as join10 } from "path";
|
|
36197
36642
|
var NPM_SEARCH_URL = "https://registry.npmjs.org/-/v1/search";
|
|
36198
36643
|
var NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
36199
|
-
var CATALOG_CACHE_PATH =
|
|
36644
|
+
var CATALOG_CACHE_PATH = join10(MCPS_DIR, "cache", "hasna-catalog.json");
|
|
36200
36645
|
var DEFAULT_CATALOG_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
36201
36646
|
var DEFAULT_REMOTE_TIMEOUT_MS = 180000;
|
|
36202
36647
|
var DEFAULT_HANDSHAKE_TIMEOUT_MS = 2500;
|
|
@@ -36206,9 +36651,9 @@ function normalizeQueryList(values) {
|
|
|
36206
36651
|
}
|
|
36207
36652
|
function readCatalogCache(maxAgeMs) {
|
|
36208
36653
|
try {
|
|
36209
|
-
if (!
|
|
36654
|
+
if (!existsSync9(CATALOG_CACHE_PATH))
|
|
36210
36655
|
return null;
|
|
36211
|
-
const parsed = JSON.parse(
|
|
36656
|
+
const parsed = JSON.parse(readFileSync5(CATALOG_CACHE_PATH, "utf-8"));
|
|
36212
36657
|
if (!parsed.cachedAt || !Array.isArray(parsed.entries))
|
|
36213
36658
|
return null;
|
|
36214
36659
|
if (Date.now() - parsed.cachedAt > maxAgeMs)
|
|
@@ -36220,7 +36665,7 @@ function readCatalogCache(maxAgeMs) {
|
|
|
36220
36665
|
}
|
|
36221
36666
|
function writeCatalogCache(entries) {
|
|
36222
36667
|
try {
|
|
36223
|
-
mkdirSync8(
|
|
36668
|
+
mkdirSync8(join10(MCPS_DIR, "cache"), { recursive: true });
|
|
36224
36669
|
writeFileSync4(CATALOG_CACHE_PATH, JSON.stringify({ cachedAt: Date.now(), entries }, null, 2), "utf-8");
|
|
36225
36670
|
} catch {}
|
|
36226
36671
|
}
|
|
@@ -36978,11 +37423,19 @@ function installProviderProfile(id, options = {}) {
|
|
|
36978
37423
|
if (!command) {
|
|
36979
37424
|
throw new Error(`Provider profile "${id}" does not define an install fallback command`);
|
|
36980
37425
|
}
|
|
37426
|
+
assertLocalCommandConsent({
|
|
37427
|
+
command,
|
|
37428
|
+
args,
|
|
37429
|
+
env: fallback?.env ?? {},
|
|
37430
|
+
transport: useFallback ? "stdio" : profile.transport,
|
|
37431
|
+
operation: "register"
|
|
37432
|
+
}, options.localCommandConsent);
|
|
36981
37433
|
return addServer({
|
|
36982
37434
|
name: options.name ?? profile.displayName,
|
|
36983
37435
|
description: profile.description ?? undefined,
|
|
36984
37436
|
command,
|
|
36985
37437
|
args,
|
|
37438
|
+
env: fallback?.env,
|
|
36986
37439
|
transport: useFallback ? "stdio" : profile.transport,
|
|
36987
37440
|
url: useFallback ? fallback?.url : profile.endpoint ?? undefined,
|
|
36988
37441
|
source: "provider-profile"
|
|
@@ -38216,22 +38669,22 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
38216
38669
|
};
|
|
38217
38670
|
|
|
38218
38671
|
// src/lib/version.ts
|
|
38219
|
-
import { existsSync as
|
|
38220
|
-
import { dirname as dirname3, join as
|
|
38672
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
38673
|
+
import { dirname as dirname3, join as join11 } from "path";
|
|
38221
38674
|
import { fileURLToPath } from "url";
|
|
38222
38675
|
var FALLBACK_VERSION = "0.0.1";
|
|
38223
38676
|
function readPackageVersion(moduleUrl, fallback = FALLBACK_VERSION) {
|
|
38224
38677
|
const baseDir = dirname3(fileURLToPath(moduleUrl));
|
|
38225
38678
|
const candidates = [
|
|
38226
|
-
|
|
38227
|
-
|
|
38228
|
-
|
|
38679
|
+
join11(baseDir, "..", "..", "package.json"),
|
|
38680
|
+
join11(baseDir, "..", "package.json"),
|
|
38681
|
+
join11(baseDir, "package.json")
|
|
38229
38682
|
];
|
|
38230
38683
|
for (const candidate of candidates) {
|
|
38231
|
-
if (!
|
|
38684
|
+
if (!existsSync10(candidate))
|
|
38232
38685
|
continue;
|
|
38233
38686
|
try {
|
|
38234
|
-
const pkg = JSON.parse(
|
|
38687
|
+
const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
|
|
38235
38688
|
if (pkg.version)
|
|
38236
38689
|
return pkg.version;
|
|
38237
38690
|
} catch {}
|
|
@@ -38257,6 +38710,16 @@ function jsonContent(value) {
|
|
|
38257
38710
|
function errorContent(text) {
|
|
38258
38711
|
return { ...textContent(text), isError: true };
|
|
38259
38712
|
}
|
|
38713
|
+
function localConsent(input) {
|
|
38714
|
+
return {
|
|
38715
|
+
approved: input.allow_local_stdio === true,
|
|
38716
|
+
allowRisky: input.allow_risky_command === true,
|
|
38717
|
+
source: "mcp"
|
|
38718
|
+
};
|
|
38719
|
+
}
|
|
38720
|
+
function readCredentialRefs(input) {
|
|
38721
|
+
return normalizeCredentialRefs(input.credential_refs ?? input.credentialRefs);
|
|
38722
|
+
}
|
|
38260
38723
|
function buildMcpTools() {
|
|
38261
38724
|
const definitions = [
|
|
38262
38725
|
{
|
|
@@ -38281,23 +38744,60 @@ function buildMcpTools() {
|
|
|
38281
38744
|
description: exports_external2.string().optional().describe("Description"),
|
|
38282
38745
|
transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("Transport type"),
|
|
38283
38746
|
url: exports_external2.string().optional().describe("URL for remote transports"),
|
|
38284
|
-
env: exports_external2.record(exports_external2.string()).optional().describe("Environment variables")
|
|
38747
|
+
env: exports_external2.record(exports_external2.string()).optional().describe("Environment variables"),
|
|
38748
|
+
credential_refs: exports_external2.record(exports_external2.object({
|
|
38749
|
+
source: exports_external2.enum(["env", "local-vault", "hosted"]),
|
|
38750
|
+
name: exports_external2.string(),
|
|
38751
|
+
required: exports_external2.boolean().optional(),
|
|
38752
|
+
description: exports_external2.string().optional()
|
|
38753
|
+
})).optional().describe("Credential references by server env key"),
|
|
38754
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering this local stdio command"),
|
|
38755
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve registering risky local command patterns")
|
|
38285
38756
|
},
|
|
38286
|
-
run: (
|
|
38287
|
-
command
|
|
38288
|
-
args
|
|
38289
|
-
|
|
38290
|
-
|
|
38291
|
-
transport
|
|
38292
|
-
|
|
38293
|
-
|
|
38294
|
-
|
|
38757
|
+
run: (input) => {
|
|
38758
|
+
const command = String(input.command);
|
|
38759
|
+
const args = Array.isArray(input.args) ? input.args.map(String) : [];
|
|
38760
|
+
const env = isRecordOfStrings(input.env) ? input.env : {};
|
|
38761
|
+
const credentialRefs = readCredentialRefs(input);
|
|
38762
|
+
const transport = input.transport;
|
|
38763
|
+
try {
|
|
38764
|
+
assertLocalCommandConsent({
|
|
38765
|
+
command,
|
|
38766
|
+
args,
|
|
38767
|
+
env: { ...env, ...Object.fromEntries(Object.keys(credentialRefs).map((key) => [key, "<credential-ref>"])) },
|
|
38768
|
+
transport,
|
|
38769
|
+
operation: "register"
|
|
38770
|
+
}, localConsent(input));
|
|
38771
|
+
return jsonContent(addServer({
|
|
38772
|
+
command,
|
|
38773
|
+
args,
|
|
38774
|
+
name: typeof input.name === "string" ? input.name : undefined,
|
|
38775
|
+
description: typeof input.description === "string" ? input.description : undefined,
|
|
38776
|
+
transport,
|
|
38777
|
+
url: typeof input.url === "string" ? input.url : undefined,
|
|
38778
|
+
env,
|
|
38779
|
+
credentialRefs
|
|
38780
|
+
}));
|
|
38781
|
+
} catch (err) {
|
|
38782
|
+
return errorContent(err.message);
|
|
38783
|
+
}
|
|
38784
|
+
}
|
|
38295
38785
|
},
|
|
38296
38786
|
{
|
|
38297
38787
|
name: "install_from_registry",
|
|
38298
38788
|
description: "Install an MCP server from the official registry",
|
|
38299
|
-
paramsSchema: {
|
|
38300
|
-
|
|
38789
|
+
paramsSchema: {
|
|
38790
|
+
id: exports_external2.string().describe("Registry server ID"),
|
|
38791
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering registry stdio commands"),
|
|
38792
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve registering risky local command patterns")
|
|
38793
|
+
},
|
|
38794
|
+
run: async (input) => {
|
|
38795
|
+
try {
|
|
38796
|
+
return jsonContent(await installFromRegistry(String(input.id), { localCommandConsent: localConsent(input) }));
|
|
38797
|
+
} catch (err) {
|
|
38798
|
+
return errorContent(err.message);
|
|
38799
|
+
}
|
|
38800
|
+
}
|
|
38301
38801
|
},
|
|
38302
38802
|
{
|
|
38303
38803
|
name: "remove_server",
|
|
@@ -38343,26 +38843,52 @@ function buildMcpTools() {
|
|
|
38343
38843
|
command: exports_external2.string().optional().describe("New command"),
|
|
38344
38844
|
args: exports_external2.array(exports_external2.string()).optional().describe("New args list"),
|
|
38345
38845
|
transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("New transport type"),
|
|
38346
|
-
url: exports_external2.string().optional().describe("New URL for remote transports")
|
|
38846
|
+
url: exports_external2.string().optional().describe("New URL for remote transports"),
|
|
38847
|
+
credential_refs: exports_external2.record(exports_external2.object({
|
|
38848
|
+
source: exports_external2.enum(["env", "local-vault", "hosted"]),
|
|
38849
|
+
name: exports_external2.string(),
|
|
38850
|
+
required: exports_external2.boolean().optional(),
|
|
38851
|
+
description: exports_external2.string().optional()
|
|
38852
|
+
})).optional().describe("Credential references by server env key"),
|
|
38853
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve updating this local stdio command"),
|
|
38854
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve risky local command patterns")
|
|
38347
38855
|
},
|
|
38348
|
-
run: (
|
|
38349
|
-
const serverId = String(id);
|
|
38856
|
+
run: (input) => {
|
|
38857
|
+
const serverId = String(input.id);
|
|
38350
38858
|
const existing = getServer(serverId);
|
|
38351
38859
|
if (!existing)
|
|
38352
38860
|
return errorContent(`Server "${serverId}" not found.`);
|
|
38353
38861
|
const fields = {};
|
|
38354
|
-
if (typeof name === "string")
|
|
38355
|
-
fields.name = name;
|
|
38356
|
-
if (typeof description === "string")
|
|
38357
|
-
fields.description = description;
|
|
38358
|
-
if (typeof command === "string")
|
|
38359
|
-
fields.command = command;
|
|
38360
|
-
if (Array.isArray(args))
|
|
38361
|
-
fields.args = args.map(String);
|
|
38362
|
-
if (
|
|
38363
|
-
fields.
|
|
38364
|
-
if (
|
|
38365
|
-
fields.
|
|
38862
|
+
if (typeof input.name === "string")
|
|
38863
|
+
fields.name = input.name;
|
|
38864
|
+
if (typeof input.description === "string")
|
|
38865
|
+
fields.description = input.description;
|
|
38866
|
+
if (typeof input.command === "string")
|
|
38867
|
+
fields.command = input.command;
|
|
38868
|
+
if (Array.isArray(input.args))
|
|
38869
|
+
fields.args = input.args.map(String);
|
|
38870
|
+
if (input.credential_refs !== undefined || input.credentialRefs !== undefined)
|
|
38871
|
+
fields.credentialRefs = readCredentialRefs(input);
|
|
38872
|
+
if (input.transport === "stdio" || input.transport === "sse" || input.transport === "streamable-http")
|
|
38873
|
+
fields.transport = input.transport;
|
|
38874
|
+
if (typeof input.url === "string")
|
|
38875
|
+
fields.url = input.url;
|
|
38876
|
+
if (fields.command !== undefined || fields.args !== undefined || fields.transport !== undefined) {
|
|
38877
|
+
try {
|
|
38878
|
+
assertLocalCommandConsent({
|
|
38879
|
+
command: fields.command ?? existing.command,
|
|
38880
|
+
args: fields.args ?? existing.args,
|
|
38881
|
+
env: {
|
|
38882
|
+
...existing.env,
|
|
38883
|
+
...Object.fromEntries(Object.keys(fields.credentialRefs ?? existing.credentialRefs ?? {}).map((key) => [key, "<credential-ref>"]))
|
|
38884
|
+
},
|
|
38885
|
+
transport: fields.transport ?? existing.transport,
|
|
38886
|
+
operation: "register"
|
|
38887
|
+
}, localConsent(input));
|
|
38888
|
+
} catch (err) {
|
|
38889
|
+
return errorContent(err.message);
|
|
38890
|
+
}
|
|
38891
|
+
}
|
|
38366
38892
|
return jsonContent(redactServerEnv(updateServer(serverId, fields)));
|
|
38367
38893
|
}
|
|
38368
38894
|
},
|
|
@@ -38444,13 +38970,16 @@ function buildMcpTools() {
|
|
|
38444
38970
|
paramsSchema: {
|
|
38445
38971
|
id: exports_external2.string().describe("Provider profile ID"),
|
|
38446
38972
|
name: exports_external2.string().optional().describe("Override registered server name"),
|
|
38447
|
-
use_fallback: exports_external2.boolean().optional().describe("Install the stdio fallback command instead of the direct remote transport")
|
|
38973
|
+
use_fallback: exports_external2.boolean().optional().describe("Install the stdio fallback command instead of the direct remote transport"),
|
|
38974
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering provider stdio fallback commands"),
|
|
38975
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve risky local command patterns")
|
|
38448
38976
|
},
|
|
38449
|
-
run: (
|
|
38977
|
+
run: (input) => {
|
|
38450
38978
|
try {
|
|
38451
|
-
return jsonContent(redactServerEnv(installProviderProfile(String(id), {
|
|
38452
|
-
name: typeof name === "string" ? name : undefined,
|
|
38453
|
-
useFallback: use_fallback === true
|
|
38979
|
+
return jsonContent(redactServerEnv(installProviderProfile(String(input.id), {
|
|
38980
|
+
name: typeof input.name === "string" ? input.name : undefined,
|
|
38981
|
+
useFallback: input.use_fallback === true,
|
|
38982
|
+
localCommandConsent: localConsent(input)
|
|
38454
38983
|
})));
|
|
38455
38984
|
} catch (err) {
|
|
38456
38985
|
return errorContent(err.message);
|
|
@@ -38523,15 +39052,19 @@ function buildMcpTools() {
|
|
|
38523
39052
|
description: "Install a registered MCP server into Claude Code, Codex, and/or Gemini",
|
|
38524
39053
|
paramsSchema: {
|
|
38525
39054
|
id: exports_external2.string().describe("Server ID to install (from list_servers)"),
|
|
38526
|
-
targets: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("Target agents to install into (default: all)")
|
|
39055
|
+
targets: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("Target agents to install into (default: all)"),
|
|
39056
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve installing local stdio commands into local agent configs"),
|
|
39057
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve installing risky local command patterns")
|
|
38527
39058
|
},
|
|
38528
|
-
run: (
|
|
38529
|
-
const serverId = String(id);
|
|
39059
|
+
run: (input) => {
|
|
39060
|
+
const serverId = String(input.id);
|
|
38530
39061
|
const entry = getServer(serverId);
|
|
38531
39062
|
if (!entry)
|
|
38532
39063
|
return errorContent(`Server "${serverId}" not found.`);
|
|
38533
|
-
const agentTargets = Array.isArray(targets) ? targets : undefined;
|
|
38534
|
-
return jsonContent(installToAgents(entry, agentTargets ?? ["claude", "codex", "gemini"]
|
|
39064
|
+
const agentTargets = Array.isArray(input.targets) ? input.targets : undefined;
|
|
39065
|
+
return jsonContent(installToAgents(entry, agentTargets ?? ["claude", "codex", "gemini"], {
|
|
39066
|
+
localCommandConsent: localConsent(input)
|
|
39067
|
+
}));
|
|
38535
39068
|
}
|
|
38536
39069
|
},
|
|
38537
39070
|
{
|
|
@@ -38543,11 +39076,14 @@ function buildMcpTools() {
|
|
|
38543
39076
|
{
|
|
38544
39077
|
name: "connect_and_list_tools",
|
|
38545
39078
|
description: "Connect to all enabled MCP servers and list their available tools",
|
|
38546
|
-
paramsSchema: {
|
|
38547
|
-
|
|
39079
|
+
paramsSchema: {
|
|
39080
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching enabled local stdio commands"),
|
|
39081
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve launching risky local command patterns")
|
|
39082
|
+
},
|
|
39083
|
+
run: async (input) => {
|
|
38548
39084
|
let liveTools = [];
|
|
38549
39085
|
try {
|
|
38550
|
-
await connectAllEnabled();
|
|
39086
|
+
await connectAllEnabled({ localCommandConsent: localConsent(input) });
|
|
38551
39087
|
liveTools = listAllTools();
|
|
38552
39088
|
} finally {
|
|
38553
39089
|
await disconnectAll().catch(() => {
|
|
@@ -38562,11 +39098,13 @@ function buildMcpTools() {
|
|
|
38562
39098
|
description: `Call a tool on a connected upstream MCP server. Tool name format: server_id${TOOL_PREFIX_SEPARATOR}tool_name`,
|
|
38563
39099
|
paramsSchema: {
|
|
38564
39100
|
tool_name: exports_external2.string().describe(`Prefixed tool name (server_id${TOOL_PREFIX_SEPARATOR}tool_name)`),
|
|
38565
|
-
arguments: exports_external2.record(exports_external2.unknown()).optional().describe("Tool arguments as key-value pairs")
|
|
39101
|
+
arguments: exports_external2.record(exports_external2.unknown()).optional().describe("Tool arguments as key-value pairs"),
|
|
39102
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching this local stdio command"),
|
|
39103
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve launching risky local command patterns")
|
|
38566
39104
|
},
|
|
38567
|
-
run: async (
|
|
39105
|
+
run: async (input) => {
|
|
38568
39106
|
try {
|
|
38569
|
-
const toolName = String(tool_name);
|
|
39107
|
+
const toolName = String(input.tool_name);
|
|
38570
39108
|
const sepIdx = toolName.indexOf(TOOL_PREFIX_SEPARATOR);
|
|
38571
39109
|
if (sepIdx === -1)
|
|
38572
39110
|
return errorContent(`Error: Invalid tool name "${toolName}"`);
|
|
@@ -38576,8 +39114,8 @@ function buildMcpTools() {
|
|
|
38576
39114
|
return errorContent(`Error: Server "${serverId}" not found.`);
|
|
38577
39115
|
if (!entry.enabled)
|
|
38578
39116
|
return errorContent(`Error: Server "${serverId}" is disabled.`);
|
|
38579
|
-
await connectToServer(entry);
|
|
38580
|
-
const result = await callTool(toolName, readRecord(
|
|
39117
|
+
await connectToServer(entry, { localCommandConsent: localConsent(input) });
|
|
39118
|
+
const result = await callTool(toolName, readRecord(input.arguments));
|
|
38581
39119
|
return { content: result.content };
|
|
38582
39120
|
} catch (error2) {
|
|
38583
39121
|
return errorContent(`Error: ${error2.message}`);
|
|
@@ -38587,13 +39125,17 @@ function buildMcpTools() {
|
|
|
38587
39125
|
{
|
|
38588
39126
|
name: "diagnose_server",
|
|
38589
39127
|
description: "Run health checks on a registered MCP server",
|
|
38590
|
-
paramsSchema: {
|
|
38591
|
-
|
|
38592
|
-
|
|
39128
|
+
paramsSchema: {
|
|
39129
|
+
id: exports_external2.string().describe("Server ID"),
|
|
39130
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching local stdio diagnostics"),
|
|
39131
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve diagnosing risky local command patterns")
|
|
39132
|
+
},
|
|
39133
|
+
run: async (input) => {
|
|
39134
|
+
const serverId = String(input.id);
|
|
38593
39135
|
const entry = getServer(serverId);
|
|
38594
39136
|
if (!entry)
|
|
38595
39137
|
return errorContent(`Server "${serverId}" not found.`);
|
|
38596
|
-
return jsonContent(await diagnoseServer(entry));
|
|
39138
|
+
return jsonContent(await diagnoseServer(entry, { localCommandConsent: localConsent(input) }));
|
|
38597
39139
|
}
|
|
38598
39140
|
},
|
|
38599
39141
|
{
|
|
@@ -38903,32 +39445,32 @@ if (isDirectRun) {
|
|
|
38903
39445
|
}
|
|
38904
39446
|
|
|
38905
39447
|
// src/server/serve.ts
|
|
38906
|
-
import { existsSync as
|
|
38907
|
-
import { join as
|
|
39448
|
+
import { existsSync as existsSync11 } from "fs";
|
|
39449
|
+
import { join as join12, dirname as dirname4, extname, resolve, relative as relative2, sep } from "path";
|
|
38908
39450
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
38909
39451
|
init_sources();
|
|
38910
39452
|
init_db();
|
|
38911
39453
|
function redactServer(server) {
|
|
38912
|
-
return { ...server, env: {} };
|
|
39454
|
+
return { ...redactServerCredentials(server), env: {} };
|
|
38913
39455
|
}
|
|
38914
39456
|
function resolveDashboardDir() {
|
|
38915
39457
|
const candidates = [];
|
|
38916
39458
|
try {
|
|
38917
39459
|
const scriptDir = dirname4(fileURLToPath2(import.meta.url));
|
|
38918
|
-
candidates.push(
|
|
38919
|
-
candidates.push(
|
|
39460
|
+
candidates.push(join12(scriptDir, "..", "dashboard", "dist"));
|
|
39461
|
+
candidates.push(join12(scriptDir, "..", "..", "dashboard", "dist"));
|
|
38920
39462
|
} catch {}
|
|
38921
39463
|
if (process.argv[1]) {
|
|
38922
39464
|
const mainDir = dirname4(process.argv[1]);
|
|
38923
|
-
candidates.push(
|
|
38924
|
-
candidates.push(
|
|
39465
|
+
candidates.push(join12(mainDir, "..", "dashboard", "dist"));
|
|
39466
|
+
candidates.push(join12(mainDir, "..", "..", "dashboard", "dist"));
|
|
38925
39467
|
}
|
|
38926
|
-
candidates.push(
|
|
39468
|
+
candidates.push(join12(process.cwd(), "dashboard", "dist"));
|
|
38927
39469
|
for (const candidate of candidates) {
|
|
38928
|
-
if (
|
|
39470
|
+
if (existsSync11(candidate))
|
|
38929
39471
|
return candidate;
|
|
38930
39472
|
}
|
|
38931
|
-
return
|
|
39473
|
+
return join12(process.cwd(), "dashboard", "dist");
|
|
38932
39474
|
}
|
|
38933
39475
|
var MIME_TYPES = {
|
|
38934
39476
|
".html": "text/html; charset=utf-8",
|
|
@@ -38962,6 +39504,20 @@ function isValidId(id) {
|
|
|
38962
39504
|
return /^[a-z0-9-]+$/.test(id);
|
|
38963
39505
|
}
|
|
38964
39506
|
var MAX_BODY_SIZE = 1024 * 1024;
|
|
39507
|
+
function consentFromInput(input) {
|
|
39508
|
+
return {
|
|
39509
|
+
approved: input.allow_local_stdio === true || input.allowLocalStdio === true,
|
|
39510
|
+
allowRisky: input.allow_risky_command === true || input.allowRiskyCommand === true,
|
|
39511
|
+
source: "api"
|
|
39512
|
+
};
|
|
39513
|
+
}
|
|
39514
|
+
function consentFromSearchParams(params) {
|
|
39515
|
+
return {
|
|
39516
|
+
approved: params.get("allow_local_stdio") === "1" || params.get("allow_local_stdio") === "true",
|
|
39517
|
+
allowRisky: params.get("allow_risky_command") === "1" || params.get("allow_risky_command") === "true",
|
|
39518
|
+
source: "api"
|
|
39519
|
+
};
|
|
39520
|
+
}
|
|
38965
39521
|
function isLoopbackHost(hostname3) {
|
|
38966
39522
|
return hostname3 === "127.0.0.1" || hostname3 === "localhost" || hostname3 === "::1";
|
|
38967
39523
|
}
|
|
@@ -39016,7 +39572,7 @@ function getAllServersWithToolCount() {
|
|
|
39016
39572
|
}));
|
|
39017
39573
|
}
|
|
39018
39574
|
function serveStaticFile(filePath) {
|
|
39019
|
-
if (!
|
|
39575
|
+
if (!existsSync11(filePath))
|
|
39020
39576
|
return null;
|
|
39021
39577
|
const ext = extname(filePath);
|
|
39022
39578
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -39049,7 +39605,7 @@ async function startServer(port, options) {
|
|
|
39049
39605
|
const host = options?.host ?? "127.0.0.1";
|
|
39050
39606
|
getDb();
|
|
39051
39607
|
const dashboardDir = resolveDashboardDir();
|
|
39052
|
-
const dashboardExists =
|
|
39608
|
+
const dashboardExists = existsSync11(dashboardDir);
|
|
39053
39609
|
if (!dashboardExists) {
|
|
39054
39610
|
console.error(`
|
|
39055
39611
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -39108,16 +39664,33 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39108
39664
|
return json({ error: "Invalid 'args' format" }, 400, port);
|
|
39109
39665
|
}
|
|
39110
39666
|
const args = body.args || [];
|
|
39667
|
+
const credentialRefs = normalizeCredentialRefs(body.credential_refs ?? body.credentialRefs);
|
|
39668
|
+
const env = body.env && typeof body.env === "object" && !Array.isArray(body.env) ? body.env : {};
|
|
39669
|
+
try {
|
|
39670
|
+
assertLocalCommandConsent({
|
|
39671
|
+
command,
|
|
39672
|
+
args,
|
|
39673
|
+
env: { ...env, ...Object.fromEntries(Object.keys(credentialRefs).map((key) => [key, "<credential-ref>"])) },
|
|
39674
|
+
transport,
|
|
39675
|
+
operation: "register"
|
|
39676
|
+
}, consentFromInput(body));
|
|
39677
|
+
} catch (err) {
|
|
39678
|
+
return json({ error: err.message }, 400, port);
|
|
39679
|
+
}
|
|
39111
39680
|
const entry = addServer({
|
|
39112
39681
|
name: body.name,
|
|
39113
39682
|
command,
|
|
39114
39683
|
args,
|
|
39115
39684
|
description: body.description,
|
|
39116
39685
|
transport,
|
|
39117
|
-
url: body.url
|
|
39686
|
+
url: body.url,
|
|
39687
|
+
env,
|
|
39688
|
+
credentialRefs
|
|
39118
39689
|
});
|
|
39119
39690
|
return json(entry, 200, port);
|
|
39120
39691
|
} catch (e) {
|
|
39692
|
+
if (e instanceof CredentialReferenceError)
|
|
39693
|
+
return json({ error: e.message }, 400, port);
|
|
39121
39694
|
return json({ error: e instanceof Error ? e.message : "Failed to add server" }, 500, port);
|
|
39122
39695
|
}
|
|
39123
39696
|
}
|
|
@@ -39196,6 +39769,11 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39196
39769
|
fields.description = body.description;
|
|
39197
39770
|
if (body.command !== undefined)
|
|
39198
39771
|
fields.command = body.command;
|
|
39772
|
+
if (body.env !== undefined)
|
|
39773
|
+
fields.env = body.env;
|
|
39774
|
+
if (body.credential_refs !== undefined || body.credentialRefs !== undefined) {
|
|
39775
|
+
fields.credentialRefs = normalizeCredentialRefs(body.credential_refs ?? body.credentialRefs);
|
|
39776
|
+
}
|
|
39199
39777
|
if (body.transport !== undefined)
|
|
39200
39778
|
fields.transport = body.transport;
|
|
39201
39779
|
if (body.url !== undefined)
|
|
@@ -39206,6 +39784,22 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39206
39784
|
}
|
|
39207
39785
|
fields.args = body.args;
|
|
39208
39786
|
}
|
|
39787
|
+
if (fields.command !== undefined || fields.args !== undefined || fields.transport !== undefined) {
|
|
39788
|
+
try {
|
|
39789
|
+
assertLocalCommandConsent({
|
|
39790
|
+
command: fields.command ?? existing.command,
|
|
39791
|
+
args: fields.args ?? existing.args,
|
|
39792
|
+
env: {
|
|
39793
|
+
...fields.env ?? existing.env,
|
|
39794
|
+
...Object.fromEntries(Object.keys(fields.credentialRefs ?? existing.credentialRefs ?? {}).map((key) => [key, "<credential-ref>"]))
|
|
39795
|
+
},
|
|
39796
|
+
transport: fields.transport ?? existing.transport,
|
|
39797
|
+
operation: "register"
|
|
39798
|
+
}, consentFromInput(body));
|
|
39799
|
+
} catch (err) {
|
|
39800
|
+
return json({ error: err.message }, 400, port);
|
|
39801
|
+
}
|
|
39802
|
+
}
|
|
39209
39803
|
const updated = updateServer(id, fields);
|
|
39210
39804
|
return json(redactServer(updated), 200, port);
|
|
39211
39805
|
} catch (e) {
|
|
@@ -39234,6 +39828,8 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39234
39828
|
setServerEnv(id, body.key, body.value);
|
|
39235
39829
|
return json({ ok: true }, 200, port);
|
|
39236
39830
|
} catch (e) {
|
|
39831
|
+
if (e instanceof CredentialReferenceError)
|
|
39832
|
+
return json({ error: e.message }, 400, port);
|
|
39237
39833
|
return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
|
|
39238
39834
|
}
|
|
39239
39835
|
}
|
|
@@ -39281,7 +39877,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39281
39877
|
}
|
|
39282
39878
|
if (!body.tool || typeof body.tool !== "string")
|
|
39283
39879
|
return json({ error: "Missing 'tool'" }, 400, port);
|
|
39284
|
-
await connectToServer(entry);
|
|
39880
|
+
await connectToServer(entry, { localCommandConsent: consentFromInput(body) });
|
|
39285
39881
|
const toolName = `${id}__${body.tool}`;
|
|
39286
39882
|
const result = await callTool(toolName, body.args || {});
|
|
39287
39883
|
await disconnectServer(id).catch(() => {
|
|
@@ -39292,6 +39888,9 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39292
39888
|
await disconnectServer(id).catch(() => {
|
|
39293
39889
|
return;
|
|
39294
39890
|
});
|
|
39891
|
+
if (e instanceof LocalCommandConsentError) {
|
|
39892
|
+
return json({ error: e.message }, 400, port);
|
|
39893
|
+
}
|
|
39295
39894
|
return json({ error: e instanceof Error ? e.message : "Failed to call tool" }, 500, port);
|
|
39296
39895
|
}
|
|
39297
39896
|
}
|
|
@@ -39304,7 +39903,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39304
39903
|
if (!entry)
|
|
39305
39904
|
return json({ error: `Server '${id}' not found` }, 404, port);
|
|
39306
39905
|
try {
|
|
39307
|
-
const report = await diagnoseServer(entry);
|
|
39906
|
+
const report = await diagnoseServer(entry, { localCommandConsent: consentFromSearchParams(url2.searchParams) });
|
|
39308
39907
|
return json(report, 200, port);
|
|
39309
39908
|
} catch (e) {
|
|
39310
39909
|
return json({ error: e instanceof Error ? e.message : "Failed to diagnose server" }, 500, port);
|
|
@@ -39433,7 +40032,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39433
40032
|
return res2;
|
|
39434
40033
|
}
|
|
39435
40034
|
}
|
|
39436
|
-
const indexPath =
|
|
40035
|
+
const indexPath = join12(dashboardDir, "index.html");
|
|
39437
40036
|
const res = serveStaticFile(indexPath);
|
|
39438
40037
|
if (res)
|
|
39439
40038
|
return res;
|
|
@@ -39959,6 +40558,13 @@ function ServerDetail({ server, onSelectTool, onBack }) {
|
|
|
39959
40558
|
const [tools2, setTools] = useState3([]);
|
|
39960
40559
|
const [loading, setLoading] = useState3(false);
|
|
39961
40560
|
const [error2, setError] = useState3(null);
|
|
40561
|
+
const commandReview = inspectLocalCommand({
|
|
40562
|
+
command: server.command,
|
|
40563
|
+
args: server.args,
|
|
40564
|
+
env: server.env,
|
|
40565
|
+
transport: server.transport,
|
|
40566
|
+
operation: "launch"
|
|
40567
|
+
});
|
|
39962
40568
|
const cachedTools = getCachedTools(server.id);
|
|
39963
40569
|
const cachedKey = cachedTools.map((t) => `${t.name}|${t.description}|${JSON.stringify(t.input_schema)}`).join(";");
|
|
39964
40570
|
useEffect3(() => {
|
|
@@ -39979,7 +40585,9 @@ function ServerDetail({ server, onSelectTool, onBack }) {
|
|
|
39979
40585
|
setLoading(true);
|
|
39980
40586
|
setError(null);
|
|
39981
40587
|
try {
|
|
39982
|
-
const conn = await connectToServer(server
|
|
40588
|
+
const conn = await connectToServer(server, {
|
|
40589
|
+
localCommandConsent: { approved: true, source: "tui" }
|
|
40590
|
+
});
|
|
39983
40591
|
setTools(conn.tools);
|
|
39984
40592
|
} catch (err) {
|
|
39985
40593
|
setError(err.message);
|
|
@@ -40038,6 +40646,21 @@ function ServerDetail({ server, onSelectTool, onBack }) {
|
|
|
40038
40646
|
server.args.join(" ")
|
|
40039
40647
|
]
|
|
40040
40648
|
}, undefined, true, undefined, this),
|
|
40649
|
+
commandReview.requiresConsent && /* @__PURE__ */ jsxDEV2(Box4, {
|
|
40650
|
+
marginTop: 1,
|
|
40651
|
+
flexDirection: "column",
|
|
40652
|
+
children: [
|
|
40653
|
+
/* @__PURE__ */ jsxDEV2(Text5, {
|
|
40654
|
+
color: commandReview.hasDangerousRisk ? "red" : "yellow",
|
|
40655
|
+
children: "Local stdio command review"
|
|
40656
|
+
}, undefined, false, undefined, this),
|
|
40657
|
+
formatLocalCommandReview(commandReview).split(`
|
|
40658
|
+
`).map((line, index) => /* @__PURE__ */ jsxDEV2(Text5, {
|
|
40659
|
+
dimColor: true,
|
|
40660
|
+
children: line
|
|
40661
|
+
}, `${index}:${line}`, false, undefined, this))
|
|
40662
|
+
]
|
|
40663
|
+
}, undefined, true, undefined, this),
|
|
40041
40664
|
server.description && /* @__PURE__ */ jsxDEV2(Text5, {
|
|
40042
40665
|
dimColor: true,
|
|
40043
40666
|
children: server.description
|
|
@@ -40206,7 +40829,9 @@ function SearchView({ onBack }) {
|
|
|
40206
40829
|
setInstalling(item.value);
|
|
40207
40830
|
setMessage(null);
|
|
40208
40831
|
try {
|
|
40209
|
-
const server = await installFromRegistry(item.value
|
|
40832
|
+
const server = await installFromRegistry(item.value, {
|
|
40833
|
+
localCommandConsent: { approved: true, source: "tui" }
|
|
40834
|
+
});
|
|
40210
40835
|
setMessage(`Installed: ${server.name} [${server.id}]`);
|
|
40211
40836
|
} catch (err) {
|
|
40212
40837
|
setMessage(`Install failed: ${err.message}`);
|
|
@@ -40317,7 +40942,9 @@ function ToolCall({ server, tool, onBack }) {
|
|
|
40317
40942
|
setError(null);
|
|
40318
40943
|
setResult(null);
|
|
40319
40944
|
try {
|
|
40320
|
-
await connectToServer(server
|
|
40945
|
+
await connectToServer(server, {
|
|
40946
|
+
localCommandConsent: { approved: true, source: "tui" }
|
|
40947
|
+
});
|
|
40321
40948
|
const prefixed = `${server.id}${TOOL_PREFIX_SEPARATOR}${tool.name}`;
|
|
40322
40949
|
const res = await callTool(prefixed, args);
|
|
40323
40950
|
const text = res.content.map((c) => c.text).join(`
|
|
@@ -40537,6 +41164,52 @@ var MACHINE_PLATFORMS = ["linux", "darwin", "unknown"];
|
|
|
40537
41164
|
var MACHINE_ARCHES = ["arm64", "x64", "unknown"];
|
|
40538
41165
|
var MACHINE_INSTALLERS = ["auto", "bun", "npm"];
|
|
40539
41166
|
var FLEET_INSTALL_MODES = ["missing", "missing-or-outdated", "all"];
|
|
41167
|
+
function localConsentFromOptions(opts, approved = false) {
|
|
41168
|
+
return {
|
|
41169
|
+
approved: approved || opts.yes === true || opts.allowLocalStdio === true,
|
|
41170
|
+
allowRisky: opts.allowRiskyCommand === true,
|
|
41171
|
+
source: "cli"
|
|
41172
|
+
};
|
|
41173
|
+
}
|
|
41174
|
+
function printLocalCommandReviewIfNeeded(input) {
|
|
41175
|
+
const review = inspectLocalCommand(input);
|
|
41176
|
+
if (!review.requiresConsent)
|
|
41177
|
+
return;
|
|
41178
|
+
console.log(chalk2.dim(formatLocalCommandReview(review)));
|
|
41179
|
+
}
|
|
41180
|
+
function assertCliLocalCommandConsent(input, opts, approved = false) {
|
|
41181
|
+
const consent = localConsentFromOptions(opts, approved);
|
|
41182
|
+
const review = inspectLocalCommand(input);
|
|
41183
|
+
if (review.requiresConsent && consent.approved === true && (!review.hasDangerousRisk || consent.allowRisky === true)) {
|
|
41184
|
+
console.log(chalk2.dim(formatLocalCommandReview(review)));
|
|
41185
|
+
}
|
|
41186
|
+
assertLocalCommandConsent(input, consent);
|
|
41187
|
+
return consent;
|
|
41188
|
+
}
|
|
41189
|
+
function parseCredentialPairs(pairs, source) {
|
|
41190
|
+
const refs = {};
|
|
41191
|
+
for (const pair of pairs ?? []) {
|
|
41192
|
+
const eqIdx = pair.indexOf("=");
|
|
41193
|
+
if (eqIdx <= 0)
|
|
41194
|
+
throw new Error(`Credential reference must use KEY=NAME format: ${pair}`);
|
|
41195
|
+
const key = pair.slice(0, eqIdx).trim();
|
|
41196
|
+
const name = pair.slice(eqIdx + 1).trim();
|
|
41197
|
+
if (!key || !name)
|
|
41198
|
+
throw new Error(`Credential reference must use KEY=NAME format: ${pair}`);
|
|
41199
|
+
refs[key] = { source, name, required: true };
|
|
41200
|
+
}
|
|
41201
|
+
return refs;
|
|
41202
|
+
}
|
|
41203
|
+
function credentialRefsFromOptions(opts) {
|
|
41204
|
+
return {
|
|
41205
|
+
...parseCredentialPairs(opts.credentialEnv, "env"),
|
|
41206
|
+
...parseCredentialPairs(opts.credentialLocal, "local-vault"),
|
|
41207
|
+
...parseCredentialPairs(opts.credentialHosted, "hosted")
|
|
41208
|
+
};
|
|
41209
|
+
}
|
|
41210
|
+
function formatCredentialRef(ref) {
|
|
41211
|
+
return `${ref.source}:${ref.name}`;
|
|
41212
|
+
}
|
|
40540
41213
|
function printProviderProfile(profile) {
|
|
40541
41214
|
const status = profile.enabled ? chalk2.green("enabled") : chalk2.red("disabled");
|
|
40542
41215
|
console.log(` ${chalk2.bold(profile.displayName)} ${chalk2.dim(`[${profile.id}]`)} \u2014 ${chalk2.dim(profile.transport)} \u2014 ${status}`);
|
|
@@ -40785,11 +41458,12 @@ providersCmd.command("info").argument("<id>", "Provider profile ID").description
|
|
|
40785
41458
|
console.log(` Docs: ${chalk2.cyan(profile.docsUrl)}`);
|
|
40786
41459
|
closeDb();
|
|
40787
41460
|
});
|
|
40788
|
-
providersCmd.command("install").argument("<id>", "Provider profile ID").description("Register a curated provider profile as an MCP server").option("--name <name>", "Override registered server name").option("--fallback", "Install the stdio fallback command instead of direct remote transport").option("--json", "Output as JSON").action((id, opts) => {
|
|
41461
|
+
providersCmd.command("install").argument("<id>", "Provider profile ID").description("Register a curated provider profile as an MCP server").option("--name <name>", "Override registered server name").option("--fallback", "Install the stdio fallback command instead of direct remote transport").option("--yes", "Approve local stdio fallback commands").option("--allow-local-stdio", "Approve local stdio fallback commands").option("--allow-risky-command", "Approve high-risk local command patterns").option("--json", "Output as JSON").action((id, opts) => {
|
|
40789
41462
|
try {
|
|
40790
41463
|
const server = installProviderProfile(id, {
|
|
40791
41464
|
name: opts.name,
|
|
40792
|
-
useFallback: opts.fallback === true
|
|
41465
|
+
useFallback: opts.fallback === true,
|
|
41466
|
+
localCommandConsent: localConsentFromOptions(opts)
|
|
40793
41467
|
});
|
|
40794
41468
|
if (opts.json) {
|
|
40795
41469
|
printJson(server);
|
|
@@ -40820,11 +41494,13 @@ function detectSourceType(url2) {
|
|
|
40820
41494
|
return "mcp-registry";
|
|
40821
41495
|
return null;
|
|
40822
41496
|
}
|
|
40823
|
-
program2.command("add").passThroughOptions().argument("[command]", "Command to run the MCP server").argument("[args...]", "Arguments for the command").option("--name <name>", "Display name for the server").option("--description <desc>", "Description").option("--from-registry <id>", "Install from official registry by ID").option("--transport <type>", "Transport type: stdio, sse, streamable-http", "stdio").option("--url <url>", "URL for remote transports").option("--env <pairs...>", "Environment variables as KEY=VALUE pairs").option("--wizard", "Interactive setup wizard").option("--force", "Register even if duplicate command exists").description("Add a local MCP server").action(async (command, args, opts) => {
|
|
41497
|
+
program2.command("add").passThroughOptions().argument("[command]", "Command to run the MCP server").argument("[args...]", "Arguments for the command").option("--name <name>", "Display name for the server").option("--description <desc>", "Description").option("--from-registry <id>", "Install from official registry by ID").option("--transport <type>", "Transport type: stdio, sse, streamable-http", "stdio").option("--url <url>", "URL for remote transports").option("--env <pairs...>", "Environment variables as KEY=VALUE pairs").option("--credential-env <pairs...>", "Credential refs as KEY=ENV_VAR pairs").option("--credential-local <pairs...>", "Credential refs as KEY=LOCAL_VAULT_NAME pairs").option("--credential-hosted <pairs...>", "Credential refs as KEY=HOSTED_CREDENTIAL_ID pairs").option("--wizard", "Interactive setup wizard").option("--force", "Register even if duplicate command exists").option("--yes", "Approve registering local stdio commands").option("--allow-local-stdio", "Approve registering local stdio commands").option("--allow-risky-command", "Approve high-risk local command patterns").description("Add a local MCP server").action(async (command, args, opts) => {
|
|
40824
41498
|
try {
|
|
40825
41499
|
if (opts.fromRegistry) {
|
|
40826
41500
|
console.log(chalk2.dim(`Installing "${opts.fromRegistry}" from registry...`));
|
|
40827
|
-
const server2 = await installFromRegistry(opts.fromRegistry
|
|
41501
|
+
const server2 = await installFromRegistry(opts.fromRegistry, {
|
|
41502
|
+
localCommandConsent: localConsentFromOptions(opts)
|
|
41503
|
+
});
|
|
40828
41504
|
console.log(chalk2.green(`Added server: ${server2.name} [${server2.id}]`));
|
|
40829
41505
|
console.log(chalk2.dim(` ${server2.command} ${server2.args.join(" ")}`));
|
|
40830
41506
|
closeDb();
|
|
@@ -40863,6 +41539,13 @@ Server to add:`));
|
|
|
40863
41539
|
console.log(` Name: ${wizardName}`);
|
|
40864
41540
|
if (Object.keys(env).length)
|
|
40865
41541
|
console.log(` Env: ${Object.keys(env).join(", ")}`);
|
|
41542
|
+
printLocalCommandReviewIfNeeded({
|
|
41543
|
+
command: wizardCommand,
|
|
41544
|
+
args: wizardArgs,
|
|
41545
|
+
env,
|
|
41546
|
+
transport,
|
|
41547
|
+
operation: "register"
|
|
41548
|
+
});
|
|
40866
41549
|
const confirm = await new Promise((resolve2) => {
|
|
40867
41550
|
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
40868
41551
|
rl2.question(chalk2.bold("Add this server? [Y/n]: "), (ans) => {
|
|
@@ -40875,13 +41558,21 @@ Server to add:`));
|
|
|
40875
41558
|
closeDb();
|
|
40876
41559
|
return;
|
|
40877
41560
|
}
|
|
41561
|
+
assertLocalCommandConsent({
|
|
41562
|
+
command: wizardCommand,
|
|
41563
|
+
args: wizardArgs,
|
|
41564
|
+
env,
|
|
41565
|
+
transport,
|
|
41566
|
+
operation: "register"
|
|
41567
|
+
}, localConsentFromOptions(opts, true));
|
|
40878
41568
|
const server2 = addServer({
|
|
40879
41569
|
command: wizardCommand,
|
|
40880
41570
|
args: wizardArgs,
|
|
40881
41571
|
name: wizardName || undefined,
|
|
40882
41572
|
description: wizardDescription || undefined,
|
|
40883
41573
|
transport,
|
|
40884
|
-
env
|
|
41574
|
+
env,
|
|
41575
|
+
credentialRefs: credentialRefsFromOptions(opts)
|
|
40885
41576
|
});
|
|
40886
41577
|
console.log(chalk2.green(`Added: ${server2.name} [${server2.id}]`));
|
|
40887
41578
|
closeDb();
|
|
@@ -40911,6 +41602,14 @@ Server to add:`));
|
|
|
40911
41602
|
envMap[key] = rest.join("=");
|
|
40912
41603
|
}
|
|
40913
41604
|
}
|
|
41605
|
+
const credentialRefs = credentialRefsFromOptions(opts);
|
|
41606
|
+
assertCliLocalCommandConsent({
|
|
41607
|
+
command,
|
|
41608
|
+
args,
|
|
41609
|
+
env: { ...envMap, ...Object.fromEntries(Object.keys(credentialRefs).map((key) => [key, "<credential-ref>"])) },
|
|
41610
|
+
transport: opts.transport,
|
|
41611
|
+
operation: "register"
|
|
41612
|
+
}, opts);
|
|
40914
41613
|
const server = addServer({
|
|
40915
41614
|
name: opts.name,
|
|
40916
41615
|
description: opts.description,
|
|
@@ -40918,7 +41617,8 @@ Server to add:`));
|
|
|
40918
41617
|
args,
|
|
40919
41618
|
transport: opts.transport,
|
|
40920
41619
|
url: opts.url,
|
|
40921
|
-
env: envMap
|
|
41620
|
+
env: envMap,
|
|
41621
|
+
credentialRefs
|
|
40922
41622
|
});
|
|
40923
41623
|
console.log(chalk2.green(`Added server: ${server.name} [${server.id}]`));
|
|
40924
41624
|
console.log(chalk2.dim(` ${server.command} ${server.args.join(" ")}`));
|
|
@@ -40933,29 +41633,44 @@ Server to add:`));
|
|
|
40933
41633
|
}
|
|
40934
41634
|
closeDb();
|
|
40935
41635
|
});
|
|
40936
|
-
program2.command("update-server").argument("<id>", "Server ID to update").description("Update fields of a registered server").option("--name <name>", "New display name").option("--description <desc>", "New description").option("--command <cmd>", "New command").option("--args <args...>", "New args list").option("--transport <type>", "New transport type").option("--url <url>", "New URL").action((id, opts) => {
|
|
40937
|
-
|
|
40938
|
-
|
|
40939
|
-
|
|
41636
|
+
program2.command("update-server").argument("<id>", "Server ID to update").description("Update fields of a registered server").option("--name <name>", "New display name").option("--description <desc>", "New description").option("--command <cmd>", "New command").option("--args <args...>", "New args list").option("--transport <type>", "New transport type").option("--url <url>", "New URL").option("--yes", "Approve updated local stdio commands").option("--allow-local-stdio", "Approve updated local stdio commands").option("--allow-risky-command", "Approve high-risk local command patterns").action((id, opts) => {
|
|
41637
|
+
try {
|
|
41638
|
+
const server = getServer(id);
|
|
41639
|
+
if (!server) {
|
|
41640
|
+
console.error(chalk2.red(`Server "${id}" not found.`));
|
|
41641
|
+
closeDb();
|
|
41642
|
+
process.exit(1);
|
|
41643
|
+
}
|
|
41644
|
+
const fields = {};
|
|
41645
|
+
if (opts.name !== undefined)
|
|
41646
|
+
fields.name = opts.name;
|
|
41647
|
+
if (opts.description !== undefined)
|
|
41648
|
+
fields.description = opts.description;
|
|
41649
|
+
if (opts.command !== undefined)
|
|
41650
|
+
fields.command = opts.command;
|
|
41651
|
+
if (opts.args !== undefined)
|
|
41652
|
+
fields.args = opts.args;
|
|
41653
|
+
if (opts.transport !== undefined)
|
|
41654
|
+
fields.transport = opts.transport;
|
|
41655
|
+
if (opts.url !== undefined)
|
|
41656
|
+
fields.url = opts.url;
|
|
41657
|
+
if (fields.command !== undefined || fields.args !== undefined || fields.transport !== undefined) {
|
|
41658
|
+
assertCliLocalCommandConsent({
|
|
41659
|
+
command: fields.command ?? server.command,
|
|
41660
|
+
args: fields.args ?? server.args,
|
|
41661
|
+
env: server.env,
|
|
41662
|
+
transport: fields.transport ?? server.transport,
|
|
41663
|
+
operation: "register"
|
|
41664
|
+
}, opts);
|
|
41665
|
+
}
|
|
41666
|
+
const updated = updateServer(id, fields);
|
|
41667
|
+
console.log(chalk2.green(`Updated server: ${updated.name} [${updated.id}]`));
|
|
41668
|
+
closeDb();
|
|
41669
|
+
} catch (err) {
|
|
41670
|
+
console.error(chalk2.red(`Failed to update server: ${err.message}`));
|
|
40940
41671
|
closeDb();
|
|
40941
41672
|
process.exit(1);
|
|
40942
41673
|
}
|
|
40943
|
-
const fields = {};
|
|
40944
|
-
if (opts.name !== undefined)
|
|
40945
|
-
fields.name = opts.name;
|
|
40946
|
-
if (opts.description !== undefined)
|
|
40947
|
-
fields.description = opts.description;
|
|
40948
|
-
if (opts.command !== undefined)
|
|
40949
|
-
fields.command = opts.command;
|
|
40950
|
-
if (opts.args !== undefined)
|
|
40951
|
-
fields.args = opts.args;
|
|
40952
|
-
if (opts.transport !== undefined)
|
|
40953
|
-
fields.transport = opts.transport;
|
|
40954
|
-
if (opts.url !== undefined)
|
|
40955
|
-
fields.url = opts.url;
|
|
40956
|
-
const updated = updateServer(id, fields);
|
|
40957
|
-
console.log(chalk2.green(`Updated server: ${updated.name} [${updated.id}]`));
|
|
40958
|
-
closeDb();
|
|
40959
41674
|
});
|
|
40960
41675
|
program2.command("clone").argument("<id>", "Server ID to clone").argument("<new-name>", "Name for the cloned server").description("Clone a server with a new name").action((id, newName) => {
|
|
40961
41676
|
try {
|
|
@@ -41002,10 +41717,10 @@ program2.command("disable").argument("<id>", "Server ID to disable").description
|
|
|
41002
41717
|
console.log(chalk2.yellow(`Disabled server: ${server.name}`));
|
|
41003
41718
|
closeDb();
|
|
41004
41719
|
});
|
|
41005
|
-
program2.command("tools").argument("[server-id]", "Optional server ID to filter by").description("List tools (all or per server)").option("--connect", "Connect to servers to fetch live tools").action(async (serverId, opts) => {
|
|
41720
|
+
program2.command("tools").argument("[server-id]", "Optional server ID to filter by").description("List tools (all or per server)").option("--connect", "Connect to servers to fetch live tools").option("--yes", "Approve launching local stdio commands when --connect is used").option("--allow-local-stdio", "Approve launching local stdio commands when --connect is used").option("--allow-risky-command", "Approve high-risk local command patterns").action(async (serverId, opts) => {
|
|
41006
41721
|
if (opts.connect) {
|
|
41007
41722
|
console.log(chalk2.dim("Connecting to enabled servers..."));
|
|
41008
|
-
await connectAllEnabled();
|
|
41723
|
+
await connectAllEnabled({ localCommandConsent: localConsentFromOptions(opts) });
|
|
41009
41724
|
const tools2 = listAllTools();
|
|
41010
41725
|
if (tools2.length === 0) {
|
|
41011
41726
|
console.log(chalk2.dim("No tools available."));
|
|
@@ -41055,7 +41770,7 @@ ${total} tool(s) total.`));
|
|
|
41055
41770
|
}
|
|
41056
41771
|
closeDb();
|
|
41057
41772
|
});
|
|
41058
|
-
program2.command("call").argument("<tool>", "Tool name (server_id__tool_name)").option("--arg <pairs...>", "Arguments as key=value pairs").option("--json <json>", "Arguments as JSON string").description("Call a tool directly").action(async (tool, opts) => {
|
|
41773
|
+
program2.command("call").argument("<tool>", "Tool name (server_id__tool_name)").option("--arg <pairs...>", "Arguments as key=value pairs").option("--json <json>", "Arguments as JSON string").option("--yes", "Approve launching local stdio commands").option("--allow-local-stdio", "Approve launching local stdio commands").option("--allow-risky-command", "Approve high-risk local command patterns").description("Call a tool directly").action(async (tool, opts) => {
|
|
41059
41774
|
let args = {};
|
|
41060
41775
|
if (opts.json) {
|
|
41061
41776
|
try {
|
|
@@ -41078,7 +41793,7 @@ program2.command("call").argument("<tool>", "Tool name (server_id__tool_name)").
|
|
|
41078
41793
|
let exitCode = 0;
|
|
41079
41794
|
try {
|
|
41080
41795
|
console.log(chalk2.dim(`Connecting to servers...`));
|
|
41081
|
-
await connectAllEnabled();
|
|
41796
|
+
await connectAllEnabled({ localCommandConsent: localConsentFromOptions(opts) });
|
|
41082
41797
|
console.log(chalk2.dim(`Calling ${tool}...`));
|
|
41083
41798
|
const result = await callTool(tool, args);
|
|
41084
41799
|
for (const c of result.content) {
|
|
@@ -41102,20 +41817,24 @@ program2.command("info").argument("<id>", "Server ID").description("Show server
|
|
|
41102
41817
|
closeDb();
|
|
41103
41818
|
process.exit(1);
|
|
41104
41819
|
}
|
|
41105
|
-
|
|
41106
|
-
console.log(
|
|
41107
|
-
console.log(`
|
|
41108
|
-
console.log(`
|
|
41109
|
-
console.log(`
|
|
41110
|
-
|
|
41111
|
-
|
|
41112
|
-
|
|
41113
|
-
|
|
41114
|
-
|
|
41115
|
-
|
|
41116
|
-
}
|
|
41117
|
-
|
|
41118
|
-
|
|
41820
|
+
const safeServer = redactServerCredentials(server);
|
|
41821
|
+
console.log(chalk2.bold(safeServer.name) + " " + chalk2.dim(`[${safeServer.id}]`));
|
|
41822
|
+
console.log(` Status: ${safeServer.enabled ? chalk2.green("enabled") : chalk2.red("disabled")}`);
|
|
41823
|
+
console.log(` Source: ${safeServer.source}`);
|
|
41824
|
+
console.log(` Transport: ${safeServer.transport}`);
|
|
41825
|
+
console.log(` Command: ${safeServer.command} ${safeServer.args.join(" ")}`);
|
|
41826
|
+
if (safeServer.url)
|
|
41827
|
+
console.log(` URL: ${safeServer.url}`);
|
|
41828
|
+
if (safeServer.description)
|
|
41829
|
+
console.log(` Desc: ${safeServer.description}`);
|
|
41830
|
+
if (Object.keys(safeServer.env).length > 0) {
|
|
41831
|
+
console.log(` Env: ${Object.entries(safeServer.env).map(([k, v]) => `${k}=${v}`).join(", ")}`);
|
|
41832
|
+
}
|
|
41833
|
+
if (Object.keys(safeServer.credentialRefs ?? {}).length > 0) {
|
|
41834
|
+
console.log(` Cred refs: ${Object.entries(safeServer.credentialRefs ?? {}).map(([k, ref]) => `${k}=${formatCredentialRef(ref)}`).join(", ")}`);
|
|
41835
|
+
}
|
|
41836
|
+
console.log(` Created: ${safeServer.created_at}`);
|
|
41837
|
+
console.log(` Updated: ${safeServer.updated_at}`);
|
|
41119
41838
|
const cached2 = getCachedTools(id);
|
|
41120
41839
|
if (cached2.length > 0) {
|
|
41121
41840
|
console.log(chalk2.bold(`
|
|
@@ -41146,7 +41865,7 @@ program2.command("status").description("Show registry stats").option("--json", "
|
|
|
41146
41865
|
console.log(` Tools: ${totalTools} (cached)`);
|
|
41147
41866
|
closeDb();
|
|
41148
41867
|
});
|
|
41149
|
-
program2.command("doctor").argument("[id]", "Server ID to check (omit to check all)").description("Diagnose server health \u2014 checks PATH, env vars, connectivity").option("--fix", "Attempt to fix issues automatically").action(async (id, opts) => {
|
|
41868
|
+
program2.command("doctor").argument("[id]", "Server ID to check (omit to check all)").description("Diagnose server health \u2014 checks PATH, env vars, connectivity").option("--fix", "Attempt to fix issues automatically").option("--yes", "Approve probing and launching local stdio commands").option("--allow-local-stdio", "Approve probing and launching local stdio commands").option("--allow-risky-command", "Approve high-risk local command patterns").action(async (id, opts) => {
|
|
41150
41869
|
const { execFileSync: execFileSync22 } = await import("child_process");
|
|
41151
41870
|
const servers = id ? [getServer(id)].filter(Boolean) : listServers();
|
|
41152
41871
|
if (servers.length === 0) {
|
|
@@ -41158,7 +41877,7 @@ program2.command("doctor").argument("[id]", "Server ID to check (omit to check a
|
|
|
41158
41877
|
for (const server of servers) {
|
|
41159
41878
|
console.log(chalk2.bold(`
|
|
41160
41879
|
${server.name} [${server.id}]`));
|
|
41161
|
-
const report = await diagnoseServer(server);
|
|
41880
|
+
const report = await diagnoseServer(server, { localCommandConsent: localConsentFromOptions(opts) });
|
|
41162
41881
|
for (const check2 of report.checks) {
|
|
41163
41882
|
const icon = check2.pass ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
41164
41883
|
console.log(` ${icon} ${check2.name}: ${chalk2.dim(check2.message)}`);
|
|
@@ -41244,7 +41963,7 @@ program2.command("update").description("Update mcps to the latest version").acti
|
|
|
41244
41963
|
process.exit(1);
|
|
41245
41964
|
}
|
|
41246
41965
|
});
|
|
41247
|
-
program2.command("find").argument("[query]", "Search query (omit to list all from awesome list)").description("Find MCP servers across npm, GitHub, official registry, and awesome lists").option("--source <sources...>", "Source IDs to search (see `mcps sources list`)").option("--limit <n>", "Max results per source", "20").option("--awesome", "List curated servers from punkpeye/awesome-mcp-servers").option("--json", "Output as JSON").option("--install", "After showing results, prompt to select one and install it").option("--yes", "Auto-install without prompting (only when there is exactly 1 result)").option("--no-cache", "Bypass source cache and fetch fresh results").action(async (query, opts) => {
|
|
41966
|
+
program2.command("find").argument("[query]", "Search query (omit to list all from awesome list)").description("Find MCP servers across npm, GitHub, official registry, and awesome lists").option("--source <sources...>", "Source IDs to search (see `mcps sources list`)").option("--limit <n>", "Max results per source", "20").option("--awesome", "List curated servers from punkpeye/awesome-mcp-servers").option("--json", "Output as JSON").option("--install", "After showing results, prompt to select one and install it").option("--yes", "Auto-install without prompting (only when there is exactly 1 result)").option("--allow-risky-command", "Approve high-risk local command patterns").option("--no-cache", "Bypass source cache and fetch fresh results").action(async (query, opts) => {
|
|
41248
41967
|
try {
|
|
41249
41968
|
if (opts.awesome) {
|
|
41250
41969
|
console.log(chalk2.dim("Fetching curated awesome-mcp-servers list..."));
|
|
@@ -41354,6 +42073,14 @@ Enter number to install (1-${results.length}), or 0 to cancel: `), resolve2);
|
|
|
41354
42073
|
return;
|
|
41355
42074
|
}
|
|
41356
42075
|
console.log(chalk2.dim(`Installing ${chosen.name}...`));
|
|
42076
|
+
const localCommandInput = {
|
|
42077
|
+
command: "npx",
|
|
42078
|
+
args: ["-y", pkg],
|
|
42079
|
+
env: {},
|
|
42080
|
+
transport: "stdio",
|
|
42081
|
+
operation: "install"
|
|
42082
|
+
};
|
|
42083
|
+
const localCommandConsent = assertCliLocalCommandConsent(localCommandInput, opts, true);
|
|
41357
42084
|
const server = addServer({
|
|
41358
42085
|
command: "npx",
|
|
41359
42086
|
args: ["-y", pkg],
|
|
@@ -41361,7 +42088,7 @@ Enter number to install (1-${results.length}), or 0 to cancel: `), resolve2);
|
|
|
41361
42088
|
description: chosen.description,
|
|
41362
42089
|
transport: "stdio"
|
|
41363
42090
|
});
|
|
41364
|
-
const results2 = installToAgents(server, ["claude", "codex", "gemini"]);
|
|
42091
|
+
const results2 = installToAgents(server, ["claude", "codex", "gemini"], { localCommandConsent });
|
|
41365
42092
|
for (const r of results2) {
|
|
41366
42093
|
if (r.success) {
|
|
41367
42094
|
console.log(chalk2.green(` \u2713 ${r.agent}`));
|
|
@@ -41506,7 +42233,7 @@ sourcesCmd.command("test").argument("<id>", "Source ID to test").description("Te
|
|
|
41506
42233
|
}
|
|
41507
42234
|
closeDb();
|
|
41508
42235
|
});
|
|
41509
|
-
program2.command("install").argument("[id]", "Server ID (from `mcps list`) to install into AI agents").description("Install a registered MCP server into Claude Code, Codex, and/or Gemini").option("--claude", "Install to Claude Code").option("--codex", "Install to Codex").option("--gemini", "Install to Gemini").option("--all", "Install to all agents (default if none specified)").option("--to <agents...>", "Agents to install to: claude, codex, gemini").option("--from-registry <id>", "Add from official registry and install in one step").option("--npm <package>", "Add an npm package as a server and install in one step").action(async (id, opts) => {
|
|
42236
|
+
program2.command("install").argument("[id]", "Server ID (from `mcps list`) to install into AI agents").description("Install a registered MCP server into Claude Code, Codex, and/or Gemini").option("--claude", "Install to Claude Code").option("--codex", "Install to Codex").option("--gemini", "Install to Gemini").option("--all", "Install to all agents (default if none specified)").option("--to <agents...>", "Agents to install to: claude, codex, gemini").option("--from-registry <id>", "Add from official registry and install in one step").option("--npm <package>", "Add an npm package as a server and install in one step").option("--yes", "Approve installing local stdio commands into agents").option("--allow-local-stdio", "Approve installing local stdio commands into agents").option("--allow-risky-command", "Approve high-risk local command patterns").action(async (id, opts) => {
|
|
41510
42237
|
const targets = [];
|
|
41511
42238
|
if (opts.to) {
|
|
41512
42239
|
for (const t of opts.to) {
|
|
@@ -41532,7 +42259,9 @@ program2.command("install").argument("[id]", "Server ID (from `mcps list`) to in
|
|
|
41532
42259
|
if (opts.fromRegistry) {
|
|
41533
42260
|
console.log(chalk2.dim(`Installing "${opts.fromRegistry}" from registry...`));
|
|
41534
42261
|
try {
|
|
41535
|
-
server = await installFromRegistry(opts.fromRegistry
|
|
42262
|
+
server = await installFromRegistry(opts.fromRegistry, {
|
|
42263
|
+
localCommandConsent: localConsentFromOptions(opts)
|
|
42264
|
+
});
|
|
41536
42265
|
console.log(chalk2.green(`Added server: ${server.name} [${server.id}]`));
|
|
41537
42266
|
} catch (err) {
|
|
41538
42267
|
console.error(chalk2.red(`Failed to install from registry: ${err.message}`));
|
|
@@ -41541,6 +42270,13 @@ program2.command("install").argument("[id]", "Server ID (from `mcps list`) to in
|
|
|
41541
42270
|
}
|
|
41542
42271
|
} else if (opts.npm) {
|
|
41543
42272
|
const pkg = opts.npm;
|
|
42273
|
+
assertCliLocalCommandConsent({
|
|
42274
|
+
command: "npx",
|
|
42275
|
+
args: ["-y", pkg],
|
|
42276
|
+
env: {},
|
|
42277
|
+
transport: "stdio",
|
|
42278
|
+
operation: "install"
|
|
42279
|
+
}, opts);
|
|
41544
42280
|
server = addServer({ command: "npx", args: ["-y", pkg], name: pkg, transport: "stdio" });
|
|
41545
42281
|
console.log(chalk2.green(`Added server: ${server.name} [${server.id}]`));
|
|
41546
42282
|
} else {
|
|
@@ -41557,7 +42293,7 @@ program2.command("install").argument("[id]", "Server ID (from `mcps list`) to in
|
|
|
41557
42293
|
}
|
|
41558
42294
|
}
|
|
41559
42295
|
console.log(chalk2.dim(`Installing "${server.name}" to: ${targets.join(", ")}...`));
|
|
41560
|
-
const results = installToAgents(server, targets);
|
|
42296
|
+
const results = installToAgents(server, targets, { localCommandConsent: localConsentFromOptions(opts) });
|
|
41561
42297
|
for (const r of results) {
|
|
41562
42298
|
if (r.success) {
|
|
41563
42299
|
console.log(chalk2.green(` \u2713 ${r.agent}`));
|
|
@@ -41738,7 +42474,7 @@ fleetCmd.command("install").argument("[machineIds...]", "Optional machine IDs to
|
|
|
41738
42474
|
}
|
|
41739
42475
|
});
|
|
41740
42476
|
program2.command("export").description("Export all servers and sources to a JSON file").option("--file <path>", "Output file path", `${process.env.HOME ?? "~"}/.hasna/mcps/export.json`).option("--stdout", "Write to stdout instead of a file").action((opts) => {
|
|
41741
|
-
const servers = listServers();
|
|
42477
|
+
const servers = listServers().map(redactServerCredentials);
|
|
41742
42478
|
const sources = listSources();
|
|
41743
42479
|
const payload = {
|
|
41744
42480
|
version: 1,
|
|
@@ -41758,7 +42494,7 @@ program2.command("export").description("Export all servers and sources to a JSON
|
|
|
41758
42494
|
program2.command("import").argument("<file>", "Path to the export JSON file").description("Import servers and sources from a JSON export file").option("--overwrite", "Overwrite existing entries with matching IDs").action((file, opts) => {
|
|
41759
42495
|
let payload;
|
|
41760
42496
|
try {
|
|
41761
|
-
payload = JSON.parse(
|
|
42497
|
+
payload = JSON.parse(readFileSync7(file, "utf-8"));
|
|
41762
42498
|
} catch (err) {
|
|
41763
42499
|
console.error(chalk2.red(`Failed to read file: ${err.message}`));
|
|
41764
42500
|
closeDb();
|
|
@@ -41780,7 +42516,23 @@ program2.command("import").argument("<file>", "Path to the export JSON file").de
|
|
|
41780
42516
|
serversSkipped++;
|
|
41781
42517
|
continue;
|
|
41782
42518
|
}
|
|
41783
|
-
|
|
42519
|
+
const literalEnv = normalizeLiteralEnv(s.env ?? {});
|
|
42520
|
+
const credentialRefs = normalizeCredentialRefs(s.credentialRefs ?? s.credential_refs ?? {});
|
|
42521
|
+
db2.run(`INSERT ${orReplace} INTO servers (id, name, description, command, args, env, credential_refs, transport, url, source, enabled, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)`, [
|
|
42522
|
+
s.id,
|
|
42523
|
+
s.name,
|
|
42524
|
+
s.description,
|
|
42525
|
+
s.command,
|
|
42526
|
+
JSON.stringify(s.args ?? []),
|
|
42527
|
+
JSON.stringify(literalEnv),
|
|
42528
|
+
JSON.stringify(credentialRefs),
|
|
42529
|
+
s.transport,
|
|
42530
|
+
s.url,
|
|
42531
|
+
s.source,
|
|
42532
|
+
s.enabled ? 1 : 0,
|
|
42533
|
+
s.created_at,
|
|
42534
|
+
s.updated_at
|
|
42535
|
+
]);
|
|
41784
42536
|
serversImported++;
|
|
41785
42537
|
}
|
|
41786
42538
|
let sourcesImported = 0;
|
|
@@ -41806,14 +42558,17 @@ envCmd.command("list").argument("<id>").description("List env vars for a server"
|
|
|
41806
42558
|
closeDb();
|
|
41807
42559
|
process.exit(1);
|
|
41808
42560
|
}
|
|
41809
|
-
const
|
|
41810
|
-
|
|
41811
|
-
|
|
42561
|
+
const envEntries = Object.entries(redactEnv(server.env));
|
|
42562
|
+
const refEntries = Object.entries(server.credentialRefs ?? {});
|
|
42563
|
+
if (envEntries.length === 0 && refEntries.length === 0) {
|
|
42564
|
+
console.log(chalk2.dim("No env vars or credential refs set."));
|
|
41812
42565
|
closeDb();
|
|
41813
42566
|
return;
|
|
41814
42567
|
}
|
|
41815
|
-
for (const [k, v] of
|
|
42568
|
+
for (const [k, v] of envEntries)
|
|
41816
42569
|
console.log(` ${chalk2.bold(k)}=${chalk2.dim(v)}`);
|
|
42570
|
+
for (const [k, ref] of refEntries)
|
|
42571
|
+
console.log(` ${chalk2.bold(k)}=${chalk2.dim(formatCredentialRef(ref))}`);
|
|
41817
42572
|
closeDb();
|
|
41818
42573
|
});
|
|
41819
42574
|
envCmd.command("set").argument("<id>").argument("<pair>", "KEY=VALUE").description("Set an env var").action((id, pair) => {
|
|
@@ -41835,6 +42590,36 @@ envCmd.command("set").argument("<id>").argument("<pair>", "KEY=VALUE").descripti
|
|
|
41835
42590
|
}
|
|
41836
42591
|
closeDb();
|
|
41837
42592
|
});
|
|
42593
|
+
envCmd.command("ref").argument("<id>").argument("<pair>", "KEY=NAME").description("Set a credential reference for a server env key").option("--source <source>", "Credential source: env, local-vault, hosted", "env").action((id, pair, opts) => {
|
|
42594
|
+
const source = opts.source;
|
|
42595
|
+
if (source !== "env" && source !== "local-vault" && source !== "hosted") {
|
|
42596
|
+
console.error(chalk2.red("Source must be one of: env, local-vault, hosted"));
|
|
42597
|
+
closeDb();
|
|
42598
|
+
process.exit(1);
|
|
42599
|
+
}
|
|
42600
|
+
try {
|
|
42601
|
+
const refs = parseCredentialPairs([pair], source);
|
|
42602
|
+
const [key, ref] = Object.entries(refs)[0];
|
|
42603
|
+
setServerCredentialRef(id, key, ref);
|
|
42604
|
+
console.log(chalk2.green(`Set credential ref ${key}=${formatCredentialRef(ref)} on ${id}`));
|
|
42605
|
+
} catch (err) {
|
|
42606
|
+
console.error(chalk2.red(err.message));
|
|
42607
|
+
closeDb();
|
|
42608
|
+
process.exit(1);
|
|
42609
|
+
}
|
|
42610
|
+
closeDb();
|
|
42611
|
+
});
|
|
42612
|
+
envCmd.command("unset-ref").argument("<id>").argument("<key>").description("Remove a credential reference").action((id, key) => {
|
|
42613
|
+
try {
|
|
42614
|
+
unsetServerCredentialRef(id, key);
|
|
42615
|
+
console.log(chalk2.green(`Unset credential ref ${key} on ${id}`));
|
|
42616
|
+
} catch (err) {
|
|
42617
|
+
console.error(chalk2.red(err.message));
|
|
42618
|
+
closeDb();
|
|
42619
|
+
process.exit(1);
|
|
42620
|
+
}
|
|
42621
|
+
closeDb();
|
|
42622
|
+
});
|
|
41838
42623
|
envCmd.command("unset").argument("<id>").argument("<key>").description("Remove an env var").action((id, key) => {
|
|
41839
42624
|
try {
|
|
41840
42625
|
unsetServerEnv(id, key);
|