-
Notifications
You must be signed in to change notification settings - Fork 20
[duplicate-code] Duplicate Code Pattern: list* MCP Operations Structural Duplication #3156
Description
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.golines 575–591 (listTools)internal/mcp/connection.golines 608–625 (listResources)internal/mcp/connection.golines 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
listResourceTemplatesor 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-finishedto 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