完成主要协议的中台实现

This commit is contained in:
huangzhelong.byte 2025-03-29 17:37:36 +08:00
parent c8c069574e
commit 4947a0d2c2
17 changed files with 274 additions and 28 deletions

View File

@ -26,6 +26,7 @@
- [x] 完成最基本的各类基础设施 - [x] 完成最基本的各类基础设施
- [ ] 支持同时调试多个 MCP Server - [ ] 支持同时调试多个 MCP Server
- [ ] 支持通过大模型进行在线验证 - [ ] 支持通过大模型进行在线验证
- [ ] 支持 completion/complete 协议字段
--- ---

View File

@ -1,7 +1,9 @@
<template> <template>
<div class="resource-module"> <div class="resource-module">
<h2>资源模块</h2> <h2>资源模块</h2>
<!-- 资源模块内容将在这里实现 -->
</div> </div>
</template> </template>

View File

@ -16,13 +16,14 @@
import { defineComponent, computed } from 'vue'; import { defineComponent, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { Connection } from './sidebar'; import { Connection } from './sidebar';
import { connectionResult } from '@/views/connect/connection';
defineComponent({ name: 'connected' }); defineComponent({ name: 'connected' });
const { t } = useI18n(); const { t } = useI18n();
const statusString = computed(() => { const statusString = computed(() => {
if (Connection.connected) { if (connectionResult.success) {
return t('connected'); return t('connected');
} else { } else {
return t('disconnected'); return t('disconnected');
@ -30,7 +31,7 @@ const statusString = computed(() => {
}); });
const statusColorStyle = computed(() => { const statusColorStyle = computed(() => {
if (Connection.connected) { if (connectionResult.success) {
return 'connected-color'; return 'connected-color';
} else { } else {
return 'disconnected-color'; return 'disconnected-color';

View File

@ -20,6 +20,5 @@ export const sidebarItems = reactive([
]); ]);
export const Connection = reactive({ export const Connection = reactive({
connected: false,
showPanel: false showPanel: false
}); });

View File

@ -104,5 +104,6 @@
"connection-method": "طريقة الاتصال", "connection-method": "طريقة الاتصال",
"command": "أمر", "command": "أمر",
"env-var": "متغيرات البيئة", "env-var": "متغيرات البيئة",
"log": "سجلات" "log": "سجلات",
"warning.click-to-connect": "يرجى النقر أولاً على $1 على اليسار للاتصال"
} }

View File

@ -104,5 +104,6 @@
"connection-method": "Verbindungsmethode", "connection-method": "Verbindungsmethode",
"command": "Befehl", "command": "Befehl",
"env-var": "Umgebungsvariablen", "env-var": "Umgebungsvariablen",
"log": "Protokolle" "log": "Protokolle",
"warning.click-to-connect": "Bitte klicken Sie zuerst auf $1 links, um eine Verbindung herzustellen"
} }

View File

@ -104,5 +104,6 @@
"connection-method": "Connection method", "connection-method": "Connection method",
"command": "Command", "command": "Command",
"env-var": "Environment variables", "env-var": "Environment variables",
"log": "Logs" "log": "Logs",
"warning.click-to-connect": "Please first click on $1 on the left to connect"
} }

View File

@ -104,5 +104,6 @@
"connection-method": "Méthode de connexion", "connection-method": "Méthode de connexion",
"command": "Commande", "command": "Commande",
"env-var": "Variables d'environnement", "env-var": "Variables d'environnement",
"log": "Journaux" "log": "Journaux",
"warning.click-to-connect": "Veuillez d'abord cliquer sur $1 à gauche pour vous connecter"
} }

View File

@ -104,5 +104,6 @@
"connection-method": "接続方法", "connection-method": "接続方法",
"command": "コマンド", "command": "コマンド",
"env-var": "環境変数", "env-var": "環境変数",
"log": "ログ" "log": "ログ",
"warning.click-to-connect": "まず左側の$1をクリックして接続してください"
} }

View File

@ -104,5 +104,6 @@
"connection-method": "연결 방법", "connection-method": "연결 방법",
"command": "명령", "command": "명령",
"env-var": "환경 변수", "env-var": "환경 변수",
"log": "로그" "log": "로그",
"warning.click-to-connect": "먼저 왼쪽의 $1을 클릭하여 연결하십시오"
} }

View File

@ -104,5 +104,6 @@
"connection-method": "Способ подключения", "connection-method": "Способ подключения",
"command": "Команда", "command": "Команда",
"env-var": "Переменные среды", "env-var": "Переменные среды",
"log": "Логи" "log": "Логи",
"warning.click-to-connect": "Пожалуйста, сначала нажмите на $1 слева для подключения"
} }

View File

@ -104,5 +104,6 @@
"connection-method": "连接方式", "connection-method": "连接方式",
"command": "命令", "command": "命令",
"env-var": "环境变量", "env-var": "环境变量",
"log": "日志" "log": "日志",
"warning.click-to-connect": "请先点击左侧的 $1 进行连接"
} }

View File

