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
7 changes: 7 additions & 0 deletions src/mcp-proxy/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ Run a single test file/package:
to render OpenAPI server URLs with `{api_name}` and `{stage}` placeholders.
- `ENCRYPT_KEY` and `BK_APIGW_CRYPTO_NONCE` are used to decrypt stored gateway JWT private keys (inner JWT signing).
- `PPROF_USERNAME` / `PPROF_PASSWORD` override pprof credentials (change from defaults in production).
- `mcpServer.logTruncate` configures log size limits (string length, not bytes). Fields use prefix naming:
`auditLogMaxBodySize` / `auditLogMaxResponseSize` for audit logs, `apiLogRequestSize` / `apiLogResponseSize` /
`apiLogErrorResponseSize` for API logs. All have safe defaults (see constants in `config.go`).
- `mcpServer.transport` configures the shared HTTP transport for backend calls (TLS, connection pool).
Initialized once via `sync.Once`; only the first call to `InitSharedTransport` takes effect.
- `mcpServer.maxConcurrentPrefetch` defaults to **20**, capped at **100**.

## Architecture

Expand All @@ -71,6 +77,7 @@ Header extraction.
- **`pkg/config/`**: Viper config (`config.G` global)
- **`pkg/metric/`**: Prometheus metrics setup
- **`pkg/constant/`**: Header names, protocol constants, context keys
- **`pkg/util/`**: Shared utilities: string truncation, sensitive header masking, goroutine recovery, request ID
- **`pkg/repo/`**: GORM Gen DAO code (`*.gen.go`); regenerate with `./bk-apigateway-mcp-proxy gen -c config.yaml`

### Data Flow: OpenAPI -> MCP Tools
Expand Down
12 changes: 9 additions & 3 deletions src/mcp-proxy/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package cmd

