update
This commit is contained in:
parent
8b3816d05c
commit
4d459464d3
4
package-lock.json
generated
4
package-lock.json
generated
@ -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",
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
// 处理中文混合名称
|
// 处理中文混合名称
|
||||||
|
@ -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()
|
||||||
|
@ -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);
|
||||||
|
@ -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 {
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
|
@ -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>
|
@ -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;
|
||||||
|
@ -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 }}
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
|
@ -28,32 +28,18 @@ 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
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
|
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
|
||||||
|
|
||||||
return { data: [ defaultOption ] };
|
return { data: [defaultOption] };
|
||||||
}
|
}
|
||||||
|
|
||||||
function acquireConnectionOption() {
|
function acquireConnectionOption() {
|
||||||
@ -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) {
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user