in pkg/github/repositories.go [229:335]
func CreateOrUpdateFile(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
return mcp.NewTool("create_or_update_file",
mcp.WithDescription(t("TOOL_CREATE_OR_UPDATE_FILE_DESCRIPTION", "Create or update a single file in a GitHub repository. If updating, you must provide the SHA of the file you want to update.")),
mcp.WithToolAnnotation(mcp.ToolAnnotation{
Title: t("TOOL_CREATE_OR_UPDATE_FILE_USER_TITLE", "Create or update file"),
ReadOnlyHint: false,
}),
mcp.WithString("owner",
mcp.Required(),
mcp.Description("Repository owner (username or organization)"),
),
mcp.WithString("repo",
mcp.Required(),
mcp.Description("Repository name"),
),
mcp.WithString("path",
mcp.Required(),
mcp.Description("Path where to create/update the file"),
),
mcp.WithString("content",
mcp.Required(),
mcp.Description("Content of the file"),
),
mcp.WithString("message",
mcp.Required(),
mcp.Description("Commit message"),
),
mcp.WithString("branch",
mcp.Required(),
mcp.Description("Branch to create/update the file in"),
),
mcp.WithString("sha",
mcp.Description("SHA of file being replaced (for updates)"),
),
),
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
}
path, err := requiredParam[string](request, "path")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
content, err := requiredParam[string](request, "content")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
message, err := requiredParam[string](request, "message")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
branch, err := requiredParam[string](request, "branch")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
// Convert content to base64
contentBytes := []byte(content)
// Create the file options
opts := &github.RepositoryContentFileOptions{
Message: github.Ptr(message),
Content: contentBytes,
Branch: github.Ptr(branch),
}
// If SHA is provided, set it (for updates)
sha, err := OptionalParam[string](request, "sha")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
if sha != "" {
opts.SHA = github.Ptr(sha)
}
// Create or update the file
client, err := getClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
}
fileContent, resp, err := client.Repositories.CreateFile(ctx, owner, repo, path, opts)
if err != nil {
return nil, fmt.Errorf("failed to create/update file: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != 200 && resp.StatusCode != 201 {
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 create/update file: %s", string(body))), nil
}
r, err := json.Marshal(fileContent)
if err != nil {
return nil, fmt.Errorf("failed to marshal response: %w", err)
}
return mcp.NewToolResultText(string(r)), nil
}
}