@@ -174,7 +174,7 @@ func TestContextToolsTypedRegistration(t *testing.T) {
174174 mockUser := & github.User {
175175 Login : new ("testuser "),
176176 HTMLURL : new ("https ://github.com/testuser"),
177- CreatedAt : & github.Timestamp {Time : time .Now ( )},
177+ CreatedAt : & github.Timestamp {Time : time .Date ( 2020 , 1 , 2 , 3 , 4 , 5 , 0 , time . UTC )},
178178 }
179179 teamsMatcher := githubv4mock .NewQueryMatcher (
180180 "query($login:String!){user(login: $login){organizations(first: 100){nodes{login,teams(first: 100, userLogins: [$login]){nodes{name,slug,description}}}}}}" ,
@@ -209,6 +209,29 @@ func TestContextToolsTypedRegistration(t *testing.T) {
209209 },
210210 }),
211211 )
212+ emptyOrganizationsMatcher := githubv4mock .NewQueryMatcher (
213+ "query($login:String!){user(login: $login){organizations(first: 100){nodes{login,teams(first: 100, userLogins: [$login]){nodes{name,slug,description}}}}}}" ,
214+ map [string ]any {"login" : "no-orgs" },
215+ githubv4mock .DataResponse (map [string ]any {
216+ "user" : map [string ]any {"organizations" : map [string ]any {"nodes" : []any {}}},
217+ }),
218+ )
219+ emptyTeamsMatcher := githubv4mock .NewQueryMatcher (
220+ "query($login:String!){user(login: $login){organizations(first: 100){nodes{login,teams(first: 100, userLogins: [$login]){nodes{name,slug,description}}}}}}" ,
221+ map [string ]any {"login" : "no-teams" },
222+ githubv4mock .DataResponse (map [string ]any {
223+ "user" : map [string ]any {"organizations" : map [string ]any {"nodes" : []any {
224+ map [string ]any {"login" : "testorg" , "teams" : map [string ]any {"nodes" : []any {}}},
225+ }}},
226+ }),
227+ )
228+ emptyMembersMatcher := githubv4mock .NewQueryMatcher (
229+ "query($org:String!$teamSlug:String!){organization(login: $org){team(slug: $teamSlug){members(first: 100){nodes{login}}}}}" ,
230+ map [string ]any {"org" : "testorg" , "teamSlug" : "emptyteam" },
231+ githubv4mock .DataResponse (map [string ]any {
232+ "organization" : map [string ]any {"team" : map [string ]any {"members" : map [string ]any {"nodes" : []any {}}}},
233+ }),
234+ )
212235 mockedGQLClient := githubv4 .NewClient (githubv4mock .NewMockedHTTPClient (teamsMatcher , membersMatcher ))
213236 failGetMe := false
214237 var graphQLCalls int
@@ -251,9 +274,16 @@ func TestContextToolsTypedRegistration(t *testing.T) {
251274 list , err := clientSession .ListTools (context .Background (), nil )
252275 require .NoError (t , err )
253276 require .Len (t , list .Tools , 3 )
277+ outputSchemas := make (map [string ]* jsonschema.Resolved )
254278 for _ , tool := range list .Tools {
255279 assert .NotNil (t , tool .InputSchema )
256280 assert .NotNil (t , tool .OutputSchema )
281+ outputJSON , err := json .Marshal (tool .OutputSchema )
282+ require .NoError (t , err )
283+ var outputSchema jsonschema.Schema
284+ require .NoError (t , json .Unmarshal (outputJSON , & outputSchema ))
285+ outputSchemas [tool .Name ], err = outputSchema .Resolve (nil )
286+ require .NoError (t , err )
257287 schemaJSON , err := json .Marshal (tool .InputSchema )
258288 require .NoError (t , err )
259289 var schema struct {
@@ -296,6 +326,8 @@ func TestContextToolsTypedRegistration(t *testing.T) {
296326 legacyText , err := json .Marshal (returnedUser )
297327 require .NoError (t , err )
298328 assert .Equal (t , string (legacyText ), getTextResult (t , result ).Text )
329+ assert .Equal (t , `{"login":"testuser","profile_url":"https://github.com/testuser","details":{"public_repos":0,"public_gists":0,"followers":0,"following":0,"created_at":"2020-01-02T03:04:05Z","updated_at":"0001-01-01T00:00:00Z"}}` , getTextResult (t , result ).Text )
330+ require .NoError (t , outputSchemas ["get_me" ].Validate (result .StructuredContent ))
299331
300332 result , err = clientSession .CallTool (context .Background (), & mcp.CallToolParams {
301333 Name : "get_teams" ,
@@ -307,6 +339,7 @@ func TestContextToolsTypedRegistration(t *testing.T) {
307339 require .NoError (t , err )
308340 assert .JSONEq (t , `[{"org":"testorg","teams":[{"name":"team1","slug":"team1","description":"Team 1"}]}]` , string (structuredJSON ))
309341 assert .Equal (t , `[{"org":"testorg","teams":[{"name":"team1","slug":"team1","description":"Team 1"}]}]` , getTextResult (t , result ).Text )
342+ require .NoError (t , outputSchemas ["get_teams" ].Validate (result .StructuredContent ))
310343 assert .Equal (t , 1 , graphQLCalls )
311344
312345 result , err = clientSession .CallTool (context .Background (), & mcp.CallToolParams {
@@ -319,6 +352,7 @@ func TestContextToolsTypedRegistration(t *testing.T) {
319352 require .NoError (t , err )
320353 assert .JSONEq (t , `["user1","user2"]` , string (structuredJSON ))
321354 assert .Equal (t , `["user1","user2"]` , getTextResult (t , result ).Text )
355+ require .NoError (t , outputSchemas ["get_team_members" ].Validate (result .StructuredContent ))
322356 assert .Equal (t , 2 , graphQLCalls )
323357
324358 result , err = clientSession .CallTool (context .Background (), & mcp.CallToolParams {
@@ -337,6 +371,28 @@ func TestContextToolsTypedRegistration(t *testing.T) {
337371 assert .True (t , result .IsError , "empty required strings should remain invalid" )
338372 assert .Equal (t , 2 , graphQLCalls , "schema validation must reject empty required strings before the handler" )
339373
374+ for _ , tc := range []struct {
375+ name string
376+ args map [string ]any
377+ text string
378+ matcher githubv4mock.Matcher
379+ }{
380+ {"get_teams" , map [string ]any {"user" : "no-orgs" }, "null" , emptyOrganizationsMatcher },
381+ {"get_teams" , map [string ]any {"user" : "no-teams" }, `[{"org":"testorg","teams":[]}]` , emptyTeamsMatcher },
382+ {"get_team_members" , map [string ]any {"org" : "testorg" , "team_slug" : "emptyteam" }, "null" , emptyMembersMatcher },
383+ } {
384+ mockedGQLClient = githubv4 .NewClient (githubv4mock .NewMockedHTTPClient (tc .matcher ))
385+ result , err := clientSession .CallTool (context .Background (), & mcp.CallToolParams {Name : tc .name , Arguments : tc .args })
386+ require .NoError (t , err )
387+ require .False (t , result .IsError )
388+ require .Len (t , result .Content , 1 , "SDK fallback must not duplicate the legacy text" )
389+ assert .Equal (t , tc .text , getTextResult (t , result ).Text )
390+ structuredJSON , err := json .Marshal (result .StructuredContent )
391+ require .NoError (t , err )
392+ assert .JSONEq (t , tc .text , string (structuredJSON ))
393+ require .NoError (t , outputSchemas [tc .name ].Validate (result .StructuredContent ))
394+ }
395+
340396 failGetMe = true
341397 result , err = clientSession .CallTool (context .Background (), & mcp.CallToolParams {Name : "get_me" })
342398 require .NoError (t , err )
0 commit comments