rush-ai 0.11.2 → 0.12.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.
@@ -403,15 +403,27 @@ async function runMcpInstall(input) {
403
403
  }
404
404
  }
405
405
  let extraConfigValues = {};
406
+ if (input.setValues) {
407
+ extraConfigValues = { ...input.setValues };
408
+ }
406
409
  if (extraConfigMeta && Object.keys(extraConfigMeta).length > 0) {
407
- const hasRequired = Object.values(extraConfigMeta).some((m) => m.required);
408
- if (hasRequired) {
409
- output.newline();
410
- output.info(`${detail.displayName} requires credentials:`);
410
+ const remainingMeta = {};
411
+ for (const [key, meta] of Object.entries(extraConfigMeta)) {
412
+ if (!(key in extraConfigValues)) {
413
+ remainingMeta[key] = meta;
414
+ }
415
+ }
416
+ if (Object.keys(remainingMeta).length > 0) {
417
+ const hasRequired = Object.values(remainingMeta).some((m) => m.required);
418
+ if (hasRequired) {
419
+ output.newline();
420
+ output.info(`${detail.displayName} requires credentials:`);
421
+ }
422
+ const prompted = await promptCredentials(remainingMeta, {
423
+ yes: input.yes
424
+ });
425
+ extraConfigValues = { ...prompted, ...extraConfigValues };
411
426
  }
412
- extraConfigValues = await promptCredentials(extraConfigMeta, {
413
- yes: input.yes
414
- });
415
427
  }
416
428
  const mergedConfig = mergeExtraConfigIntoServerConfig(
417
429
  transportType,
@@ -558,4 +570,4 @@ export {
558
570
  runMcpInstall,
559
571
  runMcpUninstall
560
572
  };
561
- //# sourceMappingURL=install-DNSVANLM.js.map
573
+ //# sourceMappingURL=install-AGHG4XKQ.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 setValues?: Record<string, string>;\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\n // 3a. Start with --set values (highest priority)\n if (input.setValues) {\n extraConfigValues = { ...input.setValues };\n }\n\n // 3b. Prompt for remaining credentials not already provided by --set\n if (extraConfigMeta && Object.keys(extraConfigMeta).length > 0) {\n // Filter out keys already provided by --set\n const remainingMeta: Record<string, ExtraConfigFieldMeta> = {};\n for (const [key, meta] of Object.entries(extraConfigMeta)) {\n if (!(key in extraConfigValues)) {\n remainingMeta[key] = meta;\n }\n }\n\n if (Object.keys(remainingMeta).length > 0) {\n const hasRequired = Object.values(remainingMeta).some((m) => m.required);\n if (hasRequired) {\n output.newline();\n output.info(`${detail.displayName} requires credentials:`);\n }\n const prompted = await promptCredentials(remainingMeta, {\n yes: input.yes,\n });\n extraConfigValues = { ...prompted, ...extraConfigValues };\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,6 +1,6 @@
1
1
  {
2
2
  "name": "rush-ai",
3
- "version": "0.11.2",
3
+ "version": "0.12.1",
4
4
  "description": "Rush CLI - Command-line interface for the Rush AI platform",
5
5
  "private": false,
6
6
  "type": "module",
@@ -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 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"]}