feat: 完成 Agent MCP 对接

- 增加 MCP 连接类型、环境检测接口和容器运行环境支持

- 将 Agent 编排改为绑定整体 MCP 并编译为 runtime McpSpec

- 优化 MCP 工具展示、审批、草稿试运行和画布回显稳定性
This commit is contained in:
2026-05-29 11:09:21 +08:00
parent e39f7521e2
commit cc3bb9cff0
33 changed files with 2405 additions and 127 deletions

View File

@@ -1,12 +1,23 @@
<script setup lang="ts">
/* eslint-disable vue/no-mutating-props */
import type {AgentOption, AgentToolBinding} from '../types';
import type { AgentOption, AgentToolBinding } from '../types';
import {ElButton, ElForm, ElFormItem, ElInput, ElOption, ElSelect, ElSwitch,} from 'element-plus';
import { computed } from 'vue';
import {
ElButton,
ElEmpty,
ElForm,
ElFormItem,
ElInput,
ElOption,
ElSelect,
ElSwitch,
} from 'element-plus';
const props = defineProps<{
binding: AgentToolBinding;
kind: 'plugin' | 'workflow';
kind: 'mcp' | 'plugin' | 'workflow';
options: AgentOption[];
}>();
@@ -15,7 +26,7 @@ const emit = defineEmits<{
remove: [];
}>();
const SAFE_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
const SAFE_TOOL_NAME_PATTERN = /^[\w-]+$/;
function isSafeToolName(name?: string) {
return SAFE_TOOL_NAME_PATTERN.test(String(name || ''));
@@ -40,11 +51,47 @@ function shouldSyncToolName() {
return name.startsWith(prefix);
}
const targetValue = computed({
get() {
return props.binding.targetId ? String(props.binding.targetId) : '';
},
set(value: string) {
props.binding.targetId = value;
},
});
const resourceLabel = computed(() => {
if (props.kind === 'workflow') return '工作流';
if (props.kind === 'mcp') return 'MCP';
return '插件工具';
});
const selectedMcpTools = computed(() => {
if (props.kind !== 'mcp') {
return [];
}
const option = props.options.find(
(item) => String(item.value) === String(props.binding.targetId),
);
const tools = option?.raw?.tools || props.binding.resourceSummary?.tools;
return Array.isArray(tools) ? tools : [];
});
const selectedMcpToolCount = computed(() => selectedMcpTools.value.length);
function handleTargetChange(value: string) {
const option = props.options.find(
(item) => String(item.value) === String(value),
);
props.binding.resourceSummary = option?.raw || {};
if (props.kind === 'mcp') {
props.binding.targetId = option?.raw?.mcpId
? String(option.raw.mcpId)
: value;
props.binding.toolName = '';
emit('change');
return;
}
if (shouldSyncToolName()) {
props.binding.toolName = resolveToolName(option);
}
@@ -54,9 +101,9 @@ function handleTargetChange(value: string) {
<template>
<ElForm label-position="top" class="agent-form">
<ElFormItem :label="kind === 'workflow' ? '工作流' : '插件工具'" required>
<ElFormItem :label="resourceLabel" required>
<ElSelect
v-model="binding.targetId"
v-model="targetValue"
filterable
placeholder="选择资源"
@change="handleTargetChange"
@@ -69,7 +116,28 @@ function handleTargetChange(value: string) {
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="工具名称" required>
<div v-if="kind === 'mcp'" class="agent-form__mcp-tools">
<div class="agent-form__mcp-tools-header">
<span>工具列表</span>
<span>{{ selectedMcpToolCount }} </span>
</div>
<div v-if="selectedMcpTools.length > 0" class="agent-form__mcp-tool-list">
<div
v-for="tool in selectedMcpTools"
:key="tool.name || tool.title"
class="agent-form__mcp-tool"
>
<div class="agent-form__mcp-tool-name">
{{ tool.name || tool.title || '未命名工具' }}
</div>
<div v-if="tool.description" class="agent-form__mcp-tool-desc">
{{ tool.description }}
</div>
</div>
</div>
<ElEmpty v-else description="暂无工具" :image-size="64" />
</div>
<ElFormItem v-if="kind !== 'mcp'" label="工具名称" required>
<ElInput
v-model="binding.toolName"
placeholder="仅支持英文、数字、下划线或中划线"
@@ -99,6 +167,59 @@ function handleTargetChange(value: string) {
width: 100%;
}
.agent-form__mcp-tools {
margin-bottom: 16px;
}
.agent-form__mcp-tools-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
font-size: 13px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.agent-form__mcp-tools-header span:last-child {
font-size: 12px;
font-weight: 500;
color: var(--el-text-color-secondary);
}
.agent-form__mcp-tool-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.agent-form__mcp-tool {
padding: 10px 12px;
background: var(--el-fill-color-lighter);
border: 1px solid var(--el-border-color-lighter);
border-radius: 8px;
}
.agent-form__mcp-tool-name {
overflow: hidden;
font-size: 13px;
font-weight: 600;
line-height: 20px;
text-overflow: ellipsis;
white-space: nowrap;
}
.agent-form__mcp-tool-desc {
display: -webkit-box;
margin-top: 4px;
overflow: hidden;
font-size: 12px;
line-height: 18px;
color: var(--el-text-color-secondary);
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.agent-form__danger {
width: 100%;
margin-top: 8px;