mcp server 为远程调用

TwoAdmin 2025-9-17 130 9/17

上一篇文章讲了mcp server本地调用,这一篇讲下远程调用

MCP Server 支持远程调用,这是生产环境中的常见部署方式。远程 MCP Server 通过 HTTP/HTTPS 或 WebSocket 提供服务,而不是通过 STDIN/STDOUT。

远程 MCP Server 架构

远程 MCP Server 实现(Go)

1. HTTP 传输的 MCP Server

go

// remote_mcp_server.go
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/websocket"
	"github.com/modelcontextprotocol/sdk-go"
)

type RemoteMCPServer struct {
	server *mcp.Server
	port   string
}

var upgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool {
		return true // 生产环境中应该限制来源
	},
}

func NewRemoteMCPServer(port string) *RemoteMCPServer {
	s := mcp.NewServer("remote-data-server", "1.0.0")
	
	// 添加工具和资源
	s.AddResourceHandler("database", func(ctx context.Context, method string, params map[string]any) (mcp.ResourceResult, error) {
		// 模拟数据库查询
		if method == "list" {
			return mcp.ResourceResult{
				Contents: []mcp.ResourceContent{
					{URI: "db:///users", Name: "用户表"},
					{URI: "db:///products", Name: "产品表"},
				},
			}, nil
		}

		// 读取具体资源
		uri, _ := params["name"].(string)
		switch uri {
		case "db:///users":
			return mcp.ResourceResult{
				Contents: []mcp.ResourceContent{
					{
						URI:      "db:///users",
						Content:  `[{"id":1,"name":"张三","email":"zhang@example.com"},{"id":2,"name":"李四","email":"li@example.com"}]`,
						MimeType: "application/json",
					},
				},
			}, nil
		case "db:///products":
			return mcp.ResourceResult{
				Contents: []mcp.ResourceContent{
					{
						URI:      "db:///products",
						Content:  `[{"id":1,"name":"产品A","price":99.99},{"id":2,"name":"产品B","price":149.99}]`,
						MimeType: "application/json",
					},
				},
			}, nil
		default:
			return mcp.ResourceResult{}, fmt.Errorf("resource not found: %s", uri)
		}
	})

	s.AddToolHandler("query-data", func(ctx context.Context, params map[string]any) (mcp.ToolResult, error) {
		query, _ := params["query"].(string)
		
		// 模拟查询处理
		result := map[string]interface{}{
			"query":   query,
			"results": []string{"结果1", "结果2", "结果3"},
			"count":   3,
			"timestamp": time.Now().Format(time.RFC3339),
		}

		jsonResult, _ := json.Marshal(result)
		
		return mcp.ToolResult{
			Content: []mcp.Content{
				{
					Type: "text",
					Text: string(jsonResult),
				},
			},
		}, nil
	}, mcp.ParamSchema{
		Type: mcp.JSONSchemaObject,
		Properties: map[string]mcp.ParamSchema{
			"query": {
				Type:        mcp.JSONSchemaString,
				Description: "查询语句",
			},
		},
		Required: []string{"query"},
	})

	return &RemoteMCPServer{
		server: s,
		port:   port,
	}
}

func (s *RemoteMCPServer) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Printf("WebSocket upgrade failed: %v", err)
		return
	}
	defer conn.Close()

	log.Println("Client connected via WebSocket")

	// 处理消息循环
	for {
		_, message, err := conn.ReadMessage()
		if err != nil {
			log.Printf("Read error: %v", err)
			break
		}

		// 处理 MCP 请求
		response, err := s.server.HandleRawMessage(context.Background(), message)
		if err != nil {
			log.Printf("Handle error: %v", err)
			continue
		}

		// 发送响应
		if err := conn.WriteMessage(websocket.TextMessage, response); err != nil {
			log.Printf("Write error: %v", err)
			break
		}
	}
}

func (s *RemoteMCPServer) HandleHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var request map[string]interface{}
	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		http.Error(w, "Invalid JSON", http.StatusBadRequest)
		return
	}

	requestBytes, _ := json.Marshal(request)
	response, err := s.server.HandleRawMessage(r.Context(), requestBytes)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(response)
}

func (s *RemoteMCPServer) Start() error {
	http.HandleFunc("/mcp/ws", s.HandleWebSocket)
	http.HandleFunc("/mcp/http", s.HandleHTTP)
	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("OK"))
	})

	log.Printf("Starting remote MCP server on port %s", s.port)
	return http.ListenAndServe(":"+s.port, nil)
}

func main() {
	server := NewRemoteMCPServer("8080")
	if err := server.Start(); err != nil {
		log.Fatalf("Server failed: %v", err)
	}
}


客户端配置

1. Claude Desktop 配置

json


// ~/.config/claude/claude_desktop_config.json
{
  "mcpServers": {
    "remote-data-server": {
      "command": "http://your-server.com:8080/mcp/ws",
      "type": "websocket"  // 或 "http" 对于 HTTP 传输
    },
    "another-remote-server": {
      "command": "https://api.example.com/mcp/http",
      "type": "http",
      "headers": {
        "Authorization": "Bearer your-api-key",
        "X-Custom-Header": "value"
      }
    }
  }
}

2. 环境变量配置(推荐)

bash

# 设置环境变量
export MCP_SERVER_URL="http://localhost:8080/mcp/ws"
export MCP_API_KEY="your-secret-key"

安全配置

1. 添加认证中间件

go

// auth_middleware.go
package main

import (
	"net/http"
	"strings"
)

