Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,11 @@ class MCPServerSLZ(ExtensibleFieldMixin, serializers.ModelSerializer):
default=False,
help_text="是否开启 OAuth2 公开客户端模式,开启后将会对 bk_app_code=public 的应用进行授权",
)
raw_response_enabled = serializers.BooleanField(
required=False,
default=False,
help_text="是否返回原始响应,开启后 mcp-proxy 将直接返回 API 响应结果,不添加 request_id 等额外信息",
)
category_names = serializers.ListField(
child=serializers.CharField(),
required=False,
Expand All @@ -870,6 +875,7 @@ class Meta:
"protocol_type",
"target_app_codes",
"oauth2_public_client_enabled",
"raw_response_enabled",
"category_names",
)
lookup_field = "id"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ class MCPServerCreateInputSLZ(serializers.ModelSerializer):
default=False,
help_text="是否开启 OAuth2 公开客户端模式,开启后将会对 bk_app_code=public 的应用进行授权",
)
raw_response_enabled = serializers.BooleanField(
required=False,
default=False,
help_text="是否返回原始响应,开启后 mcp-proxy 将直接返回 API 响应结果,不添加 request_id 等额外信息",
)

class Meta:
ref_name = "apigateway.apis.web.mcp_server.serializers.MCPServerCreateInputSLZ"
Expand All @@ -246,6 +251,7 @@ class Meta:
"protocol_type",
"category_ids",
"oauth2_public_client_enabled",
"raw_response_enabled",
)
lookup_field = "id"
validators = [MCPServerValidator()]
Expand Down Expand Up @@ -351,6 +357,11 @@ def get_title(self, obj) -> str:
read_only=True, help_text="是否开启 OAuth2 公开客户端模式,开启后将会对 bk_app_code=public 的应用进行授权"
)

raw_response_enabled = serializers.BooleanField(
read_only=True,
help_text="是否返回原始响应,开启后 mcp-proxy 将直接返回 API 响应结果,不添加 request_id 等额外信息",
)

stage = serializers.SerializerMethodField(help_text="MCPServer 环境")

updated_time = serializers.DateTimeField(read_only=True, help_text="MCPServer 更新时间")
Expand Down Expand Up @@ -447,6 +458,10 @@ class MCPServerUpdateInputSLZ(serializers.ModelSerializer):
required=False,
help_text="是否开启 OAuth2 公开客户端模式,开启后将会对 bk_app_code=public 的应用进行授权",
)
raw_response_enabled = serializers.BooleanField(
required=False,
help_text="是否返回原始响应,开启后 mcp-proxy 将直接返回 API 响应结果,不添加 request_id 等额外信息",
)

def validate_resource_names(self, resource_names):
"""验证资源名称列表"""
Expand Down Expand Up @@ -496,6 +511,7 @@ class Meta:
"protocol_type",
"category_ids",
"oauth2_public_client_enabled",
"raw_response_enabled",
)
lookup_field = "id"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated manually

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("mcp_server", "0012_mcpserver_oauth2_public_client_enabled"),
]

operations = [
migrations.AddField(
model_name="mcpserver",
name="raw_response_enabled",
field=models.BooleanField(
default=False,
help_text="是否返回原始响应,开启后 mcp-proxy 将直接返回 API 响应结果,不添加 request_id 等额外信息",
),
),
]
5 changes: 5 additions & 0 deletions src/dashboard/apigateway/apigateway/apps/mcp_server/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ class MCPServer(TimestampedModelMixin, OperatorModelMixin):
help_text=_("是否开启 OAuth2 公开客户端模式,开启后将会对 bk_app_code=public 的应用进行授权"),
)

raw_response_enabled = models.BooleanField(
default=False,
help_text=_("是否返回原始响应,开启后 mcp-proxy 将直接返回 API 响应结果,不添加 request_id 等额外信息"),
)

