rush-ai 0.11.0 → 0.11.1
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/dist/index.js
CHANGED
|
@@ -3917,7 +3917,7 @@ function registerMcpCommand(program) {
|
|
|
3917
3917
|
"Comma-separated targets: claude-desktop,claude-code (default: both)"
|
|
3918
3918
|
).option("--allow-unverified", "Allow installing unverified MCP servers").action(async (mcpId, opts) => {
|
|
3919
3919
|
const format = resolveFormat(program.opts());
|
|
3920
|
-
const { runMcpInstall, printMcpInstallSummary } = await import("./install-
|
|
3920
|
+
const { runMcpInstall, printMcpInstallSummary } = await import("./install-DNSVANLM.js");
|
|
3921
3921
|
try {
|
|
3922
3922
|
const result = await runMcpInstall({
|
|
3923
3923
|
mcpId,
|
|
@@ -3957,7 +3957,7 @@ function registerMcpCommand(program) {
|
|
|
3957
3957
|
"Comma-separated targets: claude-desktop,claude-code (default: both)"
|
|
3958
3958
|
).action(async (mcpId, opts) => {
|
|
3959
3959
|
const format = resolveFormat(program.opts());
|
|
3960
|
-
const { runMcpUninstall, printMcpUninstallSummary } = await import("./install-
|
|
3960
|
+
const { runMcpUninstall, printMcpUninstallSummary } = await import("./install-DNSVANLM.js");
|
|
3961
3961
|
try {
|
|
3962
3962
|
const result = await runMcpUninstall({
|
|
3963
3963
|
mcpId,
|
|
@@ -58,32 +58,42 @@ async function writeClaudeDesktopManaged(name, config, tools) {
|
|
|
58
58
|
metaPath,
|
|
59
59
|
() => ({ entries: [] })
|
|
60
60
|
);
|
|
61
|
-
let
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
entryId = existingEntry.id;
|
|
65
|
-
} else {
|
|
66
|
-
entryId = randomUUID();
|
|
67
|
-
meta.entries.push({ id: entryId, name });
|
|
61
|
+
let appliedId = meta.appliedId;
|
|
62
|
+
if (!appliedId && meta.entries.length > 0) {
|
|
63
|
+
appliedId = meta.entries[0].id;
|
|
68
64
|
}
|
|
69
|
-
if (!
|
|
70
|
-
|
|
65
|
+
if (!appliedId) {
|
|
66
|
+
appliedId = randomUUID();
|
|
67
|
+
meta.entries.push({ id: appliedId, name: "Default" });
|
|
68
|
+
meta.appliedId = appliedId;
|
|
69
|
+
await writeJsonFile(metaPath, meta);
|
|
71
70
|
}
|
|
71
|
+
const appliedFilePath = join(libDir, `${appliedId}.json`);
|
|
72
|
+
const { data: appliedConfig } = await readJsonFile(
|
|
73
|
+
appliedFilePath,
|
|
74
|
+
() => ({})
|
|
75
|
+
);
|
|
72
76
|
const toolPolicy = {};
|
|
73
77
|
for (const tool of tools) {
|
|
74
78
|
toolPolicy[tool.name] = "allow";
|
|
75
79
|
}
|
|
76
|
-
const
|
|
80
|
+
const serverEntry = {
|
|
77
81
|
name,
|
|
82
|
+
transport: config.type || "sse",
|
|
78
83
|
url: config.url,
|
|
79
|
-
type: config.type || "sse",
|
|
80
84
|
...config.headers && Object.keys(config.headers).length > 0 ? { headers: config.headers } : {},
|
|
81
85
|
...Object.keys(toolPolicy).length > 0 ? { toolPolicy } : {}
|
|
82
86
|
};
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
const mcpServers = appliedConfig.managedMcpServers ?? [];
|
|
88
|
+
const existingIdx = mcpServers.findIndex((s) => s.name === name);
|
|
89
|
+
if (existingIdx !== -1) {
|
|
90
|
+
mcpServers[existingIdx] = serverEntry;
|
|
91
|
+
} else {
|
|
92
|
+
mcpServers.push(serverEntry);
|
|
93
|
+
}
|
|
94
|
+
appliedConfig.managedMcpServers = mcpServers;
|
|
95
|
+
await writeJsonFile(appliedFilePath, appliedConfig);
|
|
96
|
+
return appliedFilePath;
|
|
87
97
|
}
|
|
88
98
|
async function writeClaudeCode(name, transportType, config) {
|
|
89
99
|
const filePath = claudeCodeSettingsPath();
|
|
@@ -130,17 +140,20 @@ async function removeFromClaudeDesktopManaged(name) {
|
|
|
130
140
|
metaPath,
|
|
131
141
|
() => ({ entries: [] })
|
|
132
142
|
);
|
|
133
|
-
const
|
|
143
|
+
const appliedId = meta.appliedId ?? meta.entries[0]?.id;
|
|
144
|
+
if (!appliedId) return false;
|
|
145
|
+
const appliedFilePath = join(configLibraryDir(), `${appliedId}.json`);
|
|
146
|
+
if (!await pathExists(appliedFilePath)) return false;
|
|
147
|
+
const { data: appliedConfig } = await readJsonFile(
|
|
148
|
+
appliedFilePath,
|
|
149
|
+
() => ({})
|
|
150
|
+
);
|
|
151
|
+
const mcpServers = appliedConfig.managedMcpServers ?? [];
|
|
152
|
+
const idx = mcpServers.findIndex((s) => s.name === name);
|
|
134
153
|
if (idx === -1) return false;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
meta.appliedId = meta.entries[0]?.id;
|
|
139
|
-
}
|
|
140
|
-
const serverFilePath = join(configLibraryDir(), `${entryId}.json`);
|
|
141
|
-
const { rm } = await import("fs/promises");
|
|
142
|
-
await rm(serverFilePath, { force: true });
|
|
143
|
-
await writeJsonFile(metaPath, meta);
|
|
154
|
+
mcpServers.splice(idx, 1);
|
|
155
|
+
appliedConfig.managedMcpServers = mcpServers;
|
|
156
|
+
await writeJsonFile(appliedFilePath, appliedConfig);
|
|
144
157
|
return true;
|
|
145
158
|
}
|
|
146
159
|
async function removeFromClaudeCode(name) {
|
|
@@ -545,4 +558,4 @@ export {
|
|
|
545
558
|
runMcpInstall,
|
|
546
559
|
runMcpUninstall
|
|
547
560
|
};
|
|
548
|
-
//# sourceMappingURL=install-
|
|
561
|
+
//# sourceMappingURL=install-DNSVANLM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/mcp/config-writers.ts","../src/commands/mcp/prompt-utils.ts","../src/commands/mcp/template-utils.ts","../src/commands/mcp/install.ts"],"sourcesContent":["/**\n * Claude Desktop + Claude Code 配置文件写入器。\n *\n * 支持 4 种写入场景:\n * - stdio → claude_desktop_config.json (Claude Desktop 3p)\n * - http/sse → configLibrary/<uuid>.json (Claude Desktop 3p managed server)\n * - 所有类型 → ~/.claude/settings.json (Claude Code)\n * - 移除 server(uninstall)\n *\n * 复用 installers/claude-code/atomic-json.ts 的原子读写。\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport {\n pathExists,\n readJsonFile,\n writeJsonFile,\n} from '../../installers/claude-code/atomic-json.js';\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nfunction claudeDesktop3pDir(): string {\n return join(homedir(), 'Library', 'Application Support', 'Claude-3p');\n}\n\nfunction claudeDesktopConfigPath(): string {\n return join(claudeDesktop3pDir(), 'claude_desktop_config.json');\n}\n\nfunction configLibraryDir(): string {\n return join(claudeDesktop3pDir(), 'configLibrary');\n}\n\nfunction configLibraryMetaPath(): string {\n return join(configLibraryDir(), '_meta.json');\n}\n\nfunction claudeCodeSettingsPath(): string {\n return join(homedir(), '.claude', 'settings.json');\n}\n\n// ---------------------------------------------------------------------------\n// Detection\n// ---------------------------------------------------------------------------\n\nexport async function detectClaudeDesktop(): Promise<boolean> {\n return pathExists(claudeDesktop3pDir());\n}\n\nexport async function detectClaudeCode(): Promise<boolean> {\n return pathExists(join(homedir(), '.claude'));\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface StdioConfig {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n}\n\nexport interface HttpSseConfig {\n url: string;\n type?: string;\n headers?: Record<string, string>;\n}\n\ninterface ConfigLibraryEntry {\n id: string;\n name: string;\n}\n\ninterface ConfigLibraryMeta {\n appliedId?: string;\n entries: ConfigLibraryEntry[];\n}\n\ninterface ManagedServerEntry {\n name: string;\n transport: string;\n url?: string;\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n headers?: Record<string, string>;\n toolPolicy?: Record<string, string>;\n}\n\ninterface AppliedConfigFile {\n managedMcpServers?: ManagedServerEntry[];\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Claude Desktop — stdio\n// ---------------------------------------------------------------------------\n\nexport async function writeClaudeDesktopStdio(\n name: string,\n config: StdioConfig\n): Promise<string> {\n const filePath = claudeDesktopConfigPath();\n const { data } = await readJsonFile<Record<string, unknown>>(\n filePath,\n () => ({})\n );\n\n const mcpServers = (data.mcpServers as Record<string, unknown>) || {};\n mcpServers[name] = {\n command: config.command,\n ...(config.args?.length ? { args: config.args } : {}),\n ...(config.env && Object.keys(config.env).length > 0\n ? { env: config.env }\n : {}),\n };\n data.mcpServers = mcpServers;\n\n await writeJsonFile(filePath, data);\n return filePath;\n}\n\n// ---------------------------------------------------------------------------\n// Claude Desktop — http/sse (managed server via configLibrary)\n// ---------------------------------------------------------------------------\n\nexport async function writeClaudeDesktopManaged(\n name: string,\n config: HttpSseConfig,\n tools: Array<{ name: string; description?: string }>\n): Promise<string> {\n const libDir = configLibraryDir();\n const metaPath = configLibraryMetaPath();\n\n // 1. Read _meta.json to find appliedId\n const { data: meta } = await readJsonFile<ConfigLibraryMeta>(\n metaPath,\n () => ({ entries: [] })\n );\n\n // 2. Resolve the active config file (appliedId)\n let appliedId = meta.appliedId;\n if (!appliedId && meta.entries.length > 0) {\n appliedId = meta.entries[0].id;\n }\n if (!appliedId) {\n // No config exists yet — create a new one\n appliedId = randomUUID();\n meta.entries.push({ id: appliedId, name: 'Default' });\n meta.appliedId = appliedId;\n await writeJsonFile(metaPath, meta);\n }\n\n const appliedFilePath = join(libDir, `${appliedId}.json`);\n\n // 3. Read the active config file\n const { data: appliedConfig } = await readJsonFile<AppliedConfigFile>(\n appliedFilePath,\n () => ({})\n );\n\n // 4. Build the MCP server entry\n const toolPolicy: Record<string, string> = {};\n for (const tool of tools) {\n toolPolicy[tool.name] = 'allow';\n }\n\n const serverEntry: ManagedServerEntry = {\n name,\n transport: config.type || 'sse',\n url: config.url,\n ...(config.headers && Object.keys(config.headers).length > 0\n ? { headers: config.headers }\n : {}),\n ...(Object.keys(toolPolicy).length > 0 ? { toolPolicy } : {}),\n };\n\n // 5. Merge into managedMcpServers array (replace existing by name, or append)\n const mcpServers = appliedConfig.managedMcpServers ?? [];\n const existingIdx = mcpServers.findIndex((s) => s.name === name);\n if (existingIdx !== -1) {\n mcpServers[existingIdx] = serverEntry;\n } else {\n mcpServers.push(serverEntry);\n }\n appliedConfig.managedMcpServers = mcpServers;\n\n // 6. Write back\n await writeJsonFile(appliedFilePath, appliedConfig);\n\n return appliedFilePath;\n}\n\n// ---------------------------------------------------------------------------\n// Claude Code — settings.json (all transport types)\n// ---------------------------------------------------------------------------\n\nexport async function writeClaudeCode(\n name: string,\n transportType: string,\n config: Record<string, unknown>\n): Promise<string> {\n const filePath = claudeCodeSettingsPath();\n const { data } = await readJsonFile<Record<string, unknown>>(\n filePath,\n () => ({})\n );\n\n const mcpServers =\n (data.mcpServers as Record<string, Record<string, unknown>>) || {};\n\n if (transportType === 'stdio') {\n mcpServers[name] = {\n command: config.command as string,\n ...(config.args ? { args: config.args } : {}),\n ...(config.env ? { env: config.env } : {}),\n };\n } else {\n // http / sse\n mcpServers[name] = {\n type: transportType,\n url: config.url as string,\n ...(config.headers ? { headers: config.headers } : {}),\n };\n }\n\n data.mcpServers = mcpServers;\n await writeJsonFile(filePath, data);\n return filePath;\n}\n\n// ---------------------------------------------------------------------------\n// Uninstall — remove from all targets\n// ---------------------------------------------------------------------------\n\nexport async function removeFromClaudeDesktopStdio(\n name: string\n): Promise<boolean> {\n const filePath = claudeDesktopConfigPath();\n if (!(await pathExists(filePath))) return false;\n\n const { data } = await readJsonFile<Record<string, unknown>>(\n filePath,\n () => ({})\n );\n const mcpServers = (data.mcpServers as Record<string, unknown>) || {};\n if (!(name in mcpServers)) return false;\n\n delete mcpServers[name];\n data.mcpServers = mcpServers;\n await writeJsonFile(filePath, data);\n return true;\n}\n\nexport async function removeFromClaudeDesktopManaged(\n name: string\n): Promise<boolean> {\n const metaPath = configLibraryMetaPath();\n if (!(await pathExists(metaPath))) return false;\n\n const { data: meta } = await readJsonFile<ConfigLibraryMeta>(\n metaPath,\n () => ({ entries: [] })\n );\n\n // Find the active config file\n const appliedId = meta.appliedId ?? meta.entries[0]?.id;\n if (!appliedId) return false;\n\n const appliedFilePath = join(configLibraryDir(), `${appliedId}.json`);\n if (!(await pathExists(appliedFilePath))) return false;\n\n const { data: appliedConfig } = await readJsonFile<AppliedConfigFile>(\n appliedFilePath,\n () => ({})\n );\n\n const mcpServers = appliedConfig.managedMcpServers ?? [];\n const idx = mcpServers.findIndex((s) => s.name === name);\n if (idx === -1) return false;\n\n mcpServers.splice(idx, 1);\n appliedConfig.managedMcpServers = mcpServers;\n await writeJsonFile(appliedFilePath, appliedConfig);\n return true;\n}\n\nexport async function removeFromClaudeCode(name: string): Promise<boolean> {\n const filePath = claudeCodeSettingsPath();\n if (!(await pathExists(filePath))) return false;\n\n const { data } = await readJsonFile<Record<string, unknown>>(\n filePath,\n () => ({})\n );\n const mcpServers = (data.mcpServers as Record<string, unknown>) || {};\n if (!(name in mcpServers)) return false;\n\n delete mcpServers[name];\n data.mcpServers = mcpServers;\n await writeJsonFile(filePath, data);\n return true;\n}\n","/**\n * 交互式凭证收集(raw stdin,无第三方依赖)。\n *\n * 复用 commands/task/deploy.ts:460-475 的 readOneLine 模式。\n */\n\nexport interface ExtraConfigFieldMeta {\n helpUrl?: string;\n type?: 'text' | 'secret';\n required?: boolean;\n defaultValue?: string;\n}\n\n/**\n * 从 stdin 读取一行。非 TTY 时如果 stdin 已关闭则 resolve 空字符串。\n */\nfunction readOneLine(): Promise<string> {\n return new Promise((resolve) => {\n let acc = '';\n const onData = (chunk: Buffer) => {\n acc += chunk.toString('utf-8');\n const nlIdx = acc.indexOf('\\n');\n if (nlIdx !== -1) {\n process.stdin.removeListener('data', onData);\n process.stdin.pause();\n resolve(acc.slice(0, nlIdx));\n }\n };\n const onEnd = () => {\n process.stdin.removeListener('data', onData);\n resolve(acc);\n };\n process.stdin.resume();\n process.stdin.on('data', onData);\n process.stdin.once('end', onEnd);\n });\n}\n\n/**\n * 交互式收集凭证。\n *\n * - TTY: 提示用户输入每个 required 字段\n * - 非 TTY / --yes: 使用 defaultValue,required 且无 default 的字段 → 抛错\n */\nexport async function promptCredentials(\n extraConfigMeta: Record<string, ExtraConfigFieldMeta>,\n options?: { yes?: boolean }\n): Promise<Record<string, string>> {\n const values: Record<string, string> = {};\n const isTTY = Boolean(process.stdin.isTTY);\n const skipPrompt = options?.yes || !isTTY;\n\n for (const [key, meta] of Object.entries(extraConfigMeta)) {\n if (!meta.required) {\n if (meta.defaultValue) {\n values[key] = meta.defaultValue;\n }\n continue;\n }\n\n if (skipPrompt) {\n if (meta.defaultValue) {\n values[key] = meta.defaultValue;\n } else {\n throw new Error(\n `Credential \"${key}\" is required but no default value is available. ` +\n `Run interactively or provide credentials via the Rush web UI.` +\n (meta.helpUrl ? ` Help: ${meta.helpUrl}` : '')\n );\n }\n continue;\n }\n\n // Interactive prompt\n const typeHint = meta.type === 'secret' ? ' (secret)' : '';\n const defaultHint = meta.defaultValue ? ` [${meta.defaultValue}]` : '';\n const helpHint = meta.helpUrl ? `\\n Help: ${meta.helpUrl}` : '';\n process.stderr.write(` ${key}${typeHint}${defaultHint}:${helpHint} `);\n\n const input = await readOneLine();\n const trimmed = input.trim();\n\n if (trimmed) {\n values[key] = trimmed;\n } else if (meta.defaultValue) {\n values[key] = meta.defaultValue;\n } else {\n throw new Error(\n `Credential \"${key}\" is required but was not provided.` +\n (meta.helpUrl ? ` Help: ${meta.helpUrl}` : '')\n );\n }\n }\n\n return values;\n}\n","/**\n * MCP server_config 模板变量替换工具。\n *\n * 移植自 apps/web/lib/core/mcp/template-utils.ts + install-utils.ts,\n * 零外部依赖,纯函数。\n */\n\nconst VAR_PATTERN = /\\$\\{([^}]+)\\}/g;\n\n/**\n * 替换单个字符串中的 ${var} 占位符。\n */\nfunction substituteString(\n template: string,\n variables: Record<string, string>\n): { value: string; keys: string[] } {\n const keys: string[] = [];\n const value = template.replace(VAR_PATTERN, (match, varName: string) => {\n if (Object.hasOwn(variables, varName)) {\n keys.push(varName);\n return variables[varName];\n }\n return match;\n });\n return { value, keys };\n}\n\n/**\n * 替换 Record<string, string> 中所有 value 的 ${var} 占位符。\n */\nfunction substituteRecord(\n record: Record<string, string>,\n variables: Record<string, string>,\n usedKeys: Set<string>\n): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, val] of Object.entries(record)) {\n if (typeof val === 'string') {\n const { value, keys } = substituteString(val, variables);\n result[key] = value;\n for (const k of keys) usedKeys.add(k);\n } else {\n result[key] = val;\n }\n }\n return result;\n}\n\n/**\n * 对 server_config 中 headers / env 的 value 执行 ${varName} 模板替换。\n *\n * 来源: apps/web/lib/core/mcp/template-utils.ts:66-96\n */\nexport function substituteConfigVariables(\n config: Record<string, unknown>,\n variables: Record<string, string>\n): { result: Record<string, unknown>; usedKeys: Set<string> } {\n if (!variables || Object.keys(variables).length === 0) {\n return { result: { ...config }, usedKeys: new Set() };\n }\n\n const usedKeys = new Set<string>();\n const result: Record<string, unknown> = { ...config };\n\n if (result.headers && typeof result.headers === 'object') {\n result.headers = substituteRecord(\n result.headers as Record<string, string>,\n variables,\n usedKeys\n );\n }\n\n if (result.env && typeof result.env === 'object') {\n result.env = substituteRecord(\n result.env as Record<string, string>,\n variables,\n usedKeys\n );\n }\n\n return { result, usedKeys };\n}\n\n/**\n * 将 extra_config 值合并到 server_config 中。\n *\n * 1. 模板替换:替换 headers/env value 中的 ${var}\n * 2. 未被模板消费的 key → stdio 追加到 env,http/sse 追加到 headers\n *\n * 来源: apps/web/lib/core/mcp/install-utils.ts:127-177\n */\nexport function mergeExtraConfigIntoServerConfig(\n transportType: string,\n serverConfig: Record<string, unknown>,\n extraConfigValues: Record<string, string>\n): Record<string, unknown> {\n if (Object.keys(extraConfigValues).length === 0) {\n return serverConfig;\n }\n\n // 1. 模板替换\n const { result: substituted, usedKeys } = substituteConfigVariables(\n serverConfig,\n extraConfigValues\n );\n\n // 2. 计算未被模板消费的 key\n const remainingExtra: Record<string, string> = {};\n for (const [key, value] of Object.entries(extraConfigValues)) {\n if (!usedKeys.has(key)) {\n remainingExtra[key] = value;\n }\n }\n\n if (Object.keys(remainingExtra).length === 0) {\n return substituted;\n }\n\n // 3. 未消费的 key 按原逻辑追加\n if (transportType === 'stdio') {\n const existingEnv = (substituted.env as Record<string, string>) || {};\n const existingArgs = (substituted.args as string[]) || [];\n\n const mergedArgs = existingArgs.map((arg: string) => {\n const match = Object.entries(remainingExtra).find(([key]) => arg === key);\n return match ? match[1] : arg;\n });\n\n return {\n ...substituted,\n env: { ...existingEnv, ...remainingExtra },\n args: mergedArgs,\n };\n }\n\n const existingHeaders = (substituted.headers as Record<string, string>) || {};\n return {\n ...substituted,\n headers: { ...existingHeaders, ...remainingExtra },\n };\n}\n","/**\n * `rush-ai mcp install <mcp-id>` — 从 Rush Registry 安装 MCP Server 到 Claude Desktop + Claude Code\n */\n\nimport { output } from '../../output/logger.js';\nimport { createClient } from '../../util/client.js';\nimport { ApiError } from '../../util/errors.js';\nimport {\n detectClaudeCode,\n detectClaudeDesktop,\n removeFromClaudeCode,\n removeFromClaudeDesktopManaged,\n removeFromClaudeDesktopStdio,\n writeClaudeCode,\n writeClaudeDesktopManaged,\n writeClaudeDesktopStdio,\n} from './config-writers.js';\nimport type { ExtraConfigFieldMeta } from './prompt-utils.js';\nimport { promptCredentials } from './prompt-utils.js';\nimport { mergeExtraConfigIntoServerConfig } from './template-utils.js';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction readOneLine(): Promise<string> {\n return new Promise((resolve) => {\n let acc = '';\n const onData = (chunk: Buffer) => {\n acc += chunk.toString('utf-8');\n const nlIdx = acc.indexOf('\\n');\n if (nlIdx !== -1) {\n process.stdin.removeListener('data', onData);\n process.stdin.pause();\n resolve(acc.slice(0, nlIdx));\n }\n };\n const onEnd = () => {\n process.stdin.removeListener('data', onData);\n resolve(acc);\n };\n process.stdin.resume();\n process.stdin.on('data', onData);\n process.stdin.once('end', onEnd);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface McpInstallInput {\n mcpId: string;\n yes?: boolean;\n targetRaw?: string;\n allowUnverified?: boolean;\n}\n\ntype InstallTarget = 'claude-desktop' | 'claude-code';\n\ninterface TargetResult {\n target: InstallTarget;\n status: 'ok' | 'skipped' | 'error';\n configPath?: string;\n error?: string;\n}\n\nexport interface McpInstallResult {\n mcpId: string;\n displayName: string;\n transportType: string;\n targets: TargetResult[];\n}\n\n/** Full detail from GET /api/mcp-registry/:id */\ninterface McpDetailData {\n id: string;\n name: string;\n displayName: string;\n description: string;\n transportType: string;\n serverConfig: Record<string, unknown>;\n tools: Array<{ name: string; description: string }> | null;\n extraConfig?: Record<string, string>;\n extraConfigMeta?: Record<string, ExtraConfigFieldMeta>;\n validationStatus?: {\n status: string;\n errorCode?: string;\n errorMessage?: string;\n } | null;\n author?: string;\n [key: string]: unknown;\n}\n\nconst VALID_TARGETS: InstallTarget[] = ['claude-desktop', 'claude-code'];\nconst TRUSTED_STATUSES = new Set(['verified']);\n\ninterface McpDetailApiResponse {\n success: boolean;\n data: McpDetailData;\n}\n\n// ---------------------------------------------------------------------------\n// Core\n// ---------------------------------------------------------------------------\n\nfunction parseTargets(raw?: string): InstallTarget[] {\n if (!raw) return ['claude-desktop', 'claude-code'];\n const tokens = raw.split(',').map((t) => t.trim().toLowerCase());\n const unknown = tokens.filter(\n (t) => !VALID_TARGETS.includes(t as InstallTarget)\n );\n if (unknown.length > 0) {\n throw new Error(\n `Unknown target(s): ${unknown.join(', ')}. Valid targets: ${VALID_TARGETS.join(', ')}`\n );\n }\n return tokens as InstallTarget[];\n}\n\nexport async function runMcpInstall(\n input: McpInstallInput\n): Promise<McpInstallResult> {\n // 0. Validate targets early (before any network calls)\n const requestedTargets = parseTargets(input.targetRaw);\n\n const client = createClient();\n\n // 1. Fetch MCP detail\n let detail: McpDetailData;\n try {\n const { data } = await client.get<McpDetailApiResponse>(\n `/api/mcp-registry/${encodeURIComponent(input.mcpId)}`\n );\n detail = data.data;\n } catch (err) {\n if (err instanceof ApiError && err.status === 404) {\n throw new Error(`MCP server '${input.mcpId}' not found in the registry.`);\n }\n throw err;\n }\n\n const { transportType, serverConfig, extraConfigMeta, tools } = detail;\n\n // 2. Security check: warn on unverified MCP\n const vstatus = detail.validationStatus?.status;\n if (!TRUSTED_STATUSES.has(vstatus ?? '') && !input.allowUnverified) {\n const statusLabel = vstatus || 'unknown';\n const isTTY = Boolean(process.stdin.isTTY);\n\n if (input.yes || !isTTY) {\n throw new Error(\n `MCP server '${input.mcpId}' is not verified (status: ${statusLabel}). ` +\n `Use --allow-unverified to install anyway.`\n );\n }\n\n // Interactive confirmation\n output.newline();\n output.warn(\n `MCP server '${input.mcpId}' is not verified (status: ${statusLabel}).`\n );\n output.log(` Author: ${detail.author || 'unknown'}`);\n output.log(` Transport: ${transportType}`);\n if (transportType === 'stdio') {\n output.log(` Command: ${serverConfig.command}`);\n if (serverConfig.args) {\n output.log(` Args: ${(serverConfig.args as string[]).join(' ')}`);\n }\n } else {\n output.log(` URL: ${serverConfig.url}`);\n }\n output.newline();\n process.stderr.write('Install anyway? [y/N] ');\n const answer = await readOneLine();\n if (!answer.trim().toLowerCase().startsWith('y')) {\n throw new Error('Installation cancelled by user.');\n }\n }\n\n // 3. Collect credentials if needed\n let extraConfigValues: Record<string, string> = {};\n if (extraConfigMeta && Object.keys(extraConfigMeta).length > 0) {\n const hasRequired = Object.values(extraConfigMeta).some((m) => m.required);\n if (hasRequired) {\n output.newline();\n output.info(`${detail.displayName} requires credentials:`);\n }\n extraConfigValues = await promptCredentials(extraConfigMeta, {\n yes: input.yes,\n });\n }\n\n // 4. Merge extra config into server config\n const mergedConfig = mergeExtraConfigIntoServerConfig(\n transportType,\n serverConfig,\n extraConfigValues\n );\n\n // 5. Write to targets\n const targetResults: TargetResult[] = [];\n\n for (const target of requestedTargets) {\n try {\n if (target === 'claude-desktop') {\n const detected = await detectClaudeDesktop();\n if (!detected) {\n targetResults.push({\n target,\n status: 'skipped',\n error: 'Claude Desktop not detected',\n });\n continue;\n }\n\n let configPath: string;\n if (transportType === 'stdio') {\n configPath = await writeClaudeDesktopStdio(input.mcpId, {\n command: mergedConfig.command as string,\n args: mergedConfig.args as string[] | undefined,\n env: mergedConfig.env as Record<string, string> | undefined,\n });\n } else {\n configPath = await writeClaudeDesktopManaged(\n input.mcpId,\n {\n url: mergedConfig.url as string,\n type: transportType,\n headers: mergedConfig.headers as\n | Record<string, string>\n | undefined,\n },\n tools ?? []\n );\n }\n targetResults.push({ target, status: 'ok', configPath });\n } else {\n // claude-code\n const detected = await detectClaudeCode();\n if (!detected) {\n targetResults.push({\n target,\n status: 'skipped',\n error: 'Claude Code not detected',\n });\n continue;\n }\n\n const configPath = await writeClaudeCode(\n input.mcpId,\n transportType,\n mergedConfig\n );\n targetResults.push({ target, status: 'ok', configPath });\n }\n } catch (err) {\n targetResults.push({\n target,\n status: 'error',\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n return {\n mcpId: input.mcpId,\n displayName: detail.displayName,\n transportType,\n targets: targetResults,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Uninstall\n// ---------------------------------------------------------------------------\n\nexport interface McpUninstallInput {\n mcpId: string;\n targetRaw?: string;\n}\n\nexport interface McpUninstallResult {\n mcpId: string;\n targets: Array<{\n target: InstallTarget;\n removed: boolean;\n error?: string;\n }>;\n}\n\nexport async function runMcpUninstall(\n input: McpUninstallInput\n): Promise<McpUninstallResult> {\n const requestedTargets = parseTargets(input.targetRaw); // validates early\n const targets: McpUninstallResult['targets'] = [];\n\n for (const target of requestedTargets) {\n try {\n let removed = false;\n if (target === 'claude-desktop') {\n // Try both stdio config and managed server\n const removedStdio = await removeFromClaudeDesktopStdio(input.mcpId);\n const removedManaged = await removeFromClaudeDesktopManaged(\n input.mcpId\n );\n removed = removedStdio || removedManaged;\n } else {\n removed = await removeFromClaudeCode(input.mcpId);\n }\n targets.push({ target, removed });\n } catch (err) {\n targets.push({\n target,\n removed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n return { mcpId: input.mcpId, targets };\n}\n\n// ---------------------------------------------------------------------------\n// Print helpers\n// ---------------------------------------------------------------------------\n\nexport function printMcpInstallSummary(result: McpInstallResult): void {\n output.newline();\n output.success(\n `Installed ${output.bold(result.displayName)} (${result.mcpId})`\n );\n output.log(` Transport: ${result.transportType}`);\n output.newline();\n\n for (const t of result.targets) {\n const label =\n t.target === 'claude-desktop' ? 'Claude Desktop' : 'Claude Code';\n if (t.status === 'ok') {\n output.log(` ${label}: ✓ ${t.configPath}`);\n } else if (t.status === 'skipped') {\n output.dim(` ${label}: skipped (${t.error})`);\n } else {\n output.error(` ${label}: ${t.error}`);\n }\n }\n\n const anyOk = result.targets.some((t) => t.status === 'ok');\n if (anyOk) {\n output.newline();\n output.dim('Restart the IDE to activate the new MCP server.');\n }\n}\n\nexport function printMcpUninstallSummary(result: McpUninstallResult): void {\n output.newline();\n\n let anyRemoved = false;\n for (const t of result.targets) {\n const label =\n t.target === 'claude-desktop' ? 'Claude Desktop' : 'Claude Code';\n if (t.error) {\n output.error(` ${label}: ${t.error}`);\n } else if (t.removed) {\n output.log(` ${label}: ✓ removed`);\n anyRemoved = true;\n } else {\n output.dim(` ${label}: not found`);\n }\n }\n\n if (anyRemoved) {\n output.success(`Uninstalled ${output.bold(result.mcpId)}`);\n output.dim('Restart the IDE to apply changes.');\n } else {\n output.info(`${result.mcpId} was not installed in any target.`);\n }\n}\n"],"mappings":"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","names":["readOneLine"]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/mcp/config-writers.ts","../src/commands/mcp/prompt-utils.ts","../src/commands/mcp/template-utils.ts","../src/commands/mcp/install.ts"],"sourcesContent":["/**\n * Claude Desktop + Claude Code 配置文件写入器。\n *\n * 支持 4 种写入场景:\n * - stdio → claude_desktop_config.json (Claude Desktop 3p)\n * - http/sse → configLibrary/<uuid>.json (Claude Desktop 3p managed server)\n * - 所有类型 → ~/.claude/settings.json (Claude Code)\n * - 移除 server(uninstall)\n *\n * 复用 installers/claude-code/atomic-json.ts 的原子读写。\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport {\n pathExists,\n readJsonFile,\n writeJsonFile,\n} from '../../installers/claude-code/atomic-json.js';\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nfunction claudeDesktop3pDir(): string {\n return join(homedir(), 'Library', 'Application Support', 'Claude-3p');\n}\n\nfunction claudeDesktopConfigPath(): string {\n return join(claudeDesktop3pDir(), 'claude_desktop_config.json');\n}\n\nfunction configLibraryDir(): string {\n return join(claudeDesktop3pDir(), 'configLibrary');\n}\n\nfunction configLibraryMetaPath(): string {\n return join(configLibraryDir(), '_meta.json');\n}\n\nfunction claudeCodeSettingsPath(): string {\n return join(homedir(), '.claude', 'settings.json');\n}\n\n// ---------------------------------------------------------------------------\n// Detection\n// ---------------------------------------------------------------------------\n\nexport async function detectClaudeDesktop(): Promise<boolean> {\n return pathExists(claudeDesktop3pDir());\n}\n\nexport async function detectClaudeCode(): Promise<boolean> {\n return pathExists(join(homedir(), '.claude'));\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface StdioConfig {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n}\n\nexport interface HttpSseConfig {\n url: string;\n type?: string;\n headers?: Record<string, string>;\n}\n\ninterface ConfigLibraryEntry {\n id: string;\n name: string;\n}\n\ninterface ConfigLibraryMeta {\n appliedId?: string;\n entries: ConfigLibraryEntry[];\n}\n\ninterface ManagedServerFile {\n name: string;\n url: string;\n type: string;\n headers?: Record<string, string>;\n toolPolicy?: Record<string, string>;\n}\n\n// ---------------------------------------------------------------------------\n// Claude Desktop — stdio\n// ---------------------------------------------------------------------------\n\nexport async function writeClaudeDesktopStdio(\n name: string,\n config: StdioConfig\n): Promise<string> {\n const filePath = claudeDesktopConfigPath();\n const { data } = await readJsonFile<Record<string, unknown>>(\n filePath,\n () => ({})\n );\n\n const mcpServers = (data.mcpServers as Record<string, unknown>) || {};\n mcpServers[name] = {\n command: config.command,\n ...(config.args?.length ? { args: config.args } : {}),\n ...(config.env && Object.keys(config.env).length > 0\n ? { env: config.env }\n : {}),\n };\n data.mcpServers = mcpServers;\n\n await writeJsonFile(filePath, data);\n return filePath;\n}\n\n// ---------------------------------------------------------------------------\n// Claude Desktop — http/sse (managed server via configLibrary)\n// ---------------------------------------------------------------------------\n\nexport async function writeClaudeDesktopManaged(\n name: string,\n config: HttpSseConfig,\n tools: Array<{ name: string; description?: string }>\n): Promise<string> {\n const libDir = configLibraryDir();\n const metaPath = configLibraryMetaPath();\n\n // 1. Read _meta.json\n const { data: meta } = await readJsonFile<ConfigLibraryMeta>(\n metaPath,\n () => ({ entries: [] })\n );\n\n // 2. Find existing entry or create new UUID\n let entryId: string;\n const existingEntry = meta.entries.find((e) => e.name === name);\n if (existingEntry) {\n entryId = existingEntry.id;\n } else {\n entryId = randomUUID();\n meta.entries.push({ id: entryId, name });\n }\n\n // Ensure appliedId is set\n if (!meta.appliedId) {\n meta.appliedId = entryId;\n }\n\n // 3. Build toolPolicy — all tools default to \"allow\"\n const toolPolicy: Record<string, string> = {};\n for (const tool of tools) {\n toolPolicy[tool.name] = 'allow';\n }\n\n // 4. Write configLibrary/<uuid>.json\n const serverFile: ManagedServerFile = {\n name,\n url: config.url,\n type: config.type || 'sse',\n ...(config.headers && Object.keys(config.headers).length > 0\n ? { headers: config.headers }\n : {}),\n ...(Object.keys(toolPolicy).length > 0 ? { toolPolicy } : {}),\n };\n\n const serverFilePath = join(libDir, `${entryId}.json`);\n await writeJsonFile(serverFilePath, serverFile);\n\n // 5. Update _meta.json\n await writeJsonFile(metaPath, meta);\n\n return serverFilePath;\n}\n\n// ---------------------------------------------------------------------------\n// Claude Code — settings.json (all transport types)\n// ---------------------------------------------------------------------------\n\nexport async function writeClaudeCode(\n name: string,\n transportType: string,\n config: Record<string, unknown>\n): Promise<string> {\n const filePath = claudeCodeSettingsPath();\n const { data } = await readJsonFile<Record<string, unknown>>(\n filePath,\n () => ({})\n );\n\n const mcpServers =\n (data.mcpServers as Record<string, Record<string, unknown>>) || {};\n\n if (transportType === 'stdio') {\n mcpServers[name] = {\n command: config.command as string,\n ...(config.args ? { args: config.args } : {}),\n ...(config.env ? { env: config.env } : {}),\n };\n } else {\n // http / sse\n mcpServers[name] = {\n type: transportType,\n url: config.url as string,\n ...(config.headers ? { headers: config.headers } : {}),\n };\n }\n\n data.mcpServers = mcpServers;\n await writeJsonFile(filePath, data);\n return filePath;\n}\n\n// ---------------------------------------------------------------------------\n// Uninstall — remove from all targets\n// ---------------------------------------------------------------------------\n\nexport async function removeFromClaudeDesktopStdio(\n name: string\n): Promise<boolean> {\n const filePath = claudeDesktopConfigPath();\n if (!(await pathExists(filePath))) return false;\n\n const { data } = await readJsonFile<Record<string, unknown>>(\n filePath,\n () => ({})\n );\n const mcpServers = (data.mcpServers as Record<string, unknown>) || {};\n if (!(name in mcpServers)) return false;\n\n delete mcpServers[name];\n data.mcpServers = mcpServers;\n await writeJsonFile(filePath, data);\n return true;\n}\n\nexport async function removeFromClaudeDesktopManaged(\n name: string\n): Promise<boolean> {\n const metaPath = configLibraryMetaPath();\n if (!(await pathExists(metaPath))) return false;\n\n const { data: meta } = await readJsonFile<ConfigLibraryMeta>(\n metaPath,\n () => ({ entries: [] })\n );\n\n const idx = meta.entries.findIndex((e) => e.name === name);\n if (idx === -1) return false;\n\n const entryId = meta.entries[idx].id;\n meta.entries.splice(idx, 1);\n\n // Clear appliedId if it pointed to the removed entry\n if (meta.appliedId === entryId) {\n meta.appliedId = meta.entries[0]?.id;\n }\n\n // Remove the server file\n const serverFilePath = join(configLibraryDir(), `${entryId}.json`);\n const { rm } = await import('node:fs/promises');\n await rm(serverFilePath, { force: true });\n\n // Update _meta.json\n await writeJsonFile(metaPath, meta);\n return true;\n}\n\nexport async function removeFromClaudeCode(name: string): Promise<boolean> {\n const filePath = claudeCodeSettingsPath();\n if (!(await pathExists(filePath))) return false;\n\n const { data } = await readJsonFile<Record<string, unknown>>(\n filePath,\n () => ({})\n );\n const mcpServers = (data.mcpServers as Record<string, unknown>) || {};\n if (!(name in mcpServers)) return false;\n\n delete mcpServers[name];\n data.mcpServers = mcpServers;\n await writeJsonFile(filePath, data);\n return true;\n}\n","/**\n * 交互式凭证收集(raw stdin,无第三方依赖)。\n *\n * 复用 commands/task/deploy.ts:460-475 的 readOneLine 模式。\n */\n\nexport interface ExtraConfigFieldMeta {\n helpUrl?: string;\n type?: 'text' | 'secret';\n required?: boolean;\n defaultValue?: string;\n}\n\n/**\n * 从 stdin 读取一行。非 TTY 时如果 stdin 已关闭则 resolve 空字符串。\n */\nfunction readOneLine(): Promise<string> {\n return new Promise((resolve) => {\n let acc = '';\n const onData = (chunk: Buffer) => {\n acc += chunk.toString('utf-8');\n const nlIdx = acc.indexOf('\\n');\n if (nlIdx !== -1) {\n process.stdin.removeListener('data', onData);\n process.stdin.pause();\n resolve(acc.slice(0, nlIdx));\n }\n };\n const onEnd = () => {\n process.stdin.removeListener('data', onData);\n resolve(acc);\n };\n process.stdin.resume();\n process.stdin.on('data', onData);\n process.stdin.once('end', onEnd);\n });\n}\n\n/**\n * 交互式收集凭证。\n *\n * - TTY: 提示用户输入每个 required 字段\n * - 非 TTY / --yes: 使用 defaultValue,required 且无 default 的字段 → 抛错\n */\nexport async function promptCredentials(\n extraConfigMeta: Record<string, ExtraConfigFieldMeta>,\n options?: { yes?: boolean }\n): Promise<Record<string, string>> {\n const values: Record<string, string> = {};\n const isTTY = Boolean(process.stdin.isTTY);\n const skipPrompt = options?.yes || !isTTY;\n\n for (const [key, meta] of Object.entries(extraConfigMeta)) {\n if (!meta.required) {\n if (meta.defaultValue) {\n values[key] = meta.defaultValue;\n }\n continue;\n }\n\n if (skipPrompt) {\n if (meta.defaultValue) {\n values[key] = meta.defaultValue;\n } else {\n throw new Error(\n `Credential \"${key}\" is required but no default value is available. ` +\n `Run interactively or provide credentials via the Rush web UI.` +\n (meta.helpUrl ? ` Help: ${meta.helpUrl}` : '')\n );\n }\n continue;\n }\n\n // Interactive prompt\n const typeHint = meta.type === 'secret' ? ' (secret)' : '';\n const defaultHint = meta.defaultValue ? ` [${meta.defaultValue}]` : '';\n const helpHint = meta.helpUrl ? `\\n Help: ${meta.helpUrl}` : '';\n process.stderr.write(` ${key}${typeHint}${defaultHint}:${helpHint} `);\n\n const input = await readOneLine();\n const trimmed = input.trim();\n\n if (trimmed) {\n values[key] = trimmed;\n } else if (meta.defaultValue) {\n values[key] = meta.defaultValue;\n } else {\n throw new Error(\n `Credential \"${key}\" is required but was not provided.` +\n (meta.helpUrl ? ` Help: ${meta.helpUrl}` : '')\n );\n }\n }\n\n return values;\n}\n","/**\n * MCP server_config 模板变量替换工具。\n *\n * 移植自 apps/web/lib/core/mcp/template-utils.ts + install-utils.ts,\n * 零外部依赖,纯函数。\n */\n\nconst VAR_PATTERN = /\\$\\{([^}]+)\\}/g;\n\n/**\n * 替换单个字符串中的 ${var} 占位符。\n */\nfunction substituteString(\n template: string,\n variables: Record<string, string>\n): { value: string; keys: string[] } {\n const keys: string[] = [];\n const value = template.replace(VAR_PATTERN, (match, varName: string) => {\n if (Object.hasOwn(variables, varName)) {\n keys.push(varName);\n return variables[varName];\n }\n return match;\n });\n return { value, keys };\n}\n\n/**\n * 替换 Record<string, string> 中所有 value 的 ${var} 占位符。\n */\nfunction substituteRecord(\n record: Record<string, string>,\n variables: Record<string, string>,\n usedKeys: Set<string>\n): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, val] of Object.entries(record)) {\n if (typeof val === 'string') {\n const { value, keys } = substituteString(val, variables);\n result[key] = value;\n for (const k of keys) usedKeys.add(k);\n } else {\n result[key] = val;\n }\n }\n return result;\n}\n\n/**\n * 对 server_config 中 headers / env 的 value 执行 ${varName} 模板替换。\n *\n * 来源: apps/web/lib/core/mcp/template-utils.ts:66-96\n */\nexport function substituteConfigVariables(\n config: Record<string, unknown>,\n variables: Record<string, string>\n): { result: Record<string, unknown>; usedKeys: Set<string> } {\n if (!variables || Object.keys(variables).length === 0) {\n return { result: { ...config }, usedKeys: new Set() };\n }\n\n const usedKeys = new Set<string>();\n const result: Record<string, unknown> = { ...config };\n\n if (result.headers && typeof result.headers === 'object') {\n result.headers = substituteRecord(\n result.headers as Record<string, string>,\n variables,\n usedKeys\n );\n }\n\n if (result.env && typeof result.env === 'object') {\n result.env = substituteRecord(\n result.env as Record<string, string>,\n variables,\n usedKeys\n );\n }\n\n return { result, usedKeys };\n}\n\n/**\n * 将 extra_config 值合并到 server_config 中。\n *\n * 1. 模板替换:替换 headers/env value 中的 ${var}\n * 2. 未被模板消费的 key → stdio 追加到 env,http/sse 追加到 headers\n *\n * 来源: apps/web/lib/core/mcp/install-utils.ts:127-177\n */\nexport function mergeExtraConfigIntoServerConfig(\n transportType: string,\n serverConfig: Record<string, unknown>,\n extraConfigValues: Record<string, string>\n): Record<string, unknown> {\n if (Object.keys(extraConfigValues).length === 0) {\n return serverConfig;\n }\n\n // 1. 模板替换\n const { result: substituted, usedKeys } = substituteConfigVariables(\n serverConfig,\n extraConfigValues\n );\n\n // 2. 计算未被模板消费的 key\n const remainingExtra: Record<string, string> = {};\n for (const [key, value] of Object.entries(extraConfigValues)) {\n if (!usedKeys.has(key)) {\n remainingExtra[key] = value;\n }\n }\n\n if (Object.keys(remainingExtra).length === 0) {\n return substituted;\n }\n\n // 3. 未消费的 key 按原逻辑追加\n if (transportType === 'stdio') {\n const existingEnv = (substituted.env as Record<string, string>) || {};\n const existingArgs = (substituted.args as string[]) || [];\n\n const mergedArgs = existingArgs.map((arg: string) => {\n const match = Object.entries(remainingExtra).find(([key]) => arg === key);\n return match ? match[1] : arg;\n });\n\n return {\n ...substituted,\n env: { ...existingEnv, ...remainingExtra },\n args: mergedArgs,\n };\n }\n\n const existingHeaders = (substituted.headers as Record<string, string>) || {};\n return {\n ...substituted,\n headers: { ...existingHeaders, ...remainingExtra },\n };\n}\n","/**\n * `rush-ai mcp install <mcp-id>` — 从 Rush Registry 安装 MCP Server 到 Claude Desktop + Claude Code\n */\n\nimport { output } from '../../output/logger.js';\nimport { createClient } from '../../util/client.js';\nimport { ApiError } from '../../util/errors.js';\nimport {\n detectClaudeCode,\n detectClaudeDesktop,\n removeFromClaudeCode,\n removeFromClaudeDesktopManaged,\n removeFromClaudeDesktopStdio,\n writeClaudeCode,\n writeClaudeDesktopManaged,\n writeClaudeDesktopStdio,\n} from './config-writers.js';\nimport type { ExtraConfigFieldMeta } from './prompt-utils.js';\nimport { promptCredentials } from './prompt-utils.js';\nimport { mergeExtraConfigIntoServerConfig } from './template-utils.js';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction readOneLine(): Promise<string> {\n return new Promise((resolve) => {\n let acc = '';\n const onData = (chunk: Buffer) => {\n acc += chunk.toString('utf-8');\n const nlIdx = acc.indexOf('\\n');\n if (nlIdx !== -1) {\n process.stdin.removeListener('data', onData);\n process.stdin.pause();\n resolve(acc.slice(0, nlIdx));\n }\n };\n const onEnd = () => {\n process.stdin.removeListener('data', onData);\n resolve(acc);\n };\n process.stdin.resume();\n process.stdin.on('data', onData);\n process.stdin.once('end', onEnd);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface McpInstallInput {\n mcpId: string;\n yes?: boolean;\n targetRaw?: string;\n allowUnverified?: boolean;\n}\n\ntype InstallTarget = 'claude-desktop' | 'claude-code';\n\ninterface TargetResult {\n target: InstallTarget;\n status: 'ok' | 'skipped' | 'error';\n configPath?: string;\n error?: string;\n}\n\nexport interface McpInstallResult {\n mcpId: string;\n displayName: string;\n transportType: string;\n targets: TargetResult[];\n}\n\n/** Full detail from GET /api/mcp-registry/:id */\ninterface McpDetailData {\n id: string;\n name: string;\n displayName: string;\n description: string;\n transportType: string;\n serverConfig: Record<string, unknown>;\n tools: Array<{ name: string; description: string }> | null;\n extraConfig?: Record<string, string>;\n extraConfigMeta?: Record<string, ExtraConfigFieldMeta>;\n validationStatus?: {\n status: string;\n errorCode?: string;\n errorMessage?: string;\n } | null;\n author?: string;\n [key: string]: unknown;\n}\n\nconst VALID_TARGETS: InstallTarget[] = ['claude-desktop', 'claude-code'];\nconst TRUSTED_STATUSES = new Set(['verified']);\n\ninterface McpDetailApiResponse {\n success: boolean;\n data: McpDetailData;\n}\n\n// ---------------------------------------------------------------------------\n// Core\n// ---------------------------------------------------------------------------\n\nfunction parseTargets(raw?: string): InstallTarget[] {\n if (!raw) return ['claude-desktop', 'claude-code'];\n const tokens = raw.split(',').map((t) => t.trim().toLowerCase());\n const unknown = tokens.filter(\n (t) => !VALID_TARGETS.includes(t as InstallTarget)\n );\n if (unknown.length > 0) {\n throw new Error(\n `Unknown target(s): ${unknown.join(', ')}. Valid targets: ${VALID_TARGETS.join(', ')}`\n );\n }\n return tokens as InstallTarget[];\n}\n\nexport async function runMcpInstall(\n input: McpInstallInput\n): Promise<McpInstallResult> {\n // 0. Validate targets early (before any network calls)\n const requestedTargets = parseTargets(input.targetRaw);\n\n const client = createClient();\n\n // 1. Fetch MCP detail\n let detail: McpDetailData;\n try {\n const { data } = await client.get<McpDetailApiResponse>(\n `/api/mcp-registry/${encodeURIComponent(input.mcpId)}`\n );\n detail = data.data;\n } catch (err) {\n if (err instanceof ApiError && err.status === 404) {\n throw new Error(`MCP server '${input.mcpId}' not found in the registry.`);\n }\n throw err;\n }\n\n const { transportType, serverConfig, extraConfigMeta, tools } = detail;\n\n // 2. Security check: warn on unverified MCP\n const vstatus = detail.validationStatus?.status;\n if (!TRUSTED_STATUSES.has(vstatus ?? '') && !input.allowUnverified) {\n const statusLabel = vstatus || 'unknown';\n const isTTY = Boolean(process.stdin.isTTY);\n\n if (input.yes || !isTTY) {\n throw new Error(\n `MCP server '${input.mcpId}' is not verified (status: ${statusLabel}). ` +\n `Use --allow-unverified to install anyway.`\n );\n }\n\n // Interactive confirmation\n output.newline();\n output.warn(\n `MCP server '${input.mcpId}' is not verified (status: ${statusLabel}).`\n );\n output.log(` Author: ${detail.author || 'unknown'}`);\n output.log(` Transport: ${transportType}`);\n if (transportType === 'stdio') {\n output.log(` Command: ${serverConfig.command}`);\n if (serverConfig.args) {\n output.log(` Args: ${(serverConfig.args as string[]).join(' ')}`);\n }\n } else {\n output.log(` URL: ${serverConfig.url}`);\n }\n output.newline();\n process.stderr.write('Install anyway? [y/N] ');\n const answer = await readOneLine();\n if (!answer.trim().toLowerCase().startsWith('y')) {\n throw new Error('Installation cancelled by user.');\n }\n }\n\n // 3. Collect credentials if needed\n let extraConfigValues: Record<string, string> = {};\n if (extraConfigMeta && Object.keys(extraConfigMeta).length > 0) {\n const hasRequired = Object.values(extraConfigMeta).some((m) => m.required);\n if (hasRequired) {\n output.newline();\n output.info(`${detail.displayName} requires credentials:`);\n }\n extraConfigValues = await promptCredentials(extraConfigMeta, {\n yes: input.yes,\n });\n }\n\n // 4. Merge extra config into server config\n const mergedConfig = mergeExtraConfigIntoServerConfig(\n transportType,\n serverConfig,\n extraConfigValues\n );\n\n // 5. Write to targets\n const targetResults: TargetResult[] = [];\n\n for (const target of requestedTargets) {\n try {\n if (target === 'claude-desktop') {\n const detected = await detectClaudeDesktop();\n if (!detected) {\n targetResults.push({\n target,\n status: 'skipped',\n error: 'Claude Desktop not detected',\n });\n continue;\n }\n\n let configPath: string;\n if (transportType === 'stdio') {\n configPath = await writeClaudeDesktopStdio(input.mcpId, {\n command: mergedConfig.command as string,\n args: mergedConfig.args as string[] | undefined,\n env: mergedConfig.env as Record<string, string> | undefined,\n });\n } else {\n configPath = await writeClaudeDesktopManaged(\n input.mcpId,\n {\n url: mergedConfig.url as string,\n type: transportType,\n headers: mergedConfig.headers as\n | Record<string, string>\n | undefined,\n },\n tools ?? []\n );\n }\n targetResults.push({ target, status: 'ok', configPath });\n } else {\n // claude-code\n const detected = await detectClaudeCode();\n if (!detected) {\n targetResults.push({\n target,\n status: 'skipped',\n error: 'Claude Code not detected',\n });\n continue;\n }\n\n const configPath = await writeClaudeCode(\n input.mcpId,\n transportType,\n mergedConfig\n );\n targetResults.push({ target, status: 'ok', configPath });\n }\n } catch (err) {\n targetResults.push({\n target,\n status: 'error',\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n return {\n mcpId: input.mcpId,\n displayName: detail.displayName,\n transportType,\n targets: targetResults,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Uninstall\n// ---------------------------------------------------------------------------\n\nexport interface McpUninstallInput {\n mcpId: string;\n targetRaw?: string;\n}\n\nexport interface McpUninstallResult {\n mcpId: string;\n targets: Array<{\n target: InstallTarget;\n removed: boolean;\n error?: string;\n }>;\n}\n\nexport async function runMcpUninstall(\n input: McpUninstallInput\n): Promise<McpUninstallResult> {\n const requestedTargets = parseTargets(input.targetRaw); // validates early\n const targets: McpUninstallResult['targets'] = [];\n\n for (const target of requestedTargets) {\n try {\n let removed = false;\n if (target === 'claude-desktop') {\n // Try both stdio config and managed server\n const removedStdio = await removeFromClaudeDesktopStdio(input.mcpId);\n const removedManaged = await removeFromClaudeDesktopManaged(\n input.mcpId\n );\n removed = removedStdio || removedManaged;\n } else {\n removed = await removeFromClaudeCode(input.mcpId);\n }\n targets.push({ target, removed });\n } catch (err) {\n targets.push({\n target,\n removed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n return { mcpId: input.mcpId, targets };\n}\n\n// ---------------------------------------------------------------------------\n// Print helpers\n// ---------------------------------------------------------------------------\n\nexport function printMcpInstallSummary(result: McpInstallResult): void {\n output.newline();\n output.success(\n `Installed ${output.bold(result.displayName)} (${result.mcpId})`\n );\n output.log(` Transport: ${result.transportType}`);\n output.newline();\n\n for (const t of result.targets) {\n const label =\n t.target === 'claude-desktop' ? 'Claude Desktop' : 'Claude Code';\n if (t.status === 'ok') {\n output.log(` ${label}: ✓ ${t.configPath}`);\n } else if (t.status === 'skipped') {\n output.dim(` ${label}: skipped (${t.error})`);\n } else {\n output.error(` ${label}: ${t.error}`);\n }\n }\n\n const anyOk = result.targets.some((t) => t.status === 'ok');\n if (anyOk) {\n output.newline();\n output.dim('Restart the IDE to activate the new MCP server.');\n }\n}\n\nexport function printMcpUninstallSummary(result: McpUninstallResult): void {\n output.newline();\n\n let anyRemoved = false;\n for (const t of result.targets) {\n const label =\n t.target === 'claude-desktop' ? 'Claude Desktop' : 'Claude Code';\n if (t.error) {\n output.error(` ${label}: ${t.error}`);\n } else if (t.removed) {\n output.log(` ${label}: ✓ removed`);\n anyRemoved = true;\n } else {\n output.dim(` ${label}: not found`);\n }\n }\n\n if (anyRemoved) {\n output.success(`Uninstalled ${output.bold(result.mcpId)}`);\n output.dim('Restart the IDE to apply changes.');\n } else {\n output.info(`${result.mcpId} was not installed in any target.`);\n }\n}\n"],"mappings":"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","names":["readOneLine"]}
|