@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/dist/mcp/index.js
CHANGED
|
@@ -16577,6 +16577,85 @@ var init_config2 = __esm(() => {
|
|
|
16577
16577
|
DB_PATH = process.env.HASNA_MCPS_DB_PATH ?? process.env.MCPS_DB_PATH ?? join7(MCPS_DIR, "registry.db");
|
|
16578
16578
|
});
|
|
16579
16579
|
|
|
16580
|
+
// src/lib/provider-profile-seeds.ts
|
|
16581
|
+
var DEFAULT_PROVIDER_PROFILE_SEEDS;
|
|
16582
|
+
var init_provider_profile_seeds = __esm(() => {
|
|
16583
|
+
DEFAULT_PROVIDER_PROFILE_SEEDS = [
|
|
16584
|
+
{
|
|
16585
|
+
id: "notion",
|
|
16586
|
+
displayName: "Notion",
|
|
16587
|
+
description: "Connect a Notion workspace so agents can search, read, create, and update workspace content.",
|
|
16588
|
+
endpoint: "https://mcp.notion.com/mcp",
|
|
16589
|
+
transport: "streamable-http",
|
|
16590
|
+
fallbackEndpoints: [
|
|
16591
|
+
{
|
|
16592
|
+
transport: "sse",
|
|
16593
|
+
url: "https://mcp.notion.com/sse",
|
|
16594
|
+
notes: "Fallback for clients that do not support Streamable HTTP."
|
|
16595
|
+
}
|
|
16596
|
+
],
|
|
16597
|
+
authType: "oauth2",
|
|
16598
|
+
authMetadata: {
|
|
16599
|
+
oauthVersion: "2.0",
|
|
16600
|
+
pkce: true,
|
|
16601
|
+
dynamicClientRegistration: true,
|
|
16602
|
+
bearerToken: "none",
|
|
16603
|
+
notes: "Remote Notion MCP uses OAuth with PKCE. Bearer-token authentication is only appropriate for self-hosted/local fallback deployments."
|
|
16604
|
+
},
|
|
16605
|
+
tokenMode: "workspace",
|
|
16606
|
+
installFallback: {
|
|
16607
|
+
command: "npx",
|
|
16608
|
+
args: ["-y", "mcp-remote", "https://mcp.notion.com/sse", "--transport", "sse-only"],
|
|
16609
|
+
packageName: "mcp-remote",
|
|
16610
|
+
url: "https://mcp.notion.com/sse"
|
|
16611
|
+
},
|
|
16612
|
+
docsUrl: "https://developers.notion.com/guides/mcp/build-mcp-client",
|
|
16613
|
+
safety: {
|
|
16614
|
+
requiresApproval: true,
|
|
16615
|
+
dataClasses: ["workspace_content", "pages", "databases", "comments"],
|
|
16616
|
+
notes: "Connected agents operate with the authorizing user's workspace access. Human confirmation is recommended for write-capable workflows."
|
|
16617
|
+
},
|
|
16618
|
+
provenance: {
|
|
16619
|
+
source: "curated",
|
|
16620
|
+
sourceUrl: "https://developers.notion.com/guides/mcp/build-mcp-client",
|
|
16621
|
+
verifiedAt: "2026-05-10"
|
|
16622
|
+
}
|
|
16623
|
+
},
|
|
16624
|
+
{
|
|
16625
|
+
id: "linear",
|
|
16626
|
+
displayName: "Linear",
|
|
16627
|
+
description: "Connect Linear so agents can find, create, and update issues, projects, comments, and related workspace objects.",
|
|
16628
|
+
endpoint: "https://mcp.linear.app/mcp",
|
|
16629
|
+
transport: "streamable-http",
|
|
16630
|
+
authType: "oauth2",
|
|
16631
|
+
authMetadata: {
|
|
16632
|
+
oauthVersion: "2.1",
|
|
16633
|
+
dynamicClientRegistration: true,
|
|
16634
|
+
bearerToken: "optional",
|
|
16635
|
+
notes: "Linear supports interactive OAuth 2.1 with dynamic client registration and optional Authorization: Bearer tokens for OAuth tokens or API keys."
|
|
16636
|
+
},
|
|
16637
|
+
tokenMode: "workspace",
|
|
16638
|
+
installFallback: {
|
|
16639
|
+
command: "npx",
|
|
16640
|
+
args: ["-y", "mcp-remote", "https://mcp.linear.app/mcp"],
|
|
16641
|
+
packageName: "mcp-remote",
|
|
16642
|
+
url: "https://mcp.linear.app/mcp"
|
|
16643
|
+
},
|
|
16644
|
+
docsUrl: "https://linear.app/docs/mcp",
|
|
16645
|
+
safety: {
|
|
16646
|
+
requiresApproval: true,
|
|
16647
|
+
dataClasses: ["issues", "projects", "comments", "teams", "users"],
|
|
16648
|
+
notes: "Linear tools can create and update workspace objects, so write actions should be policy-gated by the platform."
|
|
16649
|
+
},
|
|
16650
|
+
provenance: {
|
|
16651
|
+
source: "curated",
|
|
16652
|
+
sourceUrl: "https://linear.app/docs/mcp",
|
|
16653
|
+
verifiedAt: "2026-05-10"
|
|
16654
|
+
}
|
|
16655
|
+
}
|
|
16656
|
+
];
|
|
16657
|
+
});
|
|
16658
|
+
|
|
16580
16659
|
// src/lib/db.ts
|
|
16581
16660
|
import { mkdirSync as mkdirSync5 } from "fs";
|
|
16582
16661
|
function getDb() {
|
|
@@ -16662,6 +16741,50 @@ function getDb() {
|
|
|
16662
16741
|
('github-mcp-topic', 'GitHub MCP Topic', 'github-topic', 'https://api.github.com/search/repositories', 'GitHub repositories tagged with mcp-server topic')
|
|
16663
16742
|
`);
|
|
16664
16743
|
}
|
|
16744
|
+
db.exec(`
|
|
16745
|
+
CREATE TABLE IF NOT EXISTS provider_profiles (
|
|
16746
|
+
id TEXT PRIMARY KEY,
|
|
16747
|
+
display_name TEXT NOT NULL,
|
|
16748
|
+
description TEXT,
|
|
16749
|
+
endpoint TEXT,
|
|
16750
|
+
transport TEXT NOT NULL,
|
|
16751
|
+
fallback_endpoints TEXT NOT NULL DEFAULT '[]',
|
|
16752
|
+
auth_type TEXT NOT NULL,
|
|
16753
|
+
auth_metadata TEXT NOT NULL DEFAULT '{}',
|
|
16754
|
+
scopes TEXT NOT NULL DEFAULT '[]',
|
|
16755
|
+
token_mode TEXT NOT NULL DEFAULT 'none',
|
|
16756
|
+
install_fallback TEXT NOT NULL DEFAULT '{}',
|
|
16757
|
+
docs_url TEXT,
|
|
16758
|
+
safety TEXT NOT NULL DEFAULT '{}',
|
|
16759
|
+
provenance TEXT NOT NULL DEFAULT '{"source":"manual"}',
|
|
16760
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
16761
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
16762
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
16763
|
+
)
|
|
16764
|
+
`);
|
|
16765
|
+
try {
|
|
16766
|
+
db.exec("ALTER TABLE provider_profiles ADD COLUMN fallback_endpoints TEXT NOT NULL DEFAULT '[]'");
|
|
16767
|
+
} catch {}
|
|
16768
|
+
try {
|
|
16769
|
+
db.exec("ALTER TABLE provider_profiles ADD COLUMN auth_metadata TEXT NOT NULL DEFAULT '{}'");
|
|
16770
|
+
} catch {}
|
|
16771
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_provider_profiles_enabled ON provider_profiles(enabled)");
|
|
16772
|
+
const providerProfileCount = db.query("SELECT COUNT(*) as c FROM provider_profiles").get().c;
|
|
16773
|
+
if (providerProfileCount === 0) {
|
|
16774
|
+
const insertProviderProfile = db.prepare(`
|
|
16775
|
+
INSERT OR IGNORE INTO provider_profiles (
|
|
16776
|
+
id, display_name, description, endpoint, transport, fallback_endpoints,
|
|
16777
|
+
auth_type, auth_metadata, scopes, token_mode, install_fallback,
|
|
16778
|
+
docs_url, safety, provenance, enabled
|
|
16779
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
16780
|
+
`);
|
|
16781
|
+
const run = db.transaction(() => {
|
|
16782
|
+
for (const profile of DEFAULT_PROVIDER_PROFILE_SEEDS) {
|
|
16783
|
+
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);
|
|
16784
|
+
}
|
|
16785
|
+
});
|
|
16786
|
+
run();
|
|
16787
|
+
}
|
|
16665
16788
|
db.exec(`
|
|
16666
16789
|
CREATE TABLE IF NOT EXISTS feedback (
|
|
16667
16790
|
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
@@ -16685,6 +16808,7 @@ var db = null, _adapter = null;
|
|
|
16685
16808
|
var init_db = __esm(() => {
|
|
16686
16809
|
init_dist();
|
|
16687
16810
|
init_config2();
|
|
16811
|
+
init_provider_profile_seeds();
|
|
16688
16812
|
});
|
|
16689
16813
|
|
|
16690
16814
|
// src/lib/sources.ts
|
|
@@ -30716,6 +30840,185 @@ function getCachedTools(serverId) {
|
|
|
30716
30840
|
|
|
30717
30841
|
// src/lib/remote.ts
|
|
30718
30842
|
init_config2();
|
|
30843
|
+
|
|
30844
|
+
// src/lib/local-command-consent.ts
|
|
30845
|
+
class LocalCommandConsentError extends Error {
|
|
30846
|
+
review;
|
|
30847
|
+
constructor(message, review) {
|
|
30848
|
+
super(message);
|
|
30849
|
+
this.name = "LocalCommandConsentError";
|
|
30850
|
+
this.review = review;
|
|
30851
|
+
}
|
|
30852
|
+
}
|
|
30853
|
+
var SHELL_COMMANDS = new Set(["bash", "sh", "zsh", "fish", "cmd", "cmd.exe", "powershell", "powershell.exe", "pwsh"]);
|
|
30854
|
+
var DESTRUCTIVE_COMMANDS = new Set([
|
|
30855
|
+
"rm",
|
|
30856
|
+
"dd",
|
|
30857
|
+
"mkfs",
|
|
30858
|
+
"shutdown",
|
|
30859
|
+
"reboot",
|
|
30860
|
+
"poweroff",
|
|
30861
|
+
"halt",
|
|
30862
|
+
"killall",
|
|
30863
|
+
"pkill"
|
|
30864
|
+
]);
|
|
30865
|
+
var SHELL_EVAL_FLAGS = new Set(["-c", "/c", "-Command", "-command", "-EncodedCommand", "-encodedcommand"]);
|
|
30866
|
+
var SECRET_FLAG_PATTERN = /(?:^|[-_])(api[-_]?key|token|secret|password|passwd|credential|auth|private[-_]?key)(?:$|[-_])/i;
|
|
30867
|
+
var SECRET_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
|
|
30868
|
+
var SECRET_VALUE_PATTERN = /^(sk_(?:live|test)_[A-Za-z0-9_]+|ghp_[A-Za-z0-9_]+|github_pat_[A-Za-z0-9_]+|xox[baprs]-[A-Za-z0-9-]+|AIza[A-Za-z0-9_-]{20,}|eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)$/;
|
|
30869
|
+
var SHELL_META_PATTERN = /[;&|`<>]|\$\(/;
|
|
30870
|
+
function commandBase(command) {
|
|
30871
|
+
return command.trim().split(/[\\/]/).pop()?.toLowerCase() || command.trim().toLowerCase();
|
|
30872
|
+
}
|
|
30873
|
+
function normalizeArgs(args) {
|
|
30874
|
+
return (args ?? []).map((arg) => String(arg));
|
|
30875
|
+
}
|
|
30876
|
+
function isSecretKey(key) {
|
|
30877
|
+
return SECRET_KEY_PATTERN.test(key) || SECRET_FLAG_PATTERN.test(key);
|
|
30878
|
+
}
|
|
30879
|
+
function isSecretValue(value) {
|
|
30880
|
+
return SECRET_VALUE_PATTERN.test(value.trim());
|
|
30881
|
+
}
|
|
30882
|
+
function isSecretAssignment(arg) {
|
|
30883
|
+
const eqIdx = arg.indexOf("=");
|
|
30884
|
+
if (eqIdx <= 0)
|
|
30885
|
+
return false;
|
|
30886
|
+
const key = arg.slice(0, eqIdx);
|
|
30887
|
+
const value = arg.slice(eqIdx + 1);
|
|
30888
|
+
return isSecretKey(key) || isSecretValue(value);
|
|
30889
|
+
}
|
|
30890
|
+
function isSecretArg(args, index) {
|
|
30891
|
+
const arg = args[index] ?? "";
|
|
30892
|
+
const previous = args[index - 1] ?? "";
|
|
30893
|
+
return isSecretAssignment(arg) || isSecretValue(arg) || SECRET_FLAG_PATTERN.test(previous);
|
|
30894
|
+
}
|
|
30895
|
+
function quoteArg(value) {
|
|
30896
|
+
return JSON.stringify(value);
|
|
30897
|
+
}
|
|
30898
|
+
function displayCommand(command, args) {
|
|
30899
|
+
return [quoteArg(command), ...args.map((arg, index) => quoteArg(isSecretArg(args, index) ? "<redacted>" : arg))].join(" ");
|
|
30900
|
+
}
|
|
30901
|
+
function pushRisk(risks, risk) {
|
|
30902
|
+
if (risks.some((existing) => existing.code === risk.code && existing.evidence === risk.evidence))
|
|
30903
|
+
return;
|
|
30904
|
+
risks.push(risk);
|
|
30905
|
+
}
|
|
30906
|
+
function inspectRisks(command, args, env) {
|
|
30907
|
+
const risks = [];
|
|
30908
|
+
const base = commandBase(command);
|
|
30909
|
+
const joined = [command, ...args].join(" ");
|
|
30910
|
+
if (SHELL_COMMANDS.has(base)) {
|
|
30911
|
+
pushRisk(risks, {
|
|
30912
|
+
code: "shell_interpreter",
|
|
30913
|
+
severity: "warning",
|
|
30914
|
+
message: "Command launches a shell interpreter.",
|
|
30915
|
+
evidence: base
|
|
30916
|
+
});
|
|
30917
|
+
if (args.some((arg) => SHELL_EVAL_FLAGS.has(arg))) {
|
|
30918
|
+
pushRisk(risks, {
|
|
30919
|
+
code: "shell_eval",
|
|
30920
|
+
severity: "danger",
|
|
30921
|
+
message: "Shell command evaluates an inline script.",
|
|
30922
|
+
evidence: base
|
|
30923
|
+
});
|
|
30924
|
+
}
|
|
30925
|
+
}
|
|
30926
|
+
if (base === "sudo") {
|
|
30927
|
+
pushRisk(risks, {
|
|
30928
|
+
code: "privilege_escalation",
|
|
30929
|
+
severity: "danger",
|
|
30930
|
+
message: "Command requests elevated privileges.",
|
|
30931
|
+
evidence: base
|
|
30932
|
+
});
|
|
30933
|
+
}
|
|
30934
|
+
if (DESTRUCTIVE_COMMANDS.has(base) || /\brm\s+-[^\s]*[rf][^\s]*\b/.test(joined) || /--no-preserve-root\b/.test(joined)) {
|
|
30935
|
+
pushRisk(risks, {
|
|
30936
|
+
code: "destructive_command",
|
|
30937
|
+
severity: "danger",
|
|
30938
|
+
message: "Command includes a destructive system operation.",
|
|
30939
|
+
evidence: base
|
|
30940
|
+
});
|
|
30941
|
+
}
|
|
30942
|
+
if (/\b(curl|wget)\b[\s\S]*\|[\s\S]*\b(sh|bash|zsh|fish)\b/.test(joined)) {
|
|
30943
|
+
pushRisk(risks, {
|
|
30944
|
+
code: "download_pipe_shell",
|
|
30945
|
+
severity: "danger",
|
|
30946
|
+
message: "Command downloads remote content and pipes it to a shell."
|
|
30947
|
+
});
|
|
30948
|
+
}
|
|
30949
|
+
if ([command, ...args].some((part) => SHELL_META_PATTERN.test(part))) {
|
|
30950
|
+
pushRisk(risks, {
|
|
30951
|
+
code: "shell_metacharacters",
|
|
30952
|
+
severity: "warning",
|
|
30953
|
+
message: "Command or arguments contain shell metacharacters."
|
|
30954
|
+
});
|
|
30955
|
+
}
|
|
30956
|
+
if (args.some((arg, index) => isSecretArg(args, index))) {
|
|
30957
|
+
pushRisk(risks, {
|
|
30958
|
+
code: "inline_secret",
|
|
30959
|
+
severity: "danger",
|
|
30960
|
+
message: "Command arguments appear to contain inline secret material."
|
|
30961
|
+
});
|
|
30962
|
+
}
|
|
30963
|
+
const secretEnvKeys = Object.keys(env).filter(isSecretKey).sort();
|
|
30964
|
+
if (secretEnvKeys.length > 0) {
|
|
30965
|
+
pushRisk(risks, {
|
|
30966
|
+
code: "secret_env",
|
|
30967
|
+
severity: "warning",
|
|
30968
|
+
message: "Environment contains secret-like keys; values are redacted from consent output.",
|
|
30969
|
+
evidence: secretEnvKeys.join(", ")
|
|
30970
|
+
});
|
|
30971
|
+
}
|
|
30972
|
+
return risks;
|
|
30973
|
+
}
|
|
30974
|
+
function inspectLocalCommand(input) {
|
|
30975
|
+
const args = normalizeArgs(input.args);
|
|
30976
|
+
const env = input.env ?? {};
|
|
30977
|
+
const transport = input.transport ?? "stdio";
|
|
30978
|
+
const risks = inspectRisks(input.command, args, env);
|
|
30979
|
+
return {
|
|
30980
|
+
requiresConsent: transport === "stdio",
|
|
30981
|
+
operation: input.operation ?? "launch",
|
|
30982
|
+
command: input.command,
|
|
30983
|
+
args,
|
|
30984
|
+
displayCommand: displayCommand(input.command, args),
|
|
30985
|
+
envKeys: Object.keys(env).sort(),
|
|
30986
|
+
risks,
|
|
30987
|
+
hasDangerousRisk: risks.some((risk) => risk.severity === "danger")
|
|
30988
|
+
};
|
|
30989
|
+
}
|
|
30990
|
+
function formatLocalCommandReview(review) {
|
|
30991
|
+
const lines = [
|
|
30992
|
+
`Command: ${review.displayCommand}`,
|
|
30993
|
+
review.envKeys.length > 0 ? `Env keys: ${review.envKeys.join(", ")}` : "Env keys: <none>"
|
|
30994
|
+
];
|
|
30995
|
+
if (review.risks.length > 0) {
|
|
30996
|
+
lines.push("Risks:");
|
|
30997
|
+
for (const risk of review.risks) {
|
|
30998
|
+
lines.push(`- ${risk.severity}: ${risk.code} - ${risk.message}${risk.evidence ? ` (${risk.evidence})` : ""}`);
|
|
30999
|
+
}
|
|
31000
|
+
} else {
|
|
31001
|
+
lines.push("Risks: none detected");
|
|
31002
|
+
}
|
|
31003
|
+
return lines.join(`
|
|
31004
|
+
`);
|
|
31005
|
+
}
|
|
31006
|
+
function assertLocalCommandConsent(input, consent = {}) {
|
|
31007
|
+
const review = inspectLocalCommand(input);
|
|
31008
|
+
if (!review.requiresConsent)
|
|
31009
|
+
return review;
|
|
31010
|
+
if (consent.approved !== true) {
|
|
31011
|
+
throw new LocalCommandConsentError(`local stdio command approval is required before ${review.operation}.
|
|
31012
|
+
${formatLocalCommandReview(review)}`, review);
|
|
31013
|
+
}
|
|
31014
|
+
if (review.hasDangerousRisk && consent.allowRisky !== true) {
|
|
31015
|
+
throw new LocalCommandConsentError(`risky command approval is required before ${review.operation}.
|
|
31016
|
+
${formatLocalCommandReview(review)}`, review);
|
|
31017
|
+
}
|
|
31018
|
+
return review;
|
|
31019
|
+
}
|
|
31020
|
+
|
|
31021
|
+
// src/lib/remote.ts
|
|
30719
31022
|
function parseRegistryEntry(entry) {
|
|
30720
31023
|
const s = entry.server;
|
|
30721
31024
|
return {
|
|
@@ -30746,7 +31049,7 @@ async function getRegistryServer(id) {
|
|
|
30746
31049
|
const all = entries.map(parseRegistryEntry);
|
|
30747
31050
|
return all.find((s) => s.id === id) || null;
|
|
30748
31051
|
}
|
|
30749
|
-
async function installFromRegistry(id) {
|
|
31052
|
+
async function installFromRegistry(id, options = {}) {
|
|
30750
31053
|
const server = await getRegistryServer(id);
|
|
30751
31054
|
if (!server) {
|
|
30752
31055
|
throw new Error(`Server "${id}" not found in registry`);
|
|
@@ -30766,6 +31069,13 @@ async function installFromRegistry(id) {
|
|
|
30766
31069
|
transport = pkg.transport.type;
|
|
30767
31070
|
}
|
|
30768
31071
|
}
|
|
31072
|
+
assertLocalCommandConsent({
|
|
31073
|
+
command,
|
|
31074
|
+
args,
|
|
31075
|
+
transport,
|
|
31076
|
+
env: {},
|
|
31077
|
+
operation: "register"
|
|
31078
|
+
}, options.localCommandConsent);
|
|
30769
31079
|
return addServer({
|
|
30770
31080
|
name: server.name,
|
|
30771
31081
|
description: server.description,
|
|
@@ -30860,7 +31170,22 @@ function installToGemini(entry) {
|
|
|
30860
31170
|
return { agent: "gemini", success: false, error: err.message };
|
|
30861
31171
|
}
|
|
30862
31172
|
}
|
|
30863
|
-
function installToAgents(entry, targets = ["claude", "codex", "gemini"]) {
|
|
31173
|
+
function installToAgents(entry, targets = ["claude", "codex", "gemini"], options = {}) {
|
|
31174
|
+
try {
|
|
31175
|
+
assertLocalCommandConsent({
|
|
31176
|
+
command: entry.command,
|
|
31177
|
+
args: entry.args,
|
|
31178
|
+
env: entry.env,
|
|
31179
|
+
transport: entry.transport,
|
|
31180
|
+
operation: "install"
|
|
31181
|
+
}, options.localCommandConsent);
|
|
31182
|
+
} catch (err) {
|
|
31183
|
+
return targets.map((target) => ({
|
|
31184
|
+
agent: target,
|
|
31185
|
+
success: false,
|
|
31186
|
+
error: err.message
|
|
31187
|
+
}));
|
|
31188
|
+
}
|
|
30864
31189
|
return targets.map((target) => {
|
|
30865
31190
|
if (target === "claude")
|
|
30866
31191
|
return installToClaude(entry);
|
|
@@ -33277,7 +33602,7 @@ function requireUrl(entry) {
|
|
|
33277
33602
|
throw new Error(`Server "${entry.id}" has an invalid URL: ${entry.url}`);
|
|
33278
33603
|
}
|
|
33279
33604
|
}
|
|
33280
|
-
async function connectToServer(entry) {
|
|
33605
|
+
async function connectToServer(entry, options = {}) {
|
|
33281
33606
|
if (connections.has(entry.id)) {
|
|
33282
33607
|
return connections.get(entry.id);
|
|
33283
33608
|
}
|
|
@@ -33293,6 +33618,13 @@ async function connectToServer(entry) {
|
|
|
33293
33618
|
if (!entry.command?.trim()) {
|
|
33294
33619
|
throw new Error(`Server "${entry.id}" is missing a command`);
|
|
33295
33620
|
}
|
|
33621
|
+
assertLocalCommandConsent({
|
|
33622
|
+
command: entry.command,
|
|
33623
|
+
args: entry.args,
|
|
33624
|
+
env: entry.env,
|
|
33625
|
+
transport: entry.transport,
|
|
33626
|
+
operation: "launch"
|
|
33627
|
+
}, options.localCommandConsent);
|
|
33296
33628
|
transport = new StdioClientTransport({
|
|
33297
33629
|
command: entry.command,
|
|
33298
33630
|
args: entry.args,
|
|
@@ -33409,7 +33741,7 @@ async function callTool(prefixedName, args) {
|
|
|
33409
33741
|
]
|
|
33410
33742
|
};
|
|
33411
33743
|
}
|
|
33412
|
-
async function connectAllEnabled() {
|
|
33744
|
+
async function connectAllEnabled(options = {}) {
|
|
33413
33745
|
const servers = listServers().filter((s) => s.enabled);
|
|
33414
33746
|
const results = [];
|
|
33415
33747
|
let index = 0;
|
|
@@ -33421,7 +33753,7 @@ async function connectAllEnabled() {
|
|
|
33421
33753
|
return;
|
|
33422
33754
|
const server = servers[current];
|
|
33423
33755
|
try {
|
|
33424
|
-
const conn = await connectToServer(server);
|
|
33756
|
+
const conn = await connectToServer(server, options);
|
|
33425
33757
|
results.push(conn);
|
|
33426
33758
|
} catch (err) {
|
|
33427
33759
|
console.error(`Failed to connect to ${server.name}: ${err.message}`);
|
|
@@ -33434,13 +33766,28 @@ async function connectAllEnabled() {
|
|
|
33434
33766
|
|
|
33435
33767
|
// src/lib/doctor.ts
|
|
33436
33768
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
33437
|
-
async function diagnoseServer(server) {
|
|
33769
|
+
async function diagnoseServer(server, options = {}) {
|
|
33438
33770
|
const checks4 = [];
|
|
33771
|
+
let hasLocalConsent = true;
|
|
33439
33772
|
if (server.transport === "stdio") {
|
|
33773
|
+
try {
|
|
33774
|
+
assertLocalCommandConsent({
|
|
33775
|
+
command: server.command,
|
|
33776
|
+
args: server.args,
|
|
33777
|
+
env: server.env,
|
|
33778
|
+
transport: server.transport,
|
|
33779
|
+
operation: "diagnose"
|
|
33780
|
+
}, options.localCommandConsent);
|
|
33781
|
+
} catch (err) {
|
|
33782
|
+
hasLocalConsent = false;
|
|
33783
|
+
checks4.push({ name: "local command consent", pass: false, message: err.message });
|
|
33784
|
+
}
|
|
33440
33785
|
try {
|
|
33441
33786
|
const path = execFileSync2("which", [server.command], { stdio: "pipe" }).toString().trim();
|
|
33442
33787
|
let version2 = "";
|
|
33443
33788
|
try {
|
|
33789
|
+
if (!hasLocalConsent)
|
|
33790
|
+
throw new Error("local stdio command approval is required before version probing");
|
|
33444
33791
|
version2 = execFileSync2(server.command, ["--version"], { stdio: "pipe" }).toString().trim().split(`
|
|
33445
33792
|
`)[0];
|
|
33446
33793
|
} catch {}
|
|
@@ -33471,10 +33818,10 @@ async function diagnoseServer(server) {
|
|
|
33471
33818
|
checks4.push({ name: "URL reachable", pass: false, message: `unreachable: ${err.message}` });
|
|
33472
33819
|
}
|
|
33473
33820
|
}
|
|
33474
|
-
if (server.enabled) {
|
|
33821
|
+
if (server.enabled && hasLocalConsent) {
|
|
33475
33822
|
try {
|
|
33476
33823
|
await Promise.race([
|
|
33477
|
-
connectToServer(server),
|
|
33824
|
+
connectToServer(server, { localCommandConsent: options.localCommandConsent }),
|
|
33478
33825
|
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout after 10s")), 1e4))
|
|
33479
33826
|
]);
|
|
33480
33827
|
await disconnectServer(server.id);
|
|
@@ -33482,8 +33829,10 @@ async function diagnoseServer(server) {
|
|
|
33482
33829
|
} catch (err) {
|
|
33483
33830
|
checks4.push({ name: "connect & list tools", pass: false, message: err.message });
|
|
33484
33831
|
}
|
|
33485
|
-
} else {
|
|
33832
|
+
} else if (!server.enabled) {
|
|
33486
33833
|
checks4.push({ name: "connect & list tools", pass: true, message: "skipped (server disabled)" });
|
|
33834
|
+
} else {
|
|
33835
|
+
checks4.push({ name: "connect & list tools", pass: false, message: "skipped until local stdio command is approved" });
|
|
33487
33836
|
}
|
|
33488
33837
|
return {
|
|
33489
33838
|
server,
|
|
@@ -34395,6 +34744,112 @@ async function runFleetInstall(options = {}, dependencies = {}) {
|
|
|
34395
34744
|
}));
|
|
34396
34745
|
}
|
|
34397
34746
|
|
|
34747
|
+
// src/lib/provider-profiles.ts
|
|
34748
|
+
init_db();
|
|
34749
|
+
init_provider_profile_seeds();
|
|
34750
|
+
var TRANSPORTS = new Set(["stdio", "sse", "streamable-http"]);
|
|
34751
|
+
var AUTH_TYPES = new Set(["none", "oauth2", "api_key", "bearer_token", "custom"]);
|
|
34752
|
+
var TOKEN_MODES = new Set(["none", "user", "workspace", "service"]);
|
|
34753
|
+
var BEARER_TOKEN_MODES = new Set(["none", "optional", "required"]);
|
|
34754
|
+
var PROVENANCE_SOURCES = new Set(["curated", "official-registry", "npm", "github", "manual"]);
|
|
34755
|
+
function safeJsonParse2(value, fallback) {
|
|
34756
|
+
if (typeof value !== "string")
|
|
34757
|
+
return fallback;
|
|
34758
|
+
try {
|
|
34759
|
+
return JSON.parse(value);
|
|
34760
|
+
} catch {
|
|
34761
|
+
return fallback;
|
|
34762
|
+
}
|
|
34763
|
+
}
|
|
34764
|
+
function normalizeId(id) {
|
|
34765
|
+
const normalized = id.trim().toLowerCase();
|
|
34766
|
+
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(normalized)) {
|
|
34767
|
+
throw new Error("Provider profile id must be lowercase kebab-case");
|
|
34768
|
+
}
|
|
34769
|
+
return normalized;
|
|
34770
|
+
}
|
|
34771
|
+
function parseRow3(row) {
|
|
34772
|
+
const installFallback = safeJsonParse2(row.install_fallback, null);
|
|
34773
|
+
return {
|
|
34774
|
+
id: row.id,
|
|
34775
|
+
displayName: row.display_name,
|
|
34776
|
+
description: row.description || null,
|
|
34777
|
+
endpoint: row.endpoint || null,
|
|
34778
|
+
transport: row.transport,
|
|
34779
|
+
fallbackEndpoints: safeJsonParse2(row.fallback_endpoints, []),
|
|
34780
|
+
authType: row.auth_type,
|
|
34781
|
+
authMetadata: safeJsonParse2(row.auth_metadata, {}),
|
|
34782
|
+
scopes: safeJsonParse2(row.scopes, []),
|
|
34783
|
+
tokenMode: row.token_mode,
|
|
34784
|
+
installFallback,
|
|
34785
|
+
docsUrl: row.docs_url || null,
|
|
34786
|
+
safety: safeJsonParse2(row.safety, {}),
|
|
34787
|
+
provenance: safeJsonParse2(row.provenance, { source: "manual" }),
|
|
34788
|
+
enabled: row.enabled === 1 || row.enabled === true,
|
|
34789
|
+
created_at: row.created_at,
|
|
34790
|
+
updated_at: row.updated_at
|
|
34791
|
+
};
|
|
34792
|
+
}
|
|
34793
|
+
function listProviderProfiles(options = {}) {
|
|
34794
|
+
const db2 = getDb();
|
|
34795
|
+
const sql = options.enabledOnly ? "SELECT * FROM provider_profiles WHERE enabled = 1 ORDER BY display_name" : "SELECT * FROM provider_profiles ORDER BY display_name";
|
|
34796
|
+
return db2.prepare(sql).all().map(parseRow3);
|
|
34797
|
+
}
|
|
34798
|
+
function searchProviderProfiles(query, options = {}) {
|
|
34799
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
34800
|
+
if (!normalizedQuery)
|
|
34801
|
+
return listProviderProfiles(options);
|
|
34802
|
+
return listProviderProfiles(options).filter((profile) => {
|
|
34803
|
+
const searchable = [
|
|
34804
|
+
profile.id,
|
|
34805
|
+
profile.displayName,
|
|
34806
|
+
profile.description ?? "",
|
|
34807
|
+
profile.endpoint ?? "",
|
|
34808
|
+
profile.docsUrl ?? "",
|
|
34809
|
+
profile.provenance.sourceUrl ?? "",
|
|
34810
|
+
profile.provenance.packageName ?? ""
|
|
34811
|
+
].join(`
|
|
34812
|
+
`).toLowerCase();
|
|
34813
|
+
return searchable.includes(normalizedQuery);
|
|
34814
|
+
});
|
|
34815
|
+
}
|
|
34816
|
+
function getProviderProfile(id) {
|
|
34817
|
+
const db2 = getDb();
|
|
34818
|
+
const row = db2.prepare("SELECT * FROM provider_profiles WHERE id = ?").get(normalizeId(id));
|
|
34819
|
+
return row ? parseRow3(row) : null;
|
|
34820
|
+
}
|
|
34821
|
+
function installProviderProfile(id, options = {}) {
|
|
34822
|
+
const profile = getProviderProfile(id);
|
|
34823
|
+
if (!profile)
|
|
34824
|
+
throw new Error(`Provider profile "${id}" not found`);
|
|
34825
|
+
if (!profile.enabled)
|
|
34826
|
+
throw new Error(`Provider profile "${id}" is disabled`);
|
|
34827
|
+
const fallback = profile.installFallback;
|
|
34828
|
+
const useFallback = options.useFallback || !profile.endpoint;
|
|
34829
|
+
const command = useFallback ? fallback?.command : fallback?.command ?? "npx";
|
|
34830
|
+
const args = useFallback ? fallback?.args ?? [] : fallback?.args ?? [];
|
|
34831
|
+
if (!command) {
|
|
34832
|
+
throw new Error(`Provider profile "${id}" does not define an install fallback command`);
|
|
34833
|
+
}
|
|
34834
|
+
assertLocalCommandConsent({
|
|
34835
|
+
command,
|
|
34836
|
+
args,
|
|
34837
|
+
env: fallback?.env ?? {},
|
|
34838
|
+
transport: useFallback ? "stdio" : profile.transport,
|
|
34839
|
+
operation: "register"
|
|
34840
|
+
}, options.localCommandConsent);
|
|
34841
|
+
return addServer({
|
|
34842
|
+
name: options.name ?? profile.displayName,
|
|
34843
|
+
description: profile.description ?? undefined,
|
|
34844
|
+
command,
|
|
34845
|
+
args,
|
|
34846
|
+
env: fallback?.env,
|
|
34847
|
+
transport: useFallback ? "stdio" : profile.transport,
|
|
34848
|
+
url: useFallback ? fallback?.url : profile.endpoint ?? undefined,
|
|
34849
|
+
source: "provider-profile"
|
|
34850
|
+
});
|
|
34851
|
+
}
|
|
34852
|
+
|
|
34398
34853
|
// src/mcp/tools.ts
|
|
34399
34854
|
var VERSION = readPackageVersion(import.meta.url);
|
|
34400
34855
|
var mcpsAgents = new Map;
|
|
@@ -34410,6 +34865,13 @@ function jsonContent(value) {
|
|
|
34410
34865
|
function errorContent(text) {
|
|
34411
34866
|
return { ...textContent(text), isError: true };
|
|
34412
34867
|
}
|
|
34868
|
+
function localConsent(input) {
|
|
34869
|
+
return {
|
|
34870
|
+
approved: input.allow_local_stdio === true,
|
|
34871
|
+
allowRisky: input.allow_risky_command === true,
|
|
34872
|
+
source: "mcp"
|
|
34873
|
+
};
|
|
34874
|
+
}
|
|
34413
34875
|
function buildMcpTools() {
|
|
34414
34876
|
const definitions = [
|
|
34415
34877
|
{
|
|
@@ -34434,23 +34896,46 @@ function buildMcpTools() {
|
|
|
34434
34896
|
description: exports_external2.string().optional().describe("Description"),
|
|
34435
34897
|
transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("Transport type"),
|
|
34436
34898
|
url: exports_external2.string().optional().describe("URL for remote transports"),
|
|
34437
|
-
env: exports_external2.record(exports_external2.string()).optional().describe("Environment variables")
|
|
34899
|
+
env: exports_external2.record(exports_external2.string()).optional().describe("Environment variables"),
|
|
34900
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering this local stdio command"),
|
|
34901
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve registering risky local command patterns")
|
|
34438
34902
|
},
|
|
34439
|
-
run: (
|
|
34440
|
-
command
|
|
34441
|
-
args
|
|
34442
|
-
|
|
34443
|
-
|
|
34444
|
-
|
|
34445
|
-
|
|
34446
|
-
|
|
34447
|
-
|
|
34903
|
+
run: (input) => {
|
|
34904
|
+
const command = String(input.command);
|
|
34905
|
+
const args = Array.isArray(input.args) ? input.args.map(String) : [];
|
|
34906
|
+
const env = isRecordOfStrings(input.env) ? input.env : {};
|
|
34907
|
+
const transport = input.transport;
|
|
34908
|
+
try {
|
|
34909
|
+
assertLocalCommandConsent({ command, args, env, transport, operation: "register" }, localConsent(input));
|
|
34910
|
+
return jsonContent(addServer({
|
|
34911
|
+
command,
|
|
34912
|
+
args,
|
|
34913
|
+
name: typeof input.name === "string" ? input.name : undefined,
|
|
34914
|
+
description: typeof input.description === "string" ? input.description : undefined,
|
|
34915
|
+
transport,
|
|
34916
|
+
url: typeof input.url === "string" ? input.url : undefined,
|
|
34917
|
+
env
|
|
34918
|
+
}));
|
|
34919
|
+
} catch (err) {
|
|
34920
|
+
return errorContent(err.message);
|
|
34921
|
+
}
|
|
34922
|
+
}
|
|
34448
34923
|
},
|
|
34449
34924
|
{
|
|
34450
34925
|
name: "install_from_registry",
|
|
34451
34926
|
description: "Install an MCP server from the official registry",
|
|
34452
|
-
paramsSchema: {
|
|
34453
|
-
|
|
34927
|
+
paramsSchema: {
|
|
34928
|
+
id: exports_external2.string().describe("Registry server ID"),
|
|
34929
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering registry stdio commands"),
|
|
34930
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve registering risky local command patterns")
|
|
34931
|
+
},
|
|
34932
|
+
run: async (input) => {
|
|
34933
|
+
try {
|
|
34934
|
+
return jsonContent(await installFromRegistry(String(input.id), { localCommandConsent: localConsent(input) }));
|
|
34935
|
+
} catch (err) {
|
|
34936
|
+
return errorContent(err.message);
|
|
34937
|
+
}
|
|
34938
|
+
}
|
|
34454
34939
|
},
|
|
34455
34940
|
{
|
|
34456
34941
|
name: "remove_server",
|
|
@@ -34496,26 +34981,41 @@ function buildMcpTools() {
|
|
|
34496
34981
|
command: exports_external2.string().optional().describe("New command"),
|
|
34497
34982
|
args: exports_external2.array(exports_external2.string()).optional().describe("New args list"),
|
|
34498
34983
|
transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("New transport type"),
|
|
34499
|
-
url: exports_external2.string().optional().describe("New URL for remote transports")
|
|
34984
|
+
url: exports_external2.string().optional().describe("New URL for remote transports"),
|
|
34985
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve updating this local stdio command"),
|
|
34986
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve risky local command patterns")
|
|
34500
34987
|
},
|
|
34501
|
-
run: (
|
|
34502
|
-
const serverId = String(id);
|
|
34988
|
+
run: (input) => {
|
|
34989
|
+
const serverId = String(input.id);
|
|
34503
34990
|
const existing = getServer(serverId);
|
|
34504
34991
|
if (!existing)
|
|
34505
34992
|
return errorContent(`Server "${serverId}" not found.`);
|
|
34506
34993
|
const fields = {};
|
|
34507
|
-
if (typeof name === "string")
|
|
34508
|
-
fields.name = name;
|
|
34509
|
-
if (typeof description === "string")
|
|
34510
|
-
fields.description = description;
|
|
34511
|
-
if (typeof command === "string")
|
|
34512
|
-
fields.command = command;
|
|
34513
|
-
if (Array.isArray(args))
|
|
34514
|
-
fields.args = args.map(String);
|
|
34515
|
-
if (transport === "stdio" || transport === "sse" || transport === "streamable-http")
|
|
34516
|
-
fields.transport = transport;
|
|
34517
|
-
if (typeof
|
|
34518
|
-
fields.url =
|
|
34994
|
+
if (typeof input.name === "string")
|
|
34995
|
+
fields.name = input.name;
|
|
34996
|
+
if (typeof input.description === "string")
|
|
34997
|
+
fields.description = input.description;
|
|
34998
|
+
if (typeof input.command === "string")
|
|
34999
|
+
fields.command = input.command;
|
|
35000
|
+
if (Array.isArray(input.args))
|
|
35001
|
+
fields.args = input.args.map(String);
|
|
35002
|
+
if (input.transport === "stdio" || input.transport === "sse" || input.transport === "streamable-http")
|
|
35003
|
+
fields.transport = input.transport;
|
|
35004
|
+
if (typeof input.url === "string")
|
|
35005
|
+
fields.url = input.url;
|
|
35006
|
+
if (fields.command !== undefined || fields.args !== undefined || fields.transport !== undefined) {
|
|
35007
|
+
try {
|
|
35008
|
+
assertLocalCommandConsent({
|
|
35009
|
+
command: fields.command ?? existing.command,
|
|
35010
|
+
args: fields.args ?? existing.args,
|
|
35011
|
+
env: existing.env,
|
|
35012
|
+
transport: fields.transport ?? existing.transport,
|
|
35013
|
+
operation: "register"
|
|
35014
|
+
}, localConsent(input));
|
|
35015
|
+
} catch (err) {
|
|
35016
|
+
return errorContent(err.message);
|
|
35017
|
+
}
|
|
35018
|
+
}
|
|
34519
35019
|
return jsonContent(redactServerEnv(updateServer(serverId, fields)));
|
|
34520
35020
|
}
|
|
34521
35021
|
},
|
|
@@ -34561,6 +35061,58 @@ function buildMcpTools() {
|
|
|
34561
35061
|
limit: typeof limit === "number" ? limit : undefined
|
|
34562
35062
|
}))
|
|
34563
35063
|
},
|
|
35064
|
+
{
|
|
35065
|
+
name: "list_provider_profiles",
|
|
35066
|
+
description: "List curated provider profiles for hosted/common MCP integrations such as Notion and Linear.",
|
|
35067
|
+
paramsSchema: {
|
|
35068
|
+
enabled_only: exports_external2.boolean().optional().describe("Only include enabled provider profiles")
|
|
35069
|
+
},
|
|
35070
|
+
run: ({ enabled_only }) => jsonContent(listProviderProfiles({ enabledOnly: enabled_only === true }))
|
|
35071
|
+
},
|
|
35072
|
+
{
|
|
35073
|
+
name: "search_provider_profiles",
|
|
35074
|
+
description: "Search curated provider profiles separately from raw MCP registry/source search.",
|
|
35075
|
+
paramsSchema: {
|
|
35076
|
+
query: exports_external2.string().describe("Search query such as 'notion', 'linear', or an endpoint URL"),
|
|
35077
|
+
enabled_only: exports_external2.boolean().optional().describe("Only include enabled provider profiles")
|
|
35078
|
+
},
|
|
35079
|
+
run: ({ query, enabled_only }) => jsonContent(searchProviderProfiles(String(query), { enabledOnly: enabled_only === true }))
|
|
35080
|
+
},
|
|
35081
|
+
{
|
|
35082
|
+
name: "get_provider_profile",
|
|
35083
|
+
description: "Get one curated provider profile by ID.",
|
|
35084
|
+
paramsSchema: {
|
|
35085
|
+
id: exports_external2.string().describe("Provider profile ID")
|
|
35086
|
+
},
|
|
35087
|
+
run: ({ id }) => {
|
|
35088
|
+
const profile = getProviderProfile(String(id));
|
|
35089
|
+
if (!profile)
|
|
35090
|
+
return errorContent(`Provider profile "${String(id)}" not found.`);
|
|
35091
|
+
return jsonContent(profile);
|
|
35092
|
+
}
|
|
35093
|
+
},
|
|
35094
|
+
{
|
|
35095
|
+
name: "install_provider_profile",
|
|
35096
|
+
description: "Register a curated provider profile as an MCP server.",
|
|
35097
|
+
paramsSchema: {
|
|
35098
|
+
id: exports_external2.string().describe("Provider profile ID"),
|
|
35099
|
+
name: exports_external2.string().optional().describe("Override registered server name"),
|
|
35100
|
+
use_fallback: exports_external2.boolean().optional().describe("Install the stdio fallback command instead of the direct remote transport"),
|
|
35101
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering provider stdio fallback commands"),
|
|
35102
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve risky local command patterns")
|
|
35103
|
+
},
|
|
35104
|
+
run: (input) => {
|
|
35105
|
+
try {
|
|
35106
|
+
return jsonContent(redactServerEnv(installProviderProfile(String(input.id), {
|
|
35107
|
+
name: typeof input.name === "string" ? input.name : undefined,
|
|
35108
|
+
useFallback: input.use_fallback === true,
|
|
35109
|
+
localCommandConsent: localConsent(input)
|
|
35110
|
+
})));
|
|
35111
|
+
} catch (err) {
|
|
35112
|
+
return errorContent(err.message);
|
|
35113
|
+
}
|
|
35114
|
+
}
|
|
35115
|
+
},
|
|
34564
35116
|
{
|
|
34565
35117
|
name: "list_sources",
|
|
34566
35118
|
description: "List all configured search sources for finding MCP servers",
|
|
@@ -34627,15 +35179,19 @@ function buildMcpTools() {
|
|
|
34627
35179
|
description: "Install a registered MCP server into Claude Code, Codex, and/or Gemini",
|
|
34628
35180
|
paramsSchema: {
|
|
34629
35181
|
id: exports_external2.string().describe("Server ID to install (from list_servers)"),
|
|
34630
|
-
targets: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("Target agents to install into (default: all)")
|
|
35182
|
+
targets: exports_external2.array(exports_external2.enum(["claude", "codex", "gemini"])).optional().describe("Target agents to install into (default: all)"),
|
|
35183
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve installing local stdio commands into local agent configs"),
|
|
35184
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve installing risky local command patterns")
|
|
34631
35185
|
},
|
|
34632
|
-
run: (
|
|
34633
|
-
const serverId = String(id);
|
|
35186
|
+
run: (input) => {
|
|
35187
|
+
const serverId = String(input.id);
|
|
34634
35188
|
const entry = getServer(serverId);
|
|
34635
35189
|
if (!entry)
|
|
34636
35190
|
return errorContent(`Server "${serverId}" not found.`);
|
|
34637
|
-
const agentTargets = Array.isArray(targets) ? targets : undefined;
|
|
34638
|
-
return jsonContent(installToAgents(entry, agentTargets ?? ["claude", "codex", "gemini"]
|
|
35191
|
+
const agentTargets = Array.isArray(input.targets) ? input.targets : undefined;
|
|
35192
|
+
return jsonContent(installToAgents(entry, agentTargets ?? ["claude", "codex", "gemini"], {
|
|
35193
|
+
localCommandConsent: localConsent(input)
|
|
35194
|
+
}));
|
|
34639
35195
|
}
|
|
34640
35196
|
},
|
|
34641
35197
|
{
|
|
@@ -34647,11 +35203,14 @@ function buildMcpTools() {
|
|
|
34647
35203
|
{
|
|
34648
35204
|
name: "connect_and_list_tools",
|
|
34649
35205
|
description: "Connect to all enabled MCP servers and list their available tools",
|
|
34650
|
-
paramsSchema: {
|
|
34651
|
-
|
|
35206
|
+
paramsSchema: {
|
|
35207
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching enabled local stdio commands"),
|
|
35208
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve launching risky local command patterns")
|
|
35209
|
+
},
|
|
35210
|
+
run: async (input) => {
|
|
34652
35211
|
let liveTools = [];
|
|
34653
35212
|
try {
|
|
34654
|
-
await connectAllEnabled();
|
|
35213
|
+
await connectAllEnabled({ localCommandConsent: localConsent(input) });
|
|
34655
35214
|
liveTools = listAllTools();
|
|
34656
35215
|
} finally {
|
|
34657
35216
|
await disconnectAll().catch(() => {
|
|
@@ -34666,11 +35225,13 @@ function buildMcpTools() {
|
|
|
34666
35225
|
description: `Call a tool on a connected upstream MCP server. Tool name format: server_id${TOOL_PREFIX_SEPARATOR}tool_name`,
|
|
34667
35226
|
paramsSchema: {
|
|
34668
35227
|
tool_name: exports_external2.string().describe(`Prefixed tool name (server_id${TOOL_PREFIX_SEPARATOR}tool_name)`),
|
|
34669
|
-
arguments: exports_external2.record(exports_external2.unknown()).optional().describe("Tool arguments as key-value pairs")
|
|
35228
|
+
arguments: exports_external2.record(exports_external2.unknown()).optional().describe("Tool arguments as key-value pairs"),
|
|
35229
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching this local stdio command"),
|
|
35230
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve launching risky local command patterns")
|
|
34670
35231
|
},
|
|
34671
|
-
run: async (
|
|
35232
|
+
run: async (input) => {
|
|
34672
35233
|
try {
|
|
34673
|
-
const toolName = String(tool_name);
|
|
35234
|
+
const toolName = String(input.tool_name);
|
|
34674
35235
|
const sepIdx = toolName.indexOf(TOOL_PREFIX_SEPARATOR);
|
|
34675
35236
|
if (sepIdx === -1)
|
|
34676
35237
|
return errorContent(`Error: Invalid tool name "${toolName}"`);
|
|
@@ -34680,8 +35241,8 @@ function buildMcpTools() {
|
|
|
34680
35241
|
return errorContent(`Error: Server "${serverId}" not found.`);
|
|
34681
35242
|
if (!entry.enabled)
|
|
34682
35243
|
return errorContent(`Error: Server "${serverId}" is disabled.`);
|
|
34683
|
-
await connectToServer(entry);
|
|
34684
|
-
const result = await callTool(toolName, readRecord(
|
|
35244
|
+
await connectToServer(entry, { localCommandConsent: localConsent(input) });
|
|
35245
|
+
const result = await callTool(toolName, readRecord(input.arguments));
|
|
34685
35246
|
return { content: result.content };
|
|
34686
35247
|
} catch (error2) {
|
|
34687
35248
|
return errorContent(`Error: ${error2.message}`);
|
|
@@ -34691,13 +35252,17 @@ function buildMcpTools() {
|
|
|
34691
35252
|
{
|
|
34692
35253
|
name: "diagnose_server",
|
|
34693
35254
|
description: "Run health checks on a registered MCP server",
|
|
34694
|
-
paramsSchema: {
|
|
34695
|
-
|
|
34696
|
-
|
|
35255
|
+
paramsSchema: {
|
|
35256
|
+
id: exports_external2.string().describe("Server ID"),
|
|
35257
|
+
allow_local_stdio: exports_external2.boolean().optional().describe("Approve launching local stdio diagnostics"),
|
|
35258
|
+
allow_risky_command: exports_external2.boolean().optional().describe("Approve diagnosing risky local command patterns")
|
|
35259
|
+
},
|
|
35260
|
+
run: async (input) => {
|
|
35261
|
+
const serverId = String(input.id);
|
|
34697
35262
|
const entry = getServer(serverId);
|
|
34698
35263
|
if (!entry)
|
|
34699
35264
|
return errorContent(`Server "${serverId}" not found.`);
|
|
34700
|
-
return jsonContent(await diagnoseServer(entry));
|
|
35265
|
+
return jsonContent(await diagnoseServer(entry, { localCommandConsent: localConsent(input) }));
|
|
34701
35266
|
}
|
|
34702
35267
|
},
|
|
34703
35268
|
{
|