# 分类关联(多对多关系)
categories = models.ManyToManyField(
MCPServerCategory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ mcp_servers 参数说明
| `description` | string | 是 | MCP Server 描述 |
| `labels` | array[string] | 否 | MCP Server 标签 |
| `resource_names` | array[string] | 是 | MCP Server 关联的 resource 列表 |
| `tool_names` | array[string] | 否 | MCP Server 工具名称列表,默认等于 resource_names。如果需要对资源进行重命名,可设置此字段,长度必须与 resource_names 一致,且不能重复 |
| `tool_names` | array[string] | 否 | MCP Server 工具名称列表,默认等于 resource_names。如果需要对资源进行重命名,可设置此字段,长度必须与 resource_names 一致,且不能重复 |
| `is_public` | bool | 否 | 是否公开,默认不公开 |
| `status` | integer | 否 | 状态:1:启用,0:关闭(默认关闭) |
| `protocol_type` | string | 否 | MCP 协议类型:sse(默认)、streamable_http |
| `target_app_codes` | array[string] | 否 | 主动授权的应用列表 |
| `oauth2_public_client_enabled` | bool | 否 | 是否开启 OAuth2 公开客户端模式,开启后将会对 bk_app_code=public 的应用进行授权,默认不开启 |
| `category_names` | array[string] | 否 | MCP Server 分类名称列表,不传则不更新分类。当前支持的分类:`Uncategorized`(未分类)、`Official`(官方资源)、`Featured`(精选推荐)、`Monitoring`(监控告警)、`ConfigManagement`(配置管理)、`DevOps`(持续交付)、`Emergency`(故障管理)、`Database`(数据服务)、`Automation`(运维自动化)、`Observability`(可观测性)、`Security`(安全合规)、`ResourceOptimize`(资源优化)、`ChaosEngineering`(混沌工程)、`Network`(网络管理) |
| `raw_response_enabled` | bool | 否 | 是否返回原始响应,开启后 mcp-proxy 将直接返回 API 响应结果,不添加 request_id 等额外信息,默认不开启 |
| `category_names` | array[string] | 否 | MCP Server 分类名称列表,不传则不更新分类。当前支持的分类:`Uncategorized`(未分类)、`Official`(官方资源)、`Featured`(精选推荐)、`Monitoring`(监控告警)、`ConfigManagement`(配置管理)、`DevOps`(持续交付)、`Emergency`(故障管理)、`Database`(数据服务)、`Automation`(运维自动化)、`Observability`(可观测性)、`Security`(安全合规)、`ResourceOptimize`(资源优化)、`ChaosEngineering`(混沌工程)、`Network`(网络管理) |


### 请求参数示例
Expand Down Expand Up @@ -67,6 +68,7 @@ mcp_servers 参数说明
"app2"
],
"oauth2_public_client_enabled": false,
"raw_response_enabled": false,
"category_names": [
"Official"
]
Expand Down
3 changes: 2 additions & 1 deletion src/mcp-proxy/pkg/entity/model/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ type MCPServer struct {
Status int `gorm:"column:status"`
GatewayID int `gorm:"column:gateway_id"`
StageID int `gorm:"column:stage_id"`
ProtocolType string `gorm:"column:protocol_type;size:32;default:sse"`
ProtocolType string `gorm:"column:protocol_type;size:32;default:sse"`
RawResponseEnabled bool `gorm:"column:raw_response_enabled;default:false"`
}

// GetProtocolType 获取协议类型,默认返回 SSE
Expand Down
1 change: 1 addition & 0 deletions src/mcp-proxy/pkg/infra/proxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type MCPServerConfig struct {
ResourceVersionID int `json:"resource_version_id"`
Tools []*ToolConfig `json:"tools"`
ProtocolType string `json:"protocol_type"` // 协议类型: sse 或 streamable_http
RawResponseEnabled bool `json:"raw_response_enabled"` // 返回原始响应,不添加 request_id 等
}

// ToolConfig ...
Expand Down
44 changes: 27 additions & 17 deletions src/mcp-proxy/pkg/infra/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,18 +257,20 @@ func (m *MCPProxy) AddMCPServerFromConfigs(configs []*MCPServerConfig) error {
}, &mcp.StreamableHTTPOptions{
Stateless: true,
})
mcpServer = NewStreamableHTTPMCPServer(server, httpHandler, config.Name, config.ResourceVersionID)
mcpServer = NewStreamableHTTPMCPServer(
server, httpHandler, config.Name, config.ResourceVersionID, config.RawResponseEnabled,
)
} else {
// 默认使用 SSE Handler
sseHandler := mcp.NewSSEHandler(func(r *http.Request) *mcp.Server {
return server
}, nil)
mcpServer = NewMCPServer(server, sseHandler, config.Name, config.ResourceVersionID)
mcpServer = NewMCPServer(server, sseHandler, config.Name, config.ResourceVersionID, config.RawResponseEnabled)
}

