Files
cloudpods/pkg/mcp-server/server/server.go
T
cwz_eikoh b8aa072b7e Feature(mcp): support x-api-key authentication for mcp-server (#24471)
* feat(mcp-server): support base64 ak/sk

* fix(mcp-agent): try to fix route of default-mcp-tools
2026-03-19 15:04:37 +08:00

223 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}