66"time"
77
88ghErrors"github.com/github/github-mcp-server/pkg/errors"
9+ "github.com/github/github-mcp-server/pkg/toolsets"
910"github.com/github/github-mcp-server/pkg/translations"
1011"github.com/github/github-mcp-server/pkg/utils"
1112"github.com/google/jsonschema-go/jsonschema"
@@ -36,8 +37,9 @@ type UserDetails struct {
3637}
3738
3839// GetMe creates a tool to get details of the authenticated user.
39- func GetMe (getClient GetClientFn ,t translations.TranslationHelperFunc ) (mcp.Tool , mcp.ToolHandlerFor [map [string ]any ,any ]) {
40- return mcp.Tool {
40+ func GetMe (t translations.TranslationHelperFunc ) toolsets.ServerTool {
41+ return NewTool (
42+ mcp.Tool {
4143Name :"get_me" ,
4244Description :t ("TOOL_GET_ME_DESCRIPTION" ,"Get details of the authenticated GitHub user. Use this when a request is about the user's own profile for GitHub. Or when information is missing to build other tool calls." ),
4345Annotations :& mcp.ToolAnnotations {
@@ -48,50 +50,53 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Too
4850// OpenAI strict mode requires the properties field to be present.
4951InputSchema :json .RawMessage (`{"type":"object","properties":{}}` ),
5052},
51- mcp.ToolHandlerFor [map [string ]any ,any ](func (ctx context.Context ,_ * mcp.CallToolRequest ,_ map [string ]any ) (* mcp.CallToolResult ,any ,error ) {
52- client ,err := getClient (ctx )
53- if err != nil {
54- return utils .NewToolResultErrorFromErr ("failed to get GitHub client" ,err ),nil ,nil
55- }
53+ func (deps ToolDependencies ) mcp.ToolHandlerFor [map [string ]any ,any ] {
54+ return func (ctx context.Context ,_ * mcp.CallToolRequest ,_ map [string ]any ) (* mcp.CallToolResult ,any ,error ) {
55+ client ,err := deps .GetClient (ctx )
56+ if err != nil {
57+ return utils .NewToolResultErrorFromErr ("failed to get GitHub client" ,err ),nil ,nil
58+ }
5659
57- user ,res ,err := client .Users .Get (ctx ,"" )
58- if err != nil {
59- return ghErrors .NewGitHubAPIErrorResponse (ctx ,
60- "failed to get user" ,
61- res ,
62- err ,
63- ),nil ,err
64- }
60+ user ,res ,err := client .Users .Get (ctx ,"" )
61+ if err != nil {
62+ return ghErrors .NewGitHubAPIErrorResponse (ctx ,
63+ "failed to get user" ,
64+ res ,
65+ err ,
66+ ),nil ,nil
67+ }
6568
66- // Create minimal user representation instead of returning full user object
67- minimalUser := MinimalUser {
68- Login :user .GetLogin (),
69- ID :user .GetID (),
70- ProfileURL :user .GetHTMLURL (),
71- AvatarURL :user .GetAvatarURL (),
72- Details :& UserDetails {
73- Name :user .GetName (),
74- Company :user .GetCompany (),
75- Blog :user .GetBlog (),
76- Location :user .GetLocation (),
77- Email :user .GetEmail (),
78- Hireable :user .GetHireable (),
79- Bio :user .GetBio (),
80- TwitterUsername :user .GetTwitterUsername (),
81- PublicRepos :user .GetPublicRepos (),
82- PublicGists :user .GetPublicGists (),
83- Followers :user .GetFollowers (),
84- Following :user .GetFollowing (),
85- CreatedAt :user .GetCreatedAt ().Time ,
86- UpdatedAt :user .GetUpdatedAt ().Time ,
87- PrivateGists :user .GetPrivateGists (),
88- TotalPrivateRepos :user .GetTotalPrivateRepos (),
89- OwnedPrivateRepos :user .GetOwnedPrivateRepos (),
90- },
91- }
69+ // Create minimal user representation instead of returning full user object
70+ minimalUser := MinimalUser {
71+ Login :user .GetLogin (),
72+ ID :user .GetID (),
73+ ProfileURL :user .GetHTMLURL (),
74+ AvatarURL :user .GetAvatarURL (),
75+ Details :& UserDetails {
76+ Name :user .GetName (),
77+ Company :user .GetCompany (),
78+ Blog :user .GetBlog (),
79+ Location :user .GetLocation (),
80+ Email :user .GetEmail (),
81+ Hireable :user .GetHireable (),
82+ Bio :user .GetBio (),
83+ TwitterUsername :user .GetTwitterUsername (),
84+ PublicRepos :user .GetPublicRepos (),
85+ PublicGists :user .GetPublicGists (),
86+ Followers :user .GetFollowers (),
87+ Following :user .GetFollowing (),
88+ CreatedAt :user .GetCreatedAt ().Time ,
89+ UpdatedAt :user .GetUpdatedAt ().Time ,
90+ PrivateGists :user .GetPrivateGists (),
91+ TotalPrivateRepos :user .GetTotalPrivateRepos (),
92+ OwnedPrivateRepos :user .GetOwnedPrivateRepos (),
93+ },
94+ }
9295
93- return MarshalledTextResult (minimalUser ),nil ,nil
94- })
96+ return MarshalledTextResult (minimalUser ),nil ,nil
97+ }
98+ },
99+ )
95100}
96101
97102type TeamInfo struct {
@@ -105,8 +110,9 @@ type OrganizationTeams struct {
105110Teams []TeamInfo `json:"teams"`
106111}
107112
108- func GetTeams (getClient GetClientFn ,getGQLClient GetGQLClientFn ,t translations.TranslationHelperFunc ) (mcp.Tool , mcp.ToolHandlerFor [map [string ]any ,any ]) {
109- return mcp.Tool {
113+ func GetTeams (t translations.TranslationHelperFunc ) toolsets.ServerTool {
114+ return NewTool (
115+ mcp.Tool {
110116Name :"get_teams" ,
111117Description :t ("TOOL_GET_TEAMS_DESCRIPTION" ,"Get details of the teams the user is a member of. Limited to organizations accessible with current credentials" ),
112118Annotations :& mcp.ToolAnnotations {
@@ -123,84 +129,88 @@ func GetTeams(getClient GetClientFn, getGQLClient GetGQLClientFn, t translations
123129},
124130},
125131},
126- func (ctx context.Context ,_ * mcp.CallToolRequest ,args map [string ]any ) (* mcp.CallToolResult ,any ,error ) {
127- user ,err := OptionalParam [string ](args ,"user" )
128- if err != nil {
129- return utils .NewToolResultError (err .Error ()),nil ,nil
130- }
131-
132- var username string
133- if user != "" {
134- username = user
135- }else {
136- client ,err := getClient (ctx )
132+ func (deps ToolDependencies ) mcp.ToolHandlerFor [map [string ]any ,any ] {
133+ return func (ctx context.Context ,_ * mcp.CallToolRequest ,args map [string ]any ) (* mcp.CallToolResult ,any ,error ) {
134+ user ,err := OptionalParam [string ](args ,"user" )
137135if err != nil {
138- return utils .NewToolResultErrorFromErr ( "failed to get GitHub client" , err ),nil ,nil
136+ return utils .NewToolResultError ( err . Error () ),nil ,nil
139137}
140138
141- userResp ,res ,err := client .Users .Get (ctx ,"" )
139+ var username string
140+ if user != "" {
141+ username = user
142+ }else {
143+ client ,err := deps .GetClient (ctx )
144+ if err != nil {
145+ return utils .NewToolResultErrorFromErr ("failed to get GitHub client" ,err ),nil ,nil
146+ }
147+
148+ userResp ,res ,err := client .Users .Get (ctx ,"" )
149+ if err != nil {
150+ return ghErrors .NewGitHubAPIErrorResponse (ctx ,
151+ "failed to get user" ,
152+ res ,
153+ err ,
154+ ),nil ,nil
155+ }
156+ username = userResp .GetLogin ()
157+ }
158+
159+ gqlClient ,err := deps .GetGQLClient (ctx )
142160if err != nil {
143- return ghErrors .NewGitHubAPIErrorResponse (ctx ,
144- "failed to get user" ,
145- res ,
146- err ,
147- ),nil ,nil
161+ return utils .NewToolResultErrorFromErr ("failed to get GitHub GQL client" ,err ),nil ,nil
148162}
149- username = userResp .GetLogin ()
150- }
151163
152- gqlClient ,err := getGQLClient (ctx )
153- if err != nil {
154- return utils .NewToolResultErrorFromErr ("failed to get GitHub GQL client" ,err ),nil ,nil
155- }
164+ var q struct {
165+ User struct {
166+ Organizations struct {
167+ Nodes []struct {
168+ Login githubv4.String
169+ Teams struct {
170+ Nodes []struct {
171+ Name githubv4.String
172+ Slug githubv4.String
173+ Description githubv4.String
174+ }
175+ }`graphql:"teams(first: 100, userLogins: [$login])"`
176+ }
177+ }`graphql:"organizations(first: 100)"`
178+ }`graphql:"user(login: $login)"`
179+ }
180+ vars := map [string ]interface {}{
181+ "login" :githubv4 .String (username ),
182+ }
183+ if err := gqlClient .Query (ctx ,& q ,vars );err != nil {
184+ return ghErrors .NewGitHubGraphQLErrorResponse (ctx ,"Failed to find teams" ,err ),nil ,nil
185+ }
156186
157- var q struct {
158- User struct {
159- Organizations struct {
160- Nodes []struct {
161- Login githubv4.String
162- Teams struct {
163- Nodes []struct {
164- Name githubv4.String
165- Slug githubv4.String
166- Description githubv4.String
167- }
168- }`graphql:"teams(first: 100, userLogins: [$login])"`
169- }
170- }`graphql:"organizations(first: 100)"`
171- }`graphql:"user(login: $login)"`
172- }
173- vars := map [string ]interface {}{
174- "login" :githubv4 .String (username ),
175- }
176- if err := gqlClient .Query (ctx ,& q ,vars );err != nil {
177- return ghErrors .NewGitHubGraphQLErrorResponse (ctx ,"Failed to find teams" ,err ),nil ,nil
178- }
187+ var organizations []OrganizationTeams
188+ for _ ,org := range q .User .Organizations .Nodes {
189+ orgTeams := OrganizationTeams {
190+ Org :string (org .Login ),
191+ Teams :make ([]TeamInfo ,0 ,len (org .Teams .Nodes )),
192+ }
179193
180- var organizations []OrganizationTeams
181- for _ ,org := range q .User .Organizations .Nodes {
182- orgTeams := OrganizationTeams {
183- Org :string (org .Login ),
184- Teams :make ([]TeamInfo ,0 ,len (org .Teams .Nodes )),
185- }
194+ for _ ,team := range org .Teams .Nodes {
195+ orgTeams .Teams = append (orgTeams .Teams ,TeamInfo {
196+ Name :string (team .Name ),
197+ Slug :string (team .Slug ),
198+ Description :string (team .Description ),
199+ })
200+ }
186201
187- for _ ,team := range org .Teams .Nodes {
188- orgTeams .Teams = append (orgTeams .Teams ,TeamInfo {
189- Name :string (team .Name ),
190- Slug :string (team .Slug ),
191- Description :string (team .Description ),
192- })
202+ organizations = append (organizations ,orgTeams )
193203}
194204
195- organizations = append (organizations , orgTeams )
205+ return MarshalledTextResult (organizations ), nil , nil
196206}
197-
198- return MarshalledTextResult (organizations ),nil ,nil
199- }
207+ },
208+ )
200209}
201210
202- func GetTeamMembers (getGQLClient GetGQLClientFn ,t translations.TranslationHelperFunc ) (mcp.Tool , mcp.ToolHandlerFor [map [string ]any ,any ]) {
203- return mcp.Tool {
211+ func GetTeamMembers (t translations.TranslationHelperFunc ) toolsets.ServerTool {
212+ return NewTool (
213+ mcp.Tool {
204214Name :"get_team_members" ,
205215Description :t ("TOOL_GET_TEAM_MEMBERS_DESCRIPTION" ,"Get member usernames of a specific team in an organization. Limited to organizations accessible with current credentials" ),
206216Annotations :& mcp.ToolAnnotations {
@@ -222,46 +232,49 @@ func GetTeamMembers(getGQLClient GetGQLClientFn, t translations.TranslationHelpe
222232Required : []string {"org" ,"team_slug" },
223233},
224234},
225- func (ctx context.Context ,_ * mcp.CallToolRequest ,args map [string ]any ) (* mcp.CallToolResult ,any ,error ) {
226- org ,err := RequiredParam [string ](args ,"org" )
227- if err != nil {
228- return utils .NewToolResultError (err .Error ()),nil ,nil
229- }
235+ func (deps ToolDependencies ) mcp.ToolHandlerFor [map [string ]any ,any ] {
236+ return func (ctx context.Context ,_ * mcp.CallToolRequest ,args map [string ]any ) (* mcp.CallToolResult ,any ,error ) {
237+ org ,err := RequiredParam [string ](args ,"org" )
238+ if err != nil {
239+ return utils .NewToolResultError (err .Error ()),nil ,nil
240+ }
230241
231- teamSlug ,err := RequiredParam [string ](args ,"team_slug" )
232- if err != nil {
233- return utils .NewToolResultError (err .Error ()),nil ,nil
234- }
242+ teamSlug ,err := RequiredParam [string ](args ,"team_slug" )
243+ if err != nil {
244+ return utils .NewToolResultError (err .Error ()),nil ,nil
245+ }
235246
236- gqlClient ,err := getGQLClient (ctx )
237- if err != nil {
238- return utils .NewToolResultErrorFromErr ("failed to get GitHub GQL client" ,err ),nil ,nil
239- }
247+ gqlClient ,err := deps . GetGQLClient (ctx )
248+ if err != nil {
249+ return utils .NewToolResultErrorFromErr ("failed to get GitHub GQL client" ,err ),nil ,nil
250+ }
240251
241- var q struct {
242- Organization struct {
243- Team struct {
244- Members struct {
245- Nodes []struct {
246- Login githubv4.String
247- }
248- }`graphql:"members(first: 100)"`
249- }`graphql:"team(slug: $teamSlug)"`
250- }`graphql:"organization(login: $org)"`
251- }
252- vars := map [string ]interface {}{
253- "org" :githubv4 .String (org ),
254- "teamSlug" :githubv4 .String (teamSlug ),
255- }
256- if err := gqlClient .Query (ctx ,& q ,vars );err != nil {
257- return ghErrors .NewGitHubGraphQLErrorResponse (ctx ,"Failed to get team members" ,err ),nil ,nil
258- }
252+ var q struct {
253+ Organization struct {
254+ Team struct {
255+ Members struct {
256+ Nodes []struct {
257+ Login githubv4.String
258+ }
259+ }`graphql:"members(first: 100)"`
260+ }`graphql:"team(slug: $teamSlug)"`
261+ }`graphql:"organization(login: $org)"`
262+ }
263+ vars := map [string ]interface {}{
264+ "org" :githubv4 .String (org ),
265+ "teamSlug" :githubv4 .String (teamSlug ),
266+ }
267+ if err := gqlClient .Query (ctx ,& q ,vars );err != nil {
268+ return ghErrors .NewGitHubGraphQLErrorResponse (ctx ,"Failed to get team members" ,err ),nil ,nil
269+ }
259270
260- var members []string
261- for _ ,member := range q .Organization .Team .Members .Nodes {
262- members = append (members ,string (member .Login ))
263- }
271+ var members []string
272+ for _ ,member := range q .Organization .Team .Members .Nodes {
273+ members = append (members ,string (member .Login ))
274+ }
264275
265- return MarshalledTextResult (members ),nil ,nil
266- }
276+ return MarshalledTextResult (members ),nil ,nil
277+ }
278+ },
279+ )
267280}