可以,但这种架构完全违背了 MCP 的设计初衷,会产生很多问题。 不过,技术上确实可以实现。
让我先解释如何实现,然后再详细说明为什么这不是一个好主意。
如何实现(反模式)
MCP Server 端(包含大模型调用)
package main
import (
"context"
"fmt"
"log"
openai "github.com/sashabaranov/go-openai"
mcp "github.com/modelcontextprotocol/sdk-go"
)
type AIServer struct {
client *openai.Client
}
func NewAIServer(apiKey string) *AIServer {
return &AIServer{
client: openai.NewClient(apiKey),
}
}
func (s *AIServer) HandleChat(ctx context.Context, params map[string]any) (mcp.ToolResult, error) {
prompt, ok := params["prompt"].(string)
if !ok {
return mcp.ToolResult{}, fmt.Errorf("prompt parameter is required")
}
// 直接调用大模型 API
resp, err := s.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: openai.GPT4Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: prompt,
},
},
})
if err != nil {
return mcp.ToolResult{}, err
}
return mcp.ToolResult{
Content: []mcp.Content{
{
Type: "text",
Text: resp.Choices[0].Message.Content,
},
},
}, nil
}
func main() {
server := mcp.NewServer("ai-chat-server", "1.0.0")
// 从环境变量获取 API Key
apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
log.Fatal("OPENAI_API_KEY environment variable is required")
}
aiServer := NewAIServer(apiKey)
server.AddToolHandler("chat", aiServer.HandleChat, mcp.ParamSchema{
Type: mcp.JSONSchemaObject,
Properties: map[string]mcp.ParamSchema{
"prompt": {
Type: mcp.JSONSchemaString,
Description: "The prompt to send to AI",
},
},
Required: []string{"prompt"},
})
if err := server.Run(); err != nil {
log.Fatalf("Server error: %v", err)
}
}
客户端(轻量级,无大模型)
// 简单的 MCP 客户端示例
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os/exec"
"time"
)
type JSONRPCRequest struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params interface{} `json:"params,omitempty"`
}
type JSONRPCResponse struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Result interface{} `json:"result,omitempty"`
Error interface{} `json:"error,omitempty"`
}
func main() {
// 启动 MCP Server
cmd := exec.Command("./ai-chat-server")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
// 发送聊天请求
request := JSONRPCRequest{
JSONRPC: "2.0",
ID: 1,
Method: "callTool",
Params: map[string]interface{}{
"name": "chat",
"arguments": map[string]interface{}{
"prompt": "你好,请介绍一下MCP协议",
},
},
}
// 发送请求
encoder := json.NewEncoder(stdin)
if err := encoder.Encode(request); err != nil {
log.Fatal(err)
}
// 读取响应
decoder := json.NewDecoder(stdout)
var response JSONRPCResponse
if err := decoder.Decode(&response); err != nil {
log.Fatal(err)
}
fmt.Printf("AI Response: %v\n", response.Result)
}
为什么这是反模式?
1. 架构问题
2. 性能问题
// 每次调用都需要网络往返
func (s *AIServer) HandleChat(ctx context.Context, params map[string]any) (mcp.ToolResult, error) {
// ❌ 网络延迟 + API 延迟
resp, err := s.client.CreateChatCompletion(ctx, ...)
// ❌ 再次通过网络返回
return mcp.ToolResult{...}, err
}
3. 成本问题
-
重复的上下文传输
-
无法利用客户端本地模型
-
额外的网络带宽消耗
4. 功能限制
// 失去了 MCP 的核心优势 // 无法实现: // - 智能工具选择 // - 上下文感知 // - 多步骤推理 // - 动态提示词构建
哪些情况下可能考虑这种架构?
尽管不推荐,但在某些特定场景下可能暂时需要:
场景1:客户端无法直接访问API
// MCP Server 作为代理
server.AddToolHandler("proxy-chat", func(ctx context.Context, params map[string]any) (mcp.ToolResult, error) {
// 客户端没有 API key,通过服务器中转
if isRateLimited(params["user_id"]) {
return mcp.ToolResult{}, fmt.Errorf("rate limited")
}
// 调用大模型...
})
场景2:需要统一的数据处理
// 在调用大模型前进行数据预处理
server.AddToolHandler("analyze-data", func(ctx context.Context, params map[string]any) (mcp.ToolResult, error) {
// 1. 预处理数据
processedData := preprocessData(params["raw_data"])
// 2. 调用大模型
result, err := callAIWithContext(processedData)
// 3. 后处理结果
finalResult := postprocessResult(result)
return mcp.ToolResult{Content: finalResult}, err
})
场景3:企业安全要求
// 所有AI调用必须通过内部审计
server.AddToolHandler("secure-chat", func(ctx context.Context, params map[string]any) (mcp.ToolResult, error) {
// 记录审计日志
logAudit(params["user_id"], params["prompt"])
// 检查敏感词
if containsSensitiveInfo(params["prompt"]) {
return mcp.ToolResult{}, fmt.Errorf("sensitive content detected")
}
// 调用大模型
response, err := s.client.CreateChatCompletion(...)
// 再次审计输出
logResponse(params["user_id"], response)
return mcp.ToolResult{Content: response}, err
})
推荐的替代方案
方案1:混合架构(推荐)
// MCP Server 提供数据和工具,客户端负责AI推理
server.AddToolHandler("get-user-data", func(ctx context.Context, params map[string]any) (mcp.ToolResult, error) {
// 返回结构化数据,让客户端AI来分析
data := getUserData(params["user_id"])
return mcp.ToolResult{
Content: []mcp.Content{
{
Type: "text",
Text: formatAsJSON(data), // 客户端AI会解析这个JSON
},
},
}, nil
})
方案2:专用AI网关
// 如果必须集中处理AI调用,使用专门的AI网关 // 而不是滥用 MCP Server // MCP Server -> 专用AI服务 -> 大模型API // 这样保持 MCP 协议的纯洁性
总结
技术上可以,但架构上不推荐。除非你有非常特定的需求(如安全审计、统一预处理、客户端限制等),否则应该遵循 MCP 的标准设计模式:
-
✅ MCP Server: 提供数据、工具、资源
-
✅ MCP Client: 包含大模型,负责推理和决策
这样既能发挥 MCP 协议的优势,又能保证系统的性能和可维护性。
- THE END -
最后修改:2025年9月17日
非特殊说明,本博所有文章均为博主原创。