完成主要协议的中台实现
This commit is contained in:
parent
c8c069574e
commit
4947a0d2c2
@ -26,6 +26,7 @@
|
|||||||
- [x] 完成最基本的各类基础设施
|
- [x] 完成最基本的各类基础设施
|
||||||
- [ ] 支持同时调试多个 MCP Server
|
- [ ] 支持同时调试多个 MCP Server
|
||||||
- [ ] 支持通过大模型进行在线验证
|
- [ ] 支持通过大模型进行在线验证
|
||||||
|
- [ ] 支持 completion/complete 协议字段
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="resource-module">
|
<div class="resource-module">
|
||||||
<h2>资源模块</h2>
|
<h2>资源模块</h2>
|
||||||
<!-- 资源模块内容将在这里实现 -->
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -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';
|
||||||
|
@ -20,6 +20,5 @@ export const sidebarItems = reactive([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
export const Connection = reactive({
|
export const Connection = reactive({
|
||||||
connected: false,
|
|
||||||
showPanel: false
|
showPanel: false
|
||||||
});
|
});
|
||||||
|
@ -104,5 +104,6 @@
|
|||||||
"connection-method": "طريقة الاتصال",
|
"connection-method": "طريقة الاتصال",
|
||||||
"command": "أمر",
|
"command": "أمر",
|
||||||
"env-var": "متغيرات البيئة",
|
"env-var": "متغيرات البيئة",
|
||||||
"log": "سجلات"
|
"log": "سجلات",
|
||||||
|
"warning.click-to-connect": "يرجى النقر أولاً على $1 على اليسار للاتصال"
|
||||||
}
|
}
|
@ -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"
|
||||||
}
|
}
|
@ -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"
|
||||||
}
|
}
|
@ -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"
|
||||||
}
|
}
|
@ -104,5 +104,6 @@
|
|||||||
"connection-method": "接続方法",
|
"connection-method": "接続方法",
|
||||||
"command": "コマンド",
|
"command": "コマンド",
|
||||||
"env-var": "環境変数",
|
"env-var": "環境変数",
|
||||||
"log": "ログ"
|
"log": "ログ",
|
||||||
|
"warning.click-to-connect": "まず左側の$1をクリックして接続してください"
|
||||||
}
|
}
|
@ -104,5 +104,6 @@
|
|||||||
"connection-method": "연결 방법",
|
"connection-method": "연결 방법",
|
||||||
"command": "명령",
|
"command": "명령",
|
||||||
"env-var": "환경 변수",
|
"env-var": "환경 변수",
|
||||||
"log": "로그"
|
"log": "로그",
|
||||||
|
"warning.click-to-connect": "먼저 왼쪽의 $1을 클릭하여 연결하십시오"
|
||||||
}
|
}
|
@ -104,5 +104,6 @@
|
|||||||
"connection-method": "Способ подключения",
|
"connection-method": "Способ подключения",
|
||||||
"command": "Команда",
|
"command": "Команда",
|
||||||
"env-var": "Переменные среды",
|
"env-var": "Переменные среды",
|
||||||
"log": "Логи"
|
"log": "Логи",
|
||||||
|
"warning.click-to-connect": "Пожалуйста, сначала нажмите на $1 слева для подключения"
|
||||||
}
|
}
|
@ -104,5 +104,6 @@
|
|||||||
"connection-method": "连接方式",
|
"connection-method": "连接方式",
|
||||||
"command": "命令",
|
"command": "命令",
|
||||||
"env-var": "环境变量",
|
"env-var": "环境变量",
|
||||||
"log": "日志"
|
"log": "日志",
|
||||||
|
"warning.click-to-connect": "请先点击左侧的 $1 进行连接"
|
||||||
}
|
}
|
@ -104,5 +104,6 @@
|
|||||||
"connection-method": "連接方式",
|
"connection-method": "連接方式",
|
||||||
"command": "命令",
|
"command": "命令",
|
||||||
"env-var": "環境變數",
|
"env-var": "環境變數",
|
||||||
"log": "日誌"
|
"log": "日誌",
|
||||||
|
"warning.click-to-connect": "請先點擊左側的 $1 進行連接"
|
||||||
}
|
}
|
@ -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>
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
185
test/src/controller/handler.ts
Normal file
185
test/src/controller/handler.ts
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user