feat: Add TaskLog for MCP Servers (#13160)

This commit is contained in:
CityFun
2026-07-01 16:18:59 +08:00
committed by GitHub
parent 18a469e4be
commit fe9628f91c
5 changed files with 56 additions and 24 deletions

View File

@@ -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 {

View File

@@ -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 startMcp(mcpServer *model.McpServer) {
if err := refreshMcpServerFiles(mcpServer); err != 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 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())
}
}

View File

@@ -182,6 +182,7 @@ export namespace AI {
type: string;
gatewayImage: string;
protocolVersion: string;
taskID?: string;
}
export interface McpServerSearch extends ReqPage {

View File

@@ -88,11 +88,12 @@
</ComplexTable>
</template>
</LayoutContent>
<McpServerOperate ref="createRef" @close="searchWithTimeOut" />
<McpServerOperate ref="createRef" @close="searchWithTimeOut" @task="openTaskLog" />
<OpDialog ref="opRef" @search="search" />
<ComposeLogs ref="composeLogRef" />
<BindDomain ref="bindDomainRef" @close="searchWithTimeOut" />
<Config ref="configRef" />
<TaskLog ref="taskLogRef" width="70%" @close="search" />
</div>
</template>
@@ -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<AI.McpServer[]>([]);
@@ -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({

View File

@@ -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<string, string> = {
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;