mcp可不可以客户端不用大模型,调用大模型放在mcp server上呢

TwoAdmin 2025-9-17 103 9/17

可以,但这种架构完全违背了 MCP 的设计初衷,会产生很多问题。 不过,技术上确实可以实现。

让我先解释如何实现,然后再详细说明为什么这不是一个好主意。

如何实现(反模式)

MCP Server 端(包含大模型调用)

go

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)
	}
}

客户端(轻量级,无大模型)

go

// 简单的 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. 性能问题

go

// 每次调用都需要网络往返
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. 功能限制

go
// 失去了 MCP 的核心优势
// 无法实现:
// - 智能工具选择
// - 上下文感知
// - 多步骤推理
// - 动态提示词构建

哪些情况下可能考虑这种架构?

尽管不推荐,但在某些特定场景下可能暂时需要:

场景1:客户端无法直接访问API

go

// 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:需要统一的数据处理

go

// 在调用大模型前进行数据预处理
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:企业安全要求

go

// 所有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:混合架构(推荐)

go

// 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网关

go
// 如果必须集中处理AI调用,使用专门的AI网关
// 而不是滥用 MCP Server

// MCP Server -> 专用AI服务 -> 大模型API
// 这样保持 MCP 协议的纯洁性

总结

技术上可以,但架构上不推荐。除非你有非常特定的需求(如安全审计、统一预处理、客户端限制等),否则应该遵循 MCP 的标准设计模式:

  • MCP Server: 提供数据、工具、资源

  • MCP Client: 包含大模型,负责推理和决策

这样既能发挥 MCP 协议的优势,又能保证系统的性能和可维护性。

- THE END -
Tag:

TwoAdmin

9月17日17:17

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

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