// register tool
for _, toolConfig := range config.Tools {
toolHandler := genToolHandler(toolConfig)
toolHandler := genToolHandler(toolConfig, config.Name, config.RawResponseEnabled)
mcpServer.AddTool(buildMCPTool(toolConfig, config.Name), toolHandler)
}
m.AddMCPServer(config.Name, mcpServer)
Expand All @@ -281,17 +283,18 @@ func (m *MCPProxy) AddMCPServerFromConfigs(configs []*MCPServerConfig) error {
// toolNameMap: 资源名到工具名的映射,如果为 nil 则使用资源名作为工具名
func (m *MCPProxy) AddMCPServerFromOpenAPISpec(name string,
resourceVersionID int, openAPISpec *openapi3.T, operationIDList []string,
toolNameMap map[string]string, protocolType string,
toolNameMap map[string]string, protocolType string, rawResponseEnabled bool,
) error {
operationIDMap := make(map[string]struct{})
for _, operationID := range operationIDList {
operationIDMap[operationID] = struct{}{}
}
mcpServerConfig := &MCPServerConfig{
Name: name,
Tools: OpenapiToMcpToolConfig(openAPISpec, operationIDMap, toolNameMap),
ResourceVersionID: resourceVersionID,
ProtocolType: protocolType,
Name: name,
Tools: OpenapiToMcpToolConfig(openAPISpec, operationIDMap, toolNameMap),
ResourceVersionID: resourceVersionID,
ProtocolType: protocolType,
RawResponseEnabled: rawResponseEnabled,
}
return m.AddMCPServerFromConfigs([]*MCPServerConfig{mcpServerConfig})
}
Expand All @@ -308,12 +311,13 @@ func (m *MCPProxy) UpdateMCPServerFromOpenApiSpec(
operationIDMap[operationID] = struct{}{}
}
mcpServerConfig := &MCPServerConfig{
Name: name,
Tools: OpenapiToMcpToolConfig(openAPISpec, operationIDMap, toolNameMap),
Name: name,
Tools: OpenapiToMcpToolConfig(openAPISpec, operationIDMap, toolNameMap),
RawResponseEnabled: mcpServer.RawResponseEnabled(),
}
// update tool
for _, toolConfig := range mcpServerConfig.Tools {
toolHandler := genToolHandler(toolConfig)
toolHandler := genToolHandler(toolConfig, name, mcpServer.RawResponseEnabled())
mcpServer.AddTool(buildMCPTool(toolConfig, name), toolHandler)
}
// 更新资源版本号
Expand Down Expand Up @@ -504,7 +508,7 @@ func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error)
return resp, nil
}

