mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-06 13:27:43 +08:00
feat: Mcp Server support streamableHttp (#9761)
Refs https://github.com/1Panel-dev/1Panel/issues/8482
This commit is contained in:
parent
78f280c88f
commit
cab42b3c7d
18 changed files with 130 additions and 32 deletions
|
@ -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 {
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -33,6 +33,7 @@ func InitAgentDB() {
|
|||
migrations.InitAlertConfig,
|
||||
migrations.AddMethodToAlertLog,
|
||||
migrations.AddMethodToAlertTask,
|
||||
migrations.UpdateMcpServer,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -137,6 +137,8 @@ export namespace AI {
|
|||
hostIP: string;
|
||||
protocol: string;
|
||||
url: string;
|
||||
outputTransport: string;
|
||||
streamableHttpPath: string;
|
||||
}
|
||||
|
||||
export interface McpServerSearch extends ReqPage {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -692,6 +692,9 @@ const message = {
|
|||
importMcpJsonError: 'mcpServers 構造が正しくありません',
|
||||
bindDomainHelper:
|
||||
'ウェブサイトをバインドした後、インストールされたすべての MCP サーバーのアクセスアドレスを変更し、ポートへの外部アクセスを閉じます',
|
||||
outputTransport: '出力タイプ',
|
||||
streamableHttpPath: 'ストリーミングパス',
|
||||
streamableHttpPathHelper: '例:/mcp、他のサーバーと重複しないように注意してください',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
|
|
|
@ -688,6 +688,9 @@ const message = {
|
|||
importMcpJsonError: 'mcpServers 構造が正しくありません',
|
||||
bindDomainHelper:
|
||||
'웹사이트를 바인딩한 후, 설치된 모든 MCP 서버의 접근 주소를 수정하고 포트의 외부 접근을 닫습니다',
|
||||
outputTransport: '출력 유형',
|
||||
streamableHttpPath: '스트리밍 경로',
|
||||
streamableHttpPathHelper: '예: /mcp, 다른 서버와 중복되지 않도록 주의하세요',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -698,6 +698,9 @@ const message = {
|
|||
importMcpJsonError: 'Структура mcpServers некорректна',
|
||||
bindDomainHelper:
|
||||
'После привязки веб-сайта он изменит адрес доступа для всех установленных серверов MCP и закроет внешний доступ к портам',
|
||||
outputTransport: 'Тип вывода',
|
||||
streamableHttpPath: 'Путь потоковой передачи',
|
||||
streamableHttpPathHelper: 'Например: /mcp, обратите внимание, чтобы не перекрывать другие серверы',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -680,6 +680,9 @@ const message = {
|
|||
importMcpJson: '導入 MCP Server配置',
|
||||
importMcpJsonError: 'mcpServers 結構不正確',
|
||||
bindDomainHelper: '綁定網站之後會修改所有已安裝 MCP Server 的訪問地址,並關閉端口的外部訪問',
|
||||
outputTransport: '輸出類型',
|
||||
streamableHttpPath: '流式傳輸路徑',
|
||||
streamableHttpPathHelper: '例如:/mcp, 注意不要與其他 Server 重複',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
|
|
|
@ -679,6 +679,9 @@ const message = {
|
|||
importMcpJson: '导入 MCP Server 配置',
|
||||
importMcpJsonError: 'mcpServers 结构不正确',
|
||||
bindDomainHelper: '绑定网站之后会修改所有已安装 MCP Server 的访问地址,并关闭端口的外部访问',
|
||||
outputTransport: '输出类型',
|
||||
streamableHttpPath: '流式传输路径',
|
||||
streamableHttpPathHelper: '例如:/mcp, 注意不要与其他 Server 重复',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
|
|
|
@ -91,12 +91,28 @@
|
|||
<el-form-item :label="$t('app.containerName')" prop="containerName">
|
||||
<el-input v-model.trim="mcpServer.containerName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('aiTools.mcp.ssePath')" prop="ssePath">
|
||||
<el-form-item :label="$t('aiTools.mcp.outputTransport')" prop="outputTransport">
|
||||
<el-select v-model="mcpServer.outputTransport">
|
||||
<el-option label="sse" value="sse" />
|
||||
<el-option label="streamableHttp" value="streamableHttp" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('aiTools.mcp.ssePath')" prop="ssePath" v-if="mcpServer.outputTransport === 'sse'">
|
||||
<el-input v-model.trim="mcpServer.ssePath"></el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('aiTools.mcp.ssePathHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('aiTools.mcp.streamableHttpPath')"
|
||||
prop="streamableHttpPath"
|
||||
v-if="mcpServer.outputTransport === 'streamableHttp'"
|
||||
>
|
||||
<el-input v-model.trim="mcpServer.streamableHttpPath"></el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('aiTools.mcp.streamableHttpPathHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span>
|
||||
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue