ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

第10讲(最终讲):毕业项目——构建生产级 MCP 系统 BizMCP

第10讲(最终讲):毕业项目——构建生产级 MCP 系统 BizMCP 一、项目概述前九讲我们逐一攻克了 MCP 的各个模块工具设计、Transport 选型、Resource/Prompt、认证授权、Client 架构、通知机制、Gateway 网关、可观测性与限流熔断。本讲将它们整合成一个完整的生产级系统——BizMCP。BizMCP​ 是一个面向电商业务的 MCP 基础设施包含三个核心组件组件职责端口bizmcp-orders订单管理 MCP Server查询、退款、通知8091bizmcp-ops运维 MCP Server日志读取、Pod 管理8092bizmcp-gateway统一 MCP Gateway认证、路由、限流、追踪、审计8085二、系统架构┌─────────────┐ ┌──────────────────────────────────────┐ │ Agent │────▶│ bizmcp-gateway │ │ (Claude │ │ ┌─────────┐ ┌────────┐ ┌────────┐ │ │ Code/ │ │ │ Auth │ │ Router │ │ Rate │ │ │ Custom) │ │ │ (JWT) │ │ (tool │ │ Limit │ │ └─────────────┘ │ │ │ │ → svc)│ │(Token │ │ │ └─────────┘ └────────┘ │ Bucket)│ │ │ ┌─────────┐ ┌────────┐ └────────┘ │ │ │ Trace │ │ Audit │ │ │ │ (OTel) │ │ (JSONL)│ │ │ └─────────┘ └────────┘ │ └──────────────────────────────────────┘ │ ┌───────────────┴───────────────┐ ▼ ▼ ┌───────────────────┐ ┌───────────────────┐ │ bizmcp-orders │ │ bizmcp-ops │ │ :8091 │ │ :8092 │ │ │ │ │ │ Tools: │ │ Tools: │ │ • query_orders │ │ • restart_pod │ │ • create_refund │ │ • check_disk │ │ • track_delivery │ │ │ │ │ │ Resources: │ │ Resources: │ │ • logs://{ns}/{pod}│ │ • orders://{id} │ │ │ └───────────────────┘ └───────────────────┘三、完整代码实现3.1 项目结构bizmcp/ ├── cmd/ │ ├── gateway/main.go # Gateway 入口 │ ├── orders/main.go # 订单 Server 入口 │ └── ops/main.go # 运维 Server 入口 ├── internal/ │ ├── mcp/ # MCP 协议核心类型 │ │ ├── message.go # JSON-RPC 消息结构 │ │ ├── server.go # MCP Server 基础框架 │ │ └── transport.go # Transport 抽象 │ ├── auth/ # 认证授权 │ │ └── jwt.go │ ├── gateway/ # Gateway 逻辑 │ │ ├── router.go │ │ ├── ratelimit.go │ │ └── circuitbreaker.go │ └── observability/ # 可观测性 │ ├── tracing.go │ ├── metrics.go │ └── audit.go ├── deploy/ │ ├── docker-compose.yml │ └── k8s/ │ ├── deployment.yaml │ └── service.yaml ├── go.mod └── go.sum3.2 共享 MCP 核心类型internal/mcp/message.gopackage mcp import encoding/json // JSON-RPC 消息 type Request struct { JSONRPC string json:jsonrpc ID int json:id Method string json:method Params json.RawMessage json:params,omitempty } type Response struct { JSONRPC string json:jsonrpc ID int json:id Result json.RawMessage json:result,omitempty Error *ErrorObj json:error,omitempty } type ErrorObj struct { Code int json:code Message string json:message } // 工具定义 type ToolSpec struct { Name string json:name Description string json:description InputSchema interface{} json:inputSchema } // 资源定义 type Resource struct { URI string json:uri Name string json:name Description string json:description MimeType string json:mimeType } type ResourceTemplate struct { URITemplate string json:uriTemplate Name string json:name Description string json:description MimeType string json:mimeType } // 工具调用结果 type ToolResult struct { Content []ContentItem json:content IsError bool json:isError,omitempty } type ContentItem struct { Type string json:type Text string json:text }3.3 订单 MCP Servercmd/orders/main.gopackage main import ( encoding/json fmt log net/http regexp strings sync time bizmcp/internal/mcp ) // ---- 订单数据模型 ---- type Order struct { ID string json:id UserID string json:user_id Amount float64 json:amount Status string json:status CreatedAt string json:created_at } type OrderStore struct { mu sync.RWMutex orders map[string]Order } func NewOrderStore() *OrderStore { return OrderStore{ orders: map[string]Order{ ord_001: {ID: ord_001, UserID: u_123, Amount: 299.00, Status: paid, CreatedAt: 2026-09-01T10:00:00Z}, ord_002: {ID: ord_002, UserID: u_123, Amount: 59.90, Status: shipped, CreatedAt: 2026-09-05T14:30:00Z}, ord_003: {ID: ord_003, UserID: u_456, Amount: 899.00, Status: pending, CreatedAt: 2026-09-08T08:00:00Z}, }, } } // ---- 工具 Handlers ---- func handleQueryOrders(store *OrderStore, raw json.RawMessage) *mcp.ToolResult { var args struct { UserID string json:user_id Status string json:status,omitempty } json.Unmarshal(raw, args) // 业务校验 if args.UserID { return mcp.ToolResult{ IsError: true, Content: []mcp.ContentItem{{Type: text, Text: user_id 不能为空}}, } } if !regexp.MustCompile(^u_\d$).MatchString(args.UserID) { return mcp.ToolResult{ IsError: true, Content: []mcp.ContentItem{{Type: text, Text: user_id 格式无效}}, } } store.mu.RLock() defer store.mu.RUnlock() var results []Order for _, o : range store.orders { if o.UserID args.UserID { if args.Status || o.Status args.Status { results append(results, o) } } } data, _ : json.Marshal(results) return mcp.ToolResult{ Content: []mcp.ContentItem{{Type: text, Text: string(data)}}, } } func handleCreateRefund(store *OrderStore, raw json.RawMessage) *mcp.ToolResult { var args struct { OrderID string json:order_id Reason string json:reason,omitempty } json.Unmarshal(raw, args) if args.OrderID { return mcp.ToolResult{ IsError: true, Content: []mcp.ContentItem{{Type: text, Text: order_id 不能为空}}, } } store.mu.Lock() defer store.mu.Unlock() order, exists : store.orders[args.OrderID] if !exists { return mcp.ToolResult{ IsError: true, Content: []mcp.ContentItem{{Type: text, Text: fmt.Sprintf(订单 %s 不存在, args.OrderID)}}, } } if order.Status refunded { return mcp.ToolResult{ IsError: true, Content: []mcp.ContentItem{{Type: text, Text: 订单已退款不能重复退款}}, } } order.Status refunded store.orders[args.OrderID] order return mcp.ToolResult{ Content: []mcp.ContentItem{{Type: text, Text: fmt.Sprintf(订单 %s 退款成功, args.OrderID)}}, } } // ---- MCP Server ---- type OrdersMCPServer struct { store *OrderStore tools map[string]func(json.RawMessage) *mcp.ToolResult } func NewOrdersMCPServer() *OrdersMCPServer { store : NewOrderStore() return OrdersMCPServer{ store: store, tools: map[string]func(json.RawMessage) *mcp.ToolResult{ query_orders: func(raw json.RawMessage) *mcp.ToolResult { return handleQueryOrders(store, raw) }, create_refund: func(raw json.RawMessage) *mcp.ToolResult { return handleCreateRefund(store, raw) }, track_delivery: func(raw json.RawMessage) *mcp.ToolResult { return handleTrackDelivery(store, raw) }, }, } } func handleTrackDelivery(store *OrderStore, raw json.RawMessage) *mcp.ToolResult { var args struct { OrderID string json:order_id } json.Unmarshal(raw, args) store.mu.RLock() defer store.mu.RUnlock() order, exists : store.orders[args.OrderID] if !exists { return mcp.ToolResult{ IsError: true, Content: []mcp.ContentItem{{Type: text, Text: 订单不存在}}, } } if order.Status ! shipped { return mcp.ToolResult{ IsError: true, Content: []mcp.ContentItem{{Type: text, Text: 订单尚未发货}}, } } return mcp.ToolResult{ Content: []mcp.ContentItem{{Type: text, Text: fmt.Sprintf(订单 %s 已发货预计 3-5 个工作日送达, args.OrderID)}}, } } func (s *OrdersMCPServer) Handle(req mcp.Request) mcp.Response { switch req.Method { case initialize: return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Result: toRaw(map[string]interface{}{ protocolVersion: 2026-07-28, capabilities: map[string]interface{}{tools: map[string]interface{}{}}, }), } case tools/list: specs : []mcp.ToolSpec{ { Name: query_orders, Description: 按用户 ID 查询订单列表支持按状态过滤。只读操作。, InputSchema: map[string]interface{}{ type: object, properties: map[string]interface{}{ user_id: map[string]interface{}{type: string, description: 用户 UUID格式 u_xxx}, status: map[string]interface{}{type: string, enum: []string{pending, paid, shipped, refunded}, description: 按状态过滤可选}, }, required: []string{user_id}, }, }, { Name: create_refund, Description: 为指定订单发起退款。写操作不可逆。, InputSchema: map[string]interface{}{ type: object, properties: map[string]interface{}{ order_id: map[string]interface{}{type: string, description: 订单 ID}, reason: map[string]interface{}{type: string, description: 退款原因}, }, required: []string{order_id}, }, }, { Name: track_delivery, Description: 查询订单配送状态。只读。, InputSchema: map[string]interface{}{ type: object, properties: map[string]interface{}{ order_id: map[string]interface{}{type: string, description: 订单 ID}, }, required: []string{order_id}, }, }, } return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Result: toRaw(map[string]interface{}{tools: specs}), } case tools/call: var params struct { Name string json:name Arguments json.RawMessage json:arguments } json.Unmarshal(req.Params, params) handler, exists : s.tools[params.Name] if !exists { return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Error: mcp.ErrorObj{Code: -32601, Message: unknown tool: params.Name}, } } result : handler(params.Arguments) return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Result: toRaw(result), } default: return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Error: mcp.ErrorObj{Code: -32601, Message: unsupported method}, } } } func toRaw(v interface{}) json.RawMessage { b, _ : json.Marshal(v) return b } func main() { server : NewOrdersMCPServer() mux : http.NewServeMux() mux.HandleFunc(/mcp, func(w http.ResponseWriter, r *http.Request) { var req mcp.Request json.NewDecoder(r.Body).Decode(req) resp : server.Handle(req) w.Header().Set(Content-Type, application/json) json.NewEncoder(w).Encode(resp) }) log.Println(BizMCP Orders Server 启动于 :8091) log.Fatal(http.ListenAndServe(:8091, mux)) }3.4 运维 MCP Servercmd/ops/main.gopackage main import ( encoding/json fmt log net/http os path/filepath regexp strings time bizmcp/internal/mcp ) // 模拟日志目录 const logDir /var/log/bizmcp type OpsMCPServer struct{} func (s *OpsMCPServer) Handle(req mcp.Request) mcp.Response { switch req.Method { case initialize: return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Result: toRaw(map[string]interface{}{ protocolVersion: 2026-07-28, capabilities: map[string]interface{}{ tools: map[string]interface{}{}, resources: map[string]interface{}{}, }, }), } case tools/list: return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Result: toRaw(map[string]interface{}{ tools: []mcp.ToolSpec{ { Name: restart_pod, Description: 重启指定 namespace 下的 Pod。写操作。, InputSchema: map[string]interface{}{ type: object, properties: map[string]interface{}{ namespace: map[string]interface{}{type: string}, pod_name: map[string]interface{}{type: string}, }, required: []string{namespace, pod_name}, }, }, { Name: check_disk, Description: 检查节点的磁盘使用率。只读。, InputSchema: map[string]interface{}{ type: object, properties: map[string]interface{}{}, }, }, }, resourceTemplates: []mcp.ResourceTemplate{ { URITemplate: logs://{namespace}/{pod}, Name: Pod 日志, Description: 按 namespace 和 pod 名称读取容器日志只读, MimeType: text/plain, }, }, }), } case tools/call: var params struct { Name string json:name Arguments json.RawMessage json:arguments } json.Unmarshal(req.Params, params) switch params.Name { case restart_pod: var args struct { Namespace string json:namespace PodName string json:pod_name } json.Unmarshal(params.Arguments, args) if args.Namespace || args.PodName { return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Error: mcp.ErrorObj{Code: -32602, Message: namespace 和 pod_name 不能为空}, } } return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Result: toRaw(mcp.ToolResult{ Content: []mcp.ContentItem{{Type: text, Text: fmt.Sprintf(Pod %s/%s 重启命令已下发, args.Namespace, args.PodName)}}, }), } case check_disk: return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Result: toRaw(mcp.ToolResult{ Content: []mcp.ContentItem{{Type: text, Text: 磁盘使用率: /data 72%, /var 45%, /tmp 18%}}, }), } default: return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Error: mcp.ErrorObj{Code: -32601, Message: unknown tool}, } } case resources/list: return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Result: toRaw(map[string]interface{}{ resources: []mcp.Resource{}, resourceTemplates: []mcp.ResourceTemplate{ { URITemplate: logs://{namespace}/{pod}, Name: Pod 日志, Description: 按 namespace 和 pod 名称读取容器日志只读, MimeType: text/plain, }, }, }), } case resources/read: var params struct { URI string json:uri } json.Unmarshal(req.Params, params) // 解析 URI pattern : regexp.MustCompile(^logs://([a-z0-9-])/([a-z0-9-])$) matches : pattern.FindStringSubmatch(params.URI) if len(matches) ! 3 { return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Error: mcp.ErrorObj{Code: -32602, Message: URI 格式错误}, } } namespace, pod : matches[1], matches[2] // 路径遍历防护 if strings.Contains(namespace, ..) || strings.Contains(pod, ..) { return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Error: mcp.ErrorObj{Code: -32603, Message: 非法路径}, } } logPath : filepath.Join(logDir, namespace, pod.log) absPath, _ : filepath.Abs(logPath) absBase, _ : filepath.Abs(logDir) if !strings.HasPrefix(absPath, absBase) { return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Error: mcp.ErrorObj{Code: -32603, Message: 路径越界}, } } data, err : os.ReadFile(logPath) if err ! nil { return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Error: mcp.ErrorObj{Code: -32603, Message: 日志不存在}, } } return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Result: toRaw(map[string]interface{}{ contents: []map[string]interface{}{ {uri: params.URI, mimeType: text/plain, text: string(data)}, }, }), } default: return mcp.Response{ JSONRPC: 2.0, ID: req.ID, Error: mcp.ErrorObj{Code: -32601, Message: unsupported method}, } } } func toRaw(v interface{}) json.RawMessage { b, _ : json.Marshal(v) return b } func main() { // 创建模拟日志 os.MkdirAll(filepath.Join(logDir, production), 0755) logContent : fmt.Sprintf([%s] INFO Server started\n[%s] WARN Memory usage 82%%\n, time.Now().Add(-1*time.Hour).Format(time.RFC3339), time.Now().Add(-30*time.Minute).Format(time.RFC3339)) os.WriteFile(filepath.Join(logDir, production, web-abc.log), []byte(logContent), 0644) server : OpsMCPServer{} mux : http.NewServeMux() mux.HandleFunc(/mcp, func(w http.ResponseWriter, r *http.Request) { var req mcp.Request json.NewDecoder(r.Body).Decode(req) resp : server.Handle(req) w.Header().Set(Content-Type, application/json) json.NewEncoder(w).Encode(resp) }) log.Println(BizMCP Ops Server 启动于 :8092) log.Fatal(http.ListenAndServe(:8092, mux)) }3.5 MCP Gatewaycmd/gateway/main.go完整 Gateway 实现在第9讲基础上精简保留核心功能认证、路由、限流、审计。package main import ( bytes encoding/json fmt log net/http os strings sync time bizmcp/internal/mcp ) // ---- 审计日志 ---- type AuditEntry struct { Timestamp string json:timestamp AgentID string json:agent_id Method string json:method ToolName string json:tool_name,omitempty Backend string json:backend,omitempty DurationMs int64 json:duration_ms StatusCode int json:status_code ErrorMsg string json:error_msg,omitempty } var auditLogger log.New(os.Stdout, , 0) func writeAudit(entry AuditEntry) { entry.Timestamp time.Now().UTC().Format(time.RFC3339Nano) data, _ : json.Marshal(entry) auditLogger.Println(string(data)) } // ---- 令牌桶限流 ---- type TokenBucket struct { mu sync.Mutex tokens float64 maxTokens float64 refillRate float64 lastRefill time.Time } func NewTokenBucket(maxTokens, refillRate float64) *TokenBucket { return TokenBucket{tokens: maxTokens, maxTokens: maxTokens, refillRate: refillRate, lastRefill: time.Now()} } func (tb *TokenBucket) Allow() bool { tb.mu.Lock() defer tb.mu.Unlock() now : time.Now() elapsed : now.Sub(tb.lastRefill).Seconds() tb.tokens min(tb.maxTokens, tb.tokenselapsed*tb.refillRate) tb.lastRefill now if tb.tokens 1 { tb.tokens-- return true } return false } func min(a, b float64) float64 { if a b { return a }; return b } // ---- 后端 Server 注册 ---- type Backend struct { Name string URL string Token string Tools []string Client *http.Client } // ---- Gateway ---- type Gateway struct { mu sync.RWMutex backends map[string]*Backend toolRoute map[string]string gwToken string rateLimiter *TokenBucket } func NewGateway(gwToken string) *Gateway { return Gateway{ backends: make(map[string]*Backend), toolRoute: make(map[string]string), gwToken: gwToken, rateLimiter: NewTokenBucket(100, 50), } } func (g *Gateway) Register(b *Backend) { b.Client http.Client{Timeout: 30 * time.Second} g.mu.Lock() defer g.mu.Unlock() g.backends[b.Name] b for _, tool : range b.Tools { g.toolRoute[tool] b.Name } } func (g *Gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) { start : time.Now() // 1. 认证 auth : r.Header.Get(Authorization) token : strings.TrimPrefix(auth, Bearer ) if token ! g.gwToken { writeAudit(AuditEntry{AgentID: unknown, Method: auth, StatusCode: 401, ErrorMsg: unauthorized}) http.Error(w, {jsonrpc:2.0,id:null,error:{code:-32001,message:unauthorized}}, http.StatusUnauthorized) return } // 2. 限流 if !g.rateLimiter.Allow() { writeAudit(AuditEntry{AgentID: token[:8], Method: rate_limit, StatusCode: 429, ErrorMsg: rate limited}) http.Error(w, {jsonrpc:2.0,id:null,error:{code:-32000,message:too many requests}}, http.StatusTooManyRequests) return } var req mcp.Request json.NewDecoder(r.Body).Decode(req) var resp mcp.Response switch req.Method { case initialize: resp mcp.Response{ JSONRPC: 2.0, ID: req.ID, Result: toRaw(map[string]interface{}{ protocolVersion: 2026-07-28, capabilities: map[string]interface{}{tools: map[string]interface{}{}, resources: map[string]interface{}{}}, }), } case tools/list: g.mu.RLock() var allTools []map[string]interface{} for name, backend : range g.backends { for _, tool : range backend.Tools { allTools append(allTools, map[string]interface{}{ name: name : tool, description: fmt.Sprintf(%s 提供的 %s 工具, name, tool), }) } } g.mu.RUnlock() resp mcp.Response{JSONRPC: 2.0, ID: req.ID, Result: toRaw(map[string]interface{}{tools: allTools})} case tools/call: var params struct { Name string json:name Arguments json.RawMessage json:arguments } json.Unmarshal(req.Params, params) parts : strings.SplitN(params.Name, :, 2) if len(parts) ! 2 { resp mcp.Response{JSONRPC: 2.0, ID: req.ID, Error: mcp.ErrorObj{Code: -32602, Message: tool name format: backend:tool}} break } backendName, toolName : parts[0], parts[1] g.mu.RLock() backend : g.backends[backendName] g.mu.RUnlock() if backend nil { resp mcp.Response{JSONRPC: 2.0, ID: req.ID, Error: mcp.ErrorObj{Code: -32603, Message: fmt.Sprintf(backend %s not found, backendName)}} break } // 转发到后端 forwardReq : mcp.Request{ JSONRPC: req.JSONRPC, ID: req.ID, Method: req.Method, Params: toRaw(map[string]interface{}{name: toolName, arguments: params.Arguments}), } bodyBytes, _ : json.Marshal(forwardReq) httpReq, _ : http.NewRequest(POST, backend.URL, bytes.NewReader(bodyBytes)) httpReq.Header.Set(Content-Type, application/json) httpReq.Header.Set(Authorization, Bearer backend.Token) httpResp, err : backend.Client.Do(httpReq) if err ! nil { resp mcp.Response{JSONRPC: 2.0, ID: req.ID, Error: mcp.ErrorObj{Code: -32603, Message: err.Error()}} break } defer httpResp.Body.Close() json.NewDecoder(httpResp.Body).Decode(resp) default: resp mcp.Response{JSONRPC: 2.0, ID: req.ID, Error: mcp.ErrorObj{Code: -32601, Message: unsupported method}} } // 审计 durationMs : time.Since(start).Milliseconds() statusCode : 200 errorMsg : if resp.Error ! nil { statusCode resp.Error.Code errorMsg resp.Error.Message } writeAudit(AuditEntry{ AgentID: token[:8], Method: req.Method, DurationMs: durationMs, StatusCode: statusCode, ErrorMsg: errorMsg, }) w.Header().Set(Content-Type, application/json) json.NewEncoder(w).Encode(resp) } func toRaw(v interface{}) json.RawMessage { b, _ : json.Marshal(v) return b } func main() { gwToken : os.Getenv(GW_TOKEN) if gwToken { gwToken dev-token-change-in-production } gateway : NewGateway(gwToken) gateway.Register(Backend{ Name: orders, URL: http://localhost:8091/mcp, Token: orders-backend-token, Tools: []string{query_orders, create_refund, track_delivery}, }) gateway.Register(Backend{ Name: ops, URL: http://localhost:8092/mcp, Token: ops-backend-token, Tools: []string{restart_pod, check_disk}, }) mux : http.NewServeMux() mux.HandleFunc(/mcp, gateway.ServeHTTP) log.Printf(BizMCP Gateway 启动于 :8085Token: %s, gwToken) log.Fatal(http.ListenAndServe(:8085, mux)) }四、Docker Compose 部署# deploy/docker-compose.yml version: 3.8 services: bizmcp-orders: build: context: .. dockerfile: deploy/Dockerfile args: APP: orders ports: - 8091:8091 environment: - APP_ENVproduction networks: - bizmcp-net bizmcp-ops: build: context: .. dockerfile: deploy/Dockerfile args: APP: ops ports: - 8092:8092 volumes: - ./logs:/var/log/bizmcp:ro networks: - bizmcp-net bizmcp-gateway: build: context: .. dockerfile: deploy/Dockerfile args: APP: gateway ports: - 8085:8085 environment: - GW_TOKENprod-token-2026 depends_on: - bizmcp-orders - bizmcp-ops networks: - bizmcp-net networks: bizmcp-net: driver: bridge# deploy/Dockerfile FROM golang:1.22 AS builder ARG APP WORKDIR /build COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED0 GOOSlinux go build -o /app ./cmd/$APP FROM alpine:3.19 RUN apk add --no-cache ca-certificates tzdata COPY --frombuilder /app /app EXPOSE 8085 8091 8092 ENTRYPOINT [/app]五、K8s 部署简化# deploy/k8s/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: bizmcp-gateway spec: replicas: 3 selector: matchLabels: app: bizmcp-gateway template: metadata: labels: app: bizmcp-gateway spec: containers: - name: gateway image: bizmcp/gateway:latest ports: - containerPort: 8085 env: - name: GW_TOKEN valueFrom: secretKeyRef: name: bizmcp-secrets key: gw-token resources: requests: cpu: 500m memory: 512Mi limits: cpu: 1000m memory: 1Gi --- apiVersion: v1 kind: Service metadata: name: bizmcp-gateway spec: selector: app: bizmcp-gateway ports: - port: 443 targetPort: 8085 type: LoadBalancer六、CI/CD 流水线GitHub Actions 片段# .github/workflows/deploy.yml name: Deploy BizMCP on: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-gov5 with: go-version: 1.22 - run: go test ./... build-and-push: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Build images run: | docker compose -f deploy/docker-compose.yml build - name: Push to registry run: | docker tag bizmcp-gateway:latest $REGISTRY/bizmcp-gateway:$GITHUB_SHA docker push $REGISTRY/bizmcp-gateway:$GITHUB_SHA deploy: needs: build-and-push runs-on: ubuntu-latest steps: - name: Deploy to K8s run: | kubectl set image deployment/bizmcp-gateway \ gateway$REGISTRY/bizmcp-gateway:$GITHUB_SHA七、评估数据集验证 BizMCP 功能的测试用例[ { name: 正常查询订单, request: {method: tools/call, params: {name: orders:query_orders, arguments: {user_id: u_123}}}, expected_status: success }, { name: 参数校验失败 - 空 user_id, request: {method: tools/call, params: {name: orders:query_orders, arguments: {user_id: }}}, expected_error: user_id 不能为空 }, { name: 权限校验 - 未知后端, request: {method: tools/call, params: {name: unknown:tool, arguments: {}}}, expected_error: backend unknown not found }, { name: 认证失败 - 无 Token, request: {method: tools/list}, expected_status: 401 }, { name: 资源读取 - 日志, request: {method: resources/read, params: {uri: logs://production/web-abc}}, expected_status: success }, { name: 路径遍历防护, request: {method: resources/read, params: {uri: logs://../etc/passwd}}, expected_error: 非法路径 } ]八、课后实践部署运行在本地用 Docker Compose 启动 BizMCP用 curl 测试所有端点扩展工具给 orders Server 添加cancel_order工具注册到 Gateway写操作确认给create_refund和restart_pod添加人工确认步骤调用前先发通知等待确认后再执行性能测试用hey或wrk对 Gateway 施压观察限流效果监控集成配置 Prometheus 抓取 Gateway 的/metricsGrafana 展示仪表盘九、延伸阅读MCP Specification 2026-07-28完整协议规范Go net/http 中间件模式生产级 HTTP 服务的中间件链设计Kubernetes Operator Pattern如何用 Operator 管理 MCP Server 的生命周期OpenTelemetry Trace 采样策略生产环境的全量与采样权衡十、总结BizMCP 贯穿了本专题的全部知识点知识点对应组件第1讲MCP 总线概念Gateway 统一接入第2讲Transport 选型HTTP Transport第3讲工具设计orders Server 的双层校验第4讲Resourceops Server 的日志读取第5讲认证授权Gateway JWT 认证第6讲Client 架构测试用的 curl 客户端第7讲通知机制可扩展的通知接口第8讲Gateway 生态Gateway 路由与注册第9讲生产保障限流、审计、可观测性从第一讲到第十讲我们从零构建了一个生产级的 MCP 系统。MCP 不是银弹但它提供了一个清晰的协议边界让 Agent 和后端系统能够以标准化的方式协作。当你下一次需要让 AI 模型安全地调用内部系统时希望 BizMCP 能成为你的起点。开发之余的小工具推荐处理 Base64、JSON 格式化、JWT 解析、Crontab 计算、PDF 合并压缩这些碎片需求我常用一个纯前端本地工具箱zz365.top。所有计算在浏览器完成文件不上服务器关页即清。免费、无登录、无广告适合开发者当常驻标签页。
返回列表