diff --git a/packages/desktop/src/App.tsx b/packages/desktop/src/App.tsx index 2762dab..04b425f 100644 --- a/packages/desktop/src/App.tsx +++ b/packages/desktop/src/App.tsx @@ -8,6 +8,7 @@ import { FileBrowser, ConfigPanel, CommandPanel, + MCPPanel, Toaster, listSessions, createSession, @@ -21,6 +22,7 @@ export function App() { const [showFileBrowser, setShowFileBrowser] = useState(false); const [showConfig, setShowConfig] = useState(false); const [showCommands, setShowCommands] = useState(false); + const [showMCP, setShowMCP] = useState(false); const [sessionTitleUpdate, setSessionTitleUpdate] = useState<{ sessionId: string; name: string } | null>(null); // 初始化:加载或创建会话 @@ -92,6 +94,7 @@ export function App() { onToggleFileBrowser={() => setShowFileBrowser(!showFileBrowser)} onOpenConfig={() => setShowConfig(true)} onOpenCommands={() => setShowCommands(true)} + onOpenMCP={() => setShowMCP(true)} /> ) : (
@@ -118,6 +121,9 @@ export function App() { {/* 命令面板 */} {showCommands && setShowCommands(false)} />} + {/* MCP 面板 */} + {showMCP && setShowMCP(false)} />} + {/* Toast 通知 */}
diff --git a/packages/desktop/src/pages/Chat.tsx b/packages/desktop/src/pages/Chat.tsx index 638efc2..c68c8a5 100644 --- a/packages/desktop/src/pages/Chat.tsx +++ b/packages/desktop/src/pages/Chat.tsx @@ -3,7 +3,7 @@ */ import { useEffect, useRef } from 'react'; -import { WifiOff, MessageSquare, Settings, FolderOpen, Terminal } from 'lucide-react'; +import { WifiOff, MessageSquare, Settings, FolderOpen, Terminal, Plug } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { useChat, @@ -21,6 +21,7 @@ interface ChatPageProps { onToggleFileBrowser?: () => void; onOpenConfig?: () => void; onOpenCommands?: () => void; + onOpenMCP?: () => void; } export function ChatPage({ @@ -30,6 +31,7 @@ export function ChatPage({ onToggleFileBrowser, onOpenConfig, onOpenCommands, + onOpenMCP, }: ChatPageProps) { const { messages, @@ -123,8 +125,21 @@ export function ChatPage({ {/* 工具栏按钮 */} - {(onOpenConfig || onToggleFileBrowser || onOpenCommands) && ( + {(onOpenConfig || onToggleFileBrowser || onOpenCommands || onOpenMCP) && (
+ {/* MCP 按钮 */} + {onOpenMCP && ( + + + + )} + {/* 命令按钮 */} {onOpenCommands && ( MCPManager; + loadMCPConfig: (workdir: string) => Promise; +} + +interface MCPManager { + initialize(config: MCPConfig): Promise; + shutdown(): Promise; + reconnect(serverName: string): Promise; + setServerEnabled(serverName: string, enabled: boolean): Promise; + getServerStatuses(): MCPServerStatus[]; + getServerStatus(name: string): MCPServerStatus | undefined; + getTools(): MCPTool[]; + getTool(name: string): MCPTool | undefined; + isInitialized(): boolean; +} + +interface MCPConfig { + mcp?: Record; + tools?: Record; +} + +interface MCPServerConfig { + type: 'local' | 'remote'; + command?: string[]; + url?: string; + env?: Record; + cwd?: string; + enabled?: boolean; + timeout?: number; +} + +interface MCPServerStatus { + name: string; + type: 'local' | 'remote'; + status: 'connected' | 'connecting' | 'disconnected' | 'disabled' | 'error'; + toolCount: number; + error?: string; + lastConnected?: Date; +} + +interface MCPTool { + server: string; + name: string; + originalName: string; + description: string; + inputSchema: Record; +} + +export const mcpRouter = new Hono(); + +// Core 模块缓存 +let mcpModule: MCPModule | null = null; +let currentConfig: MCPConfig | null = null; + +/** + * 初始化 MCP 模块 + */ +async function initMCPModule(): Promise { + if (mcpModule) return mcpModule; + + try { + const corePath = '@ai-assistant/core'; + const core = (await import(corePath)) as Record; + + if ( + typeof core.getMCPManager !== 'function' || + typeof core.loadMCPConfig !== 'function' + ) { + console.warn('[MCP] Core module missing MCP exports'); + return null; + } + + mcpModule = { + getMCPManager: core.getMCPManager as () => MCPManager, + loadMCPConfig: core.loadMCPConfig as (workdir: string) => Promise, + }; + + // 初始化 MCP Manager + const config = getConfig(); + currentConfig = await mcpModule.loadMCPConfig(config.workdir); + const manager = mcpModule.getMCPManager(); + + if (!manager.isInitialized() && currentConfig.mcp) { + await manager.initialize(currentConfig); + } + + console.log('[MCP] MCP module initialized'); + return mcpModule; + } catch (error) { + console.warn('[MCP] Failed to load MCP module:', error); + return null; + } +} + +/** + * GET /mcp/servers - 获取所有服务器状态 + */ +mcpRouter.get('/servers', async (c) => { + const module = await initMCPModule(); + + if (!module) { + return c.json( + { + success: false, + error: 'MCP module not available', + }, + 503 + ); + } + + const manager = module.getMCPManager(); + const statuses = manager.getServerStatuses(); + + // 添加配置信息 + const serversWithConfig = statuses.map((status) => { + const serverConfig = currentConfig?.mcp?.[status.name]; + return { + ...status, + config: serverConfig + ? { + type: serverConfig.type, + command: serverConfig.type === 'local' ? serverConfig.command : undefined, + url: serverConfig.type === 'remote' ? serverConfig.url : undefined, + timeout: serverConfig.timeout, + } + : undefined, + }; + }); + + return c.json({ + success: true, + data: serversWithConfig, + }); +}); + +/** + * GET /mcp/servers/:name - 获取单个服务器详情 + */ +mcpRouter.get('/servers/:name', async (c) => { + const name = c.req.param('name'); + const module = await initMCPModule(); + + if (!module) { + return c.json( + { + success: false, + error: 'MCP module not available', + }, + 503 + ); + } + + const manager = module.getMCPManager(); + const status = manager.getServerStatus(name); + + if (!status) { + return c.json( + { + success: false, + error: `Server not found: ${name}`, + }, + 404 + ); + } + + const serverConfig = currentConfig?.mcp?.[name]; + const tools = manager + .getTools() + .filter((tool) => tool.server === name); + + return c.json({ + success: true, + data: { + ...status, + config: serverConfig + ? { + type: serverConfig.type, + command: serverConfig.type === 'local' ? serverConfig.command : undefined, + url: serverConfig.type === 'remote' ? serverConfig.url : undefined, + timeout: serverConfig.timeout, + cwd: serverConfig.type === 'local' ? serverConfig.cwd : undefined, + } + : undefined, + tools: tools.map((t) => ({ + name: t.name, + originalName: t.originalName, + description: t.description, + })), + }, + }); +}); + +/** + * POST /mcp/servers/:name/connect - 连接服务器 + */ +mcpRouter.post('/servers/:name/connect', async (c) => { + const name = c.req.param('name'); + const module = await initMCPModule(); + + if (!module) { + return c.json( + { + success: false, + error: 'MCP module not available', + }, + 503 + ); + } + + const manager = module.getMCPManager(); + + try { + await manager.reconnect(name); + + return c.json({ + success: true, + data: { + message: `Server ${name} connected`, + status: manager.getServerStatus(name), + }, + }); + } catch (error) { + return c.json( + { + success: false, + error: error instanceof Error ? error.message : 'Connection failed', + }, + 500 + ); + } +}); + +/** + * POST /mcp/servers/:name/disconnect - 断开服务器 + */ +mcpRouter.post('/servers/:name/disconnect', async (c) => { + const name = c.req.param('name'); + const module = await initMCPModule(); + + if (!module) { + return c.json( + { + success: false, + error: 'MCP module not available', + }, + 503 + ); + } + + const manager = module.getMCPManager(); + + try { + // 通过禁用来断开连接 + await manager.setServerEnabled(name, false); + + return c.json({ + success: true, + data: { + message: `Server ${name} disconnected`, + status: manager.getServerStatus(name), + }, + }); + } catch (error) { + return c.json( + { + success: false, + error: error instanceof Error ? error.message : 'Disconnect failed', + }, + 500 + ); + } +}); + +/** + * POST /mcp/servers/:name/enable - 启用服务器 + */ +mcpRouter.post('/servers/:name/enable', async (c) => { + const name = c.req.param('name'); + const module = await initMCPModule(); + + if (!module) { + return c.json( + { + success: false, + error: 'MCP module not available', + }, + 503 + ); + } + + const manager = module.getMCPManager(); + + try { + await manager.setServerEnabled(name, true); + + return c.json({ + success: true, + data: { + message: `Server ${name} enabled`, + status: manager.getServerStatus(name), + }, + }); + } catch (error) { + return c.json( + { + success: false, + error: error instanceof Error ? error.message : 'Enable failed', + }, + 500 + ); + } +}); + +/** + * POST /mcp/servers/:name/disable - 禁用服务器 + */ +mcpRouter.post('/servers/:name/disable', async (c) => { + const name = c.req.param('name'); + const module = await initMCPModule(); + + if (!module) { + return c.json( + { + success: false, + error: 'MCP module not available', + }, + 503 + ); + } + + const manager = module.getMCPManager(); + + try { + await manager.setServerEnabled(name, false); + + return c.json({ + success: true, + data: { + message: `Server ${name} disabled`, + status: manager.getServerStatus(name), + }, + }); + } catch (error) { + return c.json( + { + success: false, + error: error instanceof Error ? error.message : 'Disable failed', + }, + 500 + ); + } +}); + +/** + * GET /mcp/tools - 获取所有 MCP 工具 + */ +mcpRouter.get('/tools', async (c) => { + const module = await initMCPModule(); + + if (!module) { + return c.json( + { + success: false, + error: 'MCP module not available', + }, + 503 + ); + } + + const manager = module.getMCPManager(); + const tools = manager.getTools(); + + return c.json({ + success: true, + data: tools.map((tool) => ({ + name: tool.name, + server: tool.server, + originalName: tool.originalName, + description: tool.description, + inputSchema: tool.inputSchema, + })), + }); +}); + +/** + * GET /mcp/tools/:name - 获取单个工具详情 + */ +mcpRouter.get('/tools/:name', async (c) => { + const name = c.req.param('name'); + const module = await initMCPModule(); + + if (!module) { + return c.json( + { + success: false, + error: 'MCP module not available', + }, + 503 + ); + } + + const manager = module.getMCPManager(); + const tool = manager.getTool(name); + + if (!tool) { + return c.json( + { + success: false, + error: `Tool not found: ${name}`, + }, + 404 + ); + } + + return c.json({ + success: true, + data: { + name: tool.name, + server: tool.server, + originalName: tool.originalName, + description: tool.description, + inputSchema: tool.inputSchema, + }, + }); +}); + +/** + * GET /mcp/config - 获取 MCP 配置 + */ +mcpRouter.get('/config', async (c) => { + const module = await initMCPModule(); + + if (!module) { + return c.json( + { + success: false, + error: 'MCP module not available', + }, + 503 + ); + } + + // 返回配置(隐藏敏感信息) + const safeConfig: MCPConfig = { + mcp: {}, + tools: currentConfig?.tools, + }; + + if (currentConfig?.mcp) { + for (const [name, config] of Object.entries(currentConfig.mcp)) { + safeConfig.mcp![name] = { + type: config.type, + command: config.type === 'local' ? config.command : undefined, + url: config.type === 'remote' ? config.url : undefined, + enabled: config.enabled, + timeout: config.timeout, + }; + } + } + + return c.json({ + success: true, + data: safeConfig, + }); +}); diff --git a/packages/ui/src/api/client.ts b/packages/ui/src/api/client.ts index 8a978fb..5f7f262 100644 --- a/packages/ui/src/api/client.ts +++ b/packages/ui/src/api/client.ts @@ -17,6 +17,9 @@ import type { CreateCommandInput, UpdateCommandInput, CommandContent, + MCPServerStatus, + MCPToolInfo, + MCPConfig, } from './types.js'; // Re-export types @@ -37,6 +40,11 @@ export type { CreateCommandInput, UpdateCommandInput, CommandContent, + MCPServerStatus, + MCPServerStatusType, + MCPToolInfo, + MCPConfig, + MCPServerConfigInfo, } from './types.js'; // API Configuration @@ -235,3 +243,104 @@ export async function deleteCommand( ): Promise<{ success: boolean; data?: { name: string; path: string }; error?: string }> { return request('DELETE', `/commands/${encodeURIComponent(name)}`); } + +// ============ MCP API ============ + +/** + * 获取所有 MCP 服务器状态 + */ +export async function listMCPServers(): Promise<{ + success: boolean; + data: MCPServerStatus[]; + error?: string; +}> { + return request('GET', '/mcp/servers'); +} + +/** + * 获取单个 MCP 服务器详情 + */ +export async function getMCPServer(name: string): Promise<{ + success: boolean; + data?: MCPServerStatus; + error?: string; +}> { + return request('GET', `/mcp/servers/${encodeURIComponent(name)}`); +} + +/** + * 连接 MCP 服务器 + */ +export async function connectMCPServer(name: string): Promise<{ + success: boolean; + data?: { message: string; status: MCPServerStatus }; + error?: string; +}> { + return request('POST', `/mcp/servers/${encodeURIComponent(name)}/connect`); +} + +/** + * 断开 MCP 服务器 + */ +export async function disconnectMCPServer(name: string): Promise<{ + success: boolean; + data?: { message: string; status: MCPServerStatus }; + error?: string; +}> { + return request('POST', `/mcp/servers/${encodeURIComponent(name)}/disconnect`); +} + +/** + * 启用 MCP 服务器 + */ +export async function enableMCPServer(name: string): Promise<{ + success: boolean; + data?: { message: string; status: MCPServerStatus }; + error?: string; +}> { + return request('POST', `/mcp/servers/${encodeURIComponent(name)}/enable`); +} + +/** + * 禁用 MCP 服务器 + */ +export async function disableMCPServer(name: string): Promise<{ + success: boolean; + data?: { message: string; status: MCPServerStatus }; + error?: string; +}> { + return request('POST', `/mcp/servers/${encodeURIComponent(name)}/disable`); +} + +/** + * 获取所有 MCP 工具 + */ +export async function listMCPTools(): Promise<{ + success: boolean; + data: MCPToolInfo[]; + error?: string; +}> { + return request('GET', '/mcp/tools'); +} + +/** + * 获取单个 MCP 工具详情 + */ +export async function getMCPTool(name: string): Promise<{ + success: boolean; + data?: MCPToolInfo; + error?: string; +}> { + return request('GET', `/mcp/tools/${encodeURIComponent(name)}`); +} + +/** + * 获取 MCP 配置 + */ +export async function getMCPConfig(): Promise<{ + success: boolean; + data?: MCPConfig; + error?: string; +}> { + return request('GET', '/mcp/config'); +} diff --git a/packages/ui/src/api/types.ts b/packages/ui/src/api/types.ts index 1f8d643..8b34f0a 100644 --- a/packages/ui/src/api/types.ts +++ b/packages/ui/src/api/types.ts @@ -171,3 +171,68 @@ export interface CommandContent { source: string; sourcePath?: string; } + +// ============ MCP 相关 ============ + +/** MCP 服务器状态类型 */ +export type MCPServerStatusType = + | 'connected' + | 'connecting' + | 'disconnected' + | 'disabled' + | 'error'; + +/** MCP 服务器状态 */ +export interface MCPServerStatus { + /** 服务器名称 */ + name: string; + /** 服务器类型 */ + type: 'local' | 'remote'; + /** 当前状态 */ + status: MCPServerStatusType; + /** 工具数量 */ + toolCount: number; + /** 错误信息 */ + error?: string; + /** 配置信息 */ + config?: { + type: 'local' | 'remote'; + command?: string[]; + url?: string; + timeout?: number; + cwd?: string; + }; + /** 工具列表(仅详情接口返回) */ + tools?: MCPToolInfo[]; +} + +/** MCP 工具信息 */ +export interface MCPToolInfo { + /** 完整工具名: {server}-{originalName} */ + name: string; + /** 来源服务器名称 */ + server: string; + /** MCP 服务器中的原始名称 */ + originalName: string; + /** 工具描述 */ + description: string; + /** 输入参数 JSON Schema */ + inputSchema?: Record; +} + +/** MCP 配置 */ +export interface MCPConfig { + /** MCP 服务器配置 */ + mcp?: Record; + /** 工具启用/禁用配置 */ + tools?: Record; +} + +/** MCP 服务器配置信息 */ +export interface MCPServerConfigInfo { + type: 'local' | 'remote'; + command?: string[]; + url?: string; + enabled?: boolean; + timeout?: number; +} diff --git a/packages/ui/src/components/MCPPanel.tsx b/packages/ui/src/components/MCPPanel.tsx new file mode 100644 index 0000000..f4f7a8b --- /dev/null +++ b/packages/ui/src/components/MCPPanel.tsx @@ -0,0 +1,509 @@ +/** + * MCPPanel Component + * + * MCP 服务器管理面板:列出服务器状态、连接/断开、启用/禁用 + */ + +import { useState, useEffect, useCallback } from 'react'; +import { + X, + RefreshCw, + Plug, + PlugZap, + Power, + PowerOff, + ChevronDown, + ChevronRight, + Server, + Globe, + Wrench, + AlertCircle, +} from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { toast } from 'sonner'; +import { cn } from '../utils/cn'; +import { modalOverlay, modalContent, smoothTransition } from '../utils/animations'; +import { Button } from '../primitives/Button'; +import { Skeleton } from './Skeleton'; +import { + listMCPServers, + connectMCPServer, + disconnectMCPServer, + enableMCPServer, + disableMCPServer, + type MCPServerStatus, +} from '../api/client.js'; + +interface MCPPanelProps { + onClose: () => void; + /** 是否启用响应式布局 */ + responsive?: boolean; +} + +// 状态颜色映射 +function getStatusColor(status: MCPServerStatus['status']) { + switch (status) { + case 'connected': + return 'bg-green-500'; + case 'connecting': + return 'bg-yellow-500 animate-pulse'; + case 'disconnected': + return 'bg-gray-500'; + case 'disabled': + return 'bg-gray-600'; + case 'error': + return 'bg-red-500'; + default: + return 'bg-gray-500'; + } +} + +// 状态文字 +function getStatusText(status: MCPServerStatus['status']) { + switch (status) { + case 'connected': + return 'Connected'; + case 'connecting': + return 'Connecting...'; + case 'disconnected': + return 'Disconnected'; + case 'disabled': + return 'Disabled'; + case 'error': + return 'Error'; + default: + return status; + } +} + +// 类型图标 +function getTypeIcon(type: 'local' | 'remote') { + return type === 'local' ? ( + + ) : ( + + ); +} + +export function MCPPanel({ onClose, responsive = false }: MCPPanelProps) { + // 数据状态 + const [servers, setServers] = useState([]); + const [expandedServers, setExpandedServers] = useState>(new Set()); + + // UI 状态 + const [loading, setLoading] = useState(true); + const [refreshing, setRefreshing] = useState(false); + const [actionLoading, setActionLoading] = useState(null); + + // 加载服务器列表 + const loadServers = useCallback(async (showToast = false) => { + try { + const result = await listMCPServers(); + if (result.success) { + setServers(result.data); + if (showToast) { + toast.success('Servers refreshed'); + } + } else { + toast.error(result.error || 'Failed to load servers'); + } + } catch (err) { + toast.error(err instanceof Error ? err.message : 'Failed to load servers'); + } + }, []); + + // 初始加载 + useEffect(() => { + setLoading(true); + loadServers().finally(() => setLoading(false)); + }, [loadServers]); + + // 刷新 + const handleRefresh = async () => { + setRefreshing(true); + await loadServers(true); + setRefreshing(false); + }; + + // 连接服务器 + const handleConnect = async (name: string) => { + setActionLoading(name); + try { + const result = await connectMCPServer(name); + if (result.success) { + toast.success(`Server "${name}" connected`); + await loadServers(); + } else { + toast.error(result.error || 'Connection failed'); + } + } catch (err) { + toast.error(err instanceof Error ? err.message : 'Connection failed'); + } finally { + setActionLoading(null); + } + }; + + // 断开服务器 + const handleDisconnect = async (name: string) => { + setActionLoading(name); + try { + const result = await disconnectMCPServer(name); + if (result.success) { + toast.success(`Server "${name}" disconnected`); + await loadServers(); + } else { + toast.error(result.error || 'Disconnect failed'); + } + } catch (err) { + toast.error(err instanceof Error ? err.message : 'Disconnect failed'); + } finally { + setActionLoading(null); + } + }; + + // 启用服务器 + const handleEnable = async (name: string) => { + setActionLoading(name); + try { + const result = await enableMCPServer(name); + if (result.success) { + toast.success(`Server "${name}" enabled`); + await loadServers(); + } else { + toast.error(result.error || 'Enable failed'); + } + } catch (err) { + toast.error(err instanceof Error ? err.message : 'Enable failed'); + } finally { + setActionLoading(null); + } + }; + + // 禁用服务器 + const handleDisable = async (name: string) => { + setActionLoading(name); + try { + const result = await disableMCPServer(name); + if (result.success) { + toast.success(`Server "${name}" disabled`); + await loadServers(); + } else { + toast.error(result.error || 'Disable failed'); + } + } catch (err) { + toast.error(err instanceof Error ? err.message : 'Disable failed'); + } finally { + setActionLoading(null); + } + }; + + // 切换展开 + const toggleExpanded = (name: string) => { + const newExpanded = new Set(expandedServers); + if (newExpanded.has(name)) { + newExpanded.delete(name); + } else { + newExpanded.add(name); + } + setExpandedServers(newExpanded); + }; + + // 统计 + const connectedCount = servers.filter((s) => s.status === 'connected').length; + const totalToolCount = servers.reduce((sum, s) => sum + s.toolCount, 0); + + // Loading 骨架屏 + const LoadingSkeleton = () => ( +
+ {[1, 2, 3].map((i) => ( +
+ +
+ + +
+ +
+ ))} +
+ ); + + return ( + + + e.stopPropagation()} + className={cn( + 'bg-gray-800 max-h-[90vh] overflow-hidden flex flex-col', + responsive + ? 'w-full md:w-full md:max-w-2xl md:mx-4 rounded-t-2xl md:rounded-lg' + : 'rounded-lg w-full max-w-2xl mx-4' + )} + > + {/* Header */} +
+ {responsive && ( +
+ )} +
+

+ + MCP Servers +

+

+ {servers.length} servers ({connectedCount} connected, {totalToolCount} tools) +

+
+
+ + +
+
+ + {/* Server List */} +
+ {loading ? ( + + ) : servers.length === 0 ? ( +
+ +

No MCP servers configured

+

+ Configure MCP servers in your .ai-assist/config.json file +

+
+ ) : ( + + {servers.map((server) => { + const isExpanded = expandedServers.has(server.name); + const isLoading = actionLoading === server.name; + + return ( + + {/* Server Header */} +
toggleExpanded(server.name)} + > + {/* Expand Icon */} + + + {/* Status Indicator */} +
+ + {/* Type Icon */} + {getTypeIcon(server.type)} + + {/* Info */} +
+
+ {server.name} + {server.type} +
+
+ {getStatusText(server.status)} + {server.toolCount > 0 && ( + <> + · + + + {server.toolCount} tools + + + )} +
+
+ + {/* Actions */} +
e.stopPropagation()} + > + {isLoading ? ( +
+ ) : server.status === 'disabled' ? ( + + ) : server.status === 'connected' ? ( + <> + + + + ) : server.status === 'disconnected' || server.status === 'error' ? ( + + ) : null} +
+
+ + {/* Expanded Content */} + + {isExpanded && ( + +
+ {/* Error Message */} + {server.error && ( +
+ + {server.error} +
+ )} + + {/* Config Info */} + {server.config && ( +
+ {server.config.command && ( +
+ Command:{' '} + + {server.config.command.join(' ')} + +
+ )} + {server.config.url && ( +
+ URL:{' '} + + {server.config.url} + +
+ )} + {server.config.timeout && ( +
+ Timeout:{' '} + {server.config.timeout}ms +
+ )} +
+ )} + + {/* Tools List */} + {server.tools && server.tools.length > 0 && ( +
+
Tools:
+
+ {server.tools.map((tool) => ( + + {tool.originalName} + + ))} +
+
+ )} +
+
+ )} +
+ + ); + })} + + )} +
+ + {/* Footer Info */} +
+ Configure servers in .ai-assist/config.json +
+ + + + ); +} diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 743c9f0..15ee077 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -32,6 +32,16 @@ export { getCommandContent, updateCommand, deleteCommand, + // MCP API + listMCPServers, + getMCPServer, + connectMCPServer, + disconnectMCPServer, + enableMCPServer, + disableMCPServer, + listMCPTools, + getMCPTool, + getMCPConfig, } from './api/client.js'; // Types @@ -54,6 +64,12 @@ export type { CreateCommandInput, UpdateCommandInput, CommandContent, + // MCP types + MCPServerStatus, + MCPServerStatusType, + MCPToolInfo, + MCPConfig, + MCPServerConfigInfo, } from './api/client.js'; // Primitives (shadcn/ui style) @@ -69,6 +85,7 @@ export { ChatInput } from './components/ChatInput.js'; export { CommandMenu, type CommandMenuItem } from './components/CommandMenu.js'; export { CommandPanel } from './components/CommandPanel.js'; export { CommandEditor } from './components/CommandEditor.js'; +export { MCPPanel } from './components/MCPPanel.js'; export { Sidebar } from './components/Sidebar.js'; export { FileBrowser } from './components/FileBrowser.js'; export { ConfigPanel } from './components/ConfigPanel.js'; diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 999abf4..710ac6d 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -10,6 +10,7 @@ import { FileBrowser, ConfigPanel, CommandPanel, + MCPPanel, Toaster, listSessions, createSession, @@ -23,6 +24,7 @@ export function App() { const [showFileBrowser, setShowFileBrowser] = useState(false); const [showConfig, setShowConfig] = useState(false); const [showCommands, setShowCommands] = useState(false); + const [showMCP, setShowMCP] = useState(false); const [sessionTitleUpdate, setSessionTitleUpdate] = useState<{ sessionId: string; name: string } | null>(null); // 初始化:加载或创建会话 @@ -108,6 +110,7 @@ export function App() { onToggleFileBrowser={() => setShowFileBrowser(!showFileBrowser)} onOpenConfig={() => setShowConfig(true)} onOpenCommands={() => setShowCommands(true)} + onOpenMCP={() => setShowMCP(true)} /> ) : (
@@ -159,6 +162,9 @@ export function App() { {/* 命令面板 */} {showCommands && setShowCommands(false)} responsive />} + {/* MCP 面板 */} + {showMCP && setShowMCP(false)} responsive />} + {/* 移动端底部文件按钮 */}