From 4947a0d2c2f7f500a1336a43d7ccafbecd5ce29c Mon Sep 17 00:00:00 2001 From: "huangzhelong.byte" Date: Sat, 29 Mar 2025 17:37:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=B8=BB=E8=A6=81=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE=E7=9A=84=E4=B8=AD=E5=8F=B0=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + .../components/main-panel/resource/index.vue | 4 +- app/src/components/sidebar/connected.vue | 5 +- app/src/components/sidebar/sidebar.ts | 1 - app/src/i18n/ar.json | 3 +- app/src/i18n/de.json | 3 +- app/src/i18n/en.json | 3 +- app/src/i18n/fr.json | 3 +- app/src/i18n/ja.json | 3 +- app/src/i18n/ko.json | 3 +- app/src/i18n/ru.json | 3 +- app/src/i18n/zh-cn.json | 3 +- app/src/i18n/zh-tw.json | 3 +- app/src/views/debug/welcome.vue | 31 ++- test/src/controller/connect.ts | 12 +- test/src/controller/handler.ts | 185 ++++++++++++++++++ test/src/controller/index.ts | 36 +++- 17 files changed, 274 insertions(+), 28 deletions(-) create mode 100644 test/src/controller/handler.ts diff --git a/README.md b/README.md index b57de77..0733455 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ - [x] 完成最基本的各类基础设施 - [ ] 支持同时调试多个 MCP Server - [ ] 支持通过大模型进行在线验证 +- [ ] 支持 completion/complete 协议字段 --- diff --git a/app/src/components/main-panel/resource/index.vue b/app/src/components/main-panel/resource/index.vue index cae7784..994ebc6 100644 --- a/app/src/components/main-panel/resource/index.vue +++ b/app/src/components/main-panel/resource/index.vue @@ -1,7 +1,9 @@ diff --git a/app/src/components/sidebar/connected.vue b/app/src/components/sidebar/connected.vue index dca9935..9b95ed2 100644 --- a/app/src/components/sidebar/connected.vue +++ b/app/src/components/sidebar/connected.vue @@ -16,13 +16,14 @@ import { defineComponent, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { Connection } from './sidebar'; +import { connectionResult } from '@/views/connect/connection'; defineComponent({ name: 'connected' }); const { t } = useI18n(); const statusString = computed(() => { - if (Connection.connected) { + if (connectionResult.success) { return t('connected'); } else { return t('disconnected'); @@ -30,7 +31,7 @@ const statusString = computed(() => { }); const statusColorStyle = computed(() => { - if (Connection.connected) { + if (connectionResult.success) { return 'connected-color'; } else { return 'disconnected-color'; diff --git a/app/src/components/sidebar/sidebar.ts b/app/src/components/sidebar/sidebar.ts index df2f63e..84bef05 100644 --- a/app/src/components/sidebar/sidebar.ts +++ b/app/src/components/sidebar/sidebar.ts @@ -20,6 +20,5 @@ export const sidebarItems = reactive([ ]); export const Connection = reactive({ - connected: false, showPanel: false }); diff --git a/app/src/i18n/ar.json b/app/src/i18n/ar.json index 0fc8c6a..44ab901 100644 --- a/app/src/i18n/ar.json +++ b/app/src/i18n/ar.json @@ -104,5 +104,6 @@ "connection-method": "طريقة الاتصال", "command": "أمر", "env-var": "متغيرات البيئة", - "log": "سجلات" + "log": "سجلات", + "warning.click-to-connect": "يرجى النقر أولاً على $1 على اليسار للاتصال" } \ No newline at end of file diff --git a/app/src/i18n/de.json b/app/src/i18n/de.json index e22998f..a960b9f 100644 --- a/app/src/i18n/de.json +++ b/app/src/i18n/de.json @@ -104,5 +104,6 @@ "connection-method": "Verbindungsmethode", "command": "Befehl", "env-var": "Umgebungsvariablen", - "log": "Protokolle" + "log": "Protokolle", + "warning.click-to-connect": "Bitte klicken Sie zuerst auf $1 links, um eine Verbindung herzustellen" } \ No newline at end of file diff --git a/app/src/i18n/en.json b/app/src/i18n/en.json index d16bc29..59e1cef 100644 --- a/app/src/i18n/en.json +++ b/app/src/i18n/en.json @@ -104,5 +104,6 @@ "connection-method": "Connection method", "command": "Command", "env-var": "Environment variables", - "log": "Logs" + "log": "Logs", + "warning.click-to-connect": "Please first click on $1 on the left to connect" } \ No newline at end of file diff --git a/app/src/i18n/fr.json b/app/src/i18n/fr.json index 8e5783d..b7e672f 100644 --- a/app/src/i18n/fr.json +++ b/app/src/i18n/fr.json @@ -104,5 +104,6 @@ "connection-method": "Méthode de connexion", "command": "Commande", "env-var": "Variables d'environnement", - "log": "Journaux" + "log": "Journaux", + "warning.click-to-connect": "Veuillez d'abord cliquer sur $1 à gauche pour vous connecter" } \ No newline at end of file diff --git a/app/src/i18n/ja.json b/app/src/i18n/ja.json index 30a4603..68ee6a4 100644 --- a/app/src/i18n/ja.json +++ b/app/src/i18n/ja.json @@ -104,5 +104,6 @@ "connection-method": "接続方法", "command": "コマンド", "env-var": "環境変数", - "log": "ログ" + "log": "ログ", + "warning.click-to-connect": "まず左側の$1をクリックして接続してください" } \ No newline at end of file diff --git a/app/src/i18n/ko.json b/app/src/i18n/ko.json index 320778e..e447e40 100644 --- a/app/src/i18n/ko.json +++ b/app/src/i18n/ko.json @@ -104,5 +104,6 @@ "connection-method": "연결 방법", "command": "명령", "env-var": "환경 변수", - "log": "로그" + "log": "로그", + "warning.click-to-connect": "먼저 왼쪽의 $1을 클릭하여 연결하십시오" } \ No newline at end of file diff --git a/app/src/i18n/ru.json b/app/src/i18n/ru.json index 0da8c4a..3ea6652 100644 --- a/app/src/i18n/ru.json +++ b/app/src/i18n/ru.json @@ -104,5 +104,6 @@ "connection-method": "Способ подключения", "command": "Команда", "env-var": "Переменные среды", - "log": "Логи" + "log": "Логи", + "warning.click-to-connect": "Пожалуйста, сначала нажмите на $1 слева для подключения" } \ No newline at end of file diff --git a/app/src/i18n/zh-cn.json b/app/src/i18n/zh-cn.json index f4ad466..08923ee 100644 --- a/app/src/i18n/zh-cn.json +++ b/app/src/i18n/zh-cn.json @@ -104,5 +104,6 @@ "connection-method": "连接方式", "command": "命令", "env-var": "环境变量", - "log": "日志" + "log": "日志", + "warning.click-to-connect": "请先点击左侧的 $1 进行连接" } \ No newline at end of file diff --git a/app/src/i18n/zh-tw.json b/app/src/i18n/zh-tw.json index 6b0537d..b46798e 100644 --- a/app/src/i18n/zh-tw.json +++ b/app/src/i18n/zh-tw.json @@ -104,5 +104,6 @@ "connection-method": "連接方式", "command": "命令", "env-var": "環境變數", - "log": "日誌" + "log": "日誌", + "warning.click-to-connect": "請先點擊左側的 $1 進行連接" } \ No newline at end of file diff --git a/app/src/views/debug/welcome.vue b/app/src/views/debug/welcome.vue index edaafc5..cb20fa9 100644 --- a/app/src/views/debug/welcome.vue +++ b/app/src/views/debug/welcome.vue @@ -2,8 +2,10 @@
{{ t('choose-a-project-debug') }}
+ @@ -112,4 +130,9 @@ function chooseDebugMode(index: number) { user-select: none; } +.debug-option.disable { + cursor: not-allowed; + opacity: 0.5; +} + \ No newline at end of file diff --git a/test/src/controller/connect.ts b/test/src/controller/connect.ts index eedf368..54703aa 100644 --- a/test/src/controller/connect.ts +++ b/test/src/controller/connect.ts @@ -22,7 +22,7 @@ export interface MCPOptions { } // 增强的客户端类 -class MCPClient { +export class MCPClient { private client: Client; private transport?: McpTransport; private options: MCPOptions; @@ -77,29 +77,29 @@ class MCPClient { } // 列出提示 - public async listPrompts(): Promise { + public async listPrompts() { return await this.client.listPrompts(); } // 获取提示 - public async getPrompt(name: string, args: Record = {}): Promise { + public async getPrompt(name: string, args: Record = {}) { return await this.client.getPrompt({ name }, args); } // 列出资源 - public async listResources(): Promise { + public async listResources() { return await this.client.listResources(); } // 读取资源 - public async readResource(uri: string): Promise { + public async readResource(uri: string) { return await this.client.readResource({ uri }); } // 调用工具 - public async callTool(options: { name: string; arguments: Record }): Promise { + public async callTool(options: { name: string; arguments: Record }) { return await this.client.callTool(options); } } diff --git a/test/src/controller/handler.ts b/test/src/controller/handler.ts new file mode 100644 index 0000000..ecb08ab --- /dev/null +++ b/test/src/controller/handler.ts @@ -0,0 +1,185 @@ +import { VSCodeWebViewLike } from "../adapter"; +import { MCPClient } from "./connect"; + +// ==================== 接口定义 ==================== +export interface GetPromptOption { + promptId: string; + args?: Record; +} + +export interface ReadResourceOption { + resourceUri: string; +} + +export interface CallToolOption { + toolName: string; + toolArgs: Record; +} + +// ==================== 函数实现 ==================== + +/** + * @description 列出所有 prompts + */ +export async function listPrompts( + client: MCPClient | undefined, + webview: VSCodeWebViewLike +) { + if (!client) { + const connectResult = { + code: 501, + msg: 'mcp client 尚未连接' + }; + webview.postMessage({ command: 'prompts/list', data: connectResult }); + return; + } + + try { + const prompts = await client.listPrompts(); + const result = { + code: 200, + msg: prompts + }; + webview.postMessage({ command: 'prompts/list', data: result }); + } catch (error) { + const result = { + code: 500, + msg: (error as any).toString() + }; + webview.postMessage({ command: 'prompts/list', data: result }); + } +} + +/** + * @description 获取特定 prompt + */ +export async function getPrompt( + client: MCPClient | undefined, + option: GetPromptOption, + webview: VSCodeWebViewLike +) { + if (!client) { + const connectResult = { + code: 501, + msg: 'mcp client 尚未连接' + }; + webview.postMessage({ command: 'prompts/get', data: connectResult }); + return; + } + + try { + const prompt = await client.getPrompt(option.promptId, option.args || {}); + const result = { + code: 200, + msg: prompt + }; + webview.postMessage({ command: 'prompts/get', data: result }); + } catch (error) { + const result = { + code: 500, + msg: (error as any).toString() + }; + webview.postMessage({ command: 'prompts/get', data: result }); + } +} + +/** + * @description 列出所有resources + */ +export async function listResources( + client: MCPClient | undefined, + webview: VSCodeWebViewLike +) { + if (!client) { + const connectResult = { + code: 501, + msg: 'mcp client 尚未连接' + }; + webview.postMessage({ command: 'resources/list', data: connectResult }); + return; + } + + try { + const resources = await client.listResources(); + const result = { + code: 200, + msg: resources + }; + webview.postMessage({ command: 'resources/list', data: result }); + } catch (error) { + const result = { + code: 500, + msg: (error as any).toString() + }; + webview.postMessage({ command: 'resources/list', data: result }); + } +} + +/** + * @description 读取特定resource + */ +export async function readResource( + client: MCPClient | undefined, + option: ReadResourceOption, + webview: VSCodeWebViewLike +) { + if (!client) { + const connectResult = { + code: 501, + msg: 'mcp client 尚未连接' + }; + webview.postMessage({ command: 'resources/read', data: connectResult }); + return; + } + + try { + const resource = await client.readResource(option.resourceUri); + const result = { + code: 200, + msg: resource + }; + webview.postMessage({ command: 'resources/read', data: result }); + } catch (error) { + const result = { + code: 500, + msg: (error as any).toString() + }; + webview.postMessage({ command: 'resources/read', data: result }); + } +} + +/** + * @description 调用工具 + */ +export async function callTool( + client: MCPClient | undefined, + option: CallToolOption, + webview: VSCodeWebViewLike +) { + if (!client) { + const connectResult = { + code: 501, + msg: 'mcp client 尚未连接' + }; + webview.postMessage({ command: 'tools/call', data: connectResult }); + return; + } + + try { + const toolResult = await client.callTool({ + name: option.toolName, + arguments: option.toolArgs + }); + const result = { + code: 200, + msg: toolResult + }; + webview.postMessage({ command: 'tools/call', data: result }); + } catch (error) { + const result = { + code: 500, + msg: (error as any).toString() + }; + webview.postMessage({ command: 'tools/call', data: result }); + } +} diff --git a/test/src/controller/index.ts b/test/src/controller/index.ts index 0661867..f73e427 100644 --- a/test/src/controller/index.ts +++ b/test/src/controller/index.ts @@ -1,17 +1,22 @@ import { VSCodeWebViewLike } from '../adapter'; -import { connect, type MCPOptions } from './connect'; +import { connect, MCPClient, type MCPOptions } from './connect'; +import { callTool, getPrompt, listPrompts, listResources, readResource } from './handler'; + + +// TODO: 支持更多的 client +let client: MCPClient | undefined = undefined; async function connectHandler(option: MCPOptions, webview: VSCodeWebViewLike) { try { - const client = await connect(option); + client = await connect(option); const connectResult = { code: 200, - msg: 'connect success' + msg: 'connect success\nHello from OpenMCP | virtual client version: 0.0.1' }; webview.postMessage({ command: 'connect', data: connectResult }); } catch (error) { - + // TODO: 这边获取到的 error 不够精致,如何才能获取到更加精准的错误 // 比如 error: Failed to spawn: `server.py` // Caused by: No such file or directory (os error 2) @@ -24,12 +29,33 @@ async function connectHandler(option: MCPOptions, webview: VSCodeWebViewLike) { } } + export function messageController(command: string, data: any, webview: VSCodeWebViewLike) { switch (command) { case 'connect': connectHandler(data, webview); break; - + + case 'prompts/list': + listPrompts(client, webview); + break; + + case 'prompts/get': + getPrompt(client, data, webview); + break; + + case 'resources/list': + listResources(client, webview); + break; + + case 'resources/read': + readResource(client, data, webview); + break; + + case 'tools/call': + callTool(client, data, webview); + break; + default: break; }