Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 38 additions & 11 deletions docs/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,19 @@ _代码修改确认_
![命令执行确认](/screenshots/spec-bash-confirm.png)
_命令执行确认_

### 4.4 计划执行确认 (Plan Confirmation) {#plan-confirmation}
### 4.4 MCP 工具确认 (MCP Tool Confirmation) {#mcp-tool-confirmation}

当 AI 调用通过 MCP(Model Context Protocol)连接的外部服务工具时,确认框会展示该工具的完整参数,让用户了解即将传递给 MCP 服务器的数据。

**主要特性:**
- **参数展示**:以格式化的 JSON 显示 MCP 工具的全部输入参数,方便用户审核。
- **持久化规则**:支持为特定 MCP 工具设置"不再询问"规则(按工具名匹配,如 `mcp__wave_requirements__create_requirement`)。
- **反馈机制**:支持用户拒绝并提供反馈,指导 AI 调整参数后重新调用。

![MCP 工具确认](/screenshots/spec-mcp-tool-confirm.png)
_MCP 工具确认_

### 4.5 计划执行确认 (Plan Confirmation) {#plan-confirmation}

当 AI 制定了详细的执行计划并准备退出计划模式时,会向用户展示计划内容并请求确认。用户可以选择批准执行、自动接受后续修改,或提供反馈意见。

Expand All @@ -276,7 +288,7 @@ _命令执行确认_
![计划执行确认](/screenshots/spec-plan-confirm.png)
_计划执行确认_

### 4.5 进入计划模式确认 (Enter Plan Mode Confirmation) {#enter-plan-mode}
### 4.6 进入计划模式确认 (Enter Plan Mode Confirmation) {#enter-plan-mode}

当 AI 判断当前任务较为复杂时,会主动请求用户确认是否进入计划模式。与计划执行确认不同,进入计划模式确认仅提供两个选项:批准进入计划模式,或拒绝并直接开始实现。

Expand All @@ -292,7 +304,7 @@ _计划执行确认_
![进入计划模式确认](/screenshots/spec-enter-plan-mode.png)
_进入计划模式确认_

### 4.6 错误消息展示 {#error-message-display}
### 4.7 错误消息展示 {#error-message-display}

当工具执行出错或 AI 返回错误信息时,Wave 会以醒目的方式展示错误内容。为了防止过长的错误消息占据过多屏幕空间,错误消息区域设置了最大高度,并支持内部滚动。

Expand Down Expand Up @@ -376,13 +388,25 @@ _Skill 系统_

### 6.3 MCP 协议集成 {#mcp-integration}

支持 Model Context Protocol (MCP),允许 AI 连接到外部的 MCP 服务器,从而获取更多的上下文信息或调用外部工具。
支持 Model Context Protocol (MCP),允许 AI 连接到外部的 MCP 服务器,从而获取更多的上下文信息或调用外部工具。用户可通过配置弹窗中的 **MCP 服务器** 标签页查看和管理所有已配置的 MCP 服务器。

**提示:**用户可以通过内置的 `/settings` skill 来管理 MCP 配置,例如输入 `/settings 增加mcp:xxx` 即可快速添加新的 MCP 服务。
**主要特性:**

