客户端操作
学习如何使用 MCP 客户端通过工具、资源、提示词和订阅与服务端进行交互。
列出资源
资源提供对数据的只读访问。在读取资源之前,你通常需要发现可用的资源。
基本资源列表
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
}过滤资源列表
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))
}
}读取资源
一旦你知道有哪些可用资源,就可以读取它们的内容。
基本资源读取
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] + "..."
}类型化资源读取
// 用于常见资源类型的辅助函数
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
}资源缓存
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
}调用工具
工具提供可以用参数调用的功能。
基本工具调用
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)
}
}
}工具模式验证
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)
}批量工具操作
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 交互提供可重用的模板。
基本提示词使用
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)
}
}
}提示词模板处理
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
}动态提示词生成
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))
}
}订阅
某些传输方式支持订阅以接收实时通知。
基本订阅处理
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...")
// 刷新工具列表
}高级订阅管理
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 能力
- 需要支持生成动态内容的服务端
基本实现
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:
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(或你命名的断言值)。 ::::
基本用法
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
}提前退出
迭代器在循环终止时立即停止获取页面,这对于搜索或首次匹配查找很有用:
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:
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 接口上,因此接口对于现有实现者保持向后兼容。

