Skip to content

refactor: eliminate duplicate StreamableHTTP session callback and SDK list method wrappers#1403

Merged
lpcox merged 2 commits intomainfrom
copilot/remove-duplicate-code-patterns
Feb 25, 2026
Merged

refactor: eliminate duplicate StreamableHTTP session callback and SDK list method wrappers#1403
lpcox merged 2 commits intomainfrom
copilot/remove-duplicate-code-patterns

Conversation

Copy link
Contributor

Copilot AI commented Feb 25, 2026

Two near-identical code patterns existed across transport.go/routed.go and connection.go, creating a "fix in one place, forget the other" failure mode. The session callback duplication also had a security-relevant inconsistency: routed.go logged raw session IDs while transport.go used sanitize.TruncateSecret.

Pattern 1: Session setup callback (transport.go + routed.go)

Re-introduces setupSessionCallback in http_helpers.go, consolidating ~20 duplicated lines from both StreamableHTTP handler callbacks into a single helper:

// Before: ~20 lines inline in each of transport.go and routed.go
sessionID := extractAndValidateSession(r)
if sessionID == "" { return nil }
logger.LogInfo("client", "MCP connection established, ...")
log.Printf("=== NEW STREAMABLE HTTP CONNECTION ===")
// ... + routed.go used sessionID directly instead of sanitize.TruncateSecret(sessionID)

// After: in transport.go
if _, ok := setupSessionCallback(r, ""); !ok {
    return nil
}
// After: in routed.go
if _, ok := setupSessionCallback(r, backendID); !ok {
    return nil
}
sessionID := r.Context().Value(SessionIDContextKey).(string)

Also removes the now-unused sanitize import from transport.go.

Pattern 2: SDK list method wrappers (connection.go)

Extracts a callListMethod helper for the repeated requireSession → SDK call → marshalToResponse pattern across listTools, listResources, and listPrompts:

func (c *Connection) callListMethod(call func() (interface{}, error)) (*Response, error) {
    if err := c.requireSession(); err != nil {
        return nil, err
    }
    result, err := call()
    if err != nil {
        return nil, err
    }
    return marshalToResponse(result)
}

func (c *Connection) listTools() (*Response, error) {
    logConn.Printf("listTools: requesting tool list from backend serverID=%s", c.serverID)
    return c.callListMethod(func() (interface{}, error) {
        result, err := c.session.ListTools(c.ctx, &sdk.ListToolsParams{})
        if err == nil {
            logConn.Printf("listTools: received %d tools from serverID=%s", len(result.Tools), c.serverID)
        }
        return result, err
    })
}