- **服务器列表**:展示所有通过 `.mcp.json` 配置的 MCP 服务器,包括名称、连接状态(○ 未连接 / ● 已连接 / ⟳ 连接中 / ✗ 错误)、工具数量和连接命令/URL。
- **连接/断开控制**:支持一键连接或断开 MCP 服务器,连接状态实时刷新。
- **错误信息展示**:连接失败的服务器会显示具体错误原因。
- **空状态引导**:未配置任何服务器时,提示用户创建 `.mcp.json` 文件。
- **配置方式**:在项目根目录创建 `.mcp.json` 文件,支持 stdio(本地进程)和 SSE/HTTP(远程服务)两种连接方式。
- **工具确认**:AI 调用 MCP 工具时,确认框会展示格式化的 JSON 参数,用户可审核后再批准(详见 [第 4.4 节 MCP 工具确认](#mcp-tool-confirmation))。

**提示:**用户也可以通过内置的 `/settings` skill 来管理 MCP 配置,例如输入 `/settings 增加mcp:xxx` 即可快速添加新的 MCP 服务。

![MCP 集成](/screenshots/spec-mcp.png)
_MCP 集成_

![MCP 服务器管理](/screenshots/spec-mcp-server-tab.png)
_MCP 服务器管理(配置弹窗中的 MCP 标签页)_

### 6.4 内置 Skills {#builtin-skills}

#### settings — 配置管理 {#skill-settings}
Expand Down Expand Up @@ -590,15 +614,18 @@ Wave 在后台自动维护项目记忆,帮助 AI 持续了解项目演变:

### 10.1 配置设置 {#configuration-settings}

用户可以自定义 AI 模型、API Key、Base URL 等关键参数,以适配不同的 AI 服务提供商。配置界面中的表单字段仅显示用户手动输入的值,不会被环境变量填充;但如果设置了相应的环境变量(如 `WAVE_BASE_URL`),其值会作为 placeholder 提示显示在输入框中。服务端链接字段默认 placeholder 为"请联系管理员获取"。
用户可以自定义 AI 模型、API Key、Base URL 等关键参数,以适配不同的 AI 服务提供商。配置界面包含三个标签页:**常规设置**、**插件** 和 **MCP 服务器**。配置界面中的表单字段仅显示用户手动输入的值,不会被环境变量填充;但如果设置了相应的环境变量(如 `WAVE_BASE_URL`),其值会作为 placeholder 提示显示在输入框中。服务端链接字段默认 placeholder 为"请联系管理员获取"。

**主要特性:**

- **服务端链接**:配置 Wave AI 服务端地址,用于 SSO 认证。支持环境变量 `WAVE_SERVER_URL` 作为 fallback,默认 placeholder 提示"请联系管理员获取"。
- **Model / Fast Model**:SSO 模式下也可配置主模型和快速模型名称,支持环境变量 `WAVE_MODEL` / `WAVE_FAST_MODEL` 作为 placeholder 提示。
- **SSO 登录/登出**:当配置了服务端链接后,用户可通过 SSO 认证进行登录,无需手动配置 API Key。登录后所有 API 请求自动通过 Wave AI 代理路由。
- **浏览器登录**:点击"SSO 登录"后自动打开浏览器,用户在 Wave AI 登录页完成认证(支持 SSO 企业身份提供商或账号密码登录),授权码通过 localhost 回调自动交换为 JWT 并保存。VS Code Remote SSH 环境会自动转发端口,远程服务器体验与本地一致。
- **登录状态显示**:已认证时显示用户邮箱/ID 和登出按钮;登出后自动恢复为直接 LLM API 模式。
- **常规设置**:配置 AI 模型、API Key、Base URL、Headers、语言等。
- **服务端链接**:配置 Wave AI 服务端地址,用于 SSO 认证。支持环境变量 `WAVE_SERVER_URL` 作为 fallback,默认 placeholder 提示"请联系管理员获取"。
- **Model / Fast Model**:SSO 模式下也可配置主模型和快速模型名称,支持环境变量 `WAVE_MODEL` / `WAVE_FAST_MODEL` 作为 placeholder 提示。
- **SSO 登录/登出**:当配置了服务端链接后,用户可通过 SSO 认证进行登录,无需手动配置 API Key。登录后所有 API 请求自动通过 Wave AI 代理路由。
- **浏览器登录**:点击"SSO 登录"后自动打开浏览器,用户在 Wave AI 登录页完成认证(支持 SSO 企业身份提供商或账号密码登录),授权码通过 localhost 回调自动交换为 JWT 并保存。VS Code Remote SSH 环境会自动转发端口,远程服务器体验与本地一致。
- **登录状态显示**:已认证时显示用户邮箱/ID 和登出按钮;登出后自动恢复为直接 LLM API 模式。
- **插件**:管理插件的安装、更新、卸载和插件市场(详见 [第 11 章 插件系统](#plugin-system))。
- **MCP 服务器**:查看和管理 MCP 服务器连接状态(详见 [第 6.3 节 MCP 协议集成](#mcp-integration))。

![配置设置](/screenshots/spec-configuration.png)
_配置设置_
Expand Down
193 changes: 193 additions & 0 deletions e2e/demo/mcp-server-tab.demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { test, expect } from '../utils/webviewTestHarness.js';

test.describe('MCP Server Tab Demo', () => {
test('should show MCP server tab with configured servers', async ({ webviewPage }) => {
// 1. Open the configuration dialog
await webviewPage.evaluate(() => {
(window as any).simulateExtensionMessage({
command: 'showConfiguration',
configurationData: {
apiKey: 'test-key',
baseURL: 'https://api.example.com',
model: 'test-model',
fastModel: 'fast-model',
language: 'Chinese'
}
});
});

// Verify dialog is visible
await expect(webviewPage.getByText('配置设置', { exact: true })).toBeVisible();

// 2. Click on "MCP 服务器" tab
await webviewPage.getByText('MCP 服务器', { exact: true }).click();

// 3. Simulate receiving MCP servers list
await webviewPage.evaluate(() => {
window.postMessage({
command: 'mcpServersResponse',
servers: [
{
name: 'sqlite',
config: {
command: 'uvx',
args: ['mcp-server-sqlite', '--db-path', '/path/to/db.db']
},
status: 'connected',
toolCount: 5,
capabilities: ['tools'],
lastConnected: Date.now() - 60000
},
{
name: 'github',
config: {
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-github'],
env: {
GITHUB_PERSONAL_ACCESS_TOKEN: 'your-token-here'
}
},
status: 'disconnected',
toolCount: 0,
capabilities: []
},
{
name: 'remote-server',
config: {
url: 'https://mcp-server.example.com/sse'
},
status: 'error',
toolCount: 0,
error: 'Connection refused',
capabilities: []
},
{
name: 'fetch',
config: {
command: 'npx',
args: ['-y', '@mcp/server-fetch']
},
status: 'connecting',
toolCount: 0,
capabilities: ['tools']
}
]
}, '*');
});

// Verify all four servers are visible
await expect(webviewPage.getByText('sqlite', { exact: true })).toBeVisible();
await expect(webviewPage.getByText('github', { exact: true })).toBeVisible();
await expect(webviewPage.getByText('remote-server', { exact: true })).toBeVisible();
await expect(webviewPage.getByText('fetch', { exact: true })).toBeVisible();

// Verify connected server shows tool count
await expect(webviewPage.getByText('5 tools')).toBeVisible();

// Verify error server shows error message
await expect(webviewPage.getByText('Connection refused')).toBeVisible();

// Verify connect button for disconnected server (two servers are not connected)
await expect(webviewPage.getByRole('button', { name: '连接' }).first()).toBeVisible();

// Verify disconnect button for connected server
await expect(webviewPage.getByRole('button', { name: '断开' })).toBeVisible();

await webviewPage.screenshot({ path: 'docs/public/screenshots/spec-mcp-server-tab.png' });
});

test('should show empty state when no MCP servers configured', async ({ webviewPage }) => {
// 1. Open the configuration dialog
await webviewPage.evaluate(() => {
(window as any).simulateExtensionMessage({
command: 'showConfiguration',
configurationData: {
apiKey: 'test-key',
baseURL: 'https://api.example.com',
model: 'test-model',
fastModel: 'fast-model',
language: 'Chinese'
}
});
});

// 2. Click on "MCP 服务器" tab
await webviewPage.getByText('MCP 服务器', { exact: true }).click();

// 3. Simulate empty servers list
await webviewPage.evaluate(() => {
window.postMessage({
command: 'mcpServersResponse',
servers: []
}, '*');
});

// Verify empty state message
await expect(webviewPage.getByText('未配置 MCP 服务器')).toBeVisible();
await expect(webviewPage.locator('code', { hasText: '.mcp.json' })).toBeVisible();

await webviewPage.screenshot({ path: 'docs/public/screenshots/spec-mcp-server-empty.png' });
});

test('should handle connect/disconnect actions', async ({ webviewPage }) => {
// 1. Open the configuration dialog
await webviewPage.evaluate(() => {
(window as any).simulateExtensionMessage({
command: 'showConfiguration',
configurationData: {
apiKey: 'test-key',
baseURL: 'https://api.example.com',
model: 'test-model',
fastModel: 'fast-model',
language: 'Chinese'
}
});
});

// 2. Click on "MCP 服务器" tab
await webviewPage.getByText('MCP 服务器', { exact: true }).click();

// 3. Simulate servers list
await webviewPage.evaluate(() => {
window.postMessage({
command: 'mcpServersResponse',
servers: [
{
name: 'sqlite',
config: { command: 'uvx', args: ['mcp-server-sqlite'] },
status: 'disconnected',
toolCount: 0,
capabilities: []
}
]
}, '*');
});

// 4. Click connect button
await webviewPage.getByRole('button', { name: '连接' }).click();

// Verify backend receives the connect command
// (In demo mode, we verify the UI interaction rather than backend response)

// 5. Simulate the backend response after connection
await webviewPage.evaluate(() => {
window.postMessage({
command: 'mcpServersResponse',
servers: [
{
name: 'sqlite',
config: { command: 'uvx', args: ['mcp-server-sqlite'] },
status: 'connected',
toolCount: 5,
capabilities: ['tools'],
lastConnected: Date.now()
}
]
}, '*');
});

// Verify status changed to connected and disconnect button appears
await expect(webviewPage.getByRole('button', { name: '断开' })).toBeVisible();
await expect(webviewPage.getByText('5 tools')).toBeVisible();
});
});
26 changes: 23 additions & 3 deletions e2e/demo/product-spec-confirmations.demo.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { test, expect } from '../utils/webviewTestHarness.js';
import { MessageInjector } from '../utils/messageInjector.js';
import { UIStateVerifier } from '../utils/uiStateVerifier.js';
import {
EDIT_TOOL_NAME,
import {
EDIT_TOOL_NAME,
BASH_TOOL_NAME,
ASK_USER_QUESTION_TOOL_NAME,
ENTER_PLAN_MODE_TOOL_NAME,
Expand Down Expand Up @@ -201,7 +201,27 @@ test.describe('Product Specification Screenshots - Confirmations', () => {
await webviewPage.click('.confirmation-close-btn');
await editConfirmDialog.waitFor({ state: 'detached' });

// 17. Bash运行确认对话框 - 只显示确认对话框组件
// 17. MCP 工具确认对话框
await injector.simulateExtensionMessage('showConfirmation', {
confirmationId: 'mcp-confirm-001',
confirmationType: 'MCP 工具确认',
toolName: 'mcp__wave_requirements__create_requirement',
toolInput: {
title: '前端改动',
description: '在需求管理界面添加列表页',
priority: 'high',
tags: ['frontend', 'ui']
}
});
const mcpConfirmDialog = webviewPage.locator('.confirmation-dialog');
await mcpConfirmDialog.waitFor({ state: 'visible' });
await mcpConfirmDialog.screenshot({ path: 'docs/public/screenshots/spec-mcp-tool-confirm.png' });

// 关闭当前确认对话框
await webviewPage.click('.confirmation-close-btn');
await mcpConfirmDialog.waitFor({ state: 'detached' });

// 18. Bash运行确认对话框 - 只显示确认对话框组件
await injector.simulateExtensionMessage('showConfirmation', {
confirmationId: 'bash-confirm-001',
confirmationType: 'Bash 命令执行确认',
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "wave-vscode-chat",
"displayName": "Wave 代码智聊",
"description": "Wave code for VS Code",
"version": "0.4.7",
"version": "0.4.9",
"publisher": "wave-code",
"icon": "LOGO.png",
"repository": {
Expand Down
3 changes: 3 additions & 0 deletions src/chatProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ export class ChatProvider implements vscode.WebviewViewProvider {
},
onError: (error) => {
this.sendErrorToView(error, viewType, windowId);
},
onMcpServersChange: (servers) => {
this.webviewManager.postMessage({ command: 'mcpServersUpdate', servers }, viewType, windowId);
}
});
}
Expand Down
Loading
Loading