@ -104,5 +104,6 @@
"connection-method": "連接方式", "connection-method": "連接方式",
"command": "命令", "command": "命令",
"env-var": "環境變數", "env-var": "環境變數",
"log": "日誌" "log": "日誌",
"warning.click-to-connect": "請先點擊左側的 $1 進行連接"
} }

View File

@ -2,8 +2,10 @@
<div class="debug-welcome"> <div class="debug-welcome">
<span>{{ t('choose-a-project-debug') }}</span> <span>{{ t('choose-a-project-debug') }}</span>
<div class="welcome-container"> <div class="welcome-container">
<!-- TODO: 支持更多的 server -->
<span <span
class="debug-option" class="debug-option"
:class="{ 'disable': !connectionResult.success }"
v-for="(option, index) of debugOptions" v-for="(option, index) of debugOptions"
:key="index" :key="index"
@click="chooseDebugMode(index)" @click="chooseDebugMode(index)"
@ -21,6 +23,8 @@
import { debugModes, tabs } from '@/components/main-panel/panel'; import { debugModes, tabs } from '@/components/main-panel/panel';
import { defineComponent, markRaw } from 'vue'; import { defineComponent, markRaw } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { connectionResult } from '../connect/connection';
import { ElMessage } from 'element-plus';
defineComponent({ name: 'welcome' }); defineComponent({ name: 'welcome' });
@ -50,10 +54,24 @@ const debugOptions = [
]; ];
function chooseDebugMode(index: number) { function chooseDebugMode(index: number) {
const activeTab = tabs.activeTab;
activeTab.component = markRaw(debugModes[index]); // TODO: server
activeTab.icon = debugOptions[index].icon; if (connectionResult.success) {
activeTab.name = debugOptions[index].name; const activeTab = tabs.activeTab;
activeTab.component = markRaw(debugModes[index]);
activeTab.icon = debugOptions[index].icon;
activeTab.name = debugOptions[index].name;
} else {
const message = t('warning.click-to-connect')
.replace('$1', t('connect'));
ElMessage({
message,
type: 'error',
duration: 3000,
showClose: true,
});
}
} }
</script> </script>
@ -112,4 +130,9 @@ function chooseDebugMode(index: number) {
user-select: none; user-select: none;
} }
.debug-option.disable {
cursor: not-allowed;
opacity: 0.5;
}
</style> </style>

View File

@ -22,7 +22,7 @@ export interface MCPOptions {
} }
// 增强的客户端类 // 增强的客户端类
class MCPClient { export class MCPClient {
private client: Client; private client: Client;
private transport?: McpTransport; private transport?: McpTransport;
private options: MCPOptions; private options: MCPOptions;
@ -77,29 +77,29 @@ class MCPClient {
} }
// 列出提示 // 列出提示
public async listPrompts(): Promise<any> { public async listPrompts() {
return await this.client.listPrompts(); return await this.client.listPrompts();
} }
// 获取提示 // 获取提示
public async getPrompt(name: string, args: Record<string, any> = {}): Promise<any> { public async getPrompt(name: string, args: Record<string, any> = {}) {
return await this.client.getPrompt({ name }, args); return await this.client.getPrompt({ name }, args);
} }
// 列出资源 // 列出资源
public async listResources(): Promise<any> { public async listResources() {
return await this.client.listResources(); return await this.client.listResources();
} }
// 读取资源 // 读取资源
public async readResource(uri: string): Promise<any> { public async readResource(uri: string) {
return await this.client.readResource({ return await this.client.readResource({
uri uri
}); });
} }
// 调用工具 // 调用工具
public async callTool(options: { name: string; arguments: Record<string, any> }): Promise<any> { public async callTool(options: { name: string; arguments: Record<string, any> }) {
return await this.client.callTool(options); return await this.client.callTool(options);
} }
} }

View File

@ -0,0 +1,185 @@
import { VSCodeWebViewLike } from "../adapter";
import { MCPClient } from "./connect";
// ==================== 接口定义 ====================
export interface GetPromptOption {
promptId: string;
args?: Record<string, any>;
}
export interface ReadResourceOption {
resourceUri: string;
}
export interface CallToolOption {
toolName: string;
toolArgs: Record<string, any>;
}
// ==================== 函数实现 ====================
/**
* @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 });
}
}

View File

@ -1,13 +1,18 @@
import { VSCodeWebViewLike } from '../adapter'; 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) { async function connectHandler(option: MCPOptions, webview: VSCodeWebViewLike) {
try { try {
const client = await connect(option); client = await connect(option);
const connectResult = { const connectResult = {
code: 200, code: 200,
msg: 'connect success' msg: 'connect success\nHello from OpenMCP | virtual client version: 0.0.1'
}; };
webview.postMessage({ command: 'connect', data: connectResult }); webview.postMessage({ command: 'connect', data: connectResult });
} catch (error) { } catch (error) {
@ -24,12 +29,33 @@ async function connectHandler(option: MCPOptions, webview: VSCodeWebViewLike) {
} }
} }
export function messageController(command: string, data: any, webview: VSCodeWebViewLike) { export function messageController(command: string, data: any, webview: VSCodeWebViewLike) {
switch (command) { switch (command) {
case 'connect': case 'connect':
connectHandler(data, webview); connectHandler(data, webview);
break; 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: default:
break; break;
} }