Skip to content

[go-fan] Go Module Review: go-sdk - CRITICAL v1.2.0 Performance Upgrade Available #434

@github-actions

Description

@github-actions

🐹 Go Fan Report: MCP Go SDK

Module Overview

github.com/modelcontextprotocol/go-sdk is the official Go SDK for the Model Context Protocol (MCP), maintained by Anthropic in collaboration with Google. It provides comprehensive support for building MCP clients and servers with multiple transport protocols.

Repository: https://github.com/modelcontextprotocol/go-sdk
Current Version: v1.1.0
Latest Version: v1.2.0 (December 2025)
Last Repository Update: 2026-01-22 ⚡ (yesterday!)

Current Usage in gh-aw-mcpg

Usage Statistics

  • Files: 3 primary implementation files, plus testing infrastructure
  • Import Count: 22+ usages of SDK APIs
  • Key APIs Used: Client/Server creation, Transport abstractions, Tool registration, Session management

Implementation Patterns

1. Client Implementation (internal/mcp/connection.go)

Uses the SDK for all MCP client operations:

import sdk "github.com/modelcontextprotocol/go-sdk/mcp"

client := sdk.NewClient(&sdk.Implementation{...}, nil)
session, err := client.Connect(ctx, transport, nil)

Supports three transport types:

  • sdk.CommandTransport - stdio-based servers
  • sdk.StreamableClientTransport - HTTP (2025-03-26 spec)
  • sdk.SSEClientTransport - SSE (2024-11-05 spec)

2. Server Implementation (internal/server/)

Unified Mode (1 server):

server := sdk.NewServer(&sdk.Implementation{Name: "awmg", Version: "1.0.0"}, nil)
// All tools registered with backend prefixes

Routed Mode (N servers - KEY FINDING 🎯):

func createFilteredServer(unifiedServer *UnifiedServer, backendID string) *sdk.Server {
    // Creates NEW server instance per backend/session
    server := sdk.NewServer(&sdk.Implementation{
        Name:    fmt.Sprintf("awmg-%s", backendID),
        Version: "1.0.0",
    }, nil)
    
    // Re-registers tools for this backend
    for _, toolInfo := range tools {
        server.AddTool(&sdk.Tool{
            Name:        toolInfo.Name,
            Description: toolInfo.Description,
            InputSchema: toolInfo.InputSchema,
        }, wrappedHandler)
    }
    return server
}

This is exactly the stateless server pattern that was suffering from 70-80% performance degradation without schema caching!

Research Findings

Recent Updates (Last 7 Days)

🚨 CRITICAL: Schema Caching Performance Fix (2026-01-21)

PR #685 added automatic schema caching that dramatically improves performance for stateless server patterns.

