Skip to content

[go-fan] Go Module Review: modelcontextprotocol/go-sdkΒ #2911

@github-actions

Description

@github-actions

🐹 Go Fan Report: modelcontextprotocol/go-sdk

Module Overview

github.com/modelcontextprotocol/go-sdk (v1.4.1) is the official Go implementation of the Model Context Protocol. It is the single most critical dependency in this repository β€” the entire gateway is built on top of it. It provides server construction, client management, typed protocol operations (tools, resources, prompts), and multiple transport implementations (stdio, streamable HTTP, SSE, in-memory).

The project imports it under the alias sdk "github.com/modelcontextprotocol/go-sdk/mcp" consistently across 26 files (12 production, 14 test).

Current Usage in gh-aw-mcpg

The SDK is used at every layer of the gateway stack:

  • Files: 26 total (12 production, 14 test)
  • Import Count: All production usages use a consistent sdk alias
  • Key APIs Used:
    • sdk.NewServer / server.AddTool β€” Gateway-side MCP server construction and tool registration
    • sdk.NewClient / sdk.ClientSession.* β€” Backend MCP client connections and typed protocol calls
    • sdk.NewStreamableHTTPHandler β€” Streamable HTTP transport hosting (both unified and routed modes)
    • sdk.CommandTransport, sdk.StreamableClientTransport, sdk.SSEClientTransport β€” Backend transport selection
    • sdk.NewInMemoryTransports β€” In-memory transport pairs for unit testing
    • sdk.CallToolResult, sdk.TextContent, sdk.ImageContent, sdk.AudioContent, sdk.EmbeddedResource β€” Full MCP content type coverage
    • sdk.ServerOptions / sdk.ClientOptions / sdk.StreamableHTTPOptions β€” Logger integration via slog adapter

Notable Usage Pattern: server.AddTool vs sdk.AddTool

The project consistently uses the method form server.AddTool(tool, handler) instead of the package-level sdk.AddTool(server, input, handler). This is intentional and well-documented: the method form bypasses schema validation, which is necessary because backend MCP servers return tools with JSON Schema draft-07 (or other versions) that the SDK's sdk.AddTool function would reject.

Research Findings

The SDK is at v1.4.1 β€” on the latest version. The project is well-aligned with the SDK's core patterns and benefits from full MCP 2025-11-25 protocol support.

Recent Updates

  • v1.4.x added StreamableHTTPOptions.Stateless mode for stateless session handling
  • NewInMemoryTransports enables proper unit testing without external processes
  • slog-based logger integration via ServerOptions.Logger, ClientOptions.Logger, and StreamableHTTPOptions.Logger

Best Practices (observed in project)

  • βœ… Using typed protocol methods (ListTools, CallTool, ListResources, etc.) over raw JSON-RPC
  • βœ… slog logger integration wired through project's debug logger system
  • βœ… NewInMemoryTransports used for test isolation
  • βœ… sdk.ToolAnnotations metadata propagated through gateway
  • βœ… Full content type coverage (text, image, audio, embedded resource)

Improvement Opportunities

πŸƒ Quick Wins

1. Extract pagination helper in connection.go

listTools, listResources, and listPrompts each implement identical cursor-loop pagination (lines ~545–644), totaling ~45 lines of duplicate boilerplate:

first, err := c.getSDKSession().ListTools(c.ctx, &sdk.ListToolsParams{})
allTools := make([]*sdk.Tool, len(first.Tools), max(len(first.Tools), 1))
copy(allTools, first.Tools)
cursor := first.NextCursor
for cursor != "" {
    result, err := c.getSDKSession().ListTools(c.ctx, &sdk.ListToolsParams{Cursor: cursor})
    ...
    cursor = result.NextCursor
}

This pattern repeats identically for resources and prompts. A generic helper would eliminate the duplication and make pagination behavior (e.g., max-items guards) easy to apply uniformly.

2. resourceContents intermediate type in tool_result.go

A local resourceContents struct (lines 12–18) mirrors sdk.ResourceContents field-for-field for intermediate JSON unmarshaling. Verify whether sdk.ResourceContents can be used directly in the parse step to eliminate the duplicate type.

3. Test server nil options

mcptest/server.go calls sdk.NewServer(impl, nil). Passing &sdk.ServerOptions{} explicitly is more defensive and documents intent, guarding against future SDK changes that might panic on nil options.

✨ Feature Opportunities

1. Prompt forwarding through the aggregated gateway server

The unified server correctly forwards listPrompts/getPrompt backend requests via ClientSession, but does not register aggregated prompts on the gateway's own sdk.Server instance. This means MCP clients connected to the gateway cannot discover or invoke prompts from backends β€” the same gap that was solved for tools via us.server.AddTool. Adding server.AddPrompt registration (mirroring registerAllTools) would bring prompt support to parity with tool support.

2. Configurable session timeouts

Session timeouts are hard-coded:

  • Unified mode: SessionTimeout: 2 * time.Hour (transport.go)
  • Routed mode: SessionTimeout: 30 * time.Minute (routed.go)

These are reasonable defaults but not exposed in config. Adding gateway.session_timeout_minutes (or similar) to the TOML/JSON config would give operators control over memory/connection trade-offs.

3. Evaluate Stateless: true for stateless HTTP backends

StreamableHTTPOptions.Stateless is false everywhere. For backends that are themselves stateless (e.g., pure HTTP MCP servers), stateless mode could reduce session overhead on both the gateway and backend. This is worth evaluating when the backend transport type is http.

πŸ“ Best Practice Alignment

1. filteredServerCache in routed.go grows unboundedly

type filteredServerCache struct {
    servers map[string]*sdk.Server  // never evicted
    mu      sync.RWMutex
}

The cache accumulates one *sdk.Server per (backendID, sessionID) pair and is never pruned. In long-running deployments with many sessions, this silently grows in memory. Adding TTL-based eviction (matching the SessionTimeout) would align cache lifetime with session lifetime.

2. sdk.Transport ownership documentation

The transportConnector factory type and its return values lack documentation about lifecycle/ownership. Adding a brief comment that the caller owns (and should close) the returned sdk.Transport would improve correctness for future contributors.

πŸ”§ General Improvements

Handler signature mismatch complexity: The internal handler type

func(context.Context, *sdk.CallToolRequest, interface{}) (*sdk.CallToolResult, interface{}, error)

has an extra interface{} parameter and return value for middleware data. Every registration site must wrap this to the SDK's simpler func(ctx, req) (result, error) signature. This wrapping is correct but adds ceremony. Consider whether the middleware data could be passed via context instead, which would simplify both the type and the wrapping sites.

Recommendations

  1. [Medium] Add prompt aggregation to the unified server (parity with tool aggregation) β€” highest user-visible impact
  2. [Small] Extract pagination helper in connection.go β€” reduces ~45 lines of duplication
  3. [Small] Add cache eviction to filteredServerCache in routed.go β€” prevents memory growth
  4. [Small] Make session timeouts configurable via gateway config
  5. [Investigate] Evaluate stateless mode for HTTP backends

Next Steps

  • Open sub-issues for prompt aggregation and filteredServerCache eviction if they are prioritized
  • Consider adding specs/mods/ to the repository for ongoing module review summaries

Generated by Go Fan 🐹 | Module: github.com/modelcontextprotocol/go-sdk v1.4.1
Analysis run: Β§23785964845

Note

πŸ”’ Integrity filter blocked 23 items

The following items were blocked because they don't meet the GitHub integrity level.

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by Go Fan Β· β—·

  • expires on Apr 7, 2026, 7:49 AM UTC

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions