公告

任何建议或需求可联系我!


Skip to content

高级Server功能

图标

图标为服务器、工具、资源和提示词提供视觉标识符。所有图标 URI 必须使用 HTTPS 或数据 URI 方案以确保安全。

安全注意事项

  • 始终验证图标来源来自可信域
  • 强制大小限制以防止内存问题
  • 至少支持 PNG 和 JPEG 格式
  • 考虑支持 SVG 和 WebP 以获得更好的质量/大小

使用数据 URI 的示例

go
mcp.Icon{
    Src:      "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiLz4=",
    MIMEType: "image/svg+xml",
}

主题特定图标

图标可以选择性地声明它们设计用于哪个背景主题。客户端在浅色或深色 UI 上渲染时使用该提示来选择正确的资源:

go
icons := []mcp.Icon{
    {
        Src:      "https://example.com/icons/tool-light.svg",
        MIMEType: "image/svg+xml",
        Theme:    mcp.IconThemeLight,    // 为浅色背景设计
    },
    {
        Src:      "https://example.com/icons/tool-dark.svg",
        MIMEType: "image/svg+xml",
        Theme:    mcp.IconThemeDark,     // 为深色背景设计
    },
}

Theme 字段是可选的——没有主题的图标被视为主题无关的。支持 mcp.IconThemeLightmcp.IconThemeDark 常量;未知值会原样传递以实现向前兼容。

探索使 MCP-Go 服务器可用于生产的功能:类型工具、会话管理、中间件、钩子等。

类型工具

类型工具提供编译时类型安全和自动参数验证,减少样板代码并防止运行时错误。

基本类型工具

go
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "time"

    "github.com/mark3labs/mcp-go/mcp"
    "github.com/mark3labs/mcp-go/server"
)

// 定义输入和输出类型
type CalculateInput struct {
    Operation string  `json:"operation" validate:"required,oneof=add subtract multiply divide"`
    X         float64 `json:"x" validate:"required"`
    Y         float64 `json:"y" validate:"required"`
}

type CalculateOutput struct {
    Result    float64 `json:"result"`
    Operation string  `json:"operation"`
}

func main() {
    s := server.NewMCPServer("Typed Server", "1.0.0",
        server.WithToolCapabilities(true),
    )

    // 创建类型工具
    tool := mcp.NewTool("calculate",
        mcp.WithDescription("执行算术运算"),
        mcp.WithString("operation", mcp.Required()),
        mcp.WithNumber("x", mcp.Required()),
        mcp.WithNumber("y", mcp.Required()),
    )
    
    // 使用类型处理程序添加工具
    s.AddTool(tool, mcp.NewTypedToolHandler(handleCalculateTyped))

    server.ServeStdio(s)
}

func handleCalculateTyped(ctx context.Context, req mcp.CallToolRequest, input CalculateInput) (*mcp.CallToolResult, error) {
    var result float64
    
    switch input.Operation {
    case "add":
        result = input.X + input.Y
    case "subtract":
        result = input.X - input.Y
    case "multiply":
        result = input.X * input.Y
    case "divide":
        if input.Y == 0 {
            return mcp.NewToolResultError("division by zero"), nil
        }
        result = input.X / input.Y
    }

    output := CalculateOutput{
        Result:    result,
        Operation: input.Operation,
    }
    
    jsonData, err := json.Marshal(output)
    if err != nil {
        return mcp.NewToolResultError(fmt.Sprintf("failed to marshal result: %v", err)), nil
    }
    
    return mcp.NewToolResultText(string(jsonData)), nil
}

复杂类型工具

go
type UserCreateInput struct {
    Name     string            `json:"name" validate:"required,min=1,max=100"`
    Email    string            `json:"email" validate:"required,email"`
    Age      int               `json:"age" validate:"min=0,max=150"`
    Tags     []string          `json:"tags" validate:"dive,min=1"`
    Metadata map[string]string `json:"metadata"`
    Active   bool              `json:"active"`
}

