用 AI 开发 AI:原汤化原食的 MCP 桌面客户端
24年底,Anthropic 发布的模型上下文协议(Model Context Protocol)促使我开始边学边撰写相关专栏:
MCP(Model Context Protocol)专栏https://blog.csdn.net/aiqlcom/category_12851864.html
为了深入理解协议细节并确保技术专栏内容的准确性,我尽可能实现了协议定义的客户端能力,通过实战验证了代码可行性。在两星期内就基于当时的 SDK 跑通了大部分流程,并且写了两篇对应的实战文:
MCP(Model Context Protocol)模型上下文协议 实战篇1_mcp协议实现-CSDN博客
MCP(Model Context Protocol)模型上下文协议 实战篇2_mcp 实践-CSDN博客
鉴于项目开发所需的技术框架已趋于成熟,我由此萌生了一个实验性构想:完全从零开始,高度依赖 AI 辅助构建完整的桌面客户端。这一尝试涵盖架构设计、代码实现、文档撰写以及 UI 生成,全程以 AI 的建议为核心参考。
先说结论,现有商用AI模型(如GPT系列、Deepseek、Claude等)能够理解项目整体架构并提供基础实现方案。但在以下场景中,AI 的表现存在局限性:
- 数据结构定义:模型对复杂类型(如嵌套枚举、泛型约束)的逻辑一致性处理较弱。
- UI设计:生成的布局缺乏系统性审美考量,需人工调整组件层级与交互逻辑。
- 代码风格统一性:不同模块的代码风格差异显著(例如命名规则、异常处理范式),导致可读性降低。
想要快速逃课的同学,可直接访问Github链接获取实现细节:
Tool Unitary User Interfacehttps://github.com/AI-QL/tuui或者跳转文章末尾获得更多相关链接和结论。
代码完全开源,可以自己配置接入任意语言模型,或者搭配使用不同的 MCP server。
下面开始正文。
项目概述
TUUI 是一个基于MCP(Model Context Protocol)的 LLM 桌面应用程序,它能够集成 AI 工具并对接不同供应商的 LLM API。该项目代表了一个大胆的实验,尝试使用 AI 创建完整项目。
主要功能
- 通过 MCP 加速 AI 工具集成
- 通过动态配置编排跨供应商 LLM API
- 自动化 Playwright 应用测试支持
- TypeScript 支持
- 多语言支持(目前支持英文和简体中文)
- 基本布局管理器
- 通过 Pinia 实现全局状态管理
- Github 社区开源和官方文档
此外,TUUI还提供:
- MCP 调用可视化跟踪
- 特定 Tool 选择功能
- LLM API 设置界面
- Sampling 采样功能
- 基于 Electron 的跨平台桌面应用打包
系统架构
总览
Electron
Electron主进程 作为TUUI应用的后端核心,负责系统资源管理、窗口控制及外部集成,运行于具备完整系统访问权限的Node.js环境中。
主进程组件构成
组件模块 | 文件路径 | 核心功能 |
---|---|---|
应用入口 | src/main/index.ts | 应用生命周期管理、异常处理、IPC通信初始化 |
窗口管理器 | src/main/MainRunner.ts | 主窗口创建(createMainWindow() )、错误窗口创建(createErrorWindow() ) |
系统托盘 | src/main/tray.ts | 托盘图标创建(createTray() )、窗口定位控制 |
IPC通信枢纽 | src/main/IPCs.ts | 进程间通信桥梁搭建 |
MCP集成模块 | src/main/mcp/ | 外部服务器通信交互 |
前端架构
TUUI使用Vue 3作为前端框架,结合Electron实现跨平台桌面应用。整个应用基于以下主要技术构建:
-
UI框架: 使用Vuetify提供现代化UI组件
-
状态管理: 采用Pinia进行全局状态管理,实现持久化本地存储
-
路由系统: 使用Vue Router管理应用的不同页面
-
国际化: 通过Vue I18n实现多语言支持
主体页面
应用包含四个主要页面,通过路由系统进行管理:
- MCP主界面("/"):管理 Model Context Protocol 相关功能
- 聊天界面("/chat"):提供与 LLM 的对话功能
- 代理界面("/agent"):管理 AI 智能体配置
- 设置界面("/setting"):LLM API 设置
每个页面使用组件化设计,包含centralStage(中心区域)、sideDrawer(侧边抽屉)、sideDock(侧边底部配置)和bottomConsole(中心底部交互控制台)等部分。
主要功能实现
状态管理
TUUI使用多个Pinia store管理不同方面的状态:
主要 Store
-
MCP Store: 处理MCP工具相关配置功能,包括列出、获取和调用工具
-
Chatbot Store: 管理聊天机器人配置,支持多种LLM,如Qwen、OpenAI等
-
Message Store: 处理消息发送、接收
-
History Store: 管理对话历史,用indexedDB持久化
-
Layout Store: 管理应用布局状态
次要 Store
-
Locale Store: 处理 i18n 语言设置
-
Agent Store: 管理智能体配置,MCP tool 相关配置
-
Resource Store: 管理 MCP resource 相关配置
-
Prompt Store: 管理 MCP prompt 相关配置
LLM 集成
TUUI 支持多种语言模型,默认配置包括:
- Qwen系列(qwen-turbo, qwen-plus, qwen-max)
- OpenAI系列(gpt-4-turbo, gpt-4.1, gpt-4o, o1)
- DeepInfra API
- ...
每个语言模型配置支持自定义API端点、模型选择、认证方式等参数。所以理论上可以支持各种 OpenAI API 兼容的 LLM 后端模型。同时,可以在 Electron 配置里关闭 webSecurity 来解除 CORS 跨域限制,从而支持一些尚在开发中的私有后端 API。
MCP 客户端
主架构
通过 MCP Typescript SDK 实现 MCP 调用,支持工具列表获取、调用和结果处理。用户也可以利用 Cloudflare 推荐的 mcp-remote 来配置远程 MCP 服务器。
MCP 客户端运行于Electron主进程,作为外部MCP服务器与TUUI前端之间的通信桥接层。该系统包含以下核心功能模块:
- 服务器发现与连接管理: 动态探测可用MCP服务器并维护稳定连接,包含自动重连机制与心跳检测功能。
- 能力注册机制: 提供标准化接口供功能模块注册服务能力,支持动态能力更新与权限校验。
- 请求路由分发: 通过消息队列实现请求/响应异步处理,确保高并发场景下的消息有序传递。
能力管理
MCP服务器提供三种能力(工具、提示、资源),TUUI系统会动态将其注册为IPC处理器。通过capabilitySchemas
对象,可将各能力类型与其对应的结果模式进行映射关联。
采样
MCP客户端通过处理来自MCP服务器的采样请求实现双向通信。当服务器请求创建消息时,客户端会将该请求转发至渲染器进程,并等待响应返回。
主要代码实现
Electron 主进程
主进程入口
主进程负责应用程序的生命周期管理,包括窗口创建、应用启动和退出处理。
app.on('ready', async () => {mainWindow = await createMainWindow()
})app.on('activate', async () => {if (!mainWindow) {mainWindow = await createMainWindow()}
})app.on('window-all-closed', () => {mainWindow = nullerrorWindow = nullif (!Constants.IS_MAC) {app.quit()}
})
IPC通信层
通过IPC(进程间通信)实现主进程与渲染进程的数据交换,支持MCP服务器初始化、工具调用等核心功能。
export default class IPCs {static clients: ClientObj[] = []static currentFeatures: any[] = []static initialize(): void {// Get application versionipcMain.handle('msgRequestGetVersion', () => {return Constants.APP_VERSION})ipcMain.handle('msgInitAllMcpServers', async (event: IpcMainEvent, config: ConfigObj) => {this.clients.forEach((client: ClientObj) => {if (client.connection?.transport) {disconnect(client.connection.transport)}})IPCs.removeAllHandlers()try {const newClients = await initClients(config)const features = newClients.map((params) => {return registerIpcHandlers(params)})IPCs.updateMCP(features)this.clients = newClientsreturn features} catch (error) {const configs = await loadConfig()const features = configs.map((params) => {return registerIpcHandlers(params)})IPCs.updateMCP(features)return {status: 'error',error: error}}})
MCP 客户端集成
MCP 客户端初始化
系统通过StdioClientTransport建立与MCP服务器的连接,支持多服务器并发管理。
async function initializeStdioClient(name: String,config: ServerConfig
): Promise<McpClientTransport> {const transport = new StdioClientTransport({...config,env: {...process.env,...(config.env || {})},stderr: 'pipe'})const clientName = `${name}-client`const client = new Client({name: clientName,version: Constants.APP_VERSION},{capabilities: {sampling: {}}})if (transport.stderr) {transport.stderr.on('data', (chunk) => {console.error('stderr:', chunk.toString())})}await connect(client, transport)console.log(`${clientName} connected.`)client.setRequestHandler(CreateMessageRequestSchema, async (request) => {console.log('Sampling request received:\n', request)const response = await sendToRenderer('renderListenSampling', request)console.log(response)return response})return { client, transport }
}
工具调用管理
实现了完整的MCP工具调用流程,包括请求处理和响应管理。
export async function manageRequests(client: Client, method: string, schema: any, params?: any) {const requestObject = {method,...(params && { params })}const result = await client.request(requestObject, schema)console.log(result)return result
}
Vue3 渲染进程
应用程序初始化
渲染进程使用Vue3 + Pinia + Vue Router构建,集成了国际化和状态持久化功能。
const app = createApp(App)
const pinia = createPinia()
pinia.use(createStatePersistence())app.use(vuetify).use(i18n).use(router).use(pinia).use(Vue3Lottie)
app.mount('#app')
布局系统
采用命名视图的路由架构,支持多区域组件布局(中央舞台、侧边栏、底部控制台等)。
<SidebarLayout v-if="hasComponent('sideDrawer').value" class="position-fixed"><router-view name="sideDrawer" /><template #append><router-view v-if="hasComponent('sideDock').value" name="sideDock" /></template></SidebarLayout><HeaderLayout class="position-fixed" /><v-main class="d-flex justify-center"><v-container><router-view name="centralStage" /><router-view /></v-container></v-main><FooterLayout v-if="hasComponent('bottomConsole').value" class="position-fixed"><router-view name="bottomConsole" /></FooterLayout>
状态管理
聊天机器人配置管理
通过Pinia Store管理多个聊天机器人的配置,支持动态切换和配置更新。
export const useChatbotStore = defineStore('chatbotStore', {state: (): ChatbotStoreState => ({chatbots: [{ ...CHATBOT_DEFAULTS, name: 'Chatbot Default', mcp: false },{...CHATBOT_DEFAULTS,...CHATBOT_QWEN},{...CHATBOT_DEFAULTS,...CHATBOT_OPENAI},{...CHATBOT_DEFAULTS,...CHATBOT_DEEPINFRA}],currentChatbotId: 0, // points to first chatbot by defaultselectedChatbotId: 0}),persist: {exclude: ['currentChatbotId']},
MCP状态管理
管理MCP服务器连接状态、工具列表和服务器选择逻辑。
export const useMcpStore = defineStore('mcpStore', {state: (): any => ({version: 1,serverTools: [],loading: true,selected: undefined as string[] | undefined,selectedChips: {} // { key : 0 | 1 | 2}}),
消息状态管理
处理聊天会话、消息发送等核心业务逻辑。
export const useMessageStore = defineStore('messageStore', {state: (): any => ({userMessage: '',conversation: [],images: [],base64: '',generating: false}),
消息处理流程
消息发送逻辑
用户消息发送后触发推理流程,支持图片和文本混合输入。
sendMessage() {if (this.userMessage) {const imageBase64 = this.base64this.conversation.push({content: imageBase64? [{ type: 'image_url', image_url: { url: imageBase64 } },{ type: 'text', text: this.userMessage }]: this.userMessage,role: 'user'})if (this.conversation.length === 1) {this.syncHistory()}this.startInference()}},
工具调用后处理
自动检测助手回复中的工具调用请求,执行工具并将结果反馈给模型。
postToolCall: async function () {const mcpStore = useMcpStore()const last = this.conversation.at(-1)if (!last || !last.tool_calls) {return}if (isEmptyTools(last.tool_calls)) {delete last.tool_calls} else {let toolCalled = falseconsole.log(last.tool_calls)const callNextTool = async (toolCalls, index) => {if (index >= toolCalls.length) {return}const toolCall = toolCalls[index]let resulttry {result = await mcpStore.callTool(toolCall.function.name, toolCall.function.arguments)console.log(result)} catch (error) {result = mcpStore.packReturn(`Error calling tool: ${error}`)}if (result.content) {this.contentConvert(result.content, toolCall.id).forEach((item) => {this.conversation.push(item)})toolCalled = true}}await callNextTool(last.tool_calls, 0)if (toolCalled) {this.startInference()}}},
路由与页面
多页面路由设计
采用命名组件路由,支持MCP管理、聊天、代理配置和设置等多个功能模块。
export default createRouter({history: createWebHashHistory(),routes: [{path: '/',components: McpScreen,meta: {titleKey: 'title.main'}},{path: '/chat',components: ChatScreen,meta: {titleKey: 'title.chat'}},{path: '/agent',components: AgentScreen,meta: {titleKey: 'title.agent'}},{path: '/setting',components: SettingScreen,meta: {titleKey: 'title.setting'}},
屏幕组件组织
每个功能模块包含多个子组件,通过命名视图实现复杂布局。
export const ChatScreen: ScreenType = {centralStage: ChatMainScreen,sideDrawer: ChatHistoryScreen,sideDock: ChatEndScreen,bottomConsole: ChatInputScreen
}
技术栈组成
"dependencies": {"@mdi/font": "^7.4.47","@modelcontextprotocol/sdk": "^1.12.1","highlight.js": "^11.11.1","iconify-icon": "^3.0.0","katex": "^0.16.22","localforage": "^1.10.0","md-editor-v3": "^5.6.0","mermaid": "^11.6.0","pinia": "^3.0.2","pinia-plugin-state-persistence": "^1.10.2","uuid": "^11.1.0","vue": "^3.5.16","vue-i18n": "^11.1.5","vue-router": "^4.5.1","vue3-lottie": "^3.3.1","vuetify": "^3.8.7"},"devDependencies": {"electron": "^36.3.2","electron-builder": "^26.0.15","eslint": "^9.28.0","prettier": "^3.5.3","typescript": "^5.8.3","vite": "^6.3.5",...},
包括Electron、Vue3、TypeScript、Vuetify、Pinia等,并集成了 MCP SDK 用于协议支持。
总结
TUUI 是一个尝试性项目,几乎所有组件通过 AI 生成和转换。(包括这篇文章中的所有架构和交互图也是 AI 分析生成的)
因此,项目采用严格的语法检查和命名规范,使用ESLint和Prettier尽可能保持代码质量和风格。
从我的实际体验来看,AI 生成的代码质量往往呈现出一种矛盾的特质——就像经验丰富的匠人生硬地做着新手活计(有点"面向测试开发"的那个味道)。这意味着绝大多数AI生成的组件都需要经过深度重构,必须后期精心打磨。仅靠静态代码检查这类表面功夫,根本不足以确保最终产出质量。
即便在前端开发部分,AI仍存在明显局限,例如在UI设计层面,其生成的界面往往呈现过于生硬的极简风格,缺乏人性化考量。
本项目代码已放出,采用非常宽松的Apache 2.0开源协议,支持二次开发和商业使用。
最后完整提供一下所有链接:
项目地址:https://github.com/AI-QL/tuui
项目官网:https://www.tuui.com/
项目全架构英文解析(支持 AI 对话提问):AI-QL/tuui | DeepWiki