mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-10-31 02:38:07 +08:00
* feat(mcp-server): support base64 ak/sk * fix(mcp-agent): try to fix route of default-mcp-tools
223 lines
6.4 KiB
Go
223 lines
6.4 KiB
Go
// Copyright 2019 Yunion
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
package server
|
||
|
||
import (
|
||
"context"
|
||
"encoding/base64"
|
||
"fmt"
|
||
"net/http"
|
||
"strings"
|
||
|
||
"github.com/mark3labs/mcp-go/server"
|
||
|
||
"yunion.io/x/log"
|
||
"yunion.io/x/pkg/appctx"
|
||
|
||
api "yunion.io/x/onecloud/pkg/apis/identity"
|
||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||
"yunion.io/x/onecloud/pkg/mcp-server/adapters"
|
||
"yunion.io/x/onecloud/pkg/mcp-server/options"
|
||
"yunion.io/x/onecloud/pkg/mcp-server/registry"
|
||
"yunion.io/x/onecloud/pkg/mcp-server/tools"
|
||
)
|
||
|
||
// CloudpodsMCPServer 是 MCP 服务器的核心结构体,包含配置、日志、MCP 实例、注册中心和工具列表
|
||
type CloudpodsMCPServer struct {
|
||
mcpServer *server.MCPServer
|
||
registry *registry.Registry
|
||
tools []tools.Tool
|
||
}
|
||
|
||
// NewServer 创建一个新的 Cloudpods MCP 服务器实例,初始化 MCP 服务器和注册中心,并创建所有工具
|
||
func NewServer() *CloudpodsMCPServer {
|
||
|
||
// 创建mcp server对象
|
||
mcpServer := server.NewMCPServer(
|
||
options.Options.MCPServerName,
|
||
options.Options.MCPServerVersion,
|
||
server.WithToolCapabilities(false),
|
||
server.WithRecovery(),
|
||
)
|
||
|
||
// 创建注册中心对象
|
||
reg := registry.NewRegistry()
|
||
|
||
var allTools []tools.Tool
|
||
|
||
// 创建mcclient sdk的适配器对象
|
||
adapter := adapters.NewCloudpodsAdapter()
|
||
|
||
// 创建具体的工具函数对象
|
||
// 用于查询资源的工具函数
|
||
regionsTool := tools.NewCloudpodsRegionsTool(adapter)
|
||
vpcsTool := tools.NewCloudpodsVPCsTool(adapter)
|
||
networksTool := tools.NewCloudpodsNetworksTool(adapter)
|
||
imagesTool := tools.NewCloudpodsImagesTool(adapter)
|
||
skusTool := tools.NewCloudpodsServerSkusTool(adapter)
|
||
storagesTool := tools.NewCloudpodsStoragesTool(adapter)
|
||
serversTool := tools.NewCloudpodsServersTool(adapter)
|
||
|
||
// 用于操作资源的工具函数
|
||
serverStartTool := tools.NewCloudpodsServerStartTool(adapter)
|
||
serverStopTool := tools.NewCloudpodsServerStopTool(adapter)
|
||
serverRestartTool := tools.NewCloudpodsServerRestartTool(adapter)
|
||
serverResetPasswordTool := tools.NewCloudpodsServerResetPasswordTool(adapter)
|
||
serverDeleteTool := tools.NewCloudpodsServerDeleteTool(adapter)
|
||
serverCreateTool := tools.NewCloudpodsServerCreateTool(adapter)
|
||
serverMonitorTool := tools.NewCloudpodsServerMonitorTool(adapter)
|
||
serverStatsTool := tools.NewCloudpodsServerStatsTool(adapter)
|
||
|
||
// 将所有的工具函数存储到一个切片中
|
||
allTools = append(
|
||
allTools,
|
||
regionsTool,
|
||
vpcsTool,
|
||
networksTool,
|
||
imagesTool,
|
||
skusTool,
|
||
storagesTool,
|
||
serversTool,
|
||
|
||
serverStartTool,
|
||
serverStopTool,
|
||
serverRestartTool,
|
||
serverResetPasswordTool,
|
||
serverDeleteTool,
|
||
serverCreateTool,
|
||
serverMonitorTool,
|
||
serverStatsTool,
|
||
)
|
||
|
||
return &CloudpodsMCPServer{
|
||
mcpServer: mcpServer,
|
||
registry: reg,
|
||
tools: allTools,
|
||
}
|
||
}
|
||
|
||
// Initialize 初始化注册中心和所有工具
|
||
func (s *CloudpodsMCPServer) Initialize() error {
|
||
|
||
// 初始化工具注册中心
|
||
if err := s.registry.Initialize(s.mcpServer); err != nil {
|
||
return fmt.Errorf("初始化工具注册中心失败: %w", err)
|
||
}
|
||
|
||
// 注册内置工具
|
||
if err := s.registerAllTools(); err != nil {
|
||
return fmt.Errorf("注册内置工具失败: %w", err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// registerAllTools 将所有工具注册到注册中心
|
||
func (s *CloudpodsMCPServer) registerAllTools() error {
|
||
for _, tool := range s.tools {
|
||
// 注册距离查询工具
|
||
if err := s.registry.RegisterTool(
|
||
tool.GetName(),
|
||
tool.GetTool(),
|
||
tool.Handle,
|
||
); err != nil {
|
||
return fmt.Errorf("注册工具失败: %w", err)
|
||
}
|
||
}
|
||
|
||
log.Infof("All tools register completed")
|
||
return nil
|
||
}
|
||
|
||
// Start 以sse模式启动 mcp 服务
|
||
func (s *CloudpodsMCPServer) Start() error {
|
||
// 设置 contextFunc 来从 HTTP header 中提取认证信息并放入 context
|
||
// 支持:X-Auth-Token(token)、AK/SK(Cursor 双 header)、X-API-Key(Claude 单 header:token 或 base64(ak:sk))
|
||
contextFunc := func(ctx context.Context, r *http.Request) context.Context {
|
||
tokenStr := r.Header.Get(api.AUTH_TOKEN_HEADER)
|
||
akStr := r.Header.Get("AK")
|
||
skStr := r.Header.Get("SK")
|
||
apiKey := r.Header.Get("X-API-Key")
|
||
|
||
// 1) 优先使用 X-Auth-Token
|
||
if tokenStr != "" {
|
||
if auth.IsAuthed() {
|
||
userCred, err := auth.Verify(ctx, tokenStr)
|
||
if err != nil {
|
||
log.Errorf("Verify token failed: %s", err)
|
||
} else {
|
||
ctx = context.WithValue(ctx, appctx.APP_CONTEXT_KEY_AUTH_TOKEN, userCred)
|
||
log.Debugf("UserCred set in context from HTTP header token")
|
||
return ctx
|
||
}
|
||
}
|
||
}
|
||
|
||
// 2) Cursor 方式:直接使用 AK、SK 两个 Header
|
||
if akStr != "" && skStr != "" {
|
||
ctx = context.WithValue(ctx, adapters.ContextKeyAK, akStr)
|
||
ctx = context.WithValue(ctx, adapters.ContextKeySK, skStr)
|
||
log.Debugf("AK/SK set in context from headers")
|
||
return ctx
|
||
}
|
||
|
||
// 3) Claude 方式:X-API-Key 可为 token,或 base64(ak:sk)
|
||
if apiKey != "" {
|
||
if auth.IsAuthed() {
|
||
if userCred, err := auth.Verify(ctx, apiKey); err == nil {
|
||
ctx = context.WithValue(ctx, appctx.APP_CONTEXT_KEY_AUTH_TOKEN, userCred)
|
||
log.Debugf("UserCred set in context from X-API-Key token")
|
||
return ctx
|
||
}
|
||
}
|
||
decoded, err := base64.StdEncoding.DecodeString(apiKey)
|
||
if err == nil {
|
||
parts := strings.SplitN(string(decoded), ":", 2)
|
||
if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
|
||
ctx = context.WithValue(ctx, adapters.ContextKeyAK, parts[0])
|
||
ctx = context.WithValue(ctx, adapters.ContextKeySK, parts[1])
|
||
log.Debugf("AK/SK set in context from X-API-Key base64(ak:sk)")
|
||
return ctx
|
||
}
|
||
}
|
||
}
|
||
|
||
return ctx
|
||
}
|
||
|
||
sseServer := server.NewSSEServer(
|
||
s.mcpServer,
|
||
server.WithSSEContextFunc(contextFunc),
|
||
)
|
||
|
||
if err := sseServer.Start(fmt.Sprintf("%s:%d", options.Options.Address, options.Options.Port)); err != nil {
|
||
return err
|
||
}
|
||
log.Infof("Start mcp server successfully")
|
||
|
||
return nil
|
||
}
|
||
|
||
// StartStdio 以stdio模式启动 mcp 服务
|
||
func (s *CloudpodsMCPServer) StartStdio() error {
|
||
|
||
err := server.ServeStdio(s.mcpServer)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
log.Infof("Start mcp server successfully")
|
||
return nil
|
||
}
|