Skip to content

Commit b32d68f

Browse files
Add create_repository_from_template tool
- Implement CreateRepositoryFromTemplate function using GitHub's template API - Add comprehensive unit tests with 5 test cases - Register tool in AllTools list - Generate toolsnap and update documentation Co-authored-by: SamMorrowDrums <[email protected]>
1 parent 348be8c commit b32d68f

File tree

5 files changed

+381
-0
lines changed

5 files changed

+381
-0
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,15 @@ The following sets of tools are available:
11131113
- `organization`: Organization to create the repository in (omit to create in your personal account) (string, optional)
11141114
- `private`: Whether repo should be private (boolean, optional)
11151115

1116+
- **create_repository_from_template** - Create repository from template
1117+
- `description`: Description for the new repository (string, optional)
1118+
- `include_all_branches`: Whether to include all branches from template (default: false, only default branch) (boolean, optional)
1119+
- `name`: Name for the new repository (string, required)
1120+
- `owner`: Owner for the new repository (username or organization). Omit to create in your personal account (string, optional)
1121+
- `private`: Whether the new repository should be private (boolean, optional)
1122+
- `template_owner`: Owner of the template repository (string, required)
1123+
- `template_repo`: Name of the template repository (string, required)
1124+
11161125
- **delete_file** - Delete file
11171126
- `branch`: Branch to delete the file from (string, required)
11181127
- `message`: Commit message (string, required)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"annotations": {
3+
"title": "Create repository from template"
4+
},
5+
"description": "Create a new GitHub repository from a template repository",
6+
"inputSchema": {
7+
"type": "object",
8+
"properties": {
9+
"description": {
10+
"type": "string",
11+
"description": "Description for the new repository"
12+
},
13+
"include_all_branches": {
14+
"type": "boolean",
15+
"description": "Whether to include all branches from template (default: false, only default branch)"
16+
},
17+
"name": {
18+
"type": "string",
19+
"description": "Name for the new repository"
20+
},
21+
"owner": {
22+
"type": "string",
23+
"description": "Owner for the new repository (username or organization). Omit to create in your personal account"
24+
},
25+
"private": {
26+
"type": "boolean",
27+
"description": "Whether the new repository should be private"
28+
},
29+
"template_owner": {
30+
"type": "string",
31+
"description": "Owner of the template repository"
32+
},
33+
"template_repo": {
34+
"type": "string",
35+
"description": "Name of the template repository"
36+
}
37+
},
38+
"required": [
39+
"template_owner",
40+
"template_repo",
41+
"name"
42+
]
43+
},
44+
"name": "create_repository_from_template"
45+
}

pkg/github/repositories.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,128 @@ func CreateRepository(t translations.TranslationHelperFunc) inventory.ServerTool
612612
)
613613
}
614614

615+
// CreateRepositoryFromTemplate creates a tool to create a new GitHub repository from a template.
616+
func CreateRepositoryFromTemplate(t translations.TranslationHelperFunc) inventory.ServerTool {
617+
return NewTool(
618+
ToolsetMetadataRepos,
619+
mcp.Tool{
620+
Name: "create_repository_from_template",
621+
Description: t("TOOL_CREATE_REPOSITORY_FROM_TEMPLATE_DESCRIPTION", "Create a new GitHub repository from a template repository"),
622+
Annotations: &mcp.ToolAnnotations{
623+
Title: t("TOOL_CREATE_REPOSITORY_FROM_TEMPLATE_USER_TITLE", "Create repository from template"),
624+
ReadOnlyHint: false,
625+
},
626+
InputSchema: &jsonschema.Schema{
627+
Type: "object",
628+
Properties: map[string]*jsonschema.Schema{
629+
"template_owner": {
630+
Type: "string",
631+
Description: "Owner of the template repository",
632+
},
633+
"template_repo": {
634+
Type: "string",
635+
Description: "Name of the template repository",
636+
},
637+
"name": {
638+
Type: "string",
639+
Description: "Name for the new repository",
640+
},
641+
"description": {
642+
Type: "string",
643+
Description: "Description for the new repository",
644+
},
645+
"owner": {
646+
Type: "string",
647+
Description: "Owner for the new repository (username or organization). Omit to create in your personal account",
648+
},
649+
"private": {
650+
Type: "boolean",
651+
Description: "Whether the new repository should be private",
652+
},
653+
"include_all_branches": {
654+
Type: "boolean",
655+
Description: "Whether to include all branches from template (default: false, only default branch)",
656+
},
657+
},
658+
Required: []string{"template_owner", "template_repo", "name"},
659+
},
660+
},
661+
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
662+
templateOwner, err := RequiredParam[string](args, "template_owner")
663+
if err != nil {
664+
return utils.NewToolResultError(err.Error()), nil, nil
665+
}
666+
templateRepo, err := RequiredParam[string](args, "template_repo")
667+
if err != nil {
668+
return utils.NewToolResultError(err.Error()), nil, nil
669+
}
670+
name, err := RequiredParam[string](args, "name")
671+
if err != nil {
672+
return utils.NewToolResultError(err.Error()), nil, nil
673+
}
674+
description, err := OptionalParam[string](args, "description")
675+
if err != nil {
676+
return utils.NewToolResultError(err.Error()), nil, nil
677+
}
678+
owner, err := OptionalParam[string](args, "owner")
679+
if err != nil {
680+
return utils.NewToolResultError(err.Error()), nil, nil
681+
}
682+
private, err := OptionalParam[bool](args, "private")
683+
if err != nil {
684+
return utils.NewToolResultError(err.Error()), nil, nil
685+
}
686+
includeAllBranches, err := OptionalParam[bool](args, "include_all_branches")
687+
if err != nil {
688+
return utils.NewToolResultError(err.Error()), nil, nil
689+
}
690+
691+
templateReq := &github.TemplateRepoRequest{
692+
Name: github.Ptr(name),
693+
Owner: github.Ptr(owner),
694+
Description: github.Ptr(description),
695+
Private: github.Ptr(private),
696+
IncludeAllBranches: github.Ptr(includeAllBranches),
697+
}
698+
699+
client, err := deps.GetClient(ctx)
700+
if err != nil {
701+
return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err)
702+
}
703+
createdRepo, resp, err := client.Repositories.CreateFromTemplate(ctx, templateOwner, templateRepo, templateReq)
704+
if err != nil {
705+
return ghErrors.NewGitHubAPIErrorResponse(ctx,
706+
"failed to create repository from template",
707+
resp,
708+
err,
709+
), nil, nil
710+
}
711+
defer func() { _ = resp.Body.Close() }()
712+
713+
if resp.StatusCode != http.StatusCreated {
714+
body, err := io.ReadAll(resp.Body)
715+
if err != nil {
716+
return nil, nil, fmt.Errorf("failed to read response body: %w", err)
717+
}
718+
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to create repository from template", resp, body), nil, nil
719+
}
720+
721+
// Return minimal response with just essential information
722+
minimalResponse := MinimalResponse{
723+
ID: fmt.Sprintf("%d", createdRepo.GetID()),
724+
URL: createdRepo.GetHTMLURL(),
725+
}
726+
727+
r, err := json.Marshal(minimalResponse)
728+
if err != nil {
729+
return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
730+
}
731+
732+
return utils.NewToolResultText(string(r)), nil, nil
733+
},
734+
)
735+
}
736+
615737
// GetFileContents creates a tool to get the contents of a file or directory from a GitHub repository.
616738
func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool {
617739
return NewTool(

0 commit comments

Comments
 (0)