完成兼容
This commit is contained in:
parent
355b25b9b7
commit
c218dcba03
@ -3,6 +3,8 @@
|
|||||||
## [main] 0.1.0
|
## [main] 0.1.0
|
||||||
- 新特性:支持同时连入多个 mcp server
|
- 新特性:支持同时连入多个 mcp server
|
||||||
- 新特性:更新协议内容,支持 streamable http 协议,未来将逐步取代 SSE 的连接方式
|
- 新特性:更新协议内容,支持 streamable http 协议,未来将逐步取代 SSE 的连接方式
|
||||||
|
- 对于 uv 创建的 py 项目进行特殊支持:自动初始化项目,并将 mcp 定向到 .venv/bin/mcp 中,不再需要用户全局安装 mcp
|
||||||
|
- 对于 npm 创建的 js/ts 项目进行特殊支持:自动初始化项目
|
||||||
|
|
||||||
## [main] 0.0.9
|
## [main] 0.0.9
|
||||||
- 修复 0.0.8 引入的bug:system prompt 返回的是索引而非真实内容
|
- 修复 0.0.8 引入的bug:system prompt 返回的是索引而非真实内容
|
||||||
|
@ -48,11 +48,11 @@ onMounted(async () => {
|
|||||||
pinkLog('OpenMCP Client 启动');
|
pinkLog('OpenMCP Client 启动');
|
||||||
|
|
||||||
// 跳转到首页
|
// 跳转到首页
|
||||||
if (route.name !== 'debug') {
|
// if (route.name !== 'debug') {
|
||||||
const targetRoute = import.meta.env.BASE_URL + 'debug';
|
// const targetRoute = import.meta.env.BASE_URL + 'debug';
|
||||||
console.log('go to ' + targetRoute);
|
// console.log('go to ' + targetRoute);
|
||||||
router.push(targetRoute);
|
// router.push(targetRoute);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 进行桥接
|
// 进行桥接
|
||||||
await bridge.awaitForWebsocket();
|
await bridge.awaitForWebsocket();
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
</el-tour-step>
|
</el-tour-step>
|
||||||
|
|
||||||
<el-tour-step
|
<el-tour-step
|
||||||
:target="client.connectionSettingRef.value"
|
:target="client.connectionSettingRef"
|
||||||
:prev-button-props="{ children: '上一步' }"
|
:prev-button-props="{ children: '上一步' }"
|
||||||
:next-button-props="{ children: '下一步' }"
|
:next-button-props="{ children: '下一步' }"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
@ -78,7 +78,7 @@
|
|||||||
</el-tour-step>
|
</el-tour-step>
|
||||||
|
|
||||||
<el-tour-step
|
<el-tour-step
|
||||||
:target="client.connectionLogRef.value"
|
:target="client.connectionLogRef"
|
||||||
:prev-button-props="{ children: '上一步' }"
|
:prev-button-props="{ children: '上一步' }"
|
||||||
:next-button-props="{ children: '下一步' }"
|
:next-button-props="{ children: '下一步' }"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<el-scrollbar>
|
<el-scrollbar>
|
||||||
<div class="connection-container">
|
<div class="connection-container">
|
||||||
<div class="connect-panel-container"
|
<div class="connect-panel-container"
|
||||||
:ref="el => client.connectionSettingRef.value = el"
|
:ref="el => client.connectionSettingRef = el"
|
||||||
>
|
>
|
||||||
<ConnectionMethod :index="props.index" />
|
<ConnectionMethod :index="props.index" />
|
||||||
<ConnectionArgs :index="props.index" />
|
<ConnectionArgs :index="props.index" />
|
||||||
@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="connect-panel-container"
|
<div class="connect-panel-container"
|
||||||
:ref="el => client.connectionLogRef.value = el"
|
:ref="el => client.connectionLogRef = el"
|
||||||
>
|
>
|
||||||
<ConnectionLog :index="props.index" />
|
<ConnectionLog :index="props.index" />
|
||||||
</div>
|
</div>
|
||||||
@ -51,6 +51,8 @@ const props = defineProps({
|
|||||||
const client = mcpClientAdapter.clients[props.index];
|
const client = mcpClientAdapter.clients[props.index];
|
||||||
|
|
||||||
console.log(client);
|
console.log(client);
|
||||||
|
console.log(client.connectionSettingRef);
|
||||||
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
@ -59,8 +61,9 @@ const isLoading = ref(false);
|
|||||||
async function connect() {
|
async function connect() {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
|
||||||
const plaform = getPlatform();
|
const platform = getPlatform();
|
||||||
const ok = await client.connect(plaform);
|
const ok = await client.connect();
|
||||||
|
|
||||||
if (ok) {
|
if (ok) {
|
||||||
mcpClientAdapter.saveLaunchSignature();
|
mcpClientAdapter.saveLaunchSignature();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useMessageBridge } from "@/api/message-bridge";
|
import { useMessageBridge } from "@/api/message-bridge";
|
||||||
import { reactive, ref, type Reactive, type Ref } from "vue";
|
import { reactive } from "vue";
|
||||||
import type { IConnectionResult, ConnectionTypeOptionItem, IConnectionArgs, IConnectionEnvironment, McpOptions } from "./type";
|
import type { IConnectionResult, ConnectionTypeOptionItem, IConnectionArgs, IConnectionEnvironment, McpOptions } from "./type";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import { loadPanels } from "@/hook/panel";
|
import { loadPanels } from "@/hook/panel";
|
||||||
@ -23,49 +23,49 @@ export const connectionSelectDataViewOption: ConnectionTypeOptionItem[] = [
|
|||||||
|
|
||||||
export class McpClient {
|
export class McpClient {
|
||||||
// 连接入参
|
// 连接入参
|
||||||
public connectionArgs: Reactive<IConnectionArgs>;
|
public connectionArgs: IConnectionArgs;
|
||||||
// 连接出参
|
// 连接出参
|
||||||
public connectionResult: Reactive<IConnectionResult>;
|
public connectionResult: IConnectionResult;
|
||||||
|
|
||||||
// 预设环境变量,初始化的时候会去获取它们
|
// 预设环境变量,初始化的时候会去获取它们
|
||||||
public presetsEnvironment: string[] = ['HOME', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'USER'];
|
public presetsEnvironment: string[] = ['HOME', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'USER'];
|
||||||
// 环境变量
|
// 环境变量
|
||||||
public connectionEnvironment: Reactive<IConnectionEnvironment>;
|
public connectionEnvironment: IConnectionEnvironment;
|
||||||
|
|
||||||
// logger 面板的 ref
|
// logger 面板的 ref
|
||||||
public connectionLogRef = ref<any>(null);
|
public connectionLogRef: any = null;
|
||||||
// setting 面板的 ref
|
// setting 面板的 ref
|
||||||
public connectionSettingRef = ref<any>(null);
|
public connectionSettingRef: any = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public clientVersion: string = '0.0.1',
|
public clientVersion: string = '0.0.1',
|
||||||
public clientNamePrefix: string = 'openmcp.connect'
|
public clientNamePrefix: string = 'openmcp.connect'
|
||||||
) {
|
) {
|
||||||
// 连接入参
|
// 连接入参
|
||||||
this.connectionArgs = reactive({
|
this.connectionArgs = {
|
||||||
type: 'STDIO',
|
type: 'STDIO',
|
||||||
commandString: '',
|
commandString: '',
|
||||||
cwd: '',
|
cwd: '',
|
||||||
url: '',
|
url: '',
|
||||||
oauth: ''
|
oauth: ''
|
||||||
});
|
};
|
||||||
|
|
||||||
// 连接出参
|
// 连接出参
|
||||||
this.connectionResult = reactive({
|
this.connectionResult = {
|
||||||
success: false,
|
success: false,
|
||||||
status: 'disconnected',
|
status: 'disconnected',
|
||||||
clientId: '',
|
clientId: '',
|
||||||
name: '',
|
name: '',
|
||||||
version: '',
|
version: '',
|
||||||
logString: []
|
logString: []
|
||||||
});
|
};
|
||||||
|
|
||||||
// 环境变量
|
// 环境变量
|
||||||
this.connectionEnvironment = reactive({
|
this.connectionEnvironment = {
|
||||||
data: [],
|
data: [],
|
||||||
newKey: '',
|
newKey: '',
|
||||||
newValue: ''
|
newValue: ''
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async acquireConnectionSignature(args: IConnectionArgs) {
|
async acquireConnectionSignature(args: IConnectionArgs) {
|
||||||
@ -121,6 +121,7 @@ export class McpClient {
|
|||||||
const { command, args } = this.commandAndArgs;
|
const { command, args } = this.commandAndArgs;
|
||||||
const env = this.env;
|
const env = this.env;
|
||||||
const url = this.connectionArgs.url;
|
const url = this.connectionArgs.url;
|
||||||
|
const cwd = this.connectionArgs.cwd;
|
||||||
const oauth = this.connectionArgs.oauth;
|
const oauth = this.connectionArgs.oauth;
|
||||||
const connectionType = this.connectionArgs.type;
|
const connectionType = this.connectionArgs.type;
|
||||||
|
|
||||||
@ -132,6 +133,7 @@ export class McpClient {
|
|||||||
command,
|
command,
|
||||||
args,
|
args,
|
||||||
url,
|
url,
|
||||||
|
cwd,
|
||||||
oauth,
|
oauth,
|
||||||
clientName,
|
clientName,
|
||||||
clientVersion,
|
clientVersion,
|
||||||
@ -145,7 +147,7 @@ export class McpClient {
|
|||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async connect(platform: string) {
|
public async connect() {
|
||||||
const bridge = useMessageBridge();
|
const bridge = useMessageBridge();
|
||||||
const { code, msg } = await bridge.commandRequest<IConnectionResult>('connect', this.connectOption);
|
const { code, msg } = await bridge.commandRequest<IConnectionResult>('connect', this.connectOption);
|
||||||
|
|
||||||
@ -160,6 +162,11 @@ export class McpClient {
|
|||||||
|
|
||||||
ElMessage.error(message);
|
ElMessage.error(message);
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
this.connectionResult.logString.push({
|
||||||
|
type: 'info',
|
||||||
|
message: msg.info || ''
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connectionResult.status = msg.status;
|
this.connectionResult.status = msg.status;
|
||||||
@ -284,9 +291,13 @@ class McpClientAdapter {
|
|||||||
|
|
||||||
public async launch() {
|
public async launch() {
|
||||||
const launchSignature = await this.getLaunchSignature();
|
const launchSignature = await this.getLaunchSignature();
|
||||||
|
console.log('launchSignature', launchSignature);
|
||||||
|
|
||||||
let allOk = true;
|
let allOk = true;
|
||||||
|
|
||||||
for (const item of launchSignature) {
|
for (const item of launchSignature) {
|
||||||
|
|
||||||
|
// 创建一个新的客户端
|
||||||
const client = new McpClient();
|
const client = new McpClient();
|
||||||
|
|
||||||
// 同步连接参数
|
// 同步连接参数
|
||||||
|
@ -3,9 +3,15 @@
|
|||||||
<div class="server-list">
|
<div class="server-list">
|
||||||
<div v-for="(client, index) in mcpClientAdapter.clients" :key="index" class="server-item"
|
<div v-for="(client, index) in mcpClientAdapter.clients" :key="index" class="server-item"
|
||||||
:class="{ 'active': mcpClientAdapter.currentClientIndex === index }" @click="selectServer(index)">
|
:class="{ 'active': mcpClientAdapter.currentClientIndex === index }" @click="selectServer(index)">
|
||||||
<span class="server-name">Server {{ index + 1 }}</span>
|
<span class="connect-status">
|
||||||
<span class="server-status" :class="client.connectionResult.status">
|
<span v-if="client.connectionResult.success">
|
||||||
{{ client.connectionResult.status }}
|
<span class="iconfont icon-connect"></span>
|
||||||
|
<span class="iconfont icon-dui"></span>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
<span class="iconfont icon-connect"></span>
|
||||||
|
<span class="server-name"> Unconnected </span>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="add-server" @click="addServer">
|
<div class="add-server" @click="addServer">
|
||||||
@ -21,7 +27,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import ConnectionPanel from './connection-panel.vue';
|
import ConnectionPanel from './connection-panel.vue';
|
||||||
import { mcpClientAdapter } from './core';
|
import { McpClient, mcpClientAdapter } from './core';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
defineComponent({ name: 'connection' });
|
defineComponent({ name: 'connection' });
|
||||||
@ -32,9 +38,8 @@ function selectServer(index: number) {
|
|||||||
|
|
||||||
function addServer() {
|
function addServer() {
|
||||||
ElMessage.info('Add server is not implemented yet');
|
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;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -45,11 +50,15 @@ function addServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.server-list {
|
.server-list {
|
||||||
width: 200px;
|
width: 150px;
|
||||||
border-right: 1px solid var(--border-color);
|
border-right: 1px solid var(--border-color);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.server-name {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
.server-item {
|
.server-item {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
@ -16,6 +16,7 @@ export interface IConnectionArgs {
|
|||||||
|
|
||||||
|
|
||||||
export interface IConnectionResult {
|
export interface IConnectionResult {
|
||||||
|
info?: string;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
status: string
|
status: string
|
||||||
clientId: string
|
clientId: string
|
||||||
|
0
service/src/hook/util.ts
Normal file
0
service/src/hook/util.ts
Normal file
@ -42,19 +42,20 @@ interface ISSELaunchSignature {
|
|||||||
export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
|
export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
|
||||||
|
|
||||||
function refreshConnectionOption(envPath: string) {
|
function refreshConnectionOption(envPath: string) {
|
||||||
|
const serverPath = path.join(__dirname, '..', '..', 'servers');
|
||||||
|
|
||||||
const defaultOption = {
|
const defaultOption = {
|
||||||
type:'STDIO',
|
type:'STDIO',
|
||||||
command: 'mcp',
|
commandString: 'mcp run main.py',
|
||||||
args: ['run', 'main.py'],
|
cwd: serverPath
|
||||||
cwd: '../server'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
|
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
|
||||||
|
|
||||||
return defaultOption;
|
return { data: [ defaultOption ] };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInitConnectionOption() {
|
function acquireConnectionOption() {
|
||||||
const envPath = path.join(__dirname, '..', '.env');
|
const envPath = path.join(__dirname, '..', '.env');
|
||||||
|
|
||||||
if (!fs.existsSync(envPath)) {
|
if (!fs.existsSync(envPath)) {
|
||||||
@ -63,6 +64,15 @@ function getInitConnectionOption() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const option = JSON.parse(fs.readFileSync(envPath, 'utf-8'));
|
const option = JSON.parse(fs.readFileSync(envPath, 'utf-8'));
|
||||||
|
|
||||||
|
if (!option.data) {
|
||||||
|
return refreshConnectionOption(envPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.data && option.data.length === 0) {
|
||||||
|
return refreshConnectionOption(envPath);
|
||||||
|
}
|
||||||
|
|
||||||
return option;
|
return option;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -73,27 +83,11 @@ function getInitConnectionOption() {
|
|||||||
|
|
||||||
function updateConnectionOption(data: any) {
|
function updateConnectionOption(data: any) {
|
||||||
const envPath = path.join(__dirname, '..', '.env');
|
const envPath = path.join(__dirname, '..', '.env');
|
||||||
|
const connection = { data };
|
||||||
if (data.connectionType === 'STDIO') {
|
fs.writeFileSync(envPath, JSON.stringify(connection, null, 4));
|
||||||
const connectionItem = {
|
|
||||||
type: 'STDIO',
|
|
||||||
command: data.command,
|
|
||||||
args: data.args,
|
|
||||||
cwd: data.cwd.replace(/\\/g, '/')
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
|
||||||
} else {
|
|
||||||
const connectionItem = {
|
|
||||||
type: 'SSE',
|
|
||||||
url: data.url,
|
|
||||||
oauth: data.oauth
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const devHome = path.join(__dirname, '..', '..');
|
const devHome = path.join(__dirname, '..', '..');
|
||||||
setRunningCWD(devHome);
|
setRunningCWD(devHome);
|
||||||
|
|
||||||
@ -115,7 +109,7 @@ wss.on('connection', (ws: any) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const option = getInitConnectionOption();
|
const option = acquireConnectionOption();
|
||||||
|
|
||||||
// 注册消息接受的管线
|
// 注册消息接受的管线
|
||||||
webview.onDidReceiveMessage(message => {
|
webview.onDidReceiveMessage(message => {
|
||||||
@ -124,21 +118,9 @@ wss.on('connection', (ws: any) => {
|
|||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'web/launch-signature':
|
case 'web/launch-signature':
|
||||||
const launchResultMessage: ILaunchSigature = option.type === 'STDIO' ?
|
|
||||||
{
|
|
||||||
type: 'STDIO',
|
|
||||||
commandString: option.command + ' ' + option.args.join(' '),
|
|
||||||
cwd: option.cwd || ''
|
|
||||||
} :
|
|
||||||
{
|
|
||||||
type: 'SSE',
|
|
||||||
url: option.url,
|
|
||||||
oauth: option.oauth || ''
|
|
||||||
};
|
|
||||||
|
|
||||||
const launchResult = {
|
const launchResult = {
|
||||||
code: 200,
|
code: 200,
|
||||||
msg: launchResultMessage
|
msg: option.data
|
||||||
};
|
};
|
||||||
|
|
||||||
webview.postMessage({
|
webview.postMessage({
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { spawnSync } from 'node:child_process';
|
import { 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';
|
||||||
import { McpOptions } from './client.dto';
|
import { McpOptions } from './client.dto';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
|
import path from 'node:path';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
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 {
|
||||||
@ -15,30 +17,39 @@ 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 result = spawnSync(command, args, {
|
const commandString = [command, ...args].join(' ');
|
||||||
cwd: cwd || process.cwd(),
|
|
||||||
STDIO: 'pipe',
|
|
||||||
encoding: 'utf-8'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
const result = execSync(commandString, {
|
||||||
return result.error.message;
|
cwd: cwd || process.cwd()
|
||||||
}
|
}).toString('utf-8');
|
||||||
if (result.status !== 0) {
|
|
||||||
return result.stderr || `Command failed with code ${result.status}`;
|
return result;
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error instanceof Error ? error.message : String(error);
|
return error instanceof Error ? error.message : String(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function connectService(
|
function getCWD(option: McpOptions) {
|
||||||
option: McpOptions
|
if (option.cwd) {
|
||||||
): Promise<RestfulResponse> {
|
return option.cwd;
|
||||||
try {
|
}
|
||||||
console.log('ready to connect', option);
|
const file = option.args?.at(-1);
|
||||||
|
if (file) {
|
||||||
|
return path.dirname(file);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCommandFileExt(option: McpOptions) {
|
||||||
|
const file = option.args?.at(-1);
|
||||||
|
if (file) {
|
||||||
|
return path.extname(file);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function preprocessCommand(option: McpOptions): [McpOptions, string] {
|
||||||
// 对于特殊表示的路径,进行特殊的支持
|
// 对于特殊表示的路径,进行特殊的支持
|
||||||
if (option.args) {
|
if (option.args) {
|
||||||
option.args = option.args.map(arg => {
|
option.args = option.args.map(arg => {
|
||||||
@ -49,6 +60,98 @@ export async function connectService(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (option.connectionType === 'SSE' || option.connectionType === 'STREAMABLE_HTTP') {
|
||||||
|
return [option, ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
const cwd = getCWD(option);
|
||||||
|
if (!cwd) {
|
||||||
|
return [option, ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = getCommandFileExt(option);
|
||||||
|
if (!ext) {
|
||||||
|
return [option, ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
// STDIO 模式下,对不同类型的项目进行额外支持
|
||||||
|
// uv:如果没有初始化,则进行 uv sync,将 mcp 设置为虚拟环境的
|
||||||
|
// npm:如果没有初始化,则进行 npm init,将 mcp 设置为虚拟环境
|
||||||
|
// go:如果没有初始化,则进行 go mod init
|
||||||
|
|
||||||
|
let info: string = '';
|
||||||
|
|
||||||
|
switch (ext) {
|
||||||
|
case '.py':
|
||||||
|
info = initUv(cwd);
|
||||||
|
break;
|
||||||
|
case '.js':
|
||||||
|
case '.ts':
|
||||||
|
info = initNpm(cwd);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return [option, info];
|
||||||
|
}
|
||||||
|
|
||||||
|
function initUv(cwd: string) {
|
||||||
|
let projectDir = cwd;
|
||||||
|
|
||||||
|
while (projectDir!== path.dirname(projectDir)) {
|
||||||
|
if (fs.readFileSync(projectDir).includes('pyproject.toml')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
projectDir = path.dirname(projectDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(projectDir);
|
||||||
|
|
||||||
|
|
||||||
|
const venv = path.join(projectDir, '.venv');
|
||||||
|
const mcpCli = path.join(venv, 'bin', 'mcp');
|
||||||
|
if (fs.existsSync(mcpCli)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function initNpm(cwd: string) {
|
||||||
|
let projectDir = cwd;
|
||||||
|
|
||||||
|
while (projectDir !== path.dirname(projectDir)) {
|
||||||
|
if (fs.readFileSync(projectDir).includes('package.json')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
projectDir = path.dirname(projectDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeModulesPath = path.join(projectDir, 'node_modules');
|
||||||
|
if (fs.existsSync(nodeModulesPath)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return execSync('npm i', { cwd: projectDir }).toString('utf-8') + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function connectService(
|
||||||
|
option: McpOptions
|
||||||
|
): Promise<RestfulResponse> {
|
||||||
|
try {
|
||||||
|
console.log('ready to connect', option);
|
||||||
|
|
||||||
|
const info = preprocessCommand(option);
|
||||||
|
|
||||||
const client = await connect(option);
|
const client = await connect(option);
|
||||||
const uuid = randomUUID();
|
const uuid = randomUUID();
|
||||||
clientMap.set(uuid, client);
|
clientMap.set(uuid, client);
|
||||||
@ -61,7 +164,8 @@ export async function connectService(
|
|||||||
status: 'success',
|
status: 'success',
|
||||||
clientId: uuid,
|
clientId: uuid,
|
||||||
name: versionInfo?.name,
|
name: versionInfo?.name,
|
||||||
version: versionInfo?.version
|
version: versionInfo?.version,
|
||||||
|
info
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,19 +43,20 @@ interface ISSELaunchSignature {
|
|||||||
export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
|
export type ILaunchSigature = IStdioLaunchSignature | ISSELaunchSignature;
|
||||||
|
|
||||||
function refreshConnectionOption(envPath: string) {
|
function refreshConnectionOption(envPath: string) {
|
||||||
|
const serverPath = path.join(__dirname, '..', '..', 'servers');
|
||||||
|
|
||||||
const defaultOption = {
|
const defaultOption = {
|
||||||
type:'STDIO',
|
type:'STDIO',
|
||||||
command: 'mcp',
|
commandString: 'mcp run main.py',
|
||||||
args: ['run', 'main.py'],
|
cwd: serverPath
|
||||||
cwd: '../server'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
|
fs.writeFileSync(envPath, JSON.stringify(defaultOption, null, 4));
|
||||||
|
|
||||||
return defaultOption;
|
return { data: [ defaultOption ] };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInitConnectionOption() {
|
function acquireConnectionOption() {
|
||||||
const envPath = path.join(__dirname, '..', '.env');
|
const envPath = path.join(__dirname, '..', '.env');
|
||||||
|
|
||||||
if (!fs.existsSync(envPath)) {
|
if (!fs.existsSync(envPath)) {
|
||||||
@ -64,6 +65,15 @@ function getInitConnectionOption() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const option = JSON.parse(fs.readFileSync(envPath, 'utf-8'));
|
const option = JSON.parse(fs.readFileSync(envPath, 'utf-8'));
|
||||||
|
|
||||||
|
if (!option.data) {
|
||||||
|
return refreshConnectionOption(envPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.data && option.data.length === 0) {
|
||||||
|
return refreshConnectionOption(envPath);
|
||||||
|
}
|
||||||
|
|
||||||
return option;
|
return option;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -81,25 +91,8 @@ const authPassword = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.env
|
|||||||
|
|
||||||
function updateConnectionOption(data: any) {
|
function updateConnectionOption(data: any) {
|
||||||
const envPath = path.join(__dirname, '..', '.env');
|
const envPath = path.join(__dirname, '..', '.env');
|
||||||
|
const connection = { data };
|
||||||
if (data.connectionType === 'STDIO') {
|
fs.writeFileSync(envPath, JSON.stringify(connection, null, 4));
|
||||||
const connectionItem = {
|
|
||||||
type: 'STDIO',
|
|
||||||
command: data.command,
|
|
||||||
args: data.args,
|
|
||||||
cwd: data.cwd.replace(/\\/g, '/')
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
|
||||||
} else {
|
|
||||||
const connectionItem = {
|
|
||||||
type: 'SSE',
|
|
||||||
url: data.url,
|
|
||||||
oauth: data.oauth
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync(envPath, JSON.stringify(connectionItem, null, 4));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const devHome = path.join(__dirname, '..', '..');
|
const devHome = path.join(__dirname, '..', '..');
|
||||||
@ -146,7 +139,7 @@ wss.on('connection', (ws: any) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const option = getInitConnectionOption();
|
const option = acquireConnectionOption();
|
||||||
|
|
||||||
// 注册消息接受的管线
|
// 注册消息接受的管线
|
||||||
webview.onDidReceiveMessage(message => {
|
webview.onDidReceiveMessage(message => {
|
||||||
@ -155,21 +148,9 @@ wss.on('connection', (ws: any) => {
|
|||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'web/launch-signature':
|
case 'web/launch-signature':
|
||||||
const launchResultMessage: ILaunchSigature = option.type === 'STDIO' ?
|
|
||||||
{
|
|
||||||
type: 'STDIO',
|
|
||||||
commandString: option.command + ' ' + option.args.join(' '),
|
|
||||||
cwd: option.cwd || ''
|
|
||||||
} :
|
|
||||||
{
|
|
||||||
type: 'SSE',
|
|
||||||
url: option.url,
|
|
||||||
oauth: option.oauth || ''
|
|
||||||
};
|
|
||||||
|
|
||||||
const launchResult = {
|
const launchResult = {
|
||||||
code: 200,
|
code: 200,
|
||||||
msg: launchResultMessage
|
msg: option.data
|
||||||
};
|
};
|
||||||
|
|
||||||
webview.postMessage({
|
webview.postMessage({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user