MCP Client Usage Guide

The MCP client provides a simple and intuitive way to interact with MCP servers. This guide will walk you through initializing the client, connecting to a server, and using various MCP features.

Installation

Add the MCP Golang package to your project:

go get github.com/metoro-io/mcp-golang

Basic Usage

Here’s a simple example of creating and initializing an MCP client:

import (
    mcp "github.com/metoro-io/mcp-golang"
    "github.com/metoro-io/mcp-golang/transport/stdio"
)

// Create a transport (stdio in this example)
transport := stdio.NewStdioServerTransportWithIO(stdout, stdin)

// Create a new client
client := mcp.NewClient(transport)

// Initialize the client
response, err := client.Initialize(context.Background())
if err != nil {
    log.Fatalf("Failed to initialize client: %v", err)
}

Working with Tools

Listing Available Tools

tools, err := client.ListTools(context.Background(), nil)
if err != nil {
    log.Fatalf("Failed to list tools: %v", err)
}

for _, tool := range tools.Tools {
    log.Printf("Tool: %s. Description: %s", tool.Name, *tool.Description)
}

Calling a Tool

// Define a type-safe struct for your tool arguments
type CalculateArgs struct {
    Operation string `json:"operation"`
    A         int    `json:"a"`
    B         int    `json:"b"`
}

// Create typed arguments
args := CalculateArgs{
    Operation: "add",
    A:         10,
    B:         5,
}

response, err := client.CallTool(context.Background(), "calculate", args)
if err != nil {
    log.Printf("Failed to call tool: %v", err)
}

// Handle the response
if response != nil && len(response.Content) > 0 {
    if response.Content[0].TextContent != nil {
        log.Printf("Response: %s", response.Content[0].TextContent.Text)
    }
}

Working with Prompts

Listing Available Prompts

prompts, err := client.ListPrompts(context.Background(), nil)
if err != nil {
    log.Printf("Failed to list prompts: %v", err)
}

for _, prompt := range prompts.Prompts {
    log.Printf("Prompt: %s. Description: %s", prompt.Name, *prompt.Description)
}

Using a Prompt

// Define a type-safe struct for your prompt arguments
type PromptArgs struct {
    Input string `json:"input"`
}

// Create typed arguments
args := PromptArgs{
    Input: "Hello, MCP!",
}

response, err := client.GetPrompt(context.Background(), "prompt_name", args)
if err != nil {
    log.Printf("Failed to get prompt: %v", err)
}

if response != nil && len(response.Messages) > 0 {
    log.Printf("Response: %s", response.Messages[0].Content.TextContent.Text)
}

Working with Resources

Listing Resources

resources, err := client.ListResources(context.Background(), nil)
if err != nil {
    log.Printf("Failed to list resources: %v", err)
}

for _, resource := range resources.Resources {
    log.Printf("Resource: %s", resource.Uri)
}

Reading a Resource

resource, err := client.ReadResource(context.Background(), "resource_uri")
if err != nil {
    log.Printf("Failed to read resource: %v", err)
}

if resource != nil {
    log.Printf("Resource content: %s", resource.Content)
}

Pagination

Both ListTools and ListPrompts support pagination. You can pass a cursor to get the next page of results:

var cursor *string
for {
    tools, err := client.ListTools(context.Background(), cursor)
    if err != nil {
        log.Fatalf("Failed to list tools: %v", err)
    }

    // Process tools...

    if tools.NextCursor == nil {
        break // No more pages
    }
    cursor = tools.NextCursor
}

Error Handling

The client includes comprehensive error handling. All methods return an error as their second return value:

response, err := client.CallTool(context.Background(), "calculate", args)
if err != nil {
    switch {
    case errors.Is(err, mcp.ErrClientNotInitialized):
        // Handle initialization error
    default:
        // Handle other errors
    }
}

Best Practices

  1. Always initialize the client before making any calls
  2. Use appropriate context management for timeouts and cancellation
  3. Handle errors appropriately for your use case
  4. Close or clean up resources when done
  5. Define type-safe structs for tool and prompt arguments
  6. Use struct tags to ensure correct JSON field names

Complete Example

For a complete working example, check out our example client implementation.

Transport Options

The MCP client supports multiple transport options:

Standard I/O Transport

For command-line tools that communicate through stdin/stdout:

transport := stdio.NewStdioClientTransport()
client := mcp.NewClient(transport)

This transport supports all MCP features including bidirectional communication and notifications.

HTTP Transport

For web-based tools that communicate over HTTP/HTTPS:

transport := http.NewHTTPClientTransport("/mcp")
transport.WithBaseURL("http://localhost:8080")
client := mcp.NewClient(transport)

Note that the HTTP transport is stateless and does not support bidirectional features like notifications. Each request-response cycle is independent, making it suitable for simple tool invocations but not for scenarios requiring real-time updates or persistent connections.

Context Support

All client operations now support context propagation:

ctx := context.Background()
// With timeout
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

// Call tool with context
response, err := client.CallTool(ctx, "tool-name", args)
if err != nil {
    // Handle error
}

// List tools with context
tools, err := client.ListTools(ctx)
if err != nil {
    // Handle error
}

The context allows you to:

  • Set timeouts for operations
  • Cancel long-running operations
  • Pass request-scoped values
  • Implement tracing and monitoring