func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// 检查 API Key
		apiKey := r.Header.Get("Authorization")
		if apiKey == "" {
			apiKey = r.URL.Query().Get("api_key")
		}

		expectedKey := "Bearer your-secret-token"
		if apiKey != expectedKey {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}

		// 检查来源
		origin := r.Header.Get("Origin")
		if origin != "" && !isAllowedOrigin(origin) {
			http.Error(w, "Forbidden", http.StatusForbidden)
			return
		}

		next(w, r)
	}
}

func isAllowedOrigin(origin string) bool {
	allowedOrigins := []string{
		"https://claude.ai",
		"http://localhost:3000",
		// 添加其他允许的域名
	}
	
	for _, allowed := range allowedOrigins {
		if origin == allowed {
			return true
		}
	}
	return false
}

// 在使用时包装处理函数
http.HandleFunc("/mcp/ws", AuthMiddleware(s.HandleWebSocket))
http.HandleFunc("/mcp/http", AuthMiddleware(s.HandleHTTP))

2. HTTPS 配置

go


// https_server.go
func StartHTTPServer() error {
	// 加载 SSL 证书
	certFile := "/path/to/cert.pem"
	keyFile := "/path/to/key.pem"

	// 重定向 HTTP 到 HTTPS
	go func() {
		http.ListenAndServe(":80", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusMovedPermanently)
		}))
	}()

	return http.ListenAndServeTLS(":443", certFile, keyFile, nil)
}

Docker 部署

1. Dockerfile

dockerfile
FROM golang:1.21-alpine

WORKDIR /app

# 安装依赖
RUN apk add --no-cache git

# 下载依赖
COPY go.mod go.sum ./
RUN go mod download

# 复制源码
COPY *.go ./

# 构建
RUN go build -o remote-mcp-server

# 暴露端口
EXPOSE 8080

# 运行
CMD ["./remote-mcp-server"]

客户端调用示例(Python)

1. Python 远程 MCP 客户端

python

# remote_mcp_client.py
import requests
import json
import websockets
import asyncio

class RemoteMCPClient:
    def __init__(self, base_url, api_key=None):
        self.base_url = base_url
        self.api_key = api_key
        self.session = requests.Session()
        
        if api_key:
            self.session.headers.update({
                'Authorization': f'Bearer {api_key}',
                'Content-Type': 'application/json'
            })
    
    async def connect_websocket(self):
        """连接到 WebSocket 端点"""
        ws_url = self.base_url.replace('http', 'ws') + '/mcp/ws'
        if self.api_key:
            ws_url += f'?api_key={self.api_key}'
        
        self.websocket = await websockets.connect(ws_url)
        return self.websocket
    
    async def send_websocket_message(self, message):
        """通过 WebSocket 发送消息"""
        await self.websocket.send(json.dumps(message))
        response = await self.websocket.recv()
        return json.loads(response)
    
    def send_http_request(self, method, params=None):
        """通过 HTTP 发送请求"""
        url = f"{self.base_url}/mcp/http"
        payload = {
            "jsonrpc": "2.0",
            "id": 1,
            "method": method,
            "params": params or {}
        }
        
        response = self.session.post(url, json=payload)
        response.raise_for_status()
        return response.json()

# 使用示例
async def main():
    # HTTP 调用示例
    client = RemoteMCPClient("http://localhost:8080", "your-api-key")
    
    # 列出可用资源
    resources = client.send_http_request("listResources")
    print("Available resources:", resources)
    
    # 读取资源
    user_data = client.send_http_request("readResource", {
        "name": "database",
        "arguments": {"name": "db:///users"}
    })
    print("User data:", user_data)
    
    # WebSocket 调用示例
    ws_client = RemoteMCPClient("http://localhost:8080", "your-api-key")
    await ws_client.connect_websocket()
    
    # 调用工具
    tool_result = await ws_client.send_websocket_message({
        "jsonrpc": "2.0",
        "id": 2,
        "method": "callTool",
        "params": {
            "name": "query-data",
            "arguments": {"query": "SELECT * FROM users"}
        }
    })
    print("Tool result:", tool_result)

if __name__ == "__main__":
    asyncio.run(main())

监控和日志

1. 添加监控中间件

go


// monitoring.go
func LoggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		
		// 包装 ResponseWriter 来捕获状态码
		wrapped := &responseWriter{w, http.StatusOK}
		next(wrapped, r)
		
		duration := time.Since(start)
		log.Printf("%s %s %d %v", r.Method, r.URL.Path, wrapped.status, duration)
		
		// 可以推送到 Prometheus 等监控系统
		metrics.RequestDuration.Observe(duration.Seconds())
		metrics.RequestCount.Inc()
	}
}

type responseWriter struct {
	http.ResponseWriter
	status int
}

func (rw *responseWriter) WriteHeader(code int) {
	rw.status = code
	rw.ResponseWriter.WriteHeader(code)
}

总结

远程 MCP Server 的优势:

  1. 集中部署: 一套服务多个客户端使用

  2. 资源隔离: 敏感数据不暴露在客户端

  3. 性能优化: 可以连接专用数据库和缓存

  4. 安全控制: 统一的认证和授权机制

  5. 监控运维: 集中日志和性能监控

通过远程部署,你可以构建企业级的 MCP 服务,为 AI 应用提供强大的后端能力支持。

- THE END -

TwoAdmin

9月17日19:23

最后修改:2025年9月17日
0

非特殊说明,本博所有文章均为博主原创。