From 23876ed1866abf47decea447c334555a07431fa1 Mon Sep 17 00:00:00 2001 From: CityFun <31820853+zhengkunwang223@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:21:33 +0800 Subject: [PATCH] feat: Mcp Server support uvx (#10261) Refs https://github.com/1Panel-dev/1Panel/issues/10118 --- agent/app/dto/request/mcp_server.go | 1 + agent/app/model/mcp_server.go | 1 + agent/app/service/mcp_server.go | 39 +++++++++++++------ agent/init/migration/migrate.go | 1 + agent/init/migration/migrations/init.go | 13 +++++++ frontend/src/api/interface/ai.ts | 1 + frontend/src/lang/modules/en.ts | 3 +- frontend/src/lang/modules/ja.ts | 3 +- frontend/src/lang/modules/ko.ts | 3 +- frontend/src/lang/modules/ms.ts | 3 +- frontend/src/lang/modules/pt-br.ts | 3 +- frontend/src/lang/modules/ru.ts | 3 +- frontend/src/lang/modules/tr.ts | 3 +- frontend/src/lang/modules/zh-Hant.ts | 3 +- frontend/src/lang/modules/zh.ts | 3 +- .../src/views/ai/mcp/server/operate/index.vue | 22 +++++++++-- 16 files changed, 80 insertions(+), 25 deletions(-) diff --git a/agent/app/dto/request/mcp_server.go b/agent/app/dto/request/mcp_server.go index 9bd5d088c..721cfb00e 100644 --- a/agent/app/dto/request/mcp_server.go +++ b/agent/app/dto/request/mcp_server.go @@ -20,6 +20,7 @@ type McpServerCreate struct { HostIP string `json:"hostIP"` StreamableHttpPath string `json:"streamableHttpPath"` OutputTransport string `json:"outputTransport" validate:"required"` + Type string `json:"type" validate:"required"` } type McpServerUpdate struct { diff --git a/agent/app/model/mcp_server.go b/agent/app/model/mcp_server.go index e3207db8a..133bc8f44 100644 --- a/agent/app/model/mcp_server.go +++ b/agent/app/model/mcp_server.go @@ -17,4 +17,5 @@ type McpServer struct { HostIP string `json:"hostIP"` StreamableHttpPath string `json:"streamableHttpPath"` OutputTransport string `json:"outputTransport"` + Type string `json:"type"` } diff --git a/agent/app/service/mcp_server.go b/agent/app/service/mcp_server.go index df6b9e023..ddc0c1257 100644 --- a/agent/app/service/mcp_server.go +++ b/agent/app/service/mcp_server.go @@ -59,7 +59,11 @@ func (m McpServerService) Page(req request.McpServerSearch) response.McpServersR Environments: make([]request.Environment, 0), Volumes: make([]request.Volume, 0), } - project, _ := docker.GetComposeProject(item.Name, path.Join(global.Dir.McpDir, item.Name), []byte(item.DockerCompose), []byte(item.Env), true) + project, err := docker.GetComposeProject(item.Name, path.Join(global.Dir.McpDir, item.Name), []byte(item.DockerCompose), []byte(item.Env), true) + if err != nil { + global.LOG.Errorf("get mcp compose project error: %s", err.Error()) + continue + } for _, service := range project.Services { if service.Environment != nil { for key, value := range service.Environment { @@ -86,11 +90,7 @@ func (m McpServerService) Page(req request.McpServerSearch) response.McpServersR } func (m McpServerService) Update(req request.McpServerUpdate) error { - go func() { - if err := docker.PullImage("supercorp/supergateway:latest"); err != nil { - global.LOG.Errorf("docker pull mcp image error: %s", err.Error()) - } - }() + go pullImage(req.Type) mcpServer, err := mcpServerRepo.GetFirst(repo.WithByID(req.ID)) if err != nil { return err @@ -115,6 +115,7 @@ func (m McpServerService) Update(req request.McpServerUpdate) error { mcpServer.HostIP = req.HostIP mcpServer.StreamableHttpPath = req.StreamableHttpPath mcpServer.OutputTransport = req.OutputTransport + mcpServer.Type = req.Type if err := handleCreateParams(mcpServer, req.Environments, req.Volumes); err != nil { return err } @@ -137,11 +138,7 @@ func (m McpServerService) Update(req request.McpServerUpdate) error { } func (m McpServerService) Create(create request.McpServerCreate) error { - go func() { - if err := docker.PullImage("supercorp/supergateway:latest"); err != nil { - global.LOG.Errorf("docker pull mcp image error: %s", err.Error()) - } - }() + go pullImage(create.Type) servers, _ := mcpServerRepo.List() for _, server := range servers { if server.Port == create.Port { @@ -177,6 +174,7 @@ func (m McpServerService) Create(create request.McpServerCreate) error { HostIP: create.HostIP, StreamableHttpPath: create.StreamableHttpPath, OutputTransport: create.OutputTransport, + Type: create.Type, } if err := handleCreateParams(mcpServer, create.Environments, create.Volumes); err != nil { return err @@ -590,6 +588,11 @@ func handleCreateParams(mcpServer *model.McpServer, environments []request.Envir } serviceValue["volumes"] = volumeList } + if mcpServer.Type == "npx" { + serviceValue["image"] = "supercorp/supergateway:latest" + } else { + serviceValue["image"] = "supercorp/supergateway:uvx" + } services[mcpServer.Name] = serviceValue composeByte, err := yaml.Marshal(composeMap) @@ -641,7 +644,7 @@ func syncMcpServerContainerStatus(mcpServer *model.McpServer) error { case "restarting": mcpServer.Status = constant.StatusRestarting default: - if mcpServer.Status != constant.StatusBuilding { + if mcpServer.Status != constant.StatusStarting { mcpServer.Status = constant.StatusStopped } } @@ -656,3 +659,15 @@ func GetWebsiteID() uint { websiteIDUint, _ := strconv.ParseUint(websiteID.Value, 10, 64) return uint(websiteIDUint) } + +func pullImage(imageType string) { + if imageType == "npx" { + if err := docker.PullImage("supercorp/supergateway:latest"); err != nil { + global.LOG.Errorf("docker pull mcp image error: %s", err.Error()) + } + } else { + if err := docker.PullImage("supercorp/supergateway:uvx"); err != nil { + global.LOG.Errorf("docker pull mcp image error: %s", err.Error()) + } + } +} diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index 6a7ed525d..80c5274ce 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -38,6 +38,7 @@ func InitAgentDB() { migrations.AddColumnToAlert, migrations.UpdateWebsiteSSL, migrations.AddQuickJump, + migrations.UpdateMcpServerAddType, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index c3a7f6271..4efade8ca 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -496,3 +496,16 @@ var AddQuickJump = &gormigrate.Migration{ return nil }, } + +var UpdateMcpServerAddType = &gormigrate.Migration{ + ID: "20250904-update-mcp-server", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.McpServer{}); err != nil { + return err + } + if err := tx.Model(&model.McpServer{}).Where("1=1").Update("type", "npx").Error; err != nil { + return err + } + return nil + }, +} diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts index 9e51bc982..4ff02dae9 100644 --- a/frontend/src/api/interface/ai.ts +++ b/frontend/src/api/interface/ai.ts @@ -139,6 +139,7 @@ export namespace AI { url: string; outputTransport: string; streamableHttpPath: string; + type: string; } export interface McpServerSearch extends ReqPage { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index e84d50682..6a2196f41 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -696,7 +696,6 @@ const message = { server: 'MCP Server', create: 'Add MCP Server', edit: 'Edit MCP Server', - commandHelper: 'For example: npx -y {0}', baseUrl: 'External Access Path', baseUrlHelper: 'For example: http://192.168.1.2:8000', ssePath: 'SSE Path', @@ -717,6 +716,8 @@ const message = { outputTransport: 'Output Type', streamableHttpPath: 'Streaming Path', streamableHttpPathHelper: 'For example: /mcp, note that it should not overlap with other Servers', + npxHelper: 'Suitable for mcp started with npx or binary', + uvxHelper: 'Suitable for mcp started with uvx', }, }, container: { diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index c361aff66..1865510f1 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -684,7 +684,6 @@ const message = { server: 'MCP サーバー', create: 'サーバーを追加', edit: 'サーバーを編集', - commandHelper: '例: npx -y {0}', baseUrl: '外部アクセスパス', baseUrlHelper: '例: http://192.168.1.2:8000', ssePath: 'SSE パス', @@ -705,6 +704,8 @@ const message = { outputTransport: '出力タイプ', streamableHttpPath: 'ストリーミングパス', streamableHttpPathHelper: '例:/mcp、他のサーバーと重複しないように注意してください', + npxHelper: 'npx またはバイナリで起動する mcp に適しています', + uvxHelper: 'uvx で起動する mcp に適しています', }, }, container: { diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 0a94f0f72..82e623601 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -680,7 +680,6 @@ const message = { server: 'MCP サーバー', create: 'サーバーを追加', edit: 'サーバーを編集', - commandHelper: '例: npx -y {0}', baseUrl: '外部アクセスパス', baseUrlHelper: '例: http://192.168.1.2:8000', ssePath: 'SSE パス', @@ -701,6 +700,8 @@ const message = { outputTransport: '출력 유형', streamableHttpPath: '스트리밍 경로', streamableHttpPathHelper: '예: /mcp, 다른 서버와 중복되지 않도록 주의하세요', + npxHelper: 'npx 또는 바이너리로 시작하는 mcp에 적합', + uvxHelper: 'uvx로 시작하는 mcp에 적합', }, }, container: { diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index e42b6b202..f8b97ad0f 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -697,7 +697,6 @@ const message = { server: 'Pelayan MCP', create: 'Tambah Pelayan', edit: 'Edit Pelayan', - commandHelper: 'Contoh: npx -y {0}', baseUrl: 'Laluan Akses Luar', baseUrlHelper: 'Contoh: http://192.168.1.2:8000', ssePath: 'Laluan SSE', @@ -718,6 +717,8 @@ const message = { outputTransport: 'Jenis Output', streamableHttpPath: 'Laluan Streaming', streamableHttpPathHelper: 'Contoh: /mcp, elakkan daripada bertindan dengan pelayan lain', + npxHelper: 'Sesuai untuk mcp yang dimulakan dengan npx atau binari', + uvxHelper: 'Sesuai untuk mcp yang dimulakan dengan uvx', }, }, container: { diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index e91efb45e..b2555831c 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -693,7 +693,6 @@ const message = { server: 'Servidor MCP', create: 'Adicionar Servidor', edit: 'Editar Servidor', - commandHelper: 'Por exemplo: npx -y {0}', baseUrl: 'Caminho de Acesso Externo', baseUrlHelper: 'Por exemplo: http://192.168.1.2:8000', ssePath: 'Caminho SSE', @@ -714,6 +713,8 @@ const message = { outputTransport: 'Tipo de Saída', streamableHttpPath: 'Caminho de Streaming', streamableHttpPathHelper: 'Por exemplo: /mcp, certifique-se de que não se sobreponha a outros Servidores', + npxHelper: 'Adequado para mcp iniciado com npx ou binário', + uvxHelper: 'Adequado para mcp iniciado com uvx', }, }, container: { diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index a0a909043..32f3832c0 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -690,7 +690,6 @@ const message = { server: 'Сервер MCP', create: 'Добавить сервер', edit: 'Редактировать сервер', - commandHelper: 'Например: npx -y {0}', baseUrl: 'Внешний путь доступа', baseUrlHelper: 'Например: http://192.168.1.2:8000', ssePath: 'Путь SSE', @@ -711,6 +710,8 @@ const message = { outputTransport: 'Тип вывода', streamableHttpPath: 'Путь потоковой передачи', streamableHttpPathHelper: 'Например: /mcp, обратите внимание, чтобы не перекрывать другие серверы', + npxHelper: 'Подходит для mcp, запущенного с помощью npx или бинарного файла', + uvxHelper: 'Подходит для mcp, запущенного с помощью uvx', }, }, container: { diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 5d0221c5a..8492f3b2f 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -706,7 +706,6 @@ const message = { server: 'MCP Sunucusu', create: 'MCP Sunucusu Ekle', edit: 'MCP Sunucusunu Düzenle', - commandHelper: 'Örneğin: npx -y {0}', baseUrl: 'Harici Erişim Yolu', baseUrlHelper: 'Örneğin: http://192.168.1.2:8000', ssePath: 'SSE Yolu', @@ -727,6 +726,8 @@ const message = { outputTransport: 'Çıktı Türü', streamableHttpPath: 'Akış Yolu', streamableHttpPathHelper: 'Örneğin: /mcp, diğer Sunucularla çakışmaması gerektiğine dikkat edin', + npxHelper: 'npx veya ikili dosya ile başlatılan mcp için uygundur', + uvxHelper: 'uvx ile başlatılan mcp için uygundur', }, }, container: { diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index ae6a3e566..168b0d1b7 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -671,7 +671,6 @@ const message = { server: 'MCP Server', create: '创建 MCP Server', edit: '編輯 MCP Server', - commandHelper: '例如:npx -y {0}', baseUrl: '外部訪問路徑', baseUrlHelper: '例如:http://192.168.1.2:8000', ssePath: 'SSE 路徑', @@ -691,6 +690,8 @@ const message = { outputTransport: '輸出類型', streamableHttpPath: '流式傳輸路徑', streamableHttpPathHelper: '例如:/mcp, 注意不要與其他 Server 重複', + npxHelper: '適合 npx 或者 二進制啟動的 mcp', + uvxHelper: '適合 uvx 啟動的 mcp', }, }, container: { diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 102b6f789..3e74f48b1 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -670,7 +670,6 @@ const message = { server: 'MCP Server', create: '创建 MCP Server', edit: '编辑 MCP Server', - commandHelper: '例如:npx -y {0}', baseUrl: '外部访问路径', baseUrlHelper: '例如:http://192.168.1.1:8000', ssePath: 'SSE 路径', @@ -690,6 +689,8 @@ const message = { outputTransport: '输出类型', streamableHttpPath: '流式传输路径', streamableHttpPathHelper: '例如:/mcp, 注意不要与其他 Server 重复', + npxHelper: '适合 npx 或者 二进制启动的 mcp', + uvxHelper: '适合 uvx 启动的 mcp', }, }, container: { diff --git a/frontend/src/views/ai/mcp/server/operate/index.vue b/frontend/src/views/ai/mcp/server/operate/index.vue index 0c9d2037d..d46b2a6b6 100644 --- a/frontend/src/views/ai/mcp/server/operate/index.vue +++ b/frontend/src/views/ai/mcp/server/operate/index.vue @@ -22,16 +22,27 @@ + + + + + + + {{ $t('aiTools.mcp.' + mcpServer.type + 'Helper') }} + + - - {{ $t('aiTools.mcp.commandHelper', ['@modelcontextprotocol/server-github']) }} -
{{ $t('aiTools.mcp.environment') }} @@ -160,6 +171,7 @@ const newMcpServer = () => { url: '', outputTransport: 'sse', streamableHttpPath: '', + type: 'npx', }; }; const em = defineEmits(['close']); @@ -175,6 +187,7 @@ const rules = ref({ value: [Rules.requiredInput], outputTransport: [Rules.requiredSelect], streamableHttpPath: [Rules.requiredInput], + type: [Rules.requiredSelect], }); const hasWebsite = ref(false); @@ -201,6 +214,7 @@ const acceptParams = async (params: AI.McpServer) => { mcpServer.value.protocol = parts[0]; mcpServer.value.url = parts[1]; mcpServer.value.outputTransport = mcpServer.value.outputTransport || 'sse'; + mcpServer.value.type = mcpServer.value.type || 'npx'; } else { mcpServer.value = newMcpServer(); if (params.port) {