func genToolHandler(toolApiConfig *ToolConfig) ToolHandler {
func genToolHandler(toolApiConfig *ToolConfig, serverName string, rawResponse bool) ToolHandler {
// 生成handler
handler := func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
auditLog := logging.GetAuditLoggerWithContext(ctx)
Expand Down Expand Up @@ -659,11 +663,17 @@ func genToolHandler(toolApiConfig *ToolConfig) ToolHandler {
}
}

responseResult := buildToolResponseEnvelope(
response.Code(),
response.GetHeader(constant.BkGatewayRequestIDKey),
res,
)
var responseResult any
if rawResponse {
// raw_response 模式:直接返回 API 响应结果,不添加 request_id 等额外信息
responseResult = res
} else {
responseResult = buildToolResponseEnvelope(
response.Code(),
response.GetHeader(constant.BkGatewayRequestIDKey),
res,
)
}
if response.Code() < 200 || response.Code() > 299 {
return nil, runtime.NewAPIError("call tool err", responseResult, response.Code())
}
Expand Down
35 changes: 27 additions & 8 deletions src/mcp-proxy/pkg/infra/proxy/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type MCPServer struct {
name string
// 生效的资源版本号
resourceVersionID int
rawResponseEnabled bool // 是否返回原始响应,开启后直接返回 API 响应结果,不添加 request_id 等额外信息
tools map[string]struct{}
prompts map[string]struct{}
rwLock *sync.RWMutex
Expand All @@ -54,16 +55,18 @@ func NewMCPServer(
handler *mcp.SSEHandler,
name string,
resourceVersion int,
rawResponseEnabled bool,
) *MCPServer {
return &MCPServer{
Server: server,
SSEHandler: handler,
protocolType: constant.MCPServerProtocolTypeSSE,
tools: make(map[string]struct{}),
prompts: make(map[string]struct{}),
rwLock: &sync.RWMutex{},
name: name,
resourceVersionID: resourceVersion,
Server: server,
SSEHandler: handler,
protocolType: constant.MCPServerProtocolTypeSSE,
tools: make(map[string]struct{}),
prompts: make(map[string]struct{}),
rwLock: &sync.RWMutex{},
name: name,
resourceVersionID: resourceVersion,
rawResponseEnabled: rawResponseEnabled,
}
}

Expand All @@ -73,6 +76,7 @@ func NewStreamableHTTPMCPServer(
handler *mcp.StreamableHTTPHandler,
name string,
resourceVersion int,
rawResponseEnabled bool,
) *MCPServer {
return &MCPServer{
Server: server,
Expand All @@ -83,6 +87,7 @@ func NewStreamableHTTPMCPServer(
rwLock: &sync.RWMutex{},
name: name,
resourceVersionID: resourceVersion,
rawResponseEnabled: rawResponseEnabled,
}
}

Expand All @@ -96,6 +101,20 @@ func (s *MCPServer) IsStreamableHTTP() bool {
return s.protocolType == constant.MCPServerProtocolTypeStreamableHTTP
}

// RawResponseEnabled 是否返回原始响应
func (s *MCPServer) RawResponseEnabled() bool {
s.rwLock.RLock()
defer s.rwLock.RUnlock()
return s.rawResponseEnabled
}

// SetRawResponseEnabled 设置是否返回原始响应
func (s *MCPServer) SetRawResponseEnabled(rawResponseEnabled bool) {
s.rwLock.Lock()
defer s.rwLock.Unlock()
s.rawResponseEnabled = rawResponseEnabled
}

// HandleSSE 返回 SSE 连接 Handler
func (s *MCPServer) HandleSSE() http.Handler {
if s.SSEHandler != nil {
Expand Down
10 changes: 9 additions & 1 deletion src/mcp-proxy/pkg/mcp/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ func LoadMCPServer(ctx context.Context, mcpProxy *proxy.MCPProxy) error {
// 删除后需要重新加载 openapi spec
wouldReloadOpenapiSpec = true
} else if mcpServer.GetResourceVersionID() == release.ResourceVersionID {
// 检查 raw_response_enabled 是否变化
if mcpServer.RawResponseEnabled() != server.RawResponseEnabled {
mcpServer.SetRawResponseEnabled(server.RawResponseEnabled)
logging.GetLogger().Infof(
"mcp server[%s] raw_response_enabled changed to %v", server.Name, server.RawResponseEnabled)
}
// 判断资源版本是否变化
// 判断资源版本是否变化
// 检查 tool_names 是否有新增的工具(使用工具名而非原始 resource_names 进行比较)
currentTools := mcpServer.GetTools()
Expand Down Expand Up @@ -155,7 +162,8 @@ func LoadMCPServer(ctx context.Context, mcpProxy *proxy.MCPProxy) error {
if !mcpProxy.IsMCPServerExist(server.Name) && conf != nil {
// 使用纯资源名列表和工具名映射来添加 MCP Server
err = mcpProxy.AddMCPServerFromOpenAPISpec(server.Name,
conf.resourceVersion, conf.openapiFileData, resourceNames, toolNameMap, server.GetProtocolType())
conf.resourceVersion, conf.openapiFileData, resourceNames,
toolNameMap, server.GetProtocolType(), server.RawResponseEnabled)
if err != nil {
logging.GetLogger().Errorf("add mcp server[name:%s] error: %v", server.Name, err)
continue
Expand Down
Loading
Loading