Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit432a0b5

Browse files
committed
feat: add create repo from template
1 parent865f9bf commit432a0b5

File tree

3 files changed

+242
-1
lines changed

3 files changed

+242
-1
lines changed

‎pkg/github/repositories.go

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ func CreateOrUpdateFile(getClient GetClientFn, t translations.TranslationHelperF
321321
// CreateRepository creates a tool to create a new GitHub repository.
322322
funcCreateRepository(getClientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
323323
returnmcp.NewTool("create_repository",
324-
mcp.WithDescription(t("TOOL_CREATE_REPOSITORY_DESCRIPTION","Create a new GitHub repository in your account")),
324+
mcp.WithDescription(t("TOOL_CREATE_REPOSITORY_DESCRIPTION","Create a new GitHub repository in your account without using a template")),
325325
mcp.WithString("name",
326326
mcp.Required(),
327327
mcp.Description("Repository name"),
@@ -388,6 +388,92 @@ func CreateRepository(getClient GetClientFn, t translations.TranslationHelperFun
388388
}
389389
}
390390

391+
// CreateRepositoryFromTemplate creates a tool to create a new GitHub repository from a template.
392+
funcCreateRepositoryFromTemplate(getClientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
393+
returnmcp.NewTool("create_repository_from_template",
394+
mcp.WithDescription(t("TOOL_CREATE_REPOSITORY_FROM_TEMPLATE_DESCRIPTION","Create a new GitHub repository from a template in your account")),
395+
mcp.WithString("name",
396+
mcp.Required(),
397+
mcp.Description("Repository name"),
398+
),
399+
mcp.WithString("description",
400+
mcp.Description("Repository description"),
401+
),
402+
mcp.WithBoolean("private",
403+
mcp.Description("Whether repo should be private"),
404+
),
405+
mcp.WithBoolean("includeAllBranches",
406+
mcp.Description("Include all branches from template"),
407+
),
408+
mcp.WithString("templateOwner",
409+
mcp.Required(),
410+
mcp.Description("Template repository owner"),
411+
),
412+
mcp.WithString("templateRepo",
413+
mcp.Required(),
414+
mcp.Description("Template repository name"),
415+
),
416+
),
417+
func(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
418+
name,err:=requiredParam[string](request,"name")
419+
iferr!=nil {
420+
returnmcp.NewToolResultError(err.Error()),nil
421+
}
422+
description,err:=OptionalParam[string](request,"description")
423+
iferr!=nil {
424+
returnmcp.NewToolResultError(err.Error()),nil
425+
}
426+
private,err:=OptionalParam[bool](request,"private")
427+
iferr!=nil {
428+
returnmcp.NewToolResultError(err.Error()),nil
429+
}
430+
includeAllBranches,err:=OptionalParam[bool](request,"includeAllBranches")
431+
iferr!=nil {
432+
returnmcp.NewToolResultError(err.Error()),nil
433+
}
434+
templateOwner,err:=requiredParam[string](request,"templateOwner")
435+
iferr!=nil {
436+
returnmcp.NewToolResultError(err.Error()),nil
437+
}
438+
templateRepo,err:=requiredParam[string](request,"templateRepo")
439+
iferr!=nil {
440+
returnmcp.NewToolResultError(err.Error()),nil
441+
}
442+
443+
templateReq:=&github.TemplateRepoRequest{
444+
Name:github.Ptr(name),
445+
Description:github.Ptr(description),
446+
Private:github.Ptr(private),
447+
IncludeAllBranches:github.Ptr(includeAllBranches),
448+
}
449+
450+
client,err:=getClient(ctx)
451+
iferr!=nil {
452+
returnnil,fmt.Errorf("failed to get GitHub client: %w",err)
453+
}
454+
createdRepo,resp,err:=client.Repositories.CreateFromTemplate(ctx,templateOwner,templateRepo,templateReq)
455+
iferr!=nil {
456+
returnnil,fmt.Errorf("failed to create repository from template: %w",err)
457+
}
458+
deferfunc() {_=resp.Body.Close() }()
459+
460+
ifresp.StatusCode!=http.StatusCreated {
461+
body,err:=io.ReadAll(resp.Body)
462+
iferr!=nil {
463+
returnnil,fmt.Errorf("failed to read response body: %w",err)
464+
}
465+
returnmcp.NewToolResultError(fmt.Sprintf("failed to create repository from template: %s",string(body))),nil
466+
}
467+
468+
r,err:=json.Marshal(createdRepo)
469+
iferr!=nil {
470+
returnnil,fmt.Errorf("failed to marshal response: %w",err)
471+
}
472+
473+
returnmcp.NewToolResultText(string(r)),nil
474+
}
475+
}
476+
391477
// GetFileContents creates a tool to get the contents of a file or directory from a GitHub repository.
392478
funcGetFileContents(getClientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
393479
returnmcp.NewTool("get_file_contents",

‎pkg/github/repositories_test.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1528,3 +1528,157 @@ func Test_ListBranches(t *testing.T) {
15281528
})
15291529
}
15301530
}
1531+
1532+
funcTest_CreateRepositoryFromTemplate(t*testing.T) {
1533+
// Verify tool definition once
1534+
mockClient:=github.NewClient(nil)
1535+
tool,_:=CreateRepositoryFromTemplate(stubGetClientFn(mockClient),translations.NullTranslationHelper)
1536+
1537+
assert.Equal(t,"create_repository_from_template",tool.Name)
1538+
assert.NotEmpty(t,tool.Description)
1539+
assert.Contains(t,tool.InputSchema.Properties,"name")
1540+
assert.Contains(t,tool.InputSchema.Properties,"description")
1541+
assert.Contains(t,tool.InputSchema.Properties,"private")
1542+
assert.Contains(t,tool.InputSchema.Properties,"includeAllBranches")
1543+
assert.Contains(t,tool.InputSchema.Properties,"templateOwner")
1544+
assert.Contains(t,tool.InputSchema.Properties,"templateRepo")
1545+
assert.ElementsMatch(t,tool.InputSchema.Required, []string{"name","templateOwner","templateRepo"})
1546+
1547+
// Setup mock repository response
1548+
mockRepo:=&github.Repository{
1549+
Name:github.Ptr("test-repo"),
1550+
Description:github.Ptr("Test repository"),
1551+
Private:github.Ptr(true),
1552+
HTMLURL:github.Ptr("https://github.com/testuser/test-repo"),
1553+
CloneURL:github.Ptr("https://github.com/testuser/test-repo.git"),
1554+
CreatedAt:&github.Timestamp{Time:time.Now()},
1555+
Owner:&github.User{
1556+
Login:github.Ptr("testuser"),
1557+
},
1558+
}
1559+
1560+
tests:= []struct {
1561+
namestring
1562+
mockedClient*http.Client
1563+
requestArgsmap[string]interface{}
1564+
expectErrorbool
1565+
expectedRepo*github.Repository
1566+
expectedErrMsgstring
1567+
}{
1568+
{
1569+
name:"successful repository creation from template with all params",
1570+
mockedClient:mock.NewMockedHTTPClient(
1571+
mock.WithRequestMatchHandler(
1572+
mock.EndpointPattern{
1573+
Pattern:"/repos/template-owner/template-repo/generate",
1574+
Method:"POST",
1575+
},
1576+
expectRequestBody(t,map[string]interface{}{
1577+
"name":"test-repo",
1578+
"description":"Test repository",
1579+
"private":true,
1580+
"include_all_branches":true,
1581+
}).andThen(
1582+
mockResponse(t,http.StatusCreated,mockRepo),
1583+
),
1584+
),
1585+
),
1586+
requestArgs:map[string]interface{}{
1587+
"name":"test-repo",
1588+
"description":"Test repository",
1589+
"private":true,
1590+
"includeAllBranches":true,
1591+
"templateOwner":"template-owner",
1592+
"templateRepo":"template-repo",
1593+
},
1594+
expectError:false,
1595+
expectedRepo:mockRepo,
1596+
},
1597+
{
1598+
name:"successful repository creation from template with minimal params",
1599+
mockedClient:mock.NewMockedHTTPClient(
1600+
mock.WithRequestMatchHandler(
1601+
mock.EndpointPattern{
1602+
Pattern:"/repos/template-owner/template-repo/generate",
1603+
Method:"POST",
1604+
},
1605+
expectRequestBody(t,map[string]interface{}{
1606+
"name":"test-repo",
1607+
"description":"",
1608+
"private":false,
1609+
"include_all_branches":false,
1610+
}).andThen(
1611+
mockResponse(t,http.StatusCreated,mockRepo),
1612+
),
1613+
),
1614+
),
1615+
requestArgs:map[string]interface{}{
1616+
"name":"test-repo",
1617+
"templateOwner":"template-owner",
1618+
"templateRepo":"template-repo",
1619+
},
1620+
expectError:false,
1621+
expectedRepo:mockRepo,
1622+
},
1623+
{
1624+
name:"repository creation from template fails",
1625+
mockedClient:mock.NewMockedHTTPClient(
1626+
mock.WithRequestMatchHandler(
1627+
mock.EndpointPattern{
1628+
Pattern:"/repos/template-owner/template-repo/generate",
1629+
Method:"POST",
1630+
},
1631+
http.HandlerFunc(func(w http.ResponseWriter,_*http.Request) {
1632+
w.WriteHeader(http.StatusUnprocessableEntity)
1633+
_,_=w.Write([]byte(`{"message": "Repository creation from template failed"}`))
1634+
}),
1635+
),
1636+
),
1637+
requestArgs:map[string]interface{}{
1638+
"name":"invalid-repo",
1639+
"templateOwner":"template-owner",
1640+
"templateRepo":"template-repo",
1641+
},
1642+
expectError:true,
1643+
expectedErrMsg:"failed to create repository from template",
1644+
},
1645+
}
1646+
1647+
for_,tc:=rangetests {
1648+
t.Run(tc.name,func(t*testing.T) {
1649+
// Setup client with mock
1650+
client:=github.NewClient(tc.mockedClient)
1651+
_,handler:=CreateRepositoryFromTemplate(stubGetClientFn(client),translations.NullTranslationHelper)
1652+
1653+
// Create call request
1654+
request:=createMCPRequest(tc.requestArgs)
1655+
1656+
// Call handler
1657+
result,err:=handler(context.Background(),request)
1658+
1659+
// Verify results
1660+
iftc.expectError {
1661+
require.Error(t,err)
1662+
assert.Contains(t,err.Error(),tc.expectedErrMsg)
1663+
return
1664+
}
1665+
1666+
require.NoError(t,err)
1667+
1668+
// Parse the result and get the text content if no error
1669+
textContent:=getTextResult(t,result)
1670+
1671+
// Unmarshal and verify the result
1672+
varreturnedRepo github.Repository
1673+
err=json.Unmarshal([]byte(textContent.Text),&returnedRepo)
1674+
assert.NoError(t,err)
1675+
1676+
// Verify repository details
1677+
assert.Equal(t,*tc.expectedRepo.Name,*returnedRepo.Name)
1678+
assert.Equal(t,*tc.expectedRepo.Description,*returnedRepo.Description)
1679+
assert.Equal(t,*tc.expectedRepo.Private,*returnedRepo.Private)
1680+
assert.Equal(t,*tc.expectedRepo.HTMLURL,*returnedRepo.HTMLURL)
1681+
assert.Equal(t,*tc.expectedRepo.Owner.Login,*returnedRepo.Owner.Login)
1682+
})
1683+
}
1684+
}

‎pkg/github/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ func NewServer(getClient GetClientFn, version string, readOnly bool, t translati
7474
if!readOnly {
7575
s.AddTool(CreateOrUpdateFile(getClient,t))
7676
s.AddTool(CreateRepository(getClient,t))
77+
s.AddTool(CreateRepositoryFromTemplate(getClient,t))
7778
s.AddTool(ForkRepository(getClient,t))
7879
s.AddTool(CreateBranch(getClient,t))
7980
s.AddTool(PushFiles(getClient,t))

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp