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;