支持预设环境变量与stdio启动的 cwd 自定
This commit is contained in:
parent
ddb4dfb565
commit
f484688a4b
@ -50,6 +50,7 @@
|
||||
| `service` | 对于连接的 mcp server 进行热更新 | `MVP` | 0% | `P1` |
|
||||
| `service` | 系统配置信息云同步 | `MVP` | 0% | `P1` |
|
||||
| `all` | 系统提示词管理模块 | `MVP` | 0% | `P1` |
|
||||
| `service` | 工具 wise 的日志系统 | `MVP` | 0% | `P0` |
|
||||
|
||||
|
||||
## Dev
|
||||
|
@ -39,15 +39,18 @@ export class TaskLoop {
|
||||
const toolName = toolCall.function.name;
|
||||
const toolArgs = JSON.parse(toolCall.function.arguments);
|
||||
const toolResponse = await callTool(toolName, toolArgs);
|
||||
|
||||
if (!toolResponse.isError) {
|
||||
const content = JSON.stringify(toolResponse.content);
|
||||
return content;
|
||||
} else {
|
||||
this.onError(`工具调用失败: ${toolResponse.content}`);
|
||||
console.error(toolResponse.content);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.onError(`工具调用失败: ${(error as Error).message}`);
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,5 +143,6 @@
|
||||
"creative": "إبداع",
|
||||
"single-dialog": "محادثة من جولة واحدة",
|
||||
"multi-dialog": "محادثة متعددة الجولات",
|
||||
"press-and-run": "اكتب سؤالاً لبدء الاختبار"
|
||||
"press-and-run": "اكتب سؤالاً لبدء الاختبار",
|
||||
"connect-sigature": "توقيع الاتصال"
|
||||
}
|
@ -143,5 +143,6 @@
|
||||
"creative": "Kreativität",
|
||||
"single-dialog": "Einzelrunden-Dialog",
|
||||
"multi-dialog": "Mehrrundengespräch",
|
||||
"press-and-run": "Geben Sie eine Frage ein, um den Test zu starten"
|
||||
"press-and-run": "Geben Sie eine Frage ein, um den Test zu starten",
|
||||
"connect-sigature": "Verbindungssignatur"
|
||||
}
|
@ -143,5 +143,6 @@
|
||||
"creative": "Creativity",
|
||||
"single-dialog": "Single-round dialogue",
|
||||
"multi-dialog": "Multi-turn conversation",
|
||||
"press-and-run": "Type a question to start the test"
|
||||
"press-and-run": "Type a question to start the test",
|
||||
"connect-sigature": "Connection signature"
|
||||
}
|
@ -143,5 +143,6 @@
|
||||
"creative": "Créativité",
|
||||
"single-dialog": "Dialogue en un tour",
|
||||
"multi-dialog": "Conversation multi-tours",
|
||||
"press-and-run": "Tapez une question pour commencer le test"
|
||||
"press-and-run": "Tapez une question pour commencer le test",
|
||||
"connect-sigature": "Signature de connexion"
|
||||
}
|
@ -143,5 +143,6 @@
|
||||
"creative": "創造性",
|
||||
"single-dialog": "単一ラウンドの対話",
|
||||
"multi-dialog": "マルチターン会話",
|
||||
"press-and-run": "テストを開始するには質問を入力してください"
|
||||
"press-and-run": "テストを開始するには質問を入力してください",
|
||||
"connect-sigature": "接続署名"
|
||||
}
|
@ -143,5 +143,6 @@
|
||||
"creative": "창의성",
|
||||
"single-dialog": "단일 라운드 대화",
|
||||
"multi-dialog": "다중 턴 대화",
|
||||
"press-and-run": "테스트를 시작하려면 질문을 입력하세요"
|
||||
"press-and-run": "테스트를 시작하려면 질문을 입력하세요",
|
||||
"connect-sigature": "연결 서명"
|
||||
}
|
@ -143,5 +143,6 @@
|
||||
"creative": "Творчество",
|
||||
"single-dialog": "Однораундовый диалог",
|
||||
"multi-dialog": "Многораундовый разговор",
|
||||
"press-and-run": "Введите вопрос, чтобы начать тест"
|
||||
"press-and-run": "Введите вопрос, чтобы начать тест",
|
||||
"connect-sigature": "Подпись соединения"
|
||||
}
|
@ -143,5 +143,6 @@
|
||||
"creative": "创意",
|
||||
"single-dialog": "单轮对话",
|
||||
"multi-dialog": "多轮对话",
|
||||
"press-and-run": "键入问题以开始测试"
|
||||
"press-and-run": "键入问题以开始测试",
|
||||
"connect-sigature": "连接签名"
|
||||
}
|
@ -143,5 +143,6 @@
|
||||
"creative": "創意",
|
||||
"single-dialog": "單輪對話",
|
||||
"multi-dialog": "多輪對話",
|
||||
"press-and-run": "輸入問題以開始測試"
|
||||
"press-and-run": "輸入問題以開始測試",
|
||||
"connect-sigature": "連接簽名"
|
||||
}
|
@ -1,23 +1,41 @@
|
||||
<template>
|
||||
<!-- STDIO 模式下的命令输入 -->
|
||||
<div class="connection-option" v-if="connectionMethods.current === 'STDIO'">
|
||||
<span>{{ t('command') }}</span>
|
||||
<span>{{ t('connect-sigature') }}</span>
|
||||
<span style="width: 310px;">
|
||||
<el-form :model="connectionArgs" :rules="rules" ref="stdioForm">
|
||||
<el-form-item prop="commandString">
|
||||
<el-input v-model="connectionArgs.commandString"></el-input>
|
||||
<div class="input-with-label">
|
||||
<span class="input-label">命令</span>
|
||||
<el-input v-model="connectionArgs.commandString" placeholder="mcp run <your script>"></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="cwd">
|
||||
<div class="input-with-label">
|
||||
<span class="input-label">执行目录</span>
|
||||
<el-input v-model="connectionArgs.cwd" placeholder="cwd, 可为空"></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 其他模式下的URL输入 -->
|
||||
<!-- SSE 模式下的URL输入 -->
|
||||
<div class="connection-option" v-else>
|
||||
<span>{{ "URL" }}</span>
|
||||
<span>{{ t('connect-sigature') }}</span>
|
||||
<span style="width: 310px;">
|
||||
<el-form :model="connectionArgs" :rules="rules" ref="urlForm">
|
||||
<el-form-item prop="urlString">
|
||||
<div class="input-with-label">
|
||||
<span class="input-label">URL</span>
|
||||
<el-input v-model="connectionArgs.urlString" placeholder="http://"></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="oauth">
|
||||
<div class="input-with-label">
|
||||
<span class="input-label">OAuth</span>
|
||||
<el-input v-model="connectionArgs.oauth" placeholder="认证签名, 可为空"></el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</span>
|
||||
@ -41,6 +59,12 @@ const rules = reactive<FormRules>({
|
||||
commandString: [
|
||||
{ required: true, message: '命令不能为空', trigger: 'blur' }
|
||||
],
|
||||
cwd: [
|
||||
{ required: false, trigger: 'blur' }
|
||||
],
|
||||
oauth: [
|
||||
{ required: false, trigger: 'blur' }
|
||||
],
|
||||
urlString: [
|
||||
{ required: true, message: 'URL不能为空', trigger: 'blur' }
|
||||
]
|
||||
@ -62,3 +86,29 @@ const validateForm = async () => {
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.input-with-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
width: 80px;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.connection-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background-color: var(--el-bg-color);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="connection-option">
|
||||
<span>{{ t('log') }}</span>
|
||||
<el-scrollbar height="300px">
|
||||
<el-scrollbar height="100%">
|
||||
<div
|
||||
class="output-content"
|
||||
contenteditable="false"
|
||||
@ -24,6 +24,14 @@ const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.connection-option {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.connection-option .el-scrollbar__view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.connection-option .output-content {
|
||||
border-radius: .5em;
|
||||
padding: 15px;
|
||||
@ -37,5 +45,6 @@ const { t } = useI18n();
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
background-color: var(--sidebar);
|
||||
height: 95%;
|
||||
}
|
||||
</style>
|
@ -22,7 +22,6 @@ export const connectionMethods = reactive({
|
||||
export const connectionArgs = reactive({
|
||||
commandString: '',
|
||||
cwd: '',
|
||||
env: {},
|
||||
oauth: '',
|
||||
urlString: ''
|
||||
});
|
||||
@ -44,6 +43,14 @@ export const connectionEnv = reactive<IConnectionEnv>({
|
||||
newValue: ''
|
||||
});
|
||||
|
||||
export function makeEnv() {
|
||||
const env = {} as Record<string, string>;
|
||||
connectionEnv.data.forEach(item => {
|
||||
env[item.key] = item.value;
|
||||
});
|
||||
return env;
|
||||
}
|
||||
|
||||
|
||||
// 定义连接类型
|
||||
type ConnectionType = 'STDIO' | 'SSE';
|
||||
@ -54,6 +61,8 @@ export interface MCPOptions {
|
||||
// STDIO 特定选项
|
||||
command?: string;
|
||||
args?: string[];
|
||||
cwd?: string;
|
||||
env?: Record<string, string>;
|
||||
// SSE 特定选项
|
||||
url?: string;
|
||||
// 通用客户端选项
|
||||
@ -64,6 +73,7 @@ export interface MCPOptions {
|
||||
export function doConnect() {
|
||||
let connectOption: MCPOptions;
|
||||
const bridge = useMessageBridge();
|
||||
const env = makeEnv();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// 监听 connect
|
||||
@ -86,7 +96,6 @@ export function doConnect() {
|
||||
resolve(void 0);
|
||||
}, { once: true });
|
||||
|
||||
// TODO: 增加判断,获取 cwd
|
||||
if (connectionMethods.current === 'STDIO') {
|
||||
|
||||
if (connectionArgs.commandString.length === 0) {
|
||||
@ -100,7 +109,9 @@ export function doConnect() {
|
||||
connectOption = {
|
||||
connectionType: 'STDIO',
|
||||
command: command,
|
||||
cwd: connectionArgs.cwd,
|
||||
args: commandComponents,
|
||||
env: env,
|
||||
clientName: 'openmcp.connect.stdio',
|
||||
clientVersion: '0.0.1'
|
||||
}
|
||||
@ -115,6 +126,7 @@ export function doConnect() {
|
||||
connectOption = {
|
||||
connectionType: 'SSE',
|
||||
url: url,
|
||||
env: env,
|
||||
clientName: 'openmcp.connect.sse',
|
||||
clientVersion: '0.0.1'
|
||||
}
|
||||
@ -147,7 +159,6 @@ export async function launchConnect(option: { updateCommandString?: boolean } =
|
||||
if (updateCommandString) {
|
||||
connectionArgs.commandString = connectionItem.commandString;
|
||||
connectionArgs.cwd = connectionItem.cwd;
|
||||
connectionArgs.env = {};
|
||||
|
||||
if (connectionArgs.commandString.length === 0) {
|
||||
return;
|
||||
@ -171,6 +182,7 @@ export async function launchConnect(option: { updateCommandString?: boolean } =
|
||||
|
||||
async function launchStdio() {
|
||||
const bridge = useMessageBridge();
|
||||
const env = makeEnv();
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// 监听 connect
|
||||
@ -196,7 +208,7 @@ async function launchStdio() {
|
||||
command: command,
|
||||
args: commandComponents,
|
||||
cwd: connectionArgs.cwd,
|
||||
env: connectionArgs.env,
|
||||
env
|
||||
};
|
||||
|
||||
bridge.postMessage({
|
||||
@ -225,7 +237,8 @@ async function launchStdio() {
|
||||
args: commandComponents,
|
||||
cwd: connectionArgs.cwd,
|
||||
clientName: 'openmcp.connect.stdio',
|
||||
clientVersion: '0.0.1'
|
||||
clientVersion: '0.0.1',
|
||||
env
|
||||
};
|
||||
|
||||
bridge.postMessage({
|
||||
@ -237,6 +250,7 @@ async function launchStdio() {
|
||||
|
||||
async function launchSSE() {
|
||||
const bridge = useMessageBridge();
|
||||
const env = makeEnv();
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// 监听 connect
|
||||
@ -257,7 +271,7 @@ async function launchSSE() {
|
||||
name: 'openmcp.connect.sse',
|
||||
url: connectionArgs.urlString,
|
||||
oauth: connectionArgs.oauth,
|
||||
env: connectionArgs.env
|
||||
env: env
|
||||
};
|
||||
bridge.postMessage({
|
||||
command: 'vscode/update-connection-sigature',
|
||||
@ -278,7 +292,8 @@ async function launchSSE() {
|
||||
connectionType: 'SSE',
|
||||
url: connectionArgs.urlString,
|
||||
clientName: 'openmcp.connect.sse',
|
||||
clientVersion: '0.0.1'
|
||||
clientVersion: '0.0.1',
|
||||
env
|
||||
};
|
||||
|
||||
bridge.postMessage({
|
||||
|
@ -1,6 +1,16 @@
|
||||
<template>
|
||||
<div class="connection-option">
|
||||
<div class="env-switch">
|
||||
<span>{{ t('env-var') }}</span>
|
||||
|
||||
<el-switch
|
||||
v-model="envEnabled"
|
||||
@change="handleEnvSwitch"
|
||||
inline-prompt
|
||||
active-text="预设"
|
||||
inactive-text="预设"
|
||||
></el-switch>
|
||||
</div>
|
||||
<div class="input-env">
|
||||
<span class="input-env-container">
|
||||
<span>
|
||||
@ -15,7 +25,6 @@
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<el-scrollbar height="200px" width="350px" class="display-env-container">
|
||||
<div class="display-env">
|
||||
@ -33,14 +42,42 @@
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent, onMounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { connectionEnv, EnvItem } from './connection';
|
||||
import { connectionEnv, connectionResult, EnvItem } from './connection';
|
||||
import { useMessageBridge } from '@/api/message-bridge';
|
||||
|
||||
defineComponent({ name: 'env-var' });
|
||||
|
||||
const { t } = useI18n();
|
||||
const bridge = useMessageBridge();
|
||||
|
||||
function lookupEnvVar(varNames: string[]) {
|
||||
console.log('enter');
|
||||
|
||||
return new Promise<string[] | undefined>((resolve, reject) => {
|
||||
bridge.addCommandListener('lookup-env-var', data => {
|
||||
const { code, msg } = data;
|
||||
|
||||
if (code === 200) {
|
||||
resolve(msg);
|
||||
} else {
|
||||
connectionResult.logString += '\n' + msg;
|
||||
resolve(undefined);
|
||||
}
|
||||
}, { once: true });
|
||||
|
||||
console.log(varNames);
|
||||
|
||||
|
||||
bridge.postMessage({
|
||||
command: 'lookup-env-var',
|
||||
data: {
|
||||
keys: varNames
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 添加环境变量
|
||||
@ -78,6 +115,63 @@ function deleteEnvVar(option: EnvItem) {
|
||||
}
|
||||
|
||||
|
||||
const envEnabled = ref(true);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
handleEnvSwitch(envEnabled.value);
|
||||
}, 200);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style>
|
||||
.env-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.env-switch .el-switch .el-switch__action {
|
||||
background-color: var(--main-color);
|
||||
}
|
||||
|
||||
.env-switch .el-switch.is-checked .el-switch__action {
|
||||
background-color: var(--sidebar);
|
||||
}
|
||||
|
||||
.env-switch .el-switch__core {
|
||||
border: 1px solid var(--main-color) !important;
|
||||
}
|
||||
</style>
|
@ -6,22 +6,11 @@
|
||||
<EnvVar></EnvVar>
|
||||
|
||||
<div class="connect-action">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
:disabled="!connectionResult"
|
||||
@click="suitableConnect()"
|
||||
>
|
||||
<el-button type="primary" size="large" :loading="isLoading" :disabled="!connectionResult"
|
||||
@click="suitableConnect()">
|
||||
<span class="iconfont icon-connect" v-if="!isLoading"></span>
|
||||
{{ t('connect.appearance.connect') }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="doReconnect()"
|
||||
>
|
||||
{{ t('connect.appearance.reconnect') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -33,7 +22,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
@ -58,12 +47,19 @@ bridge.addCommandListener('connect', data => {
|
||||
connectionResult.logString = msg;
|
||||
}, { once: false });
|
||||
|
||||
function suitableConnect() {
|
||||
const isLoading = ref(false);
|
||||
|
||||
async function suitableConnect() {
|
||||
isLoading.value = true;
|
||||
connectionResult.logString = '';
|
||||
|
||||
if (acquireVsCodeApi === undefined) {
|
||||
doConnect();
|
||||
await doConnect();
|
||||
} else {
|
||||
launchConnect({ updateCommandString: false });
|
||||
await launchConnect({ updateCommandString: false });
|
||||
}
|
||||
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
@ -1,6 +1,7 @@
|
||||
|
||||
import { PostMessageble } from '../adapter';
|
||||
import { connect, MCPClient, type MCPOptions } from './connect';
|
||||
import { lookupEnvVarHandler } from './env-var';
|
||||
import { callTool, getPrompt, getServerVersion, listPrompts, listResources, listResourceTemplates, listTools, readResource } from './handler';
|
||||
import { chatCompletionHandler } from './llm';
|
||||
import { panelLoadHandler, panelSaveHandler } from './panel';
|
||||
@ -132,6 +133,10 @@ export function messageController(command: string, data: any, webview: PostMessa
|
||||
chatCompletionHandler(client, data, webview);
|
||||
break;
|
||||
|
||||
case 'lookup-env-var':
|
||||
lookupEnvVarHandler(client, data, webview);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -139,50 +139,6 @@
|
||||
"isError": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "资源",
|
||||
"icon": "icon-file",
|
||||
"type": "blank",
|
||||
"componentIndex": 0,
|
||||
"storage": {
|
||||
"currentResourceName": "greeting",
|
||||
"formData": {
|
||||
"name": "kirigaya"
|
||||
},
|
||||
"lastResourceReadResponse": {
|
||||
"contents": [
|
||||
{
|
||||
"uri": "greeting://kirigaya",
|
||||
"mimeType": "text/plain",
|
||||
"text": "Hello, kirigaya!"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "提词",
|
||||
"icon": "icon-chat",
|
||||
"type": "blank",
|
||||
"componentIndex": 1,
|
||||
"storage": {
|
||||
"formData": {
|
||||
"message": "你好"
|
||||
},
|
||||
"currentPromptName": "translate",
|
||||
"lastPromptGetResponse": {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": {
|
||||
"type": "text",
|
||||
"text": "请将下面的话语翻译成中文:\n\n你好"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user