From f7b2227569f65e104d4ae04f86e9a5d943e0b3f3 Mon Sep 17 00:00:00 2001 From: Erel Vanono Date: Thu, 29 May 2025 21:57:24 +0300 Subject: [PATCH 1/3] Implement new tool to update repository metadata --- pkg/github/repositories.go | 84 ++++++++++++++++++++++++++++++++++++++ pkg/github/tools.go | 1 + 2 files changed, 85 insertions(+) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 8c3371632..451676414 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -408,6 +408,90 @@ func CreateRepository(getClient GetClientFn, t translations.TranslationHelperFun } } +// UpdateRepository update the metadata for GitHub repository +func UpdateRepository(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { + return mcp.NewTool("update_repository", + mcp.WithDescription(t("TOOL_UPDATE_REPOSITORY_DESCRIPTION", "Update a GitHub repository's metadata")), + mcp.WithToolAnnotation(mcp.ToolAnnotation{ + Title: t("TOOL_UPDATE_REPOSITORY_USER_TITLE", "Update repository"), + ReadOnlyHint: toBoolPtr(false), + }), + mcp.WithString("owner", + mcp.Required(), + mcp.Description("Repository owner (username or organization)"), + ), + mcp.WithString("repo", + mcp.Required(), + mcp.Description("Repository name"), + ), + mcp.WithString("name", + mcp.Description("The name to change the repository to"), + ), + mcp.WithString("description", + mcp.Description("A short description of the repository"), + ), + mcp.WithString("default_branch", + mcp.Description("The default branch of the repository"), + ), + mcp.WithBoolean("archived", + mcp.Description("Whether to archive this repository"), + ), + mcp.WithBoolean("allow_forking", + mcp.Description("Whether to allow forking this repository"), + ), + ), + func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + var paramsErr error + var owner, repo, name, description, defaultBranch string + var archived, allowForking bool + + owner, paramsErr = requiredParam[string](request, "owner") + repo, paramsErr = requiredParam[string](request, "repo") + name, paramsErr = OptionalParam[string](request, "name") + description, paramsErr = OptionalParam[string](request, "description") + defaultBranch, paramsErr = OptionalParam[string](request, "default_branch") + archived, paramsErr = OptionalParam[bool](request, "archived") + allowForking, paramsErr = OptionalParam[bool](request, "allow_forking") + + if paramsErr != nil { + return mcp.NewToolResultError(paramsErr.Error()), nil + } + + repoObj := &github.Repository{ + Name: github.Ptr(name), + Description: github.Ptr(description), + DefaultBranch: github.Ptr(defaultBranch), + Archived: github.Ptr(archived), + AllowForking: github.Ptr(allowForking), + } + + client, err := getClient(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get GitHub client: %w", err) + } + updatedRepo, resp, err := client.Repositories.Edit(ctx, owner, repo, repoObj) + if err != nil { + return nil, fmt.Errorf("failed to update repository: %w", err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + 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 update repository: %s", string(body))), nil + } + + r, err := json.Marshal(updatedRepo) + if err != nil { + return nil, fmt.Errorf("failed to marshal response: %w", err) + } + + return mcp.NewToolResultText(string(r)), nil + } +} + // GetFileContents creates a tool to get the contents of a file or directory from a GitHub repository. func GetFileContents(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_file_contents", diff --git a/pkg/github/tools.go b/pkg/github/tools.go index 9c1ab34af..f047a79d7 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -39,6 +39,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(UpdateRepository(getClient, t)), ) issues := toolsets.NewToolset("issues", "GitHub Issues related tools"). AddReadTools( From becfaa4e34c6d304d91d29ffe73cf147d6c5c918 Mon Sep 17 00:00:00 2001 From: Erel Vanono Date: Thu, 29 May 2025 22:43:57 +0300 Subject: [PATCH 2/3] Implement testing for the new update function --- pkg/github/repositories_test.go | 96 +++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index e4edeee88..8ff7732e1 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -464,6 +464,102 @@ func Test_CreateBranch(t *testing.T) { } } +func Test_UpdateRepository(t *testing.T) { + mockClient := github.NewClient(nil) + tool, _ := UpdateRepository(stubGetClientFn(mockClient), translations.NullTranslationHelper) + + assert.Equal(t, "update_repository", 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, "name") + assert.Contains(t, tool.InputSchema.Properties, "description") + assert.Contains(t, tool.InputSchema.Properties, "default_branch") + assert.Contains(t, tool.InputSchema.Properties, "archived") + assert.Contains(t, tool.InputSchema.Properties, "allow_forking") + + mockRepo := &github.Repository{ + ID: github.Ptr(int64(123456)), + Name: github.Ptr("new-repo"), + FullName: github.Ptr("owner/repo"), + DefaultBranch: github.Ptr("main"), + Archived: github.Ptr(true), + AllowForking: github.Ptr(true), + Description: github.Ptr("new description"), + } + + tests := []struct { + name string + mockedClient *http.Client + requestArgs map[string]interface{} + expectError bool + expectedRepo *github.Repository + expectedErrMsg string + }{ + { + name: "successful repository update", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.PatchReposByOwnerByRepo, + mockResponse(t, http.StatusOK, mockRepo), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "name": "new-repo", + "description": "new description", + "default_branch": "new-branch", + "archived": true, + "allow_forking": true, + }, + expectError: false, + expectedRepo: mockRepo, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Setup client with mock + client := github.NewClient(tc.mockedClient) + _, handler := UpdateRepository(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 returnedRepo github.Repository + err = json.Unmarshal([]byte(textContent.Text), &returnedRepo) + require.NoError(t, err) + assert.Equal(t, *tc.expectedRepo.ID, *returnedRepo.ID) + assert.Equal(t, *tc.expectedRepo.Name, *returnedRepo.Name) + assert.Equal(t, *tc.expectedRepo.FullName, *returnedRepo.FullName) + assert.Equal(t, *tc.expectedRepo.DefaultBranch, *returnedRepo.DefaultBranch) + assert.Equal(t, *tc.expectedRepo.Archived, *returnedRepo.Archived) + assert.Equal(t, *tc.expectedRepo.AllowForking, *returnedRepo.AllowForking) + assert.Equal(t, *tc.expectedRepo.Description, *returnedRepo.Description) + assert.Equal(t, *tc.expectedRepo.Archived, *returnedRepo.Archived) + assert.Equal(t, *tc.expectedRepo.AllowForking, *returnedRepo.AllowForking) + assert.Equal(t, *tc.expectedRepo.Description, *returnedRepo.Description) + }) + } +} + func Test_GetCommit(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil) From d3891ba0cb32b7eb034d942b64388a0d51132f9e Mon Sep 17 00:00:00 2001 From: erel213 <99283653+erel213@users.noreply.github.com> Date: Thu, 29 May 2025 22:47:44 +0300 Subject: [PATCH 3/3] Remove redundant assaertions Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/github/repositories_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index 8ff7732e1..e37b8ae42 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -553,9 +553,6 @@ func Test_UpdateRepository(t *testing.T) { assert.Equal(t, *tc.expectedRepo.Archived, *returnedRepo.Archived) assert.Equal(t, *tc.expectedRepo.AllowForking, *returnedRepo.AllowForking) assert.Equal(t, *tc.expectedRepo.Description, *returnedRepo.Description) - assert.Equal(t, *tc.expectedRepo.Archived, *returnedRepo.Archived) - assert.Equal(t, *tc.expectedRepo.AllowForking, *returnedRepo.AllowForking) - assert.Equal(t, *tc.expectedRepo.Description, *returnedRepo.Description) }) } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy