mirror of
				https://github.com/1Panel-dev/1Panel.git
				synced 2025-10-31 03:07:34 +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