update
This commit is contained in:
parent
9b45c272b4
commit
23d0ce1d93
28
.vscode/tabs.example-servers_puppeteer.json
vendored
28
.vscode/tabs.example-servers_puppeteer.json
vendored
@ -331,6 +331,34 @@
|
|||||||
"state": "success",
|
"state": "success",
|
||||||
"serverName": "Huoshan DeepSeek"
|
"serverName": "Huoshan DeepSeek"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "请将下面的更新日志翻译成 GitHub release 风格的英文说明,请只返回翻译后的结果,不要出现任何多余的前缀: ",
|
||||||
|
"extraInfo": {
|
||||||
|
"created": 1747598248965,
|
||||||
|
"state": "success",
|
||||||
|
"serverName": "deepseek"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Here’s the translation in GitHub release style:\n\n---\n\n### **New Features**\n- Added support for real-time data synchronization.\n- Introduced a new dashboard for analytics.\n\n### **Improvements**\n- Optimized query performance for large datasets.\n- Enhanced error handling for API requests.\n\n### **Bug Fixes**\n- Fixed an issue where the system crashed on invalid input.\n- Resolved a memory leak in the background service.\n\n### **Breaking Changes**\n- Removed deprecated endpoints (`/v1/old`).\n- Updated the authentication mechanism to use OAuth 2.0.\n\n### **Documentation**\n- Added detailed guides for API integration.\n- Improved inline code comments.",
|
||||||
|
"extraInfo": {
|
||||||
|
"created": 1747598260799,
|
||||||
|
"state": "success",
|
||||||
|
"serverName": "deepseek",
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 7946,
|
||||||
|
"completion_tokens": 136,
|
||||||
|
"total_tokens": 8082,
|
||||||
|
"prompt_tokens_details": {
|
||||||
|
"cached_tokens": 896
|
||||||
|
},
|
||||||
|
"prompt_cache_hit_tokens": 896,
|
||||||
|
"prompt_cache_miss_tokens": 7050
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"name": "openmcp",
|
"name": "openmcp",
|
||||||
"displayName": "OpenMCP",
|
"displayName": "OpenMCP",
|
||||||
"description": "An all in one MCP Client/TestTool",
|
"description": "An all in one MCP Client/TestTool",
|
||||||
"version": "0.0.8",
|
"version": "0.1.0",
|
||||||
"publisher": "kirigaya",
|
"publisher": "kirigaya",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "kirigaya",
|
"name": "kirigaya",
|
||||||
|
@ -19,6 +19,11 @@ interface AddCommandListenerOption {
|
|||||||
once: boolean // 只调用一次就销毁
|
once: boolean // 只调用一次就销毁
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ICommandRequestData {
|
||||||
|
clientId?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
export class MessageBridge {
|
export class MessageBridge {
|
||||||
private ws: WebSocket | null = null;
|
private ws: WebSocket | null = null;
|
||||||
private handlers = new Map<string, Set<CommandHandler>>();
|
private handlers = new Map<string, Set<CommandHandler>>();
|
||||||
@ -206,7 +211,7 @@ export class MessageBridge {
|
|||||||
* @param data
|
* @param data
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public commandRequest<T = any>(command: string, data?: any): Promise<RestFulResponse<T>> {
|
public commandRequest<T = any>(command: string, data?: ICommandRequestData): Promise<RestFulResponse<T>> {
|
||||||
return new Promise<RestFulResponse>((resolve, reject) => {
|
return new Promise<RestFulResponse>((resolve, reject) => {
|
||||||
this.addCommandListener(command, (data) => {
|
this.addCommandListener(command, (data) => {
|
||||||
resolve(data as RestFulResponse);
|
resolve(data as RestFulResponse);
|
||||||
|
@ -3,6 +3,7 @@ import { pinkLog } from "@/views/setting/util";
|
|||||||
import { debugModes, tabs } from "@/components/main-panel/panel";
|
import { debugModes, tabs } from "@/components/main-panel/panel";
|
||||||
import { markRaw, ref, nextTick } from "vue";
|
import { markRaw, ref, nextTick } from "vue";
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import type { McpClient } from "@/views/connect/connection-item";
|
||||||
|
|
||||||
interface SaveTabItem {
|
interface SaveTabItem {
|
||||||
name: string;
|
name: string;
|
||||||
@ -12,32 +13,30 @@ interface SaveTabItem {
|
|||||||
storage: Record<string, any>;
|
storage: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SaveTab {
|
export interface SaveTab {
|
||||||
tabs: SaveTabItem[]
|
tabs: SaveTabItem[]
|
||||||
currentIndex: number
|
currentIndex: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const panelLoaded = ref(false);
|
export const panelLoaded = ref(false);
|
||||||
|
|
||||||
export function loadPanels() {
|
export async function loadPanels(client: McpClient) {
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const bridge = useMessageBridge();
|
const bridge = useMessageBridge();
|
||||||
|
const { code, msg } = await bridge.commandRequest<SaveTab>('panel/load', {
|
||||||
bridge.addCommandListener('panel/load', data => {
|
clientId: client.clientId
|
||||||
if (data.code !== 200) {
|
});
|
||||||
|
if (code !== 200) {
|
||||||
pinkLog('tabs 加载失败');
|
pinkLog('tabs 加载失败');
|
||||||
console.log(data.msg);
|
console.log(msg);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const persistTab = data.msg as SaveTab;
|
const persistTab = msg;
|
||||||
|
|
||||||
pinkLog('tabs 加载成功');
|
pinkLog('tabs 加载成功');
|
||||||
|
|
||||||
if (persistTab.tabs.length === 0) {
|
if (persistTab.tabs.length === 0) {
|
||||||
// 空的,直接返回不需要管
|
// 空的,直接返回不需要管
|
||||||
panelLoaded.value = true;
|
panelLoaded.value = true;
|
||||||
resolve(void 0);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,13 +62,6 @@ export function loadPanels() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
panelLoaded.value = true;
|
panelLoaded.value = true;
|
||||||
resolve(void 0);
|
|
||||||
}, { once: true });
|
|
||||||
|
|
||||||
bridge.postMessage({
|
|
||||||
command: 'panel/load'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let debounceHandler: NodeJS.Timeout;
|
let debounceHandler: NodeJS.Timeout;
|
||||||
|
@ -25,10 +25,10 @@
|
|||||||
<span>{{ t('connect-sigature') }}</span>
|
<span>{{ t('connect-sigature') }}</span>
|
||||||
<span style="width: 310px;">
|
<span style="width: 310px;">
|
||||||
<el-form :model="connectionArgs" :rules="rules" ref="urlForm">
|
<el-form :model="connectionArgs" :rules="rules" ref="urlForm">
|
||||||
<el-form-item prop="urlString">
|
<el-form-item prop="url">
|
||||||
<div class="input-with-label">
|
<div class="input-with-label">
|
||||||
<span class="input-label">URL</span>
|
<span class="input-label">URL</span>
|
||||||
<el-input v-model="connectionArgs.urlString" placeholder="http://"></el-input>
|
<el-input v-model="connectionArgs.url" placeholder="http://"></el-input>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="oauth">
|
<el-form-item prop="oauth">
|
||||||
@ -65,7 +65,7 @@ const rules = reactive<FormRules>({
|
|||||||
oauth: [
|
oauth: [
|
||||||
{ required: false, trigger: 'blur' }
|
{ required: false, trigger: 'blur' }
|
||||||
],
|
],
|
||||||
urlString: [
|
url: [
|
||||||
{ required: true, message: 'URL不能为空', trigger: 'blur' }
|
{ required: true, message: 'URL不能为空', trigger: 'blur' }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
|
import { useMessageBridge } from "@/api/message-bridge";
|
||||||
import { reactive, type Reactive } from "vue";
|
import { reactive, type Reactive } from "vue";
|
||||||
|
import type { IConnectionResult, ConnectionTypeOptionItem, IConnectionArgs, IConnectionEnvironment, McpOptions } from "./type";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { loadPanels, type SaveTab } from "@/hook/panel";
|
||||||
|
import { getPlatform } from "@/api/platform";
|
||||||
|
|
||||||
export type ConnectionType = 'STDIO' | 'SSE' | 'STREAMABLE_HTTP';
|
|
||||||
|
|
||||||
export interface ConnectionTypeOptionItem {
|
|
||||||
value: ConnectionType;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const connectionSelectDataViewOption: ConnectionTypeOptionItem[] = [
|
export const connectionSelectDataViewOption: ConnectionTypeOptionItem[] = [
|
||||||
{
|
{
|
||||||
@ -22,37 +21,266 @@ export const connectionSelectDataViewOption: ConnectionTypeOptionItem[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
export interface IConnectionArgs {
|
export async function getLaunchSignature(platform: string): Promise<IConnectionArgs[]> {
|
||||||
type: ConnectionType;
|
const bridge = useMessageBridge();
|
||||||
commandString?: string;
|
const { code, msg } = await bridge.commandRequest(platform + '/launch-signature');
|
||||||
cwd?: string;
|
|
||||||
urlString?: string;
|
if (code !== 200) {
|
||||||
|
const message = msg.toString();
|
||||||
|
ElMessage.error(message);
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class McpClient {
|
// 判断一下版本,新版本的 msg 应该是数组,老版本是对象
|
||||||
public clientId?: string;
|
// 返回的数组的第一个为主节点,其余为从节点
|
||||||
public name?: string;
|
if (Array.isArray(msg)) {
|
||||||
public version?: string;
|
return msg;
|
||||||
public connectionArgs: Reactive<IConnectionArgs>;
|
}
|
||||||
|
return [msg];
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
|
||||||
|
|
||||||
|
export class McpClient {
|
||||||
|
|
||||||
|
public connectionArgs: Reactive<IConnectionArgs>;
|
||||||
|
public connectionResult: Reactive<IConnectionResult>;
|
||||||
|
|
||||||
|
public presetsEnvironment: string[] = ['HOME', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'USER'];
|
||||||
|
public connectionEnvironment: Reactive<IConnectionEnvironment>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public clientVersion: string = '0.0.1',
|
||||||
|
public clientNamePrefix: string = 'openmcp.connect'
|
||||||
|
) {
|
||||||
|
// 连接入参
|
||||||
this.connectionArgs = reactive({
|
this.connectionArgs = reactive({
|
||||||
type: 'STDIO',
|
type: 'STDIO',
|
||||||
commandString: '',
|
commandString: '',
|
||||||
cwd: '',
|
cwd: '',
|
||||||
urlString: ''
|
url: '',
|
||||||
|
oauth: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// 连接出参
|
||||||
|
this.connectionResult = reactive({
|
||||||
|
success: false,
|
||||||
|
status: 'disconnected',
|
||||||
|
clientId: '',
|
||||||
|
name: '',
|
||||||
|
version: '',
|
||||||
|
logString: []
|
||||||
|
});
|
||||||
|
|
||||||
|
// 环境变量
|
||||||
|
this.connectionEnvironment = reactive({
|
||||||
|
data: [],
|
||||||
|
newKey: '',
|
||||||
|
newValue: ''
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect() {
|
async acquireConnectionSignature(args: IConnectionArgs) {
|
||||||
|
this.connectionArgs.type = args.type;
|
||||||
|
this.connectionArgs.commandString = args.commandString || '';
|
||||||
|
this.connectionArgs.cwd = args.cwd || '';
|
||||||
|
this.connectionArgs.url = args.url || '';
|
||||||
|
this.connectionArgs.oauth = args.oauth || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
get clientId() {
|
||||||
|
return this.connectionResult.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return this.connectionResult.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get version() {
|
||||||
|
return this.connectionResult.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
return this.connectionResult.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
get connected() {
|
||||||
|
return this.connectionResult.success;
|
||||||
|
}
|
||||||
|
|
||||||
|
get env() {
|
||||||
|
const env = {} as Record<string, string>;
|
||||||
|
this.connectionEnvironment.data.forEach(item => {
|
||||||
|
env[item.key] = item.value;
|
||||||
|
});
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get commandAndArgs() {
|
||||||
|
const commandString = this.connectionArgs.commandString;
|
||||||
|
|
||||||
|
if (!commandString) {
|
||||||
|
return { command: '', args: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = commandString.split(' ');
|
||||||
|
const command = args.shift() || '';
|
||||||
|
|
||||||
|
return { command, args };
|
||||||
|
}
|
||||||
|
|
||||||
|
get connectOption() {
|
||||||
|
const { command, args } = this.commandAndArgs;
|
||||||
|
const env = this.env;
|
||||||
|
const url = this.connectionArgs.url;
|
||||||
|
const oauth = this.connectionArgs.oauth;
|
||||||
|
const connectionType = this.connectionArgs.type;
|
||||||
|
|
||||||
|
const clientName = this.clientNamePrefix + '.' + this.connectionArgs.type;
|
||||||
|
const clientVersion = this.clientVersion;
|
||||||
|
|
||||||
|
const option: McpOptions = {
|
||||||
|
connectionType,
|
||||||
|
command,
|
||||||
|
args,
|
||||||
|
url,
|
||||||
|
oauth,
|
||||||
|
clientName,
|
||||||
|
clientVersion,
|
||||||
|
env,
|
||||||
|
serverInfo: {
|
||||||
|
name: this.connectionResult.name,
|
||||||
|
version: this.connectionResult.version
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async connect(platform: string) {
|
||||||
|
const bridge = useMessageBridge();
|
||||||
|
const { code, msg } = await bridge.commandRequest<IConnectionResult>('connect', this.connectOption);
|
||||||
|
|
||||||
|
this.connectionResult.success = (code === 200);
|
||||||
|
|
||||||
|
if (code !== 200) {
|
||||||
|
const message = msg.toString();
|
||||||
|
this.connectionResult.logString.push({
|
||||||
|
type: 'error',
|
||||||
|
message
|
||||||
|
});
|
||||||
|
|
||||||
|
ElMessage.error(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connectionResult.status = msg.status;
|
||||||
|
this.connectionResult.clientId = msg.clientId;
|
||||||
|
this.connectionResult.name = msg.name;
|
||||||
|
this.connectionResult.version = msg.version;
|
||||||
|
|
||||||
|
// 同步成功的连接参数到后端,更新 vscode treeview 中的列表
|
||||||
|
const deserializeOption = JSON.parse(JSON.stringify(this.connectOption));
|
||||||
|
|
||||||
|
bridge.postMessage({
|
||||||
|
command: platform + '/update-connection-sigature',
|
||||||
|
data: deserializeOption
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 处理环境变量开关
|
||||||
|
* - 开启时,刷新预设环境变量的数值
|
||||||
|
* - 关闭时,清空预设环境变量的数值
|
||||||
|
* @param enabled
|
||||||
|
*/
|
||||||
|
public async handleEnvSwitch(enabled: boolean) {
|
||||||
|
const presetVars = this.presetsEnvironment;
|
||||||
|
if (enabled) {
|
||||||
|
const values = await this.lookupEnvVar(presetVars);
|
||||||
|
|
||||||
|
if (values) {
|
||||||
|
// 将 key values 合并进 connectionEnv.data 中
|
||||||
|
// 若已有相同的 key, 则替换 value
|
||||||
|
for (let i = 0; i < presetVars.length; i++) {
|
||||||
|
const key = presetVars[i];
|
||||||
|
const value = values[i];
|
||||||
|
const sameNameItems = this.connectionEnvironment.data.filter(item => item.key === key);
|
||||||
|
if (sameNameItems.length > 0) {
|
||||||
|
const conflictItem = sameNameItems[0];
|
||||||
|
conflictItem.value = value;
|
||||||
|
} else {
|
||||||
|
this.connectionEnvironment.data.push({
|
||||||
|
key: key, value: value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 清空 connectionEnv.data 中所有 key 为 presetVars 的项
|
||||||
|
const reserveItems = this.connectionEnvironment.data.filter(item => !presetVars.includes(item.key));
|
||||||
|
this.connectionEnvironment.data = reserveItems;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用于描述一个连接的数据结构
|
|
||||||
export interface McpServer {
|
|
||||||
type: ConnectionType;
|
|
||||||
clientId: string;
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 查询环境变量
|
||||||
|
* @param varNames
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async lookupEnvVar(varNames: string[]) {
|
||||||
|
const bridge = useMessageBridge();
|
||||||
|
const { code, msg } = await bridge.commandRequest('lookup-env-var', { keys: varNames });
|
||||||
|
|
||||||
|
if (code === 200) {
|
||||||
|
this.connectionResult.logString.push({
|
||||||
|
type: 'info',
|
||||||
|
message: '预设环境变量同步完成'
|
||||||
|
});
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
} else {
|
||||||
|
this.connectionResult.logString.push({
|
||||||
|
type: 'error',
|
||||||
|
message: '预设环境变量同步失败: ' + msg
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class McpClientAdapter {
|
||||||
|
public clients: McpClient[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public platform: string
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async launch() {
|
||||||
|
const launchSignature = await getLaunchSignature(this.platform);
|
||||||
|
|
||||||
|
for (const item of launchSignature) {
|
||||||
|
const client = new McpClient();
|
||||||
|
|
||||||
|
// 同步连接参数
|
||||||
|
await client.acquireConnectionSignature(item);
|
||||||
|
|
||||||
|
// 同步环境变量
|
||||||
|
await client.handleEnvSwitch(true);
|
||||||
|
|
||||||
|
// 连接
|
||||||
|
await client.connect(this.platform);
|
||||||
|
|
||||||
|
this.clients.push(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async loadPanels() {
|
||||||
|
const masterNode = this.clients[0];
|
||||||
|
await loadPanels(masterNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const platform = getPlatform();
|
||||||
|
export const mcpClientAdapter = new McpClientAdapter(platform);
|
@ -1,386 +0,0 @@
|
|||||||
import { useMessageBridge } from '@/api/message-bridge';
|
|
||||||
import { reactive, ref } from 'vue';
|
|
||||||
import { pinkLog } from '../setting/util';
|
|
||||||
import { ElLoading, ElMessage } from 'element-plus';
|
|
||||||
import { getPlatform, type OpenMcpSupportPlatform } from '@/api/platform';
|
|
||||||
import { getTour, loadSetting } from '@/hook/setting';
|
|
||||||
import { loadPanels } from '@/hook/panel';
|
|
||||||
import type { ConnectionType } from './connection-item';
|
|
||||||
|
|
||||||
export const connectionMethods = reactive<{
|
|
||||||
current: ConnectionType,
|
|
||||||
data: {
|
|
||||||
value: ConnectionType,
|
|
||||||
label: string
|
|
||||||
}[]
|
|
||||||
}>({
|
|
||||||
current: 'STDIO',
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
value: 'STDIO',
|
|
||||||
label: 'STDIO'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'SSE',
|
|
||||||
label: 'SSE'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'STREAMABLE_HTTP',
|
|
||||||
label: 'STREAMABLE_HTTP'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
export const connectionSettingRef = ref<any>(null);
|
|
||||||
export const connectionLogRef = ref<any>(null);
|
|
||||||
|
|
||||||
// 主 mcp 服务器的连接参数
|
|
||||||
export const connectionArgs = reactive({
|
|
||||||
commandString: '',
|
|
||||||
cwd: '',
|
|
||||||
oauth: '',
|
|
||||||
urlString: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface EnvItem {
|
|
||||||
key: string
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IConnectionEnv {
|
|
||||||
data: EnvItem[]
|
|
||||||
newKey: string
|
|
||||||
newValue: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConnectionResult {
|
|
||||||
status: string
|
|
||||||
clientId: string
|
|
||||||
name: string
|
|
||||||
version: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const connectionEnv = reactive<IConnectionEnv>({
|
|
||||||
data: [],
|
|
||||||
newKey: '',
|
|
||||||
newValue: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
export function makeEnv() {
|
|
||||||
const env = {} as Record<string, string>;
|
|
||||||
connectionEnv.data.forEach(item => {
|
|
||||||
env[item.key] = item.value;
|
|
||||||
});
|
|
||||||
return env;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 定义命令行参数接口
|
|
||||||
export interface McpOptions {
|
|
||||||
connectionType: ConnectionType;
|
|
||||||
// STDIO 特定选项
|
|
||||||
command?: string;
|
|
||||||
args?: string[];
|
|
||||||
cwd?: string;
|
|
||||||
env?: Record<string, string>;
|
|
||||||
// SSE 特定选项
|
|
||||||
url?: string;
|
|
||||||
// 通用客户端选项
|
|
||||||
clientName?: string;
|
|
||||||
clientVersion?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 试图启动 mcp 服务器,它会
|
|
||||||
* 1. 请求启动参数
|
|
||||||
* 2. 启动 mcp 服务器
|
|
||||||
* 3. 将本次的启动参数同步到本地
|
|
||||||
* @param option
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export async function doConnect(
|
|
||||||
option: {
|
|
||||||
namespace: OpenMcpSupportPlatform
|
|
||||||
updateCommandString?: boolean
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const {
|
|
||||||
// updateCommandString 为 true 代表是初始化阶段
|
|
||||||
namespace,
|
|
||||||
updateCommandString = true
|
|
||||||
} = option;
|
|
||||||
|
|
||||||
// 如果是初始化,则需要请求启动参数
|
|
||||||
if (updateCommandString) {
|
|
||||||
pinkLog('请求启动参数');
|
|
||||||
const connectionItem = await getLaunchSignature(namespace + '/launch-signature');
|
|
||||||
connectionMethods.current = connectionItem.type;
|
|
||||||
connectionArgs.commandString = connectionItem.commandString || '';
|
|
||||||
connectionArgs.cwd = connectionItem.cwd || '';
|
|
||||||
connectionArgs.oauth = connectionItem.oauth || '';
|
|
||||||
connectionArgs.urlString = connectionItem.url || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connectionMethods.current === 'STDIO') {
|
|
||||||
return await launchStdio(namespace);
|
|
||||||
} else {
|
|
||||||
return await launchRemote(namespace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function launchStdio(namespace: string) {
|
|
||||||
const bridge = useMessageBridge();
|
|
||||||
const env = makeEnv();
|
|
||||||
|
|
||||||
const commandComponents = connectionArgs.commandString.split(/\s+/g);
|
|
||||||
const command = commandComponents[0];
|
|
||||||
commandComponents.shift();
|
|
||||||
|
|
||||||
const connectOption = {
|
|
||||||
connectionType: 'STDIO',
|
|
||||||
command: command,
|
|
||||||
args: commandComponents,
|
|
||||||
cwd: connectionArgs.cwd,
|
|
||||||
clientName: 'openmcp.connect.STDIO',
|
|
||||||
clientVersion: '0.0.1',
|
|
||||||
env
|
|
||||||
};
|
|
||||||
|
|
||||||
const { code, msg } = await bridge.commandRequest<ConnectionResult>('connect', connectOption);
|
|
||||||
|
|
||||||
connectionResult.success = (code === 200);
|
|
||||||
|
|
||||||
if (code === 200) {
|
|
||||||
|
|
||||||
const message = `connect to ${msg.name} ${msg.version} success, clientId: ${msg.clientId}`;
|
|
||||||
connectionResult.logString.push({ type: 'info', message });
|
|
||||||
|
|
||||||
connectionResult.serverInfo.name = msg.name || '';
|
|
||||||
connectionResult.serverInfo.version = msg.version || '';
|
|
||||||
connectionResult.clientId = msg.clientId || '';
|
|
||||||
|
|
||||||
// 同步信息到 后端
|
|
||||||
const commandComponents = connectionArgs.commandString.split(/\s+/g);
|
|
||||||
const command = commandComponents[0];
|
|
||||||
commandComponents.shift();
|
|
||||||
|
|
||||||
const clientStdioConnectionItem = {
|
|
||||||
serverInfo: connectionResult.serverInfo,
|
|
||||||
connectionType: 'STDIO',
|
|
||||||
name: 'openmcp.connect.STDIO',
|
|
||||||
command: command,
|
|
||||||
args: commandComponents,
|
|
||||||
cwd: connectionArgs.cwd,
|
|
||||||
env
|
|
||||||
};
|
|
||||||
|
|
||||||
bridge.postMessage({
|
|
||||||
command: namespace + '/update-connection-sigature',
|
|
||||||
data: JSON.parse(JSON.stringify(clientStdioConnectionItem))
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const messaage = msg.toString();
|
|
||||||
connectionResult.logString.push({
|
|
||||||
type: 'error',
|
|
||||||
message: messaage
|
|
||||||
});
|
|
||||||
|
|
||||||
ElMessage.error(messaage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function launchRemote(namespace: string) {
|
|
||||||
const bridge = useMessageBridge();
|
|
||||||
const env = makeEnv();
|
|
||||||
|
|
||||||
const connectOption: McpOptions = {
|
|
||||||
connectionType: connectionMethods.current,
|
|
||||||
url: connectionArgs.urlString,
|
|
||||||
clientName: 'openmcp.connect.' + connectionMethods.current,
|
|
||||||
clientVersion: '0.0.1',
|
|
||||||
env
|
|
||||||
};
|
|
||||||
|
|
||||||
const { code, msg } = await bridge.commandRequest<ConnectionResult>('connect', connectOption);
|
|
||||||
|
|
||||||
connectionResult.success = (code === 200);
|
|
||||||
|
|
||||||
if (code === 200) {
|
|
||||||
const message = `connect to ${msg.name} ${msg.version} success, clientId: ${msg.clientId}`;
|
|
||||||
|
|
||||||
connectionResult.logString.push({
|
|
||||||
type: 'info',
|
|
||||||
message: message
|
|
||||||
});
|
|
||||||
|
|
||||||
connectionResult.serverInfo.name = msg.name || '';
|
|
||||||
connectionResult.serverInfo.version = msg.version || '';
|
|
||||||
connectionResult.clientId = msg.clientId || '';
|
|
||||||
|
|
||||||
// 同步信息到 vscode
|
|
||||||
const clientSseConnectionItem = {
|
|
||||||
serverInfo: connectionResult.serverInfo,
|
|
||||||
connectionType: connectionMethods.current,
|
|
||||||
name: 'openmcp.connect.' + connectionMethods.current,
|
|
||||||
url: connectionArgs.urlString,
|
|
||||||
oauth: connectionArgs.oauth,
|
|
||||||
env: env
|
|
||||||
};
|
|
||||||
|
|
||||||
bridge.postMessage({
|
|
||||||
command: namespace + '/update-connection-sigature',
|
|
||||||
data: JSON.parse(JSON.stringify(clientSseConnectionItem))
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const message = msg.toString();
|
|
||||||
connectionResult.logString.push({
|
|
||||||
type: 'error',
|
|
||||||
message: message
|
|
||||||
});
|
|
||||||
|
|
||||||
ElMessage.error(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function getLaunchSignature(signatureName: string) {
|
|
||||||
const bridge = useMessageBridge();
|
|
||||||
const { code, msg } = await bridge.commandRequest(signatureName);
|
|
||||||
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function doReconnect() {
|
|
||||||
// TODO: finish this
|
|
||||||
console.log();
|
|
||||||
}
|
|
||||||
|
|
||||||
export const connectionResult = reactive<{
|
|
||||||
success: boolean,
|
|
||||||
logString: { type: 'info' | 'error' | 'warning', message: string }[],
|
|
||||||
serverInfo: {
|
|
||||||
name: string,
|
|
||||||
version: string
|
|
||||||
},
|
|
||||||
clientId: string
|
|
||||||
}>({
|
|
||||||
success: false,
|
|
||||||
logString: [],
|
|
||||||
serverInfo: {
|
|
||||||
name: '',
|
|
||||||
version: ''
|
|
||||||
},
|
|
||||||
clientId: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const envVarStatus = {
|
|
||||||
launched: false
|
|
||||||
};
|
|
||||||
|
|
||||||
function lookupEnvVar(varNames: string[]) {
|
|
||||||
const bridge = useMessageBridge();
|
|
||||||
|
|
||||||
return new Promise<string[] | undefined>((resolve, reject) => {
|
|
||||||
bridge.addCommandListener('lookup-env-var', data => {
|
|
||||||
const { code, msg } = data;
|
|
||||||
|
|
||||||
if (code === 200) {
|
|
||||||
connectionResult.logString.push({
|
|
||||||
type: 'info',
|
|
||||||
message: '预设环境变量同步完成'
|
|
||||||
});
|
|
||||||
|
|
||||||
resolve(msg);
|
|
||||||
} else {
|
|
||||||
connectionResult.logString.push({
|
|
||||||
type: 'error',
|
|
||||||
message: '预设环境变量同步失败: ' + msg
|
|
||||||
});
|
|
||||||
|
|
||||||
resolve(undefined);
|
|
||||||
}
|
|
||||||
}, { once: true });
|
|
||||||
|
|
||||||
console.log(varNames);
|
|
||||||
|
|
||||||
|
|
||||||
bridge.postMessage({
|
|
||||||
command: 'lookup-env-var',
|
|
||||||
data: {
|
|
||||||
keys: varNames
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export async function handleEnvSwitch(enabled: boolean) {
|
|
||||||
const presetVars = ['HOME', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'USER'];
|
|
||||||
|
|
||||||
if (enabled) {
|
|
||||||
const values = await lookupEnvVar(presetVars);
|
|
||||||
|
|
||||||
if (values) {
|
|
||||||
// 将 key values 合并进 connectionEnv.data 中
|
|
||||||
// 若已有相同的 key, 则替换 value
|
|
||||||
for (let i = 0; i < presetVars.length; i++) {
|
|
||||||
const key = presetVars[i];
|
|
||||||
const value = values[i];
|
|
||||||
const sameNameItems = connectionEnv.data.filter(item => item.key === key);
|
|
||||||
if (sameNameItems.length > 0) {
|
|
||||||
const conflictItem = sameNameItems[0];
|
|
||||||
conflictItem.value = value;
|
|
||||||
} else {
|
|
||||||
connectionEnv.data.push({
|
|
||||||
key: key, value: value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 清空 connectionEnv.data 中所有 key 为 presetVars 的项
|
|
||||||
const reserveItems = connectionEnv.data.filter(item => !presetVars.includes(item.key));
|
|
||||||
connectionEnv.data = reserveItems;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadEnvVar() {
|
|
||||||
return await handleEnvSwitch(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function initialise() {
|
|
||||||
|
|
||||||
pinkLog('准备请求设置');
|
|
||||||
|
|
||||||
const loading = ElLoading.service({
|
|
||||||
fullscreen: true,
|
|
||||||
lock: true,
|
|
||||||
text: 'Loading',
|
|
||||||
background: 'rgba(0, 0, 0, 0.7)'
|
|
||||||
});
|
|
||||||
const platform = getPlatform();
|
|
||||||
|
|
||||||
// 加载全局设置
|
|
||||||
loadSetting();
|
|
||||||
|
|
||||||
// 设置环境变量
|
|
||||||
await loadEnvVar();
|
|
||||||
|
|
||||||
// 获取引导状态
|
|
||||||
await getTour();
|
|
||||||
|
|
||||||
// 尝试进行初始化连接
|
|
||||||
await doConnect({
|
|
||||||
namespace: platform,
|
|
||||||
updateCommandString: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// loading panels
|
|
||||||
await loadPanels();
|
|
||||||
|
|
||||||
loading.close();
|
|
||||||
}
|
|
@ -44,7 +44,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent, ref } from 'vue';
|
import { defineComponent, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { connectionEnv, type EnvItem, handleEnvSwitch } from './connection';
|
|
||||||
|
|
||||||
defineComponent({ name: 'env-var' });
|
defineComponent({ name: 'env-var' });
|
||||||
|
|
||||||
|
30
renderer/src/views/connect/index.ts
Normal file
30
renderer/src/views/connect/index.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { getTour, loadSetting } from "@/hook/setting";
|
||||||
|
import { ElLoading } from "element-plus";
|
||||||
|
import { pinkLog } from "../setting/util";
|
||||||
|
import { mcpClientAdapter } from "./connection-item";
|
||||||
|
|
||||||
|
export async function initialise() {
|
||||||
|
|
||||||
|
pinkLog('准备请求设置');
|
||||||
|
|
||||||
|
const loading = ElLoading.service({
|
||||||
|
fullscreen: true,
|
||||||
|
lock: true,
|
||||||
|
text: 'Loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载全局设置
|
||||||
|
loadSetting();
|
||||||
|
|
||||||
|
// 获取引导状态
|
||||||
|
await getTour();
|
||||||
|
|
||||||
|
// 尝试进行初始化连接
|
||||||
|
await mcpClientAdapter.launch();
|
||||||
|
|
||||||
|
// loading panels
|
||||||
|
await mcpClientAdapter.loadPanels();
|
||||||
|
|
||||||
|
loading.close();
|
||||||
|
}
|
80
renderer/src/views/connect/type.ts
Normal file
80
renderer/src/views/connect/type.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
|
||||||
|
export type ConnectionType = 'STDIO' | 'SSE' | 'STREAMABLE_HTTP';
|
||||||
|
|
||||||
|
export interface ConnectionTypeOptionItem {
|
||||||
|
value: ConnectionType;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IConnectionArgs {
|
||||||
|
type: ConnectionType;
|
||||||
|
commandString?: string;
|
||||||
|
cwd?: string;
|
||||||
|
url?: string;
|
||||||
|
oauth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IConnectionResult {
|
||||||
|
success: boolean;
|
||||||
|
status: string
|
||||||
|
clientId: string
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
logString: {
|
||||||
|
type: 'info' | 'error' | 'warning',
|
||||||
|
message: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export interface McpOptions {
|
||||||
|
connectionType: ConnectionType;
|
||||||
|
command?: string;
|
||||||
|
|
||||||
|
// STDIO 特定选项
|
||||||
|
args?: string[];
|
||||||
|
cwd?: string;
|
||||||
|
env?: Record<string, string>;
|
||||||
|
// SSE 特定选项
|
||||||
|
url?: string;
|
||||||
|
oauth?: any;
|
||||||
|
|
||||||
|
// 通用客户端选项
|
||||||
|
clientName?: string;
|
||||||
|
clientVersion?: string;
|
||||||
|
serverInfo: {
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnvItem {
|
||||||
|
key: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IConnectionEnvironment {
|
||||||
|
data: EnvItem[]
|
||||||
|
newKey: string
|
||||||
|
newValue: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IConnectionArgs {
|
||||||
|
type: ConnectionType;
|
||||||
|
commandString?: string;
|
||||||
|
cwd?: string;
|
||||||
|
url?: string;
|
||||||
|
oauth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface ConnectionResult {
|
||||||
|
status: string
|
||||||
|
clientId: string
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user