The Problem:
In stateless deployments (like gh-aw-mcpg's routed mode), creating new sdk.Server instances and calling AddTool repeatedly was causing:

  • Expensive jsonschema.ForType() reflection on every call
  • Schema re-parsing and re-resolution on every request
  • 70-80% latency regression
  • 3.4x more memory allocations

The Solution:
Global concurrent-safe schema cache using sync.Map:

  • Caches schemas by reflect.Type for typed handlers
  • Caches resolved schemas by pointer for pre-defined schemas
  • Zero code changes required - automatic benefit

Benchmark Results:

Metric Without Cache With Cache Improvement
Time 161,463 ns/op 1,223 ns/op 132x faster
Allocations 1,072 allocs 21 allocs 51x fewer 🎯
Memory 39,262 B/op 1,208 B/op 32x less 💾

Real-World Results (github-mcp-server with 130 tools):

  • initialize: 20.47ms → 10.36ms (50% faster)
  • tools/list: 22.98ms → 14.15ms (38% faster)
  • Memory allocations: 1208.70 MB → baseline (3.4x reduction)

🔧 HTTP Compliance Fix (2026-01-22)

PR #757 added Allow header to 405 responses per RFC 9110 §15.5.6:

  • Fixes issues with strict HTTP gateways (like Apigee) returning 502 Bad Gateway errors
  • Ensures all 405 Method Not Allowed responses include proper Allow header
  • Zero code changes required

Other Notable Improvements in v1.2.0

  • Exported GetError/SetError (#753) - Better error handling API
  • DisableListening option (#729) - Control standalone SSE streams
  • Content-Type validation (#736) - Prevent infinite redirect loops
  • Capability configuration (#713) - Fine-grained control over capabilities
  • Debounce notifications (#717) - Batch server change notifications

Best Practices

From the SDK repository and recent changes:

  1. Stateless Server Pattern: Create new sdk.Server per request and rely on schema caching (now efficient!)
  2. Transport Selection: Use StreamableClientTransport for modern HTTP, SSEClientTransport for compatibility
  3. Error Handling: Use typed errors from jsonrpc package, leverage GetError/SetError APIs
  4. Capabilities: Configure via ServerOptions.Capabilities and ClientOptions.Capabilities
  5. Tool Registration: Use sdk.AddTool() for typed handlers with automatic schema inference

Improvement Opportunities

🏃 Quick Wins

1. ⚡ CRITICAL: Upgrade to v1.2.0 Immediately

Impact: HIGH - 50% faster initialization, 38% faster tool listing, 3.4x less memory
Effort: LOW - Single line change in go.mod
Risk: LOW - SDK maintains backward compatibility

Why This Matters for gh-aw-mcpg:

The routed mode implementation in internal/server/routed.go creates new sdk.Server instances dynamically:

  • 1 server instance per backend per session
  • Each server re-registers all tools with AddTool()
  • Without schema caching: expensive reflection on every server creation
  • With schema caching: cached lookups after first registration

Expected Impact:

  • Faster client connections in routed mode
  • Reduced memory pressure under concurrent load
  • Better scalability with many backends/sessions
  • Improved latency for all MCP operations

Action:

# Update go.mod
go get github.com/modelcontextprotocol/go-sdk@v1.2.0
go mod tidy

# Test both modes
make test-all

2. 🔐 Use HTTP 405 Compliance Fixes

Impact: Prevents potential 502 errors from strict HTTP gateways
Effort: Zero - automatic with v1.2.0 upgrade
Risk: None

The RFC 9110 compliance ensures the gateway works correctly with enterprise proxies and API gateways like Apigee that enforce strict HTTP standards.

✨ Feature Opportunities

1. Leverage DisableListening Option

Benefit: Reduce resource usage for request-response patterns

transport := &sdk.StreamableClientTransport{
    URL:              url,
    HTTPClient:       httpClient,
    DisableListening: true,  // NEW: Disable standalone SSE if not needed
}

Use Case: When MCP servers don't send server-initiated notifications (like tool/prompt updates), this eliminates the persistent SSE connection overhead.

Consideration: Analyze which backend servers actually send notifications vs. pure request-response.

2. Use Capability Configuration API

Benefit: Fine-grained control over server capabilities and notifications

server := sdk.NewServer(&sdk.Implementation{...}, &sdk.ServerOptions{
    Capabilities: &sdk.ServerCapabilities{
        ListChanged: false,  // Suppress change notifications if not needed
    },
})

Use Case: In routed mode where servers are ephemeral per session, suppressing ListChanged notifications may reduce overhead.

3. Export GetError/SetError for Better Error Handling

Benefit: More idiomatic error handling with SDK types

// New in v1.2.0
result, err := session.CallTool(ctx, params)
if wireErr := sdk.GetError(err); wireErr != nil {
    log.Printf("MCP error code: %d, message: %s", wireErr.Code, wireErr.Message)
}

Use Case: Improve error logging and debugging by extracting structured error information.

📐 Best Practice Alignment

1. Review Schema Normalization Logic

Current: Custom NormalizeInputSchema() in internal/mcp/types.go
Consideration: With v1.2.0's schema caching, test if custom normalization is still necessary

The SDK's schema handling has improved significantly. Consider:

  1. Testing if SDK handles missing schemas gracefully with v1.2.0
  2. Simplifying or removing custom normalization if SDK covers all cases
  3. Moving normalization into tool registration if still needed

2. Optimize Routed Mode Server Caching

Current: Cache servers by backend/session key
With v1.2.0: Server creation is much cheaper (132x faster)

Consider:

  • Keep caching: Still beneficial for maintaining per-session state
  • Simplify logic: Less critical with new schema cache performance
  • Monitor metrics: Measure actual benefit of server-level caching

3. Document Transport Selection Strategy

Current: Uses StreamableClientTransport by default
Opportunity: Document when to use each transport type

With three transport options, document the rationale:

  • StreamableHTTP: Modern (2025-03-26), stateful, best performance
  • SSE: Compatible (2024-11-05), server-initiated messages
  • Stdio: Local processes only

🔧 General Improvements

1. Compare Unified vs Routed Mode Performance

With v1.2.0, the performance characteristics have changed:

  • Unified Mode: 1 server at startup → minimal benefit (one-time cost)
  • Routed Mode: N servers dynamically → major benefit (repeated cost eliminated)

Action: Benchmark both modes with v1.2.0 to quantify the improvement.

2. Add Performance Metrics

Monitor:

  • Server creation time (routed mode)
  • Tool registration time
  • Memory allocation patterns
  • Request latency percentiles

This validates the expected improvements from v1.2.0.

Recommendations

✅ Priority 1: Upgrade to v1.2.0 IMMEDIATELY

Impact: High performance improvement for routed mode
Risk: Low - backward compatible
Action: Update go.mod, run full test suite, benchmark both modes

🔍 Priority 2: Test DisableListening Option

Impact: Potential resource savings
Risk: Low - feature flag
Action: Experiment with backends that don't send notifications

📊 Priority 3: Monitor Real-World Performance

Impact: Validate improvements
Risk: None
Action: Add metrics for server creation, tool registration, memory usage

🧹 Priority 4: Review Schema Normalization

Impact: Code simplification
Risk: Low - has test coverage
Action: Test if custom normalization is still needed with v1.2.0

Next Steps

  1. Immediate (This Week):

    • Upgrade to v1.2.0
    • Run make test-all to validate
    • Benchmark routed mode performance
  2. Short-term (Next Sprint):

    • Add performance metrics
    • Test DisableListening option
    • Measure memory allocation improvements
  3. Medium-term (Next Month):

    • Review schema normalization necessity
    • Experiment with capability configuration APIs
    • Document transport selection strategy
  4. Long-term (Ongoing):

    • Monitor SDK releases for new features
    • Track MCP spec updates
    • Optimize based on real-world metrics

References:

Analysis saved to: /tmp/gh-aw/cache-memory/go-sdk-review-2026-01-23.md

AI generated by Go Fan

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions