公告

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


Skip to content

客户端操作

学习如何使用 MCP 客户端通过工具、资源、提示词和订阅与服务端进行交互。

列出资源

资源提供对数据的只读访问。在读取资源之前,你通常需要发现可用的资源。

基本资源列表

go
import (
    "base64"
    "context"
    "encoding/json"
    "fmt"
    "log"
    "reflect"
    "regexp"
    "strings"
    "sync"
    "time"

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

func listResources(ctx context.Context, c client.Client) error {
    // 列出所有可用资源
    resources, err := c.ListResources(ctx, mcp.ListResourcesRequest{})
    if err != nil {
        return fmt.Errorf("failed to list resources: %w", err)
    }

    fmt.Printf("Available resources: %d\n", len(resources.Resources))
    for _, resource := range resources.Resources {
        fmt.Printf("- %s (%s): %s\n", 
            resource.URI, 
            resource.MIMEType, 
            resource.Name)
        
        if resource.Description != "" {
            fmt.Printf("  Description: %s\n", resource.Description)
        }
    }

    return nil
}

过滤资源列表

go
func listResourcesByType(ctx context.Context, c client.Client, mimeType string) ([]mcp.Resource, error) {
    resources, err := c.ListResources(ctx, mcp.ListResourcesRequest{})
    if err != nil {
        return nil, err
    }

    var filtered []mcp.Resource
    for _, resource := range resources.Resources {
        if resource.MIMEType == mimeType {
            filtered = append(filtered, resource)
        }
    }

    return filtered, nil
}

func listResourcesByPattern(ctx context.Context, c client.Client, pattern string) ([]mcp.Resource, error) {
    resources, err := c.ListResources(ctx, mcp.ListResourcesRequest{})
    if err != nil {
        return nil, err
    }

    regex, err := regexp.Compile(pattern)
    if err != nil {
        return nil, fmt.Errorf("invalid pattern: %w", err)
    }

    var filtered []mcp.Resource
    for _, resource := range resources.Resources {
        if regex.MatchString(resource.URI) {
            filtered = append(filtered, resource)
        }
    }

    return filtered, nil
}

// 使用示例
func demonstrateResourceFiltering(ctx context.Context, c client.Client) {
    // 查找所有 JSON 资源
    jsonResources, err := listResourcesByType(ctx, c, "application/json")
    if err != nil {
        log.Printf("Error listing JSON resources: %v", err)
    } else {
        fmt.Printf("Found %d JSON resources\n", len(jsonResources))
    }

    // 查找所有用户相关资源
    userResources, err := listResourcesByPattern(ctx, c, `users?://.*`)
    if err != nil {
        log.Printf("Error listing user resources: %v", err)
    } else {
        fmt.Printf("Found %d user resources\n", len(userResources))
    }
}

读取资源

一旦你知道有哪些可用资源,就可以读取它们的内容。

基本资源读取

go
func readResource(ctx context.Context, c client.Client, uri string) (*mcp.ReadResourceResult, error) {
    result, err := c.ReadResource(ctx, mcp.ReadResourceRequest{
        Params: mcp.ReadResourceRequestParams{
            URI: uri,
        },
    })
    if err != nil {
        return nil, fmt.Errorf("failed to read resource %s: %w", uri, err)
    }

    return result, nil
}

func demonstrateResourceReading(ctx context.Context, c client.Client) {
    // 首先列出资源
   resources, err := c.ListResources(ctx, mcp.ListResourcesRequest{})
    if err != nil {
        log.Printf("Failed to list resources: %v", err)
        return
    }

    // 读取每个资源
    for _, resource := range resources.Resources {
        fmt.Printf("\nReading resource: %s\n", resource.URI)
        
        result, err := readResource(ctx, c, resource.URI)
        if err != nil {
            log.Printf("Failed to read resource %s: %v", resource.URI, err)
            continue
        }

        // 处理资源内容
        for i, content := range result.Contents {
            fmt.Printf("Content %d:\n", i+1)
            fmt.Printf("  URI: %s\n", content.URI)
            fmt.Printf("  MIME Type: %s\n", content.MIMEType)
            
            if content.Text != "" {
                fmt.Printf("  Text: %s\n", truncateString(content.Text, 100))
            }
            
            if content.Blob != "" {
                fmt.Printf("  Blob: %d bytes\n", len(content.Blob))
            }
        }
    }
}

func truncateString(s string, maxLen int) string {
    if len(s) <= maxLen {
        return s
    }
    return s[:maxLen] + "..."
}

类型化资源读取

go
// 用于常见资源类型的辅助函数
func readJSONResource(ctx context.Context, c client.Client, uri string) (map[string]interface{}, error) {
    result, err := readResource(ctx, c, uri)
    if err != nil {
        return nil, err
    }

    if len(result.Contents) == 0 {
        return nil, fmt.Errorf("no content in resource")
    }

    content := result.Contents[0]
    if content.MIMEType != "application/json" {
        return nil, fmt.Errorf("expected JSON, got %s", content.MIMEType)
    }

    var data map[string]interface{}
    if err := json.Unmarshal([]byte(content.Text), &data); err != nil {
        return nil, fmt.Errorf("failed to parse JSON: %w", err)
    }

    return data, nil
}

func readTextResource(ctx context.Context, c client.Client, uri string) (string, error) {
    result, err := readResource(ctx, c, uri)
    if err != nil {
        return "", err
    }

    if len(result.Contents) == 0 {
        return "", fmt.Errorf("no content in resource")
    }

    content := result.Contents[0]
    if !strings.HasPrefix(content.MIMEType, "text/") {
        return "", fmt.Errorf("expected text, got %s", content.MIMEType)
    }

    return content.Text, nil
}

func readBinaryResource(ctx context.Context, c client.Client, uri string) ([]byte, error) {
    result, err := readResource(ctx, c, uri)
    if err != nil {
        return nil, err
    }

    if len(result.Contents) == 0 {
        return nil, fmt.Errorf("no content in resource")
    }

    content := result.Contents[0]
    if content.Blob == "" {
        return nil, fmt.Errorf("no binary data in resource")
    }

    data, err := base64.StdEncoding.DecodeString(content.Blob)
    if err != nil {
        return nil, fmt.Errorf("failed to decode binary data: %w", err)
    }

    return data, nil
}

资源缓存

go
type ResourceCache struct {
    cache map[string]cacheEntry
    mutex sync.RWMutex
    ttl   time.Duration
}

type cacheEntry struct {
    result    *mcp.ReadResourceResult
    timestamp time.Time
}

func NewResourceCache(ttl time.Duration) *ResourceCache {
    return &ResourceCache{
        cache: make(map[string]cacheEntry),
        ttl:   ttl,
    }
}

func (rc *ResourceCache) Get(uri string) (*mcp.ReadResourceResult, bool) {
    rc.mutex.RLock()
    defer rc.mutex.RUnlock()

    entry, exists := rc.cache[uri]
    if !exists || time.Since(entry.timestamp) > rc.ttl {
        return nil, false
    }

    return entry.result, true
}

func (rc *ResourceCache) Set(uri string, result *mcp.ReadResourceResult) {
    rc.mutex.Lock()
    defer rc.mutex.Unlock()

    rc.cache[uri] = cacheEntry{
        result:    result,
        timestamp: time.Now(),
    }
}

func (rc *ResourceCache) ReadResource(ctx context.Context, c client.Client, uri string) (*mcp.ReadResourceResult, error) {
    // 首先检查缓存
    if cached, found := rc.Get(uri); found {
        return cached, nil
    }

    // 从服务端读取
    result, err := readResource(ctx, c, uri)
    if err != nil {
        return nil, err
    }

    // 缓存结果
    rc.Set(uri, result)
    return result, nil
}

调用工具

工具提供可以用参数调用的功能。

基本工具调用

go
func callTool(ctx context.Context, c client.Client, name string, args map[string]interface{}) (*mcp.CallToolResult, error) {
    result, err := c.CallTool(ctx, mcp.CallToolRequest{
        Params: mcp.CallToolRequestParams{
            Name:      name,
            Arguments: args,
        },
    })
    if err != nil {
        return nil, fmt.Errorf("tool call failed: %w", err)
    }

    return result, nil
}

func demonstrateToolCalling(ctx context.Context, c client.Client) {
    // 列出可用的工具
    tools, err := c.ListTools(ctx, mcp.ListToolsRequest{})
    if err != nil {
        log.Printf("Failed to list tools: %v", err)
        return
    }

    fmt.Printf("Available tools: %d\n", len(tools.Tools))
    for _, tool := range tools.Tools {
        fmt.Printf("- %s: %s\n", tool.Name, tool.Description)
    }

    // 调用特定工具
    if len(tools.Tools) > 0 {
        tool := tools.Tools[0]
        fmt.Printf("\nCalling tool: %s\n", tool.Name)

        result, err := callTool(ctx, c, tool.Name, map[string]interface{}{
            "input": "example input",
            "format": "text",
        })
        if err != nil {
            log.Printf("Tool call failed: %v", err)
            return
        }

        fmt.Printf("Tool result:\n")
        for i, content := range result.Content {
            fmt.Printf("Content %d (%s): %s\n", i+1, content.Type, content.Text)
        }
    }
}

工具模式验证

go
func validateToolArguments(tool mcp.Tool, args map[string]interface{}) error {
    schema := tool.InputSchema
    
    // 检查必需的属性
    if schema.Required != nil {
        for _, required := range schema.Required {
            if _, exists := args[required]; !exists {
                return fmt.Errorf("missing required argument: %s", required)
            }
        }
    }

    // 验证参数类型
    if schema.Properties != nil {
        for name, value := range args {
            propSchema, exists := schema.Properties[name]
            if !exists {
                return fmt.Errorf("unknown argument: %s", name)
            }

            if err := validateValue(value, propSchema); err != nil {
                return fmt.Errorf("invalid argument %s: %w", name, err)
            }
        }
    }

    return nil
}

func validateValue(value interface{}, schema map[string]any) error {
    schemaType, ok := schema["type"].(string)
    if !ok {
        return fmt.Errorf("schema missing type")
    }
    
    switch schemaType {
    case "string":
        if _, ok := value.(string); !ok {
            return fmt.Errorf("expected string, got %T", value)
        }
    case "number":
        if _, ok := value.(float64); !ok {
            return fmt.Errorf("expected number, got %T", value)
        }
    case "integer":
        if _, ok := value.(float64); !ok {
            return fmt.Errorf("expected integer, got %T", value)
        }
    case "boolean":
        if _, ok := value.(bool); !ok {
            return fmt.Errorf("expected boolean, got %T", value)
        }
    case "array":
        if _, ok := value.([]interface{}); !ok {
            return fmt.Errorf("expected array, got %T", value)
        }
    case "object":
        if _, ok := value.(map[string]interface{}); !ok {
            return fmt.Errorf("expected object, got %T", value)
        }
    }

    return nil
}

func callToolWithValidation(ctx context.Context, c client.Client, toolName string, args map[string]interface{}) (*mcp.CallToolResult, error) {
    // 获取工具模式
    tools, err := c.ListTools(ctx, mcp.ListToolsRequest{})
    if err != nil {
        return nil, fmt.Errorf("failed to list tools: %w", err)
    }

    var tool *mcp.Tool
    for _, t := range tools.Tools {
        if t.Name == toolName {
            tool = &t
            break
        }
    }

    if tool == nil {
        return nil, fmt.Errorf("tool not found: %s", toolName)
    }

    // 验证参数
    if err := validateToolArguments(*tool, args); err != nil {
        return nil, fmt.Errorf("argument validation failed: %w", err)
    }

    // 调用工具
    return callTool(ctx, c, toolName, args)
}

批量工具操作

go
type ToolCall struct {
    Name      string
    Arguments map[string]interface{}
}

type ToolResult struct {
    Call   ToolCall
    Result *mcp.CallToolResult
    Error  error
}

func callToolsBatch(ctx context.Context, c client.Client, calls []ToolCall) []ToolResult {
    results := make([]ToolResult, len(calls))
    
    // 使用 goroutine 进行并发调用
    var wg sync.WaitGroup
    for i, call := range calls {
        wg.Add(1)
        go func(index int, toolCall ToolCall) {
            defer wg.Done()
            
            result, err := callTool(ctx, c, toolCall.Name, toolCall.Arguments)
            results[index] = ToolResult{
                Call:   toolCall,
                Result: result,
                Error:  err,
            }
        }(i, call)
    }
    
    wg.Wait()
    return results
}

func demonstrateBatchToolCalls(ctx context.Context, c client.Client) {
    calls := []ToolCall{
        {
            Name: "get_weather",
            Arguments: map[string]interface{}{
                "location": "New York",
            },
        },
        {
            Name: "get_weather",
            Arguments: map[string]interface{}{
                "location": "London",
            },
        },
        {
            Name: "calculate",
            Arguments: map[string]interface{}{
                "operation": "add",
                "x":         10,
                "y":         20,
            },
        },
    }

    results := callToolsBatch(ctx, c, calls)
    
    for i, result := range results {
        fmt.Printf("Call %d (%s):\n", i+1, result.Call.Name)
        if result.Error != nil {
            fmt.Printf("  Error: %v\n", result.Error)
        } else {
            fmt.Printf("  Success: %+v\n", result.Result)
        }
    }
}

使用提示词

提示词为 LLM 交互提供可重用的模板。

基本提示词使用

go
func getPrompt(ctx context.Context, c client.Client, name string, args map[string]interface{}) (*mcp.GetPromptResult, error) {
    result, err := c.GetPrompt(ctx, mcp.GetPromptRequest{
        Params: mcp.GetPromptRequestParams{
            Name:      name,
            Arguments: args,
        },
    })
    if err != nil {
        return nil, fmt.Errorf("failed to get prompt: %w", err)
    }

    return result, nil
}

func demonstratePromptUsage(ctx context.Context, c client.Client) {
    // 列出可用的提示词
    prompts, err := c.ListPrompts(ctx, mcp.ListPromptsRequest{})
    if err != nil {
        log.Printf("Failed to list prompts: %v", err)
        return
    }

    fmt.Printf("Available prompts: %d\n", len(prompts.Prompts))
    for _, prompt := range prompts.Prompts {
        fmt.Printf("- %s: %s\n", prompt.Name, prompt.Description)
        
        if len(prompt.Arguments) > 0 {
            fmt.Printf("  Arguments:\n")
            for _, arg := range prompt.Arguments {
                fmt.Printf("    - %s: %s\n", arg.Name, arg.Description)
            }
        }
    }

    // 使用特定提示词
    if len(prompts.Prompts) > 0 {
        prompt := prompts.Prompts[0]
        fmt.Printf("\nUsing prompt: %s\n", prompt.Name)

        result, err := getPrompt(ctx, c, prompt.Name, map[string]interface{}{
            // 根据提示词模式添加适当的参数
        })
        if err != nil {
            log.Printf("Failed to get prompt: %v", err)
            return
        }

        fmt.Printf("Prompt result:\n")
        fmt.Printf("Description: %s\n", result.Description)
        fmt.Printf("Messages: %d\n", len(result.Messages))
        
        for i, message := range result.Messages {
            fmt.Printf("Message %d (%s): %s\n", i+1, message.Role, message.Content.Text)
        }
    }
}

提示词模板处理

go
type PromptProcessor struct {
    client client.Client
}

func NewPromptProcessor(c client.Client) *PromptProcessor {
    return &PromptProcessor{client: c}
}

func (pp *PromptProcessor) ProcessPrompt(ctx context.Context, name string, args map[string]interface{}) ([]mcp.PromptMessage, error) {
    result, err := pp.client.GetPrompt(ctx, mcp.GetPromptRequest{
        Params: mcp.GetPromptRequestParams{
            Name:      name,
            Arguments: args,
        },
    })
    if err != nil {
        return nil, err
    }

    return result.Messages, nil
}

func (pp *PromptProcessor) BuildConversation(ctx context.Context, promptName string, args map[string]interface{}, userMessage string) ([]mcp.PromptMessage, error) {
    // 获取提示词模板
    messages, err := pp.ProcessPrompt(ctx, promptName, args)
    if err != nil {
        return nil, err
    }

    // 添加用户消息
    messages = append(messages, mcp.PromptMessage{
        Role: "user",
        Content: mcp.TextContent(userMessage),
    })

    return messages, nil
}

func (pp *PromptProcessor) FormatForLLM(messages []mcp.PromptMessage) []map[string]interface{} {
    formatted := make([]map[string]interface{}, len(messages))
    
    for i, message := range messages {
        formatted[i] = map[string]interface{}{
            "role":    message.Role,
            "content": message.Content.Text,
        }
    }
    
    return formatted
}

动态提示词生成

go
func generateCodeReviewPrompt(ctx context.Context, c client.Client, code, language string) ([]mcp.PromptMessage, error) {
    processor := NewPromptProcessor(c)
    
    return processor.ProcessPrompt(ctx, "code_review", map[string]interface{}{
        "code":     code,
        "language": language,
        "focus":    "best-practices",
    })
}

func generateDataAnalysisPrompt(ctx context.Context, c client.Client, datasetURI string, analysisType string) ([]mcp.PromptMessage, error) {
    processor := NewPromptProcessor(c)
    
    return processor.ProcessPrompt(ctx, "analyze_data", map[string]interface{}{
        "dataset_uri":   datasetURI,
        "analysis_type": analysisType,
        "focus_areas":   []string{"trends", "outliers", "correlations"},
    })
}

func demonstrateDynamicPrompts(ctx context.Context, c client.Client) {
    // 生成代码审查提示词
    codeReviewMessages, err := generateCodeReviewPrompt(ctx, c, 
        "func main() { fmt.Println(\"Hello\") }", 
        "go")
    if err != nil {
        log.Printf("Failed to generate code review prompt: %v", err)
    } else {
        fmt.Printf("Code review prompt: %d messages\n", len(codeReviewMessages))
    }

    // 生成数据分析提示词
    analysisMessages, err := generateDataAnalysisPrompt(ctx, c, 
        "dataset://sales_data", 
        "exploratory")
    if err != nil {
        log.Printf("Failed to generate analysis prompt: %v", err)
    } else {
        fmt.Printf("Data analysis prompt: %d messages\n", len(analysisMessages))
    }
}

订阅

某些传输方式支持订阅以接收实时通知。

基本订阅处理

go
func handleSubscriptions(ctx context.Context, c client.Client) {
    // 检查客户端是否支持订阅
    subscriber, ok := c.(client.Subscriber)
    if !ok {
        log.Println("Client does not support subscriptions")
        return
    }

    // 订阅通知
    notifications, err := subscriber.Subscribe(ctx)
    if err != nil {
        log.Printf("Failed to subscribe: %v", err)
        return
    }

    // 在后台处理通知
    for {
        select {
        case notification := <-notifications:
            handleNotification(notification)
        case <-ctx.Done():
            log.Println("Subscription cancelled")
            return
        }
    }
}

func handleNotification(notification mcp.Notification) {
    switch notification.Method {
    case "notifications/progress":
        handleProgressNotification(notification)
    case "notifications/message":
        handleMessageNotification(notification)
    case "notifications/resources/updated":
        handleResourceUpdateNotification(notification)
    case "notifications/tools/updated":
        handleToolUpdateNotification(notification)
    default:
        log.Printf("Unknown notification: %s", notification.Method)
    }
}

func handleProgressNotification(notification mcp.Notification) {
    var progress mcp.ProgressNotification
    if err := json.Unmarshal(notification.Params, &progress); err != nil {
        log.Printf("Failed to parse progress notification: %v", err)
        return
    }

    fmt.Printf("Progress: %d/%d - %s\n", 
        progress.Progress, 
        progress.Total, 
        progress.Message)
}

func handleMessageNotification(notification mcp.Notification) {
    var message mcp.MessageNotification
    if err := json.Unmarshal(notification.Params, &message); err != nil {
        log.Printf("Failed to parse message notification: %v", err)
        return
    }

    fmt.Printf("Server message: %s\n", message.Text)
}

func handleResourceUpdateNotification(notification mcp.Notification) {
    log.Println("Resources updated, refreshing cache...")
    // 使资源缓存失效或刷新资源列表
}

func handleToolUpdateNotification(notification mcp.Notification) {
    log.Println("Tools updated, refreshing tool list...")
    // 刷新工具列表
}

高级订阅管理

go
type SubscriptionManager struct {
    client        client.Client
    subscriber    client.Subscriber
    notifications chan mcp.Notification
    handlers      map[string][]NotificationHandler
    ctx           context.Context
    cancel        context.CancelFunc
    wg            sync.WaitGroup
    mutex         sync.RWMutex
}

type NotificationHandler func(mcp.Notification) error

func NewSubscriptionManager(c client.Client) (*SubscriptionManager, error) {
    subscriber, ok := c.(client.Subscriber)
    if !ok {
        return nil, fmt.Errorf("client does not support subscriptions")
    }

    ctx, cancel := context.WithCancel(context.Background())

    sm := &SubscriptionManager{
        client:     c,
        subscriber: subscriber,
        handlers:   make(map[string][]NotificationHandler),
        ctx:        ctx,
        cancel:     cancel,
    }

    return sm, nil
}

func (sm *SubscriptionManager) Start() error {
    notifications, err := sm.subscriber.Subscribe(sm.ctx)
    if err != nil {
        return fmt.Errorf("failed to subscribe: %w", err)
    }

    sm.notifications = notifications

    sm.wg.Add(1)
    go sm.handleNotifications()

    return nil
}

func (sm *SubscriptionManager) Stop() {
    sm.cancel()
    sm.wg.Wait()
}

func (sm *SubscriptionManager) AddHandler(method string, handler NotificationHandler) {
    sm.mutex.Lock()
    defer sm.mutex.Unlock()

    sm.handlers[method] = append(sm.handlers[method], handler)
}

func (sm *SubscriptionManager) RemoveHandler(method string, handler NotificationHandler) {
    sm.mutex.Lock()
    defer sm.mutex.Unlock()

    handlers := sm.handlers[method]
    for i, h := range handlers {
        if reflect.ValueOf(h).Pointer() == reflect.ValueOf(handler).Pointer() {
            sm.handlers[method] = append(handlers[:i], handlers[i+1:]...)
            break
        }
    }
}

func (sm *SubscriptionManager) handleNotifications() {
    defer sm.wg.Done()

    for {
        select {
        case notification := <-sm.notifications:
            sm.processNotification(notification)
        case <-sm.ctx.Done():
            return
        }
    }
}

func (sm *SubscriptionManager) processNotification(notification mcp.Notification) {
    sm.mutex.RLock()
    handlers := sm.handlers[notification.Method]
    sm.mutex.RUnlock()

    for _, handler := range handlers {
        if err := handler(notification); err != nil {
            log.Printf("Handler error for %s: %v", notification.Method, err)
        }
    }
}

// 使用示例
func demonstrateSubscriptionManager(c client.Client) {
    sm, err := NewSubscriptionManager(c)
    if err != nil {
        log.Printf("Failed to create subscription manager: %v", err)
        return
    }

    // 添加处理器
    sm.AddHandler("notifications/progress", func(n mcp.Notification) error {
        log.Printf("Progress notification: %+v", n)
        return nil
    })

    sm.AddHandler("notifications/message", func(n mcp.Notification) error {
        log.Printf("Message notification: %+v", n)
        return nil
    })

    // 开始处理
    if err := sm.Start(); err != nil {
        log.Printf("Failed to start subscription manager: %v", err)
        return
    }

    // 运行一段时间
    time.Sleep(30 * time.Second)

    // 停止
    sm.Stop()
}

高级:采样支持

采样是一项高级功能,允许客户端响应来自服务端的 LLM 完成请求。这使得服务端能够利用客户端端的 LLM 能力进行内容生成和推理。

注意:采样是一项大多数客户端不需要的高级功能。只有在构建向服务端提供 LLM 能力的客户端时才需要实现采样。

何时实现采样

在以下情况下考虑实现采样:

  • 客户端有权访问 LLM API(OpenAI、Anthropic 等)
  • 希望向服务端提供 LLM 能力
  • 需要支持生成动态内容的服务端

基本实现

go
import "github.com/mark3labs/mcp-go/client"

// 实现 SamplingHandler 接口
type MySamplingHandler struct {
    // 在这里添加你的 LLM 客户端
}

func (h *MySamplingHandler) CreateMessage(ctx context.Context, request mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
    // 使用你的 LLM 处理请求
    // 以 MCP 格式返回结果
    return &mcp.CreateMessageResult{
        Model: "your-model",
        Role:  mcp.RoleAssistant,
        Content: mcp.TextContent{
            Type: "text",
            Text: "Your LLM response here",
        },
        StopReason: "endTurn",
    }, nil
}

// 使用采样支持创建客户端
mcpClient, err := client.NewStdioClient(
    "/path/to/server",
    client.WithSamplingHandler(&MySamplingHandler{}),
)

有关完整的采样文档,请参阅 客户端采样指南

使用迭代器的流式分页

MCP 服务端可能返回跨多个页面的较大结果集(工具、资源、资源模板或提示词)。标准的 ListTools / ListResources / ListPrompts / ListResourceTemplates 方法自动获取每一页并返回聚合切片 —— 方便,但在只需要几个项目或结果集非常大时会浪费资源。

对于这些情况,客户端还暴露了 Go 1.23 迭代器方法,按需懒加载获取页面:

  • IterTools(ctx, mcp.ListToolsRequest) iter.Seq2[mcp.Tool, error]
  • IterResources(ctx, mcp.ListResourcesRequest) iter.Seq2[mcp.Resource, error]
  • IterResourceTemplates(ctx, mcp.ListResourceTemplatesRequest) iter.Seq2[mcp.ResourceTemplate, error]
  • IterPrompts(ctx, mcp.ListPromptsRequest) iter.Seq2[mcp.Prompt, error]

每个迭代器一次产生一个项目,只有当当前页面耗尽时才获取下一页。退出 range 循环会停止进一步请求,因此提前退出是廉价的。

::::note 这些方法定义在具体的 *client.Client 类型上,而不是 MCPClient 接口上。下面的示例使用 c *client.Client 而不是你将在本指南其他部分看到的 c client.Client。如果你持有 MCPClient 接口值,请使用双值类型断言,这样不匹配的实现不会 panic:

go
cc, ok := c.(*client.Client)
if !ok {
    return fmt.Errorf("iterators require *client.Client, got %T", c)
}
// 在这里可以安全调用 cc.IterTools / cc.IterResources / ...

在本节的其余部分中,c 直接指 *client.Client —— 当你通过接口到达时,替换为 cc(或你命名的断言值)。 ::::

基本用法

go
func printToolNames(ctx context.Context, c *client.Client) error {
    for tool, err := range c.IterTools(ctx, mcp.ListToolsRequest{}) {
        if err != nil {
            return fmt.Errorf("list tools: %w", err)
        }
        fmt.Println(tool.Name)
    }
    return nil
}

提前退出

迭代器在循环终止时立即停止获取页面,这对于搜索或首次匹配查找很有用:

go
func findTool(ctx context.Context, c *client.Client, name string) (*mcp.Tool, error) {
    for tool, err := range c.IterTools(ctx, mcp.ListToolsRequest{}) {
        if err != nil {
            return nil, err
        }
        if tool.Name == name {
            return &tool, nil // 不再获取更多页面
        }
    }
    return nil, fmt.Errorf("tool %q not found", name)
}

错误处理和取消

如果页面获取失败,或者上下文在页面之间被取消,迭代器会产生一个 (零值, err) 对然后停止。始终在每次迭代时检查 err

go
func processResources(ctx context.Context, c *client.Client) error {
    for resource, err := range c.IterResources(ctx, mcp.ListResourcesRequest{}) {
        if err != nil {
            // ctx.Err() 或传输/服务端错误 —— 迭代器现在已完成
            return err
        }
        process(resource)
    }
    return nil
}

何时使用哪种方法

方法行为适用于
ListTools / ListResources / ...自动获取所有页面,返回聚合切片。小结果集,当你需要完整列表时。
ListToolsByPage / ListResourcesByPage / ...获取单个页面;调用者管理游标。自定义分页 UI,手动游标控制。
IterTools / IterResources / ...按需懒加载获取页面。大结果集、搜索、提前退出查找。

如上所述,迭代器方法位于具体的 *client.Client 类型而不是 MCPClient 接口上,因此接口对于现有实现者保持向后兼容。

下一步