From 41bb300542c1d0a92e8ec89b3ba294c1c0dd28a9 Mon Sep 17 00:00:00 2001 From: LaZzyMan Date: Mon, 9 Mar 2026 10:59:20 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20MCP=20=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8=E7=A6=81=E7=94=A8=E7=8A=B6=E6=80=81=E4=B8=8B?= =?UTF-8?q?=E7=9A=84=E6=93=8D=E4=BD=9C=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 问题 2.1: 禁用的服务器不再显示'查看工具'选项,避免用户查看空工具列表 - 问题 2.2: 禁用的服务器不再显示'重新连接'选项,保持逻辑一致性 - 改进:根据服务器状态动态生成可用操作列表 - 改进:启用/禁用选项始终显示,方便用户切换状态 --- OPTIMIZATION_PLAN.md | 962 ++++++++++++++++++ .../components/mcp/steps/ServerDetailStep.tsx | 62 +- 2 files changed, 1001 insertions(+), 23 deletions(-) create mode 100644 OPTIMIZATION_PLAN.md diff --git a/OPTIMIZATION_PLAN.md b/OPTIMIZATION_PLAN.md new file mode 100644 index 000000000..b56e14ea9 --- /dev/null +++ b/OPTIMIZATION_PLAN.md @@ -0,0 +1,962 @@ +# Qwen Code 0.12.0 MCP & Extension Management 优化方案 + +## 问题梳理与解决方案 + +根据钉钉文档《0.12.0 体验反馈》中提出的问题,本文件详细分析了每个问题的根本原因,并提供具体的解决方案和代码修改建议。 + +--- + +## 文档问题概览 + +本文档共包含 **6 个问题** (3 个 P1 + 3 个 P2),分为两个主要部分: + +### Part 1: MCP Management TUI (5 个问题) + +- **P1 级别**: 3 个问题 +- **P2 级别**: 2 个细节问题 (共 10 个小点) + +### Part 2: Extension Management TUI (1 个问题) + +- **P2 级别**: 1 个命令报错问题 + +## 问题 1: 【P1】Auth 属于 manage 的一部分,应该加到 manage 里 + +### 问题描述 + +- **现状**: 当前 MCP Management Dialog 中**没有 OAuth 认证功能**,用户必须使用 `/mcp auth ` 命令进行认证 +- **问题**: + - Auth 功能独立于 Manage Dialog 之外,用户体验割裂 + - 需要记住命令行才能认证,不够直观 + - MCP 管理对话框中只能查看服务器状态和工具,无法进行认证操作 +- **文档建议**: Auth 应该整合到 manage dialog 中,在 UI 界面内完成所有 MCP 管理操作 + +### 根本原因分析 + +#### 当前实现 + +```typescript +// packages/cli/src/ui/commands/mcpCommand.ts +const mcpCommand: SlashCommand = { + name: 'mcp', + subCommands: [manageCommand, authCommand], // auth 作为独立子命令存在 + action: async (): Promise => ({ + type: 'dialog', + dialog: 'mcp', // 默认打开管理对话框 + }), +}; +``` + +#### MCP Management Dialog 现状 + +```typescript +// packages/cli/src/ui/components/mcp/MCPManagementDialog.tsx +// 当前的步骤类型 +export const MCP_MANAGEMENT_STEPS = { + SERVER_LIST: 'server-list', + SERVER_DETAIL: 'server-detail', + DISABLE_SCOPE_SELECT: 'disable-scope-select', + TOOL_LIST: 'tool-list', + TOOL_DETAIL: 'tool-detail', +} as const; + +// ServerDetailStep 中的操作选项 +const actions = [ + { label: 'View tools', value: 'view-tools' }, + { label: 'Reconnect', value: 'reconnect' }, + { label: 'Enable/Disable', value: 'toggle-disable' }, + // ❌ 缺少 'Authenticate' 选项 +]; +``` + +#### 问题分析 + +1. **UI 层面**: MCP Management Dialog 中没有认证相关的 UI 组件和操作入口 +2. **代码层面**: OAuth 认证逻辑只在命令行 handler 中实现 (`mcpCommand.ts` 的 `authCommand`) +3. **体验层面**: 用户需要在 TUI 和 CLI 之间切换,无法在一个界面内完成所有操作 + +### 解决方案 + +#### 方案 A: 在 MCP Dialog 中集成完整的 OAuth 认证功能 (强烈推荐) + +**核心思路**: + +- 在 Server Detail 页面添加 "Authenticate" 操作选项 +- 复用现有的 `MCPOAuthProvider` 和 OAuth 流程 +- 通过事件系统显示认证过程中的提示信息 + +**实现步骤**: + +##### 1. 扩展 MCP_MANAGEMENT_STEPS + +```typescript +// packages/cli/src/ui/components/mcp/types.ts +export const MCP_MANAGEMENT_STEPS = { + SERVER_LIST: 'server-list', + SERVER_DETAIL: 'server-detail', + DISABLE_SCOPE_SELECT: 'disable-scope-select', + TOOL_LIST: 'tool-list', + TOOL_DETAIL: 'tool-detail', + AUTHENTICATE: 'authenticate', // 新增:认证步骤 +} as const; +``` + +##### 2. 在 ServerDetailStep 中添加认证选项 + +```typescript +// packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx +type ServerAction = + | 'view-tools' + | 'reconnect' + | 'toggle-disable' + | 'authenticate'; // 新增 + +const actions = useMemo(() => { + const result: Array<{ label: string; value: ServerAction }> = []; + + result.push({ label: t('View Tools'), value: 'view-tools' }); + + if (!server.isDisabled && server.status === MCPServerStatus.DISCONNECTED) { + result.push({ label: t('Reconnect'), value: 'reconnect' }); + } + + // 新增:显示认证选项的场景 + const needsAuth = + server.config.oauth?.enabled || + server.status === MCPServerStatus.DISCONNECTED || + server.errorMessage?.includes('401') || + server.errorMessage?.includes('OAuth'); + + if (needsAuth) { + result.push({ + label: t('Authenticate'), + value: 'authenticate', + icon: '🔐', // 可选:添加图标增强视觉提示 + }); + } + + result.push({ + label: server.isDisabled ? t('Enable') : t('Disable'), + value: 'toggle-disable', + }); + + return result; +}, [server]); +``` + +##### 3. 在 MCPManagementDialog 中实现认证逻辑 + +```typescript +// packages/cli/src/ui/components/mcp/MCPManagementDialog.tsx +import { MCPOAuthProvider, MCPOAuthConfig } from '@qwen-code/qwen-code-core'; +import { appEvents, AppEvent } from '../../utils/events.js'; + +// 新增:处理认证 +const handleAuthenticate = useCallback(async () => { + if (!config || !selectedServer) return; + + try { + setIsLoading(true); + + // 显示开始认证提示 + context.ui.addItem( + { + type: 'info', + text: t("Starting OAuth authentication for '{{name}}'...", { + name: selectedServer.name, + }), + }, + Date.now() + ); + + // 监听并显示认证过程中的消息 + const displayListener = (message: string) => { + context.ui.addItem({ type: 'info', text: message }, Date.now()); + }; + appEvents.on(AppEvent.OauthDisplayMessage, displayListener); + + // 准备 OAuth 配置 + let oauthConfig: MCPOAuthConfig = selectedServer.config.oauth || { enabled: false }; + + // 执行认证 + const authProvider = new MCPOAuthProvider(new MCPOAuthTokenStorage()); + await authProvider.authenticate( + selectedServer.name, + oauthConfig, + selectedServer.config.httpUrl || selectedServer.config.url + ); + + // 认证成功 + context.ui.addItem( + { + type: 'success', + text: t("✓ Authentication successful for '{{name}}'", { + name: selectedServer.name, + }), + }, + Date.now() + ); + + // 移除消息监听器 + appEvents.off(AppEvent.OauthDisplayMessage, displayListener); + + // 重新加载服务器数据以更新状态 + await reloadServers(); + + // 返回上一级 + handleNavigateBack(); + } catch (error) { + debugLogger.error( + `Authentication failed for '${selectedServer.name}':`, + error + ); + context.ui.addItem( + { + type: 'error', + text: t("✗ Authentication failed: {{error}}", { + error: getErrorMessage(error), + }), + }, + Date.now() + ); + } finally { + setIsLoading(false); + } +}, [config, selectedServer, reloadServers, handleNavigateBack, context]); + +// 在 renderStepContent 中添加认证步骤的处理 +case MCP_MANAGEMENT_STEPS.AUTHENTICATE: + // 可以直接执行认证,或者显示一个确认对话框 + void handleAuthenticate(); + return {t('Authenticating...')}; +``` + +##### 4. 更新 i18n 翻译文件 + +```javascript +// packages/cli/src/i18n/locales/en.js +{ + 'Authenticate': 'Authenticate', + 'Authenticate with OAuth': 'Authenticate with OAuth', + "Starting OAuth authentication for '{{name}}'...": "Starting OAuth authentication for '{{name}}'...", + "✓ Authentication successful for '{{name}}'": "✓ Authentication successful for '{{name}}'", + "✗ Authentication failed: {{error}}": "✗ Authentication failed: {{error}}", +} +``` + +**优点**: + +- ✅ 用户体验统一,所有 MCP 管理操作在一个界面完成 +- ✅ 复用现有 OAuth 认证逻辑,开发成本低 +- ✅ 直观的视觉反馈,认证过程透明 +- ✅ 符合现代 UI/UX 设计原则 + +**缺点**: + +- ⚠️ 需要处理浏览器跳转和回调 (已有完善实现,风险低) + +#### 方案 B: 保留命令行但改进引导提示 + +如果某些场景下确实需要命令行认证 (如自动化脚本),可以: + +- 保留 `/mcp auth` 命令 +- 在 Dialog 中提供快速复制的命令模板 +- 添加"Copy Auth Command"按钮 + +但这会增加复杂性,不如方案 A 简洁。 + +--- + +## 问题 2: 【P1】一些异常状态 + +### 2.1 禁用之后还可以点击"查看工具",点进去是空的 + +#### 问题描述 + +- **现象**: MCP Server 被禁用后,仍然可以在 UI 中看到"查看工具"选项,点击进入后显示空列表 +- **期望**: 禁用后的服务器不应该显示"查看工具"选项,或者应该给出明确的提示信息 + +#### 根本原因分析 + +当前代码逻辑: + +```typescript +// packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx +const actions = useMemo(() => { + const result: Array<{ label: string; value: ServerAction }> = []; + + // 无论服务器是否禁用,都添加"查看工具"选项 + result.push({ label: t('View Tools'), value: 'view-tools' }); + + if (server.status === 'disconnected') { + result.push({ label: t('Reconnect'), value: 'reconnect' }); + } + + result.push({ + label: server.isDisabled ? t('Enable') : t('Disable'), + value: 'toggle-disable', + }); + + return result; +}, [server]); +``` + +问题在于: + +1. 没有根据 `server.isDisabled` 状态过滤操作选项 +2. 禁用服务器的工具列表获取逻辑可能存在问题 +3. 缺少用户友好的提示信息 + +#### 解决方案 + +**方案 A: 禁用时隐藏"查看工具"选项 (推荐)** + +**代码修改**: + +```typescript +// packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx +const actions = useMemo(() => { + const result: Array<{ label: string; value: ServerAction }> = []; + + // 只在服务器启用且已连接时显示"查看工具"选项 + if (!server.isDisabled && server.status === MCPServerStatus.CONNECTED) { + result.push({ + label: t('View Tools'), + value: 'view-tools', + disabled: server.toolCount === 0, // 可选:工具数量为 0 时禁用 + }); + } + + // 禁用状态下显示提示信息 + if (server.isDisabled) { + result.push({ + label: t('Enable to view tools'), + value: 'toggle-disable', + }); + } else { + if (server.status === MCPServerStatus.DISCONNECTED) { + result.push({ label: t('Reconnect'), value: 'reconnect' }); + } + + result.push({ + label: t('Disable'), + value: 'toggle-disable', + }); + } + + return result; +}, [server]); +``` + +**同时修改 ToolListStep**: + +```typescript +// packages/cli/src/ui/components/mcp/steps/ToolListStep.tsx +export const ToolListStep: React.FC = ({ + tools, + serverName, + onSelect, + onBack, +}) => { + // 添加禁用状态检查 + if (tools.length === 0) { + return ( + + + {t('No tools available for this server.')} + + {/* 添加提示:服务器可能被禁用 */} + + {t('Note: This server may be disabled. Please enable it in the server settings.')} + + + ); + } + // ... 其余代码保持不变 +}; +``` + +**方案 B: 显示友好提示并阻止导航** + +在 `MCPManagementDialog` 中添加拦截逻辑: + +```typescript +// packages/cli/src/ui/components/mcp/MCPManagementDialog.tsx +const handleViewTools = useCallback(() => { + if (!selectedServer) return; + + // 检查服务器是否禁用 + if (selectedServer.isDisabled) { + // 显示提示信息,不执行导航 + debugLogger.warn( + `Cannot view tools for disabled server '${selectedServer.name}'`, + ); + // 可选:在 UI 上显示临时消息 + return; + } + + // 检查是否有工具 + if (selectedServer.toolCount === 0) { + debugLogger.info(`No tools available for server '${selectedServer.name}'`); + // 仍然可以进入查看,但会显示空状态提示 + } + + handleNavigateToStep(MCP_MANAGEMENT_STEPS.TOOL_LIST); +}, [selectedServer, handleNavigateToStep]); +``` + +#### 推荐方案:方案 A + ToolListStep 的提示增强 + +--- + +### 2.2 禁用之后还能重新连接 + +#### 问题描述 + +- **现象**: MCP Server 被禁用后,仍然可以看到"重新连接"选项 +- **期望**: 禁用之后应该没有"重新连接"入口 +- **文档建议**: 禁用之后应该没有"重新连接"入口 + +#### 根本原因分析 + +当前代码逻辑: + +```typescript +// packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx +if (server.status === 'disconnected') { + result.push({ label: t('Reconnect'), value: 'reconnect' }); +} +``` + +问题在于: + +1. 只检查了连接状态,没有检查禁用状态 +2. 禁用的服务器不应该允许重新连接操作 +3. 逻辑上矛盾:既然禁用了就不应该尝试连接 + +#### 解决方案 + +**代码修改**: + +```typescript +// packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx +const actions = useMemo(() => { + const result: Array<{ label: string; value: ServerAction }> = []; + + // View Tools 选项 + if (!server.isDisabled && server.toolCount > 0) { + result.push({ label: t('View Tools'), value: 'view-tools' }); + } + + // Reconnect 选项:只在未禁用且断开连接时显示 + if (!server.isDisabled && server.status === MCPServerStatus.DISCONNECTED) { + result.push({ label: t('Reconnect'), value: 'reconnect' }); + } + + // Enable/Disable 选项 + result.push({ + label: server.isDisabled ? t('Enable Server') : t('Disable Server'), + value: 'toggle-disable', + }); + + return result; +}, [server]); +``` + +**同时在 ServerListStep 中添加视觉提示**: + +```typescript +// packages/cli/src/ui/components/mcp/steps/ServerListStep.tsx +{server.isDisabled && ( + + {' '} + {t('(disabled - no connection possible)')} + +)} +``` + +--- + +### 问题 3: 【P1】禁用有个选择设置的 dialog,有点费解 + +#### 问题描述 + +- **现象**: 禁用服务器时会弹出一个对话框让用户选择禁用范围 (user/workspace) +- **问题**: 这个选择让用户体验困惑,特别是当 MCP server 在项目级配置时,在用户级别禁用就有点费解 +- **文档建议**: MCP server 在哪里,就在哪里禁用(如果 MCP server 在项目级,在用户级别禁用就有点费解) + +#### 根本原因分析 + +当前实现逻辑: + +```typescript +// packages/cli/src/ui/components/mcp/MCPManagementDialog.tsx +const handleSelectDisableScope = useCallback( + async (scope: 'user' | 'workspace') => { + // 允许用户在 user 或 workspace 层面禁用服务器 + // 即使服务器配置在 workspace 层面,也允许在 user 层面禁用 + }, + [config, selectedServer, handleNavigateBack, reloadServers], +); +``` + +问题在于: + +1. 用户可以跨 scope 禁用服务器,造成配置混乱 +2. 不符合"在哪里配置就在哪里管理"的直觉 +3. 增加了不必要的复杂性 + +#### 解决方案 + +**方案 A: 根据服务器来源自动确定禁用 scope (强烈推荐)** + +**核心思路**: + +- User 级别的配置 → 只能在 User 级别禁用 +- Workspace 级别的配置 → 只能在 Workspace 级别禁用 +- Extension 级别的配置 → 不允许禁用 (只能卸载扩展) + +**代码修改**: + +```typescript +// packages/cli/src/ui/components/mcp/MCPManagementDialog.tsx + +// 修改 handleDisable 函数 +const handleDisable = useCallback(() => { + if (!selectedServer) return; + + // 如果服务器已经被禁用,直接启用 + if (selectedServer.isDisabled) { + void handleEnableServer(); + return; + } + + // Extension 提供的服务器不允许禁用 + if (selectedServer.source === 'extension') { + debugLogger.warn( + `Cannot disable extension-provided server '${selectedServer.name}'`, + ); + // 显示提示信息 + return; + } + + // 根据服务器 scope 直接禁用,不再询问 + const scope = + selectedServer.scope === 'extension' + ? SettingScope.User + : selectedServer.scope === 'workspace' + ? SettingScope.Workspace + : SettingScope.User; + + // 直接执行禁用操作 + void executeDisable(scope); +}, [selectedServer, handleEnableServer]); + +// 新增执行禁用函数 +const executeDisable = useCallback( + async (scope: SettingScope) => { + if (!config || !selectedServer) return; + + try { + setIsLoading(true); + + const settings = loadSettings(); + const scopeSettings = settings.forScope(scope).settings; + const currentExcluded = scopeSettings.mcp?.excluded || []; + + if (!currentExcluded.includes(selectedServer.name)) { + const newExcluded = [...currentExcluded, selectedServer.name]; + settings.setValue(scope, 'mcp.excluded', newExcluded); + } + + const toolRegistry = config.getToolRegistry(); + if (toolRegistry) { + await toolRegistry.disableMcpServer(selectedServer.name); + } + + await reloadServers(); + handleNavigateBack(); + } catch (error) { + debugLogger.error( + `Error disabling server '${selectedServer.name}':`, + error, + ); + } finally { + setIsLoading(false); + } + }, + [config, selectedServer, reloadServers, handleNavigateBack], +); + +// 移除 DisableScopeSelectStep 相关的代码和导航逻辑 +``` + +**同时修改 UI 提示**: + +```typescript +// packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx + + + {t('Scope:')} + + + + {t(server.scope)} + {server.source === 'extension' && ( + + {' '}({t('provided by {{name}}', { name: server.config.extensionName })}) + + )} + + + + +// 禁用按钮文本根据 scope 调整 +{server.isDisabled ? ( + {t('Enable (will remove from exclusion list)')} +) : server.source === 'extension' ? ( + {t('Cannot disable extension server')} +) : ( + {t('Disable (in {{scope}})', { scope: server.scope })} +)} +``` + +**方案 B: 保留选择但改进 UX** + +如果确实需要支持跨 scope 禁用 (考虑到某些特殊场景),至少应该: + +1. 明确显示当前服务器的配置位置 +2. 说明不同选择的影响 +3. 给出推荐选项 + +但这会增加复杂性,不如方案 A 简洁明了。 + +#### 推荐方案:方案 A + +--- + +## 实施计划 + +--- + +## 问题 6: 【P2】Extension Management - /extension manage 报错 + +### 问题描述 + +- **现象**: 使用 `/extension manage` 命令时直接报错 +- **期望**: 应该能正常打开 Extension Management Dialog + +### 根本原因分析 + +#### 可能的原因 + +1. **命令拼写错误** (最可能) + - 正确的命令是 `/extensions manage` (复数形式) + - 用户可能输入了 `/extension manage` (单数形式) +2. **ExtensionManager 未正确初始化** + + ```typescript + // packages/cli/src/ui/commands/extensionsCommand.ts#L103-108 + async function listAction(_context: CommandContext, _args: string) { + const extensionManager = context.services.config?.getExtensionManager(); + if (!(extensionManager instanceof ExtensionManager)) { + debugLogger.error( + `Cannot ${context.invocation?.name} extensions in this environment`, + ); + return; // ❌ 这里直接返回,没有给用户任何提示 + } + // ... + } + ``` + +3. **环境限制** + - 某些环境下无法加载 ExtensionManager + - 沙箱模式可能限制扩展管理功能 + +#### 当前错误处理问题 + +- 如果 `getExtensionManager()` 返回 null 或不是 ExtensionManager 实例 +- 代码只是记录 debug 日志并静默返回 +- **用户看不到任何错误提示**,只会感到困惑 + +### 解决方案 + +#### 方案 A: 改进错误提示 (强烈推荐) + +**代码修改**: + +```typescript +// packages/cli/src/ui/commands/extensionsCommand.ts +async function listAction(context: CommandContext, _args: string) { + const extensionManager = context.services.config?.getExtensionManager(); + + if (!(extensionManager instanceof ExtensionManager)) { + debugLogger.error( + `Cannot ${context.invocation?.name} extensions in this environment`, + ); + + // ✅ 添加用户友好的错误提示 + context.ui.addItem( + { + type: MessageType.ERROR, + text: t( + 'Extension management is not available in the current environment. ' + + 'This feature may not be supported in your current mode or configuration.', + ), + }, + Date.now(), + ); + return; + } + + return { + type: 'dialog' as const, + dialog: 'extensions_manage' as const, + }; +} +``` + +#### 方案 B: 检查命令拼写并给出提示 + +在命令解析层面添加提示: + +```typescript +// packages/cli/src/ui/commands/registry.ts 或相关位置 +// 当检测到用户输入 '/extension'(单数) 时,给出提示 +if (commandName === 'extension') { + context.ui.addItem( + { + type: MessageType.INFO, + text: t('Did you mean "/extensions"? (plural form)'), + }, + Date.now(), + ); +} +``` + +#### 方案 C: 同时支持单复数形式 + +为了用户体验,可以同时支持两种形式: + +```typescript +// packages/cli/src/ui/commands/extensionsCommand.ts +export const extensionsCommand: SlashCommand = { + name: 'extensions', // 主要命令 (复数) + aliases: ['extension'], // ✅ 添加别名 (单数) + get description() { + return t('Manage extensions'); + }, + kind: CommandKind.BUILT_IN, + subCommands: [ + manageExtensionsCommand, + installCommand, + exploreExtensionsCommand, + ], + action: async (context, args) => + manageExtensionsCommand.action!(context, args), +}; +``` + +**注意**: 需要检查 SlashCommand 类型定义是否支持 `aliases` 属性 + +### 推荐方案 + +**采用方案 A + 方案 C**: + +1. 改进错误提示,让用户知道发生了什么 +2. 如果可能,同时支持单复数形式 + +--- + +## 实施计划 + +### Phase 1: 修复异常状态问题 (优先级:高) + +1. **修复问题 2.1**: 禁用后可查看工具 + - 修改 `ServerDetailStep.tsx` 的操作列表逻辑 + - 修改 `ToolListStep.tsx` 添加友好提示 + - 预计工时:2 小时 + +2. **修复问题 2.2**: 禁用后可重新连接 + - 修改 `ServerDetailStep.tsx` 的 reconnect 选项条件 + - 预计工时:1 小时 + +### Phase 2: 在 Dialog 中集成 Auth 功能 (优先级:高) + +3. **修复问题 1**: MCP Dialog 集成 OAuth 认证 + - 扩展 `MCP_MANAGEMENT_STEPS` 添加认证步骤 + - 在 `ServerDetailStep` 中添加"Authenticate"选项 + - 在 `MCPManagementDialog` 中实现认证逻辑 + - 更新 i18n 翻译文件 + - 预计工时:4 小时 + +### Phase 3: 改进禁用体验 (优先级:中) + +4. **修复问题 3**: 简化禁用流程 + - 移除 `DisableScopeSelectStep` + - 实现自动 scope 判断逻辑 + - 更新 UI 提示 + - 预计工时:4 小时 + +### Phase 4: UI 细节优化 (优先级:中) + +5. **修复问题 4**: Dialog 1 细节优化 + - 移除重复的来源显示 + - 优化错误信息显示逻辑 (只在有错误时显示) + - 移除多余的空格 + - 优化布局紧凑度 + - 预计工时:3 小时 + +6. **修复问题 5**: Dialog 2 细节优化 + - 统一来源颜色与其他部分一致 + - 添加功能说明 tooltip + - 统一选中色为 theme.text.accent + - 优化工具标注文案 (如"destructive, open-world") + - 移除不必要的序号 + - 预计工时:3 小时 + +### Phase 5: Extension Management 修复 (优先级:低) + +7. **修复问题 6**: Extension 命令报错 + - 改进错误提示 (方案 A) + - 考虑支持单复数形式 (方案 C) + - 预计工时:2 小时 + +### Phase 6: 测试与验证 (优先级:高) + +8. **回归测试** + - 更新所有相关测试用例 + - 手动测试各个场景 + - 确保没有破坏性变更 + - 预计工时:4 小时 + +**总预计工时**: 约 23 小时 (约 3 个工作日) + +--- + +## 影响评估 + +### 兼容性影响 + +- **Breaking Changes**: 无 +- **Deprecation**: 无 +- **新功能**: MCP Dialog 集成 OAuth 认证功能 + +### 需要更新的文档 + +1. `docs/developers/tools/mcp-server.md` - 更新 MCP 管理对话框使用说明 +2. `docs/users/features/mcp-servers.md` - 更新用户指南 +3. `docs/users/features/extensions.md` - 更新扩展管理说明 +4. 内联帮助文本和 i18n 文件 + +### 需要更新的测试 + +1. `packages/cli/src/ui/commands/mcpCommand.test.ts` +2. `packages/cli/src/ui/components/mcp/MCPManagementDialog.test.tsx` +3. `packages/cli/src/ui/components/mcp/steps/ServerDetailStep.test.tsx` +4. `packages/cli/src/ui/commands/extensionsCommand.test.ts` +5. `packages/cli/src/ui/components/extensions/ExtensionsManagerDialog.test.tsx` + +--- + +## 验收标准 + +### 问题 1 验收标准 + +- [ ] MCP Management Dialog 中显示"Authenticate"选项 (针对需要认证的服务器) +- [ ] 点击认证后能正确启动 OAuth 流程 +- [ ] 认证过程中显示友好的提示信息 +- [ ] 认证成功后自动刷新服务器状态 +- [ ] 认证失败时显示明确的错误信息 +- [ ] 保留 `/mcp auth` 命令作为备选方案 (可选) + +### 问题 2.1 验收标准 + +- [ ] 禁用的服务器不显示"查看工具"选项,或显示友好提示 +- [ ] 工具列表为空时,明确提示原因 +- [ ] 用户不会看到空的工具列表页面 + +### 问题 2.2 验收标准 + +- [ ] 禁用的服务器不显示"重新连接"选项 +- [ ] UI 逻辑自洽,不会出现矛盾的操作选项 +- [ ] 禁用状态下只能看到"启用"选项 + +### 问题 3 验收标准 + +- [ ] 禁用操作一键完成,无需选择 scope +- [ ] 禁用范围自动匹配配置范围 +- [ ] UI 明确显示服务器的配置位置 +- [ ] 用户体验流畅,无困惑点 + +### 问题 4 验收标准 (Dialog 1 细节优化) + +- [ ] 移除重复的来源显示 +- [ ] 只在有错误时显示"运行 qwen --debug..."提示 +- [ ] 没有错误时不显示多余的空格 +- [ ] 布局更加紧凑,接近 claude code 的视觉效果 + +### 问题 5 验收标准 (Dialog 2 细节优化) + +- [ ] 来源颜色与其他部分统一 +- [ ] 添加清晰的功能说明 +- [ ] 统一选中色为 theme.text.accent +- [ ] 工具标注文案更易懂 (如改为"Destructive, Open-world") +- [ ] 移除列表项前的序号 (1、2、3...) + +### 问题 6 验收标准 (Extension Management) + +- [ ] `/extensions manage` 命令能正常工作 +- [ ] 如果 ExtensionManager 不可用,显示明确的错误提示 +- [ ] 考虑支持 `/extension`(单数) 作为别名 (可选) +- [ ] 测试不同环境下的行为 (普通模式、沙箱模式等) + +--- + +## 技术细节补充 + +### 关键文件清单 + +``` +# MCP Management +packages/cli/src/ui/commands/mcpCommand.ts +packages/cli/src/ui/components/mcp/MCPManagementDialog.tsx +packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx +packages/cli/src/ui/components/mcp/steps/ServerListStep.tsx +packages/cli/src/ui/components/mcp/steps/ToolListStep.tsx +packages/cli/src/ui/components/mcp/types.ts +packages/core/src/tools/mcp-client-manager.ts +packages/core/src/config/config.ts + +# Extension Management +packages/cli/src/ui/commands/extensionsCommand.ts +packages/cli/src/ui/components/extensions/ExtensionsManagerDialog.tsx +packages/cli/src/ui/components/extensions/types.ts +packages/core/src/extension/extensionManager.ts +``` + +### 依赖关系 + +- MCP Management Dialog 依赖于 Config、ToolRegistry、PromptRegistry +- 禁用逻辑涉及 Settings 的多 scope 管理 +- 状态跟踪通过 `getMCPServerStatus` 和状态监听器实现 + +### 潜在风险点 + +1. **OAuth 认证流程**: 确保在 Dialog 中集成的认证功能不影响现有命令行认证 +2. **多 Scope 配置**: 确保自动 scope 判断不会误删其他 scope 的配置 +3. **Extension 集成**: 确保扩展提供的服务器正确处理 +4. **环境兼容性**: 确保 Extension Management 在不同环境下都能给出正确的错误提示 + +--- + +## 总结 + +本文档针对 0.12.0 版本体验反馈中提出的 **6 个问题** (3 个 P1 + 3 个 P2) 进行了详细分析,并提供了具体的解决方案。所有修改都遵循以下原则: + +1. **用户体验优先**: 简化操作流程,减少困惑 +2. **逻辑一致性**: 确保 UI 状态和行为逻辑自洽 +3. **向后兼容**: 避免破坏性变更 +4. **代码质量**: 简化代码结构,提高可维护性 +5. **错误友好**: 提供清晰、有帮助的错误信息 + +建议按优先级分阶段实施,确保每个问题都得到妥善解决。 diff --git a/packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx b/packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx index 07b8da439..50445d7b7 100644 --- a/packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx +++ b/packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { useState } from 'react'; +import { useState, useMemo } from 'react'; import { Box, Text } from 'ink'; import { theme } from '../../../semantic-colors.js'; import { useKeypress } from '../../../hooks/useKeypress.js'; @@ -34,29 +34,45 @@ export const ServerDetailStep: React.FC = ({ const statusColor = server ? getStatusColor(server.status) : 'gray'; - const actions = [ - { - key: 'view-tools', - get label() { - return t('View tools'); - }, - value: 'view-tools' as const, - }, - { - key: 'reconnect', - get label() { - return t('Reconnect'); - }, - value: 'reconnect' as const, - }, - { + // 根据服务器状态动态生成可用操作 + const actions = useMemo(() => { + const result: Array<{ + key: string; + label: string; + value: ServerAction; + }> = []; + + if (!server) { + return result; + } + + // 只在服务器未禁用且有工具时显示"查看工具"选项 + if (!server.isDisabled && (server.toolCount ?? 0) > 0) { + result.push({ + key: 'view-tools', + label: t('View tools'), + value: 'view-tools', + }); + } + + // 只在服务器未禁用且已断开连接时显示"重新连接"选项 + if (!server.isDisabled && server.status === 'disconnected') { + result.push({ + key: 'reconnect', + label: t('Reconnect'), + value: 'reconnect', + }); + } + + // 始终显示启用/禁用选项 + result.push({ key: 'toggle-disable', - get label() { - return server?.isDisabled ? t('Enable') : t('Disable'); - }, - value: 'toggle-disable' as const, - }, - ]; + label: server?.isDisabled ? t('Enable') : t('Disable'), + value: 'toggle-disable', + }); + + return result; + }, [server]); useKeypress( (key) => {