Part of duplicate code analysis: #4458
Summary
The logic for making a GitHub REST API call to fetch collaborator permission (/repos/{owner}/{repo}/collaborators/{username}/permission) is nearly identical in two files, with ~35 lines duplicated.
Duplication Details
Pattern: get_collaborator_permission HTTP call logic
- Severity: High
- Occurrences: 2
- Locations:
internal/server/unified.go — callCollaboratorPermission method (lines ~282–337)
internal/proxy/proxy.go — case "get_collaborator_permission" inside restBackendCaller.CallTool (lines ~278–328)
Both locations perform:
- Parse
owner, repo, username from argsMap
- Validate all three fields are non-empty
- Build the path
/repos/{owner}/{repo}/collaborators/{username}/permission
- Make a
GET request to the GitHub API
- Read the response body
- Return error for HTTP 4xx/5xx
- Call
mcp.LogAndWrapCollaboratorPermission
internal/server/unified.go (callCollaboratorPermission):
argsMap, ok := args.(map[string]interface{})
owner, _ := argsMap["owner"].(string)
repo, _ := argsMap["repo"].(string)
username, _ := argsMap["username"].(string)
if owner == "" || repo == "" || username == "" {
return nil, fmt.Errorf("get_collaborator_permission: missing owner/repo/username")
}
token := envutil.LookupGitHubToken()
apiURL := envutil.DeriveGitHubAPIURL(envutil.DefaultGitHubAPIBaseURL)
path := fmt.Sprintf("/repos/%s/%s/collaborators/%s/permission", owner, repo, username)
req, _ := http.NewRequestWithContext(ctx, "GET", apiURL+path, nil)
req.Header.Set("Authorization", "token "+token)
req.Header.Set("Accept", "application/vnd.github+json")
resp, _ := http.DefaultClient.Do(req)
// ... read body, check status, call LogAndWrapCollaboratorPermission
internal/proxy/proxy.go (restBackendCaller.CallTool, case "get_collaborator_permission"):
owner, _ := argsMap["owner"].(string)
repo, _ := argsMap["repo"].(string)
username, _ := argsMap["username"].(string)
if owner == "" || repo == "" || username == "" {
return nil, fmt.Errorf("get_collaborator_permission: missing owner/repo/username")
}
apiPath = fmt.Sprintf("/repos/%s/%s/collaborators/%s/permission", owner, repo, username)
resp, _ := r.server.forwardToGitHub(ctx, "GET", apiPath, nil, "", enrichmentAuth)
// ... read body, check status, call LogAndWrapCollaboratorPermission
Impact Analysis
- Maintainability: Changes to error messages, path format, or response handling must be made in two places
- Bug Risk: The two implementations already diverge slightly (server uses
http.DefaultClient with explicit header setup; proxy uses forwardToGitHub). A bug fix applied to one may not be applied to the other.
- Code Bloat: ~35 lines duplicated
Refactoring Recommendations
-
Extract a CollaboratorPermissionArgs parser to internal/mcp
- Add a helper
ParseCollaboratorPermissionArgs(args interface{}) (owner, repo, username string, err error) to internal/mcp/collaborator_permission.go
- This already contains
LogAndWrapCollaboratorPermission, making it the natural home
-
Extract a GitHub REST helper to internal/github (new package) or internal/mcp
- Create
FetchCollaboratorPermission(ctx, httpClient, apiURL, token, owner, repo, username) ([]byte, int, error) to unify the HTTP call
- Both server and proxy can use this shared function
-
Estimated effort: 1–2 hours
Implementation Checklist
Parent Issue
See parent analysis report: #4458
Related to #4458
Generated by Duplicate Code Detector · ● 3M · ◷
Part of duplicate code analysis: #4458
Summary
The logic for making a GitHub REST API call to fetch collaborator permission (
/repos/{owner}/{repo}/collaborators/{username}/permission) is nearly identical in two files, with ~35 lines duplicated.Duplication Details
Pattern:
get_collaborator_permissionHTTP call logicinternal/server/unified.go—callCollaboratorPermissionmethod (lines ~282–337)internal/proxy/proxy.go—case "get_collaborator_permission"insiderestBackendCaller.CallTool(lines ~278–328)Both locations perform:
owner,repo,usernamefromargsMap/repos/{owner}/{repo}/collaborators/{username}/permissionGETrequest to the GitHub APImcp.LogAndWrapCollaboratorPermissioninternal/server/unified.go(callCollaboratorPermission):internal/proxy/proxy.go(restBackendCaller.CallTool, case "get_collaborator_permission"):Impact Analysis
http.DefaultClientwith explicit header setup; proxy usesforwardToGitHub). A bug fix applied to one may not be applied to the other.Refactoring Recommendations
Extract a
CollaboratorPermissionArgsparser tointernal/mcpParseCollaboratorPermissionArgs(args interface{}) (owner, repo, username string, err error)tointernal/mcp/collaborator_permission.goLogAndWrapCollaboratorPermission, making it the natural homeExtract a GitHub REST helper to
internal/github(new package) orinternal/mcpFetchCollaboratorPermission(ctx, httpClient, apiURL, token, owner, repo, username) ([]byte, int, error)to unify the HTTP callEstimated effort: 1–2 hours
Implementation Checklist
ParseCollaboratorPermissionArgstointernal/mcp/collaborator_permission.gointernal/mcporinternal/httputil)internal/server/unified.go:callCollaboratorPermissionto use shared helpersinternal/proxy/proxy.go:restBackendCaller.CallToolto use shared helpersmake test-allto verify no regressionsParent Issue
See parent analysis report: #4458
Related to #4458