mirror of
https://gitea.com/gitea/gitea-mcp.git
synced 2026-03-19 03:15:12 +00:00
Consolidate tools from 110 to 45 using method dispatch (#143)
Consolidate 110 individual MCP tools down to 45 using a method dispatch pattern, aligning tool names with the GitHub MCP server conventions. **Motivation:** LLMs work better with fewer, well-organized tools. The method dispatch pattern (used by GitHub's MCP server) groups related operations under read/write tools with a `method` parameter. **Changes:** - Group related tools into `_read`/`_write` pairs with method dispatch (e.g. `issue_read`, `issue_write`, `pull_request_read`, `pull_request_write`) - Rename tools to match GitHub MCP naming (`get_file_contents`, `create_or_update_file`, `list_issues`, `list_pull_requests`, etc.) - Rename `pageSize` to `perPage` for GitHub MCP compat - Move issue label ops (`add_labels`, `remove_label`, etc.) into `issue_write` - Merge `create_file`/`update_file` into `create_or_update_file` with optional `sha` - Make `delete_file` require `sha` - Add `get_labels` method to `issue_read` - Add shared helpers: `GetInt64Slice`, `GetStringSlice`, `GetPagination` in params package - Unexport all dispatch handler functions - Fix: pass assignees/milestone in `CreateIssue`, bounds check in `GetFileContent` Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/143 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-committed-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ListRepoCommitsToolName = "list_repo_commits"
|
||||
ListRepoCommitsToolName = "list_commits"
|
||||
)
|
||||
|
||||
var ListRepoCommitsTool = mcp.NewTool(
|
||||
@@ -26,7 +26,7 @@ var ListRepoCommitsTool = mcp.NewTool(
|
||||
mcp.WithString("sha", mcp.Description("SHA or branch to start listing commits from")),
|
||||
mcp.WithString("path", mcp.Description("path indicates that only commits that include the path's file/dir should be returned.")),
|
||||
mcp.WithNumber("page", mcp.Required(), mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
|
||||
mcp.WithNumber("page_size", mcp.Required(), mcp.Description("page size"), mcp.DefaultNumber(30), mcp.Min(1)),
|
||||
mcp.WithNumber("perPage", mcp.Required(), mcp.Description("results per page"), mcp.DefaultNumber(30), mcp.Min(1)),
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -51,7 +51,7 @@ func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
pageSize, err := params.GetIndex(args, "page_size")
|
||||
pageSize, err := params.GetIndex(args, "perPage")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
|
||||
@@ -19,11 +19,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
GetFileToolName = "get_file_content"
|
||||
GetDirToolName = "get_dir_content"
|
||||
CreateFileToolName = "create_file"
|
||||
UpdateFileToolName = "update_file"
|
||||
DeleteFileToolName = "delete_file"
|
||||
GetFileToolName = "get_file_contents"
|
||||
GetDirToolName = "get_dir_contents"
|
||||
CreateOrUpdateFileToolName = "create_or_update_file"
|
||||
DeleteFileToolName = "delete_file"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -46,28 +45,17 @@ var (
|
||||
mcp.WithString("filePath", mcp.Required(), mcp.Description("directory path")),
|
||||
)
|
||||
|
||||
CreateFileTool = mcp.NewTool(
|
||||
CreateFileToolName,
|
||||
mcp.WithDescription("Create file"),
|
||||
CreateOrUpdateFileTool = mcp.NewTool(
|
||||
CreateOrUpdateFileToolName,
|
||||
mcp.WithDescription("Create or update a file. If sha is provided, updates the existing file; otherwise creates a new file."),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")),
|
||||
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")),
|
||||
mcp.WithString("new_branch_name", mcp.Description("new branch name")),
|
||||
)
|
||||
|
||||
UpdateFileTool = mcp.NewTool(
|
||||
UpdateFileToolName,
|
||||
mcp.WithDescription("Update file"),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
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")),
|
||||
mcp.WithString("message", mcp.Required(), mcp.Description("commit message")),
|
||||
mcp.WithString("branch_name", mcp.Required(), mcp.Description("branch name")),
|
||||
mcp.WithString("sha", mcp.Description("SHA of the existing file (required for update, omit for create)")),
|
||||
mcp.WithString("new_branch_name", mcp.Description("new branch name (for create only)")),
|
||||
)
|
||||
|
||||
DeleteFileTool = mcp.NewTool(
|
||||
@@ -78,7 +66,7 @@ var (
|
||||
mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")),
|
||||
mcp.WithString("message", mcp.Required(), mcp.Description("commit message")),
|
||||
mcp.WithString("branch_name", mcp.Required(), mcp.Description("branch name")),
|
||||
mcp.WithString("sha", mcp.Description("sha")),
|
||||
mcp.WithString("sha", mcp.Required(), mcp.Description("sha")),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -92,12 +80,8 @@ func init() {
|
||||
Handler: GetDirContentFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: CreateFileTool,
|
||||
Handler: CreateFileFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: UpdateFileTool,
|
||||
Handler: UpdateFileFn,
|
||||
Tool: CreateOrUpdateFileTool,
|
||||
Handler: CreateOrUpdateFileFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: DeleteFileTool,
|
||||
@@ -160,7 +144,7 @@ func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
|
||||
|
||||
// remove the last blank line if exists
|
||||
// git does not consider the last line as a new line
|
||||
if contentLines[len(contentLines)-1].Content == "" {
|
||||
if len(contentLines) > 0 && contentLines[len(contentLines)-1].Content == "" {
|
||||
contentLines = contentLines[:len(contentLines)-1]
|
||||
}
|
||||
|
||||
@@ -201,8 +185,8 @@ func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
|
||||
return to.TextResult(slimDirEntries(content))
|
||||
}
|
||||
|
||||
func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called CreateFileFn")
|
||||
func CreateOrUpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called CreateOrUpdateFileFn")
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
@@ -219,6 +203,31 @@ func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
|
||||
content, _ := args["content"].(string)
|
||||
message, _ := args["message"].(string)
|
||||
branchName, _ := args["branch_name"].(string)
|
||||
sha, _ := args["sha"].(string)
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
|
||||
if sha != "" {
|
||||
// Update existing file
|
||||
opt := gitea_sdk.UpdateFileOptions{
|
||||
SHA: sha,
|
||||
Content: base64.StdEncoding.EncodeToString([]byte(content)),
|
||||
FileOptions: gitea_sdk.FileOptions{
|
||||
Message: message,
|
||||
BranchName: branchName,
|
||||
},
|
||||
}
|
||||
_, _, err = client.UpdateFile(owner, repo, filePath, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("update file err: %v", err))
|
||||
}
|
||||
return to.TextResult("Update file success")
|
||||
}
|
||||
|
||||
// Create new file
|
||||
opt := gitea_sdk.CreateFileOptions{
|
||||
Content: base64.StdEncoding.EncodeToString([]byte(content)),
|
||||
FileOptions: gitea_sdk.FileOptions{
|
||||
@@ -226,10 +235,8 @@ func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
|
||||
BranchName: branchName,
|
||||
},
|
||||
}
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
if newBranch, ok := args["new_branch_name"].(string); ok && newBranch != "" {
|
||||
opt.NewBranchName = newBranch
|
||||
}
|
||||
_, _, err = client.CreateFile(owner, repo, filePath, opt)
|
||||
if err != nil {
|
||||
@@ -238,48 +245,6 @@ func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
|
||||
return to.TextResult("Create file success")
|
||||
}
|
||||
|
||||
func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called UpdateFileFn")
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
filePath, err := params.GetString(args, "filePath")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
sha, err := params.GetString(args, "sha")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
content, _ := args["content"].(string)
|
||||
message, _ := args["message"].(string)
|
||||
branchName, _ := args["branch_name"].(string)
|
||||
|
||||
opt := gitea_sdk.UpdateFileOptions{
|
||||
SHA: sha,
|
||||
Content: base64.StdEncoding.EncodeToString([]byte(content)),
|
||||
FileOptions: gitea_sdk.FileOptions{
|
||||
Message: message,
|
||||
BranchName: branchName,
|
||||
},
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
_, _, err = client.UpdateFile(owner, repo, filePath, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("update file err: %v", err))
|
||||
}
|
||||
return to.TextResult("Update file success")
|
||||
}
|
||||
|
||||
func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called DeleteFileFn")
|
||||
args := req.GetArguments()
|
||||
|
||||
@@ -67,7 +67,7 @@ var (
|
||||
mcp.WithBoolean("is_draft", mcp.Description("Whether the release is draft"), mcp.DefaultBool(false)),
|
||||
mcp.WithBoolean("is_pre_release", mcp.Description("Whether the release is pre-release"), mcp.DefaultBool(false)),
|
||||
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
|
||||
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(20), mcp.Min(1)),
|
||||
mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(20), mcp.Min(1)),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -242,7 +242,7 @@ func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
|
||||
pIsPreRelease = new(isPreRelease)
|
||||
}
|
||||
page := params.GetOptionalInt(args, "page", 1)
|
||||
pageSize := params.GetOptionalInt(args, "pageSize", 20)
|
||||
pageSize := params.GetOptionalInt(args, "perPage", 20)
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -53,7 +53,7 @@ var (
|
||||
ListMyReposToolName,
|
||||
mcp.WithDescription("List my repositories"),
|
||||
mcp.WithNumber("page", mcp.Required(), mcp.Description("Page number"), mcp.DefaultNumber(1), mcp.Min(1)),
|
||||
mcp.WithNumber("pageSize", mcp.Required(), mcp.Description("Page size number"), mcp.DefaultNumber(30), mcp.Min(1)),
|
||||
mcp.WithNumber("perPage", mcp.Required(), mcp.Description("results per page"), mcp.DefaultNumber(30), mcp.Min(1)),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -72,39 +72,6 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
func RegisterTool(s *server.MCPServer) {
|
||||
s.AddTool(CreateRepoTool, CreateRepoFn)
|
||||
s.AddTool(ForkRepoTool, ForkRepoFn)
|
||||
s.AddTool(ListMyReposTool, ListMyReposFn)
|
||||
|
||||
// File
|
||||
s.AddTool(GetFileContentTool, GetFileContentFn)
|
||||
s.AddTool(CreateFileTool, CreateFileFn)
|
||||
s.AddTool(UpdateFileTool, UpdateFileFn)
|
||||
s.AddTool(DeleteFileTool, DeleteFileFn)
|
||||
|
||||
// Branch
|
||||
s.AddTool(CreateBranchTool, CreateBranchFn)
|
||||
s.AddTool(DeleteBranchTool, DeleteBranchFn)
|
||||
s.AddTool(ListBranchesTool, ListBranchesFn)
|
||||
|
||||
// Release
|
||||
s.AddTool(CreateReleaseTool, CreateReleaseFn)
|
||||
s.AddTool(DeleteReleaseTool, DeleteReleaseFn)
|
||||
s.AddTool(GetReleaseTool, GetReleaseFn)
|
||||
s.AddTool(GetLatestReleaseTool, GetLatestReleaseFn)
|
||||
s.AddTool(ListReleasesTool, ListReleasesFn)
|
||||
|
||||
// Tag
|
||||
s.AddTool(CreateTagTool, CreateTagFn)
|
||||
s.AddTool(DeleteTagTool, DeleteTagFn)
|
||||
s.AddTool(GetTagTool, GetTagFn)
|
||||
s.AddTool(ListTagsTool, ListTagsFn)
|
||||
|
||||
// Commit
|
||||
s.AddTool(ListRepoCommitsTool, ListRepoCommitsFn)
|
||||
}
|
||||
|
||||
func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called CreateRepoFn")
|
||||
args := req.GetArguments()
|
||||
|
||||
@@ -54,7 +54,7 @@ var (
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
|
||||
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(20), mcp.Min(1)),
|
||||
mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(20), mcp.Min(1)),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -179,7 +179,7 @@ func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
page := params.GetOptionalInt(args, "page", 1)
|
||||
pageSize := params.GetOptionalInt(args, "pageSize", 20)
|
||||
pageSize := params.GetOptionalInt(args, "perPage", 20)
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user