From 342929b187995555f7283454b4428eb4c597c313 Mon Sep 17 00:00:00 2001 From: LuluBeatson Date: Fri, 4 Jul 2025 10:32:43 +0100 Subject: [PATCH 1/4] omit site_admin from get_me output --- pkg/github/context_tools.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/github/context_tools.go b/pkg/github/context_tools.go index bed2f4a39..280420b91 100644 --- a/pkg/github/context_tools.go +++ b/pkg/github/context_tools.go @@ -38,6 +38,9 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Too ), nil } + // Set nil to omit from output + user.SiteAdmin = nil + return MarshalledTextResult(user), nil }) From 45aae1e73b466a6bfa2e99b6d3d5aea7cc852c82 Mon Sep 17 00:00:00 2001 From: LuluBeatson Date: Fri, 4 Jul 2025 12:05:05 +0100 Subject: [PATCH 2/4] return MinimalUser --- pkg/github/context_tools.go | 15 ++++++++++++--- pkg/github/context_tools_test.go | 12 ++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/pkg/github/context_tools.go b/pkg/github/context_tools.go index 280420b91..43a5ce726 100644 --- a/pkg/github/context_tools.go +++ b/pkg/github/context_tools.go @@ -38,10 +38,19 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Too ), nil } - // Set nil to omit from output - user.SiteAdmin = nil + // Create minimal user representation instead of returning full user object + minimalUser := MinimalUser{ + Login: user.GetLogin(), + ID: user.GetID(), + } + if user.HTMLURL != nil { + minimalUser.ProfileURL = *user.HTMLURL + } + if user.AvatarURL != nil { + minimalUser.AvatarURL = *user.AvatarURL + } - return MarshalledTextResult(user), nil + return MarshalledTextResult(minimalUser), nil }) return tool, handler diff --git a/pkg/github/context_tools_test.go b/pkg/github/context_tools_test.go index 0d9193976..675e04dce 100644 --- a/pkg/github/context_tools_test.go +++ b/pkg/github/context_tools_test.go @@ -117,17 +117,13 @@ func Test_GetMe(t *testing.T) { } // Unmarshal and verify the result - var returnedUser github.User + var returnedUser MinimalUser err = json.Unmarshal([]byte(textContent.Text), &returnedUser) require.NoError(t, err) - // Verify user details - assert.Equal(t, *tc.expectedUser.Login, *returnedUser.Login) - assert.Equal(t, *tc.expectedUser.Name, *returnedUser.Name) - assert.Equal(t, *tc.expectedUser.Email, *returnedUser.Email) - assert.Equal(t, *tc.expectedUser.Bio, *returnedUser.Bio) - assert.Equal(t, *tc.expectedUser.HTMLURL, *returnedUser.HTMLURL) - assert.Equal(t, *tc.expectedUser.Type, *returnedUser.Type) + // Verify minimal user details + assert.Equal(t, *tc.expectedUser.Login, returnedUser.Login) + assert.Equal(t, *tc.expectedUser.HTMLURL, returnedUser.ProfileURL) }) } } From 8c1bae1ba93d842717eeb107987331a8a554e32b Mon Sep 17 00:00:00 2001 From: LuluBeatson Date: Fri, 4 Jul 2025 12:29:29 +0100 Subject: [PATCH 3/4] refactor: user get methods to avoid nil checks --- pkg/github/context_tools.go | 12 ++++-------- pkg/github/search.go | 14 +++++--------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/pkg/github/context_tools.go b/pkg/github/context_tools.go index 43a5ce726..cf859f8bd 100644 --- a/pkg/github/context_tools.go +++ b/pkg/github/context_tools.go @@ -40,14 +40,10 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Too // Create minimal user representation instead of returning full user object minimalUser := MinimalUser{ - Login: user.GetLogin(), - ID: user.GetID(), - } - if user.HTMLURL != nil { - minimalUser.ProfileURL = *user.HTMLURL - } - if user.AvatarURL != nil { - minimalUser.AvatarURL = *user.AvatarURL + Login: user.GetLogin(), + ID: user.GetID(), + ProfileURL: user.GetHTMLURL(), + AvatarURL: user.GetAvatarURL(), } return MarshalledTextResult(minimalUser), nil diff --git a/pkg/github/search.go b/pkg/github/search.go index 5106b84d8..82f920351 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -224,15 +224,11 @@ func userOrOrgHandler(accountType string, getClient GetClientFn) server.ToolHand for _, user := range result.Users { if user.Login != nil { - mu := MinimalUser{Login: *user.Login} - if user.ID != nil { - mu.ID = *user.ID - } - if user.HTMLURL != nil { - mu.ProfileURL = *user.HTMLURL - } - if user.AvatarURL != nil { - mu.AvatarURL = *user.AvatarURL + mu := MinimalUser{ + Login: user.GetLogin(), + ID: user.GetID(), + ProfileURL: user.GetHTMLURL(), + AvatarURL: user.GetAvatarURL(), } minimalUsers = append(minimalUsers, mu) } From dd62f8d80be5d69adaa8f36adab0395a1e570273 Mon Sep 17 00:00:00 2001 From: LuluBeatson Date: Fri, 4 Jul 2025 13:20:28 +0100 Subject: [PATCH 4/4] embed optional UserDetails in MinimalUser --- pkg/github/context_tools.go | 42 ++++++++++++++++++++++++++++++++ pkg/github/context_tools_test.go | 30 ++++++++++++++++------- pkg/github/search.go | 10 +++++--- 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/pkg/github/context_tools.go b/pkg/github/context_tools.go index cf859f8bd..3525277fe 100644 --- a/pkg/github/context_tools.go +++ b/pkg/github/context_tools.go @@ -2,6 +2,7 @@ package github import ( "context" + "time" ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/translations" @@ -9,6 +10,28 @@ import ( "github.com/mark3labs/mcp-go/server" ) +// UserDetails contains additional fields about a GitHub user not already +// present in MinimalUser. Used by get_me context tool but omitted from search_users. +type UserDetails struct { + Name string `json:"name,omitempty"` + Company string `json:"company,omitempty"` + Blog string `json:"blog,omitempty"` + Location string `json:"location,omitempty"` + Email string `json:"email,omitempty"` + Hireable bool `json:"hireable,omitempty"` + Bio string `json:"bio,omitempty"` + TwitterUsername string `json:"twitter_username,omitempty"` + PublicRepos int `json:"public_repos"` + PublicGists int `json:"public_gists"` + Followers int `json:"followers"` + Following int `json:"following"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PrivateGists int `json:"private_gists,omitempty"` + TotalPrivateRepos int64 `json:"total_private_repos,omitempty"` + OwnedPrivateRepos int64 `json:"owned_private_repos,omitempty"` +} + // GetMe creates a tool to get details of the authenticated user. func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) { tool := mcp.NewTool("get_me", @@ -44,6 +67,25 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Too ID: user.GetID(), ProfileURL: user.GetHTMLURL(), AvatarURL: user.GetAvatarURL(), + Details: &UserDetails{ + Name: user.GetName(), + Company: user.GetCompany(), + Blog: user.GetBlog(), + Location: user.GetLocation(), + Email: user.GetEmail(), + Hireable: user.GetHireable(), + Bio: user.GetBio(), + TwitterUsername: user.GetTwitterUsername(), + PublicRepos: user.GetPublicRepos(), + PublicGists: user.GetPublicGists(), + Followers: user.GetFollowers(), + Following: user.GetFollowing(), + CreatedAt: user.GetCreatedAt().Time, + UpdatedAt: user.GetUpdatedAt().Time, + PrivateGists: user.GetPrivateGists(), + TotalPrivateRepos: user.GetTotalPrivateRepos(), + OwnedPrivateRepos: user.GetOwnedPrivateRepos(), + }, } return MarshalledTextResult(minimalUser), nil diff --git a/pkg/github/context_tools_test.go b/pkg/github/context_tools_test.go index 675e04dce..03af4175d 100644 --- a/pkg/github/context_tools_test.go +++ b/pkg/github/context_tools_test.go @@ -26,15 +26,17 @@ func Test_GetMe(t *testing.T) { // Setup mock user response mockUser := &github.User{ - Login: github.Ptr("testuser"), - Name: github.Ptr("Test User"), - Email: github.Ptr("test@example.com"), - Bio: github.Ptr("GitHub user for testing"), - Company: github.Ptr("Test Company"), - Location: github.Ptr("Test Location"), - HTMLURL: github.Ptr("https://github.com/testuser"), - CreatedAt: &github.Timestamp{Time: time.Now().Add(-365 * 24 * time.Hour)}, - Type: github.Ptr("User"), + Login: github.Ptr("testuser"), + Name: github.Ptr("Test User"), + Email: github.Ptr("test@example.com"), + Bio: github.Ptr("GitHub user for testing"), + Company: github.Ptr("Test Company"), + Location: github.Ptr("Test Location"), + HTMLURL: github.Ptr("https://github.com/testuser"), + CreatedAt: &github.Timestamp{Time: time.Now().Add(-365 * 24 * time.Hour)}, + Type: github.Ptr("User"), + Hireable: github.Ptr(true), + TwitterUsername: github.Ptr("testuser_twitter"), Plan: &github.Plan{ Name: github.Ptr("pro"), }, @@ -124,6 +126,16 @@ func Test_GetMe(t *testing.T) { // Verify minimal user details assert.Equal(t, *tc.expectedUser.Login, returnedUser.Login) assert.Equal(t, *tc.expectedUser.HTMLURL, returnedUser.ProfileURL) + + // Verify user details + require.NotNil(t, returnedUser.Details) + assert.Equal(t, *tc.expectedUser.Name, returnedUser.Details.Name) + assert.Equal(t, *tc.expectedUser.Email, returnedUser.Details.Email) + assert.Equal(t, *tc.expectedUser.Bio, returnedUser.Details.Bio) + assert.Equal(t, *tc.expectedUser.Company, returnedUser.Details.Company) + assert.Equal(t, *tc.expectedUser.Location, returnedUser.Details.Location) + assert.Equal(t, *tc.expectedUser.Hireable, returnedUser.Details.Hireable) + assert.Equal(t, *tc.expectedUser.TwitterUsername, returnedUser.Details.TwitterUsername) }) } } diff --git a/pkg/github/search.go b/pkg/github/search.go index 82f920351..a72b38bc6 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -155,11 +155,13 @@ func SearchCode(getClient GetClientFn, t translations.TranslationHelperFunc) (to } } +// MinimalUser is the output type for user and organization search results. type MinimalUser struct { - Login string `json:"login"` - ID int64 `json:"id,omitempty"` - ProfileURL string `json:"profile_url,omitempty"` - AvatarURL string `json:"avatar_url,omitempty"` + Login string `json:"login"` + ID int64 `json:"id,omitempty"` + ProfileURL string `json:"profile_url,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` + Details *UserDetails `json:"details,omitempty"` // Optional field for additional user details } type MinimalSearchUsersResult struct {