mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-18 05:19:19 +08:00
feat: Mcp Server support uvx (#10261)
Refs https://github.com/1Panel-dev/1Panel/issues/10118
This commit is contained in:
parent
ac4962cf19
commit
23876ed186
16 changed files with 80 additions and 25 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -17,4 +17,5 @@ type McpServer struct {
|
|||
HostIP string `json:"hostIP"`
|
||||
StreamableHttpPath string `json:"streamableHttpPath"`
|
||||
OutputTransport string `json:"outputTransport"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ func InitAgentDB() {
|
|||
migrations.AddColumnToAlert,
|
||||
migrations.UpdateWebsiteSSL,
|
||||
migrations.AddQuickJump,
|
||||
migrations.UpdateMcpServerAddType,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ export namespace AI {
|
|||
url: string;
|
||||
outputTransport: string;
|
||||
streamableHttpPath: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface McpServerSearch extends ReqPage {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -22,16 +22,27 @@
|
|||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model="mcpServer.name" :disabled="mode == 'edit'" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.type')" prop="type">
|
||||
<el-select v-model="mcpServer.type">
|
||||
<el-option label="npx" value="npx" />
|
||||
<el-option label="uvx" value="uvx" />
|
||||
</el-select>
|
||||
<span class="input-help">
|
||||
{{ $t('aiTools.mcp.' + mcpServer.type + 'Helper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('runtime.runScript')" prop="command">
|
||||
<el-input
|
||||
v-model="mcpServer.command"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
:placeholder="$t('aiTools.mcp.commandPlaceHolder')"
|
||||
:placeholder="
|
||||
$t('ssl.commonNameHelper') +
|
||||
(mcpServer.type == 'npx'
|
||||
? ' npx -y @modelcontextprotocol/server-github'
|
||||
: ' uvx mcp-server-fetch')
|
||||
"
|
||||
></el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('aiTools.mcp.commandHelper', ['@modelcontextprotocol/server-github']) }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<div>
|
||||
<el-text>{{ $t('aiTools.mcp.environment') }}</el-text>
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue