From cab42b3c7dea85dc5b33da5632153fb78a314185 Mon Sep 17 00:00:00 2001
From: CityFun <31820853+zhengkunwang223@users.noreply.github.com>
Date: Wed, 30 Jul 2025 18:54:51 +0800
Subject: [PATCH] feat: Mcp Server support streamableHttp (#9761)
Refs https://github.com/1Panel-dev/1Panel/issues/8482
---
agent/app/dto/request/mcp_server.go | 20 ++++++-----
agent/app/model/mcp_server.go | 28 ++++++++-------
agent/app/service/mcp_server.go | 34 ++++++++++++++-----
agent/cmd/server/mcp/compose.yml | 2 ++
agent/init/migration/migrate.go | 1 +
agent/init/migration/migrations/init.go | 13 +++++++
agent/utils/docker/docker.go | 12 +++++++
frontend/src/api/interface/ai.ts | 2 ++
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 | 23 ++++++++++++-
18 files changed, 130 insertions(+), 32 deletions(-)
diff --git a/agent/app/dto/request/mcp_server.go b/agent/app/dto/request/mcp_server.go
index 984c287db..9bd5d088c 100644
--- a/agent/app/dto/request/mcp_server.go
+++ b/agent/app/dto/request/mcp_server.go
@@ -9,15 +9,17 @@ type McpServerSearch struct {
}
type McpServerCreate struct {
- Name string `json:"name" validate:"required"`
- Command string `json:"command" validate:"required"`
- Environments []Environment `json:"environments"`
- Volumes []Volume `json:"volumes"`
- Port int `json:"port" validate:"required"`
- ContainerName string `json:"containerName"`
- BaseURL string `json:"baseUrl"`
- SsePath string `json:"ssePath"`
- HostIP string `json:"hostIP"`
+ Name string `json:"name" validate:"required"`
+ Command string `json:"command" validate:"required"`
+ Environments []Environment `json:"environments"`
+ Volumes []Volume `json:"volumes"`
+ Port int `json:"port" validate:"required"`
+ ContainerName string `json:"containerName"`
+ BaseURL string `json:"baseUrl"`
+ SsePath string `json:"ssePath"`
+ HostIP string `json:"hostIP"`
+ StreamableHttpPath string `json:"streamableHttpPath"`
+ OutputTransport string `json:"outputTransport" validate:"required"`
}
type McpServerUpdate struct {
diff --git a/agent/app/model/mcp_server.go b/agent/app/model/mcp_server.go
index 61ce66b65..e3207db8a 100644
--- a/agent/app/model/mcp_server.go
+++ b/agent/app/model/mcp_server.go
@@ -2,17 +2,19 @@ package model
type McpServer struct {
BaseModel
- Name string `json:"name"`
- DockerCompose string `json:"dockerCompose"`
- Command string `json:"command"`
- ContainerName string `json:"containerName"`
- Message string `json:"message"`
- Port int `json:"port"`
- Status string `json:"status"`
- Env string `json:"env"`
- BaseURL string `json:"baseUrl"`
- SsePath string `json:"ssePath"`
- WebsiteID int `json:"websiteID"`
- Dir string `json:"dir"`
- HostIP string `json:"hostIP"`
+ Name string `json:"name"`
+ DockerCompose string `json:"dockerCompose"`
+ Command string `json:"command"`
+ ContainerName string `json:"containerName"`
+ Message string `json:"message"`
+ Port int `json:"port"`
+ Status string `json:"status"`
+ Env string `json:"env"`
+ BaseURL string `json:"baseUrl"`
+ SsePath string `json:"ssePath"`
+ WebsiteID int `json:"websiteID"`
+ Dir string `json:"dir"`
+ HostIP string `json:"hostIP"`
+ StreamableHttpPath string `json:"streamableHttpPath"`
+ OutputTransport string `json:"outputTransport"`
}
diff --git a/agent/app/service/mcp_server.go b/agent/app/service/mcp_server.go
index 4b76fa1de..df6b9e023 100644
--- a/agent/app/service/mcp_server.go
+++ b/agent/app/service/mcp_server.go
@@ -86,6 +86,11 @@ 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())
+ }
+ }()
mcpServer, err := mcpServerRepo.GetFirst(repo.WithByID(req.ID))
if err != nil {
return err
@@ -108,6 +113,8 @@ func (m McpServerService) Update(req request.McpServerUpdate) error {
mcpServer.BaseURL = req.BaseURL
mcpServer.SsePath = req.SsePath
mcpServer.HostIP = req.HostIP
+ mcpServer.StreamableHttpPath = req.StreamableHttpPath
+ mcpServer.OutputTransport = req.OutputTransport
if err := handleCreateParams(mcpServer, req.Environments, req.Volumes); err != nil {
return err
}
@@ -130,6 +137,11 @@ 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())
+ }
+ }()
servers, _ := mcpServerRepo.List()
for _, server := range servers {
if server.Port == create.Port {
@@ -154,15 +166,17 @@ func (m McpServerService) Create(create request.McpServerCreate) error {
}
mcpDir := path.Join(global.Dir.McpDir, create.Name)
mcpServer := &model.McpServer{
- Name: create.Name,
- ContainerName: create.ContainerName,
- Port: create.Port,
- Command: create.Command,
- Status: constant.StatusStarting,
- BaseURL: create.BaseURL,
- SsePath: create.SsePath,
- Dir: mcpDir,
- HostIP: create.HostIP,
+ Name: create.Name,
+ ContainerName: create.ContainerName,
+ Port: create.Port,
+ Command: create.Command,
+ Status: constant.StatusStarting,
+ BaseURL: create.BaseURL,
+ SsePath: create.SsePath,
+ Dir: mcpDir,
+ HostIP: create.HostIP,
+ StreamableHttpPath: create.StreamableHttpPath,
+ OutputTransport: create.OutputTransport,
}
if err := handleCreateParams(mcpServer, create.Environments, create.Volumes); err != nil {
return err
@@ -524,6 +538,8 @@ func handleEnv(mcpServer *model.McpServer) gotenv.Env {
env["BASE_URL"] = mcpServer.BaseURL
env["SSE_PATH"] = mcpServer.SsePath
env["HOST_IP"] = mcpServer.HostIP
+ env["STREAMABLE_HTTP_PATH"] = mcpServer.StreamableHttpPath
+ env["OUTPUT_TRANSPORT"] = mcpServer.OutputTransport
envStr, _ := gotenv.Marshal(env)
mcpServer.Env = envStr
return env
diff --git a/agent/cmd/server/mcp/compose.yml b/agent/cmd/server/mcp/compose.yml
index 9aef87157..433ee2278 100644
--- a/agent/cmd/server/mcp/compose.yml
+++ b/agent/cmd/server/mcp/compose.yml
@@ -7,9 +7,11 @@ services:
- "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${PANEL_APP_PORT_HTTP}"
command: [
"--stdio", "${COMMAND}",
+ "--outputTransport","${OUTPUT_TRANSPORT}",
"--port", "${PANEL_APP_PORT_HTTP}",
"--baseUrl", "${BASE_URL}",
"--ssePath", "${SSE_PATH}",
+ "--streamableHttpPath", "${STREAMABLE_HTTP_PATH}",
"--messagePath", "${SSE_PATH}/messages"
]
networks:
diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go
index fe27532af..4d5b1da20 100644
--- a/agent/init/migration/migrate.go
+++ b/agent/init/migration/migrate.go
@@ -33,6 +33,7 @@ func InitAgentDB() {
migrations.InitAlertConfig,
migrations.AddMethodToAlertLog,
migrations.AddMethodToAlertTask,
+ migrations.UpdateMcpServer,
})
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 af465a146..7dab00f6d 100644
--- a/agent/init/migration/migrations/init.go
+++ b/agent/init/migration/migrations/init.go
@@ -419,3 +419,16 @@ var AddMethodToAlertTask = &gormigrate.Migration{
return nil
},
}
+
+var UpdateMcpServer = &gormigrate.Migration{
+ ID: "20250729-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("output_transport", "sse").Error; err != nil {
+ return err
+ }
+ return nil
+ },
+}
diff --git a/agent/utils/docker/docker.go b/agent/utils/docker/docker.go
index 6030f2adc..eaaff8dc8 100644
--- a/agent/utils/docker/docker.go
+++ b/agent/utils/docker/docker.go
@@ -353,3 +353,15 @@ func logProcess(progress map[string]interface{}, task *task.Task) {
}
_ = setLog(id, progressStr, task)
}
+
+func PullImage(imageName string) error {
+ cli, err := NewDockerClient()
+ if err != nil {
+ return err
+ }
+ defer cli.Close()
+ if _, err := cli.ImagePull(context.Background(), imageName, image.PullOptions{}); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts
index 037ad7a63..9e51bc982 100644
--- a/frontend/src/api/interface/ai.ts
+++ b/frontend/src/api/interface/ai.ts
@@ -137,6 +137,8 @@ export namespace AI {
hostIP: string;
protocol: string;
url: string;
+ outputTransport: string;
+ streamableHttpPath: string;
}
export interface McpServerSearch extends ReqPage {
diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts
index 5b35e6aa1..7461289a2 100644
--- a/frontend/src/lang/modules/en.ts
+++ b/frontend/src/lang/modules/en.ts
@@ -706,6 +706,9 @@ const message = {
importMcpJsonError: 'mcpServers structure is incorrect',
bindDomainHelper:
'After binding the website, it will modify the access address of all installed MCP Servers and close external access to the ports',
+ outputTransport: 'Output Type',
+ streamableHttpPath: 'Streaming Path',
+ streamableHttpPathHelper: 'For example: /mcp, note that it should not overlap with other Servers',
},
},
container: {
diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts
index ef6c02440..faee02fa3 100644
--- a/frontend/src/lang/modules/ja.ts
+++ b/frontend/src/lang/modules/ja.ts
@@ -692,6 +692,9 @@ const message = {
importMcpJsonError: 'mcpServers 構造が正しくありません',
bindDomainHelper:
'ウェブサイトをバインドした後、インストールされたすべての MCP サーバーのアクセスアドレスを変更し、ポートへの外部アクセスを閉じます',
+ outputTransport: '出力タイプ',
+ streamableHttpPath: 'ストリーミングパス',
+ streamableHttpPathHelper: '例:/mcp、他のサーバーと重複しないように注意してください',
},
},
container: {
diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts
index c15ddcfa6..8afce23ec 100644
--- a/frontend/src/lang/modules/ko.ts
+++ b/frontend/src/lang/modules/ko.ts
@@ -688,6 +688,9 @@ const message = {
importMcpJsonError: 'mcpServers 構造が正しくありません',
bindDomainHelper:
'웹사이트를 바인딩한 후, 설치된 모든 MCP 서버의 접근 주소를 수정하고 포트의 외부 접근을 닫습니다',
+ outputTransport: '출력 유형',
+ streamableHttpPath: '스트리밍 경로',
+ streamableHttpPathHelper: '예: /mcp, 다른 서버와 중복되지 않도록 주의하세요',
},
},
container: {
diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts
index bf4a5ed17..035360f75 100644
--- a/frontend/src/lang/modules/ms.ts
+++ b/frontend/src/lang/modules/ms.ts
@@ -705,6 +705,9 @@ const message = {
importMcpJsonError: 'Struktur mcpServers tidak betul',
bindDomainHelper:
'Setelah mengikat laman web, ia akan mengubah alamat akses semua Pelayan MCP yang dipasang dan menutup akses luaran ke pelabuhan',
+ outputTransport: 'Jenis Output',
+ streamableHttpPath: 'Laluan Streaming',
+ streamableHttpPathHelper: 'Contoh: /mcp, elakkan daripada bertindan dengan pelayan lain',
},
},
container: {
diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts
index 01c782285..594ada2aa 100644
--- a/frontend/src/lang/modules/pt-br.ts
+++ b/frontend/src/lang/modules/pt-br.ts
@@ -701,6 +701,9 @@ const message = {
importMcpJsonError: 'A estrutura mcpServers está incorreta',
bindDomainHelper:
'Após vincular o site, ele modificará o endereço de acesso de todos os servidores MCP instalados e fechará o acesso externo às portas',
+ outputTransport: 'Tipo de Saída',
+ streamableHttpPath: 'Caminho de Streaming',
+ streamableHttpPathHelper: 'Por exemplo: /mcp, certifique-se de que não se sobreponha a outros Servidores',
},
},
container: {
diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts
index 853b4e29a..f2fc11171 100644
--- a/frontend/src/lang/modules/ru.ts
+++ b/frontend/src/lang/modules/ru.ts
@@ -698,6 +698,9 @@ const message = {
importMcpJsonError: 'Структура mcpServers некорректна',
bindDomainHelper:
'После привязки веб-сайта он изменит адрес доступа для всех установленных серверов MCP и закроет внешний доступ к портам',
+ outputTransport: 'Тип вывода',
+ streamableHttpPath: 'Путь потоковой передачи',
+ streamableHttpPathHelper: 'Например: /mcp, обратите внимание, чтобы не перекрывать другие серверы',
},
},
container: {
diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts
index 90e3df85a..2a753467c 100644
--- a/frontend/src/lang/modules/tr.ts
+++ b/frontend/src/lang/modules/tr.ts
@@ -716,6 +716,9 @@ const message = {
importMcpJsonError: 'mcpServers yapısı yanlış',
bindDomainHelper:
'Web sitesini bağladıktan sonra, kurulu tüm MCP Sunucularının erişim adresini değiştirecek ve portlara harici erişimi kapatacaktır',
+ outputTransport: 'Çıktı Türü',
+ streamableHttpPath: 'Akış Yolu',
+ streamableHttpPathHelper: 'Örneğin: /mcp, diğer Sunucularla çakışmaması gerektiğine dikkat edin',
},
},
container: {
diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts
index f34d6c50d..ea957af0e 100644
--- a/frontend/src/lang/modules/zh-Hant.ts
+++ b/frontend/src/lang/modules/zh-Hant.ts
@@ -680,6 +680,9 @@ const message = {
importMcpJson: '導入 MCP Server配置',
importMcpJsonError: 'mcpServers 結構不正確',
bindDomainHelper: '綁定網站之後會修改所有已安裝 MCP Server 的訪問地址,並關閉端口的外部訪問',
+ outputTransport: '輸出類型',
+ streamableHttpPath: '流式傳輸路徑',
+ streamableHttpPathHelper: '例如:/mcp, 注意不要與其他 Server 重複',
},
},
container: {
diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts
index f2a963e75..fd4679296 100644
--- a/frontend/src/lang/modules/zh.ts
+++ b/frontend/src/lang/modules/zh.ts
@@ -679,6 +679,9 @@ const message = {
importMcpJson: '导入 MCP Server 配置',
importMcpJsonError: 'mcpServers 结构不正确',
bindDomainHelper: '绑定网站之后会修改所有已安装 MCP Server 的访问地址,并关闭端口的外部访问',
+ outputTransport: '输出类型',
+ streamableHttpPath: '流式传输路径',
+ streamableHttpPathHelper: '例如:/mcp, 注意不要与其他 Server 重复',
},
},
container: {
diff --git a/frontend/src/views/ai/mcp/server/operate/index.vue b/frontend/src/views/ai/mcp/server/operate/index.vue
index edb8dbc05..0c9d2037d 100644
--- a/frontend/src/views/ai/mcp/server/operate/index.vue
+++ b/frontend/src/views/ai/mcp/server/operate/index.vue
@@ -91,12 +91,28 @@
-
+
+
+
+
+
+
+
{{ $t('aiTools.mcp.ssePathHelper') }}
+
+
+
+ {{ $t('aiTools.mcp.streamableHttpPathHelper') }}
+
+
@@ -142,6 +158,8 @@ const newMcpServer = () => {
hostIP: '127.0.0.1',
protocol: 'http://',
url: '',
+ outputTransport: 'sse',
+ streamableHttpPath: '',
};
};
const em = defineEmits(['close']);
@@ -155,6 +173,8 @@ const rules = ref({
ssePath: [Rules.requiredInput],
key: [Rules.requiredInput],
value: [Rules.requiredInput],
+ outputTransport: [Rules.requiredSelect],
+ streamableHttpPath: [Rules.requiredInput],
});
const hasWebsite = ref(false);
@@ -180,6 +200,7 @@ const acceptParams = async (params: AI.McpServer) => {
const parts = mcpServer.value.baseUrl.split(/(https?:\/\/)/).filter(Boolean);
mcpServer.value.protocol = parts[0];
mcpServer.value.url = parts[1];
+ mcpServer.value.outputTransport = mcpServer.value.outputTransport || 'sse';
} else {
mcpServer.value = newMcpServer();
if (params.port) {