3 Commits

Author SHA1 Message Date
hiifong
dc3e120e97 Update operation/repo/file.go 2025-08-29 05:57:44 +00:00
marcluer
f33b04a3df feat: added parameter 'organization' to tool 'create_repo' (#88)
Using the Gitea-mcp server I was missing the ability to create repositories in other organizations. e.g.:
* I was only able to create `https://gitea.domain.com/myuser/repo` 
* I was not able to create `https://gitea.domain.com/organization/repo` 

This feature was planned, implemented and compiled by Claude Code. I have no clue about Golang.

I then took the resulting `gitea-mcp` file and sucessfully tested it on my self-hosted gitea instance:
* Creating `https://gitea.domain.com/myuser/repo` 
* Creating `https://gitea.domain.com/organization/repo` 

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/88
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: marcluer <gitea@marcluerssen.de>
Co-committed-by: marcluer <gitea@marcluerssen.de>
2025-08-29 05:37:19 +00:00
appleboy
ba07925969 refactor: refactor MCP tool registration and pagination handling (#86)
- Add documentation for MCP tool constants and tool registration
- Use configurable default values for pagination arguments in user organization queries
- Introduce registerTools helper to streamline MCP tool registration
- Refactor pagination argument parsing into a reusable getIntArg function
- Add descriptive logging for tool handler execution
- Improve code organization for defining and registering MCP tools

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/86
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-08-23 04:31:57 +00:00
3 changed files with 69 additions and 31 deletions

View File

@@ -64,7 +64,7 @@ var (
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")),
mcp.WithString("sha", mcp.Required(), mcp.Description("sha is the SHA for the file that already exists")),
mcp.WithString("content", mcp.Required(), mcp.Description("file content, base64 encoded")),
mcp.WithString("content", mcp.Required(), mcp.Description("file content")),
mcp.WithString("message", mcp.Required(), mcp.Description("commit message")),
mcp.WithString("branch_name", mcp.Required(), mcp.Description("branch name")),
)

View File

@@ -27,7 +27,7 @@ const (
var (
CreateRepoTool = mcp.NewTool(
CreateRepoToolName,
mcp.WithDescription("Create repository"),
mcp.WithDescription("Create repository in personal account or organization"),
mcp.WithString("name", mcp.Required(), mcp.Description("Name of the repository to create")),
mcp.WithString("description", mcp.Description("Description of the repository to create")),
mcp.WithBoolean("private", mcp.Description("Whether the repository is private")),
@@ -38,6 +38,7 @@ var (
mcp.WithString("license", mcp.Description("License to use")),
mcp.WithString("readme", mcp.Description("Readme of the repository to create")),
mcp.WithString("default_branch", mcp.Description("DefaultBranch of the repository (used when initializes and in template)")),
mcp.WithString("organization", mcp.Description("Organization name to create repository in (optional - defaults to personal account)")),
)
ForkRepoTool = mcp.NewTool(
@@ -120,6 +121,7 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
license, _ := req.GetArguments()["license"].(string)
readme, _ := req.GetArguments()["readme"].(string)
defaultBranch, _ := req.GetArguments()["default_branch"].(string)
organization, _ := req.GetArguments()["organization"].(string)
opt := gitea_sdk.CreateRepoOption{
Name: name,
@@ -133,9 +135,19 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
Readme: readme,
DefaultBranch: defaultBranch,
}
repo, _, err := gitea.Client().CreateRepo(opt)
var repo *gitea_sdk.Repository
var err error
if organization != "" {
repo, _, err = gitea.Client().CreateOrgRepo(organization, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create repo err: %v", err))
return to.ErrorResult(fmt.Errorf("create organization repository '%s' in '%s' err: %v", name, organization, err))
}
} else {
repo, _, err = gitea.Client().CreateRepo(opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create repository '%s' err: %v", name, err))
}
}
return to.TextResult(repo)
}

View File

@@ -15,68 +15,94 @@ import (
)
const (
// GetMyUserInfoToolName is the unique tool name used for MCP registration and lookup of the get_my_user_info command.
GetMyUserInfoToolName = "get_my_user_info"
// GetUserOrgsToolName is the unique tool name used for MCP registration and lookup of the get_user_orgs command.
GetUserOrgsToolName = "get_user_orgs"
// defaultPage is the default starting page number used for paginated organization listings.
defaultPage = 1
// defaultPageSize is the default number of organizations per page for paginated queries.
defaultPageSize = 100
)
// Tool is the MCP tool manager instance for registering all MCP tools in this package.
var Tool = tool.New()
var (
// GetMyUserInfoTool is the MCP tool for retrieving the current user's info.
// It is registered with a specific name and a description string.
GetMyUserInfoTool = mcp.NewTool(
GetMyUserInfoToolName,
mcp.WithDescription("Get my user info"),
)
// GetUserOrgsTool is the MCP tool for listing organizations for the authenticated user.
// It supports pagination via "page" and "pageSize" arguments with default values specified above.
GetUserOrgsTool = mcp.NewTool(
GetUserOrgsToolName,
mcp.WithDescription("Get organizations associated with the authenticated user"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(defaultPage)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(defaultPageSize)),
)
)
// init registers all MCP tools in Tool at package initialization.
// This function ensures the handler functions are registered before server usage.
func init() {
Tool.RegisterRead(server.ServerTool{
Tool: GetMyUserInfoTool,
Handler: GetUserInfoFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: GetUserOrgsTool,
Handler: GetUserOrgsFn,
})
registerTools()
}
// registerTools registers all local MCP tool definitions and their handler functions.
// To add new functionality, append your tool/handler pair to the tools slice below.
func registerTools() {
tools := []server.ServerTool{
{Tool: GetMyUserInfoTool, Handler: GetUserInfoFn},
{Tool: GetUserOrgsTool, Handler: GetUserOrgsFn},
}
for _, t := range tools {
Tool.RegisterRead(t)
}
}
// getIntArg parses an integer argument from the MCP request arguments map.
// Returns def if missing, not a number, or less than 1. Used for pagination arguments.
func getIntArg(req mcp.CallToolRequest, name string, def int) int {
val, ok := req.GetArguments()[name].(float64)
if !ok || val < 1 {
return def
}
return int(val)
}
// GetUserInfoFn is the handler for "get_my_user_info" MCP tool requests.
// Logs invocation, fetches current user info from gitea, wraps result for MCP.
func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetUserInfoFn")
log.Debugf("[User] Called GetUserInfoFn")
user, _, err := gitea.Client().GetMyUserInfo()
if err != nil {
return to.ErrorResult(fmt.Errorf("get user info err: %v", err))
}
return to.TextResult(user)
}
// GetUserOrgsFn is the handler for "get_user_orgs" MCP tool requests.
// Logs invocation, pulls validated pagination arguments from request,
// performs Gitea organization listing, and wraps the result for MCP.
func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetUserOrgsFn")
page, ok := req.GetArguments()["page"].(float64)
if !ok || page < 1 {
page = 1
}
pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok || pageSize < 1 {
pageSize = 100
}
log.Debugf("[User] Called GetUserOrgsFn")
page := getIntArg(req, "page", defaultPage)
pageSize := getIntArg(req, "pageSize", defaultPageSize)
opt := gitea_sdk.ListOrgsOptions{
ListOptions: gitea_sdk.ListOptions{
Page: int(page),
PageSize: int(pageSize),
Page: page,
PageSize: pageSize,
},
}
orgs, _, err := gitea.Client().ListMyOrgs(opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("get user orgs err: %v", err))
}
return to.TextResult(orgs)
}