@hasna/mcps 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +897 -94
- package/bin/mcp.js +618 -53
- package/dist/index.d.ts +5 -1
- package/dist/index.js +620 -13
- 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/provider-profile-seeds.d.ts +2 -0
- package/dist/lib/provider-profiles.d.ts +14 -0
- package/dist/lib/proxy.d.ts +6 -2
- package/dist/lib/remote.d.ts +4 -1
- package/dist/mcp/index.js +618 -53
- package/dist/types.d.ts +87 -2
- package/package.json +2 -2
package/bin/index.js
CHANGED
|
@@ -11944,6 +11944,85 @@ var init_config2 = __esm(() => {
|
|
|
11944
11944
|
DB_PATH = process.env.HASNA_MCPS_DB_PATH ?? process.env.MCPS_DB_PATH ?? join5(MCPS_DIR, "registry.db");
|
|
11945
11945
|
});
|
|
11946
11946
|
|
|
11947
|
+
// src/lib/provider-profile-seeds.ts
|
|
11948
|
+
var DEFAULT_PROVIDER_PROFILE_SEEDS;
|
|
11949
|
+
var init_provider_profile_seeds = __esm(() => {
|
|
11950
|
+
DEFAULT_PROVIDER_PROFILE_SEEDS = [
|
|
11951
|
+
{
|
|
11952
|
+
id: "notion",
|
|
11953
|
+
displayName: "Notion",
|
|
11954
|
+
description: "Connect a Notion workspace so agents can search, read, create, and update workspace content.",
|
|
11955
|
+
endpoint: "https://mcp.notion.com/mcp",
|
|
11956
|
+
transport: "streamable-http",
|
|
11957
|
+
fallbackEndpoints: [
|
|
11958
|
+
{
|
|
11959
|
+
transport: "sse",
|
|
11960
|
+
url: "https://mcp.notion.com/sse",
|
|
11961
|
+
notes: "Fallback for clients that do not support Streamable HTTP."
|
|
11962
|
+
}
|
|
11963
|
+
],
|
|
11964
|
+
authType: "oauth2",
|
|
11965
|
+
authMetadata: {
|
|
11966
|
+
oauthVersion: "2.0",
|
|
11967
|
+
pkce: true,
|
|
11968
|
+
dynamicClientRegistration: true,
|
|
11969
|
+
bearerToken: "none",
|
|
11970
|
+
notes: "Remote Notion MCP uses OAuth with PKCE. Bearer-token authentication is only appropriate for self-hosted/local fallback deployments."
|
|
11971
|
+
},
|
|
11972
|
+
tokenMode: "workspace",
|
|
11973
|
+
installFallback: {
|
|
11974
|
+
command: "npx",
|
|
11975
|
+
args: ["-y", "mcp-remote", "https://mcp.notion.com/sse", "--transport", "sse-only"],
|
|
11976
|
+
packageName: "mcp-remote",
|
|
11977
|
+
url: "https://mcp.notion.com/sse"
|
|
11978
|
+
},
|
|
11979
|
+
docsUrl: "https://developers.notion.com/guides/mcp/build-mcp-client",
|
|
11980
|
+
safety: {
|
|
11981
|
+
requiresApproval: true,
|
|
11982
|
+
dataClasses: ["workspace_content", "pages", "databases", "comments"],
|
|
11983
|
+
notes: "Connected agents operate with the authorizing user's workspace access. Human confirmation is recommended for write-capable workflows."
|
|
11984
|
+
},
|
|
11985
|
+
provenance: {
|
|
11986
|
+
source: "curated",
|
|
11987
|
+
sourceUrl: "https://developers.notion.com/guides/mcp/build-mcp-client",
|
|
11988
|
+
verifiedAt: "2026-05-10"
|
|
11989
|
+
}
|
|
11990
|
+
},
|
|
11991
|
+
{
|
|
11992
|
+
id: "linear",
|
|
11993
|
+
displayName: "Linear",
|
|
11994
|
+
description: "Connect Linear so agents can find, create, and update issues, projects, comments, and related workspace objects.",
|
|
11995
|
+
endpoint: "https://mcp.linear.app/mcp",
|
|
11996
|
+
transport: "streamable-http",
|
|
11997
|
+
authType: "oauth2",
|
|
11998
|
+
authMetadata: {
|
|
11999
|
+
oauthVersion: "2.1",
|
|
12000
|
+
dynamicClientRegistration: true,
|
|
12001
|
+
bearerToken: "optional",
|
|
12002
|
+
notes: "Linear supports interactive OAuth 2.1 with dynamic client registration and optional Authorization: Bearer tokens for OAuth tokens or API keys."
|
|
12003
|
+
},
|
|
12004
|
+
tokenMode: "workspace",
|
|
12005
|
+
installFallback: {
|
|
12006
|
+
command: "npx",
|
|
12007
|
+
args: ["-y", "mcp-remote", "https://mcp.linear.app/mcp"],
|
|
12008
|
+
packageName: "mcp-remote",
|
|
12009
|
+
url: "https://mcp.linear.app/mcp"
|
|
12010
|
+
},
|
|
12011
|
+
docsUrl: "https://linear.app/docs/mcp",
|
|
12012
|
+
safety: {
|
|
12013
|
+
requiresApproval: true,
|
|
12014
|
+
dataClasses: ["issues", "projects", "comments", "teams", "users"],
|
|
12015
|
+
notes: "Linear tools can create and update workspace objects, so write actions should be policy-gated by the platform."
|
|
12016
|
+
},
|
|
12017
|
+
provenance: {
|
|
12018
|
+
source: "curated",
|
|
12019
|
+
sourceUrl: "https://linear.app/docs/mcp",
|
|
12020
|
+
verifiedAt: "2026-05-10"
|
|
12021
|
+
}
|
|
12022
|
+
}
|
|
12023
|
+
];
|
|
12024
|
+
});
|
|
12025
|
+
|
|
11947
12026
|
// src/lib/db.ts
|
|
11948
12027
|
import { mkdirSync as mkdirSync5 } from "fs";
|
|
11949
12028
|
function getDb() {
|
|
@@ -12029,6 +12108,50 @@ function getDb() {
|
|
|
12029
12108
|
('github-mcp-topic', 'GitHub MCP Topic', 'github-topic', 'https://api.github.com/search/repositories', 'GitHub repositories tagged with mcp-server topic')
|
|
12030
12109
|
`);
|
|
12031
12110
|
}
|
|
12111
|
+
db.exec(`
|
|
12112
|
+
CREATE TABLE IF NOT EXISTS provider_profiles (
|
|
12113
|
+
id TEXT PRIMARY KEY,
|
|
12114
|
+
display_name TEXT NOT NULL,
|
|
12115
|
+
description TEXT,
|
|
12116
|
+
endpoint TEXT,
|
|
12117
|
+
transport TEXT NOT NULL,
|
|
12118
|
+
fallback_endpoints TEXT NOT NULL DEFAULT '[]',
|
|
12119
|
+
auth_type TEXT NOT NULL,
|
|
12120
|
+
auth_metadata TEXT NOT NULL DEFAULT '{}',
|
|
12121
|
+
scopes TEXT NOT NULL DEFAULT '[]',
|
|
12122
|
+
token_mode TEXT NOT NULL DEFAULT 'none',
|
|
12123
|
+
install_fallback TEXT NOT NULL DEFAULT '{}',
|
|
12124
|
+
docs_url TEXT,
|
|
12125
|
+
safety TEXT NOT NULL DEFAULT '{}',
|
|
12126
|
+
provenance TEXT NOT NULL DEFAULT '{"source":"manual"}',
|
|
12127
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
12128
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
12129
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
12130
|
+
)
|
|
12131
|
+
`);
|
|
12132
|
+
try {
|
|
12133
|
+
db.exec("ALTER TABLE provider_profiles ADD COLUMN fallback_endpoints TEXT NOT NULL DEFAULT '[]'");
|
|
12134
|
+
} catch {}
|
|
12135
|
+
try {
|
|
12136
|
+
db.exec("ALTER TABLE provider_profiles ADD COLUMN auth_metadata TEXT NOT NULL DEFAULT '{}'");
|
|
12137
|
+
} catch {}
|
|
12138
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_provider_profiles_enabled ON provider_profiles(enabled)");
|
|
12139
|
+
const providerProfileCount = db.query("SELECT COUNT(*) as c FROM provider_profiles").get().c;
|
|
12140
|
+
if (providerProfileCount === 0) {
|
|
12141
|
+
const insertProviderProfile = db.prepare(`
|
|
12142
|
+
INSERT OR IGNORE INTO provider_profiles (
|
|
12143
|
+
id, display_name, description, endpoint, transport, fallback_endpoints,
|
|
12144
|
+
auth_type, auth_metadata, scopes, token_mode, install_fallback,
|
|
12145
|
+
docs_url, safety, provenance, enabled
|
|
12146
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
12147
|
+
`);
|
|
12148
|
+
const run = db.transaction(() => {
|
|
12149
|
+
for (const profile of DEFAULT_PROVIDER_PROFILE_SEEDS) {
|
|
12150
|
+
insertProviderProfile.run(profile.id, profile.displayName, profile.description ?? null, profile.endpoint ?? null, profile.transport, JSON.stringify(profile.fallbackEndpoints ?? []), profile.authType, JSON.stringify(profile.authMetadata ?? {}), JSON.stringify(profile.scopes ?? []), profile.tokenMode ?? "none", JSON.stringify(profile.installFallback ?? null), profile.docsUrl ?? null, JSON.stringify(profile.safety ?? {}), JSON.stringify(profile.provenance), profile.enabled === false ? 0 : 1);
|
|
12151
|
+
}
|
|
12152
|
+
});
|
|
12153
|
+
run();
|
|
12154
|
+
}
|
|
12032
12155
|
db.exec(`
|
|
12033
12156
|
CREATE TABLE IF NOT EXISTS feedback (
|
|
12034
12157
|
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
@@ -12059,6 +12182,7 @@ var db = null, _adapter = null;
|
|
|
12059
12182
|
var init_db = __esm(() => {
|
|
12060
12183
|
init_dist();
|
|
12061
12184
|
init_config2();
|
|
12185
|
+
init_provider_profile_seeds();
|
|
12062
12186
|
});
|
|
12063
12187
|
|
|
12064
12188
|
// node_modules/ajv/dist/compile/codegen/code.js
|
|
@@ -19288,12 +19412,12 @@ var init_sources = __esm(() => {
|
|
|
19288
19412
|
var require_package = __commonJS((exports, module) => {
|
|
19289
19413
|
module.exports = {
|
|
19290
19414
|
name: "@hasna/mcps",
|
|
19291
|
-
version: "0.0.
|
|
19415
|
+
version: "0.0.15",
|
|
19292
19416
|
description: "Meta-MCP registry & CLI \u2014 discover, manage, and proxy MCP servers",
|
|
19293
19417
|
type: "module",
|
|
19294
19418
|
repository: {
|
|
19295
19419
|
type: "git",
|
|
19296
|
-
url: "https://github.com/hasna/mcps.git"
|
|
19420
|
+
url: "git+https://github.com/hasna/mcps.git"
|
|
19297
19421
|
},
|
|
19298
19422
|
main: "dist/index.js",
|
|
19299
19423
|
types: "dist/index.d.ts",
|
|
@@ -35467,6 +35591,185 @@ class StreamableHTTPClientTransport {
|
|
|
35467
35591
|
// src/lib/proxy.ts
|
|
35468
35592
|
init_config2();
|
|
35469
35593
|
init_db();
|
|
35594
|
+
|
|
35595
|
+
// src/lib/local-command-consent.ts
|
|
35596
|
+
class LocalCommandConsentError extends Error {
|
|
35597
|
+
review;
|
|
35598
|
+
constructor(message, review) {
|
|
35599
|
+
super(message);
|
|
35600
|
+
this.name = "LocalCommandConsentError";
|
|
35601
|
+
this.review = review;
|
|
35602
|
+
}
|
|
35603
|
+
}
|
|
35604
|
+
var SHELL_COMMANDS = new Set(["bash", "sh", "zsh", "fish", "cmd", "cmd.exe", "powershell", "powershell.exe", "pwsh"]);
|
|
35605
|
+
var DESTRUCTIVE_COMMANDS = new Set([
|
|
35606
|
+
"rm",
|
|
35607
|
+
"dd",
|
|
35608
|
+
"mkfs",
|
|
35609
|
+
"shutdown",
|
|
35610
|
+
"reboot",
|
|
35611
|
+
"poweroff",
|
|
35612
|
+
"halt",
|
|
35613
|
+
"killall",
|
|
35614
|
+
"pkill"
|
|
35615
|
+
]);
|
|
35616
|
+
var SHELL_EVAL_FLAGS = new Set(["-c", "/c", "-Command", "-command", "-EncodedCommand", "-encodedcommand"]);
|
|
35617
|
+
var SECRET_FLAG_PATTERN = /(?:^|[-_])(api[-_]?key|token|secret|password|passwd|credential|auth|private[-_]?key)(?:$|[-_])/i;
|
|
35618
|
+
var SECRET_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
|
|
35619
|
+
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_-]+)$/;
|
|
35620
|
+
var SHELL_META_PATTERN = /[;&|`<>]|\$\(/;
|
|
35621
|
+
function commandBase(command) {
|
|
35622
|
+
return command.trim().split(/[\\/]/).pop()?.toLowerCase() || command.trim().toLowerCase();
|
|
35623
|
+
}
|
|
35624
|
+
function normalizeArgs(args) {
|
|
35625
|
+
return (args ?? []).map((arg) => String(arg));
|
|
35626
|
+
}
|
|
35627
|
+
function isSecretKey(key) {
|
|
35628
|
+
return SECRET_KEY_PATTERN.test(key) || SECRET_FLAG_PATTERN.test(key);
|
|
35629
|
+
}
|
|
35630
|
+
function isSecretValue(value) {
|
|
35631
|
+
return SECRET_VALUE_PATTERN.test(value.trim());
|
|
35632
|
+
}
|
|
35633
|
+
function isSecretAssignment(arg) {
|
|
35634
|
+
const eqIdx = arg.indexOf("=");
|
|
35635
|
+
if (eqIdx <= 0)
|
|
35636
|
+
return false;
|
|
35637
|
+
const key = arg.slice(0, eqIdx);
|
|
35638
|
+
const value = arg.slice(eqIdx + 1);
|
|
35639
|
+
return isSecretKey(key) || isSecretValue(value);
|
|
35640
|
+
}
|
|
35641
|
+
function isSecretArg(args, index) {
|
|
35642
|
+
const arg = args[index] ?? "";
|
|
35643
|
+
const previous = args[index - 1] ?? "";
|
|
35644
|
+
return isSecretAssignment(arg) || isSecretValue(arg) || SECRET_FLAG_PATTERN.test(previous);
|
|
35645
|
+
}
|
|
35646
|
+
function quoteArg(value) {
|
|
35647
|
+
return JSON.stringify(value);
|
|
35648
|
+
}
|
|
35649
|
+
function displayCommand(command, args) {
|
|
35650
|
+
return [quoteArg(command), ...args.map((arg, index) => quoteArg(isSecretArg(args, index) ? "<redacted>" : arg))].join(" ");
|
|
35651
|
+
}
|
|
35652
|
+
function pushRisk(risks, risk) {
|
|
35653
|
+
if (risks.some((existing) => existing.code === risk.code && existing.evidence === risk.evidence))
|
|
35654
|
+
return;
|
|
35655
|
+
risks.push(risk);
|
|
35656
|
+
}
|
|
35657
|
+
function inspectRisks(command, args, env) {
|
|
35658
|
+
const risks = [];
|
|
35659
|
+
const base = commandBase(command);
|
|
35660
|
+
const joined = [command, ...args].join(" ");
|
|
35661
|
+
if (SHELL_COMMANDS.has(base)) {
|
|
35662
|
+
pushRisk(risks, {
|
|
35663
|
+
code: "shell_interpreter",
|
|
35664
|
+
severity: "warning",
|
|
35665
|
+
message: "Command launches a shell interpreter.",
|
|
35666
|
+
evidence: base
|
|
35667
|
+
});
|
|
35668
|
+
if (args.some((arg) => SHELL_EVAL_FLAGS.has(arg))) {
|
|
35669
|
+
pushRisk(risks, {
|
|
35670
|
+
code: "shell_eval",
|
|
35671
|
+
severity: "danger",
|
|
35672
|
+
message: "Shell command evaluates an inline script.",
|
|
35673
|
+
evidence: base
|
|
35674
|
+
});
|
|
35675
|
+
}
|
|
35676
|
+
}
|
|
35677
|
+
if (base === "sudo") {
|
|
35678
|
+
pushRisk(risks, {
|
|
35679
|
+
code: "privilege_escalation",
|
|
35680
|
+
severity: "danger",
|
|
35681
|
+
message: "Command requests elevated privileges.",
|
|
35682
|
+
evidence: base
|
|
35683
|
+
});
|
|
35684
|
+
}
|
|
35685
|
+
if (DESTRUCTIVE_COMMANDS.has(base) || /\brm\s+-[^\s]*[rf][^\s]*\b/.test(joined) || /--no-preserve-root\b/.test(joined)) {
|
|
35686
|
+
pushRisk(risks, {
|
|
35687
|
+
code: "destructive_command",
|
|
35688
|
+
severity: "danger",
|
|
35689
|
+
message: "Command includes a destructive system operation.",
|
|
35690
|
+
evidence: base
|
|
35691
|
+
});
|
|
35692
|
+
}
|
|
35693
|
+
if (/\b(curl|wget)\b[\s\S]*\|[\s\S]*\b(sh|bash|zsh|fish)\b/.test(joined)) {
|
|
35694
|
+
pushRisk(risks, {
|
|
35695
|
+
code: "download_pipe_shell",
|
|
35696
|
+
severity: "danger",
|
|
35697
|
+
message: "Command downloads remote content and pipes it to a shell."
|
|
35698
|
+
});
|
|
35699
|
+
}
|
|
35700
|
+
if ([command, ...args].some((part) => SHELL_META_PATTERN.test(part))) {
|
|
35701
|
+
pushRisk(risks, {
|
|
35702
|
+
code: "shell_metacharacters",
|
|
35703
|
+
severity: "warning",
|
|
35704
|
+
message: "Command or arguments contain shell metacharacters."
|
|
35705
|
+
});
|
|
35706
|
+
}
|
|
35707
|
+
if (args.some((arg, index) => isSecretArg(args, index))) {
|
|
35708
|
+
pushRisk(risks, {
|
|
35709
|
+
code: "inline_secret",
|
|
35710
|
+
severity: "danger",
|
|
35711
|
+
message: "Command arguments appear to contain inline secret material."
|
|
35712
|
+
});
|
|
35713
|
+
}
|
|
35714
|
+
const secretEnvKeys = Object.keys(env).filter(isSecretKey).sort();
|
|
35715
|
+
if (secretEnvKeys.length > 0) {
|
|
35716
|
+
pushRisk(risks, {
|
|
35717
|
+
code: "secret_env",
|
|
35718
|
+
severity: "warning",
|
|
35719
|
+
message: "Environment contains secret-like keys; values are redacted from consent output.",
|
|
35720
|
+
evidence: secretEnvKeys.join(", ")
|
|
35721
|
+
});
|
|
35722
|
+
}
|
|
35723
|
+
return risks;
|
|
35724
|
+
}
|
|
35725
|
+
function inspectLocalCommand(input) {
|
|
35726
|
+
const args = normalizeArgs(input.args);
|
|
35727
|
+
const env = input.env ?? {};
|
|
35728
|
+
const transport = input.transport ?? "stdio";
|
|
35729
|
+
const risks = inspectRisks(input.command, args, env);
|
|
35730
|
+
return {
|
|
35731
|
+
requiresConsent: transport === "stdio",
|
|
35732
|
+
operation: input.operation ?? "launch",
|
|
35733
|
+
command: input.command,
|
|
35734
|
+
args,
|
|
35735
|
+
displayCommand: displayCommand(input.command, args),
|
|
35736
|
+
envKeys: Object.keys(env).sort(),
|
|
35737
|
+
risks,
|
|
35738
|
+
hasDangerousRisk: risks.some((risk) => risk.severity === "danger")
|
|
35739
|
+
};
|
|
35740
|
+
}
|
|
35741
|
+
function formatLocalCommandReview(review) {
|
|
35742
|
+
const lines = [
|
|
35743
|
+
`Command: ${review.displayCommand}`,
|
|
35744
|
+
review.envKeys.length > 0 ? `Env keys: ${review.envKeys.join(", ")}` : "Env keys: <none>"
|
|
35745
|
+
];
|
|
35746
|
+
if (review.risks.length > 0) {
|
|
35747
|
+
lines.push("Risks:");
|
|
35748
|
+
for (const risk of review.risks) {
|
|
35749
|
+
lines.push(`- ${risk.severity}: ${risk.code} - ${risk.message}${risk.evidence ? ` (${risk.evidence})` : ""}`);
|
|
35750
|
+
}
|
|
35751
|
+
} else {
|
|
35752
|
+
lines.push("Risks: none detected");
|
|
35753
|
+
}
|
|
35754
|
+
return lines.join(`
|
|
35755
|
+
`);
|
|
35756
|
+
}
|
|
35757
|
+
function assertLocalCommandConsent(input, consent = {}) {
|
|
35758
|
+
const review = inspectLocalCommand(input);
|
|
35759
|
+
if (!review.requiresConsent)
|
|
35760
|
+
return review;
|
|
35761
|
+
if (consent.approved !== true) {
|
|
35762
|
+
throw new LocalCommandConsentError(`local stdio command approval is required before ${review.operation}.
|
|
35763
|
+
${formatLocalCommandReview(review)}`, review);
|
|
35764
|
+
}
|
|
35765
|
+
if (review.hasDangerousRisk && consent.allowRisky !== true) {
|
|
35766
|
+
throw new LocalCommandConsentError(`risky command approval is required before ${review.operation}.
|
|
35767
|
+
${formatLocalCommandReview(review)}`, review);
|
|
35768
|
+
}
|
|
35769
|
+
return review;
|
|
35770
|
+
}
|
|
35771
|
+
|
|
35772
|
+
// src/lib/proxy.ts
|
|
35470
35773
|
var connections = new Map;
|
|
35471
35774
|
var inflightConnections = new Map;
|
|
35472
35775
|
var CONNECT_CONCURRENCY = 4;
|
|
@@ -35493,7 +35796,7 @@ function requireUrl(entry) {
|
|
|
35493
35796
|
throw new Error(`Server "${entry.id}" has an invalid URL: ${entry.url}`);
|
|
35494
35797
|
}
|
|
35495
35798
|
}
|
|
35496
|
-
async function connectToServer(entry) {
|
|
35799
|
+
async function connectToServer(entry, options = {}) {
|
|
35497
35800
|
if (connections.has(entry.id)) {
|
|
35498
35801
|
return connections.get(entry.id);
|
|
35499
35802
|
}
|
|
@@ -35509,6 +35812,13 @@ async function connectToServer(entry) {
|
|
|
35509
35812
|
if (!entry.command?.trim()) {
|
|
35510
35813
|
throw new Error(`Server "${entry.id}" is missing a command`);
|
|
35511
35814
|
}
|
|
35815
|
+
assertLocalCommandConsent({
|
|
35816
|
+
command: entry.command,
|
|
35817
|
+
args: entry.args,
|
|
35818
|
+
env: entry.env,
|
|
35819
|
+
transport: entry.transport,
|
|
35820
|
+
operation: "launch"
|
|
35821
|
+
}, options.localCommandConsent);
|
|
35512
35822
|
transport = new StdioClientTransport({
|
|
35513
35823
|
command: entry.command,
|
|
35514
35824
|
args: entry.args,
|
|
@@ -35625,7 +35935,7 @@ async function callTool(prefixedName, args) {
|
|
|
35625
35935
|
]
|
|
35626
35936
|
};
|
|
35627
35937
|
}
|
|
35628
|
-
async function connectAllEnabled() {
|
|
35938
|
+
async function connectAllEnabled(options = {}) {
|
|
35629
35939
|
const servers = listServers().filter((s) => s.enabled);
|
|
35630
35940
|
const results = [];
|
|
35631
35941
|
let index = 0;
|
|
@@ -35637,7 +35947,7 @@ async function connectAllEnabled() {
|
|
|
35637
35947
|
return;
|
|
35638
35948
|
const server = servers[current];
|
|
35639
35949
|
try {
|
|
35640
|
-
const conn = await connectToServer(server);
|
|
35950
|
+
const conn = await connectToServer(server, options);
|
|
35641
35951
|
results.push(conn);
|
|
35642
35952
|
} catch (err) {
|
|
35643
35953
|
console.error(`Failed to connect to ${server.name}: ${err.message}`);
|
|
@@ -35649,13 +35959,28 @@ async function connectAllEnabled() {
|
|
|
35649
35959
|
}
|
|
35650
35960
|
|
|
35651
35961
|
// src/lib/doctor.ts
|
|
35652
|
-
async function diagnoseServer(server) {
|
|
35962
|
+
async function diagnoseServer(server, options = {}) {
|
|
35653
35963
|
const checks4 = [];
|
|
35964
|
+
let hasLocalConsent = true;
|
|
35654
35965
|
if (server.transport === "stdio") {
|
|
35966
|
+
try {
|
|
35967
|
+
assertLocalCommandConsent({
|
|
35968
|
+
command: server.command,
|
|
35969
|
+
args: server.args,
|
|
35970
|
+
env: server.env,
|
|
35971
|
+
transport: server.transport,
|
|
35972
|
+
operation: "diagnose"
|
|
35973
|
+
}, options.localCommandConsent);
|
|
35974
|
+
} catch (err) {
|
|
35975
|
+
hasLocalConsent = false;
|
|
35976
|
+
checks4.push({ name: "local command consent", pass: false, message: err.message });
|
|
35977
|
+
}
|
|
35655
35978
|
try {
|
|
35656
35979
|
const path = execFileSync("which", [server.command], { stdio: "pipe" }).toString().trim();
|
|
35657
35980
|
let version2 = "";
|
|
35658
35981
|
try {
|
|
35982
|
+
if (!hasLocalConsent)
|
|
35983
|
+
throw new Error("local stdio command approval is required before version probing");
|
|
35659
35984
|
version2 = execFileSync(server.command, ["--version"], { stdio: "pipe" }).toString().trim().split(`
|
|
35660
35985
|
`)[0];
|
|
35661
35986
|
} catch {}
|
|
@@ -35686,10 +36011,10 @@ async function diagnoseServer(server) {
|
|
|
35686
36011
|
checks4.push({ name: "URL reachable", pass: false, message: `unreachable: ${err.message}` });
|
|
35687
36012
|
}
|
|
35688
36013
|
}
|
|
35689
|
-
if (server.enabled) {
|
|
36014
|
+
if (server.enabled && hasLocalConsent) {
|
|
35690
36015
|
try {
|
|
35691
36016
|
await Promise.race([
|
|
35692
|
-
connectToServer(server),
|
|
36017
|
+
connectToServer(server, { localCommandConsent: options.localCommandConsent }),
|
|
35693
36018
|
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout after 10s")), 1e4))
|
|
35694
36019
|
]);
|
|
35695
36020
|
await disconnectServer(server.id);
|
|
@@ -35697,8 +36022,10 @@ async function diagnoseServer(server) {
|
|
|
35697
36022
|
} catch (err) {
|
|
35698
36023
|
checks4.push({ name: "connect & list tools", pass: false, message: err.message });
|
|
35699
36024
|
}
|
|
35700
|
-
} else {
|
|
36025
|
+
} else if (!server.enabled) {
|
|
35701
36026
|
checks4.push({ name: "connect & list tools", pass: true, message: "skipped (server disabled)" });
|
|
36027
|
+
} else {
|
|
36028
|
+
checks4.push({ name: "connect & list tools", pass: false, message: "skipped until local stdio command is approved" });
|
|
35702
36029
|
}
|
|
35703
36030
|
return {
|
|
35704
36031
|
server,
|
|
@@ -35739,7 +36066,7 @@ async function getRegistryServer(id) {
|
|
|
35739
36066
|
const all = entries.map(parseRegistryEntry);
|
|
35740
36067
|
return all.find((s) => s.id === id) || null;
|
|
35741
36068
|
}
|
|
35742
|
-
async function installFromRegistry(id) {
|
|
36069
|
+
async function installFromRegistry(id, options = {}) {
|
|
35743
36070
|
const server = await getRegistryServer(id);
|
|
35744
36071
|
if (!server) {
|
|
35745
36072
|
throw new Error(`Server "${id}" not found in registry`);
|
|
@@ -35759,6 +36086,13 @@ async function installFromRegistry(id) {
|
|
|
35759
36086
|
transport = pkg.transport.type;
|
|
35760
36087
|
}
|
|
35761
36088
|
}
|
|
36089
|
+
assertLocalCommandConsent({
|
|
36090
|
+
command,
|
|
36091
|
+
args,
|
|
36092
|
+
transport,
|
|
36093
|
+
env: {},
|
|
36094
|
+
operation: "register"
|
|
36095
|
+
}, options.localCommandConsent);
|
|
35762
36096
|
return addServer({
|
|
35763
36097
|
name: server.name,
|
|
35764
36098
|
description: server.description,
|
|
@@ -35853,7 +36187,22 @@ function installToGemini(entry) {
|
|
|
35853
36187
|
return { agent: "gemini", success: false, error: err.message };
|
|
35854
36188
|
}
|
|
35855
36189
|
}
|
|
35856
|
-
function installToAgents(entry, targets = ["claude", "codex", "gemini"]) {
|
|
36190
|
+
function installToAgents(entry, targets = ["claude", "codex", "gemini"], options = {}) {
|
|
36191
|
+
try {
|
|
36192
|
+
assertLocalCommandConsent({
|
|
36193
|
+
command: entry.command,
|
|
36194
|
+
args: entry.args,
|
|
36195
|
+
env: entry.env,
|
|
36196
|
+
transport: entry.transport,
|
|
36197
|
+
operation: "install"
|
|
36198
|
+
}, options.localCommandConsent);
|
|
36199
|
+
} catch (err) {
|
|
36200
|
+
return targets.map((target) => ({
|
|
36201
|
+
agent: target,
|
|
36202
|
+
success: false,
|
|
36203
|
+
error: err.message
|
|
36204
|
+
}));
|
|
36205
|
+
}
|
|
35857
36206
|
return targets.map((target) => {
|
|
35858
36207
|
if (target === "claude")
|
|
35859
36208
|
return installToClaude(entry);
|
|
@@ -36767,6 +37116,112 @@ async function runFleetInstall(options = {}, dependencies = {}) {
|
|
|
36767
37116
|
}));
|
|
36768
37117
|
}
|
|
36769
37118
|
|
|
37119
|
+
// src/lib/provider-profiles.ts
|
|
37120
|
+
init_db();
|
|
37121
|
+
init_provider_profile_seeds();
|
|
37122
|
+
var TRANSPORTS = new Set(["stdio", "sse", "streamable-http"]);
|
|
37123
|
+
var AUTH_TYPES = new Set(["none", "oauth2", "api_key", "bearer_token", "custom"]);
|
|
37124
|
+
var TOKEN_MODES = new Set(["none", "user", "workspace", "service"]);
|
|
37125
|
+
var BEARER_TOKEN_MODES = new Set(["none", "optional", "required"]);
|
|
37126
|
+
var PROVENANCE_SOURCES = new Set(["curated", "official-registry", "npm", "github", "manual"]);
|
|
37127
|
+
function safeJsonParse2(value, fallback) {
|
|
37128
|
+
if (typeof value !== "string")
|
|
37129
|
+
return fallback;
|
|
37130
|
+
try {
|
|
37131
|
+
return JSON.parse(value);
|
|
37132
|
+
} catch {
|
|
37133
|
+
return fallback;
|
|
37134
|
+
}
|
|
37135
|
+
}
|
|
37136
|
+
function normalizeId(id) {
|
|
37137
|
+
const normalized = id.trim().toLowerCase();
|
|
37138
|
+
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(normalized)) {
|
|
37139
|
+
throw new Error("Provider profile id must be lowercase kebab-case");
|
|
37140
|
+
}
|
|
37141
|
+
return normalized;
|
|
37142
|
+
}
|
|
37143
|
+
function parseRow3(row) {
|
|
37144
|
+
const installFallback = safeJsonParse2(row.install_fallback, null);
|
|
37145
|
+
return {
|
|
37146
|
+
id: row.id,
|
|
37147
|
+
displayName: row.display_name,
|
|
37148
|
+
description: row.description || null,
|
|
37149
|
+
endpoint: row.endpoint || null,
|
|
37150
|
+
transport: row.transport,
|
|
37151
|
+
fallbackEndpoints: safeJsonParse2(row.fallback_endpoints, []),
|
|
37152
|
+
authType: row.auth_type,
|
|
37153
|
+
authMetadata: safeJsonParse2(row.auth_metadata, {}),
|
|
37154
|
+
scopes: safeJsonParse2(row.scopes, []),
|
|
37155
|
+
tokenMode: row.token_mode,
|
|
37156
|
+
installFallback,
|
|
37157
|
+
docsUrl: row.docs_url || null,
|
|
37158
|
+
safety: safeJsonParse2(row.safety, {}),
|
|
37159
|
+
provenance: safeJsonParse2(row.provenance, { source: "manual" }),
|
|
37160
|
+
enabled: row.enabled === 1 || row.enabled === true,
|
|
37161
|
+
created_at: row.created_at,
|
|
37162
|
+
updated_at: row.updated_at
|
|
37163
|
+
};
|
|
37164
|
+
}
|
|
37165
|
+
function listProviderProfiles(options = {}) {
|
|
37166
|
+
const db2 = getDb();
|
|
37167
|
+
const sql = options.enabledOnly ? "SELECT * FROM provider_profiles WHERE enabled = 1 ORDER BY display_name" : "SELECT * FROM provider_profiles ORDER BY display_name";
|
|
37168
|
+
return db2.prepare(sql).all().map(parseRow3);
|
|
37169
|
+
}
|
|
37170
|
+
function searchProviderProfiles(query, options = {}) {
|
|
37171
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
37172
|
+
if (!normalizedQuery)
|
|
37173
|
+
return listProviderProfiles(options);
|
|
37174
|
+
return listProviderProfiles(options).filter((profile) => {
|
|
37175
|
+
const searchable = [
|
|
37176
|
+
profile.id,
|
|
37177
|
+
profile.displayName,
|
|
37178
|
+
profile.description ?? "",
|
|
37179
|
+
profile.endpoint ?? "",
|
|
37180
|
+
profile.docsUrl ?? "",
|
|
37181
|
+
profile.provenance.sourceUrl ?? "",
|
|
37182
|
+
profile.provenance.packageName ?? ""
|
|
37183
|
+
].join(`
|
|
37184
|
+
`).toLowerCase();
|
|
37185
|
+
return searchable.includes(normalizedQuery);
|
|
37186
|
+
});
|
|
37187
|
+
}
|
|
37188
|
+
function getProviderProfile(id) {
|
|
37189
|
+
const db2 = getDb();
|
|
37190
|
+
const row = db2.prepare("SELECT * FROM provider_profiles WHERE id = ?").get(normalizeId(id));
|
|
37191
|
+
return row ? parseRow3(row) : null;
|
|
37192
|
+
}
|
|
37193
|
+
function installProviderProfile(id, options = {}) {
|
|
37194
|
+
const profile = getProviderProfile(id);
|
|
37195
|
+
if (!profile)
|
|
37196
|
+
throw new Error(`Provider profile "${id}" not found`);
|
|
37197
|
+
if (!profile.enabled)
|
|
37198
|
+
throw new Error(`Provider profile "${id}" is disabled`);
|
|
37199
|
+
const fallback = profile.installFallback;
|
|
37200
|
+
const useFallback = options.useFallback || !profile.endpoint;
|
|
37201
|
+
const command = useFallback ? fallback?.command : fallback?.command ?? "npx";
|
|
37202
|
+
const args = useFallback ? fallback?.args ?? [] : fallback?.args ?? [];
|
|
37203
|
+
if (!command) {
|
|
37204
|
+
throw new Error(`Provider profile "${id}" does not define an install fallback command`);
|
|
37205
|
+
}
|
|
37206
|
+
assertLocalCommandConsent({
|
|
37207
|
+
command,
|
|
37208
|
+
args,
|
|
37209
|
+
env: fallback?.env ?? {},
|
|
37210
|
+
transport: useFallback ? "stdio" : profile.transport,
|
|
37211
|
+
operation: "register"
|
|
37212
|
+
}, options.localCommandConsent);
|
|
37213
|
+
return addServer({
|
|
37214
|
+
name: options.name ?? profile.displayName,
|
|
37215
|
+
description: profile.description ?? undefined,
|
|
37216
|
+
command,
|
|
37217
|
+
args,
|
|
37218
|
+
env: fallback?.env,
|
|
37219
|
+
transport: useFallback ? "stdio" : profile.transport,
|
|
37220
|
+
url: useFallback ? fallback?.url : profile.endpoint ?? undefined,
|
|
37221
|
+
source: "provider-profile"
|
|
37222
|
+
});
|
|
37223
|
+
}
|
|
37224
|
+
|
|
36770
37225
|
// src/cli/index.tsx
|
|
36771
37226
|
import * as readline from "readline";
|
|
36772
37227
|
|
|
@@ -38035,6 +38490,13 @@ function jsonContent(value) {
|
|
|
38035
38490
|
function errorContent(text) {
|
|
38036
38491
|
return { ...textContent(text), isError: true };
|
|
38037
38492
|
}
|
|
38493
|
+
function localConsent(input) {
|
|
38494
|
+
return {
|
|
38495
|
+
approved: input.allow_local_stdio === true,
|
|
38496
|
+
allowRisky: input.allow_risky_command === true,
|
|
38497
|
+
source: "mcp"
|
|
38498
|
+
};
|
|
38499
|
+
}
|
|
38038
38500
|
function buildMcpTools() {
|
|
38039
38501
|
const definitions = [
|
|
38040
38502
|
{
|
|
@@ -38059,23 +38521,46 @@ function buildMcpTools() {
|
|
|
38059
38521
|
description: exports_external2.string().optional().describe("Description"),
|
|
38060
38522
|
transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("Transport type"),
|
|
38061
38523
|
url: exports_external2.string().optional().describe("URL for remote transports"),
|
|
38062
|
-
env: exports_external2.record(exports_external2.string()).optional().describe("Environment variables")
|
|
38524
|
+
env: exports_external2.record(exports_external2.string()).optional().describe("Environment variables"),
|
|
38525
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering this local stdio command"),
|
|
38526
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve registering risky local command patterns")
|
|
38063
38527
|
},
|
|
38064
|
-
run: (
|
|
38065
|
-
command
|
|
38066
|
-
args
|
|
38067
|
-
|
|
38068
|
-
|
|
38069
|
-
|
|
38070
|
-
|
|
38071
|
-
|
|
38072
|
-
|
|
38528
|
+
run: (input) => {
|
|
38529
|
+
const command = String(input.command);
|
|
38530
|
+
const args = Array.isArray(input.args) ? input.args.map(String) : [];
|
|
38531
|
+
const env = isRecordOfStrings(input.env) ? input.env : {};
|
|
38532
|
+
const transport = input.transport;
|
|
38533
|
+
try {
|
|
38534
|
+
assertLocalCommandConsent({ command, args, env, transport, operation: "register" }, localConsent(input));
|
|
38535
|
+
return jsonContent(addServer({
|
|
38536
|
+
command,
|
|
38537
|
+
args,
|
|
38538
|
+
name: typeof input.name === "string" ? input.name : undefined,
|
|
38539
|
+
description: typeof input.description === "string" ? input.description : undefined,
|
|
38540
|
+
transport,
|
|
38541
|
+
url: typeof input.url === "string" ? input.url : undefined,
|
|
38542
|
+
env
|
|
38543
|
+
}));
|
|
38544
|
+
} catch (err) {
|
|
38545
|
+
return errorContent(err.message);
|
|
38546
|
+
}
|
|
38547
|
+
}
|
|
38073
38548
|
},
|
|
38074
38549
|
{
|
|
38075
38550
|
name: "install_from_registry",
|
|
38076
38551
|
description: "Install an MCP server from the official registry",
|
|
38077
|
-
paramsSchema: {
|
|
38078
|
-
|
|
38552
|
+
paramsSchema: {
|
|
38553
|
+
id: exports_external2.string().describe("Registry server ID"),
|
|
38554
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering registry stdio commands"),
|
|
38555
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve registering risky local command patterns")
|
|
38556
|
+
},
|
|
38557
|
+
run: async (input) => {
|
|
38558
|
+
try {
|
|
38559
|
+
return jsonContent(await installFromRegistry(String(input.id), { localCommandConsent: localConsent(input) }));
|
|
38560
|
+
} catch (err) {
|
|
38561
|
+
return errorContent(err.message);
|
|
38562
|
+
}
|
|
38563
|
+
}
|
|
38079
38564
|
},
|
|
38080
38565
|
{
|
|
38081
38566
|
name: "remove_server",
|
|
@@ -38121,26 +38606,41 @@ function buildMcpTools() {
|
|
|
38121
38606
|
command: exports_external2.string().optional().describe("New command"),
|
|
38122
38607
|
args: exports_external2.array(exports_external2.string()).optional().describe("New args list"),
|
|
38123
38608
|
transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("New transport type"),
|
|
38124
|
-
url: exports_external2.string().optional().describe("New URL for remote transports")
|
|
38609
|
+
url: exports_external2.string().optional().describe("New URL for remote transports"),
|
|
38610
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve updating this local stdio command"),
|
|
38611
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve risky local command patterns")
|
|
38125
38612
|
},
|
|
38126
|
-
run: (
|
|
38127
|
-
const serverId = String(id);
|
|
38613
|
+
run: (input) => {
|
|
38614
|
+
const serverId = String(input.id);
|
|
38128
38615
|
const existing = getServer(serverId);
|
|
38129
38616
|
if (!existing)
|
|
38130
38617
|
return errorContent(`Server "${serverId}" not found.`);
|
|
38131
38618
|
const fields = {};
|
|
38132
|
-
if (typeof name === "string")
|
|
38133
|
-
fields.name = name;
|
|
38134
|
-
if (typeof description === "string")
|
|
38135
|
-
fields.description = description;
|
|
38136
|
-
if (typeof command === "string")
|
|
38137
|
-
fields.command = command;
|
|
38138
|
-
if (Array.isArray(args))
|
|
38139
|
-
fields.args = args.map(String);
|
|
38140
|
-
if (transport === "stdio" || transport === "sse" || transport === "streamable-http")
|
|
38141
|
-
fields.transport = transport;
|
|
38142
|
-
if (typeof
|
|
38143
|
-
fields.url =
|
|
38619
|
+
if (typeof input.name === "string")
|
|
38620
|
+
fields.name = input.name;
|
|
38621
|
+
if (typeof input.description === "string")
|
|
38622
|
+
fields.description = input.description;
|
|
38623
|
+
if (typeof input.command === "string")
|
|
38624
|
+
fields.command = input.command;
|
|
38625
|
+
if (Array.isArray(input.args))
|
|
38626
|
+
fields.args = input.args.map(String);
|
|
38627
|
+
if (input.transport === "stdio" || input.transport === "sse" || input.transport === "streamable-http")
|
|
38628
|
+
fields.transport = input.transport;
|
|
38629
|
+
if (typeof input.url === "string")
|
|
38630
|
+
fields.url = input.url;
|
|
38631
|
+
if (fields.command !== undefined || fields.args !== undefined || fields.transport !== undefined) {
|
|
38632
|
+
try {
|
|
38633
|
+
assertLocalCommandConsent({
|
|
38634
|
+
command: fields.command ?? existing.command,
|
|
38635
|
+
args: fields.args ?? existing.args,
|
|
38636
|
+
env: existing.env,
|
|
38637
|
+
transport: fields.transport ?? existing.transport,
|
|
38638
|
+
operation: "register"
|
|
38639
|
+
}, localConsent(input));
|
|
38640
|
+
} catch (err) {
|
|
38641
|
+
return errorContent(err.message);
|
|
38642
|
+
}
|
|
38643
|
+
}
|
|
38144
38644
|
return jsonContent(redactServerEnv(updateServer(serverId, fields)));
|
|
38145
38645
|
}
|
|
38146
38646
|
},
|
|
@@ -38186,6 +38686,58 @@ function buildMcpTools() {
|
|
|
38186
38686
|
limit: typeof limit === "number" ? limit : undefined
|
|
38187
38687
|
}))
|
|
38188
38688
|
},
|
|
38689
|
+
{
|
|
38690
|
+
name: "list_provider_profiles",
|
|
38691
|
+
description: "List curated provider profiles for hosted/common MCP integrations such as Notion and Linear.",
|
|
38692
|
+
paramsSchema: {
|
|
38693
|
+
enabled_only: exports_external2.boolean().optional().describe("Only include enabled provider profiles")
|
|
38694
|
+
},
|
|
38695
|
+
run: ({ enabled_only }) => jsonContent(listProviderProfiles({ enabledOnly: enabled_only === true }))
|
|
38696
|
+
},
|
|
38697
|
+
{
|
|
38698
|
+
name: "search_provider_profiles",
|
|
38699
|
+
description: "Search curated provider profiles separately from raw MCP registry/source search.",
|
|
38700
|
+
paramsSchema: {
|
|
38701
|
+
query: exports_external2.string().describe("Search query such as 'notion', 'linear', or an endpoint URL"),
|
|
38702
|
+
enabled_only: exports_external2.boolean().optional().describe("Only include enabled provider profiles")
|
|
38703
|
+
},
|
|
38704
|
+
run: ({ query, enabled_only }) => jsonContent(searchProviderProfiles(String(query), { enabledOnly: enabled_only === true }))
|
|
38705
|
+
},
|
|
38706
|
+
{
|
|
38707
|
+
name: "get_provider_profile",
|
|
38708
|
+
description: "Get one curated provider profile by ID.",
|
|
38709
|
+
paramsSchema: {
|
|
38710
|
+
id: exports_external2.string().describe("Provider profile ID")
|
|
38711
|
+
},
|
|
38712
|
+
run: ({ id }) => {
|
|
38713
|
+
const profile = getProviderProfile(String(id));
|
|
38714
|
+
if (!profile)
|
|
38715
|
+
return errorContent(`Provider profile "${String(id)}" not found.`);
|
|
38716
|
+
return jsonContent(profile);
|
|
38717
|
+
}
|
|
38718
|
+
},
|
|
38719
|
+
{
|
|
38720
|
+
name: "install_provider_profile",
|
|
38721
|
+
description: "Register a curated provider profile as an MCP server.",
|
|
38722
|
+
paramsSchema: {
|
|
38723
|
+
id: exports_external2.string().describe("Provider profile ID"),
|
|
38724
|
+
name: exports_external2.string().optional().describe("Override registered server name"),
|
|
38725
|
+
use_fallback: exports_external2.boolean().optional().describe("Install the stdio fallback command instead of the direct remote transport"),
|
|
38726
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering provider stdio fallback commands"),
|
|
38727
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve risky local command patterns")
|
|
38728
|
+
},
|
|
38729
|
+
run: (input) => {
|
|
38730
|
+
try {
|
|
38731
|
+
return jsonContent(redactServerEnv(installProviderProfile(String(input.id), {
|
|
38732
|
+
name: typeof input.name === "string" ? input.name : undefined,
|
|
38733
|
+
useFallback: input.use_fallback === true,
|
|
38734
|
+
localCommandConsent: localConsent(input)
|
|
38735
|
+
})));
|
|
38736
|
+
} catch (err) {
|
|
38737
|
+
return errorContent(err.message);
|
|
38738
|
+
}
|
|
38739
|
+
}
|
|
38740
|
+
},
|
|
38189
38741
|
{
|
|
38190
38742
|
name: "list_sources",
|
|
38191
38743
|
description: "List all configured search sources for finding MCP servers",
|
|
@@ -38252,15 +38804,19 @@ function buildMcpTools() {
|
|
|
38252
38804
|
description: "Install a registered MCP server into Claude Code, Codex, and/or Gemini",
|
|
38253
38805
|
paramsSchema: {
|
|
38254
38806
|
id: exports_external2.string().describe("Server ID to install (from list_servers)"),
|
|
38255
|
-
targets: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("Target agents to install into (default: all)")
|
|
38807
|
+
targets: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("Target agents to install into (default: all)"),
|
|
38808
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve installing local stdio commands into local agent configs"),
|
|
38809
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve installing risky local command patterns")
|
|
38256
38810
|
},
|
|
38257
|
-
run: (
|
|
38258
|
-
const serverId = String(id);
|
|
38811
|
+
run: (input) => {
|
|
38812
|
+
const serverId = String(input.id);
|
|
38259
38813
|
const entry = getServer(serverId);
|
|
38260
38814
|
if (!entry)
|
|
38261
38815
|
return errorContent(`Server "${serverId}" not found.`);
|
|
38262
|
-
const agentTargets = Array.isArray(targets) ? targets : undefined;
|
|
38263
|
-
return jsonContent(installToAgents(entry, agentTargets ?? ["claude", "codex", "gemini"]
|
|
38816
|
+
const agentTargets = Array.isArray(input.targets) ? input.targets : undefined;
|
|
38817
|
+
return jsonContent(installToAgents(entry, agentTargets ?? ["claude", "codex", "gemini"], {
|
|
38818
|
+
localCommandConsent: localConsent(input)
|
|
38819
|
+
}));
|
|
38264
38820
|
}
|
|
38265
38821
|
},
|
|
38266
38822
|
{
|
|
@@ -38272,11 +38828,14 @@ function buildMcpTools() {
|
|
|
38272
38828
|
{
|
|
38273
38829
|
name: "connect_and_list_tools",
|
|
38274
38830
|
description: "Connect to all enabled MCP servers and list their available tools",
|
|
38275
|
-
paramsSchema: {
|
|
38276
|
-
|
|
38831
|
+
paramsSchema: {
|
|
38832
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching enabled local stdio commands"),
|
|
38833
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve launching risky local command patterns")
|
|
38834
|
+
},
|
|
38835
|
+
run: async (input) => {
|
|
38277
38836
|
let liveTools = [];
|
|
38278
38837
|
try {
|
|
38279
|
-
await connectAllEnabled();
|
|
38838
|
+
await connectAllEnabled({ localCommandConsent: localConsent(input) });
|
|
38280
38839
|
liveTools = listAllTools();
|
|
38281
38840
|
} finally {
|
|
38282
38841
|
await disconnectAll().catch(() => {
|
|
@@ -38291,11 +38850,13 @@ function buildMcpTools() {
|
|
|
38291
38850
|
description: `Call a tool on a connected upstream MCP server. Tool name format: server_id${TOOL_PREFIX_SEPARATOR}tool_name`,
|
|
38292
38851
|
paramsSchema: {
|
|
38293
38852
|
tool_name: exports_external2.string().describe(`Prefixed tool name (server_id${TOOL_PREFIX_SEPARATOR}tool_name)`),
|
|
38294
|
-
arguments: exports_external2.record(exports_external2.unknown()).optional().describe("Tool arguments as key-value pairs")
|
|
38853
|
+
arguments: exports_external2.record(exports_external2.unknown()).optional().describe("Tool arguments as key-value pairs"),
|
|
38854
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching this local stdio command"),
|
|
38855
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve launching risky local command patterns")
|
|
38295
38856
|
},
|
|
38296
|
-
run: async (
|
|
38857
|
+
run: async (input) => {
|
|
38297
38858
|
try {
|
|
38298
|
-
const toolName = String(tool_name);
|
|
38859
|
+
const toolName = String(input.tool_name);
|
|
38299
38860
|
const sepIdx = toolName.indexOf(TOOL_PREFIX_SEPARATOR);
|
|
38300
38861
|
if (sepIdx === -1)
|
|
38301
38862
|
return errorContent(`Error: Invalid tool name "${toolName}"`);
|
|
@@ -38305,8 +38866,8 @@ function buildMcpTools() {
|
|
|
38305
38866
|
return errorContent(`Error: Server "${serverId}" not found.`);
|
|
38306
38867
|
if (!entry.enabled)
|
|
38307
38868
|
return errorContent(`Error: Server "${serverId}" is disabled.`);
|
|
38308
|
-
await connectToServer(entry);
|
|
38309
|
-
const result = await callTool(toolName, readRecord(
|
|
38869
|
+
await connectToServer(entry, { localCommandConsent: localConsent(input) });
|
|
38870
|
+
const result = await callTool(toolName, readRecord(input.arguments));
|
|
38310
38871
|
return { content: result.content };
|
|
38311
38872
|
} catch (error2) {
|
|
38312
38873
|
return errorContent(`Error: ${error2.message}`);
|
|
@@ -38316,13 +38877,17 @@ function buildMcpTools() {
|
|
|
38316
38877
|
{
|
|
38317
38878
|
name: "diagnose_server",
|
|
38318
38879
|
description: "Run health checks on a registered MCP server",
|
|
38319
|
-
paramsSchema: {
|
|
38320
|
-
|
|
38321
|
-
|
|
38880
|
+
paramsSchema: {
|
|
38881
|
+
id: exports_external2.string().describe("Server ID"),
|
|
38882
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching local stdio diagnostics"),
|
|
38883
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve diagnosing risky local command patterns")
|
|
38884
|
+
},
|
|
38885
|
+
run: async (input) => {
|
|
38886
|
+
const serverId = String(input.id);
|
|
38322
38887
|
const entry = getServer(serverId);
|
|
38323
38888
|
if (!entry)
|
|
38324
38889
|
return errorContent(`Server "${serverId}" not found.`);
|
|
38325
|
-
return jsonContent(await diagnoseServer(entry));
|
|
38890
|
+
return jsonContent(await diagnoseServer(entry, { localCommandConsent: localConsent(input) }));
|
|
38326
38891
|
}
|
|
38327
38892
|
},
|
|
38328
38893
|
{
|
|
@@ -38691,6 +39256,20 @@ function isValidId(id) {
|
|
|
38691
39256
|
return /^[a-z0-9-]+$/.test(id);
|
|
38692
39257
|
}
|
|
38693
39258
|
var MAX_BODY_SIZE = 1024 * 1024;
|
|
39259
|
+
function consentFromInput(input) {
|
|
39260
|
+
return {
|
|
39261
|
+
approved: input.allow_local_stdio === true || input.allowLocalStdio === true,
|
|
39262
|
+
allowRisky: input.allow_risky_command === true || input.allowRiskyCommand === true,
|
|
39263
|
+
source: "api"
|
|
39264
|
+
};
|
|
39265
|
+
}
|
|
39266
|
+
function consentFromSearchParams(params) {
|
|
39267
|
+
return {
|
|
39268
|
+
approved: params.get("allow_local_stdio") === "1" || params.get("allow_local_stdio") === "true",
|
|
39269
|
+
allowRisky: params.get("allow_risky_command") === "1" || params.get("allow_risky_command") === "true",
|
|
39270
|
+
source: "api"
|
|
39271
|
+
};
|
|
39272
|
+
}
|
|
38694
39273
|
function isLoopbackHost(hostname3) {
|
|
38695
39274
|
return hostname3 === "127.0.0.1" || hostname3 === "localhost" || hostname3 === "::1";
|
|
38696
39275
|
}
|
|
@@ -38837,6 +39416,11 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
38837
39416
|
return json({ error: "Invalid 'args' format" }, 400, port);
|
|
38838
39417
|
}
|
|
38839
39418
|
const args = body.args || [];
|
|
39419
|
+
try {
|
|
39420
|
+
assertLocalCommandConsent({ command, args, env: {}, transport, operation: "register" }, consentFromInput(body));
|
|
39421
|
+
} catch (err) {
|
|
39422
|
+
return json({ error: err.message }, 400, port);
|
|
39423
|
+
}
|
|
38840
39424
|
const entry = addServer({
|
|
38841
39425
|
name: body.name,
|
|
38842
39426
|
command,
|
|
@@ -38935,6 +39519,19 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
38935
39519
|
}
|
|
38936
39520
|
fields.args = body.args;
|
|
38937
39521
|
}
|
|
39522
|
+
if (fields.command !== undefined || fields.args !== undefined || fields.transport !== undefined) {
|
|
39523
|
+
try {
|
|
39524
|
+
assertLocalCommandConsent({
|
|
39525
|
+
command: fields.command ?? existing.command,
|
|
39526
|
+
args: fields.args ?? existing.args,
|
|
39527
|
+
env: existing.env,
|
|
39528
|
+
transport: fields.transport ?? existing.transport,
|
|
39529
|
+
operation: "register"
|
|
39530
|
+
}, consentFromInput(body));
|
|
39531
|
+
} catch (err) {
|
|
39532
|
+
return json({ error: err.message }, 400, port);
|
|
39533
|
+
}
|
|
39534
|
+
}
|
|
38938
39535
|
const updated = updateServer(id, fields);
|
|
38939
39536
|
return json(redactServer(updated), 200, port);
|
|
38940
39537
|
} catch (e) {
|
|
@@ -39010,7 +39607,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39010
39607
|
}
|
|
39011
39608
|
if (!body.tool || typeof body.tool !== "string")
|
|
39012
39609
|
return json({ error: "Missing 'tool'" }, 400, port);
|
|
39013
|
-
await connectToServer(entry);
|
|
39610
|
+
await connectToServer(entry, { localCommandConsent: consentFromInput(body) });
|
|
39014
39611
|
const toolName = `${id}__${body.tool}`;
|
|
39015
39612
|
const result = await callTool(toolName, body.args || {});
|
|
39016
39613
|
await disconnectServer(id).catch(() => {
|
|
@@ -39021,6 +39618,9 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39021
39618
|
await disconnectServer(id).catch(() => {
|
|
39022
39619
|
return;
|
|
39023
39620
|
});
|
|
39621
|
+
if (e instanceof LocalCommandConsentError) {
|
|
39622
|
+
return json({ error: e.message }, 400, port);
|
|
39623
|
+
}
|
|
39024
39624
|
return json({ error: e instanceof Error ? e.message : "Failed to call tool" }, 500, port);
|
|
39025
39625
|
}
|
|
39026
39626
|
}
|
|
@@ -39033,7 +39633,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39033
39633
|
if (!entry)
|
|
39034
39634
|
return json({ error: `Server '${id}' not found` }, 404, port);
|
|
39035
39635
|
try {
|
|
39036
|
-
const report = await diagnoseServer(entry);
|
|
39636
|
+
const report = await diagnoseServer(entry, { localCommandConsent: consentFromSearchParams(url2.searchParams) });
|
|
39037
39637
|
return json(report, 200, port);
|
|
39038
39638
|
} catch (e) {
|
|
39039
39639
|
return json({ error: e instanceof Error ? e.message : "Failed to diagnose server" }, 500, port);
|
|
@@ -39688,6 +40288,13 @@ function ServerDetail({ server, onSelectTool, onBack }) {
|
|
|
39688
40288
|
const [tools2, setTools] = useState3([]);
|
|
39689
40289
|
const [loading, setLoading] = useState3(false);
|
|
39690
40290
|
const [error2, setError] = useState3(null);
|
|
40291
|
+
const commandReview = inspectLocalCommand({
|
|
40292
|
+
command: server.command,
|
|
40293
|
+
args: server.args,
|
|
40294
|
+
env: server.env,
|
|
40295
|
+
transport: server.transport,
|
|
40296
|
+
operation: "launch"
|
|
40297
|
+
});
|
|
39691
40298
|
const cachedTools = getCachedTools(server.id);
|
|
39692
40299
|
const cachedKey = cachedTools.map((t) => `${t.name}|${t.description}|${JSON.stringify(t.input_schema)}`).join(";");
|
|
39693
40300
|
useEffect3(() => {
|
|
@@ -39708,7 +40315,9 @@ function ServerDetail({ server, onSelectTool, onBack }) {
|
|
|
39708
40315
|
setLoading(true);
|
|
39709
40316
|
setError(null);
|
|
39710
40317
|
try {
|
|
39711
|
-
const conn = await connectToServer(server
|
|
40318
|
+
const conn = await connectToServer(server, {
|
|
40319
|
+
localCommandConsent: { approved: true, source: "tui" }
|
|
40320
|
+
});
|
|
39712
40321
|
setTools(conn.tools);
|
|
39713
40322
|
} catch (err) {
|
|
39714
40323
|
setError(err.message);
|
|
@@ -39767,6 +40376,21 @@ function ServerDetail({ server, onSelectTool, onBack }) {
|
|
|
39767
40376
|
server.args.join(" ")
|
|
39768
40377
|
]
|
|
39769
40378
|
}, undefined, true, undefined, this),
|
|
40379
|
+
commandReview.requiresConsent && /* @__PURE__ */ jsxDEV2(Box4, {
|
|
40380
|
+
marginTop: 1,
|
|
40381
|
+
flexDirection: "column",
|
|
40382
|
+
children: [
|
|
40383
|
+
/* @__PURE__ */ jsxDEV2(Text5, {
|
|
40384
|
+
color: commandReview.hasDangerousRisk ? "red" : "yellow",
|
|
40385
|
+
children: "Local stdio command review"
|
|
40386
|
+
}, undefined, false, undefined, this),
|
|
40387
|
+
formatLocalCommandReview(commandReview).split(`
|
|
40388
|
+
`).map((line, index) => /* @__PURE__ */ jsxDEV2(Text5, {
|
|
40389
|
+
dimColor: true,
|
|
40390
|
+
children: line
|
|
40391
|
+
}, `${index}:${line}`, false, undefined, this))
|
|
40392
|
+
]
|
|
40393
|
+
}, undefined, true, undefined, this),
|
|
39770
40394
|
server.description && /* @__PURE__ */ jsxDEV2(Text5, {
|
|
39771
40395
|
dimColor: true,
|
|
39772
40396
|
children: server.description
|
|
@@ -39935,7 +40559,9 @@ function SearchView({ onBack }) {
|
|
|
39935
40559
|
setInstalling(item.value);
|
|
39936
40560
|
setMessage(null);
|
|
39937
40561
|
try {
|
|
39938
|
-
const server = await installFromRegistry(item.value
|
|
40562
|
+
const server = await installFromRegistry(item.value, {
|
|
40563
|
+
localCommandConsent: { approved: true, source: "tui" }
|
|
40564
|
+
});
|
|
39939
40565
|
setMessage(`Installed: ${server.name} [${server.id}]`);
|
|
39940
40566
|
} catch (err) {
|
|
39941
40567
|
setMessage(`Install failed: ${err.message}`);
|
|
@@ -40046,7 +40672,9 @@ function ToolCall({ server, tool, onBack }) {
|
|
|
40046
40672
|
setError(null);
|
|
40047
40673
|
setResult(null);
|
|
40048
40674
|
try {
|
|
40049
|
-
await connectToServer(server
|
|
40675
|
+
await connectToServer(server, {
|
|
40676
|
+
localCommandConsent: { approved: true, source: "tui" }
|
|
40677
|
+
});
|
|
40050
40678
|
const prefixed = `${server.id}${TOOL_PREFIX_SEPARATOR}${tool.name}`;
|
|
40051
40679
|
const res = await callTool(prefixed, args);
|
|
40052
40680
|
const text = res.content.map((c) => c.text).join(`
|
|
@@ -40266,6 +40894,46 @@ var MACHINE_PLATFORMS = ["linux", "darwin", "unknown"];
|
|
|
40266
40894
|
var MACHINE_ARCHES = ["arm64", "x64", "unknown"];
|
|
40267
40895
|
var MACHINE_INSTALLERS = ["auto", "bun", "npm"];
|
|
40268
40896
|
var FLEET_INSTALL_MODES = ["missing", "missing-or-outdated", "all"];
|
|
40897
|
+
function localConsentFromOptions(opts, approved = false) {
|
|
40898
|
+
return {
|
|
40899
|
+
approved: approved || opts.yes === true || opts.allowLocalStdio === true,
|
|
40900
|
+
allowRisky: opts.allowRiskyCommand === true,
|
|
40901
|
+
source: "cli"
|
|
40902
|
+
};
|
|
40903
|
+
}
|
|
40904
|
+
function printLocalCommandReviewIfNeeded(input) {
|
|
40905
|
+
const review = inspectLocalCommand(input);
|
|
40906
|
+
if (!review.requiresConsent)
|
|
40907
|
+
return;
|
|
40908
|
+
console.log(chalk2.dim(formatLocalCommandReview(review)));
|
|
40909
|
+
}
|
|
40910
|
+
function assertCliLocalCommandConsent(input, opts, approved = false) {
|
|
40911
|
+
const consent = localConsentFromOptions(opts, approved);
|
|
40912
|
+
const review = inspectLocalCommand(input);
|
|
40913
|
+
if (review.requiresConsent && consent.approved === true && (!review.hasDangerousRisk || consent.allowRisky === true)) {
|
|
40914
|
+
console.log(chalk2.dim(formatLocalCommandReview(review)));
|
|
40915
|
+
}
|
|
40916
|
+
assertLocalCommandConsent(input, consent);
|
|
40917
|
+
return consent;
|
|
40918
|
+
}
|
|
40919
|
+
function printProviderProfile(profile) {
|
|
40920
|
+
const status = profile.enabled ? chalk2.green("enabled") : chalk2.red("disabled");
|
|
40921
|
+
console.log(` ${chalk2.bold(profile.displayName)} ${chalk2.dim(`[${profile.id}]`)} \u2014 ${chalk2.dim(profile.transport)} \u2014 ${status}`);
|
|
40922
|
+
if (profile.description)
|
|
40923
|
+
console.log(` ${chalk2.dim(profile.description)}`);
|
|
40924
|
+
if (profile.endpoint)
|
|
40925
|
+
console.log(` ${chalk2.cyan(profile.endpoint)}`);
|
|
40926
|
+
if (profile.authMetadata.bearerToken === "optional") {
|
|
40927
|
+
console.log(` ${chalk2.dim("Auth: OAuth with optional bearer token/API key support")}`);
|
|
40928
|
+
} else if (profile.authMetadata.pkce || profile.authMetadata.dynamicClientRegistration) {
|
|
40929
|
+
const parts = [
|
|
40930
|
+
profile.authMetadata.oauthVersion ? `OAuth ${profile.authMetadata.oauthVersion}` : "OAuth",
|
|
40931
|
+
profile.authMetadata.pkce ? "PKCE" : null,
|
|
40932
|
+
profile.authMetadata.dynamicClientRegistration ? "dynamic client registration" : null
|
|
40933
|
+
].filter(Boolean);
|
|
40934
|
+
console.log(` ${chalk2.dim(`Auth: ${parts.join(", ")}`)}`);
|
|
40935
|
+
}
|
|
40936
|
+
}
|
|
40269
40937
|
function printJson(value) {
|
|
40270
40938
|
console.log(JSON.stringify(value, null, 2));
|
|
40271
40939
|
}
|
|
@@ -40438,6 +41106,86 @@ ${results.length} result(s). Use \`mcps add --from-registry <id>\` to install.`)
|
|
|
40438
41106
|
closeDb();
|
|
40439
41107
|
}
|
|
40440
41108
|
});
|
|
41109
|
+
var providersCmd = program2.command("providers").description("Discover and install curated MCP provider profiles");
|
|
41110
|
+
providersCmd.command("list").description("List curated provider profiles").option("--json", "Output as JSON").option("--enabled-only", "Only include enabled profiles").action((opts) => {
|
|
41111
|
+
const profiles = listProviderProfiles({ enabledOnly: opts.enabledOnly === true });
|
|
41112
|
+
if (opts.json) {
|
|
41113
|
+
printJson(profiles);
|
|
41114
|
+
closeDb();
|
|
41115
|
+
return;
|
|
41116
|
+
}
|
|
41117
|
+
if (profiles.length === 0) {
|
|
41118
|
+
console.log(chalk2.dim("No curated provider profiles available."));
|
|
41119
|
+
closeDb();
|
|
41120
|
+
return;
|
|
41121
|
+
}
|
|
41122
|
+
for (const profile of profiles)
|
|
41123
|
+
printProviderProfile(profile);
|
|
41124
|
+
closeDb();
|
|
41125
|
+
});
|
|
41126
|
+
providersCmd.command("search").argument("<query>", "Search query").description("Search curated provider profiles").option("--json", "Output as JSON").option("--enabled-only", "Only include enabled profiles").action((query, opts) => {
|
|
41127
|
+
const profiles = searchProviderProfiles(query, { enabledOnly: opts.enabledOnly === true });
|
|
41128
|
+
if (opts.json) {
|
|
41129
|
+
printJson(profiles);
|
|
41130
|
+
closeDb();
|
|
41131
|
+
return;
|
|
41132
|
+
}
|
|
41133
|
+
if (profiles.length === 0) {
|
|
41134
|
+
console.log(chalk2.dim("No curated provider profiles found."));
|
|
41135
|
+
closeDb();
|
|
41136
|
+
return;
|
|
41137
|
+
}
|
|
41138
|
+
for (const profile of profiles)
|
|
41139
|
+
printProviderProfile(profile);
|
|
41140
|
+
console.log(chalk2.dim(`
|
|
41141
|
+
${profiles.length} provider profile(s). Use \`mcps providers install <id>\` to register one.`));
|
|
41142
|
+
closeDb();
|
|
41143
|
+
});
|
|
41144
|
+
providersCmd.command("info").argument("<id>", "Provider profile ID").description("Show a curated provider profile").option("--json", "Output as JSON").action((id, opts) => {
|
|
41145
|
+
const profile = getProviderProfile(id);
|
|
41146
|
+
if (!profile) {
|
|
41147
|
+
console.error(chalk2.red(`Provider profile "${id}" not found.`));
|
|
41148
|
+
closeDb();
|
|
41149
|
+
process.exit(1);
|
|
41150
|
+
}
|
|
41151
|
+
if (opts.json) {
|
|
41152
|
+
printJson(profile);
|
|
41153
|
+
closeDb();
|
|
41154
|
+
return;
|
|
41155
|
+
}
|
|
41156
|
+
printProviderProfile(profile);
|
|
41157
|
+
if (profile.fallbackEndpoints.length > 0) {
|
|
41158
|
+
console.log(chalk2.bold(" Fallback endpoints:"));
|
|
41159
|
+
for (const fallback of profile.fallbackEndpoints) {
|
|
41160
|
+
console.log(` ${fallback.transport}: ${chalk2.cyan(fallback.url)}`);
|
|
41161
|
+
}
|
|
41162
|
+
}
|
|
41163
|
+
if (profile.docsUrl)
|
|
41164
|
+
console.log(` Docs: ${chalk2.cyan(profile.docsUrl)}`);
|
|
41165
|
+
closeDb();
|
|
41166
|
+
});
|
|
41167
|
+
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) => {
|
|
41168
|
+
try {
|
|
41169
|
+
const server = installProviderProfile(id, {
|
|
41170
|
+
name: opts.name,
|
|
41171
|
+
useFallback: opts.fallback === true,
|
|
41172
|
+
localCommandConsent: localConsentFromOptions(opts)
|
|
41173
|
+
});
|
|
41174
|
+
if (opts.json) {
|
|
41175
|
+
printJson(server);
|
|
41176
|
+
} else {
|
|
41177
|
+
console.log(chalk2.green(`Installed provider profile: ${server.name} [${server.id}]`));
|
|
41178
|
+
console.log(chalk2.dim(` Transport: ${server.transport}`));
|
|
41179
|
+
if (server.url)
|
|
41180
|
+
console.log(chalk2.dim(` URL: ${server.url}`));
|
|
41181
|
+
}
|
|
41182
|
+
closeDb();
|
|
41183
|
+
} catch (err) {
|
|
41184
|
+
console.error(chalk2.red(`Failed to install provider profile: ${err.message}`));
|
|
41185
|
+
closeDb();
|
|
41186
|
+
process.exit(1);
|
|
41187
|
+
}
|
|
41188
|
+
});
|
|
40441
41189
|
async function promptReadline(rl, question) {
|
|
40442
41190
|
return new Promise((resolve2) => rl.question(question, resolve2));
|
|
40443
41191
|
}
|
|
@@ -40452,11 +41200,13 @@ function detectSourceType(url2) {
|
|
|
40452
41200
|
return "mcp-registry";
|
|
40453
41201
|
return null;
|
|
40454
41202
|
}
|
|
40455
|
-
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) => {
|
|
41203
|
+
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").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) => {
|
|
40456
41204
|
try {
|
|
40457
41205
|
if (opts.fromRegistry) {
|
|
40458
41206
|
console.log(chalk2.dim(`Installing "${opts.fromRegistry}" from registry...`));
|
|
40459
|
-
const server2 = await installFromRegistry(opts.fromRegistry
|
|
41207
|
+
const server2 = await installFromRegistry(opts.fromRegistry, {
|
|
41208
|
+
localCommandConsent: localConsentFromOptions(opts)
|
|
41209
|
+
});
|
|
40460
41210
|
console.log(chalk2.green(`Added server: ${server2.name} [${server2.id}]`));
|
|
40461
41211
|
console.log(chalk2.dim(` ${server2.command} ${server2.args.join(" ")}`));
|
|
40462
41212
|
closeDb();
|
|
@@ -40495,6 +41245,13 @@ Server to add:`));
|
|
|
40495
41245
|
console.log(` Name: ${wizardName}`);
|
|
40496
41246
|
if (Object.keys(env).length)
|
|
40497
41247
|
console.log(` Env: ${Object.keys(env).join(", ")}`);
|
|
41248
|
+
printLocalCommandReviewIfNeeded({
|
|
41249
|
+
command: wizardCommand,
|
|
41250
|
+
args: wizardArgs,
|
|
41251
|
+
env,
|
|
41252
|
+
transport,
|
|
41253
|
+
operation: "register"
|
|
41254
|
+
});
|
|
40498
41255
|
const confirm = await new Promise((resolve2) => {
|
|
40499
41256
|
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
40500
41257
|
rl2.question(chalk2.bold("Add this server? [Y/n]: "), (ans) => {
|
|
@@ -40507,6 +41264,13 @@ Server to add:`));
|
|
|
40507
41264
|
closeDb();
|
|
40508
41265
|
return;
|
|
40509
41266
|
}
|
|
41267
|
+
assertLocalCommandConsent({
|
|
41268
|
+
command: wizardCommand,
|
|
41269
|
+
args: wizardArgs,
|
|
41270
|
+
env,
|
|
41271
|
+
transport,
|
|
41272
|
+
operation: "register"
|
|
41273
|
+
}, localConsentFromOptions(opts, true));
|
|
40510
41274
|
const server2 = addServer({
|
|
40511
41275
|
command: wizardCommand,
|
|
40512
41276
|
args: wizardArgs,
|
|
@@ -40543,6 +41307,13 @@ Server to add:`));
|
|
|
40543
41307
|
envMap[key] = rest.join("=");
|
|
40544
41308
|
}
|
|
40545
41309
|
}
|
|
41310
|
+
assertCliLocalCommandConsent({
|
|
41311
|
+
command,
|
|
41312
|
+
args,
|
|
41313
|
+
env: envMap,
|
|
41314
|
+
transport: opts.transport,
|
|
41315
|
+
operation: "register"
|
|
41316
|
+
}, opts);
|
|
40546
41317
|
const server = addServer({
|
|
40547
41318
|
name: opts.name,
|
|
40548
41319
|
description: opts.description,
|
|
@@ -40565,29 +41336,44 @@ Server to add:`));
|
|
|
40565
41336
|
}
|
|
40566
41337
|
closeDb();
|
|
40567
41338
|
});
|
|
40568
|
-
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) => {
|
|
40569
|
-
|
|
40570
|
-
|
|
40571
|
-
|
|
41339
|
+
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) => {
|
|
41340
|
+
try {
|
|
41341
|
+
const server = getServer(id);
|
|
41342
|
+
if (!server) {
|
|
41343
|
+
console.error(chalk2.red(`Server "${id}" not found.`));
|
|
41344
|
+
closeDb();
|
|
41345
|
+
process.exit(1);
|
|
41346
|
+
}
|
|
41347
|
+
const fields = {};
|
|
41348
|
+
if (opts.name !== undefined)
|
|
41349
|
+
fields.name = opts.name;
|
|
41350
|
+
if (opts.description !== undefined)
|
|
41351
|
+
fields.description = opts.description;
|
|
41352
|
+
if (opts.command !== undefined)
|
|
41353
|
+
fields.command = opts.command;
|
|
41354
|
+
if (opts.args !== undefined)
|
|
41355
|
+
fields.args = opts.args;
|
|
41356
|
+
if (opts.transport !== undefined)
|
|
41357
|
+
fields.transport = opts.transport;
|
|
41358
|
+
if (opts.url !== undefined)
|
|
41359
|
+
fields.url = opts.url;
|
|
41360
|
+
if (fields.command !== undefined || fields.args !== undefined || fields.transport !== undefined) {
|
|
41361
|
+
assertCliLocalCommandConsent({
|
|
41362
|
+
command: fields.command ?? server.command,
|
|
41363
|
+
args: fields.args ?? server.args,
|
|
41364
|
+
env: server.env,
|
|
41365
|
+
transport: fields.transport ?? server.transport,
|
|
41366
|
+
operation: "register"
|
|
41367
|
+
}, opts);
|
|
41368
|
+
}
|
|
41369
|
+
const updated = updateServer(id, fields);
|
|
41370
|
+
console.log(chalk2.green(`Updated server: ${updated.name} [${updated.id}]`));
|
|
41371
|
+
closeDb();
|
|
41372
|
+
} catch (err) {
|
|
41373
|
+
console.error(chalk2.red(`Failed to update server: ${err.message}`));
|
|
40572
41374
|
closeDb();
|
|
40573
41375
|
process.exit(1);
|
|
40574
41376
|
}
|
|
40575
|
-
const fields = {};
|
|
40576
|
-
if (opts.name !== undefined)
|
|
40577
|
-
fields.name = opts.name;
|
|
40578
|
-
if (opts.description !== undefined)
|
|
40579
|
-
fields.description = opts.description;
|
|
40580
|
-
if (opts.command !== undefined)
|
|
40581
|
-
fields.command = opts.command;
|
|
40582
|
-
if (opts.args !== undefined)
|
|
40583
|
-
fields.args = opts.args;
|
|
40584
|
-
if (opts.transport !== undefined)
|
|
40585
|
-
fields.transport = opts.transport;
|
|
40586
|
-
if (opts.url !== undefined)
|
|
40587
|
-
fields.url = opts.url;
|
|
40588
|
-
const updated = updateServer(id, fields);
|
|
40589
|
-
console.log(chalk2.green(`Updated server: ${updated.name} [${updated.id}]`));
|
|
40590
|
-
closeDb();
|
|
40591
41377
|
});
|
|
40592
41378
|
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) => {
|
|
40593
41379
|
try {
|
|
@@ -40634,10 +41420,10 @@ program2.command("disable").argument("<id>", "Server ID to disable").description
|
|
|
40634
41420
|
console.log(chalk2.yellow(`Disabled server: ${server.name}`));
|
|
40635
41421
|
closeDb();
|
|
40636
41422
|
});
|
|
40637
|
-
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) => {
|
|
41423
|
+
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) => {
|
|
40638
41424
|
if (opts.connect) {
|
|
40639
41425
|
console.log(chalk2.dim("Connecting to enabled servers..."));
|
|
40640
|
-
await connectAllEnabled();
|
|
41426
|
+
await connectAllEnabled({ localCommandConsent: localConsentFromOptions(opts) });
|
|
40641
41427
|
const tools2 = listAllTools();
|
|
40642
41428
|
if (tools2.length === 0) {
|
|
40643
41429
|
console.log(chalk2.dim("No tools available."));
|
|
@@ -40687,7 +41473,7 @@ ${total} tool(s) total.`));
|
|
|
40687
41473
|
}
|
|
40688
41474
|
closeDb();
|
|
40689
41475
|
});
|
|
40690
|
-
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) => {
|
|
41476
|
+
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) => {
|
|
40691
41477
|
let args = {};
|
|
40692
41478
|
if (opts.json) {
|
|
40693
41479
|
try {
|
|
@@ -40710,7 +41496,7 @@ program2.command("call").argument("<tool>", "Tool name (server_id__tool_name)").
|
|
|
40710
41496
|
let exitCode = 0;
|
|
40711
41497
|
try {
|
|
40712
41498
|
console.log(chalk2.dim(`Connecting to servers...`));
|
|
40713
|
-
await connectAllEnabled();
|
|
41499
|
+
await connectAllEnabled({ localCommandConsent: localConsentFromOptions(opts) });
|
|
40714
41500
|
console.log(chalk2.dim(`Calling ${tool}...`));
|
|
40715
41501
|
const result = await callTool(tool, args);
|
|
40716
41502
|
for (const c of result.content) {
|
|
@@ -40778,7 +41564,7 @@ program2.command("status").description("Show registry stats").option("--json", "
|
|
|
40778
41564
|
console.log(` Tools: ${totalTools} (cached)`);
|
|
40779
41565
|
closeDb();
|
|
40780
41566
|
});
|
|
40781
|
-
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) => {
|
|
41567
|
+
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) => {
|
|
40782
41568
|
const { execFileSync: execFileSync22 } = await import("child_process");
|
|
40783
41569
|
const servers = id ? [getServer(id)].filter(Boolean) : listServers();
|
|
40784
41570
|
if (servers.length === 0) {
|
|
@@ -40790,7 +41576,7 @@ program2.command("doctor").argument("[id]", "Server ID to check (omit to check a
|
|
|
40790
41576
|
for (const server of servers) {
|
|
40791
41577
|
console.log(chalk2.bold(`
|
|
40792
41578
|
${server.name} [${server.id}]`));
|
|
40793
|
-
const report = await diagnoseServer(server);
|
|
41579
|
+
const report = await diagnoseServer(server, { localCommandConsent: localConsentFromOptions(opts) });
|
|
40794
41580
|
for (const check2 of report.checks) {
|
|
40795
41581
|
const icon = check2.pass ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
40796
41582
|
console.log(` ${icon} ${check2.name}: ${chalk2.dim(check2.message)}`);
|
|
@@ -40816,7 +41602,7 @@ ${server.name} [${server.id}]`));
|
|
|
40816
41602
|
closeDb();
|
|
40817
41603
|
});
|
|
40818
41604
|
program2.command("completion").argument("<shell>", "Shell type: bash, zsh, fish").description("Generate shell completion script").action((shell) => {
|
|
40819
|
-
const commands = ["list", "search", "find", "add", "remove", "enable", "disable", "info", "status", "tools", "call", "doctor", "install", "machines", "fleet", "export", "import", "env", "sources", "clone", "update-server", "serve", "update", "mcp", "completion"];
|
|
41605
|
+
const commands = ["list", "search", "providers", "find", "add", "remove", "enable", "disable", "info", "status", "tools", "call", "doctor", "install", "machines", "fleet", "export", "import", "env", "sources", "clone", "update-server", "serve", "update", "mcp", "completion"];
|
|
40820
41606
|
if (shell === "bash") {
|
|
40821
41607
|
console.log(`# Add to ~/.bashrc: eval "$(mcps completion bash)"
|
|
40822
41608
|
_mcps_complete() {
|
|
@@ -40876,7 +41662,7 @@ program2.command("update").description("Update mcps to the latest version").acti
|
|
|
40876
41662
|
process.exit(1);
|
|
40877
41663
|
}
|
|
40878
41664
|
});
|
|
40879
|
-
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) => {
|
|
41665
|
+
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) => {
|
|
40880
41666
|
try {
|
|
40881
41667
|
if (opts.awesome) {
|
|
40882
41668
|
console.log(chalk2.dim("Fetching curated awesome-mcp-servers list..."));
|
|
@@ -40986,6 +41772,14 @@ Enter number to install (1-${results.length}), or 0 to cancel: `), resolve2);
|
|
|
40986
41772
|
return;
|
|
40987
41773
|
}
|
|
40988
41774
|
console.log(chalk2.dim(`Installing ${chosen.name}...`));
|
|
41775
|
+
const localCommandInput = {
|
|
41776
|
+
command: "npx",
|
|
41777
|
+
args: ["-y", pkg],
|
|
41778
|
+
env: {},
|
|
41779
|
+
transport: "stdio",
|
|
41780
|
+
operation: "install"
|
|
41781
|
+
};
|
|
41782
|
+
const localCommandConsent = assertCliLocalCommandConsent(localCommandInput, opts, true);
|
|
40989
41783
|
const server = addServer({
|
|
40990
41784
|
command: "npx",
|
|
40991
41785
|
args: ["-y", pkg],
|
|
@@ -40993,7 +41787,7 @@ Enter number to install (1-${results.length}), or 0 to cancel: `), resolve2);
|
|
|
40993
41787
|
description: chosen.description,
|
|
40994
41788
|
transport: "stdio"
|
|
40995
41789
|
});
|
|
40996
|
-
const results2 = installToAgents(server, ["claude", "codex", "gemini"]);
|
|
41790
|
+
const results2 = installToAgents(server, ["claude", "codex", "gemini"], { localCommandConsent });
|
|
40997
41791
|
for (const r of results2) {
|
|
40998
41792
|
if (r.success) {
|
|
40999
41793
|
console.log(chalk2.green(` \u2713 ${r.agent}`));
|
|
@@ -41138,7 +41932,7 @@ sourcesCmd.command("test").argument("<id>", "Source ID to test").description("Te
|
|
|
41138
41932
|
}
|
|
41139
41933
|
closeDb();
|
|
41140
41934
|
});
|
|
41141
|
-
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) => {
|
|
41935
|
+
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) => {
|
|
41142
41936
|
const targets = [];
|
|
41143
41937
|
if (opts.to) {
|
|
41144
41938
|
for (const t of opts.to) {
|
|
@@ -41164,7 +41958,9 @@ program2.command("install").argument("[id]", "Server ID (from `mcps list`) to in
|
|
|
41164
41958
|
if (opts.fromRegistry) {
|
|
41165
41959
|
console.log(chalk2.dim(`Installing "${opts.fromRegistry}" from registry...`));
|
|
41166
41960
|
try {
|
|
41167
|
-
server = await installFromRegistry(opts.fromRegistry
|
|
41961
|
+
server = await installFromRegistry(opts.fromRegistry, {
|
|
41962
|
+
localCommandConsent: localConsentFromOptions(opts)
|
|
41963
|
+
});
|
|
41168
41964
|
console.log(chalk2.green(`Added server: ${server.name} [${server.id}]`));
|
|
41169
41965
|
} catch (err) {
|
|
41170
41966
|
console.error(chalk2.red(`Failed to install from registry: ${err.message}`));
|
|
@@ -41173,6 +41969,13 @@ program2.command("install").argument("[id]", "Server ID (from `mcps list`) to in
|
|
|
41173
41969
|
}
|
|
41174
41970
|
} else if (opts.npm) {
|
|
41175
41971
|
const pkg = opts.npm;
|
|
41972
|
+
assertCliLocalCommandConsent({
|
|
41973
|
+
command: "npx",
|
|
41974
|
+
args: ["-y", pkg],
|
|
41975
|
+
env: {},
|
|
41976
|
+
transport: "stdio",
|
|
41977
|
+
operation: "install"
|
|
41978
|
+
}, opts);
|
|
41176
41979
|
server = addServer({ command: "npx", args: ["-y", pkg], name: pkg, transport: "stdio" });
|
|
41177
41980
|
console.log(chalk2.green(`Added server: ${server.name} [${server.id}]`));
|
|
41178
41981
|
} else {
|
|
@@ -41189,7 +41992,7 @@ program2.command("install").argument("[id]", "Server ID (from `mcps list`) to in
|
|
|
41189
41992
|
}
|
|
41190
41993
|
}
|
|
41191
41994
|
console.log(chalk2.dim(`Installing "${server.name}" to: ${targets.join(", ")}...`));
|
|
41192
|
-
const results = installToAgents(server, targets);
|
|
41995
|
+
const results = installToAgents(server, targets, { localCommandConsent: localConsentFromOptions(opts) });
|
|
41193
41996
|
for (const r of results) {
|
|
41194
41997
|
if (r.success) {
|
|
41195
41998
|
console.log(chalk2.green(` \u2713 ${r.agent}`));
|