Skip to content

[duplicate-code] Duplicate Code Pattern: list* MCP Operations Structural Duplication #3156

@github-actions

Description

@github-actions

Part of duplicate code analysis: #3155

Summary

internal/mcp/connection.go contains three nearly-identical functions — listTools, listResources, and listPrompts — that implement the same pagination pattern with only type-level differences. The paginateAll generic helper already encapsulates the pagination loop itself, but the surrounding boilerplate (session check, log, error propagation, marshal) is repeated verbatim in all three functions.

Duplication Details

Pattern: Identical MCP list-operation boilerplate

  • Severity: Medium
  • Occurrences: 3
  • Locations:
    • internal/mcp/connection.go lines 575–591 (listTools)
    • internal/mcp/connection.go lines 608–625 (listResources)
    • internal/mcp/connection.go lines 638–655 (listPrompts)

Template structure (identical in all three):

func (c *Connection) list<X>() (*Response, error) {
    if err := c.requireSession(); err != nil {
        return nil, err
    }
    logConn.Printf("list<X>: requesting <x> list from backend serverID=%s", c.serverID)
    items, err := paginateAll(c.serverID, "<x>", func(cursor string) (paginatedPage[*sdk.<X>], error) {
        result, err := c.getSDKSession().List<X>(c.ctx, &sdk.List<X>Params{Cursor: cursor})
        if err != nil {
            return paginatedPage[*sdk.<X>]{}, err
        }
        return paginatedPage[*sdk.<X>]{Items: result.<X>s, NextCursor: result.NextCursor}, nil
    })
    if err != nil {
        return nil, err
    }
    return marshalToResponse(&sdk.List<X>Result{<X>s: items})
}

Differences across the three functions:

Function SDK Type SDK List method SDK Params type Result field
listTools *sdk.Tool ListTools sdk.ListToolsParams result.Tools
listResources *sdk.Resource ListResources sdk.ListResourcesParams result.Resources
listPrompts *sdk.Prompt ListPrompts sdk.ListPromptsParams result.Prompts

Impact Analysis

  • Maintainability: Any change to list-operation behavior (e.g., adding retry logic, changing session error handling, adding metrics) must be replicated in all three functions manually.
  • Bug Risk: Medium — inconsistent future edits are likely if one function is updated and the others are forgotten.
  • Code Bloat: ~34 lines of unnecessary duplication (51 total lines, ~17 lines are unique per function).

Refactoring Recommendations

Option A: Generic listMCPItems helper (recommended)

Extract the common boilerplate into a generic function that accepts the SDK-specific list function and result-to-items extractor:

// listMCPItems is a generic helper for the list<X> family of MCP operations.
// It handles session check, pagination, and response marshalling.
func listMCPItems[Item any, Result any](
    c *Connection,
    kind string,
    listFn func(cursor string) (paginatedPage[Item], error),
    buildResult func([]Item) Result,
) (*Response, error) {
    if err := c.requireSession(); err != nil {
        return nil, err
    }
    logConn.Printf("list%s: requesting %s list from backend serverID=%s", kind, kind, c.serverID)
    items, err := paginateAll(c.serverID, kind, listFn)
    if err != nil {
        return nil, err
    }
    return marshalToResponse(buildResult(items))
}

// listTools uses listMCPItems to fetch all tools from the backend.
func (c *Connection) listTools() (*Response, error) {
    return listMCPItems(c, "tools",
        func(cursor string) (paginatedPage[*sdk.Tool], error) {
            result, err := c.getSDKSession().ListTools(c.ctx, &sdk.ListToolsParams{Cursor: cursor})
            if err != nil {
                return paginatedPage[*sdk.Tool]{}, err
            }
            return paginatedPage[*sdk.Tool]{Items: result.Tools, NextCursor: result.NextCursor}, nil
        },
        func(items []*sdk.Tool) *sdk.ListToolsResult {
            return &sdk.ListToolsResult{Tools: items}
        },
    )
}

// listResources and listPrompts follow the same pattern.
  • Estimated effort: ~1 hour
  • Benefits: Single point of change for list-operation boilerplate; future listResourceTemplates or similar additions are one short function instead of 17 lines

Option B: Accept the current duplication (low-risk alternative)

If Go generics feel verbose for this callsite, add a comment block above the three functions noting they form an intentional family (similar to common.go logger documentation) to prevent silent divergence during future edits.

Implementation Checklist

  • Review duplication findings
  • Choose refactoring approach (generic helper vs. documentation)
  • Implement changes
  • Verify existing tests still pass (make test-unit)
  • Run make agent-finished to confirm build and lint pass

Parent Issue

See parent analysis report: #3155
Related to #3155

Generated by Duplicate Code Detector ·

  • expires on Apr 11, 2026, 5:51 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