import (
"fmt"
"time"

"github.com/spf13/viper"

Expand Down Expand Up @@ -53,43 +54,48 @@ func initConfig() {
}

func initDatabase() {
start := time.Now()
defaultDBConfig, ok := globalConfig.DatabaseMap["apigateway"]
if !ok {
panic("database config apigateway not found")
}
database.InitDBClient(&defaultDBConfig)
// 设置repo db
repo.SetDefault(database.Client())
logging.GetLogger().Infof("init database success, duration=%s", time.Since(start))
}

func initLogger() {
logging.InitLogger(globalConfig)
}

func initSentry() {
start := time.Now()
err := sty.Init(globalConfig.Sentry)
if err != nil {
logging.GetLogger().Errorf("init Sentry fail: %s", err)
} else {
logging.GetLogger().Info("init Sentry success")
logging.GetLogger().Infof("init Sentry success, duration=%s", time.Since(start))
}
}

func initMetrics() {
start := time.Now()
metric.InitMetrics()
logging.GetLogger().Info("init Metrics success")
logging.GetLogger().Infof("init Metrics success, duration=%s", time.Since(start))
}

func initTracing() {
if !globalConfig.Tracing.Enable {
logging.GetLogger().Info("tracing is not enabled, will not init it")
return
}
start := time.Now()
logging.GetLogger().Info("enabling tracing")
err := trace.InitTrace(globalConfig.Tracing)
if err != nil {
logging.GetLogger().Errorf("init tracing fail: %+v", err)
return
}
logging.GetLogger().Info("init tracing success")
logging.GetLogger().Infof("init tracing success, duration=%s", time.Since(start))
}
5 changes: 5 additions & 0 deletions src/mcp-proxy/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ package cmd
import (
"fmt"
"os"
"time"

_ "github.com/go-sql-driver/mysql"
"github.com/spf13/cobra"
"github.com/spf13/viper"

"mcp_proxy/pkg/infra/logging"
"mcp_proxy/pkg/server"
)

Expand Down Expand Up @@ -72,6 +74,7 @@ func init() {

// Start the server, do init then run http server
func Start() {
startTime := time.Now()
fmt.Println("It's mcp-proxy, start it now")

// 0. init config
Expand All @@ -89,5 +92,7 @@ func Start() {
initSentry()
initMetrics()

logging.GetLogger().Infof("all components initialized, total startup duration=%s", time.Since(startTime))

server.Run(globalConfig)
}
31 changes: 30 additions & 1 deletion src/mcp-proxy/config.yaml.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ logger:
writer: file
buffered: true
settings: {name: core_api.log, size: 100, backups: 10, age: 7, path: ./}
database:
level: info
writer: os
buffered: false
settings: {name: stdout}



Expand All @@ -75,4 +80,28 @@ tracing:
## config for pprof
pprof:
username: "bk-apigateway" # 可通过环境变量 PPROF_USERNAME 覆盖
password: "xxxxx" # 可通过环境变量 PPROF_PASSWORD 覆盖,生产环境请使用强密码
password: "xxxxx" # 可通过环境变量 PPROF_PASSWORD 覆盖,生产环境请使用强密码

## config for mcp server
mcpServer:
# Maximum concurrent goroutines for prefetching server configs (default: 20)
maxConcurrentPrefetch: 20
# Shared HTTP transport config for upstream tool calls
transport:
# Skip TLS certificate verification (only for internal networks; set false for public networks)
insecureSkipVerify: true
maxIdleConns: 200
maxIdleConnsPerHost: 20
idleConnTimeoutSecond: 90
# Log truncation limits (string length, not bytes)
logTruncate:
# Audit log body size limit for tool call requests and body params
auditLogMaxBodySize: 4096
# Audit log response size limit for tool call responses
auditLogMaxResponseSize: 4096
# MCP API log request params size limit
apiLogRequestSize: 2048
# MCP API log response size limit (normal responses)
apiLogResponseSize: 1024
# MCP API log response size limit (error responses, keeps more diagnostic info)
apiLogErrorResponseSize: 4096
3 changes: 0 additions & 3 deletions src/mcp-proxy/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ go 1.24.4
require (
github.com/TencentBlueKing/gopkg v1.3.0
github.com/getkin/kin-openapi v0.132.0
github.com/getsentry/raven-go v0.2.0
github.com/getsentry/sentry-go v0.34.1
github.com/gin-contrib/pprof v1.5.3
github.com/gin-gonic/contrib v0.0.0-20250521004450-2b1292699c15
github.com/gin-gonic/gin v1.10.1
github.com/go-openapi/runtime v0.28.0
github.com/go-openapi/strfmt v0.23.0
Expand Down Expand Up @@ -55,7 +53,6 @@ require (
github.com/bytedance/sonic v1.13.3 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
Expand Down
6 changes: 0 additions & 6 deletions src/mcp-proxy/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZw
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s=
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
Expand Down Expand Up @@ -70,8 +68,6 @@ github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFA
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
github.com/getsentry/sentry-go v0.34.1 h1:HSjc1C/OsnZttohEPrrqKH42Iud0HuLCXpv8cU1pWcw=
github.com/getsentry/sentry-go v0.34.1/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
Expand All @@ -80,8 +76,6 @@ github.com/gin-contrib/pprof v1.5.3/go.mod h1:0+LQSZ4SLO0B6+2n6JBzaEygpTBxe/nI+Y
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/contrib v0.0.0-20250521004450-2b1292699c15 h1:AoSudS8CW8Mc9rRf5sO1vBtNxr2Ok6TaAICjgg5oKUY=
github.com/gin-gonic/contrib v0.0.0-20250521004450-2b1292699c15/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
Expand Down
126 changes: 123 additions & 3 deletions src/mcp-proxy/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ type DesensitizationFiled struct {

// Logger is the config for all logger, including default logger and api
type Logger struct {
Default LogConfig
API LogConfig
Audit LogConfig
Default LogConfig
API LogConfig
Audit LogConfig
Database LogConfig
}

// TLS is the config for tls
Expand Down Expand Up @@ -153,6 +154,85 @@ type Instrument struct {
McpAPI bool
}

// Transport is the config for the shared HTTP transport used by tool calls.
type Transport struct {
InsecureSkipVerify bool
MaxIdleConns int
MaxIdleConnsPerHost int
IdleConnTimeoutSecond int
}

// LogTruncate default values.
const (
defaultAuditLogMaxBodySize = 4096
defaultAuditLogMaxResponseSize = 4096
defaultAPILogRequestSize = 2048
defaultAPILogResponseSize = 1024
defaultAPILogErrorRespSize = 4096
)

// LogTruncate is the config for log truncation limits.
// NOTE: All size limits are measured in string length (number of characters), not bytes.
// For ASCII content this equals the byte count, but for multi-byte characters (e.g. CJK)
// the actual byte size may be larger.
type LogTruncate struct {
// AuditLogMaxBodySize limits the audit log body size for tool call requests and body params (string length).
// Defaults to 4096 if not set.
AuditLogMaxBodySize int
// AuditLogMaxResponseSize limits the audit log response size for tool call responses (string length).
// Defaults to 4096 if not set.
AuditLogMaxResponseSize int
// APILogRequestSize limits the MCP API log request params size (string length).
// Defaults to 2048 if not set.
APILogRequestSize int
// APILogResponseSize limits the MCP API log response size for normal responses (string length).
// Defaults to 1024 if not set.
APILogResponseSize int
// APILogErrorResponseSize limits the MCP API log response size for error responses (string length).
// Defaults to 4096 if not set.
APILogErrorResponseSize int
}

// GetAuditLogMaxBodySize returns AuditLogMaxBodySize with a safe default fallback.
func (l LogTruncate) GetAuditLogMaxBodySize() int {
if l.AuditLogMaxBodySize <= 0 {
return defaultAuditLogMaxBodySize
}
return l.AuditLogMaxBodySize
}

// GetAuditLogMaxResponseSize returns AuditLogMaxResponseSize with a safe default fallback.
func (l LogTruncate) GetAuditLogMaxResponseSize() int {
if l.AuditLogMaxResponseSize <= 0 {
return defaultAuditLogMaxResponseSize
}
return l.AuditLogMaxResponseSize
}

// GetAPILogRequestSize returns APILogRequestSize with a safe default fallback.
func (l LogTruncate) GetAPILogRequestSize() int {
if l.APILogRequestSize <= 0 {
return defaultAPILogRequestSize
}
return l.APILogRequestSize
}

// GetAPILogResponseSize returns APILogResponseSize with a safe default fallback.
func (l LogTruncate) GetAPILogResponseSize() int {
if l.APILogResponseSize <= 0 {
return defaultAPILogResponseSize
}
return l.APILogResponseSize
}

// GetAPILogErrorResponseSize returns APILogErrorResponseSize with a safe default fallback.
func (l LogTruncate) GetAPILogErrorResponseSize() int {
if l.APILogErrorResponseSize <= 0 {
return defaultAPILogErrorRespSize
}
return l.APILogErrorResponseSize
}

// McpServer ...
type McpServer struct {
// the interval of mcp server reload
Expand All @@ -163,6 +243,13 @@ type McpServer struct {
InnerJwtExpireTime time.Duration
EncryptKey string
CryptoNonce string
// MaxConcurrentPrefetch limits the number of concurrent goroutines when prefetching server configs.
// Defaults to 20 if not set.
MaxConcurrentPrefetch int
// Transport is the config for the shared HTTP transport used by upstream tool calls.
Transport Transport
// LogTruncate configures log truncation limits for audit and API logs.
LogTruncate LogTruncate
}

// Pprof is the config for pprof
Expand Down Expand Up @@ -236,6 +323,39 @@ func Load(v *viper.Viper) (*Config, error) {
if cfg.McpServer.CryptoNonce == "" {
cfg.McpServer.CryptoNonce = os.Getenv("BK_APIGW_CRYPTO_NONCE")
}
// Transport defaults for upstream tool calls
if cfg.McpServer.Transport.MaxIdleConns == 0 {
cfg.McpServer.Transport.MaxIdleConns = 200
}
if cfg.McpServer.Transport.MaxIdleConnsPerHost == 0 {
cfg.McpServer.Transport.MaxIdleConnsPerHost = 20
}
if cfg.McpServer.Transport.IdleConnTimeoutSecond == 0 {
cfg.McpServer.Transport.IdleConnTimeoutSecond = 90
}
// MaxConcurrentPrefetch defaults to 20, capped at 100
if cfg.McpServer.MaxConcurrentPrefetch == 0 {
cfg.McpServer.MaxConcurrentPrefetch = 20
}
if cfg.McpServer.MaxConcurrentPrefetch > 100 {
cfg.McpServer.MaxConcurrentPrefetch = 100
}
// LogTruncate defaults
if cfg.McpServer.LogTruncate.AuditLogMaxBodySize == 0 {
cfg.McpServer.LogTruncate.AuditLogMaxBodySize = defaultAuditLogMaxBodySize
}
if cfg.McpServer.LogTruncate.AuditLogMaxResponseSize == 0 {
cfg.McpServer.LogTruncate.AuditLogMaxResponseSize = defaultAuditLogMaxResponseSize
}
if cfg.McpServer.LogTruncate.APILogRequestSize == 0 {
cfg.McpServer.LogTruncate.APILogRequestSize = defaultAPILogRequestSize
}
if cfg.McpServer.LogTruncate.APILogResponseSize == 0 {
cfg.McpServer.LogTruncate.APILogResponseSize = defaultAPILogResponseSize
}
if cfg.McpServer.LogTruncate.APILogErrorResponseSize == 0 {
cfg.McpServer.LogTruncate.APILogErrorResponseSize = defaultAPILogErrorRespSize
}

if cfg.PProf.Username == "" {
cfg.PProf.Username = os.Getenv("PPROF_USERNAME")
Expand Down
Loading
Loading