Per-method count logging is preserved inside each closure since it references method-specific result fields.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • example.com
    • Triggering command: /tmp/go-build2204598335/b280/launcher.test /tmp/go-build2204598335/b280/launcher.test -test.testlogfile=/tmp/go-build2204598335/b280/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true go riter/tabwriter.--64 64/pkg/tool/linu-o r (dns block)
    • Triggering command: /tmp/go-build391587417/b280/launcher.test /tmp/go-build391587417/b280/launcher.test -test.testlogfile=/tmp/go-build391587417/b280/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true /difc/agent.go /difc/capabilities.go x_amd64/link --with-filename func marshalToResponse /home/REDACTED/work/gh-aw-mcpg/gh-b7Byqgxqkr-cN-7rSOp1/b7Byqgxqkr-cN-7rSOp1 x_amd64/link 4598�� 64/src/net ache/go/1.25.7/x64/src/crypto/in-nolocalimports 4598335/b171/ -p weak -lang=go1.25 OI/f_Y5u0Vsu1Q6lRjIvans/P_v2VhJmRvDLOu0RfjOj (dns block)
  • invalid-host-that-does-not-exist-12345.com
    • Triggering command: /tmp/go-build2204598335/b262/config.test /tmp/go-build2204598335/b262/config.test -test.testlogfile=/tmp/go-build2204598335/b262/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true go (dns block)
  • nonexistent.local
    • Triggering command: /tmp/go-build2204598335/b280/launcher.test /tmp/go-build2204598335/b280/launcher.test -test.testlogfile=/tmp/go-build2204598335/b280/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true go riter/tabwriter.--64 64/pkg/tool/linu-o r (dns block)
    • Triggering command: /tmp/go-build391587417/b280/launcher.test /tmp/go-build391587417/b280/launcher.test -test.testlogfile=/tmp/go-build391587417/b280/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true /difc/agent.go /difc/capabilities.go x_amd64/link --with-filename func marshalToResponse /home/REDACTED/work/gh-aw-mcpg/gh-b7Byqgxqkr-cN-7rSOp1/b7Byqgxqkr-cN-7rSOp1 x_amd64/link 4598�� 64/src/net ache/go/1.25.7/x64/src/crypto/in-nolocalimports 4598335/b171/ -p weak -lang=go1.25 OI/f_Y5u0Vsu1Q6lRjIvans/P_v2VhJmRvDLOu0RfjOj (dns block)
  • slow.example.com
    • Triggering command: /tmp/go-build2204598335/b280/launcher.test /tmp/go-build2204598335/b280/launcher.test -test.testlogfile=/tmp/go-build2204598335/b280/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true go riter/tabwriter.--64 64/pkg/tool/linu-o r (dns block)
    • Triggering command: /tmp/go-build391587417/b280/launcher.test /tmp/go-build391587417/b280/launcher.test -test.testlogfile=/tmp/go-build391587417/b280/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true /difc/agent.go /difc/capabilities.go x_amd64/link --with-filename func marshalToResponse /home/REDACTED/work/gh-aw-mcpg/gh-b7Byqgxqkr-cN-7rSOp1/b7Byqgxqkr-cN-7rSOp1 x_amd64/link 4598�� 64/src/net ache/go/1.25.7/x64/src/crypto/in-nolocalimports 4598335/b171/ -p weak -lang=go1.25 OI/f_Y5u0Vsu1Q6lRjIvans/P_v2VhJmRvDLOu0RfjOj (dns block)
  • this-host-does-not-exist-12345.com
    • Triggering command: /tmp/go-build2204598335/b289/mcp.test /tmp/go-build2204598335/b289/mcp.test -test.testlogfile=/tmp/go-build2204598335/b289/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true -b 64/src/internal/-fno-stack-protector x_amd64/vet --depth 2 REDACTED x_amd64/vet abis�� 64/src/runtime/c-errorsas --global 64/bin/as user.name record (dns block)
    • Triggering command: /tmp/go-build391587417/b289/mcp.test /tmp/go-build391587417/b289/mcp.test -test.testlogfile=/tmp/go-build391587417/b289/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true s iJR4/CmZNTCtTejDoWTZLiJR4 86_64/as (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>[duplicate-code] Duplicate Code Analysis Report</issue_title>
<issue_description>## Summary

Analysis of the latest refactor commit (splitting mcp/connection.go and relocating server helpers) found 2 significant duplication patterns in non-test Go files. Both patterns exceed the 10-line threshold and present concrete maintainability risks.

Detected Patterns

This analysis found 2 significant duplication patterns:

  1. Streamable HTTP Session Setup Callback – Severity: Medium – See sub-issue [duplicate-code] Duplicate Code Pattern: StreamableHTTP Session Setup Callback #1389
  2. SDK List Method Wrappers – Severity: Low – See sub-issue [duplicate-code] Duplicate Code Pattern: SDK List Method Wrappers in Connection #1390

Overall Impact

  • Total Duplicated Lines: ~36 lines across both patterns
  • Affected Files: 3 files (internal/server/transport.go, internal/server/routed.go, internal/mcp/connection.go)
  • Maintainability Risk: Medium — the session callback duplication in particular creates a "fix in one place, forget the other" failure mode, as was evidenced by the fact that a previous setupSessionCallback helper existed and was removed during the recent refactor
  • Refactoring Priority: Medium — the session callback pattern should be addressed first

Next Steps

  1. Review individual sub-issues for detailed analysis and code examples
  2. Prioritize the session callback refactoring (pattern 1) as it is most likely to diverge
  3. Consider the list-method wrapper refactoring (pattern 2) as a lower-priority clean-up

Analysis Metadata

  • Analyzed Files: 68 Go source files changed in the commit
  • Detection Method: Manual semantic code analysis of changed files
  • Commit: 387f45d3dbcb2c917a56501047a633eaf981605f
  • Analysis Date: 2026-02-25</issue_description>

Comments on the Issue (you are @copilot in this section)

Custom agent used: agentic-workflows
GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

…r code

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copilot AI changed the title [WIP] Analyze and document duplicate code patterns in Go files refactor: eliminate duplicate StreamableHTTP session callback and SDK list method wrappers Feb 25, 2026
@lpcox lpcox marked this pull request as ready for review February 25, 2026 18:09
Copilot AI review requested due to automatic review settings February 25, 2026 18:09
@lpcox lpcox merged commit 158da60 into main Feb 25, 2026
5 checks passed
@lpcox lpcox deleted the copilot/remove-duplicate-code-patterns branch February 25, 2026 18:09
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors duplicated MCP StreamableHTTP session-establishment logic and repeated SDK “list” method wrappers to reduce drift risk and centralize shared behavior in the server and MCP connection layers.

Changes:

  • Introduces setupSessionCallback in internal/server/http_helpers.go and switches unified/routed StreamableHTTP handlers to use it.
  • Removes the now-redundant inline session callback code in internal/server/transport.go and internal/server/routed.go (including dropping an unused import in transport.go).
  • Adds Connection.callListMethod in internal/mcp/connection.go to consolidate the requireSession → SDK call → marshalToResponse pattern for list operations.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
internal/server/transport.go Replaces inline StreamableHTTP session setup with setupSessionCallback, removes unused sanitize import.
internal/server/routed.go Replaces inline session setup with setupSessionCallback; obtains sessionID from request context for caching.
internal/server/http_helpers.go Adds setupSessionCallback helper to centralize extraction/logging/body logging/context injection for session establishment.
internal/mcp/connection.go Adds callListMethod helper and refactors listTools/listResources/listPrompts to use it.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +112 to +125
if backendID != "" {
logger.LogInfo("client", "New MCP client connection, remote=%s, method=%s, path=%s, backend=%s, session=%s",
r.RemoteAddr, r.Method, r.URL.Path, backendID, sessionID)
log.Printf("=== NEW STREAMABLE HTTP CONNECTION (ROUTED) ===")
} else {
logger.LogInfo("client", "MCP connection established, remote=%s, method=%s, path=%s, session=%s",
r.RemoteAddr, r.Method, r.URL.Path, sessionID)
log.Printf("=== NEW STREAMABLE HTTP CONNECTION ===")
}
log.Printf("[%s] %s %s", r.RemoteAddr, r.Method, r.URL.Path)
if backendID != "" {
log.Printf("Backend: %s", backendID)
}
log.Printf("Authorization (Session ID): %s", sanitize.TruncateSecret(sessionID))
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setupSessionCallback logs the raw sessionID in logger.LogInfo (and uses it in formatted messages). Since the session ID is derived from the Authorization header (often an API key), this can leak credentials into production logs. Use a truncated/sanitized form (e.g., sanitize.TruncateSecret(sessionID)) for all log output and avoid emitting the full value at info level.

Suggested change
if backendID != "" {
logger.LogInfo("client", "New MCP client connection, remote=%s, method=%s, path=%s, backend=%s, session=%s",
r.RemoteAddr, r.Method, r.URL.Path, backendID, sessionID)
log.Printf("=== NEW STREAMABLE HTTP CONNECTION (ROUTED) ===")
} else {
logger.LogInfo("client", "MCP connection established, remote=%s, method=%s, path=%s, session=%s",
r.RemoteAddr, r.Method, r.URL.Path, sessionID)
log.Printf("=== NEW STREAMABLE HTTP CONNECTION ===")
}
log.Printf("[%s] %s %s", r.RemoteAddr, r.Method, r.URL.Path)
if backendID != "" {
log.Printf("Backend: %s", backendID)
}
log.Printf("Authorization (Session ID): %s", sanitize.TruncateSecret(sessionID))
sanitizedSessionID := sanitize.TruncateSecret(sessionID)
if backendID != "" {
logger.LogInfo("client", "New MCP client connection, remote=%s, method=%s, path=%s, backend=%s, session=%s",
r.RemoteAddr, r.Method, r.URL.Path, backendID, sanitizedSessionID)
log.Printf("=== NEW STREAMABLE HTTP CONNECTION (ROUTED) ===")
} else {
logger.LogInfo("client", "MCP connection established, remote=%s, method=%s, path=%s, session=%s",
r.RemoteAddr, r.Method, r.URL.Path, sanitizedSessionID)
log.Printf("=== NEW STREAMABLE HTTP CONNECTION ===")
}
log.Printf("[%s] %s %s", r.RemoteAddr, r.Method, r.URL.Path)
if backendID != "" {
log.Printf("Backend: %s", backendID)
}
log.Printf("Authorization (Session ID): %s", sanitizedSessionID)

Copilot uses AI. Check for mistakes.
Comment on lines 108 to 116
routeHandler := sdk.NewStreamableHTTPHandler(func(r *http.Request) *sdk.Server {
// Extract and validate session ID from Authorization header
sessionID := extractAndValidateSession(r)
if sessionID == "" {
if _, ok := setupSessionCallback(r, backendID); !ok {
return nil
}

logger.LogInfo("client", "New MCP client connection, remote=%s, method=%s, path=%s, backend=%s, session=%s",
r.RemoteAddr, r.Method, r.URL.Path, backendID, sessionID)
log.Printf("=== NEW STREAMABLE HTTP CONNECTION (ROUTED) ===")
log.Printf("[%s] %s %s", r.RemoteAddr, r.Method, r.URL.Path)
log.Printf("Backend: %s", backendID)
log.Printf("Authorization (Session ID): %s", sessionID)

// Log request body for debugging
logHTTPRequestBody(r, sessionID, backendID)

// Store session ID and backend ID in request context
*r = *injectSessionContext(r, sessionID, backendID)
log.Printf("✓ Injected session ID and backend ID into context")
log.Printf("===================================\n")

// Return a cached filtered proxy server for this backend and session
// This ensures the same server instance is reused for all requests in a session
sessionID := r.Context().Value(SessionIDContextKey).(string)
return serverCache.getOrCreate(backendID, sessionID, func() *sdk.Server {
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In routed mode, setupSessionCallback already returns the sessionID, but this handler discards it and then re-fetches it from the request context with a type assertion. Capturing the returned sessionID avoids the extra context lookup and removes a potential panic if the context value ever changes or is missing.

See below for a potential fix:

			sessionID, ok := setupSessionCallback(r, backendID)
			if !ok {
				return nil
			}

			// Return a cached filtered proxy server for this backend and session
			// This ensures the same server instance is reused for all requests in a session

Copilot uses AI. Check for mistakes.
Comment on lines +101 to +134
// setupSessionCallback performs common session establishment for StreamableHTTP handlers.
// It extracts and validates the session ID, logs connection details, logs the request body,
// and injects session context into the request. The backendID parameter is optional
// and can be empty for unified mode. Returns false if the request should be rejected
// (empty session ID).
func setupSessionCallback(r *http.Request, backendID string) (string, bool) {
sessionID := extractAndValidateSession(r)
if sessionID == "" {
return "", false
}

if backendID != "" {
logger.LogInfo("client", "New MCP client connection, remote=%s, method=%s, path=%s, backend=%s, session=%s",
r.RemoteAddr, r.Method, r.URL.Path, backendID, sessionID)
log.Printf("=== NEW STREAMABLE HTTP CONNECTION (ROUTED) ===")
} else {
logger.LogInfo("client", "MCP connection established, remote=%s, method=%s, path=%s, session=%s",
r.RemoteAddr, r.Method, r.URL.Path, sessionID)
log.Printf("=== NEW STREAMABLE HTTP CONNECTION ===")
}
log.Printf("[%s] %s %s", r.RemoteAddr, r.Method, r.URL.Path)
if backendID != "" {
log.Printf("Backend: %s", backendID)
}
log.Printf("Authorization (Session ID): %s", sanitize.TruncateSecret(sessionID))

logHTTPRequestBody(r, sessionID, backendID)

*r = *injectSessionContext(r, sessionID, backendID)
log.Printf("✓ Injected session ID into context")
log.Printf("===================================\n")

return sessionID, true
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setupSessionCallback is new behavior centralizing session establishment, logging, request body restoration, and context injection, but internal/server/http_helpers_test.go currently doesn't cover it. Add focused tests for: (1) missing Authorization header returns ok=false and does not modify context/body; (2) valid header returns sessionID and injects SessionIDContextKey (and backend-id when provided); (3) POST body is still readable after the callback.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[duplicate-code] Duplicate Code Analysis Report

3 participants