diff --git a/README.md b/README.md index 352bb50e..ff822650 100644 --- a/README.md +++ b/README.md @@ -496,6 +496,15 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description - `page`: Page number (number, optional) - `perPage`: Results per page (number, optional) +- **is_repository_starred** - Check if a repository is starred by the authenticated user + - `owner`: Repository owner (string, required) + - `repo`: Repository name (string, required) + +- **toggle_repository_star** - Star or unstar a repository for the authenticated user + - `owner`: Repository owner (string, required) + - `repo`: Repository name (string, required) + - `star`: True to star, false to unstar the repository (boolean, required) + - **create_repository** - Create a new GitHub repository - `name`: Repository name (string, required) - `description`: Repository description (string, optional) @@ -550,6 +559,12 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description - `page`: Page number (number, optional) - `perPage`: Results per page (number, optional) +- **list_starred_repositories** - List repositories starred by the authenticated user + - `sort`: How to sort the results ('created' or 'updated') (string, optional) + - `direction`: Direction to sort ('asc' or 'desc') (string, optional) + - `page`: Page number (number, optional) + - `perPage`: Results per page (number, optional) + ### Code Scanning - **get_code_scanning_alert** - Get a code scanning alert diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 4403e2a1..018cf68e 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -556,6 +556,132 @@ func ForkRepository(getClient GetClientFn, t translations.TranslationHelperFunc) } } +// IsRepositoryStarred creates a tool to check if a repository is starred by the authenticated user. +func IsRepositoryStarred(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { + return mcp.NewTool("is_repository_starred", + mcp.WithDescription(t("TOOL_IS_REPOSITORY_STARRED_DESCRIPTION", "Check if a GitHub repository is starred by the authenticated user")), + mcp.WithToolAnnotation(mcp.ToolAnnotation{ + Title: t("TOOL_IS_REPOSITORY_STARRED_USER_TITLE", "Check if repository is starred"), + ReadOnlyHint: toBoolPtr(true), + }), + mcp.WithString("owner", + mcp.Required(), + mcp.Description("Repository owner"), + ), + mcp.WithString("repo", + mcp.Required(), + mcp.Description("Repository name"), + ), + ), + func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + owner, err := requiredParam[string](request, "owner") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + repo, err := requiredParam[string](request, "repo") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + + client, err := getClient(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get GitHub client: %w", err) + } + + isStarred, resp, err := client.Activity.IsStarred(ctx, owner, repo) + if err != nil && resp == nil { + return nil, fmt.Errorf("failed to check if repository is starred: %w", err) + } + if resp != nil { + defer func() { _ = resp.Body.Close() }() + } + + return mcp.NewToolResultText(fmt.Sprintf(`{"is_starred": %t}`, isStarred)), nil + } +} + +// ToggleRepositoryStar creates a tool to star or unstar a repository. +func ToggleRepositoryStar(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { + return mcp.NewTool("toggle_repository_star", + mcp.WithDescription(t("TOOL_TOGGLE_REPOSITORY_STAR_DESCRIPTION", "Star or unstar a GitHub repository with your account")), + mcp.WithToolAnnotation(mcp.ToolAnnotation{ + Title: t("TOOL_TOGGLE_REPOSITORY_STAR_USER_TITLE", "Star/unstar repository"), + ReadOnlyHint: toBoolPtr(false), + }), + mcp.WithString("owner", + mcp.Required(), + mcp.Description("Repository owner"), + ), + mcp.WithString("repo", + mcp.Required(), + mcp.Description("Repository name"), + ), + mcp.WithString("star", + mcp.Required(), + mcp.Description("'true' to star, 'false' to unstar the repository"), + ), + ), + func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + owner, err := requiredParam[string](request, "owner") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + repo, err := requiredParam[string](request, "repo") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + starStr, err := requiredParam[string](request, "star") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + + var star bool + switch starStr { + case "true": + star = true + case "false": + star = false + default: + return mcp.NewToolResultError("parameter 'star' must be exactly 'true' or 'false'"), nil + } + + client, err := getClient(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get GitHub client: %w", err) + } + + var resp *github.Response + var action string + + if star { + resp, err = client.Activity.Star(ctx, owner, repo) + action = "star" + } else { + resp, err = client.Activity.Unstar(ctx, owner, repo) + action = "unstar" + } + + if err != nil { + return nil, fmt.Errorf("failed to %s repository: %w", action, err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusNoContent { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + return mcp.NewToolResultError(fmt.Sprintf("failed to %s repository: %s", action, string(body))), nil + } + + resultAction := "starred" + if !star { + resultAction = "unstarred" + } + return mcp.NewToolResultText(fmt.Sprintf("Successfully %s repository %s/%s", resultAction, owner, repo)), nil + } +} + // DeleteFile creates a tool to delete a file in a GitHub repository. // This tool uses a more roundabout way of deleting a file than just using the client.Repositories.DeleteFile. // This is because REST file deletion endpoint (and client.Repositories.DeleteFile) don't add commit signing to the deletion commit, diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index e4edeee8..1f72ca90 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -1963,3 +1963,268 @@ func Test_GetTag(t *testing.T) { }) } } + +func Test_IsRepositoryStarred(t *testing.T) { + // Verify tool definition + mockClient := github.NewClient(nil) + tool, _ := IsRepositoryStarred(stubGetClientFn(mockClient), translations.NullTranslationHelper) + + assert.Equal(t, "is_repository_starred", tool.Name) + assert.NotEmpty(t, tool.Description) + assert.Contains(t, tool.InputSchema.Properties, "owner") + assert.Contains(t, tool.InputSchema.Properties, "repo") + assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo"}) + + tests := []struct { + name string + mockedClient *http.Client + requestArgs map[string]interface{} + expectError bool + expectedErrMsg string + isStarred bool + }{ + { + name: "repository is starred", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{ + Pattern: "/user/starred/owner/repo", + Method: "GET", + }, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) // Status code for "is starred" = true + }), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + }, + expectError: false, + isStarred: true, + }, + { + name: "repository is not starred", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{ + Pattern: "/user/starred/owner/repo", + Method: "GET", + }, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) // Status code for "is starred" = false + }), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + }, + expectError: false, + isStarred: false, + }, + { + name: "check starred fails with unauthorized", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{ + Pattern: "/user/starred/owner/repo", + Method: "GET", + }, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte(`{"message": "Requires authentication"}`)) + }), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + }, + expectError: false, // The GitHub API returns false for not authenticated, not an error + isStarred: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Setup client with mock + client := github.NewClient(tc.mockedClient) + _, handler := IsRepositoryStarred(stubGetClientFn(client), translations.NullTranslationHelper) + + // Create call request + request := createMCPRequest(tc.requestArgs) + + // Call handler + result, err := handler(context.Background(), request) + + // Verify results + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.expectedErrMsg) + return + } + + require.NoError(t, err) + + // Parse the result and get the text content if no error + textContent := getTextResult(t, result) + + // Check if the result contains the correct starred status + var expectedResult string + if tc.isStarred { + expectedResult = `{"is_starred": true}` + } else { + expectedResult = `{"is_starred": false}` + } + assert.Contains(t, textContent.Text, expectedResult) + }) + } +} + +func Test_ToggleRepositoryStar(t *testing.T) { + // Verify tool definition + mockClient := github.NewClient(nil) + tool, _ := ToggleRepositoryStar(stubGetClientFn(mockClient), translations.NullTranslationHelper) + + assert.Equal(t, "toggle_repository_star", tool.Name) + assert.NotEmpty(t, tool.Description) + assert.Contains(t, tool.InputSchema.Properties, "owner") + assert.Contains(t, tool.InputSchema.Properties, "repo") + assert.Contains(t, tool.InputSchema.Properties, "star") + assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "star"}) + + tests := []struct { + name string + mockedClient *http.Client + requestArgs map[string]interface{} + expectError bool + expectedErrMsg string + expectedResult string + }{ + { + name: "successfully star repository", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{ + Pattern: "/user/starred/owner/repo", + Method: "PUT", + }, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "star": "true", + }, + expectError: false, + expectedResult: "Successfully starred repository owner/repo", + }, + { + name: "successfully unstar repository", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{ + Pattern: "/user/starred/owner/repo", + Method: "DELETE", + }, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "star": "false", + }, + expectError: false, + expectedResult: "Successfully unstarred repository owner/repo", + }, + { + name: "star repository fails with unauthorized", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{ + Pattern: "/user/starred/owner/repo", + Method: "PUT", + }, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte(`{"message": "Requires authentication"}`)) + }), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "star": "true", + }, + expectError: true, + expectedErrMsg: "failed to star repository", + }, + { + name: "unstar repository fails with unauthorized", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{ + Pattern: "/user/starred/owner/repo", + Method: "DELETE", + }, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte(`{"message": "Requires authentication"}`)) + }), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "star": "false", + }, + expectError: true, + expectedErrMsg: "failed to unstar repository", + }, + { + name: "invalid star parameter", + mockedClient: mock.NewMockedHTTPClient(), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "star": "invalid", + }, + expectError: false, // We expect a tool error, not a Go error + expectedResult: "parameter 'star' must be exactly 'true' or 'false'", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Setup client with mock + client := github.NewClient(tc.mockedClient) + _, handler := ToggleRepositoryStar(stubGetClientFn(client), translations.NullTranslationHelper) + + // Create call request + request := createMCPRequest(tc.requestArgs) + + // Call handler + result, err := handler(context.Background(), request) + + // Verify results + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.expectedErrMsg) + return + } + + require.NoError(t, err) + + // Parse the result and get the text content if no error + textContent := getTextResult(t, result) + assert.Equal(t, tc.expectedResult, textContent.Text) + }) + } +} diff --git a/pkg/github/search.go b/pkg/github/search.go index ac5e2994..7159137e 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -222,3 +222,116 @@ func SearchUsers(getClient GetClientFn, t translations.TranslationHelperFunc) (t return mcp.NewToolResultText(string(r)), nil } } + +// ListStarredRepositories creates a tool to list repositories starred by the authenticated user. +func ListStarredRepositories(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { + return mcp.NewTool("list_starred_repositories", + mcp.WithDescription(t("TOOL_LIST_STARRED_REPOSITORIES_DESCRIPTION", "List repositories starred by the authenticated user")), + mcp.WithToolAnnotation(mcp.ToolAnnotation{ + Title: t("TOOL_LIST_STARRED_REPOSITORIES_USER_TITLE", "List starred repositories"), + ReadOnlyHint: toBoolPtr(true), + }), + mcp.WithString("sort", + mcp.Description("How to sort the results"), + mcp.Enum("created", "updated"), + ), + mcp.WithString("direction", + mcp.Description("Direction to sort"), + mcp.Enum("asc", "desc"), + ), + WithPagination(), + ), + func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + sort, err := OptionalParam[string](request, "sort") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + direction, err := OptionalParam[string](request, "direction") + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + pagination, err := OptionalPaginationParams(request) + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + + opts := &github.ActivityListStarredOptions{ + Sort: sort, + Direction: direction, + ListOptions: github.ListOptions{ + Page: pagination.page, + PerPage: pagination.perPage, + }, + } + + client, err := getClient(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get GitHub client: %w", err) + } + + // Empty string for user parameter means the authenticated user + starredRepos, resp, err := client.Activity.ListStarred(ctx, "", opts) + if err != nil { + return nil, fmt.Errorf("failed to list starred repositories: %w", err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != 200 { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + return mcp.NewToolResultError(fmt.Sprintf("failed to list starred repositories: %s", string(body))), nil + } + + // Filter results to only include starred_at, repo.full_name, repo.html_url, repo.description, repo.stargazers_count, repo.language + // This saves context tokens, further information can be requested via repository_info tool + type SimplifiedRepo struct { + FullName string `json:"full_name,omitempty"` + HTMLURL string `json:"html_url,omitempty"` + Description string `json:"description,omitempty"` + StargazersCount int `json:"stargazers_count,omitempty"` + Language string `json:"language,omitempty"` + } + + type SimplifiedStarredRepo struct { + StarredAt *github.Timestamp `json:"starred_at,omitempty"` + Repository SimplifiedRepo `json:"repository,omitempty"` + } + + filteredRepos := make([]SimplifiedStarredRepo, 0, len(starredRepos)) + for _, repo := range starredRepos { + simplifiedRepo := SimplifiedStarredRepo{ + StarredAt: repo.StarredAt, + Repository: SimplifiedRepo{}, + } + + if repo.Repository != nil { + if repo.Repository.FullName != nil { + simplifiedRepo.Repository.FullName = *repo.Repository.FullName + } + if repo.Repository.HTMLURL != nil { + simplifiedRepo.Repository.HTMLURL = *repo.Repository.HTMLURL + } + if repo.Repository.Description != nil { + simplifiedRepo.Repository.Description = *repo.Repository.Description + } + if repo.Repository.StargazersCount != nil { + simplifiedRepo.Repository.StargazersCount = *repo.Repository.StargazersCount + } + if repo.Repository.Language != nil { + simplifiedRepo.Repository.Language = *repo.Repository.Language + } + } + + filteredRepos = append(filteredRepos, simplifiedRepo) + } + + r, err := json.Marshal(filteredRepos) + if err != nil { + return nil, fmt.Errorf("failed to marshal response: %w", err) + } + + return mcp.NewToolResultText(string(r)), nil + } +} diff --git a/pkg/github/search_test.go b/pkg/github/search_test.go index b61518e4..ba353fa5 100644 --- a/pkg/github/search_test.go +++ b/pkg/github/search_test.go @@ -311,6 +311,212 @@ func Test_SearchCode(t *testing.T) { } } +func Test_ListStarredRepositories(t *testing.T) { + // Verify tool definition + mockClient := github.NewClient(nil) + tool, _ := ListStarredRepositories(stubGetClientFn(mockClient), translations.NullTranslationHelper) + + assert.Equal(t, "list_starred_repositories", tool.Name) + assert.NotEmpty(t, tool.Description) + assert.Contains(t, tool.InputSchema.Properties, "sort") + assert.Contains(t, tool.InputSchema.Properties, "direction") + assert.Contains(t, tool.InputSchema.Properties, "page") + assert.Contains(t, tool.InputSchema.Properties, "perPage") + assert.Empty(t, tool.InputSchema.Required) // No required parameters + + // Setup mock starred repositories results + mockStarredRepos := []*github.StarredRepository{ + { + StarredAt: &github.Timestamp{}, + Repository: &github.Repository{ + ID: github.Ptr(int64(12345)), + Name: github.Ptr("repo-1"), + FullName: github.Ptr("owner/repo-1"), + HTMLURL: github.Ptr("https://github.com/owner/repo-1"), + Description: github.Ptr("Test repository 1"), + StargazersCount: github.Ptr(100), + Language: github.Ptr("Go"), + Fork: github.Ptr(false), + }, + }, + { + StarredAt: &github.Timestamp{}, + Repository: &github.Repository{ + ID: github.Ptr(int64(67890)), + Name: github.Ptr("repo-2"), + FullName: github.Ptr("owner/repo-2"), + HTMLURL: github.Ptr("https://github.com/owner/repo-2"), + Description: github.Ptr("Test repository 2"), + StargazersCount: github.Ptr(50), + Language: github.Ptr("JavaScript"), + Fork: github.Ptr(true), + }, + }, + } + + type SimplifiedRepo struct { + FullName string `json:"full_name,omitempty"` + HTMLURL string `json:"html_url,omitempty"` + Description string `json:"description,omitempty"` + StargazersCount int `json:"stargazers_count,omitempty"` + Language string `json:"language,omitempty"` + } + + type SimplifiedStarredRepo struct { + StarredAt *github.Timestamp `json:"starred_at,omitempty"` + Repository SimplifiedRepo `json:"repository,omitempty"` + } + + expectedFilteredRepos := []SimplifiedStarredRepo{ + { + StarredAt: &github.Timestamp{}, + Repository: SimplifiedRepo{ + FullName: "owner/repo-1", + HTMLURL: "https://github.com/owner/repo-1", + Description: "Test repository 1", + StargazersCount: 100, + Language: "Go", + }, + }, + { + StarredAt: &github.Timestamp{}, + Repository: SimplifiedRepo{ + FullName: "owner/repo-2", + HTMLURL: "https://github.com/owner/repo-2", + Description: "Test repository 2", + StargazersCount: 50, + Language: "JavaScript", + }, + }, + } + + tests := []struct { + name string + mockedClient *http.Client + requestArgs map[string]interface{} + expectError bool + expectedResult []SimplifiedStarredRepo + expectedErrMsg string + }{ + { + name: "successful starred repositories list with all parameters", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetUserStarred, + expectQueryParams(t, map[string]string{ + "sort": "created", + "direction": "desc", + "page": "2", + "per_page": "10", + }).andThen( + mockResponse(t, http.StatusOK, mockStarredRepos), + ), + ), + ), + requestArgs: map[string]interface{}{ + "sort": "created", + "direction": "desc", + "page": float64(2), + "perPage": float64(10), + }, + expectError: false, + expectedResult: expectedFilteredRepos, + }, + { + name: "list starred repositories with default parameters", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetUserStarred, + expectQueryParams(t, map[string]string{ + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockStarredRepos), + ), + ), + ), + requestArgs: map[string]interface{}{}, + expectError: false, + expectedResult: expectedFilteredRepos, + }, + { + name: "list starred repositories with sort only", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetUserStarred, + expectQueryParams(t, map[string]string{ + "sort": "updated", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockStarredRepos), + ), + ), + ), + requestArgs: map[string]interface{}{ + "sort": "updated", + }, + expectError: false, + expectedResult: expectedFilteredRepos, + }, + { + name: "list starred repositories fails", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetUserStarred, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte(`{"message": "Requires authentication"}`)) + }), + ), + ), + requestArgs: map[string]interface{}{}, + expectError: true, + expectedErrMsg: "failed to list starred repositories", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Setup client with mock + client := github.NewClient(tc.mockedClient) + _, handler := ListStarredRepositories(stubGetClientFn(client), translations.NullTranslationHelper) + + // Create call request + request := createMCPRequest(tc.requestArgs) + + // Call handler + result, err := handler(context.Background(), request) + + // Verify results + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.expectedErrMsg) + return + } + + require.NoError(t, err) + + // Parse the result and get the text content if no error + textContent := getTextResult(t, result) + + // Unmarshal and verify the result + var returnedResult []SimplifiedStarredRepo + err = json.Unmarshal([]byte(textContent.Text), &returnedResult) + require.NoError(t, err) + assert.Len(t, returnedResult, len(tc.expectedResult)) + + for i, repo := range returnedResult { + assert.Equal(t, tc.expectedResult[i].Repository.FullName, repo.Repository.FullName) + assert.Equal(t, tc.expectedResult[i].Repository.HTMLURL, repo.Repository.HTMLURL) + assert.Equal(t, tc.expectedResult[i].Repository.Description, repo.Repository.Description) + assert.Equal(t, tc.expectedResult[i].Repository.StargazersCount, repo.Repository.StargazersCount) + assert.Equal(t, tc.expectedResult[i].Repository.Language, repo.Repository.Language) + } + }) + } +} + func Test_SearchUsers(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) diff --git a/pkg/github/tools.go b/pkg/github/tools.go index cd379ebb..3b9104e4 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -31,6 +31,7 @@ func InitToolsets(passedToolsets []string, readOnly bool, getClient GetClientFn, toolsets.NewServerTool(ListBranches(getClient, t)), toolsets.NewServerTool(ListTags(getClient, t)), toolsets.NewServerTool(GetTag(getClient, t)), + toolsets.NewServerTool(IsRepositoryStarred(getClient, t)), ). AddWriteTools( toolsets.NewServerTool(CreateOrUpdateFile(getClient, t)), @@ -39,6 +40,7 @@ func InitToolsets(passedToolsets []string, readOnly bool, getClient GetClientFn, toolsets.NewServerTool(CreateBranch(getClient, t)), toolsets.NewServerTool(PushFiles(getClient, t)), toolsets.NewServerTool(DeleteFile(getClient, t)), + toolsets.NewServerTool(ToggleRepositoryStar(getClient, t)), ) issues := toolsets.NewToolset("issues", "GitHub Issues related tools"). AddReadTools( @@ -56,6 +58,7 @@ func InitToolsets(passedToolsets []string, readOnly bool, getClient GetClientFn, users := toolsets.NewToolset("users", "GitHub User related tools"). AddReadTools( toolsets.NewServerTool(SearchUsers(getClient, t)), + toolsets.NewServerTool(ListStarredRepositories(getClient, t)), ) pullRequests := toolsets.NewToolset("pull_requests", "GitHub Pull Request related tools"). AddReadTools(