Skip to content

Commit c53d788

Browse files
committed
add support for the push_files tool
1 parent 7c772d2 commit c53d788

File tree

4 files changed

+431
-6
lines changed

4 files changed

+431
-6
lines changed

README.md

+8-6
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ and set it as the GITHUB_PERSONAL_ACCESS_TOKEN environment variable.
148148
- `branch`: Branch name (string, optional)
149149
- `sha`: File SHA if updating (string, optional)
150150

151+
- **push_files** - Push multiple files in a single commit
152+
153+
- `owner`: Repository owner (string, required)
154+
- `repo`: Repository name (string, required)
155+
- `branch`: Branch to push to (string, required)
156+
- `files`: Files to push, each with path and content (array, required)
157+
- `message`: Commit message (string, required)
158+
151159
- **search_repositories** - Search for GitHub repositories
152160

153161
- `query`: Search query (string, required)
@@ -385,12 +393,6 @@ I'd like to know more about my GitHub profile.
385393
386394
## TODO
387395
388-
Lots of things!
389-
390-
Missing tools:
391-
392-
- push_files (files array)
393-
394396
Testing
395397
396398
- Integration tests

pkg/github/repositories.go

+115
Original file line numberDiff line numberDiff line change
@@ -413,3 +413,118 @@ func createBranch(client *github.Client, t translations.TranslationHelperFunc) (
413413
return mcp.NewToolResultText(string(r)), nil
414414
}
415415
}
416+
417+
// pushFiles creates a tool to push multiple files in a single commit to a GitHub repository.
418+
func pushFiles(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
419+
return mcp.NewTool("push_files",
420+
mcp.WithDescription(t("TOOL_PUSH_FILES_DESCRIPTION", "Push multiple files to a GitHub repository in a single commit")),
421+
mcp.WithString("owner",
422+
mcp.Required(),
423+
mcp.Description("Repository owner"),
424+
),
425+
mcp.WithString("repo",
426+
mcp.Required(),
427+
mcp.Description("Repository name"),
428+
),
429+
mcp.WithString("branch",
430+
mcp.Required(),
431+
mcp.Description("Branch to push to"),
432+
),
433+
mcp.WithArray("files",
434+
mcp.Required(),
435+
mcp.Description("Array of file objects to push, each object with path (string) and content (string)"),
436+
),
437+
mcp.WithString("message",
438+
mcp.Required(),
439+
mcp.Description("Commit message"),
440+
),
441+
),
442+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
443+
owner := request.Params.Arguments["owner"].(string)
444+
repo := request.Params.Arguments["repo"].(string)
445+
branch := request.Params.Arguments["branch"].(string)
446+
message := request.Params.Arguments["message"].(string)
447+
448+
// Parse files parameter - this should be an array of objects with path and content
449+
filesObj, ok := request.Params.Arguments["files"].([]interface{})
450+
if !ok {
451+
return mcp.NewToolResultError("files parameter must be an array of objects with path and content"), nil
452+
}
453+
454+
// Get the reference for the branch
455+
ref, resp, err := client.Git.GetRef(ctx, owner, repo, "refs/heads/"+branch)
456+
if err != nil {
457+
return nil, fmt.Errorf("failed to get branch reference: %w", err)
458+
}
459+
defer func() { _ = resp.Body.Close() }()
460+
461+
// Get the commit object that the branch points to
462+
baseCommit, resp, err := client.Git.GetCommit(ctx, owner, repo, *ref.Object.SHA)
463+
if err != nil {
464+
return nil, fmt.Errorf("failed to get base commit: %w", err)
465+
}
466+
defer func() { _ = resp.Body.Close() }()
467+
468+
// Create tree entries for all files
469+
var entries []*github.TreeEntry
470+
471+
for _, file := range filesObj {
472+
fileMap, ok := file.(map[string]interface{})
473+
if !ok {
474+
return mcp.NewToolResultError("each file must be an object with path and content"), nil
475+
}
476+
477+
path, ok := fileMap["path"].(string)
478+
if !ok || path == "" {
479+
return mcp.NewToolResultError("each file must have a path"), nil
480+
}
481+
482+
content, ok := fileMap["content"].(string)
483+
if !ok {
484+
return mcp.NewToolResultError("each file must have content"), nil
485+
}
486+
487+
// Create a tree entry for the file
488+
entries = append(entries, &github.TreeEntry{
489+
Path: github.Ptr(path),
490+
Mode: github.Ptr("100644"), // Regular file mode
491+
Type: github.Ptr("blob"),
492+
Content: github.Ptr(content),
493+
})
494+
}
495+
496+
// Create a new tree with the file entries
497+
newTree, resp, err := client.Git.CreateTree(ctx, owner, repo, *baseCommit.Tree.SHA, entries)
498+
if err != nil {
499+
return nil, fmt.Errorf("failed to create tree: %w", err)
500+
}
501+
defer func() { _ = resp.Body.Close() }()
502+
503+
// Create a new commit
504+
commit := &github.Commit{
505+
Message: github.Ptr(message),
506+
Tree: newTree,
507+
Parents: []*github.Commit{{SHA: baseCommit.SHA}},
508+
}
509+
newCommit, resp, err := client.Git.CreateCommit(ctx, owner, repo, commit, nil)
510+
if err != nil {
511+
return nil, fmt.Errorf("failed to create commit: %w", err)
512+
}
513+
defer func() { _ = resp.Body.Close() }()
514+
515+
// Update the reference to point to the new commit
516+
ref.Object.SHA = newCommit.SHA
517+
updatedRef, resp, err := client.Git.UpdateRef(ctx, owner, repo, ref, false)
518+
if err != nil {
519+
return nil, fmt.Errorf("failed to update reference: %w", err)
520+
}
521+
defer func() { _ = resp.Body.Close() }()
522+
523+
r, err := json.Marshal(updatedRef)
524+
if err != nil {
525+
return nil, fmt.Errorf("failed to marshal response: %w", err)
526+
}
527+
528+
return mcp.NewToolResultText(string(r)), nil
529+
}
530+
}

0 commit comments

Comments
 (0)