Client Operations
Learn how to use MCP clients to interact with servers through tools, resources, prompts, and subscriptions.
Listing Resources
Resources provide read-only access to data. Before reading resources, you typically need to discover what's available.
Basic Resource Listing
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 {
// List all available resources
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
}Filtered Resource Listing
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
}
// Usage examples
func demonstrateResourceFiltering(ctx context.Context, c client.Client) {
// Find all JSON resources
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))
}
// Find all user-related resources
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))
}
}Reading Resources
Once you know what resources are available, you can read their content.
Basic Resource Reading
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) {
// List resources first
resources, err := c.ListResources(ctx, mcp.ListResourcesRequest{})
if err != nil {
log.Printf("Failed to list resources: %v", err)
return
}
// Read each resource
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
}
// Process resource contents
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] + "..."
}Typed Resource Reading
// Helper functions for common resource types
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
}Resource Caching
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) {
// Check cache first
if cached, found := rc.Get(uri); found {
return cached, nil
}
// Read from server
result, err := readResource(ctx, c, uri)
if err != nil {
return nil, err
}
// Cache the result
rc.Set(uri, result)
return result, nil
}Calling Tools
Tools provide functionality that can be invoked with parameters.
Basic Tool Calling
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) {
// List available tools
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)
}
// Call a specific tool
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)
}
}
}Tool Schema Validation
func validateToolArguments(tool mcp.Tool, args map[string]interface{}) error {
schema := tool.InputSchema
// Check required properties
if schema.Required != nil {
for _, required := range schema.Required {
if _, exists := args[required]; !exists {
return fmt.Errorf("missing required argument: %s", required)
}
}
}
// Validate argument types
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) {
// Get tool schema
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)
}
// Validate arguments
if err := validateToolArguments(*tool, args); err != nil {
return nil, fmt.Errorf("argument validation failed: %w", err)
}
// Call tool
return callTool(ctx, c, toolName, args)
}Batch Tool Operations
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))
// Use goroutines for concurrent calls
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)
}
}
}Using Prompts
Prompts provide reusable templates for LLM interactions.
Basic Prompt Usage
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) {
// List available prompts
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)
}
}
}
// Use a specific prompt
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{}{
// Add appropriate arguments based on prompt schema
})
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)
}
}
}Prompt Template Processing
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) {
// Get prompt template
messages, err := pp.ProcessPrompt(ctx, promptName, args)
if err != nil {
return nil, err
}
// Add user message
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
}Dynamic Prompt Generation
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) {
// Generate code review prompt
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))
}
// Generate data analysis prompt
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))
}
}Subscriptions
Some transports support subscriptions for receiving real-time notifications.
Basic Subscription Handling
func handleSubscriptions(ctx context.Context, c client.Client) {
// Check if client supports subscriptions
subscriber, ok := c.(client.Subscriber)
if !ok {
log.Println("Client does not support subscriptions")
return
}
// Subscribe to notifications
notifications, err := subscriber.Subscribe(ctx)
if err != nil {
log.Printf("Failed to subscribe: %v", err)
return
}
// Handle notifications
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...")
// Invalidate resource cache or refresh resource list
}
func handleToolUpdateNotification(notification mcp.Notification) {
log.Println("Tools updated, refreshing tool list...")
// Refresh tool list
}Advanced Subscription Management
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)
}
}
}
// Usage example
func demonstrateSubscriptionManager(c client.Client) {
sm, err := NewSubscriptionManager(c)
if err != nil {
log.Printf("Failed to create subscription manager: %v", err)
return
}
// Add handlers
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
})
// Start handling
if err := sm.Start(); err != nil {
log.Printf("Failed to start subscription manager: %v", err)
return
}
// Let it run for a while
time.Sleep(30 * time.Second)
// Stop
sm.Stop()
}Advanced: Sampling Support
Sampling is an advanced feature that allows clients to respond to LLM completion requests from servers. This enables servers to leverage client-side LLM capabilities for content generation and reasoning.
Note: Sampling is an advanced feature that most clients don't need. Only implement sampling if you're building a client that provides LLM capabilities to servers.
When to Implement Sampling
Consider implementing sampling when your client:
- Has access to LLM APIs (OpenAI, Anthropic, etc.)
- Wants to provide LLM capabilities to servers
- Needs to support servers that generate dynamic content
Basic Implementation
import "github.com/mark3labs/mcp-go/client"
// Implement the SamplingHandler interface
type MySamplingHandler struct {
// Add your LLM client here
}
func (h *MySamplingHandler) CreateMessage(ctx context.Context, request mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
// Process the request with your LLM
// Return the result in MCP format
return &mcp.CreateMessageResult{
Model: "your-model",
Role: mcp.RoleAssistant,
Content: mcp.TextContent{
Type: "text",
Text: "Your LLM response here",
},
StopReason: "endTurn",
}, nil
}
// Create client with sampling support
mcpClient, err := client.NewStdioClient(
"/path/to/server",
client.WithSamplingHandler(&MySamplingHandler{}),
)For complete sampling documentation, see Client Sampling Guide.
Streaming Pagination with Iterators
MCP servers may return large result sets (tools, resources, resource templates,
or prompts) across multiple pages. The standard ListTools / ListResources /
ListPrompts / ListResourceTemplates methods auto-fetch every page and return
an aggregated slice — convenient, but wasteful when you only need a few items
or when the result set is very large.
For these cases, the client also exposes Go 1.23 iterator methods that lazily fetch pages on demand:
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]
Each iterator yields one item at a time and only fetches the next page when the
current one is exhausted. Breaking out of the range stops further requests,
so early-exit is cheap.
Basic Usage
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
}Early Exit
The iterator stops fetching pages as soon as the loop terminates, which is useful for searches or first-match lookups:
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 // no further pages are fetched
}
}
return nil, fmt.Errorf("tool %q not found", name)
}Error Handling and Cancellation
If a page fetch fails, or the context is cancelled between pages, the iterator
yields a single (zero-value, err) pair and then stops. Always check err on
each iteration:
func processResources(ctx context.Context, c *client.Client) error {
for resource, err := range c.IterResources(ctx, mcp.ListResourcesRequest{}) {
if err != nil {
// ctx.Err() or a transport / server error — iterator is now done.
return err
}
process(resource)
}
return nil
}When to Use Which
| Method | Behavior | Best for |
|---|---|---|
ListTools / ListResources / ... | Auto-fetches all pages, returns an aggregated slice. | Small result sets, when you need the full list. |
ListToolsByPage / ListResourcesByPage / ... | Fetches a single page; caller manages cursors. | Custom pagination UI, manual cursor control. |
IterTools / IterResources / ... | Lazily fetches pages as the iterator is consumed. | Large result sets, searches, early-exit lookups. |
As mentioned above, the iterator methods live on the concrete *client.Client
type rather than the MCPClient interface, so the interface remains
backwards-compatible for existing implementers.
Next Steps
- Client Transports - Learn transport-specific client features
- Client Basics - Review fundamental concepts
