diff --git a/agent/app/dto/request/mcp_server.go b/agent/app/dto/request/mcp_server.go index 686ecd310ab6..ff59dc04ca35 100644 --- a/agent/app/dto/request/mcp_server.go +++ b/agent/app/dto/request/mcp_server.go @@ -23,6 +23,7 @@ type McpServerCreate struct { Type string `json:"type" validate:"required"` GatewayImage string `json:"gatewayImage"` ProtocolVersion string `json:"protocolVersion"` + TaskID string `json:"taskID"` } type McpServerUpdate struct { diff --git a/agent/app/service/mcp_server.go b/agent/app/service/mcp_server.go index ecca6d326bca..27777db39940 100644 --- a/agent/app/service/mcp_server.go +++ b/agent/app/service/mcp_server.go @@ -18,6 +18,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/app/dto/response" "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/cmd/server/ai" "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" @@ -127,7 +128,6 @@ func (m McpServerService) Update(req request.McpServerUpdate) error { mcpServer.GatewayImage = req.GatewayImage mcpServer.ProtocolVersion = req.ProtocolVersion normalizeMcpServerGateway(mcpServer) - go pullImage(mcpServer.GatewayImage) if req.OutputTransport == mcpOutputTransportSSE { mcpServer.SsePath = req.SsePath } else { @@ -150,7 +150,7 @@ func (m McpServerService) Update(req request.McpServerUpdate) error { if err := mcpServerRepo.Save(mcpServer); err != nil { return err } - go startMcp(mcpServer) + go runMcpServerTask(mcpServer, req.TaskID, task.TaskUpdate) return nil } @@ -193,7 +193,6 @@ func (m McpServerService) Create(create request.McpServerCreate) error { ProtocolVersion: create.ProtocolVersion, } normalizeMcpServerGateway(mcpServer) - go pullImage(mcpServer.GatewayImage) if create.OutputTransport == mcpOutputTransportSSE { mcpServer.SsePath = create.SsePath } else { @@ -221,7 +220,7 @@ func (m McpServerService) Create(create request.McpServerCreate) error { return err } addProxy(mcpServer) - go startMcp(mcpServer) + go runMcpServerTask(mcpServer, create.TaskID, task.TaskCreate) return nil } @@ -896,25 +895,56 @@ func handleCreateParams(mcpServer *model.McpServer, environments []request.Envir return nil } +func runMcpServerTask(mcpServer *model.McpServer, taskID, operate string) { + mcpTask, err := task.NewTaskWithOps(mcpServer.Name, operate, task.TaskScopeAI, taskID, mcpServer.ID) + if err != nil { + mcpServer.Status = constant.StatusError + mcpServer.Message = err.Error() + _ = mcpServerRepo.Save(mcpServer) + return + } + mcpTask.AddSubTask(task.GetTaskName(mcpServer.Name, operate, task.TaskScopeAI), func(t *task.Task) error { + return startMcpWithTask(mcpServer, t) + }, nil) + _ = mcpTask.Execute() +} + func startMcp(mcpServer *model.McpServer) { + _ = startMcpWithTask(mcpServer, nil) +} + +func startMcpWithTask(mcpServer *model.McpServer, taskItem *task.Task) error { if err := refreshMcpServerFiles(mcpServer); err != nil { mcpServer.Status = constant.StatusError mcpServer.Message = err.Error() _ = mcpServerRepo.Save(mcpServer) - return + return err } composePath := path.Join(global.Dir.McpDir, mcpServer.Name, "docker-compose.yml") if mcpServer.Status != constant.StatusNormal { _, _ = compose.Down(composePath) } - if out, err := compose.Up(composePath); err != nil { + var err error + var out string + if taskItem != nil { + err = compose.UpWithTask(composePath, taskItem, false) + } else { + out, err = compose.Up(composePath) + } + if err != nil { mcpServer.Status = constant.StatusError - mcpServer.Message = out + if out != "" { + mcpServer.Message = out + } else { + mcpServer.Message = err.Error() + } + _ = mcpServerRepo.Save(mcpServer) + return err } else { mcpServer.Status = constant.StatusRunning mcpServer.Message = "" } - _ = syncMcpServerContainerStatus(mcpServer) + return syncMcpServerContainerStatus(mcpServer) } func syncMcpServerContainerStatus(mcpServer *model.McpServer) error { @@ -965,15 +995,3 @@ func GetWebsiteID() uint { websiteIDUint, _ := strconv.ParseUint(websiteID.Value, 10, 64) return uint(websiteIDUint) } - -func pullImage(image string) { - if global.CONF.Base.IsOffline { - return - } - if image == "" { - image = defaultMcpGatewayImageNpx - } - if err := docker.PullImage(image); err != nil { - global.LOG.Errorf("docker pull mcp image error: %s", err.Error()) - } -} diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts index 8956eff234e5..9fb406be96bc 100644 --- a/frontend/src/api/interface/ai.ts +++ b/frontend/src/api/interface/ai.ts @@ -182,6 +182,7 @@ export namespace AI { type: string; gatewayImage: string; protocolVersion: string; + taskID?: string; } export interface McpServerSearch extends ReqPage { diff --git a/frontend/src/views/ai/mcp/server/index.vue b/frontend/src/views/ai/mcp/server/index.vue index 6da6314727c5..b48e2b8e10f5 100644 --- a/frontend/src/views/ai/mcp/server/index.vue +++ b/frontend/src/views/ai/mcp/server/index.vue @@ -88,11 +88,12 @@ - + + @@ -111,6 +112,7 @@ import { onMounted, reactive, ref } from 'vue'; import { dateFormat } from '@/utils/date'; import McpServerOperate from './operate/index.vue'; import ComposeLogs from '@/components/log/compose/index.vue'; +import TaskLog from '@/components/log/task/index.vue'; import i18n from '@/lang'; import { MsgError, MsgSuccess } from '@/utils/message'; import BindDomain from './bind/index.vue'; @@ -122,6 +124,7 @@ const loading = ref(false); const createRef = ref(); const opRef = ref(); const composeLogRef = ref(); +const taskLogRef = ref(); const bindDomainRef = ref(); const configRef = ref(); const items = ref([]); @@ -263,6 +266,10 @@ const openLog = (row: AI.McpServer) => { }); }; +const openTaskLog = (taskID: string) => { + taskLogRef.value.openWithTaskID(taskID); +}; + const deleteServer = async (row: AI.McpServer) => { try { opRef.value.acceptParams({ diff --git a/frontend/src/views/ai/mcp/server/operate/index.vue b/frontend/src/views/ai/mcp/server/operate/index.vue index a61ab3f76d99..f4a5e1dd66d5 100644 --- a/frontend/src/views/ai/mcp/server/operate/index.vue +++ b/frontend/src/views/ai/mcp/server/operate/index.vue @@ -141,6 +141,7 @@ import { AI } from '@/api/interface/ai'; import { createMcpServer, getMcpDomain, updateMcpServer } from '@/api/modules/ai'; import { Rules } from '@/global/form-rules'; import i18n from '@/lang'; +import { newUUID } from '@/utils/id'; import { MsgSuccess } from '@/utils/message'; import { FormInstance } from 'element-plus'; import { ref, watch } from 'vue'; @@ -158,7 +159,7 @@ const defaultGatewayImages: Record = { npx: 'supercorp/supergateway:3.4.3', uvx: 'supercorp/supergateway:uvx', }; -const newMcpServer = () => { +const newMcpServer = (): AI.McpServer => { return { id: 0, name: '', @@ -179,9 +180,10 @@ const newMcpServer = () => { type: 'npx', gatewayImage: defaultGatewayImages.npx, protocolVersion: defaultProtocolVersion, + taskID: '', }; }; -const em = defineEmits(['close']); +const em = defineEmits(['close', 'task']); const mcpServer = ref(newMcpServer()); const rules = ref({ name: [Rules.requiredInput, Rules.appName], @@ -303,6 +305,8 @@ const submit = async (formEl: FormInstance | undefined) => { try { loading.value = true; normalizeGatewayConfig(); + const taskID = newUUID(); + mcpServer.value.taskID = taskID; mcpServer.value.baseUrl = mcpServer.value.protocol + mcpServer.value.url; if (mode.value == 'create') { await createMcpServer(mcpServer.value); @@ -311,6 +315,7 @@ const submit = async (formEl: FormInstance | undefined) => { await updateMcpServer(mcpServer.value); MsgSuccess(i18n.global.t('commons.msg.updateSuccess')); } + em('task', taskID); handleClose(); } finally { loading.value = false;