type UserCreateOutput struct {
    ID        string    `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    CreatedAt time.Time `json:"created_at"`
    Status    string    `json:"status"`
}

func handleCreateUser(ctx context.Context, req mcp.CallToolRequest, input UserCreateInput) (*mcp.CallToolResult, error) {
    // 验证基于结构标签是自动的
    
    // 在数据库中创建用户
    user := &User{
        ID:        generateID(),
        Name:      input.Name,
        Email:     input.Email,
        Age:       input.Age,
        Tags:      input.Tags,
        Metadata:  input.Metadata,
        Active:    input.Active,
        CreatedAt: time.Now(),
    }

    if err := db.Create(user); err != nil {
        return nil, fmt.Errorf("failed to create user: %w", err)
    }

    output := &UserCreateOutput{
        ID:        user.ID,
        Name:      user.Name,
        Email:     user.Email,
        CreatedAt: user.CreatedAt,
        Status:    "created",
    }

    return mcp.NewToolResultJSON(output)
}

自定义验证

go
import (
    "path/filepath"
    "strings"

    "github.com/go-playground/validator/v10"
)

type FileOperationInput struct {
    Path      string `json:"path" validate:"required,filepath"`
    Operation string `json:"operation" validate:"required,oneof=read write delete"`
    Content   string `json:"content" validate:"required_if=Operation write"`
}

// 自定义验证器
func init() {
    validate := validator.New()
    validate.RegisterValidation("filepath", validateFilePath)
}

func validateFilePath(fl validator.FieldLevel) bool {
    path := fl.Field().String()
    
    // 防止目录遍历
    if strings.Contains(path, "..") {
        return false
    }
    
    // 确保路径在允许的目录内
    allowedDir := "/app/data"
    absPath, err := filepath.Abs(path)
    if err != nil {
        return false
    }
    
    return strings.HasPrefix(absPath, allowedDir)
}

会话管理

使用每个会话状态和工具处理多个客户端。

每个会话状态

go
type SessionState struct {
    UserID      string
    Permissions []string
    Settings    map[string]interface{}
    StartTime   time.Time
}

type SessionManager struct {
    sessions map[string]*SessionState
    mutex    sync.RWMutex
}

func NewSessionManager() *SessionManager {
    return &SessionManager{
        sessions: make(map[string]*SessionState),
    }
}

func (sm *SessionManager) CreateSession(sessionID, userID string, permissions []string) {
    sm.mutex.Lock()
    defer sm.mutex.Unlock()
    
    sm.sessions[sessionID] = &SessionState{
        UserID:      userID,
        Permissions: permissions,
        Settings:    make(map[string]interface{}),
        StartTime:   time.Now(),
    }
}

func (sm *SessionManager) GetSession(sessionID string) (*SessionState, bool) {
    sm.mutex.RLock()
    defer sm.mutex.RUnlock()
    
    session, exists := sm.sessions[sessionID]
    return session, exists
}

func (sm *SessionManager) RemoveSession(sessionID string) {
    sm.mutex.Lock()
    defer sm.mutex.Unlock()
    
    delete(sm.sessions, sessionID)
}

会话感知工具

go
func main() {
    sessionManager := NewSessionManager()
    
    hooks := &server.Hooks{}
    
    hooks.AddOnRegisterSession(func(ctx context.Context, session server.ClientSession) {
        // 使用默认权限初始化会话
        sessionManager.CreateSession(session.ID(), "anonymous", []string{"read"})
        log.Printf("Session %s started", session.ID())
    })
    
    hooks.AddOnUnregisterSession(func(ctx context.Context, session server.ClientSession) {
        sessionManager.RemoveSession(session.ID())
        log.Printf("Session %s ended", session.ID())
    })
    
    s := server.NewMCPServer("Session Server", "1.0.0",
        server.WithToolCapabilities(true),
        server.WithHooks(hooks),
    )

    // 添加会话感知工具
    s.AddTool(
        mcp.NewTool("get_user_data",
            mcp.WithDescription("获取用户特定数据"),
            mcp.WithString("data_type", mcp.Required()),
        ),
        createSessionAwareTool(sessionManager),
    )

    server.ServeStdio(s)
}

func createSessionAwareTool(sm *SessionManager) server.ToolHandler {
    return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
        sessionID := server.GetSessionID(ctx)
        session, exists := sm.GetSession(sessionID)
        if !exists {
            return nil, fmt.Errorf("invalid session")
        }

        dataType := req.Params.Arguments["data_type"].(string)
        
        // 检查权限
        if !hasPermission(session.Permissions, "read") {
            return nil, fmt.Errorf("insufficient permissions")
        }

        // 获取用户特定数据
        data, err := getUserData(session.UserID, dataType)
        if err != nil {
            return nil, err
        }

        jsonData, err := json.Marshal(data)
        if err != nil {
            return mcp.NewToolResultError(fmt.Sprintf("failed to marshal result: %v", err)), nil
        }
        
        return mcp.NewToolResultText(string(jsonData)), nil
    }
}

中间件

添加日志记录、身份验证和速率限制等横切关注点。

日志中间件

go
type LoggingMiddleware struct {
    logger *log.Logger
}

func NewLoggingMiddleware(logger *log.Logger) *LoggingMiddleware {
    return &LoggingMiddleware{logger: logger}
}

func (m *LoggingMiddleware) ToolMiddleware(next server.ToolHandlerFunc) server.ToolHandlerFunc {
    return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
        start := time.Now()
        sessionID := server.GetSessionID(ctx)
        
        m.logger.Printf("Tool call started: tool=%s", req.Params.Name)
        
        result, err := next(ctx, req)
        
        duration := time.Since(start)
        if err != nil {
            m.logger.Printf("Tool call failed: session=%s tool=%s duration=%v error=%v", 
                sessionID, req.Params.Name, duration, err)
        } else {
            m.logger.Printf("Tool call completed: session=%s tool=%s duration=%v", 
                sessionID, req.Params.Name, duration)
        }
        
        return result, err
    }
}

func (m *LoggingMiddleware) ResourceMiddleware(next server.ResourceHandler) server.ResourceHandler {
    return func(ctx context.Context, req mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
        start := time.Now()
        sessionID := server.GetSessionID(ctx)
        
        m.logger.Printf("Resource read started: session=%s uri=%s", sessionID, req.Params.URI)
        
        result, err := next(ctx, req)
        
        duration := time.Since(start)
        if err != nil {
            m.logger.Printf("Resource read failed: session=%s uri=%s duration=%v error=%v", 
                sessionID, req.Params.URI, duration, err)
        } else {
            m.logger.Printf("Resource read completed: session=%s uri=%s duration=%v", 
                sessionID, req.Params.URI, duration)
        }
        
        return result, err
    }
}

速率限制中间件

go
type RateLimitMiddleware struct {
    limiters map[string]*rate.Limiter
    mutex    sync.RWMutex
    rate     rate.Limit
    burst    int
}

func NewRateLimitMiddleware(requestsPerSecond float64, burst int) *RateLimitMiddleware {
    return &RateLimitMiddleware{
        limiters: make(map[string]*rate.Limiter),
        rate:     rate.Limit(requestsPerSecond),
        burst:    burst,
    }
}

func (m *RateLimitMiddleware) getLimiter(sessionID string) *rate.Limiter {
    m.mutex.RLock()
    limiter, exists := m.limiters[sessionID]
    m.mutex.RUnlock()
    
    if !exists {
        m.mutex.Lock()
        limiter = rate.NewLimiter(m.rate, m.burst)
        m.limiters[sessionID] = limiter
        m.mutex.Unlock()
    }
    
    return limiter
}

func (m *RateLimitMiddleware) ToolMiddleware(next server.ToolHandler) server.ToolHandler {
    return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
        sessionID := server.GetSessionID(ctx)
        limiter := m.getLimiter(sessionID)
        
        if !limiter.Allow() {
            return nil, fmt.Errorf("rate limit exceeded for session %s", sessionID)
        }
        
        return next(ctx, req)
    }
}

身份验证中间件

go
type AuthMiddleware struct {
    tokenValidator TokenValidator
}

func NewAuthMiddleware(validator TokenValidator) *AuthMiddleware {
    return &AuthMiddleware{tokenValidator: validator}
}

func (m *AuthMiddleware) ToolMiddleware(next server.ToolHandler) server.ToolHandler {
    return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
        // 从上下文或请求中提取令牌
        token := extractToken(ctx, req)
        if token == "" {
            return nil, fmt.Errorf("authentication required")
        }
        
        // 验证令牌
        user, err := m.tokenValidator.Validate(token)
        if err != nil {
            return nil, fmt.Errorf("invalid token: %w", err)
        }
        
        // 将用户添加到上下文
        ctx = context.WithValue(ctx, "user", user)
        
        return next(ctx, req)
    }
}

钩子

实现用于遥测、日志记录和自定义行为的生命周期回调。

使用 GetHooks() 的钩子可组合性

使用 GetHooks() 向现有服务器追加钩子,而不替换整个钩子配置。这对于需要在构建后添加钩子的库或插件很有用:

go
s := server.NewMCPServer("My Server", "1.0.0",
    server.WithHooks(initialHooks),
)

// 之后,在不替换现有钩子的情况下添加更多钩子
hooks := s.GetHooks()
if hooks == nil {
    hooks = &server.Hooks{}
}
hooks.AddBeforeAny(func(ctx context.Context, id any, method mcp.MCPMethod, message any) {
    log.Printf("Request: %s", method)
})

综合钩子

go
type TelemetryHooks struct {
    metrics MetricsCollector
    logger  *log.Logger
}

func NewTelemetryHooks(metrics MetricsCollector, logger *log.Logger) *TelemetryHooks {
    return &TelemetryHooks{
        metrics: metrics,
        logger:  logger,
    }
}

func (h *TelemetryHooks) OnServerStart() {
    h.logger.Println("MCP Server starting")
    h.metrics.Increment("server.starts")
}

func (h *TelemetryHooks) OnServerStop() {
    h.logger.Println("MCP Server stopping")
    h.metrics.Increment("server.stops")
}

func (h *TelemetryHooks) OnSessionStart(sessionID string) {
    h.logger.Printf("Session started: %s", sessionID)
    h.metrics.Increment("sessions.started")
    h.metrics.Gauge("sessions.active", h.getActiveSessionCount())
}

func (h *TelemetryHooks) OnSessionEnd(sessionID string) {
    h.logger.Printf("Session ended: %s", sessionID)
    h.metrics.Increment("sessions.ended")
    h.metrics.Gauge("sessions.active", h.getActiveSessionCount())
}

func (h *TelemetryHooks) OnToolCall(sessionID, toolName string, duration time.Duration, err error) {
    h.metrics.Increment("tools.calls", map[string]string{
        "tool":    toolName,
        "session": sessionID,
    })
    h.metrics.Histogram("tools.duration", duration.Seconds(), map[string]string{
        "tool": toolName,
    })
    
    if err != nil {
        h.metrics.Increment("tools.errors", map[string]string{
            "tool": toolName,
        })
    }
}

func (h *TelemetryHooks) OnResourceRead(sessionID, uri string, duration time.Duration, err error) {
    h.metrics.Increment("resources.reads", map[string]string{
        "session": sessionID,
    })
    h.metrics.Histogram("resources.duration", duration.Seconds())
    
    if err != nil {
        h.metrics.Increment("resources.errors")
    }
}

自定义业务逻辑钩子

go
type BusinessHooks struct {
    auditLogger AuditLogger
    notifier    Notifier
}

func (h *BusinessHooks) OnToolCall(sessionID, toolName string, duration time.Duration, err error) {
    // 审计敏感操作
    if isSensitiveTool(toolName) {
        h.auditLogger.LogToolCall(sessionID, toolName, err)
    }
    
    // 警报错误
    if err != nil {
        h.notifier.SendAlert(fmt.Sprintf("Tool %s failed for session %s: %v", 
            toolName, sessionID, err))
    }
    
    // 监控性能
    if duration > 30*time.Second {
        h.notifier.SendAlert(fmt.Sprintf("Slow tool execution: %s took %v", 
            toolName, duration))
    }
}

func (h *BusinessHooks) OnSessionStart(sessionID string) {
    // 初始化用户特定资源
    h.initializeUserResources(sessionID)
    
    // 发送欢迎通知
    h.notifier.SendWelcome(sessionID)
}

func (h *BusinessHooks) OnSessionEnd(sessionID string) {
    // 清理用户资源
    h.cleanupUserResources(sessionID)
    
    // 记录会话摘要
    h.auditLogger.LogSessionEnd(sessionID)
}

工具过滤

根据上下文、权限或其他条件有条件地公开工具。

基于权限的过滤

go
type PermissionFilter struct {
    sessionManager *SessionManager
}

func NewPermissionFilter(sm *SessionManager) *PermissionFilter {
    return &PermissionFilter{sessionManager: sm}
}

func (f *PermissionFilter) FilterTools(ctx context.Context, tools []mcp.Tool) []mcp.Tool {
    sessionID := server.GetSessionID(ctx)
    session, exists := f.sessionManager.GetSession(sessionID)
    if !exists {
        return []mcp.Tool{} // 无效会话没有工具
    }

    var filtered []mcp.Tool
    for _, tool := range tools {
        if f.hasPermissionForTool(session, tool.Name) {
            filtered = append(filtered, tool)
        }
    }
    
    return filtered
}

func (f *PermissionFilter) hasPermissionForTool(session *SessionState, toolName string) bool {
    requiredPermissions := map[string][]string{
        "delete_user":    {"admin"},
        "modify_system":  {"admin", "operator"},
        "read_data":      {"admin", "operator", "user"},
        "create_report":  {"admin", "operator", "user"},
    }
    
    required, exists := requiredPermissions[toolName]
    if !exists {
        return true // 允许没有特定要求的工具
    }
    
    for _, permission := range session.Permissions {
        for _, req := range required {
            if permission == req {
                return true
            }
        }
    }
    
    return false
}

基于上下文的过滤

go
type ContextFilter struct{}

func (f *ContextFilter) FilterTools(ctx context.Context, tools []mcp.Tool) []mcp.Tool {
    timeOfDay := time.Now().Hour()
    environment := os.Getenv("ENVIRONMENT")
    
    var filtered []mcp.Tool
    for _, tool := range tools {
        if f.shouldIncludeTool(tool, timeOfDay, environment) {
            filtered = append(filtered, tool)
        }
    }
    
    return filtered
}

func (f *ContextFilter) shouldIncludeTool(tool mcp.Tool, hour int, env string) bool {
    // 维护工具仅在非工作时间
    maintenanceTools := map[string]bool{
        "backup_database":  true,
        "cleanup_logs":     true,
        "restart_service":  true,
    }
    
    if maintenanceTools[tool.Name] {
        return hour < 6 || hour > 22 // 仅在晚上10点到早上6点之间
    }
    
    // 调试工具仅在开发中
    debugTools := map[string]bool{
        "debug_session": true,
        "dump_state":    true,
    }
    
    if debugTools[tool.Name] {
        return env == "development"
    }
    
    return true
}

通知

发送服务器到客户端的消息以获取实时更新。

自定义通知

go
func handleLongRunningTool(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    srv := server.ServerFromContext(ctx)
    
    // 模拟长时间运行的工作
    for i := 0; i < 100; i++ {
        time.Sleep(100 * time.Millisecond)
        
        // 向所有客户端发送自定义通知
        notification := map[string]interface{}{
            "type":     "progress",
            "progress": i + 1,
            "total":    100,
            "message":  fmt.Sprintf("Processing step %d/100", i+1),
        }
        
        srv.SendNotificationToAllClients("progress", notification)
    }
    
    return mcp.NewToolResultText("Long operation completed successfully"), nil
}

自定义通知器

go
type CustomNotifier struct {
    sessions map[string]chan mcp.Notification
    mutex    sync.RWMutex
}

func NewCustomNotifier() *CustomNotifier {
    return &CustomNotifier{
        sessions: make(map[string]chan mcp.Notification),
    }
}

func (n *CustomNotifier) RegisterSession(sessionID string) {
    n.mutex.Lock()
    defer n.mutex.Unlock()
    
    n.sessions[sessionID] = make(chan mcp.Notification, 100)
}

func (n *CustomNotifier) UnregisterSession(sessionID string) {
    n.mutex.Lock()
    defer n.mutex.Unlock()
    
    if ch, exists := n.sessions[sessionID]; exists {
        close(ch)
        delete(n.sessions, sessionID)
    }
}

func (n *CustomNotifier) SendAlert(sessionID, message string, severity string) {
    n.mutex.RLock()
    defer n.mutex.RUnlock()
    
    if ch, exists := n.sessions[sessionID]; exists {
        select {
        case ch <- mcp.Notification{
            Type: "alert",
            Data: map[string]interface{}{
                "message":  message,
                "severity": severity,
                "timestamp": time.Now().Unix(),
            },
        }:
        default:
            // 通道已满,丢弃通知
        }
    }
}

func (n *CustomNotifier) BroadcastSystemMessage(message string) {
    n.mutex.RLock()
    defer n.mutex.RUnlock()
    
    notification := mcp.Notification{
        Type: "system_message",
        Data: map[string]interface{}{
            "message":   message,
            "timestamp": time.Now().Unix(),
        },
    }
    
    for _, ch := range n.sessions {
        select {
        case ch <- notification:
        default:
            // 通道已满,跳过此会话
        }
    }
}

生产配置

完整生产服务器

go
func main() {
    // 初始化组件
    logger := log.New(os.Stdout, "[MCP] ", log.LstdFlags)
    metrics := NewPrometheusMetrics()
    sessionManager := NewSessionManager()
    notifier := NewCustomNotifier()
    
    // 创建中间件
    loggingMW := NewLoggingMiddleware(logger)
    rateLimitMW := NewRateLimitMiddleware(10.0, 20) // 10 req/sec, burst 20
    authMW := NewAuthMiddleware(NewJWTValidator())
    
    // 创建钩子
    telemetryHooks := NewTelemetryHooks(metrics, logger)
    businessHooks := NewBusinessHooks(NewAuditLogger(), NewSlackNotifier())
    
    // 使用所有功能创建服务器
    s := server.NewMCPServer("Production Server", "1.0.0",
        server.WithToolCapabilities(true),
        server.WithResourceCapabilities(false, true),
        server.WithPromptCapabilities(true),
        server.WithRecovery(),
        server.WithHooks(telemetryHooks),
        server.WithToolHandlerMiddleware(loggingMW.ToolMiddleware),
        server.WithToolFilter(NewPermissionFilter(sessionManager)),
    )
    
    // 添加工具和资源
    addProductionTools(s)
    addProductionResources(s)
    addProductionPrompts(s)
    
    // 使用优雅关闭启动服务器
    startWithGracefulShutdown(s)
}

func startWithGracefulShutdown(s *server.MCPServer) {
    // 设置信号处理
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, os.Interrupt, syscall.Sigterm)
    
    // 在 goroutine 中启动服务器
    go func() {
        if err := server.ServeStdio(s); err != nil {
            log.Printf("Server error: %v", err)
        }
    }()
    
    // 等待关闭信号
    <-sigChan
    log.Println("Shutting down server...")
    
    // 带超时的优雅关闭
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    
    if err := s.Shutdown(ctx); err != nil {
        log.Printf("Shutdown error: %v", err)
    }
    
    log.Println("Server stopped")
}

基于客户端能力的过滤

go
package main

import (
    "context"
    "fmt"

    "github.com/mark3labs/mcp-go/mcp"
    "github.com/mark3labs/mcp-go/server"
)

func main() {
    s := server.NewMCPServer("Typed Server", "1.0.0",
        server.WithToolCapabilities(true),
    )

     s.AddTool(
        mcp.NewTool("calculate",
            mcp.WithDescription("执行基本数学计算"),
            mcp.WithString("operation",
                mcp.Required(),
                mcp.Enum("add", "subtract", "multiply", "divide"),
                mcp.Description("要执行的运算"),
            ),
            mcp.WithNumber("x", mcp.Required(), mcp.Description("第一个数字")),
            mcp.WithNumber("y", mcp.Required(), mcp.Description("第二个数字")),
        ),
        handleCalculate,
    )

    server.ServeStdio(s)
}

func handleCalculate(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    session := server.ClientSessionFromContext(ctx)
	if session == nil {
		return nil, fmt.Errorf("no active session")
	}

	if clientSession, ok := session.(server.SessionWithClientInfo); ok {
             clientCapabilities := clientSession.GetClientCapabilities()
			 if clientCapabilities.Sampling == nil {
				 fmt.Println("sampling is not enabled in client")
			 }
	}

    // TODO: implement calculation logic
    return mcp.NewToolResultError("not implemented"), nil
}

采样(高级)

采样是一种高级功能,允许服务器从客户端请求 LLM 完成。这实现了双向通信,服务器可以利用客户端的 LLM 功能进行内容生成、推理和问答。

注意:采样是一种高级功能,大多数服务器不需要。仅当您的服务器明确需要使用客户端的 LLM 生成内容时才实现采样。

何时使用采样

当您的服务器需要以下功能时考虑采样:

  • 基于用户输入生成内容
  • 使用 LLM 推理回答问题
  • 执行文本分析或摘要
  • 创建需要 LLM 功能的动态响应

基本实现

go
// 启用采样能力
mcpServer.EnableSampling()

// 添加使用采样的工具
mcpServer.AddTool(mcp.Tool{
    Name:        "ask_llm",
    Description: "使用采样向 LLM 提问",
    InputSchema: mcp.ToolInputSchema{
        Type: "object",
        Properties: map[string]any{
            "question": map[string]any{
                "type":        "string",
                "description": "要问 LLM 的问题",
            },
        },
        Required: []string{"question"},
    },
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    question, err := request.RequireString("question")
    if err != nil {
        return nil, err
    }
    
    // 创建采样请求
    samplingRequest := mcp.CreateMessageRequest{
        CreateMessageParams: mcp.CreateMessageParams{
            Messages: []mcp.SamplingMessage{
                {
                    Role: mcp.RoleUser,
                    Content: mcp.TextContent{
                        Type: "text",
                        Text: question,
                    },
                },
            },
            SystemPrompt: "You are a helpful assistant.",
            MaxTokens:    1000,
            Temperature:  0.7,
        },
    }
    
    // 从客户端请求采样
    result, err := mcpServer.RequestSampling(ctx, samplingRequest)
    if err != nil {
        return &mcp.CallToolResult{
            Content: []mcp.Content{
                mcp.TextContent{
                    Type: "text",
                    Text: fmt.Sprintf("Error: %v", err),
                },
            },
            IsError: true,
        }, nil
    }
    
    return &mcp.CallToolResult{
        Content: []mcp.Content{
            mcp.TextContent{
                Type: "text",
                Text: fmt.Sprintf("LLM Response: %s", result.Content),
            },
        },
    }, nil
})

其他内容类型

AudioContent

音频数据可以嵌入在提示词消息或工具结果中:

go
audioResult := &mcp.CallToolResult{
    Content: []mcp.Content{
        mcp.AudioContent{
            Type:     "audio",
            Data:     base64EncodedAudioData,
            MIMEType: "audio/wav",
        },
    },
}

AudioContent 字段:

  • Type — 必须为 "audio"
  • Data — base64 编码的音频数据
  • MIMEType — 音频的 MIME 类型(例如 "audio/wav""audio/mp3"

工具结果可以包含指向服务器资源的链接,而不是内联内容:

go
result := &mcp.CallToolResult{
    Content: []mcp.Content{
        mcp.ResourceLink{
            Type:        "resource_link",
            URI:         "files://reports/q4-summary.pdf",
            Name:        "Q4 Summary Report",
            Description: "季度财务摘要",
            MIMEType:    "application/pdf",
        },
    },
}

Roots 能力

启用 roots 能力以从连接的客户端请求根目录信息:

go
s := server.NewMCPServer("Server", "1.0.0",
    server.WithRoots(),
)

启用后,服务器在其能力中公布 roots 支持。支持 SessionWithRoots 接口的客户端可以提供服务器可用于文件操作、项目范围界定和类似用例的根目录信息。

有关完整的采样文档,请参阅**服务器采样指南**。

下一步