Skip to content

[go-fan] Go Module Review: modelcontextprotocol/go-sdk (v1.6.0) #33967

@github-actions

Description

@github-actions

🐹 Go Fan Report: github.com/modelcontextprotocol/go-sdk

The official Go SDK for Model Context Protocol (MCP), maintained jointly by Anthropic and Google. gh-aw uses it both as a server (exposing gh aw CLI commands as MCP tools via gh aw mcp-server) and as a client (inspecting other MCP servers in gh aw mcp inspect). Current pinned version is v1.6.0, the latest release — well within recommended freshness.

The SDK has shipped two minor releases since gh-aw's last bump (v1.5.0 and v1.6.0) and one of those changes silently weakens our HTTP MCP server's security posture, which is the headline finding below.

Summary

  • Files: 11 non-test files in pkg/cli/ + pkg/parser/ import the SDK
  • Surface: server, client, transports (stdio + StreamableHTTP), tool registration, middleware, jsonrpc errors
  • Key APIs: mcp.NewServer, mcp.AddTool[Args], mcp.ToolAnnotations, mcp.NewStreamableHTTPHandler, mcp.CommandTransport, mcp.StreamableClientTransport, server.AddReceivingMiddleware, jsonrpc.Error
  • Status: ⚠️ One security-relevant follow-up from the v1.6.0 upgrade; otherwise idiomatic.

Critical Findings

🚨 1. CrossOriginProtection is no longer enabled by default (v1.6.0 behaviour change)

In v1.4.1–v1.5.0 the SDK enabled cross-origin protection whenever CrossOriginProtection in StreamableHTTPOptions was nil. v1.6.0 reversed this: nil now means no protection. The change is documented in the v1.6.0 release notes under "Cross-Origin Protection Default Change".

gh-aw's HTTP MCP server constructs its handler without setting this field:

// pkg/cli/mcp_server_http.go:68-73
handler := mcp.NewStreamableHTTPHandler(func(req *http.Request) *mcp.Server {
    return server
}, &mcp.StreamableHTTPOptions{
    SessionTimeout: 2 * time.Hour,
    Logger:         logger.NewSlogLoggerWithHandler(mcpLog),
})

That means anyone running gh aw mcp-server --port N against pre-v1.6.0 of the SDK was protected by the SDK default; after the v1.6.0 bump they are not. The same change retroactively applies to the SSE transport too (PR #891 added DNS-rebinding/cross-origin protections to SSE in v1.6.0).

Action: explicitly populate CrossOriginProtection with a sensible default (mirror what v1.5.0 did by default), or document that operators should set MCPGODEBUG=enableoriginverification=1. Recommend the former — the env-var route is easy to miss.

Improvement Opportunities

🏃 Quick Wins

  • Set CrossOriginProtection explicitly in pkg/cli/mcp_server_http.go:70 (see Critical Findings).
  • Factor the repeated select { case <-ctx.Done(): return newMCPError(...) default: } preamble (~11 occurrences across mcp_tools_readonly.go, mcp_tools_privileged.go, mcp_tools_management.go) into a small SendingMiddleware or wrapper that fails fast on cancelled context. Net: fewer lines, no behavioural change, one place to update.
  • The reserved , nil, nil extension-slot return at the end of every handler is purely SDK boilerplate. A thin wrapper helper would keep handler bodies focused on the actual result.

✨ Feature Opportunities

  • HTTP header standardisation (v1.6.0): mirrors JSON-RPC method/name into request headers so load balancers and observability tools can route MCP traffic without deep packet inspection. Useful once we have anything sitting in front of gh aw mcp-server --port.
  • DNS-rebinding/SSE cross-origin protections (PR Rename --workflow-dir to --workflows-dir #891, v1.6.0): opt-in via the same CrossOriginProtection field — fixing the Quick Win above gets us this for free.
  • extauth.ClientCredentialsHandler (v1.6.0): new OAuth 2.0 client-credentials grant helper. Worth keeping in mind for any future MCP-client code path where gh-aw needs to talk to a third-party MCP server protected by client-credentials OAuth. (Distinct from the workflow-level AuthStrategyOAuthClientCreds in pkg/workflow/engine_definition.go, which is about engine HTTP auth, not MCP transport auth — not a replacement candidate.)

📐 Best Practice Alignment

  • The IsError: false JSON-envelope pattern used by audit / audit-diff (pkg/cli/mcp_tools_privileged.go:453,567) aligns with the v1.5.0 direction of returning structured tool results rather than JSON-RPC errors for application-level failures. Correct usage — no change needed.
  • argumentValidationMiddleware already gates on IsError=true before rewriting (pkg/cli/mcp_argument_validation.go:52). Good defensive coding.
  • ServerCapabilities{Tools{ListChanged: false}} is correct since gh-aw's tool set is static — avoids unnecessary notifications.
  • ToolAnnotations (ReadOnlyHint, IdempotentHint, DestructiveHint, OpenWorldHint) are set thoughtfully on every tool. Idiomatic.

🔧 General Improvements

  • pkg/cli/mcp_inspect_mcp.go swallows errors from session.ListTools / session.ListResources (only logs when verbose). For an inspector this is intentional, but consider surfacing them in the structured MCPServerInfo output so callers know capabilities are partial.
  • SetError(...) is not used anywhere in gh-aw — handlers construct CallToolResult manually. The v1.6.0 SetError behaviour change (preserves existing Content) therefore does not affect us. ✅

Recommendations (prioritised)

  1. Patch the cross-origin regression by explicitly setting CrossOriginProtection in pkg/cli/mcp_server_http.go.
  2. Extract the ctx.Done() boilerplate into a middleware once rejig docs #1 lands.
  3. Track the v1.6.0 HTTP header standardisation feature for the next time we revisit the HTTP MCP server's deployment model.

Next Steps

  • Open a small PR addressing rejig docs #1 (security regression) — single-file change with a regression test verifying the option is set.
  • Open a follow-up issue for the middleware refactor if the team agrees on the direction.

Generated by Go Fan
Module summary saved to: scratchpad/mods/modelcontextprotocol-go-sdk.md

Generated by 🐹 Go Fan · ● 11M ·

  • expires on May 23, 2026, 8:58 AM UTC

Metadata

Metadata

Assignees

No one assigned

    Labels

    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