这种架构是完全可行且在某些场景下更简洁高效。这就是 "瘦客户端 + 胖服务端" 的架构模式。
直接调用架构
优势对比
✅ 直接调用的优势
-
更简单:减少中间层,降低复杂度
-
更可控:完全掌控调用逻辑和错误处理
-
更高效:减少不必要的协议转换
-
更灵活:不受 MCP 协议限制,可以自定义通信格式
⚠️ 使用 Claude Desktop 的优势
-
开箱即用:无需自己写客户端
-
对话集成:天然与 AI 对话流集成
-
生态丰富:可以利用现有的 MCP 工具生态
直接调用实现方案
方案1:简单的 HTTP 客户端
// direct_client.go
package main
import (
"encoding/json"
"fmt"
"net/http"
"bytes"
)
// 直接调用 MCP Server 的客户端
type DirectMCPClient struct {
baseURL string
httpClient *http.Client
apiKey string
}
func NewDirectMCPClient(baseURL, apiKey string) *DirectMCPClient {
return &DirectMCPClient{
baseURL: baseURL,
httpClient: &http.Client{},
apiKey: apiKey,
}
}
// 直接调用工具方法
func (c *DirectMCPClient) CallTool(toolName string, arguments map[string]interface{}) (map[string]interface{}, error) {
payload := map[string]interface{}{
"method": "callTool",
"params": map[string]interface{}{
"name": toolName,
"arguments": arguments,
},
}
return c.sendRequest(payload)
}
// 直接读取资源
func (c *DirectMCPClient) ReadResource(resourceName string, arguments map[string]interface{}) (map[string]interface{}, error) {
payload := map[string]interface{}{
"method": "readResource",
"params": map[string]interface{}{
"name": resourceName,
"arguments": arguments,
},
}
return c.sendRequest(payload)
}
// 发送 HTTP 请求
func (c *DirectMCPClient) sendRequest(payload map[string]interface{}) (map[string]interface{}, error) {
jsonData, err := json.Marshal(payload)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", c.baseURL+"/mcp/http", bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
if c.apiKey != "" {
req.Header.Set("Authorization", "Bearer "+c.apiKey)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("server returned status: %d", resp.StatusCode)
}
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result, nil
}
// 使用示例
func main() {
client := NewDirectMCPClient("http://localhost:8080", "your-api-key")
// 直接调用工具
result, err := client.CallTool("query-data", map[string]interface{}{
"query": "SELECT * FROM users WHERE active = true",
})
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Result: %+v\n", result)
// 直接读取资源
resource, err := client.ReadResource("database", map[string]interface{}{
"name": "db:///users",
})
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Resource: %+v\n", resource)
}
方案2:WebSocket 客户端
// websocket_client.go
package main
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/gorilla/websocket"
)
type WebsocketMCPClient struct {
conn *websocket.Conn
baseURL string
}
func NewWebsocketMCPClient(baseURL, apiKey string) (*WebsocketMCPClient, error) {
wsURL := "ws" + baseURL[4:] + "/mcp/ws" // 将 http 转换为 ws
if apiKey != "" {
wsURL += "?api_key=" + apiKey
}
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
if err != nil {
return nil, err
}
return &WebsocketMCPClient{
conn: conn,
baseURL: baseURL,
}, nil
}
func (c *WebsocketMCPClient) SendRequest(method string, params map[string]interface{}) (map[string]interface{}, error) {
request := map[string]interface{}{
"jsonrpc": "2.0",
"id": time.Now().UnixNano(),
"method": method,
"params": params,
}
if err := c.conn.WriteJSON(request); err != nil {
return nil, err
}
_, message, err := c.conn.ReadMessage()
if err != nil {
return nil, err
}
var response map[string]interface{}
if err := json.Unmarshal(message, &response); err != nil {
return nil, err
}
return response, nil
}
func (c *WebsocketMCPClient) Close() {
c.conn.Close()
}
方案3:简化的自定义 API(推荐)
如果你不需要严格的 MCP 协议兼容,可以创建更简单的 API:
// simple_api_server.go
package main
import (
"encoding/json"
"fmt"
"net/http"
)
// 简化的 API 端点
func SimpleQueryHandler(w http.ResponseWriter, r *http.Request) {
var request struct {
Query string `json:"query"`
Type string `json:"type"` // "sql", "api", "calculation"
}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// 直接处理请求,不需要 MCP 协议包装
result, err := processDirectQuery(request.Query, request.Type)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
}
func processDirectQuery(query, queryType string) (map[string]interface{}, error) {
// 直接处理逻辑
switch queryType {
case "sql":
return executeSQLQuery(query)
case "api":
return callExternalAPI(query)
case "calculation":
return performCalculation(query)
default:
return nil, fmt.Errorf("unknown query type: %s", queryType)
}
}
func executeSQLQuery(query string) (map[string]interface{}, error) {
// 直接执行 SQL 查询
return map[string]interface{}{
"data": []map[string]interface{}{{"id": 1, "name": "John"}},
"count": 1,
"success": true,
}, nil
}
客户端集成示例
前端直接调用
// 前端直接调用 MCP Server
async function callMCPServer(toolName, arguments) {
try {
const response = await fetch('http://your-mcp-server:8080/mcp/http', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token'
},
body: JSON.stringify({
method: 'callTool',
params: {
name: toolName,
arguments: arguments
}
})
});
const result = await response.json();
return result;
} catch (error) {
console.error('MCP Server call failed:', error);
throw error;
}
}
// 使用示例
const salesData = await callMCPServer('get-sales-data', {
period: '2024-01',
region: 'north'
});
命令行工具集成
#!/bin/bash
# mcp-cli.sh
# 直接调用 MCP Server 的命令行工具
function mcp-tool {
local tool_name=$1
shift
local args=$@
curl -X POST http://localhost:8080/mcp/http \
-H "Content-Type: application/json" \
-d "{
\"method\": \"callTool\",
\"params\": {
\"name\": \"$tool_name\",
\"arguments\": {$args}
}
}"
}
# 使用示例
mcp-tool "file-analysis" "\"path\": \"/home/user/document.txt\""
什么时候选择直接调用?
✅ 适合直接调用的场景
-
自定义客户端:你自己开发的应用程序
-
自动化脚本:CI/CD 流程、定时任务
-
内部工具:企业内部使用的工具链
-
性能敏感:需要最小化延迟的场景
-
简单集成:不需要复杂对话交互的场景
✅ 适合 Claude Desktop 的场景
-
对话式交互:需要与 AI 自然语言交互
-
快速原型:想要快速测试 MCP 功能
-
生态利用:想要利用现有的 MCP 工具
-
非技术用户:为不懂编程的用户提供界面
迁移建议
如果你已经从 Claude Desktop 开始,想要迁移到直接调用:
-
保持 API 兼容:确保直接调用的接口与 MCP 协议兼容
-
逐步迁移:先并行运行,再逐步切换
-
客户端封装:创建统一的客户端库来封装调用逻辑
// 统一的客户端封装
type UniversalClient struct {
// 可以支持多种调用方式
useDirect bool
baseURL string
// ... 其他配置
}
func (c *UniversalClient) CallTool(toolName string, args map[string]interface{}) (interface{}, error) {
if c.useDirect {
return c.directCall(toolName, args)
} else {
return c.mcpProtocolCall(toolName, args)
}
}
总结
完全可以不要 Claude Desktop,直接让你的客户端调用 MCP Server。这种架构:
-
✅ 更简单直接:减少中间层
-
✅ 更高效:降低延迟和复杂度
-
✅ 更灵活:不受特定客户端限制
-
✅ 更可控:完全掌控调用逻辑
选择依据:
-
如果需要对话式AI交互 → 使用 Claude Desktop
-
如果需要程序化调用 → 直接调用 MCP Server
-
如果两者都需要 → 实现兼容两种方式的接口
直接调用是更传统和灵活的集成方式,特别适合企业应用和自动化场景。
- THE END -
最后修改:2025年9月17日
非特殊说明,本博所有文章均为博主原创。