This commit is contained in:
锦恢 2025-05-20 03:47:15 +08:00
parent 8b3816d05c
commit 4d459464d3
17 changed files with 223 additions and 141 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "openmcp", "name": "openmcp",
"version": "0.0.9", "version": "0.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "openmcp", "name": "openmcp",
"version": "0.0.9", "version": "0.1.0",
"workspaces": [ "workspaces": [
"service", "service",
"renderer", "renderer",

View File

@ -55,8 +55,13 @@ onMounted(async () => {
// } // }
// //
console.log('enter');
await bridge.awaitForWebsocket(); await bridge.awaitForWebsocket();
console.log('enter2');
// //
if (!privilegeStatus.allow) { if (!privilegeStatus.allow) {
return; return;

View File

@ -77,6 +77,8 @@ export class MessageBridge {
throw new Error('setupSignature must be a string'); throw new Error('setupSignature must be a string');
} }
console.log(wsUrl);
this.ws = new WebSocket(wsUrl); this.ws = new WebSocket(wsUrl);
const ws = this.ws; const ws = this.ws;
@ -117,6 +119,7 @@ export class MessageBridge {
} }
public async awaitForWebsocket() { public async awaitForWebsocket() {
if (this.isConnected) { if (this.isConnected) {
return await this.isConnected; return await this.isConnected;
} }

View File

@ -37,14 +37,16 @@ import { mcpClientAdapter } from '@/views/connect/core';
defineComponent({ name: 'connected' }); defineComponent({ name: 'connected' });
const { t } = useI18n(); const { t } = useI18n();
const client = mcpClientAdapter.masterNode; const client = computed(() => mcpClientAdapter.masterNode);
console.log(client);
const fullDisplayServerName = computed(() => { const fullDisplayServerName = computed(() => {
return client.connectionResult.name + '/' + client.connectionResult.version; return client.value.connectionResult.name + '/' + client.value.connectionResult.version;
}); });
const displayServerName = computed(() => { const displayServerName = computed(() => {
const name = client.connectionResult.name; const name = client.value.connectionResult.name;
if (name.length <= 3) return name; if (name.length <= 3) return name;
// //

View File

@ -1,6 +1,6 @@
<template> <template>
<!-- STDIO 模式下的命令输入 --> <!-- STDIO 模式下的命令输入 -->
<div class="connection-option" v-if="client.connectionArgs.type === 'STDIO'"> <div class="connection-option" v-if="client.connectionArgs.connectionType === 'STDIO'">
<span>{{ t('connect-sigature') }}</span> <span>{{ t('connect-sigature') }}</span>
<span style="width: 310px;"> <span style="width: 310px;">
<el-form :model="client.connectionArgs" :rules="rules" ref="stdioForm"> <el-form :model="client.connectionArgs" :rules="rules" ref="stdioForm">
@ -43,7 +43,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref } from 'vue'; import { computed, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus'; import type { FormInstance, FormRules } from 'element-plus';
@ -58,7 +58,7 @@ const props = defineProps({
} }
}); });
const client = mcpClientAdapter.clients[props.index]; const client = computed(() => mcpClientAdapter.clients[props.index]);
const stdioForm = ref<FormInstance>() const stdioForm = ref<FormInstance>()
const urlForm = ref<FormInstance>() const urlForm = ref<FormInstance>()
@ -82,7 +82,7 @@ const rules = reactive<FormRules>({
// //
const validateForm = async () => { const validateForm = async () => {
try { try {
if (client.connectionArgs.type === 'STDIO') { if (client.value.connectionArgs.connectionType === 'STDIO') {
await stdioForm.value?.validate() await stdioForm.value?.validate()
} else { } else {
await urlForm.value?.validate() await urlForm.value?.validate()

View File

@ -42,7 +42,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent, ref } from 'vue'; import { computed, defineComponent, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { mcpClientAdapter } from './core'; import { mcpClientAdapter } from './core';
import type { EnvItem } from './type'; import type { EnvItem } from './type';
@ -55,7 +55,7 @@ const props = defineProps({
} }
}); });
const client = mcpClientAdapter.clients[props.index]; const client = computed(() => mcpClientAdapter.clients[props.index]);
const { t } = useI18n(); const { t } = useI18n();
@ -64,24 +64,24 @@ const { t } = useI18n();
*/ */
function addEnvVar() { function addEnvVar() {
// key // key
const currentKey = client.connectionEnvironment.newKey; const currentKey = client.value.connectionEnvironment.newKey;
const currentValue = client.connectionEnvironment.newValue; const currentValue = client.value.connectionEnvironment.newValue;
if (currentKey.length === 0 || currentValue.length === 0) { if (currentKey.length === 0 || currentValue.length === 0) {
return; return;
} }
const sameNameItems = client.connectionEnvironment.data.filter(item => item.key === currentKey); const sameNameItems = client.value.connectionEnvironment.data.filter(item => item.key === currentKey);
if (sameNameItems.length > 0) { if (sameNameItems.length > 0) {
const conflictItem = sameNameItems[0]; const conflictItem = sameNameItems[0];
conflictItem.value = currentValue; conflictItem.value = currentValue;
} else { } else {
client.connectionEnvironment.data.push({ client.value.connectionEnvironment.data.push({
key: currentKey, value: currentValue key: currentKey, value: currentValue
}); });
client.connectionEnvironment.newKey = ''; client.value.connectionEnvironment.newKey = '';
client.connectionEnvironment.newValue = ''; client.value.connectionEnvironment.newValue = '';
} }
} }
@ -90,8 +90,8 @@ function addEnvVar() {
*/ */
function deleteEnvVar(option: EnvItem) { function deleteEnvVar(option: EnvItem) {
const currentKey = option.key; const currentKey = option.key;
const reserveItems = client.connectionEnvironment.data.filter(item => item.key !== currentKey); const reserveItems = client.value.connectionEnvironment.data.filter(item => item.key !== currentKey);
client.connectionEnvironment.data = reserveItems; client.value.connectionEnvironment.data = reserveItems;
} }
const envEnabled = ref(true); const envEnabled = ref(true);

View File

@ -12,7 +12,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { mcpClientAdapter } from './core'; import { mcpClientAdapter } from './core';
@ -24,7 +24,7 @@ const props = defineProps({
} }
}); });
const client = mcpClientAdapter.clients[props.index]; const client = computed(() => mcpClientAdapter.clients[props.index]);
const { t } = useI18n(); const { t } = useI18n();
@ -32,7 +32,7 @@ const { t } = useI18n();
<style> <style>
.connection-option { .connection-option {
height: 90%; height: 98%;
} }
.connection-option .el-scrollbar__view { .connection-option .el-scrollbar__view {

View File

@ -2,7 +2,7 @@
<div class="connection-option"> <div class="connection-option">
<span>{{ t('connection-method') }}</span> <span>{{ t('connection-method') }}</span>
<span style="width: 200px;"> <span style="width: 200px;">
<el-select name="language-setting" class="language-setting" v-model="client.connectionArgs.type"> <el-select name="language-setting" class="language-setting" v-model="client.connectionArgs.connectionType">
<el-option v-for="option in connectionSelectDataViewOption" :value="option.value" :label="option.label" <el-option v-for="option in connectionSelectDataViewOption" :value="option.value" :label="option.label"
:key="option.label"></el-option> :key="option.label"></el-option>
</el-select> </el-select>
@ -11,7 +11,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { connectionSelectDataViewOption, mcpClientAdapter } from './core'; import { connectionSelectDataViewOption, mcpClientAdapter } from './core';
@ -23,7 +23,7 @@ const props = defineProps({
} }
}); });
const client = mcpClientAdapter.clients[props.index]; const client = computed(() => mcpClientAdapter.clients[props.index]);
const { t } = useI18n(); const { t } = useI18n();

View File

@ -28,7 +28,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent, ref } from 'vue'; import { computed, defineComponent, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import ConnectionMethod from './connection-method.vue'; import ConnectionMethod from './connection-method.vue';
@ -48,11 +48,7 @@ const props = defineProps({
} }
}); });
const client = mcpClientAdapter.clients[props.index]; const client = computed(() => mcpClientAdapter.clients[props.index]);
console.log(client);
console.log(client.connectionSettingRef);
const { t } = useI18n(); const { t } = useI18n();
@ -62,7 +58,7 @@ async function connect() {
isLoading.value = true; isLoading.value = true;
const platform = getPlatform(); const platform = getPlatform();
const ok = await client.connect(); const ok = await client.value.connect();
if (ok) { if (ok) {
mcpClientAdapter.saveLaunchSignature(); mcpClientAdapter.saveLaunchSignature();
@ -83,8 +79,9 @@ async function connect() {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 45%; width: 45%;
max-height: 85vh;
min-width: 300px; min-width: 300px;
padding: 20px; padding: 5px 20px;
} }
.connection-option { .connection-option {

View File

@ -43,7 +43,7 @@ export class McpClient {
) { ) {
// 连接入参 // 连接入参
this.connectionArgs = { this.connectionArgs = {
type: 'STDIO', connectionType: 'STDIO',
commandString: '', commandString: '',
cwd: '', cwd: '',
url: '', url: '',
@ -69,7 +69,7 @@ export class McpClient {
} }
async acquireConnectionSignature(args: IConnectionArgs) { async acquireConnectionSignature(args: IConnectionArgs) {
this.connectionArgs.type = args.type; this.connectionArgs.connectionType = args.connectionType;
this.connectionArgs.commandString = args.commandString || ''; this.connectionArgs.commandString = args.commandString || '';
this.connectionArgs.cwd = args.cwd || ''; this.connectionArgs.cwd = args.cwd || '';
this.connectionArgs.url = args.url || ''; this.connectionArgs.url = args.url || '';
@ -123,9 +123,9 @@ export class McpClient {
const url = this.connectionArgs.url; const url = this.connectionArgs.url;
const cwd = this.connectionArgs.cwd; const cwd = this.connectionArgs.cwd;
const oauth = this.connectionArgs.oauth; const oauth = this.connectionArgs.oauth;
const connectionType = this.connectionArgs.type; const connectionType = this.connectionArgs.connectionType;
const clientName = this.clientNamePrefix + '.' + this.connectionArgs.type; const clientName = this.clientNamePrefix + '.' + this.connectionArgs.connectionType;
const clientVersion = this.clientVersion; const clientVersion = this.clientVersion;
const option: McpOptions = { const option: McpOptions = {
@ -163,10 +163,18 @@ export class McpClient {
ElMessage.error(message); ElMessage.error(message);
return false; return false;
} else { } else {
const info = msg.info || '';
if (info) {
this.connectionResult.logString.push({ this.connectionResult.logString.push({
type: 'info', type: 'info',
message: msg.info || '' message: msg.info || ''
}) });
}
this.connectionResult.logString.push({
type: 'info',
message: msg.name + ' ' + msg.version + ' 连接成功'
});
} }
this.connectionResult.status = msg.status; this.connectionResult.status = msg.status;
@ -307,7 +315,7 @@ class McpClientAdapter {
await client.handleEnvSwitch(true); await client.handleEnvSwitch(true);
// 连接 // 连接
const ok = await client.connect(this.platform); const ok = await client.connect();
allOk &&= ok; allOk &&= ok;
this.clients.push(client); this.clients.push(client);

View File

@ -1,19 +1,35 @@
<template> <template>
<div class="connection-container"> <div class="connection-container-wrapper">
<div class="server-list"> <div class="server-list">
<div v-for="(client, index) in mcpClientAdapter.clients" :key="index" class="server-item" <el-segmented
:class="{ 'active': mcpClientAdapter.currentClientIndex === index }" @click="selectServer(index)"> v-model="mcpClientAdapter.currentClientIndex"
:options="serverOptions"
style="background-color: var(--background);"
>
<template #default="scope"
@click="selectServer(scope.item.index)"
>
<div class="server-item" :class="{ 'active': mcpClientAdapter.currentClientIndex === scope.index }">
<span class="connect-status"> <span class="connect-status">
<span v-if="client.connectionResult.success"> <span v-if="scope.item.client.connectionResult.success"
<span class="iconfont icon-connect"></span> class="success"
>
<span class="name">{{ scope.item.client.connectionResult.name }}</span>
<span class="iconfont icon-dui"></span> <span class="iconfont icon-dui"></span>
</span> </span>
<span v-else> <span v-else>
<span class="iconfont icon-connect"></span> <span class="server-name" style="margin-right: 60px;">
<span class="server-name"> Unconnected </span> <span class="iconfont icon-blank"></span>
</span> </span>
<span class="iconfont icon-cuo"></span>
</span>
</span>
<span class="delete-btn" @click.stop="deleteServer(scope.item.index)">
<span class="iconfont icon-delete"></span>
</span> </span>
</div> </div>
</template>
</el-segmented>
<div class="add-server" @click="addServer"> <div class="add-server" @click="addServer">
<span class="iconfont icon-add"></span> <span class="iconfont icon-add"></span>
</div> </div>
@ -25,7 +41,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, computed } from 'vue';
import ConnectionPanel from './connection-panel.vue'; import ConnectionPanel from './connection-panel.vue';
import { McpClient, mcpClientAdapter } from './core'; import { McpClient, mcpClientAdapter } from './core';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
@ -37,22 +53,44 @@ function selectServer(index: number) {
} }
function addServer() { function addServer() {
ElMessage.info('Add server is not implemented yet');
mcpClientAdapter.clients.push(new McpClient()); mcpClientAdapter.clients.push(new McpClient());
mcpClientAdapter.currentClientIndex = mcpClientAdapter.clients.length - 1; mcpClientAdapter.currentClientIndex = mcpClientAdapter.clients.length - 1;
} }
const serverOptions = computed(() => {
return mcpClientAdapter.clients.map((client, index) => ({
value: index,
label: `Server ${index + 1}`,
client,
index
}));
});
function deleteServer(index: number) {
if (mcpClientAdapter.clients.length <= 1) {
ElMessage.warning('至少需要保留一个服务器连接');
return;
}
mcpClientAdapter.clients.splice(index, 1);
if (mcpClientAdapter.currentClientIndex >= mcpClientAdapter.clients.length) {
mcpClientAdapter.currentClientIndex = mcpClientAdapter.clients.length - 1;
}
}
</script> </script>
<style> <style>
.connection-container { .connection-container-wrapper {
display: flex; display: flex;
flex-direction: column;
height: 100%; height: 100%;
} }
.server-list { .server-list {
display: flex;
align-items: center;
width: 150px; width: 150px;
border-right: 1px solid var(--border-color); border-right: 1px solid var(--border-color);
padding: 10px; padding: 15px 25px;
} }
.server-name { .server-name {
@ -74,6 +112,19 @@ function addServer() {
color: white; color: white;
} }
.server-item .name {
width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 5px;
}
.server-item .success {
display: flex;
align-items: center;
}
.server-status { .server-status {
font-size: 12px; font-size: 12px;
} }
@ -100,6 +151,15 @@ function addServer() {
.panel-container { .panel-container {
flex: 1; flex: 1;
padding: 20px; padding: 5px;
}
.delete-btn {
margin-left: 10px;
cursor: pointer;
color: var(--error-color);
}
.delete-btn:hover {
opacity: 0.8;
} }
</style> </style>

View File

@ -6,14 +6,6 @@ export interface ConnectionTypeOptionItem {
label: string; label: string;
} }
export interface IConnectionArgs {
type: ConnectionType;
commandString?: string;
cwd?: string;
url?: string;
oauth?: string;
}
export interface IConnectionResult { export interface IConnectionResult {
info?: string; info?: string;
@ -64,7 +56,7 @@ export interface IConnectionEnvironment {
} }
export interface IConnectionArgs { export interface IConnectionArgs {
type: ConnectionType; connectionType: ConnectionType;
commandString?: string; commandString?: string;
cwd?: string; cwd?: string;
url?: string; url?: string;

View File

@ -2,12 +2,8 @@
<el-scrollbar height="100%"> <el-scrollbar height="100%">
<div class="setting-container"> <div class="setting-container">
<div> <div>
<el-segmented <el-segmented v-model="settingSections.current" :options="settingSections.data" size="large"
v-model="settingSections.current" style="margin: 10px; font-size: 16px; background-color: var(--background);">
:options="settingSections.data"
size="large"
style="margin: 10px; font-size: 16px; background-color: var(--background);"
>
<template #default="scope"> <template #default="scope">
<div class="setting-section-option"> <div class="setting-section-option">
{{ scope.item.label }} {{ scope.item.label }}

View File

@ -27,25 +27,11 @@ const logger = pino({
export type MessageHandler = (message: VSCodeMessage) => void; export type MessageHandler = (message: VSCodeMessage) => void;
interface IStdioLaunchSignature {
type: 'STDIO';
commandString: string;
cwd: string;
}
interface ISSELaunchSignature {
type:'SSE';
url: string;
oauth: string;
}
export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
function refreshConnectionOption(envPath: string) { function refreshConnectionOption(envPath: string) {
const serverPath = path.join(__dirname, '..', '..', 'servers'); const serverPath = path.join(__dirname, '..', '..', 'servers');
const defaultOption = { const defaultOption = {
type:'STDIO', connectionType: 'STDIO',
commandString: 'mcp run main.py', commandString: 'mcp run main.py',
cwd: serverPath cwd: serverPath
}; };
@ -73,6 +59,17 @@ function acquireConnectionOption() {
return refreshConnectionOption(envPath); return refreshConnectionOption(envPath);
} }
// 按照前端的规范,整理成 commandString 样式
option.data = option.data.map((item: any) => {
if (item.connectionType === 'STDIO') {
item.commandString = [item.command, ...item.args]?.join(' ');
} else {
item.url = item.url;
}
return item;
});
return option; return option;
} catch (error) { } catch (error) {

View File

@ -1,4 +1,4 @@
import { execSync, spawnSync } from 'node:child_process'; import { exec, execSync, spawnSync } from 'node:child_process';
import { RequestClientType } from '../common'; import { RequestClientType } from '../common';
import { connect } from './client.service'; import { connect } from './client.service';
import { RestfulResponse } from '../common/index.dto'; import { RestfulResponse } from '../common/index.dto';
@ -6,6 +6,7 @@ import { McpOptions } from './client.dto';
import { randomUUID } from 'node:crypto'; import { randomUUID } from 'node:crypto';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
import * as os from 'os';
export const clientMap: Map<string, RequestClientType> = new Map(); export const clientMap: Map<string, RequestClientType> = new Map();
export function getClient(clientId?: string): RequestClientType | undefined { export function getClient(clientId?: string): RequestClientType | undefined {
@ -17,13 +18,22 @@ export function tryGetRunCommandError(command: string, args: string[] = [], cwd?
console.log('current command', command); console.log('current command', command);
console.log('current args', args); console.log('current args', args);
const commandString = [command, ...args].join(' '); const commandString = command + ' ' + args.join(' ');
const result = execSync(commandString, { const result = spawnSync(commandString, {
cwd: cwd || process.cwd() cwd: cwd || process.cwd(),
}).toString('utf-8'); STDIO: 'pipe',
encoding: 'utf-8'
});
if (result.error) {
return result.error.message;
}
if (result.status !== 0) {
return result.stderr || `Command failed with code ${result.status}`;
}
return null;
return result;
} catch (error) { } catch (error) {
return error instanceof Error ? error.message : String(error); return error instanceof Error ? error.message : String(error);
} }
@ -48,8 +58,15 @@ function getCommandFileExt(option: McpOptions) {
return undefined; return undefined;
} }
function collectAllOutputExec(command: string, cwd: string) {
return new Promise<string>((resolve, reject) => {
exec(command, { cwd }, (error, stdout, stderr) => {
resolve(error + stdout + stderr);
});
});
}
function preprocessCommand(option: McpOptions): [McpOptions, string] { async function preprocessCommand(option: McpOptions): Promise<[McpOptions, string]> {
// 对于特殊表示的路径,进行特殊的支持 // 对于特殊表示的路径,进行特殊的支持
if (option.args) { if (option.args) {
option.args = option.args.map(arg => { option.args = option.args.map(arg => {
@ -83,49 +100,56 @@ function preprocessCommand(option: McpOptions): [McpOptions, string] {
switch (ext) { switch (ext) {
case '.py': case '.py':
info = initUv(cwd); info = await initUv(option, cwd);
break; break;
case '.js': case '.js':
case '.ts': case '.ts':
info = initNpm(cwd); info = await initNpm(option, cwd);
break; break;
default: default:
break; break;
} }
return [option, ''];
return [option, info];
} }
function initUv(cwd: string) { async function initUv(option: McpOptions, cwd: string) {
let projectDir = cwd; let projectDir = cwd;
while (projectDir!== path.dirname(projectDir)) { while (projectDir!== path.dirname(projectDir)) {
if (fs.readFileSync(projectDir).includes('pyproject.toml')) { if (fs.readdirSync(projectDir).includes('pyproject.toml')) {
break; break;
} }
projectDir = path.dirname(projectDir); projectDir = path.dirname(projectDir);
} }
console.log(projectDir);
const venv = path.join(projectDir, '.venv'); const venv = path.join(projectDir, '.venv');
const mcpCli = path.join(venv, 'bin', 'mcp');
// judge by OS
const mcpCli = os.platform() === 'win32' ?
path.join(venv, 'Scripts','mcp.exe') :
path.join(venv, 'bin', 'mcp');
if (option.command === 'mcp') {
option.command = mcpCli;
option.cwd = projectDir;
}
if (fs.existsSync(mcpCli)) { if (fs.existsSync(mcpCli)) {
return ''; return '';
} }
let info = ''; let info = '';
info += execSync('uv sync', { cwd: projectDir }).toString('utf-8') + '\n';
info += execSync('uv add mcp "mcp[cli]"', { cwd: projectDir }).toString('utf-8') + '\n'; info += await collectAllOutputExec('uv sync', projectDir) + '\n';
info += await collectAllOutputExec('uv add mcp "mcp[cli]"', projectDir) + '\n';
return info; return info;
} }
function initNpm(cwd: string) { async function initNpm(option: McpOptions, cwd: string) {
let projectDir = cwd; let projectDir = cwd;
while (projectDir !== path.dirname(projectDir)) { while (projectDir !== path.dirname(projectDir)) {
@ -148,9 +172,10 @@ export async function connectService(
option: McpOptions option: McpOptions
): Promise<RestfulResponse> { ): Promise<RestfulResponse> {
try { try {
console.log('ready to connect', option); const { env, ...others } = option;
console.log('ready to connect', others);
const info = preprocessCommand(option); const [_, info] = await preprocessCommand(option);
const client = await connect(option); const client = await connect(option);
const uuid = randomUUID(); const uuid = randomUUID();
@ -172,6 +197,8 @@ export async function connectService(
return connectResult; return connectResult;
} catch (error) { } catch (error) {
console.log(error);
// TODO: 这边获取到的 error 不够精致,如何才能获取到更加精准的错误 // TODO: 这边获取到的 error 不够精致,如何才能获取到更加精准的错误
// 比如 error: Failed to spawn: `server.py` // 比如 error: Failed to spawn: `server.py`
// Caused by: No such file or directory (os error 2) // Caused by: No such file or directory (os error 2)

View File

@ -28,25 +28,11 @@ const logger = pino({
export type MessageHandler = (message: VSCodeMessage) => void; export type MessageHandler = (message: VSCodeMessage) => void;
interface IStdioLaunchSignature {
type: 'STDIO';
commandString: string;
cwd: string;
}
interface ISSELaunchSignature {
type: 'SSE';
url: string;
oauth: string;
}
export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
function refreshConnectionOption(envPath: string) { function refreshConnectionOption(envPath: string) {
const serverPath = path.join(__dirname, '..', '..', 'servers'); const serverPath = path.join(__dirname, '..', '..', 'servers');
const defaultOption = { const defaultOption = {
type:'STDIO', connectionType: 'STDIO',
commandString: 'mcp run main.py', commandString: 'mcp run main.py',
cwd: serverPath cwd: serverPath
}; };
@ -74,6 +60,17 @@ function acquireConnectionOption() {
return refreshConnectionOption(envPath); return refreshConnectionOption(envPath);
} }
// 按照前端的规范,整理成 commandString 样式
option.data = option.data.map((item: any) => {
if (item.connectionType === 'STDIO') {
item.commandString = [item.command, ...item.args]?.join(' ');
} else {
item.url = item.url;
}
return item;
});
return option; return option;
} catch (error) { } catch (error) {

View File

@ -8,7 +8,6 @@ export class SettingController {
@Controller('setting/save') @Controller('setting/save')
async saveSetting(data: RequestData, webview: PostMessageble) { async saveSetting(data: RequestData, webview: PostMessageble) {
const client = getClient(data.clientId);
saveSetting(data); saveSetting(data);
console.log('Settings saved successfully'); console.log('Settings saved successfully');
@ -20,7 +19,6 @@ export class SettingController {
@Controller('setting/load') @Controller('setting/load')
async loadSetting(data: RequestData, webview: PostMessageble) { async loadSetting(data: RequestData, webview: PostMessageble) {
const client = getClient(data.clientId);
const config = loadSetting(); const config = loadSetting();
return { return {
code: 200, code: 200,