上一篇文章讲了mcp server本地调用,这一篇讲下远程调用
MCP Server 支持远程调用,这是生产环境中的常见部署方式。远程 MCP Server 通过 HTTP/HTTPS 或 WebSocket 提供服务,而不是通过 STDIN/STDOUT。
远程 MCP Server 架构
远程 MCP Server 实现(Go)
1. HTTP 传输的 MCP Server
// 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 配置
// ~/.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. 环境变量配置(推荐)
# 设置环境变量
export MCP_SERVER_URL="http://localhost:8080/mcp/ws"
export MCP_API_KEY="your-secret-key"
安全配置
1. 添加认证中间件
// 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 配置
// 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
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 客户端
# 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. 添加监控中间件
// 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 的优势:
-
集中部署: 一套服务多个客户端使用
-
资源隔离: 敏感数据不暴露在客户端
-
性能优化: 可以连接专用数据库和缓存
-
安全控制: 统一的认证和授权机制
-
监控运维: 集中日志和性能监控
通过远程部署,你可以构建企业级的 MCP 服务,为 AI 应用提供强大的后端能力支持。
- THE END -
最后修改:2025年9月17日
非特殊说明,本博所有文章均为博主原创。