feat: slim tool responses (#141)

Reduce token usage by slimming tool responses. Instead of returning full Gitea SDK objects (with nested user/repo objects, avatars, permissions, etc.), each operation now has a colocated `slim.go` that extracts only the fields an LLM needs. List endpoints return even fewer fields than single-item endpoints.

Other changes:
- Add `params` helpers to DRY parameter extraction across 40+ handlers
- Remove `{"Result": ...}` wrapper for flatter responses
- Reduce default pageSize from 100 to 30

Fixes: https://gitea.com/gitea/gitea-mcp/issues/128

*Created by Claude on behalf of @silverwind*

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/141
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:
silverwind
2026-03-05 05:56:23 +00:00
committed by silverwind
parent 9ce5604e4c
commit c3db4fb65f
35 changed files with 2274 additions and 1156 deletions

View File

@@ -109,17 +109,17 @@ func limitBytes(data []byte, maxBytes int) ([]byte, bool) {
func GetRepoActionJobLogPreviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetRepoActionJobLogPreviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetRepoActionJobLogPreviewFn") log.Debugf("Called GetRepoActionJobLogPreviewFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
jobID, err := params.GetIndex(req.GetArguments(), "job_id") jobID, err := params.GetIndex(req.GetArguments(), "job_id")
if err != nil || jobID <= 0 { if err != nil {
return to.ErrorResult(errors.New("job_id is required")) return to.ErrorResult(err)
} }
tailLines := int(params.GetOptionalInt(req.GetArguments(), "tail_lines", 200)) tailLines := int(params.GetOptionalInt(req.GetArguments(), "tail_lines", 200))
maxBytes := int(params.GetOptionalInt(req.GetArguments(), "max_bytes", 65536)) maxBytes := int(params.GetOptionalInt(req.GetArguments(), "max_bytes", 65536))
@@ -144,17 +144,17 @@ func GetRepoActionJobLogPreviewFn(ctx context.Context, req mcp.CallToolRequest)
func DownloadRepoActionJobLogFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DownloadRepoActionJobLogFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DownloadRepoActionJobLogFn") log.Debugf("Called DownloadRepoActionJobLogFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
jobID, err := params.GetIndex(req.GetArguments(), "job_id") jobID, err := params.GetIndex(req.GetArguments(), "job_id")
if err != nil || jobID <= 0 { if err != nil {
return to.ErrorResult(errors.New("job_id is required")) return to.ErrorResult(err)
} }
outputPath, _ := req.GetArguments()["output_path"].(string) outputPath, _ := req.GetArguments()["output_path"].(string)

View File

@@ -39,7 +39,7 @@ var (
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30), mcp.Min(1)),
) )
GetRepoActionWorkflowTool = mcp.NewTool( GetRepoActionWorkflowTool = mcp.NewTool(
@@ -66,7 +66,7 @@ var (
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30), mcp.Min(1)),
mcp.WithString("status", mcp.Description("optional status filter")), mcp.WithString("status", mcp.Description("optional status filter")),
) )
@@ -100,7 +100,7 @@ var (
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30), mcp.Min(1)),
mcp.WithString("status", mcp.Description("optional status filter")), mcp.WithString("status", mcp.Description("optional status filter")),
) )
@@ -111,7 +111,7 @@ var (
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("run_id", mcp.Required(), mcp.Description("run ID")), mcp.WithNumber("run_id", mcp.Required(), mcp.Description("run ID")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30), mcp.Min(1)),
) )
) )
@@ -148,22 +148,21 @@ func doJSONWithFallback(ctx context.Context, method string, paths []string, quer
func ListRepoActionWorkflowsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoActionWorkflowsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoActionWorkflowsFn") log.Debugf("Called ListRepoActionWorkflowsFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 50)
query := url.Values{} query := url.Values{}
query.Set("page", strconv.Itoa(int(page))) query.Set("page", strconv.Itoa(page))
query.Set("limit", strconv.Itoa(int(pageSize))) query.Set("limit", strconv.Itoa(pageSize))
var result any var result any
err := doJSONWithFallback(ctx, "GET", err = doJSONWithFallback(ctx, "GET",
[]string{ []string{
fmt.Sprintf("repos/%s/%s/actions/workflows", url.PathEscape(owner), url.PathEscape(repo)), fmt.Sprintf("repos/%s/%s/actions/workflows", url.PathEscape(owner), url.PathEscape(repo)),
}, },
@@ -172,26 +171,26 @@ func ListRepoActionWorkflowsFn(ctx context.Context, req mcp.CallToolRequest) (*m
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list action workflows err: %v", err)) return to.ErrorResult(fmt.Errorf("list action workflows err: %v", err))
} }
return to.TextResult(result) return to.TextResult(slimActionWorkflows(result))
} }
func GetRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetRepoActionWorkflowFn") log.Debugf("Called GetRepoActionWorkflowFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
workflowID, ok := req.GetArguments()["workflow_id"].(string) workflowID, err := params.GetString(req.GetArguments(), "workflow_id")
if !ok || workflowID == "" { if err != nil || workflowID == "" {
return to.ErrorResult(errors.New("workflow_id is required")) return to.ErrorResult(errors.New("workflow_id is required"))
} }
var result any var result any
err := doJSONWithFallback(ctx, "GET", err = doJSONWithFallback(ctx, "GET",
[]string{ []string{
fmt.Sprintf("repos/%s/%s/actions/workflows/%s", url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(workflowID)), fmt.Sprintf("repos/%s/%s/actions/workflows/%s", url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(workflowID)),
}, },
@@ -200,25 +199,25 @@ func GetRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get action workflow err: %v", err)) return to.ErrorResult(fmt.Errorf("get action workflow err: %v", err))
} }
return to.TextResult(result) return to.TextResult(slimActionWorkflow(result))
} }
func DispatchRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DispatchRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DispatchRepoActionWorkflowFn") log.Debugf("Called DispatchRepoActionWorkflowFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
workflowID, ok := req.GetArguments()["workflow_id"].(string) workflowID, err := params.GetString(req.GetArguments(), "workflow_id")
if !ok || workflowID == "" { if err != nil || workflowID == "" {
return to.ErrorResult(errors.New("workflow_id is required")) return to.ErrorResult(errors.New("workflow_id is required"))
} }
ref, ok := req.GetArguments()["ref"].(string) ref, err := params.GetString(req.GetArguments(), "ref")
if !ok || ref == "" { if err != nil || ref == "" {
return to.ErrorResult(errors.New("ref is required")) return to.ErrorResult(errors.New("ref is required"))
} }
@@ -239,7 +238,7 @@ func DispatchRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest)
body["inputs"] = inputs body["inputs"] = inputs
} }
err := doJSONWithFallback(ctx, "POST", err = doJSONWithFallback(ctx, "POST",
[]string{ []string{
fmt.Sprintf("repos/%s/%s/actions/workflows/%s/dispatches", url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(workflowID)), fmt.Sprintf("repos/%s/%s/actions/workflows/%s/dispatches", url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(workflowID)),
fmt.Sprintf("repos/%s/%s/actions/workflows/%s/dispatch", url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(workflowID)), fmt.Sprintf("repos/%s/%s/actions/workflows/%s/dispatch", url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(workflowID)),
@@ -258,27 +257,26 @@ func DispatchRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest)
func ListRepoActionRunsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoActionRunsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoActionRunsFn") log.Debugf("Called ListRepoActionRunsFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 50)
statusFilter, _ := req.GetArguments()["status"].(string) statusFilter, _ := req.GetArguments()["status"].(string)
query := url.Values{} query := url.Values{}
query.Set("page", strconv.Itoa(int(page))) query.Set("page", strconv.Itoa(page))
query.Set("limit", strconv.Itoa(int(pageSize))) query.Set("limit", strconv.Itoa(pageSize))
if statusFilter != "" { if statusFilter != "" {
query.Set("status", statusFilter) query.Set("status", statusFilter)
} }
var result any var result any
err := doJSONWithFallback(ctx, "GET", err = doJSONWithFallback(ctx, "GET",
[]string{ []string{
fmt.Sprintf("repos/%s/%s/actions/runs", url.PathEscape(owner), url.PathEscape(repo)), fmt.Sprintf("repos/%s/%s/actions/runs", url.PathEscape(owner), url.PathEscape(repo)),
}, },
@@ -287,17 +285,17 @@ func ListRepoActionRunsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list action runs err: %v", err)) return to.ErrorResult(fmt.Errorf("list action runs err: %v", err))
} }
return to.TextResult(result) return to.TextResult(slimActionRuns(result))
} }
func GetRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetRepoActionRunFn") log.Debugf("Called GetRepoActionRunFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
runID, err := params.GetIndex(req.GetArguments(), "run_id") runID, err := params.GetIndex(req.GetArguments(), "run_id")
@@ -315,17 +313,17 @@ func GetRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get action run err: %v", err)) return to.ErrorResult(fmt.Errorf("get action run err: %v", err))
} }
return to.TextResult(result) return to.TextResult(slimActionRun(result))
} }
func CancelRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CancelRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CancelRepoActionRunFn") log.Debugf("Called CancelRepoActionRunFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
runID, err := params.GetIndex(req.GetArguments(), "run_id") runID, err := params.GetIndex(req.GetArguments(), "run_id")
@@ -347,12 +345,12 @@ func CancelRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.C
func RerunRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func RerunRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called RerunRepoActionRunFn") log.Debugf("Called RerunRepoActionRunFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
runID, err := params.GetIndex(req.GetArguments(), "run_id") runID, err := params.GetIndex(req.GetArguments(), "run_id")
@@ -379,27 +377,26 @@ func RerunRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
func ListRepoActionJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoActionJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoActionJobsFn") log.Debugf("Called ListRepoActionJobsFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 50)
statusFilter, _ := req.GetArguments()["status"].(string) statusFilter, _ := req.GetArguments()["status"].(string)
query := url.Values{} query := url.Values{}
query.Set("page", strconv.Itoa(int(page))) query.Set("page", strconv.Itoa(page))
query.Set("limit", strconv.Itoa(int(pageSize))) query.Set("limit", strconv.Itoa(pageSize))
if statusFilter != "" { if statusFilter != "" {
query.Set("status", statusFilter) query.Set("status", statusFilter)
} }
var result any var result any
err := doJSONWithFallback(ctx, "GET", err = doJSONWithFallback(ctx, "GET",
[]string{ []string{
fmt.Sprintf("repos/%s/%s/actions/jobs", url.PathEscape(owner), url.PathEscape(repo)), fmt.Sprintf("repos/%s/%s/actions/jobs", url.PathEscape(owner), url.PathEscape(repo)),
}, },
@@ -408,29 +405,28 @@ func ListRepoActionJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list action jobs err: %v", err)) return to.ErrorResult(fmt.Errorf("list action jobs err: %v", err))
} }
return to.TextResult(result) return to.TextResult(slimActionJobs(result))
} }
func ListRepoActionRunJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoActionRunJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoActionRunJobsFn") log.Debugf("Called ListRepoActionRunJobsFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
runID, err := params.GetIndex(req.GetArguments(), "run_id") runID, err := params.GetIndex(req.GetArguments(), "run_id")
if err != nil || runID <= 0 { if err != nil || runID <= 0 {
return to.ErrorResult(errors.New("run_id is required")) return to.ErrorResult(errors.New("run_id is required"))
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 50)
query := url.Values{} query := url.Values{}
query.Set("page", strconv.Itoa(int(page))) query.Set("page", strconv.Itoa(page))
query.Set("limit", strconv.Itoa(int(pageSize))) query.Set("limit", strconv.Itoa(pageSize))
var result any var result any
err = doJSONWithFallback(ctx, "GET", err = doJSONWithFallback(ctx, "GET",
@@ -442,5 +438,5 @@ func ListRepoActionRunJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list action run jobs err: %v", err)) return to.ErrorResult(fmt.Errorf("list action run jobs err: %v", err))
} }
return to.TextResult(result) return to.TextResult(slimActionJobs(result))
} }

View File

@@ -39,7 +39,7 @@ var (
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100), mcp.Min(1)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30), mcp.Min(1)),
) )
UpsertRepoActionSecretTool = mcp.NewTool( UpsertRepoActionSecretTool = mcp.NewTool(
@@ -65,7 +65,7 @@ var (
mcp.WithDescription("List organization Actions secrets (metadata only; secret values are never returned)"), mcp.WithDescription("List organization Actions secrets (metadata only; secret values are never returned)"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")), mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100), mcp.Min(1)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30), mcp.Min(1)),
) )
UpsertOrgActionSecretTool = mcp.NewTool( UpsertOrgActionSecretTool = mcp.NewTool(
@@ -97,16 +97,15 @@ func init() {
func ListRepoActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoActionSecretsFn") log.Debugf("Called ListRepoActionSecretsFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -114,7 +113,7 @@ func ListRepoActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
} }
secrets, _, err := client.ListRepoActionSecret(owner, repo, gitea_sdk.ListRepoActionSecretOption{ secrets, _, err := client.ListRepoActionSecret(owner, repo, gitea_sdk.ListRepoActionSecretOption{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)}, ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: pageSize},
}) })
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list repo action secrets err: %v", err)) return to.ErrorResult(fmt.Errorf("list repo action secrets err: %v", err))
@@ -136,20 +135,20 @@ func ListRepoActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
func UpsertRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func UpsertRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UpsertRepoActionSecretFn") log.Debugf("Called UpsertRepoActionSecretFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
name, ok := req.GetArguments()["name"].(string) name, err := params.GetString(req.GetArguments(), "name")
if !ok || name == "" { if err != nil || name == "" {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(errors.New("name is required"))
} }
data, ok := req.GetArguments()["data"].(string) data, err := params.GetString(req.GetArguments(), "data")
if !ok || data == "" { if err != nil || data == "" {
return to.ErrorResult(errors.New("data is required")) return to.ErrorResult(errors.New("data is required"))
} }
description, _ := req.GetArguments()["description"].(string) description, _ := req.GetArguments()["description"].(string)
@@ -171,16 +170,16 @@ func UpsertRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mc
func DeleteRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteRepoActionSecretFn") log.Debugf("Called DeleteRepoActionSecretFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
secretName, ok := req.GetArguments()["secretName"].(string) secretName, err := params.GetString(req.GetArguments(), "secretName")
if !ok || secretName == "" { if err != nil || secretName == "" {
return to.ErrorResult(errors.New("secretName is required")) return to.ErrorResult(errors.New("secretName is required"))
} }
@@ -197,12 +196,11 @@ func DeleteRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mc
func ListOrgActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListOrgActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListOrgActionSecretsFn") log.Debugf("Called ListOrgActionSecretsFn")
org, ok := req.GetArguments()["org"].(string) org, err := params.GetString(req.GetArguments(), "org")
if !ok || org == "" { if err != nil || org == "" {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(errors.New("org is required"))
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -210,7 +208,7 @@ func ListOrgActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
} }
secrets, _, err := client.ListOrgActionSecret(org, gitea_sdk.ListOrgActionSecretOption{ secrets, _, err := client.ListOrgActionSecret(org, gitea_sdk.ListOrgActionSecretOption{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)}, ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: pageSize},
}) })
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list org action secrets err: %v", err)) return to.ErrorResult(fmt.Errorf("list org action secrets err: %v", err))
@@ -232,16 +230,16 @@ func ListOrgActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
func UpsertOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func UpsertOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UpsertOrgActionSecretFn") log.Debugf("Called UpsertOrgActionSecretFn")
org, ok := req.GetArguments()["org"].(string) org, err := params.GetString(req.GetArguments(), "org")
if !ok || org == "" { if err != nil || org == "" {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(errors.New("org is required"))
} }
name, ok := req.GetArguments()["name"].(string) name, err := params.GetString(req.GetArguments(), "name")
if !ok || name == "" { if err != nil || name == "" {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(errors.New("name is required"))
} }
data, ok := req.GetArguments()["data"].(string) data, err := params.GetString(req.GetArguments(), "data")
if !ok || data == "" { if err != nil || data == "" {
return to.ErrorResult(errors.New("data is required")) return to.ErrorResult(errors.New("data is required"))
} }
description, _ := req.GetArguments()["description"].(string) description, _ := req.GetArguments()["description"].(string)
@@ -263,18 +261,18 @@ func UpsertOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
func DeleteOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteOrgActionSecretFn") log.Debugf("Called DeleteOrgActionSecretFn")
org, ok := req.GetArguments()["org"].(string) org, err := params.GetString(req.GetArguments(), "org")
if !ok || org == "" { if err != nil || org == "" {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(errors.New("org is required"))
} }
secretName, ok := req.GetArguments()["secretName"].(string) secretName, err := params.GetString(req.GetArguments(), "secretName")
if !ok || secretName == "" { if err != nil || secretName == "" {
return to.ErrorResult(errors.New("secretName is required")) return to.ErrorResult(errors.New("secretName is required"))
} }
escapedOrg := url.PathEscape(org) escapedOrg := url.PathEscape(org)
escapedSecret := url.PathEscape(secretName) escapedSecret := url.PathEscape(secretName)
_, err := gitea.DoJSON(ctx, "DELETE", fmt.Sprintf("orgs/%s/actions/secrets/%s", escapedOrg, escapedSecret), nil, nil, nil) _, err = gitea.DoJSON(ctx, "DELETE", fmt.Sprintf("orgs/%s/actions/secrets/%s", escapedOrg, escapedSecret), nil, nil, nil)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("delete org action secret err: %v", err)) return to.ErrorResult(fmt.Errorf("delete org action secret err: %v", err))
} }

92
operation/actions/slim.go Normal file
View File

@@ -0,0 +1,92 @@
package actions
func pick(m map[string]any, keys ...string) map[string]any {
out := make(map[string]any, len(keys))
for _, k := range keys {
if v, ok := m[k]; ok {
out[k] = v
}
}
return out
}
func slimPaginated(raw any, itemFn func(map[string]any) map[string]any) any {
m, ok := raw.(map[string]any)
if !ok {
return raw
}
result := make(map[string]any)
if tc, ok := m["total_count"]; ok {
result["total_count"] = tc
}
for key, val := range m {
if key == "total_count" {
continue
}
arr, ok := val.([]any)
if !ok {
continue
}
slimmed := make([]any, 0, len(arr))
for _, item := range arr {
if im, ok := item.(map[string]any); ok {
slimmed = append(slimmed, itemFn(im))
}
}
result[key] = slimmed
break
}
return result
}
func slimRun(m map[string]any) map[string]any {
return pick(m, "id", "name", "head_branch", "head_sha", "run_number",
"event", "status", "conclusion", "workflow_id",
"html_url", "created_at", "updated_at")
}
func slimJob(m map[string]any) map[string]any {
out := pick(m, "id", "run_id", "name", "workflow_name",
"status", "conclusion", "html_url",
"started_at", "completed_at")
if steps, ok := m["steps"].([]any); ok {
slim := make([]any, 0, len(steps))
for _, s := range steps {
if sm, ok := s.(map[string]any); ok {
slim = append(slim, pick(sm, "name", "number", "status", "conclusion"))
}
}
out["steps"] = slim
}
return out
}
func slimWorkflow(m map[string]any) map[string]any {
return pick(m, "id", "name", "path", "state", "html_url", "created_at", "updated_at")
}
func slimActionRun(raw any) any {
if m, ok := raw.(map[string]any); ok {
return slimRun(m)
}
return raw
}
func slimActionRuns(raw any) any {
return slimPaginated(raw, slimRun)
}
func slimActionJobs(raw any) any {
return slimPaginated(raw, slimJob)
}
func slimActionWorkflow(raw any) any {
if m, ok := raw.(map[string]any); ok {
return slimWorkflow(m)
}
return raw
}
func slimActionWorkflows(raw any) any {
return slimPaginated(raw, slimWorkflow)
}

View File

@@ -38,7 +38,7 @@ var (
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100), mcp.Min(1)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30), mcp.Min(1)),
) )
GetRepoActionVariableTool = mcp.NewTool( GetRepoActionVariableTool = mcp.NewTool(
@@ -80,7 +80,7 @@ var (
mcp.WithDescription("List organization Actions variables"), mcp.WithDescription("List organization Actions variables"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")), mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100), mcp.Min(1)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30), mcp.Min(1)),
) )
GetOrgActionVariableTool = mcp.NewTool( GetOrgActionVariableTool = mcp.NewTool(
@@ -132,23 +132,22 @@ func init() {
func ListRepoActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoActionVariablesFn") log.Debugf("Called ListRepoActionVariablesFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
query := url.Values{} query := url.Values{}
query.Set("page", strconv.Itoa(int(page))) query.Set("page", strconv.Itoa(page))
query.Set("limit", strconv.Itoa(int(pageSize))) query.Set("limit", strconv.Itoa(pageSize))
var result any var result any
_, err := gitea.DoJSON(ctx, "GET", fmt.Sprintf("repos/%s/%s/actions/variables", url.PathEscape(owner), url.PathEscape(repo)), query, nil, &result) _, err = gitea.DoJSON(ctx, "GET", fmt.Sprintf("repos/%s/%s/actions/variables", url.PathEscape(owner), url.PathEscape(repo)), query, nil, &result)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list repo action variables err: %v", err)) return to.ErrorResult(fmt.Errorf("list repo action variables err: %v", err))
} }
@@ -157,16 +156,16 @@ func ListRepoActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*m
func GetRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetRepoActionVariableFn") log.Debugf("Called GetRepoActionVariableFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
name, ok := req.GetArguments()["name"].(string) name, err := params.GetString(req.GetArguments(), "name")
if !ok || name == "" { if err != nil || name == "" {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(errors.New("name is required"))
} }
@@ -183,20 +182,20 @@ func GetRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
func CreateRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateRepoActionVariableFn") log.Debugf("Called CreateRepoActionVariableFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
name, ok := req.GetArguments()["name"].(string) name, err := params.GetString(req.GetArguments(), "name")
if !ok || name == "" { if err != nil || name == "" {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(errors.New("name is required"))
} }
value, ok := req.GetArguments()["value"].(string) value, err := params.GetString(req.GetArguments(), "value")
if !ok || value == "" { if err != nil || value == "" {
return to.ErrorResult(errors.New("value is required")) return to.ErrorResult(errors.New("value is required"))
} }
@@ -213,20 +212,20 @@ func CreateRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*
func UpdateRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func UpdateRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UpdateRepoActionVariableFn") log.Debugf("Called UpdateRepoActionVariableFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
name, ok := req.GetArguments()["name"].(string) name, err := params.GetString(req.GetArguments(), "name")
if !ok || name == "" { if err != nil || name == "" {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(errors.New("name is required"))
} }
value, ok := req.GetArguments()["value"].(string) value, err := params.GetString(req.GetArguments(), "value")
if !ok || value == "" { if err != nil || value == "" {
return to.ErrorResult(errors.New("value is required")) return to.ErrorResult(errors.New("value is required"))
} }
@@ -243,16 +242,16 @@ func UpdateRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*
func DeleteRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteRepoActionVariableFn") log.Debugf("Called DeleteRepoActionVariableFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok || owner == "" { if err != nil || owner == "" {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(errors.New("owner is required"))
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok || repo == "" { if err != nil || repo == "" {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(errors.New("repo is required"))
} }
name, ok := req.GetArguments()["name"].(string) name, err := params.GetString(req.GetArguments(), "name")
if !ok || name == "" { if err != nil || name == "" {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(errors.New("name is required"))
} }
@@ -269,19 +268,18 @@ func DeleteRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*
func ListOrgActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListOrgActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListOrgActionVariablesFn") log.Debugf("Called ListOrgActionVariablesFn")
org, ok := req.GetArguments()["org"].(string) org, err := params.GetString(req.GetArguments(), "org")
if !ok || org == "" { if err != nil || org == "" {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(errors.New("org is required"))
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
} }
variables, _, err := client.ListOrgActionVariable(org, gitea_sdk.ListOrgActionVariableOption{ variables, _, err := client.ListOrgActionVariable(org, gitea_sdk.ListOrgActionVariableOption{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)}, ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: pageSize},
}) })
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list org action variables err: %v", err)) return to.ErrorResult(fmt.Errorf("list org action variables err: %v", err))
@@ -291,12 +289,12 @@ func ListOrgActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*mc
func GetOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetOrgActionVariableFn") log.Debugf("Called GetOrgActionVariableFn")
org, ok := req.GetArguments()["org"].(string) org, err := params.GetString(req.GetArguments(), "org")
if !ok || org == "" { if err != nil || org == "" {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(errors.New("org is required"))
} }
name, ok := req.GetArguments()["name"].(string) name, err := params.GetString(req.GetArguments(), "name")
if !ok || name == "" { if err != nil || name == "" {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(errors.New("name is required"))
} }
@@ -313,16 +311,16 @@ func GetOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
func CreateOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateOrgActionVariableFn") log.Debugf("Called CreateOrgActionVariableFn")
org, ok := req.GetArguments()["org"].(string) org, err := params.GetString(req.GetArguments(), "org")
if !ok || org == "" { if err != nil || org == "" {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(errors.New("org is required"))
} }
name, ok := req.GetArguments()["name"].(string) name, err := params.GetString(req.GetArguments(), "name")
if !ok || name == "" { if err != nil || name == "" {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(errors.New("name is required"))
} }
value, ok := req.GetArguments()["value"].(string) value, err := params.GetString(req.GetArguments(), "value")
if !ok || value == "" { if err != nil || value == "" {
return to.ErrorResult(errors.New("value is required")) return to.ErrorResult(errors.New("value is required"))
} }
description, _ := req.GetArguments()["description"].(string) description, _ := req.GetArguments()["description"].(string)
@@ -344,16 +342,16 @@ func CreateOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*m
func UpdateOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func UpdateOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UpdateOrgActionVariableFn") log.Debugf("Called UpdateOrgActionVariableFn")
org, ok := req.GetArguments()["org"].(string) org, err := params.GetString(req.GetArguments(), "org")
if !ok || org == "" { if err != nil || org == "" {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(errors.New("org is required"))
} }
name, ok := req.GetArguments()["name"].(string) name, err := params.GetString(req.GetArguments(), "name")
if !ok || name == "" { if err != nil || name == "" {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(errors.New("name is required"))
} }
value, ok := req.GetArguments()["value"].(string) value, err := params.GetString(req.GetArguments(), "value")
if !ok || value == "" { if err != nil || value == "" {
return to.ErrorResult(errors.New("value is required")) return to.ErrorResult(errors.New("value is required"))
} }
description, _ := req.GetArguments()["description"].(string) description, _ := req.GetArguments()["description"].(string)
@@ -374,16 +372,16 @@ func UpdateOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*m
func DeleteOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteOrgActionVariableFn") log.Debugf("Called DeleteOrgActionVariableFn")
org, ok := req.GetArguments()["org"].(string) org, err := params.GetString(req.GetArguments(), "org")
if !ok || org == "" { if err != nil || org == "" {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(errors.New("org is required"))
} }
name, ok := req.GetArguments()["name"].(string) name, err := params.GetString(req.GetArguments(), "name")
if !ok || name == "" { if err != nil || name == "" {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(errors.New("name is required"))
} }
_, err := gitea.DoJSON(ctx, "DELETE", fmt.Sprintf("orgs/%s/actions/variables/%s", url.PathEscape(org), url.PathEscape(name)), nil, nil, nil) _, err = gitea.DoJSON(ctx, "DELETE", fmt.Sprintf("orgs/%s/actions/variables/%s", url.PathEscape(org), url.PathEscape(name)), nil, nil, nil)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("delete org action variable err: %v", err)) return to.ErrorResult(fmt.Errorf("delete org action variable err: %v", err))
} }

View File

@@ -2,7 +2,6 @@ package issue
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
@@ -44,7 +43,7 @@ var (
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("state", mcp.Description("issue state"), mcp.DefaultString("all")), mcp.WithString("state", mcp.Description("issue state"), mcp.DefaultString("all")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30)),
) )
CreateIssueTool = mcp.NewTool( CreateIssueTool = mcp.NewTool(
@@ -129,13 +128,13 @@ func init() {
func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetIssueByIndexFn") log.Debugf("Called GetIssueByIndexFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
@@ -150,30 +149,29 @@ func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
return to.ErrorResult(fmt.Errorf("get %v/%v/issue/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/issue/%v err: %v", owner, repo, index, err))
} }
return to.TextResult(issue) return to.TextResult(slimIssue(issue))
} }
func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListIssuesFn") log.Debugf("Called ListIssuesFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
state, ok := req.GetArguments()["state"].(string) state, ok := req.GetArguments()["state"].(string)
if !ok { if !ok {
state = "all" state = "all"
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
opt := gitea_sdk.ListIssueOption{ opt := gitea_sdk.ListIssueOption{
State: gitea_sdk.StateType(state), State: gitea_sdk.StateType(state),
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
@@ -184,26 +182,26 @@ func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/issues err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/issues err: %v", owner, repo, err))
} }
return to.TextResult(issues) return to.TextResult(slimIssues(issues))
} }
func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateIssueFn") log.Debugf("Called CreateIssueFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
title, ok := req.GetArguments()["title"].(string) title, err := params.GetString(req.GetArguments(), "title")
if !ok { if err != nil {
return to.ErrorResult(errors.New("title is required")) return to.ErrorResult(err)
} }
body, ok := req.GetArguments()["body"].(string) body, err := params.GetString(req.GetArguments(), "body")
if !ok { if err != nil {
return to.ErrorResult(errors.New("body is required")) return to.ErrorResult(err)
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -217,26 +215,26 @@ func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
return to.ErrorResult(fmt.Errorf("create %v/%v/issue err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("create %v/%v/issue err: %v", owner, repo, err))
} }
return to.TextResult(issue) return to.TextResult(slimIssue(issue))
} }
func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateIssueCommentFn") log.Debugf("Called CreateIssueCommentFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
body, ok := req.GetArguments()["body"].(string) body, err := params.GetString(req.GetArguments(), "body")
if !ok { if err != nil {
return to.ErrorResult(errors.New("body is required")) return to.ErrorResult(err)
} }
opt := gitea_sdk.CreateIssueCommentOption{ opt := gitea_sdk.CreateIssueCommentOption{
Body: body, Body: body,
@@ -250,18 +248,18 @@ func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
return to.ErrorResult(fmt.Errorf("create %v/%v/issue/%v/comment err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("create %v/%v/issue/%v/comment err: %v", owner, repo, index, err))
} }
return to.TextResult(issueComment) return to.TextResult(slimComment(issueComment))
} }
func EditIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func EditIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditIssueFn") log.Debugf("Called EditIssueFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
@@ -278,17 +276,7 @@ func EditIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
if ok { if ok {
opt.Body = new(body) opt.Body = new(body)
} }
var assignees []string opt.Assignees = params.GetStringSlice(req.GetArguments(), "assignees")
if assigneesArg, exists := req.GetArguments()["assignees"]; exists {
if assigneesSlice, ok := assigneesArg.([]any); ok {
for _, assignee := range assigneesSlice {
if assigneeStr, ok := assignee.(string); ok {
assignees = append(assignees, assigneeStr)
}
}
}
}
opt.Assignees = assignees
if val, exists := req.GetArguments()["milestone"]; exists { if val, exists := req.GetArguments()["milestone"]; exists {
if milestone, ok := params.ToInt64(val); ok { if milestone, ok := params.ToInt64(val); ok {
opt.Milestone = new(milestone) opt.Milestone = new(milestone)
@@ -308,26 +296,26 @@ func EditIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
return to.ErrorResult(fmt.Errorf("edit %v/%v/issue/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("edit %v/%v/issue/%v err: %v", owner, repo, index, err))
} }
return to.TextResult(issue) return to.TextResult(slimIssue(issue))
} }
func EditIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func EditIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditIssueCommentFn") log.Debugf("Called EditIssueCommentFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
commentID, err := params.GetIndex(req.GetArguments(), "commentID") commentID, err := params.GetIndex(req.GetArguments(), "commentID")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
body, ok := req.GetArguments()["body"].(string) body, err := params.GetString(req.GetArguments(), "body")
if !ok { if err != nil {
return to.ErrorResult(errors.New("body is required")) return to.ErrorResult(err)
} }
opt := gitea_sdk.EditIssueCommentOption{ opt := gitea_sdk.EditIssueCommentOption{
Body: body, Body: body,
@@ -341,18 +329,18 @@ func EditIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
return to.ErrorResult(fmt.Errorf("edit %v/%v/issues/comments/%v err: %v", owner, repo, commentID, err)) return to.ErrorResult(fmt.Errorf("edit %v/%v/issues/comments/%v err: %v", owner, repo, commentID, err))
} }
return to.TextResult(issueComment) return to.TextResult(slimComment(issueComment))
} }
func GetIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetIssueCommentsByIndexFn") log.Debugf("Called GetIssueCommentsByIndexFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
@@ -368,5 +356,5 @@ func GetIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*m
return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/comments err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/comments err: %v", owner, repo, index, err))
} }
return to.TextResult(issue) return to.TextResult(slimComments(issue))
} }

116
operation/issue/slim.go Normal file
View File

@@ -0,0 +1,116 @@
package issue
import (
gitea_sdk "code.gitea.io/sdk/gitea"
)
func userLogin(u *gitea_sdk.User) string {
if u == nil {
return ""
}
return u.UserName
}
func userLogins(users []*gitea_sdk.User) []string {
if len(users) == 0 {
return nil
}
out := make([]string, 0, len(users))
for _, u := range users {
if u != nil {
out = append(out, u.UserName)
}
}
return out
}
func labelNames(labels []*gitea_sdk.Label) []string {
if len(labels) == 0 {
return nil
}
out := make([]string, 0, len(labels))
for _, l := range labels {
if l != nil {
out = append(out, l.Name)
}
}
return out
}
func slimIssue(i *gitea_sdk.Issue) map[string]any {
if i == nil {
return nil
}
m := map[string]any{
"number": i.Index,
"title": i.Title,
"body": i.Body,
"state": i.State,
"html_url": i.HTMLURL,
"user": userLogin(i.Poster),
"labels": labelNames(i.Labels),
"comments": i.Comments,
"created_at": i.Created,
"updated_at": i.Updated,
"closed_at": i.Closed,
}
if len(i.Assignees) > 0 {
m["assignees"] = userLogins(i.Assignees)
}
if i.Milestone != nil {
m["milestone"] = map[string]any{
"id": i.Milestone.ID,
"title": i.Milestone.Title,
}
}
if i.PullRequest != nil {
m["is_pull"] = true
}
return m
}
func slimIssues(issues []*gitea_sdk.Issue) []map[string]any {
out := make([]map[string]any, 0, len(issues))
for _, i := range issues {
if i == nil {
continue
}
m := map[string]any{
"number": i.Index,
"title": i.Title,
"state": i.State,
"html_url": i.HTMLURL,
"user": userLogin(i.Poster),
"comments": i.Comments,
"created_at": i.Created,
"updated_at": i.Updated,
}
if len(i.Labels) > 0 {
m["labels"] = labelNames(i.Labels)
}
out = append(out, m)
}
return out
}
func slimComment(c *gitea_sdk.Comment) map[string]any {
if c == nil {
return nil
}
return map[string]any{
"id": c.ID,
"body": c.Body,
"user": userLogin(c.Poster),
"html_url": c.HTMLURL,
"created_at": c.Created,
"updated_at": c.Updated,
}
}
func slimComments(comments []*gitea_sdk.Comment) []map[string]any {
out := make([]map[string]any, 0, len(comments))
for _, c := range comments {
out = append(out, slimComment(c))
}
return out
}

View File

@@ -0,0 +1,69 @@
package issue
import (
"testing"
gitea_sdk "code.gitea.io/sdk/gitea"
)
func TestSlimIssue(t *testing.T) {
i := &gitea_sdk.Issue{
Index: 42,
Title: "Bug report",
Body: "Something is broken",
State: "open",
HTMLURL: "https://gitea.com/org/repo/issues/42",
Poster: &gitea_sdk.User{UserName: "alice"},
Labels: []*gitea_sdk.Label{{Name: "bug"}},
Milestone: &gitea_sdk.Milestone{
ID: 1,
Title: "v1.0",
},
PullRequest: &gitea_sdk.PullRequestMeta{HasMerged: false},
}
m := slimIssue(i)
if m["number"] != int64(42) {
t.Errorf("expected number 42, got %v", m["number"])
}
if m["body"] != "Something is broken" {
t.Errorf("expected body, got %v", m["body"])
}
if m["is_pull"] != true {
t.Error("expected is_pull true for issue with PullRequest")
}
ms := m["milestone"].(map[string]any)
if ms["title"] != "v1.0" {
t.Errorf("expected milestone title v1.0, got %v", ms["title"])
}
}
func TestSlimIssues_ListIsSlimmer(t *testing.T) {
i := &gitea_sdk.Issue{
Index: 1,
Title: "Issue",
State: "open",
Body: "Full body",
Poster: &gitea_sdk.User{UserName: "alice"},
Labels: []*gitea_sdk.Label{{Name: "enhancement"}},
}
single := slimIssue(i)
list := slimIssues([]*gitea_sdk.Issue{i})
// Single has body, list does not
if _, ok := single["body"]; !ok {
t.Error("single issue should have body")
}
if _, ok := list[0]["body"]; ok {
t.Error("list issue should not have body")
}
}
func TestSlimIssues_Nil(t *testing.T) {
if r := slimIssues(nil); len(r) != 0 {
t.Errorf("expected empty slice, got %v", r)
}
}

View File

@@ -41,7 +41,7 @@ var (
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30)),
) )
GetRepoLabelTool = mcp.NewTool( GetRepoLabelTool = mcp.NewTool(
@@ -121,7 +121,7 @@ var (
mcp.WithDescription("Lists labels defined at organization level"), mcp.WithDescription("Lists labels defined at organization level"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")), mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30)),
) )
CreateOrgLabelTool = mcp.NewTool( CreateOrgLabelTool = mcp.NewTool(
@@ -210,21 +210,20 @@ func init() {
func ListRepoLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoLabelsFn") log.Debugf("Called ListRepoLabelsFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
opt := gitea_sdk.ListLabelsOptions{ opt := gitea_sdk.ListLabelsOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
@@ -235,18 +234,18 @@ func ListRepoLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list %v/%v/labels err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("list %v/%v/labels err: %v", owner, repo, err))
} }
return to.TextResult(labels) return to.TextResult(slimLabels(labels))
} }
func GetRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetRepoLabelFn") log.Debugf("Called GetRepoLabelFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
id, err := params.GetIndex(req.GetArguments(), "id") id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil { if err != nil {
@@ -261,26 +260,26 @@ func GetRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/label/%v err: %v", owner, repo, id, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/label/%v err: %v", owner, repo, id, err))
} }
return to.TextResult(label) return to.TextResult(slimLabel(label))
} }
func CreateRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateRepoLabelFn") log.Debugf("Called CreateRepoLabelFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
name, ok := req.GetArguments()["name"].(string) name, err := params.GetString(req.GetArguments(), "name")
if !ok { if err != nil {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(err)
} }
color, ok := req.GetArguments()["color"].(string) color, err := params.GetString(req.GetArguments(), "color")
if !ok { if err != nil {
return to.ErrorResult(errors.New("color is required")) return to.ErrorResult(err)
} }
description, _ := req.GetArguments()["description"].(string) // Optional description, _ := req.GetArguments()["description"].(string) // Optional
@@ -298,18 +297,18 @@ func CreateRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create %v/%v/label err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("create %v/%v/label err: %v", owner, repo, err))
} }
return to.TextResult(label) return to.TextResult(slimLabel(label))
} }
func EditRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func EditRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditRepoLabelFn") log.Debugf("Called EditRepoLabelFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
id, err := params.GetIndex(req.GetArguments(), "id") id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil { if err != nil {
@@ -335,18 +334,18 @@ func EditRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("edit %v/%v/label/%v err: %v", owner, repo, id, err)) return to.ErrorResult(fmt.Errorf("edit %v/%v/label/%v err: %v", owner, repo, id, err))
} }
return to.TextResult(label) return to.TextResult(slimLabel(label))
} }
func DeleteRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteRepoLabelFn") log.Debugf("Called DeleteRepoLabelFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
id, err := params.GetIndex(req.GetArguments(), "id") id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil { if err != nil {
@@ -366,13 +365,13 @@ func DeleteRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
func AddIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func AddIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AddIssueLabelsFn") log.Debugf("Called AddIssueLabelsFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
@@ -403,18 +402,18 @@ func AddIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("add labels to %v/%v/issue/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("add labels to %v/%v/issue/%v err: %v", owner, repo, index, err))
} }
return to.TextResult(issueLabels) return to.TextResult(slimLabels(issueLabels))
} }
func ReplaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ReplaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ReplaceIssueLabelsFn") log.Debugf("Called ReplaceIssueLabelsFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
@@ -445,18 +444,18 @@ func ReplaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("replace labels on %v/%v/issue/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("replace labels on %v/%v/issue/%v err: %v", owner, repo, index, err))
} }
return to.TextResult(issueLabels) return to.TextResult(slimLabels(issueLabels))
} }
func ClearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ClearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ClearIssueLabelsFn") log.Debugf("Called ClearIssueLabelsFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
@@ -476,13 +475,13 @@ func ClearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
func RemoveIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func RemoveIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called RemoveIssueLabelFn") log.Debugf("Called RemoveIssueLabelFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
@@ -506,17 +505,16 @@ func RemoveIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
func ListOrgLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListOrgLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListOrgLabelsFn") log.Debugf("Called ListOrgLabelsFn")
org, ok := req.GetArguments()["org"].(string) org, err := params.GetString(req.GetArguments(), "org")
if !ok { if err != nil {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(err)
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
opt := gitea_sdk.ListOrgLabelsOptions{ opt := gitea_sdk.ListOrgLabelsOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
@@ -527,22 +525,22 @@ func ListOrgLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list %v/labels err: %v", org, err)) return to.ErrorResult(fmt.Errorf("list %v/labels err: %v", org, err))
} }
return to.TextResult(labels) return to.TextResult(slimLabels(labels))
} }
func CreateOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateOrgLabelFn") log.Debugf("Called CreateOrgLabelFn")
org, ok := req.GetArguments()["org"].(string) org, err := params.GetString(req.GetArguments(), "org")
if !ok { if err != nil {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(err)
} }
name, ok := req.GetArguments()["name"].(string) name, err := params.GetString(req.GetArguments(), "name")
if !ok { if err != nil {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(err)
} }
color, ok := req.GetArguments()["color"].(string) color, err := params.GetString(req.GetArguments(), "color")
if !ok { if err != nil {
return to.ErrorResult(errors.New("color is required")) return to.ErrorResult(err)
} }
description, _ := req.GetArguments()["description"].(string) description, _ := req.GetArguments()["description"].(string)
exclusive, _ := req.GetArguments()["exclusive"].(bool) exclusive, _ := req.GetArguments()["exclusive"].(bool)
@@ -562,14 +560,14 @@ func CreateOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create %v/labels err: %v", org, err)) return to.ErrorResult(fmt.Errorf("create %v/labels err: %v", org, err))
} }
return to.TextResult(label) return to.TextResult(slimLabel(label))
} }
func EditOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func EditOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditOrgLabelFn") log.Debugf("Called EditOrgLabelFn")
org, ok := req.GetArguments()["org"].(string) org, err := params.GetString(req.GetArguments(), "org")
if !ok { if err != nil {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(err)
} }
id, err := params.GetIndex(req.GetArguments(), "id") id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil { if err != nil {
@@ -598,14 +596,14 @@ func EditOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("edit %v/labels/%v err: %v", org, id, err)) return to.ErrorResult(fmt.Errorf("edit %v/labels/%v err: %v", org, id, err))
} }
return to.TextResult(label) return to.TextResult(slimLabel(label))
} }
func DeleteOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteOrgLabelFn") log.Debugf("Called DeleteOrgLabelFn")
org, ok := req.GetArguments()["org"].(string) org, err := params.GetString(req.GetArguments(), "org")
if !ok { if err != nil {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(err)
} }
id, err := params.GetIndex(req.GetArguments(), "id") id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil { if err != nil {

26
operation/label/slim.go Normal file
View File

@@ -0,0 +1,26 @@
package label
import (
gitea_sdk "code.gitea.io/sdk/gitea"
)
func slimLabel(l *gitea_sdk.Label) map[string]any {
if l == nil {
return nil
}
return map[string]any{
"id": l.ID,
"name": l.Name,
"color": l.Color,
"description": l.Description,
"exclusive": l.Exclusive,
}
}
func slimLabels(labels []*gitea_sdk.Label) []map[string]any {
out := make([]map[string]any, 0, len(labels))
for _, l := range labels {
out = append(out, slimLabel(l))
}
return out
}

View File

@@ -0,0 +1,25 @@
package label
import (
"testing"
gitea_sdk "code.gitea.io/sdk/gitea"
)
func TestSlimLabel(t *testing.T) {
l := &gitea_sdk.Label{
ID: 1,
Name: "bug",
Color: "#d73a4a",
Description: "Something isn't working",
Exclusive: false,
}
m := slimLabel(l)
if m["name"] != "bug" {
t.Errorf("expected name bug, got %v", m["name"])
}
if m["color"] != "#d73a4a" {
t.Errorf("expected color, got %v", m["color"])
}
}

View File

@@ -2,7 +2,6 @@ package milestone
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
@@ -43,7 +42,7 @@ var (
mcp.WithString("state", mcp.Description("milestone state"), mcp.DefaultString("all")), mcp.WithString("state", mcp.Description("milestone state"), mcp.DefaultString("all")),
mcp.WithString("name", mcp.Description("milestone name")), mcp.WithString("name", mcp.Description("milestone name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30)),
) )
CreateMilestoneTool = mcp.NewTool( CreateMilestoneTool = mcp.NewTool(
@@ -102,13 +101,13 @@ func init() {
func GetMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetMilestoneFn") log.Debugf("Called GetMilestoneFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
id, err := params.GetIndex(req.GetArguments(), "id") id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil { if err != nil {
@@ -123,35 +122,28 @@ func GetMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
return to.ErrorResult(fmt.Errorf("get %v/%v/milestone/%v err: %v", owner, repo, id, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/milestone/%v err: %v", owner, repo, id, err))
} }
return to.TextResult(milestone) return to.TextResult(slimMilestone(milestone))
} }
func ListMilestonesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListMilestonesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListMilestonesFn") log.Debugf("Called ListMilestonesFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
state, ok := req.GetArguments()["state"].(string) state := params.GetOptionalString(req.GetArguments(), "state", "all")
if !ok { name := params.GetOptionalString(req.GetArguments(), "name", "")
state = "all" page, pageSize := params.GetPagination(req.GetArguments(), 30)
}
name, ok := req.GetArguments()["name"].(string)
if !ok {
name = ""
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
opt := gitea_sdk.ListMilestoneOption{ opt := gitea_sdk.ListMilestoneOption{
State: gitea_sdk.StateType(state), State: gitea_sdk.StateType(state),
Name: name, Name: name,
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
@@ -162,22 +154,22 @@ func ListMilestonesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/milestones err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/milestones err: %v", owner, repo, err))
} }
return to.TextResult(milestones) return to.TextResult(slimMilestones(milestones))
} }
func CreateMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateMilestoneFn") log.Debugf("Called CreateMilestoneFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
title, ok := req.GetArguments()["title"].(string) title, err := params.GetString(req.GetArguments(), "title")
if !ok { if err != nil {
return to.ErrorResult(errors.New("title is required")) return to.ErrorResult(err)
} }
opt := gitea_sdk.CreateMilestoneOption{ opt := gitea_sdk.CreateMilestoneOption{
@@ -198,18 +190,18 @@ func CreateMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
return to.ErrorResult(fmt.Errorf("create %v/%v/milestone err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("create %v/%v/milestone err: %v", owner, repo, err))
} }
return to.TextResult(milestone) return to.TextResult(slimMilestone(milestone))
} }
func EditMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func EditMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditMilestoneFn") log.Debugf("Called EditMilestoneFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
id, err := params.GetIndex(req.GetArguments(), "id") id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil { if err != nil {
@@ -240,18 +232,18 @@ func EditMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
return to.ErrorResult(fmt.Errorf("edit %v/%v/milestone/%v err: %v", owner, repo, id, err)) return to.ErrorResult(fmt.Errorf("edit %v/%v/milestone/%v err: %v", owner, repo, id, err))
} }
return to.TextResult(milestone) return to.TextResult(slimMilestone(milestone))
} }
func DeleteMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteMilestoneFn") log.Debugf("Called DeleteMilestoneFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
id, err := params.GetIndex(req.GetArguments(), "id") id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil { if err != nil {

View File

@@ -0,0 +1,28 @@
package milestone
import (
gitea_sdk "code.gitea.io/sdk/gitea"
)
func slimMilestone(m *gitea_sdk.Milestone) map[string]any {
if m == nil {
return nil
}
return map[string]any{
"id": m.ID,
"title": m.Title,
"description": m.Description,
"state": m.State,
"open_issues": m.OpenIssues,
"closed_issues": m.ClosedIssues,
"due_on": m.Deadline,
}
}
func slimMilestones(milestones []*gitea_sdk.Milestone) []map[string]any {
out := make([]map[string]any, 0, len(milestones))
for _, m := range milestones {
out = append(out, slimMilestone(m))
}
return out
}

View File

@@ -2,7 +2,6 @@ package pull
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
@@ -63,7 +62,7 @@ var (
mcp.WithString("sort", mcp.Description("sort"), mcp.Enum("oldest", "recentupdate", "leastupdate", "mostcomment", "leastcomment", "priority"), mcp.DefaultString("recentupdate")), mcp.WithString("sort", mcp.Description("sort"), mcp.Enum("oldest", "recentupdate", "leastupdate", "mostcomment", "leastcomment", "priority"), mcp.DefaultString("recentupdate")),
mcp.WithNumber("milestone", mcp.Description("milestone")), mcp.WithNumber("milestone", mcp.Description("milestone")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30)),
) )
CreatePullRequestTool = mcp.NewTool( CreatePullRequestTool = mcp.NewTool(
@@ -104,7 +103,7 @@ var (
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("index", mcp.Required(), mcp.Description("pull request index")), mcp.WithNumber("index", mcp.Required(), mcp.Description("pull request index")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30)),
) )
GetPullRequestReviewTool = mcp.NewTool( GetPullRequestReviewTool = mcp.NewTool(
@@ -269,15 +268,16 @@ func init() {
func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetPullRequestByIndexFn") log.Debugf("Called GetPullRequestByIndexFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(args, "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -290,24 +290,25 @@ func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, index, err))
} }
return to.TextResult(pr) return to.TextResult(slimPullRequest(pr))
} }
func GetPullRequestDiffFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetPullRequestDiffFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetPullRequestDiffFn") log.Debugf("Called GetPullRequestDiffFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
binary, _ := req.GetArguments()["binary"].(bool) repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
binary, _ := args["binary"].(bool)
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -320,41 +321,31 @@ func GetPullRequestDiffFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v diff err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v diff err: %v", owner, repo, index, err))
} }
result := map[string]any{ return to.TextResult(string(diffBytes))
"diff": string(diffBytes),
"binary": binary,
"index": index,
"repo": repo,
"owner": owner,
}
return to.TextResult(result)
} }
func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoPullRequests") log.Debugf("Called ListRepoPullRequests")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
state, _ := req.GetArguments()["state"].(string) state, _ := args["state"].(string)
sort, ok := req.GetArguments()["sort"].(string) sort := params.GetOptionalString(args, "sort", "recentupdate")
if !ok { milestone := params.GetOptionalInt(args, "milestone", 0)
sort = "recentupdate" page, pageSize := params.GetPagination(args, 30)
}
milestone := params.GetOptionalInt(req.GetArguments(), "milestone", 0)
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
opt := gitea_sdk.ListPullRequestsOptions{ opt := gitea_sdk.ListPullRequestsOptions{
State: gitea_sdk.StateType(state), State: gitea_sdk.StateType(state),
Sort: sort, Sort: sort,
Milestone: milestone, Milestone: milestone,
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
@@ -366,34 +357,35 @@ func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
return to.ErrorResult(fmt.Errorf("list %v/%v/pull_requests err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("list %v/%v/pull_requests err: %v", owner, repo, err))
} }
return to.TextResult(pullRequests) return to.TextResult(slimPullRequests(pullRequests))
} }
func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreatePullRequestFn") log.Debugf("Called CreatePullRequestFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
title, ok := req.GetArguments()["title"].(string) title, err := params.GetString(args, "title")
if !ok { if err != nil {
return to.ErrorResult(errors.New("title is required")) return to.ErrorResult(err)
} }
body, ok := req.GetArguments()["body"].(string) body, err := params.GetString(args, "body")
if !ok { if err != nil {
return to.ErrorResult(errors.New("body is required")) return to.ErrorResult(err)
} }
head, ok := req.GetArguments()["head"].(string) head, err := params.GetString(args, "head")
if !ok { if err != nil {
return to.ErrorResult(errors.New("head is required")) return to.ErrorResult(err)
} }
base, ok := req.GetArguments()["base"].(string) base, err := params.GetString(args, "base")
if !ok { if err != nil {
return to.ErrorResult(errors.New("base is required")) return to.ErrorResult(err)
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -409,45 +401,27 @@ func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
return to.ErrorResult(fmt.Errorf("create %v/%v/pull_request err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("create %v/%v/pull_request err: %v", owner, repo, err))
} }
return to.TextResult(pr) return to.TextResult(slimPullRequest(pr))
} }
func CreatePullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreatePullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreatePullRequestReviewerFn") log.Debugf("Called CreatePullRequestReviewerFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(args, "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
var reviewers []string reviewers := params.GetStringSlice(args, "reviewers")
if reviewersArg, exists := req.GetArguments()["reviewers"]; exists { teamReviewers := params.GetStringSlice(args, "team_reviewers")
if reviewersSlice, ok := reviewersArg.([]any); ok {
for _, reviewer := range reviewersSlice {
if reviewerStr, ok := reviewer.(string); ok {
reviewers = append(reviewers, reviewerStr)
}
}
}
}
var teamReviewers []string
if teamReviewersArg, exists := req.GetArguments()["team_reviewers"]; exists {
if teamReviewersSlice, ok := teamReviewersArg.([]any); ok {
for _, teamReviewer := range teamReviewersSlice {
if teamReviewerStr, ok := teamReviewer.(string); ok {
teamReviewers = append(teamReviewers, teamReviewerStr)
}
}
}
}
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -476,40 +450,22 @@ func CreatePullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (
func DeletePullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeletePullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeletePullRequestReviewerFn") log.Debugf("Called DeletePullRequestReviewerFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(args, "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
var reviewers []string reviewers := params.GetStringSlice(args, "reviewers")
if reviewersArg, exists := req.GetArguments()["reviewers"]; exists { teamReviewers := params.GetStringSlice(args, "team_reviewers")
if reviewersSlice, ok := reviewersArg.([]any); ok {
for _, reviewer := range reviewersSlice {
if reviewerStr, ok := reviewer.(string); ok {
reviewers = append(reviewers, reviewerStr)
}
}
}
}
var teamReviewers []string
if teamReviewersArg, exists := req.GetArguments()["team_reviewers"]; exists {
if teamReviewersSlice, ok := teamReviewersArg.([]any); ok {
for _, teamReviewer := range teamReviewersSlice {
if teamReviewerStr, ok := teamReviewer.(string); ok {
teamReviewers = append(teamReviewers, teamReviewerStr)
}
}
}
}
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -537,20 +493,20 @@ func DeletePullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (
func ListPullRequestReviewsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListPullRequestReviewsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListPullRequestReviewsFn") log.Debugf("Called ListPullRequestReviewsFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) repo, err := params.GetString(args, "repo")
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100) if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
page, pageSize := params.GetPagination(args, 30)
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -559,32 +515,33 @@ func ListPullRequestReviewsFn(ctx context.Context, req mcp.CallToolRequest) (*mc
reviews, _, err := client.ListPullReviews(owner, repo, index, gitea_sdk.ListPullReviewsOptions{ reviews, _, err := client.ListPullReviews(owner, repo, index, gitea_sdk.ListPullReviewsOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
}) })
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list reviews for %v/%v/pr/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("list reviews for %v/%v/pr/%v err: %v", owner, repo, index, err))
} }
return to.TextResult(reviews) return to.TextResult(slimReviews(reviews))
} }
func GetPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetPullRequestReviewFn") log.Debugf("Called GetPullRequestReviewFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
reviewID, err := params.GetIndex(req.GetArguments(), "review_id") repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
reviewID, err := params.GetIndex(args, "review_id")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -599,24 +556,25 @@ func GetPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
return to.ErrorResult(fmt.Errorf("get review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("get review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
} }
return to.TextResult(review) return to.TextResult(slimReview(review))
} }
func ListPullRequestReviewCommentsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListPullRequestReviewCommentsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListPullRequestReviewCommentsFn") log.Debugf("Called ListPullRequestReviewCommentsFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
reviewID, err := params.GetIndex(req.GetArguments(), "review_id") repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
reviewID, err := params.GetIndex(args, "review_id")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -631,38 +589,39 @@ func ListPullRequestReviewCommentsFn(ctx context.Context, req mcp.CallToolReques
return to.ErrorResult(fmt.Errorf("list review comments for review %v on %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("list review comments for review %v on %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
} }
return to.TextResult(comments) return to.TextResult(slimReviewComments(comments))
} }
func CreatePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreatePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreatePullRequestReviewFn") log.Debugf("Called CreatePullRequestReviewFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(args, "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
opt := gitea_sdk.CreatePullReviewOptions{} opt := gitea_sdk.CreatePullReviewOptions{}
if state, ok := req.GetArguments()["state"].(string); ok { if state, ok := args["state"].(string); ok {
opt.State = gitea_sdk.ReviewStateType(state) opt.State = gitea_sdk.ReviewStateType(state)
} }
if body, ok := req.GetArguments()["body"].(string); ok { if body, ok := args["body"].(string); ok {
opt.Body = body opt.Body = body
} }
if commitID, ok := req.GetArguments()["commit_id"].(string); ok { if commitID, ok := args["commit_id"].(string); ok {
opt.CommitID = commitID opt.CommitID = commitID
} }
// Parse inline comments // Parse inline comments
if commentsArg, exists := req.GetArguments()["comments"]; exists { if commentsArg, exists := args["comments"]; exists {
if commentsSlice, ok := commentsArg.([]any); ok { if commentsSlice, ok := commentsArg.([]any); ok {
for _, comment := range commentsSlice { for _, comment := range commentsSlice {
if commentMap, ok := comment.(map[string]any); ok { if commentMap, ok := comment.(map[string]any); ok {
@@ -695,36 +654,37 @@ func CreatePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m
return to.ErrorResult(fmt.Errorf("create review for %v/%v/pr/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("create review for %v/%v/pr/%v err: %v", owner, repo, index, err))
} }
return to.TextResult(review) return to.TextResult(slimReview(review))
} }
func SubmitPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func SubmitPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called SubmitPullRequestReviewFn") log.Debugf("Called SubmitPullRequestReviewFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
reviewID, err := params.GetIndex(req.GetArguments(), "review_id") repo, err := params.GetString(args, "repo")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
state, ok := req.GetArguments()["state"].(string) index, err := params.GetIndex(args, "index")
if !ok { if err != nil {
return to.ErrorResult(errors.New("state is required")) return to.ErrorResult(err)
}
reviewID, err := params.GetIndex(args, "review_id")
if err != nil {
return to.ErrorResult(err)
}
state, err := params.GetString(args, "state")
if err != nil {
return to.ErrorResult(err)
} }
opt := gitea_sdk.SubmitPullReviewOptions{ opt := gitea_sdk.SubmitPullReviewOptions{
State: gitea_sdk.ReviewStateType(state), State: gitea_sdk.ReviewStateType(state),
} }
if body, ok := req.GetArguments()["body"].(string); ok { if body, ok := args["body"].(string); ok {
opt.Body = body opt.Body = body
} }
@@ -738,24 +698,25 @@ func SubmitPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m
return to.ErrorResult(fmt.Errorf("submit review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("submit review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
} }
return to.TextResult(review) return to.TextResult(slimReview(review))
} }
func DeletePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeletePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeletePullRequestReviewFn") log.Debugf("Called DeletePullRequestReviewFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
reviewID, err := params.GetIndex(req.GetArguments(), "review_id") repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
reviewID, err := params.GetIndex(args, "review_id")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -782,25 +743,26 @@ func DeletePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m
func DismissPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DismissPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DismissPullRequestReviewFn") log.Debugf("Called DismissPullRequestReviewFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
reviewID, err := params.GetIndex(req.GetArguments(), "review_id") repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
reviewID, err := params.GetIndex(args, "review_id")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
opt := gitea_sdk.DismissPullReviewOptions{} opt := gitea_sdk.DismissPullReviewOptions{}
if message, ok := req.GetArguments()["message"].(string); ok { if message, ok := args["message"].(string); ok {
opt.Message = message opt.Message = message
} }
@@ -826,38 +788,24 @@ func DismissPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*
func MergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func MergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called MergePullRequestFn") log.Debugf("Called MergePullRequestFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(args, "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
mergeStyle := "merge" mergeStyle := params.GetOptionalString(args, "merge_style", "merge")
if style, exists := req.GetArguments()["merge_style"].(string); exists && style != "" { title, _ := args["title"].(string)
mergeStyle = style message, _ := args["message"].(string)
} deleteBranch, _ := args["delete_branch"].(bool)
title := ""
if t, exists := req.GetArguments()["title"].(string); exists {
title = t
}
message := ""
if msg, exists := req.GetArguments()["message"].(string); exists {
message = msg
}
deleteBranch := false
if del, exists := req.GetArguments()["delete_branch"].(bool); exists {
deleteBranch = del
}
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -897,53 +845,46 @@ func MergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
func EditPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func EditPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditPullRequestFn") log.Debugf("Called EditPullRequestFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(args, "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
opt := gitea_sdk.EditPullRequestOption{} opt := gitea_sdk.EditPullRequestOption{}
if title, ok := req.GetArguments()["title"].(string); ok { if title, ok := args["title"].(string); ok {
opt.Title = title opt.Title = title
} }
if body, ok := req.GetArguments()["body"].(string); ok { if body, ok := args["body"].(string); ok {
opt.Body = new(body) opt.Body = new(body)
} }
if base, ok := req.GetArguments()["base"].(string); ok { if base, ok := args["base"].(string); ok {
opt.Base = base opt.Base = base
} }
if assignee, ok := req.GetArguments()["assignee"].(string); ok { if assignee, ok := args["assignee"].(string); ok {
opt.Assignee = assignee opt.Assignee = assignee
} }
if assigneesArg, exists := req.GetArguments()["assignees"]; exists { if assignees := params.GetStringSlice(args, "assignees"); assignees != nil {
if assigneesSlice, ok := assigneesArg.([]any); ok { opt.Assignees = assignees
var assignees []string
for _, a := range assigneesSlice {
if s, ok := a.(string); ok {
assignees = append(assignees, s)
}
}
opt.Assignees = assignees
}
} }
if val, exists := req.GetArguments()["milestone"]; exists { if val, exists := args["milestone"]; exists {
if milestone, ok := params.ToInt64(val); ok { if milestone, ok := params.ToInt64(val); ok {
opt.Milestone = milestone opt.Milestone = milestone
} }
} }
if state, ok := req.GetArguments()["state"].(string); ok { if state, ok := args["state"].(string); ok {
opt.State = new(gitea_sdk.StateType(state)) opt.State = new(gitea_sdk.StateType(state))
} }
if allowMaintainerEdit, ok := req.GetArguments()["allow_maintainer_edit"].(bool); ok { if allowMaintainerEdit, ok := args["allow_maintainer_edit"].(bool); ok {
opt.AllowMaintainerEdit = new(allowMaintainerEdit) opt.AllowMaintainerEdit = new(allowMaintainerEdit)
} }
@@ -957,5 +898,5 @@ func EditPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
return to.ErrorResult(fmt.Errorf("edit %v/%v/pr/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("edit %v/%v/pr/%v err: %v", owner, repo, index, err))
} }
return to.TextResult(pr) return to.TextResult(slimPullRequest(pr))
} }

View File

@@ -116,13 +116,11 @@ func TestEditPullRequestFn(t *testing.T) {
t.Fatalf("expected text content, got %T", result.Content[0]) t.Fatalf("expected text content, got %T", result.Content[0])
} }
var parsed struct { var parsed map[string]any
Result map[string]any `json:"Result"`
}
if err := json.Unmarshal([]byte(textContent.Text), &parsed); err != nil { if err := json.Unmarshal([]byte(textContent.Text), &parsed); err != nil {
t.Fatalf("unmarshal result text: %v", err) t.Fatalf("unmarshal result text: %v", err)
} }
if got := parsed.Result["title"].(string); got != "WIP: my feature" { if got := parsed["title"].(string); got != "WIP: my feature" {
t.Fatalf("result title = %q, want %q", got, "WIP: my feature") t.Fatalf("result title = %q, want %q", got, "WIP: my feature")
} }
}) })
@@ -239,20 +237,18 @@ func TestMergePullRequestFn(t *testing.T) {
t.Fatalf("expected text content, got %T", result.Content[0]) t.Fatalf("expected text content, got %T", result.Content[0])
} }
var parsed struct { var parsed map[string]any
Result map[string]any `json:"Result"`
}
if err := json.Unmarshal([]byte(textContent.Text), &parsed); err != nil { if err := json.Unmarshal([]byte(textContent.Text), &parsed); err != nil {
t.Fatalf("unmarshal result text: %v", err) t.Fatalf("unmarshal result text: %v", err)
} }
if parsed.Result["merged"] != true { if parsed["merged"] != true {
t.Fatalf("expected merged=true, got %v", parsed.Result["merged"]) t.Fatalf("expected merged=true, got %v", parsed["merged"])
} }
if parsed.Result["merge_style"] != "squash" { if parsed["merge_style"] != "squash" {
t.Fatalf("expected merge_style 'squash', got %v", parsed.Result["merge_style"]) t.Fatalf("expected merge_style 'squash', got %v", parsed["merge_style"])
} }
if parsed.Result["branch_deleted"] != true { if parsed["branch_deleted"] != true {
t.Fatalf("expected branch_deleted=true, got %v", parsed.Result["branch_deleted"]) t.Fatalf("expected branch_deleted=true, got %v", parsed["branch_deleted"])
} }
}) })
} }
@@ -370,27 +366,13 @@ func TestGetPullRequestDiffFn(t *testing.T) {
t.Fatalf("expected text content, got %T", result.Content[0]) t.Fatalf("expected text content, got %T", result.Content[0])
} }
var parsed struct { // The diff response is now a plain string
Result map[string]any `json:"Result"` var parsed string
}
if err := json.Unmarshal([]byte(textContent.Text), &parsed); err != nil { if err := json.Unmarshal([]byte(textContent.Text), &parsed); err != nil {
t.Fatalf("unmarshal result text: %v", err) t.Fatalf("unmarshal result text: %v", err)
} }
if parsed != diffRaw {
if got, ok := parsed.Result["diff"].(string); !ok || got != diffRaw { t.Fatalf("diff = %q, want %q", parsed, diffRaw)
t.Fatalf("diff = %q, want %q", got, diffRaw)
}
if got, ok := parsed.Result["binary"].(bool); !ok || got != true {
t.Fatalf("binary = %v, want true", got)
}
if got, ok := parsed.Result["index"].(float64); !ok || int64(got) != int64(index) {
t.Fatalf("index = %v, want %d", got, index)
}
if got, ok := parsed.Result["owner"].(string); !ok || got != owner {
t.Fatalf("owner = %q, want %q", got, owner)
}
if got, ok := parsed.Result["repo"].(string); !ok || got != repo {
t.Fatalf("repo = %q, want %q", got, repo)
} }
}) })
} }

191
operation/pull/slim.go Normal file
View File

@@ -0,0 +1,191 @@
package pull
import (
gitea_sdk "code.gitea.io/sdk/gitea"
)
func userLogin(u *gitea_sdk.User) string {
if u == nil {
return ""
}
return u.UserName
}
func userLogins(users []*gitea_sdk.User) []string {
if len(users) == 0 {
return nil
}
out := make([]string, 0, len(users))
for _, u := range users {
if u != nil {
out = append(out, u.UserName)
}
}
return out
}
func labelNames(labels []*gitea_sdk.Label) []string {
if len(labels) == 0 {
return nil
}
out := make([]string, 0, len(labels))
for _, l := range labels {
if l != nil {
out = append(out, l.Name)
}
}
return out
}
func repoRef(r *gitea_sdk.Repository) map[string]any {
if r == nil {
return nil
}
return map[string]any{
"full_name": r.FullName,
"description": r.Description,
}
}
func slimPullRequest(pr *gitea_sdk.PullRequest) map[string]any {
if pr == nil {
return nil
}
m := map[string]any{
"number": pr.Index,
"title": pr.Title,
"body": pr.Body,
"state": pr.State,
"draft": pr.Draft,
"merged": pr.HasMerged,
"mergeable": pr.Mergeable,
"html_url": pr.HTMLURL,
"user": userLogin(pr.Poster),
"labels": labelNames(pr.Labels),
"comments": pr.Comments,
"created_at": pr.Created,
"updated_at": pr.Updated,
"closed_at": pr.Closed,
}
if pr.HasMerged {
m["merged_at"] = pr.Merged
m["merge_commit_sha"] = pr.MergedCommitID
m["merged_by"] = userLogin(pr.MergedBy)
}
if pr.Head != nil {
head := map[string]any{"ref": pr.Head.Ref, "sha": pr.Head.Sha}
if pr.Head.Repository != nil {
head["repo"] = repoRef(pr.Head.Repository)
}
m["head"] = head
}
if pr.Base != nil {
base := map[string]any{"ref": pr.Base.Ref, "sha": pr.Base.Sha}
if pr.Base.Repository != nil {
base["repo"] = repoRef(pr.Base.Repository)
}
m["base"] = base
}
if pr.Additions != nil {
m["additions"] = *pr.Additions
}
if pr.Deletions != nil {
m["deletions"] = *pr.Deletions
}
if pr.ChangedFiles != nil {
m["changed_files"] = *pr.ChangedFiles
}
if len(pr.Assignees) > 0 {
m["assignees"] = userLogins(pr.Assignees)
}
if pr.Milestone != nil {
m["milestone"] = pr.Milestone.Title
}
if pr.ReviewComments > 0 {
m["review_comments"] = pr.ReviewComments
}
return m
}
func slimPullRequests(prs []*gitea_sdk.PullRequest) []map[string]any {
out := make([]map[string]any, 0, len(prs))
for _, pr := range prs {
if pr == nil {
continue
}
m := map[string]any{
"number": pr.Index,
"title": pr.Title,
"state": pr.State,
"draft": pr.Draft,
"merged": pr.HasMerged,
"html_url": pr.HTMLURL,
"user": userLogin(pr.Poster),
"created_at": pr.Created,
"updated_at": pr.Updated,
}
if pr.Head != nil {
m["head"] = pr.Head.Ref
}
if pr.Base != nil {
m["base"] = pr.Base.Ref
}
if len(pr.Labels) > 0 {
m["labels"] = labelNames(pr.Labels)
}
out = append(out, m)
}
return out
}
func slimReview(r *gitea_sdk.PullReview) map[string]any {
if r == nil {
return nil
}
return map[string]any{
"id": r.ID,
"state": r.State,
"body": r.Body,
"user": userLogin(r.Reviewer),
"comments_count": r.CodeCommentsCount,
"submitted_at": r.Submitted,
"html_url": r.HTMLURL,
"stale": r.Stale,
"official": r.Official,
"dismissed": r.Dismissed,
}
}
func slimReviews(reviews []*gitea_sdk.PullReview) []map[string]any {
out := make([]map[string]any, 0, len(reviews))
for _, r := range reviews {
out = append(out, slimReview(r))
}
return out
}
func slimReviewComment(c *gitea_sdk.PullReviewComment) map[string]any {
if c == nil {
return nil
}
return map[string]any{
"id": c.ID,
"body": c.Body,
"path": c.Path,
"position": c.LineNum,
"old_position": c.OldLineNum,
"diff_hunk": c.DiffHunk,
"user": userLogin(c.Reviewer),
"html_url": c.HTMLURL,
"created_at": c.Created,
"updated_at": c.Updated,
}
}
func slimReviewComments(comments []*gitea_sdk.PullReviewComment) []map[string]any {
out := make([]map[string]any, 0, len(comments))
for _, c := range comments {
out = append(out, slimReviewComment(c))
}
return out
}

124
operation/pull/slim_test.go Normal file
View File

@@ -0,0 +1,124 @@
package pull
import (
"testing"
"time"
gitea_sdk "code.gitea.io/sdk/gitea"
)
func TestSlimPullRequest(t *testing.T) {
now := time.Now()
additions := 10
deletions := 5
changedFiles := 3
pr := &gitea_sdk.PullRequest{
Index: 1,
Title: "Fix bug",
Body: "Fixes #123",
State: "open",
Draft: false,
HasMerged: false,
Mergeable: true,
HTMLURL: "https://gitea.com/org/repo/pulls/1",
Poster: &gitea_sdk.User{UserName: "bob"},
Labels: []*gitea_sdk.Label{
{Name: "bug"},
{Name: "priority"},
},
Comments: 2,
Created: &now,
Updated: &now,
Additions: &additions,
Deletions: &deletions,
ChangedFiles: &changedFiles,
Head: &gitea_sdk.PRBranchInfo{
Ref: "fix-branch",
Sha: "abc123",
},
Base: &gitea_sdk.PRBranchInfo{
Ref: "main",
Sha: "def456",
},
Assignees: []*gitea_sdk.User{
{UserName: "alice"},
},
Milestone: &gitea_sdk.Milestone{Title: "v1.0"},
}
m := slimPullRequest(pr)
if m["number"] != int64(1) {
t.Errorf("expected number 1, got %v", m["number"])
}
if m["title"] != "Fix bug" {
t.Errorf("expected title Fix bug, got %v", m["title"])
}
if m["user"] != "bob" {
t.Errorf("expected user bob, got %v", m["user"])
}
if m["additions"] != 10 {
t.Errorf("expected additions 10, got %v", m["additions"])
}
if m["milestone"] != "v1.0" {
t.Errorf("expected milestone v1.0, got %v", m["milestone"])
}
labels := m["labels"].([]string)
if len(labels) != 2 || labels[0] != "bug" {
t.Errorf("expected labels [bug priority], got %v", labels)
}
head := m["head"].(map[string]any)
if head["ref"] != "fix-branch" {
t.Errorf("expected head ref fix-branch, got %v", head["ref"])
}
assignees := m["assignees"].([]string)
if len(assignees) != 1 || assignees[0] != "alice" {
t.Errorf("expected assignees [alice], got %v", assignees)
}
// merged fields should not be present for unmerged PR
if _, ok := m["merged_at"]; ok {
t.Error("merged_at should not be present for unmerged PR")
}
}
func TestSlimPullRequests_ListIsSlimmer(t *testing.T) {
pr := &gitea_sdk.PullRequest{
Index: 1,
Title: "PR title",
State: "open",
HTMLURL: "https://gitea.com/org/repo/pulls/1",
Poster: &gitea_sdk.User{UserName: "bob"},
Body: "Full body text here",
Head: &gitea_sdk.PRBranchInfo{Ref: "feature"},
Base: &gitea_sdk.PRBranchInfo{Ref: "main"},
}
single := slimPullRequest(pr)
list := slimPullRequests([]*gitea_sdk.PullRequest{pr})
// Single has body, list does not
if _, ok := single["body"]; !ok {
t.Error("single PR should have body")
}
if _, ok := list[0]["body"]; ok {
t.Error("list PR should not have body")
}
// List has head as string ref, single has head as map
if _, ok := single["head"].(map[string]any); !ok {
t.Error("single PR head should be a map")
}
if list[0]["head"] != "feature" {
t.Errorf("list PR head should be string ref, got %v", list[0]["head"])
}
}
func TestSlimPullRequests_Nil(t *testing.T) {
if r := slimPullRequests(nil); len(r) != 0 {
t.Errorf("expected empty slice, got %v", r)
}
}

View File

@@ -2,11 +2,11 @@ package repo
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
@@ -63,19 +63,20 @@ func init() {
func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateBranchFn") log.Debugf("Called CreateBranchFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
branch, ok := req.GetArguments()["branch"].(string) branch, err := params.GetString(args, "branch")
if !ok { if err != nil {
return to.ErrorResult(errors.New("branch is required")) return to.ErrorResult(err)
} }
oldBranch, _ := req.GetArguments()["old_branch"].(string) oldBranch, _ := args["old_branch"].(string)
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -94,17 +95,18 @@ func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteBranchFn") log.Debugf("Called DeleteBranchFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
branch, ok := req.GetArguments()["branch"].(string) branch, err := params.GetString(args, "branch")
if !ok { if err != nil {
return to.ErrorResult(errors.New("branch is required")) return to.ErrorResult(err)
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -120,18 +122,19 @@ func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListBranchesFn") log.Debugf("Called ListBranchesFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
opt := gitea_sdk.ListRepoBranchesOptions{ opt := gitea_sdk.ListRepoBranchesOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: 1, Page: 1,
PageSize: 100, PageSize: 30,
}, },
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
@@ -143,5 +146,5 @@ func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
return to.ErrorResult(fmt.Errorf("list branches error: %v", err)) return to.ErrorResult(fmt.Errorf("list branches error: %v", err))
} }
return to.TextResult(branches) return to.TextResult(slimBranches(branches))
} }

View File

@@ -2,7 +2,6 @@ package repo
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
@@ -27,7 +26,7 @@ var ListRepoCommitsTool = mcp.NewTool(
mcp.WithString("sha", mcp.Description("SHA or branch to start listing commits from")), 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.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", mcp.Required(), mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("page_size", mcp.Required(), mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)), mcp.WithNumber("page_size", mcp.Required(), mcp.Description("page size"), mcp.DefaultNumber(30), mcp.Min(1)),
) )
func init() { func init() {
@@ -39,24 +38,25 @@ func init() {
func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoCommitsFn") log.Debugf("Called ListRepoCommitsFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
page, err := params.GetIndex(req.GetArguments(), "page")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
pageSize, err := params.GetIndex(req.GetArguments(), "page_size") repo, err := params.GetString(args, "repo")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
sha, _ := req.GetArguments()["sha"].(string) page, err := params.GetIndex(args, "page")
path, _ := req.GetArguments()["path"].(string) if err != nil {
return to.ErrorResult(err)
}
pageSize, err := params.GetIndex(args, "page_size")
if err != nil {
return to.ErrorResult(err)
}
sha, _ := args["sha"].(string)
path, _ := args["path"].(string)
opt := gitea_sdk.ListCommitOptions{ opt := gitea_sdk.ListCommitOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: int(page),
@@ -73,5 +73,5 @@ func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list repo commits err: %v", err)) return to.ErrorResult(fmt.Errorf("list repo commits err: %v", err))
} }
return to.TextResult(commits) return to.TextResult(slimCommits(commits))
} }

View File

@@ -6,11 +6,11 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
@@ -112,18 +112,19 @@ type ContentLine struct {
func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetFileFn") log.Debugf("Called GetFileFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
ref, _ := req.GetArguments()["ref"].(string) ref, _ := args["ref"].(string)
filePath, ok := req.GetArguments()["filePath"].(string) filePath, err := params.GetString(args, "filePath")
if !ok { if err != nil {
return to.ErrorResult(errors.New("filePath is required")) return to.ErrorResult(err)
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -133,7 +134,7 @@ func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get file err: %v", err)) return to.ErrorResult(fmt.Errorf("get file err: %v", err))
} }
withLines, _ := req.GetArguments()["withLines"].(bool) withLines, _ := args["withLines"].(bool)
if withLines { if withLines {
rawContent, err := base64.StdEncoding.DecodeString(*content.Content) rawContent, err := base64.StdEncoding.DecodeString(*content.Content)
if err != nil { if err != nil {
@@ -170,23 +171,24 @@ func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
contentStr := string(contentBytes) contentStr := string(contentBytes)
content.Content = &contentStr content.Content = &contentStr
} }
return to.TextResult(content) return to.TextResult(slimContents(content))
} }
func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetDirContentFn") log.Debugf("Called GetDirContentFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
ref, _ := req.GetArguments()["ref"].(string) ref, _ := args["ref"].(string)
filePath, ok := req.GetArguments()["filePath"].(string) filePath, err := params.GetString(args, "filePath")
if !ok { if err != nil {
return to.ErrorResult(errors.New("filePath is required")) return to.ErrorResult(err)
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -196,26 +198,27 @@ func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get dir content err: %v", err)) return to.ErrorResult(fmt.Errorf("get dir content err: %v", err))
} }
return to.TextResult(content) return to.TextResult(slimDirEntries(content))
} }
func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateFileFn") log.Debugf("Called CreateFileFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
filePath, ok := req.GetArguments()["filePath"].(string) filePath, err := params.GetString(args, "filePath")
if !ok { if err != nil {
return to.ErrorResult(errors.New("filePath is required")) return to.ErrorResult(err)
} }
content, _ := req.GetArguments()["content"].(string) content, _ := args["content"].(string)
message, _ := req.GetArguments()["message"].(string) message, _ := args["message"].(string)
branchName, _ := req.GetArguments()["branch_name"].(string) branchName, _ := args["branch_name"].(string)
opt := gitea_sdk.CreateFileOptions{ opt := gitea_sdk.CreateFileOptions{
Content: base64.StdEncoding.EncodeToString([]byte(content)), Content: base64.StdEncoding.EncodeToString([]byte(content)),
FileOptions: gitea_sdk.FileOptions{ FileOptions: gitea_sdk.FileOptions{
@@ -237,25 +240,26 @@ func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UpdateFileFn") log.Debugf("Called UpdateFileFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
filePath, ok := req.GetArguments()["filePath"].(string) filePath, err := params.GetString(args, "filePath")
if !ok { if err != nil {
return to.ErrorResult(errors.New("filePath is required")) return to.ErrorResult(err)
} }
sha, ok := req.GetArguments()["sha"].(string) sha, err := params.GetString(args, "sha")
if !ok { if err != nil {
return to.ErrorResult(errors.New("sha is required")) return to.ErrorResult(err)
} }
content, _ := req.GetArguments()["content"].(string) content, _ := args["content"].(string)
message, _ := req.GetArguments()["message"].(string) message, _ := args["message"].(string)
branchName, _ := req.GetArguments()["branch_name"].(string) branchName, _ := args["branch_name"].(string)
opt := gitea_sdk.UpdateFileOptions{ opt := gitea_sdk.UpdateFileOptions{
SHA: sha, SHA: sha,
@@ -278,23 +282,24 @@ func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteFileFn") log.Debugf("Called DeleteFileFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
filePath, ok := req.GetArguments()["filePath"].(string) filePath, err := params.GetString(args, "filePath")
if !ok { if err != nil {
return to.ErrorResult(errors.New("filePath is required")) return to.ErrorResult(err)
} }
message, _ := req.GetArguments()["message"].(string) message, _ := args["message"].(string)
branchName, _ := req.GetArguments()["branch_name"].(string) branchName, _ := args["branch_name"].(string)
sha, ok := req.GetArguments()["sha"].(string) sha, err := params.GetString(args, "sha")
if !ok { if err != nil {
return to.ErrorResult(errors.New("sha is required")) return to.ErrorResult(err)
} }
opt := gitea_sdk.DeleteFileOptions{ opt := gitea_sdk.DeleteFileOptions{
FileOptions: gitea_sdk.FileOptions{ FileOptions: gitea_sdk.FileOptions{

View File

@@ -2,9 +2,7 @@ package repo
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"time"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/log"
@@ -96,44 +94,32 @@ func init() {
}) })
} }
// To avoid return too many tokens, we need to provide at least information as possible
// llm can call get release to get more information
type ListReleaseResult struct {
ID int64 `json:"id"`
TagName string `json:"tag_name"`
Target string `json:"target_commitish"`
Title string `json:"title"`
IsDraft bool `json:"draft"`
IsPrerelease bool `json:"prerelease"`
CreatedAt time.Time `json:"created_at"`
PublishedAt time.Time `json:"published_at"`
}
func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateReleasesFn") log.Debugf("Called CreateReleasesFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return nil, errors.New("owner is required") if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return nil, errors.New("repo is required") return to.ErrorResult(err)
} }
tagName, ok := req.GetArguments()["tag_name"].(string) tagName, err := params.GetString(args, "tag_name")
if !ok { if err != nil {
return nil, errors.New("tag_name is required") return to.ErrorResult(err)
} }
target, ok := req.GetArguments()["target"].(string) target, err := params.GetString(args, "target")
if !ok { if err != nil {
return nil, errors.New("target is required") return to.ErrorResult(err)
} }
title, ok := req.GetArguments()["title"].(string) title, err := params.GetString(args, "title")
if !ok { if err != nil {
return nil, errors.New("title is required") return to.ErrorResult(err)
} }
isDraft, _ := req.GetArguments()["is_draft"].(bool) isDraft, _ := args["is_draft"].(bool)
isPreRelease, _ := req.GetArguments()["is_pre_release"].(bool) isPreRelease, _ := args["is_pre_release"].(bool)
body, _ := req.GetArguments()["body"].(string) body, _ := args["body"].(string)
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -156,15 +142,16 @@ func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteReleaseFn") log.Debugf("Called DeleteReleaseFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return nil, errors.New("owner is required") if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return nil, errors.New("repo is required") return to.ErrorResult(err)
} }
id, err := params.GetIndex(req.GetArguments(), "id") id, err := params.GetIndex(args, "id")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -183,15 +170,16 @@ func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetReleaseFn") log.Debugf("Called GetReleaseFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return nil, errors.New("owner is required") if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return nil, errors.New("repo is required") return to.ErrorResult(err)
} }
id, err := params.GetIndex(req.GetArguments(), "id") id, err := params.GetIndex(args, "id")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -205,18 +193,19 @@ func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
return nil, fmt.Errorf("get release error: %v", err) return nil, fmt.Errorf("get release error: %v", err)
} }
return to.TextResult(release) return to.TextResult(slimRelease(release))
} }
func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetLatestReleaseFn") log.Debugf("Called GetLatestReleaseFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return nil, errors.New("owner is required") if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return nil, errors.New("repo is required") return to.ErrorResult(err)
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
@@ -228,31 +217,32 @@ func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
return nil, fmt.Errorf("get latest release error: %v", err) return nil, fmt.Errorf("get latest release error: %v", err)
} }
return to.TextResult(release) return to.TextResult(slimRelease(release))
} }
func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListReleasesFn") log.Debugf("Called ListReleasesFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return nil, errors.New("owner is required") if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return nil, errors.New("repo is required") return to.ErrorResult(err)
} }
var pIsDraft *bool var pIsDraft *bool
isDraft, ok := req.GetArguments()["is_draft"].(bool) isDraft, ok := args["is_draft"].(bool)
if ok { if ok {
pIsDraft = new(isDraft) pIsDraft = new(isDraft)
} }
var pIsPreRelease *bool var pIsPreRelease *bool
isPreRelease, ok := req.GetArguments()["is_pre_release"].(bool) isPreRelease, ok := args["is_pre_release"].(bool)
if ok { if ok {
pIsPreRelease = new(isPreRelease) pIsPreRelease = new(isPreRelease)
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page := params.GetOptionalInt(args, "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 20) pageSize := params.GetOptionalInt(args, "pageSize", 20)
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -270,18 +260,5 @@ func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
return nil, fmt.Errorf("list releases error: %v", err) return nil, fmt.Errorf("list releases error: %v", err)
} }
results := make([]ListReleaseResult, len(releases)) return to.TextResult(slimReleases(releases))
for _, release := range releases {
results = append(results, ListReleaseResult{
ID: release.ID,
TagName: release.TagName,
Target: release.Target,
Title: release.Title,
IsDraft: release.IsDraft,
IsPrerelease: release.IsPrerelease,
CreatedAt: release.CreatedAt,
PublishedAt: release.PublishedAt,
})
}
return to.TextResult(results)
} }

View File

@@ -2,7 +2,6 @@ package repo
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
@@ -54,7 +53,7 @@ var (
ListMyReposToolName, ListMyReposToolName,
mcp.WithDescription("List my repositories"), mcp.WithDescription("List my repositories"),
mcp.WithNumber("page", mcp.Required(), mcp.Description("Page number"), mcp.DefaultNumber(1), mcp.Min(1)), 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(100), mcp.Min(1)), mcp.WithNumber("pageSize", mcp.Required(), mcp.Description("Page size number"), mcp.DefaultNumber(30), mcp.Min(1)),
) )
) )
@@ -108,20 +107,21 @@ func RegisterTool(s *server.MCPServer) {
func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateRepoFn") log.Debugf("Called CreateRepoFn")
name, ok := req.GetArguments()["name"].(string) args := req.GetArguments()
if !ok { name, err := params.GetString(args, "name")
return to.ErrorResult(errors.New("repository name is required")) if err != nil {
return to.ErrorResult(err)
} }
description, _ := req.GetArguments()["description"].(string) description, _ := args["description"].(string)
private, _ := req.GetArguments()["private"].(bool) private, _ := args["private"].(bool)
issueLabels, _ := req.GetArguments()["issue_labels"].(string) issueLabels, _ := args["issue_labels"].(string)
autoInit, _ := req.GetArguments()["auto_init"].(bool) autoInit, _ := args["auto_init"].(bool)
template, _ := req.GetArguments()["template"].(bool) template, _ := args["template"].(bool)
gitignores, _ := req.GetArguments()["gitignores"].(string) gitignores, _ := args["gitignores"].(string)
license, _ := req.GetArguments()["license"].(string) license, _ := args["license"].(string)
readme, _ := req.GetArguments()["readme"].(string) readme, _ := args["readme"].(string)
defaultBranch, _ := req.GetArguments()["default_branch"].(string) defaultBranch, _ := args["default_branch"].(string)
organization, _ := req.GetArguments()["organization"].(string) organization, _ := args["organization"].(string)
opt := gitea_sdk.CreateRepoOption{ opt := gitea_sdk.CreateRepoOption{
Name: name, Name: name,
@@ -152,25 +152,26 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
return to.ErrorResult(fmt.Errorf("create repository '%s' err: %v", name, err)) return to.ErrorResult(fmt.Errorf("create repository '%s' err: %v", name, err))
} }
} }
return to.TextResult(repo) return to.TextResult(slimRepo(repo))
} }
func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ForkRepoFn") log.Debugf("Called ForkRepoFn")
user, ok := req.GetArguments()["user"].(string) args := req.GetArguments()
if !ok { user, err := params.GetString(args, "user")
return to.ErrorResult(errors.New("user name is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repository name is required")) return to.ErrorResult(err)
} }
organization, ok := req.GetArguments()["organization"].(string) organization, ok := args["organization"].(string)
organizationPtr := new(organization) organizationPtr := new(organization)
if !ok || organization == "" { if !ok || organization == "" {
organizationPtr = nil organizationPtr = nil
} }
name, ok := req.GetArguments()["name"].(string) name, ok := args["name"].(string)
namePtr := new(name) namePtr := new(name)
if !ok || name == "" { if !ok || name == "" {
namePtr = nil namePtr = nil
@@ -192,12 +193,11 @@ func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListMyReposFn") log.Debugf("Called ListMyReposFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
opt := gitea_sdk.ListReposOptions{ opt := gitea_sdk.ListReposOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
@@ -209,5 +209,5 @@ func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
return to.ErrorResult(fmt.Errorf("list my repositories error: %v", err)) return to.ErrorResult(fmt.Errorf("list my repositories error: %v", err))
} }
return to.TextResult(repos) return to.TextResult(slimRepos(repos))
} }

201
operation/repo/slim.go Normal file
View File

@@ -0,0 +1,201 @@
package repo
import (
gitea_sdk "code.gitea.io/sdk/gitea"
)
func userLogin(u *gitea_sdk.User) string {
if u == nil {
return ""
}
return u.UserName
}
func slimRepo(r *gitea_sdk.Repository) map[string]any {
if r == nil {
return nil
}
m := map[string]any{
"id": r.ID,
"full_name": r.FullName,
"description": r.Description,
"html_url": r.HTMLURL,
"clone_url": r.CloneURL,
"ssh_url": r.SSHURL,
"default_branch": r.DefaultBranch,
"private": r.Private,
"fork": r.Fork,
"archived": r.Archived,
"language": r.Language,
"stars_count": r.Stars,
"forks_count": r.Forks,
"open_issues_count": r.OpenIssues,
"open_pr_counter": r.OpenPulls,
"created_at": r.Created,
"updated_at": r.Updated,
}
if r.Owner != nil {
m["owner"] = r.Owner.UserName
}
if len(r.Topics) > 0 {
m["topics"] = r.Topics
}
return m
}
func slimRepos(repos []*gitea_sdk.Repository) []map[string]any {
out := make([]map[string]any, 0, len(repos))
for _, r := range repos {
out = append(out, slimRepo(r))
}
return out
}
func slimBranch(b *gitea_sdk.Branch) map[string]any {
if b == nil {
return nil
}
m := map[string]any{
"name": b.Name,
"protected": b.Protected,
}
if b.Commit != nil {
m["commit_sha"] = b.Commit.ID
}
return m
}
func slimBranches(branches []*gitea_sdk.Branch) []map[string]any {
out := make([]map[string]any, 0, len(branches))
for _, b := range branches {
out = append(out, slimBranch(b))
}
return out
}
func slimCommit(c *gitea_sdk.Commit) map[string]any {
if c == nil {
return nil
}
m := map[string]any{
"sha": c.SHA,
"html_url": c.HTMLURL,
"created": c.Created,
}
if c.RepoCommit != nil {
m["message"] = c.RepoCommit.Message
if c.RepoCommit.Author != nil {
m["author"] = map[string]any{
"name": c.RepoCommit.Author.Name,
"email": c.RepoCommit.Author.Email,
"date": c.RepoCommit.Author.Date,
}
}
}
return m
}
func slimCommits(commits []*gitea_sdk.Commit) []map[string]any {
out := make([]map[string]any, 0, len(commits))
for _, c := range commits {
out = append(out, slimCommit(c))
}
return out
}
func slimTag(t *gitea_sdk.Tag) map[string]any {
if t == nil {
return nil
}
m := map[string]any{
"name": t.Name,
"message": t.Message,
}
if t.Commit != nil {
m["commit_sha"] = t.Commit.SHA
}
return m
}
func slimTags(tags []*gitea_sdk.Tag) []map[string]any {
out := make([]map[string]any, 0, len(tags))
for _, t := range tags {
m := map[string]any{
"name": t.Name,
}
if t.Commit != nil {
m["commit_sha"] = t.Commit.SHA
}
out = append(out, m)
}
return out
}
func slimRelease(r *gitea_sdk.Release) map[string]any {
if r == nil {
return nil
}
return map[string]any{
"id": r.ID,
"tag_name": r.TagName,
"target": r.Target,
"title": r.Title,
"body": r.Note,
"draft": r.IsDraft,
"prerelease": r.IsPrerelease,
"html_url": r.HTMLURL,
"author": userLogin(r.Publisher),
"created_at": r.CreatedAt,
"published_at": r.PublishedAt,
}
}
func slimReleases(releases []*gitea_sdk.Release) []map[string]any {
out := make([]map[string]any, 0, len(releases))
for _, r := range releases {
out = append(out, slimRelease(r))
}
return out
}
func slimContents(c *gitea_sdk.ContentsResponse) map[string]any {
if c == nil {
return nil
}
m := map[string]any{
"name": c.Name,
"path": c.Path,
"sha": c.SHA,
"type": c.Type,
"size": c.Size,
}
if c.Content != nil {
m["content"] = *c.Content
}
if c.Encoding != nil {
m["encoding"] = *c.Encoding
}
if c.HTMLURL != nil {
m["html_url"] = *c.HTMLURL
}
if c.DownloadURL != nil {
m["download_url"] = *c.DownloadURL
}
return m
}
func slimDirEntries(entries []*gitea_sdk.ContentsResponse) []map[string]any {
out := make([]map[string]any, 0, len(entries))
for _, c := range entries {
if c == nil {
continue
}
out = append(out, map[string]any{
"name": c.Name,
"path": c.Path,
"type": c.Type,
"size": c.Size,
})
}
return out
}

142
operation/repo/slim_test.go Normal file
View File

@@ -0,0 +1,142 @@
package repo
import (
"testing"
gitea_sdk "code.gitea.io/sdk/gitea"
)
func TestSlimRepo(t *testing.T) {
r := &gitea_sdk.Repository{
ID: 1,
FullName: "org/repo",
Description: "A test repo",
HTMLURL: "https://gitea.com/org/repo",
CloneURL: "https://gitea.com/org/repo.git",
SSHURL: "git@gitea.com:org/repo.git",
DefaultBranch: "main",
Private: false,
Fork: false,
Archived: false,
Language: "Go",
Stars: 10,
Forks: 2,
Owner: &gitea_sdk.User{UserName: "org"},
Topics: []string{"mcp", "gitea"},
}
m := slimRepo(r)
if m["full_name"] != "org/repo" {
t.Errorf("expected full_name org/repo, got %v", m["full_name"])
}
if m["owner"] != "org" {
t.Errorf("expected owner org, got %v", m["owner"])
}
topics := m["topics"].([]string)
if len(topics) != 2 {
t.Errorf("expected 2 topics, got %d", len(topics))
}
}
func TestSlimTag(t *testing.T) {
tag := &gitea_sdk.Tag{
Name: "v1.0.0",
Message: "Release v1.0.0",
Commit: &gitea_sdk.CommitMeta{SHA: "abc123"},
}
m := slimTag(tag)
if m["name"] != "v1.0.0" {
t.Errorf("expected name v1.0.0, got %v", m["name"])
}
if m["message"] != "Release v1.0.0" {
t.Errorf("expected message, got %v", m["message"])
}
// List variant omits message
list := slimTags([]*gitea_sdk.Tag{tag})
if _, ok := list[0]["message"]; ok {
t.Error("Tags list should omit message")
}
if list[0]["name"] != "v1.0.0" {
t.Errorf("expected name in list, got %v", list[0]["name"])
}
}
func TestSlimRelease(t *testing.T) {
r := &gitea_sdk.Release{
ID: 1,
TagName: "v1.0.0",
Title: "First Release",
Note: "Release notes",
IsDraft: false,
Publisher: &gitea_sdk.User{UserName: "alice"},
}
m := slimRelease(r)
if m["tag_name"] != "v1.0.0" {
t.Errorf("expected tag_name v1.0.0, got %v", m["tag_name"])
}
if m["body"] != "Release notes" {
t.Errorf("expected body from Note field, got %v", m["body"])
}
if m["author"] != "alice" {
t.Errorf("expected author alice, got %v", m["author"])
}
}
func TestSlimContents(t *testing.T) {
content := "package main"
encoding := "base64"
htmlURL := "https://gitea.com/org/repo/src/branch/main/main.go"
c := &gitea_sdk.ContentsResponse{
Name: "main.go",
Path: "main.go",
SHA: "abc123",
Type: "file",
Size: 12,
Content: &content,
Encoding: &encoding,
HTMLURL: &htmlURL,
}
m := slimContents(c)
if m["name"] != "main.go" {
t.Errorf("expected name main.go, got %v", m["name"])
}
if m["content"] != "package main" {
t.Errorf("expected content, got %v", m["content"])
}
}
func TestSlimDirEntries(t *testing.T) {
entries := []*gitea_sdk.ContentsResponse{
{Name: "src", Path: "src", Type: "dir", Size: 0},
{Name: "main.go", Path: "main.go", Type: "file", Size: 100},
}
result := slimDirEntries(entries)
if len(result) != 2 {
t.Fatalf("expected 2 entries, got %d", len(result))
}
if result[0]["name"] != "src" {
t.Errorf("expected first entry name src, got %v", result[0]["name"])
}
// Dir entries should not have content
if _, ok := result[0]["content"]; ok {
t.Error("dir entries should not have content field")
}
}
func TestSlimTags_Nil(t *testing.T) {
if r := slimTags(nil); len(r) != 0 {
t.Errorf("expected empty slice, got %v", r)
}
}
func TestSlimReleases_Nil(t *testing.T) {
if r := slimReleases(nil); len(r) != 0 {
t.Errorf("expected empty slice, got %v", r)
}
}

View File

@@ -2,7 +2,6 @@ package repo
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
@@ -78,31 +77,23 @@ func init() {
}) })
} }
// To avoid return too many tokens, we need to provide at least information as possible
// llm can call get tag to get more information
type ListTagResult struct {
ID string `json:"id"`
Name string `json:"name"`
Commit *gitea_sdk.CommitMeta `json:"commit"`
// message may be a long text, so we should not provide it here
}
func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateTagFn") log.Debugf("Called CreateTagFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return nil, errors.New("owner is required") if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return nil, errors.New("repo is required") return to.ErrorResult(err)
} }
tagName, ok := req.GetArguments()["tag_name"].(string) tagName, err := params.GetString(args, "tag_name")
if !ok { if err != nil {
return nil, errors.New("tag_name is required") return to.ErrorResult(err)
} }
target, _ := req.GetArguments()["target"].(string) target, _ := args["target"].(string)
message, _ := req.GetArguments()["message"].(string) message, _ := args["message"].(string)
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -122,17 +113,18 @@ func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteTagFn") log.Debugf("Called DeleteTagFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return nil, errors.New("owner is required") if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return nil, errors.New("repo is required") return to.ErrorResult(err)
} }
tagName, ok := req.GetArguments()["tag_name"].(string) tagName, err := params.GetString(args, "tag_name")
if !ok { if err != nil {
return nil, errors.New("tag_name is required") return to.ErrorResult(err)
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
@@ -149,17 +141,18 @@ func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetTagFn") log.Debugf("Called GetTagFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return nil, errors.New("owner is required") if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return nil, errors.New("repo is required") return to.ErrorResult(err)
} }
tagName, ok := req.GetArguments()["tag_name"].(string) tagName, err := params.GetString(args, "tag_name")
if !ok { if err != nil {
return nil, errors.New("tag_name is required") return to.ErrorResult(err)
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
@@ -171,21 +164,22 @@ func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult
return nil, fmt.Errorf("get tag error: %v", err) return nil, fmt.Errorf("get tag error: %v", err)
} }
return to.TextResult(tag) return to.TextResult(slimTag(tag))
} }
func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListTagsFn") log.Debugf("Called ListTagsFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return nil, errors.New("owner is required") if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return nil, errors.New("repo is required") return to.ErrorResult(err)
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page := params.GetOptionalInt(args, "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 20) pageSize := params.GetOptionalInt(args, "pageSize", 20)
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
@@ -201,13 +195,5 @@ func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
return nil, fmt.Errorf("list tags error: %v", err) return nil, fmt.Errorf("list tags error: %v", err)
} }
results := make([]ListTagResult, 0, len(tags)) return to.TextResult(slimTags(tags))
for _, tag := range tags {
results = append(results, ListTagResult{
ID: tag.ID,
Name: tag.Name,
Commit: tag.Commit,
})
}
return to.TextResult(results)
} }

View File

@@ -2,7 +2,6 @@ package search
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
@@ -30,7 +29,7 @@ var (
mcp.WithDescription("search users"), mcp.WithDescription("search users"),
mcp.WithString("keyword", mcp.Required(), mcp.Description("Keyword")), mcp.WithString("keyword", mcp.Required(), mcp.Description("Keyword")),
mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("PageSize"), mcp.DefaultNumber(100)), mcp.WithNumber("pageSize", mcp.Description("PageSize"), mcp.DefaultNumber(30)),
) )
SearOrgTeamsTool = mcp.NewTool( SearOrgTeamsTool = mcp.NewTool(
@@ -40,7 +39,7 @@ var (
mcp.WithString("query", mcp.Required(), mcp.Description("search organization teams")), mcp.WithString("query", mcp.Required(), mcp.Description("search organization teams")),
mcp.WithBoolean("includeDescription", mcp.Description("include description?")), mcp.WithBoolean("includeDescription", mcp.Description("include description?")),
mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("PageSize"), mcp.DefaultNumber(100)), mcp.WithNumber("pageSize", mcp.Description("PageSize"), mcp.DefaultNumber(30)),
) )
SearchReposTool = mcp.NewTool( SearchReposTool = mcp.NewTool(
@@ -55,7 +54,7 @@ var (
mcp.WithString("sort", mcp.Description("Sort")), mcp.WithString("sort", mcp.Description("Sort")),
mcp.WithString("order", mcp.Description("Order")), mcp.WithString("order", mcp.Description("Order")),
mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("PageSize"), mcp.DefaultNumber(100)), mcp.WithNumber("pageSize", mcp.Description("PageSize"), mcp.DefaultNumber(30)),
) )
) )
@@ -76,17 +75,16 @@ func init() {
func UsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func UsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UsersFn") log.Debugf("Called UsersFn")
keyword, ok := req.GetArguments()["keyword"].(string) keyword, err := params.GetString(req.GetArguments(), "keyword")
if !ok { if err != nil {
return to.ErrorResult(errors.New("keyword is required")) return to.ErrorResult(err)
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
opt := gitea_sdk.SearchUsersOption{ opt := gitea_sdk.SearchUsersOption{
KeyWord: keyword, KeyWord: keyword,
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
@@ -97,28 +95,27 @@ func UsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult,
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("search users err: %v", err)) return to.ErrorResult(fmt.Errorf("search users err: %v", err))
} }
return to.TextResult(users) return to.TextResult(slimUserDetails(users))
} }
func OrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func OrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called OrgTeamsFn") log.Debugf("Called OrgTeamsFn")
org, ok := req.GetArguments()["org"].(string) org, err := params.GetString(req.GetArguments(), "org")
if !ok { if err != nil {
return to.ErrorResult(errors.New("organization is required")) return to.ErrorResult(err)
} }
query, ok := req.GetArguments()["query"].(string) query, err := params.GetString(req.GetArguments(), "query")
if !ok { if err != nil {
return to.ErrorResult(errors.New("query is required")) return to.ErrorResult(err)
} }
includeDescription, _ := req.GetArguments()["includeDescription"].(bool) includeDescription, _ := req.GetArguments()["includeDescription"].(bool)
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
opt := gitea_sdk.SearchTeamsOptions{ opt := gitea_sdk.SearchTeamsOptions{
Query: query, Query: query,
IncludeDescription: includeDescription, IncludeDescription: includeDescription,
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
@@ -129,14 +126,14 @@ func OrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("search organization teams error: %v", err)) return to.ErrorResult(fmt.Errorf("search organization teams error: %v", err))
} }
return to.TextResult(teams) return to.TextResult(slimTeams(teams))
} }
func ReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ReposFn") log.Debugf("Called ReposFn")
keyword, ok := req.GetArguments()["keyword"].(string) keyword, err := params.GetString(req.GetArguments(), "keyword")
if !ok { if err != nil {
return to.ErrorResult(errors.New("keyword is required")) return to.ErrorResult(err)
} }
keywordIsTopic, _ := req.GetArguments()["keywordIsTopic"].(bool) keywordIsTopic, _ := req.GetArguments()["keywordIsTopic"].(bool)
keywordInDescription, _ := req.GetArguments()["keywordInDescription"].(bool) keywordInDescription, _ := req.GetArguments()["keywordInDescription"].(bool)
@@ -153,8 +150,7 @@ func ReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult,
} }
sort, _ := req.GetArguments()["sort"].(string) sort, _ := req.GetArguments()["sort"].(string)
order, _ := req.GetArguments()["order"].(string) order, _ := req.GetArguments()["order"].(string)
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
opt := gitea_sdk.SearchRepoOptions{ opt := gitea_sdk.SearchRepoOptions{
Keyword: keyword, Keyword: keyword,
KeywordIsTopic: keywordIsTopic, KeywordIsTopic: keywordIsTopic,
@@ -165,8 +161,8 @@ func ReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult,
Sort: sort, Sort: sort,
Order: order, Order: order,
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
@@ -177,5 +173,5 @@ func ReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult,
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("search repos error: %v", err)) return to.ErrorResult(fmt.Errorf("search repos error: %v", err))
} }
return to.TextResult(repos) return to.TextResult(slimRepos(repos))
} }

88
operation/search/slim.go Normal file
View File

@@ -0,0 +1,88 @@
package search
import (
gitea_sdk "code.gitea.io/sdk/gitea"
)
func slimUserDetail(u *gitea_sdk.User) map[string]any {
if u == nil {
return nil
}
return map[string]any{
"id": u.ID,
"login": u.UserName,
"full_name": u.FullName,
"email": u.Email,
"avatar_url": u.AvatarURL,
"html_url": u.HTMLURL,
"is_admin": u.IsAdmin,
}
}
func slimUserDetails(users []*gitea_sdk.User) []map[string]any {
out := make([]map[string]any, 0, len(users))
for _, u := range users {
out = append(out, slimUserDetail(u))
}
return out
}
func slimTeam(t *gitea_sdk.Team) map[string]any {
if t == nil {
return nil
}
return map[string]any{
"id": t.ID,
"name": t.Name,
"description": t.Description,
"permission": t.Permission,
}
}
func slimTeams(teams []*gitea_sdk.Team) []map[string]any {
out := make([]map[string]any, 0, len(teams))
for _, t := range teams {
out = append(out, slimTeam(t))
}
return out
}
func slimRepo(r *gitea_sdk.Repository) map[string]any {
if r == nil {
return nil
}
m := map[string]any{
"id": r.ID,
"full_name": r.FullName,
"description": r.Description,
"html_url": r.HTMLURL,
"clone_url": r.CloneURL,
"ssh_url": r.SSHURL,
"default_branch": r.DefaultBranch,
"private": r.Private,
"fork": r.Fork,
"archived": r.Archived,
"language": r.Language,
"stars_count": r.Stars,
"forks_count": r.Forks,
"open_issues_count": r.OpenIssues,
"open_pr_counter": r.OpenPulls,
"created_at": r.Created,
"updated_at": r.Updated,
}
if r.Owner != nil {
m["owner"] = r.Owner.UserName
}
if len(r.Topics) > 0 {
m["topics"] = r.Topics
}
return m
}
func slimRepos(repos []*gitea_sdk.Repository) []map[string]any {
out := make([]map[string]any, 0, len(repos))
for _, r := range repos {
out = append(out, slimRepo(r))
}
return out
}

View File

@@ -0,0 +1,47 @@
package timetracking
import (
gitea_sdk "code.gitea.io/sdk/gitea"
)
func slimStopWatch(s *gitea_sdk.StopWatch) map[string]any {
if s == nil {
return nil
}
return map[string]any{
"issue_index": s.IssueIndex,
"issue_title": s.IssueTitle,
"repo_name": s.RepoName,
"repo_owner": s.RepoOwnerName,
"created": s.Created,
"seconds": s.Seconds,
}
}
func slimStopWatches(watches []*gitea_sdk.StopWatch) []map[string]any {
out := make([]map[string]any, 0, len(watches))
for _, s := range watches {
out = append(out, slimStopWatch(s))
}
return out
}
func slimTrackedTime(t *gitea_sdk.TrackedTime) map[string]any {
if t == nil {
return nil
}
return map[string]any{
"id": t.ID,
"time": t.Time,
"user_name": t.UserName,
"created": t.Created,
}
}
func slimTrackedTimes(times []*gitea_sdk.TrackedTime) []map[string]any {
out := make([]map[string]any, 0, len(times))
for _, t := range times {
out = append(out, slimTrackedTime(t))
}
return out
}

View File

@@ -3,7 +3,6 @@ package timetracking
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
@@ -73,7 +72,7 @@ var (
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")), mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30)),
) )
AddTrackedTimeTool = mcp.NewTool( AddTrackedTimeTool = mcp.NewTool(
@@ -100,7 +99,7 @@ var (
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(30)),
) )
GetMyTimesTool = mcp.NewTool( GetMyTimesTool = mcp.NewTool(
@@ -128,13 +127,13 @@ func init() {
func StartStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func StartStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called StartStopwatchFn") log.Debugf("Called StartStopwatchFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
@@ -153,13 +152,13 @@ func StartStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
func StopStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func StopStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called StopStopwatchFn") log.Debugf("Called StopStopwatchFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
@@ -178,13 +177,13 @@ func StopStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
func DeleteStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteStopwatchFn") log.Debugf("Called DeleteStopwatchFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
@@ -214,27 +213,26 @@ func GetMyStopwatchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
if len(stopwatches) == 0 { if len(stopwatches) == 0 {
return to.TextResult("No active stopwatches") return to.TextResult("No active stopwatches")
} }
return to.TextResult(stopwatches) return to.TextResult(slimStopWatches(stopwatches))
} }
// Tracked time handler functions // Tracked time handler functions
func ListTrackedTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListTrackedTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListTrackedTimesFn") log.Debugf("Called ListTrackedTimesFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
@@ -242,8 +240,8 @@ func ListTrackedTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
times, _, err := client.ListIssueTrackedTimes(owner, repo, index, gitea_sdk.ListTrackedTimesOptions{ times, _, err := client.ListIssueTrackedTimes(owner, repo, index, gitea_sdk.ListTrackedTimesOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
}) })
if err != nil { if err != nil {
@@ -252,18 +250,18 @@ func ListTrackedTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
if len(times) == 0 { if len(times) == 0 {
return to.TextResult(fmt.Sprintf("No tracked times for issue %s/%s#%d", owner, repo, index)) return to.TextResult(fmt.Sprintf("No tracked times for issue %s/%s#%d", owner, repo, index))
} }
return to.TextResult(times) return to.TextResult(slimTrackedTimes(times))
} }
func AddTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func AddTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AddTrackedTimeFn") log.Debugf("Called AddTrackedTimeFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil { if err != nil {
@@ -284,18 +282,18 @@ func AddTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("add tracked time to %s/%s#%d err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("add tracked time to %s/%s#%d err: %v", owner, repo, index, err))
} }
return to.TextResult(trackedTime) return to.TextResult(slimTrackedTime(trackedTime))
} }
func DeleteTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteTrackedTimeFn") log.Debugf("Called DeleteTrackedTimeFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "index")
@@ -319,25 +317,24 @@ func DeleteTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
func ListRepoTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoTimesFn") log.Debugf("Called ListRepoTimesFn")
owner, ok := req.GetArguments()["owner"].(string) owner, err := params.GetString(req.GetArguments(), "owner")
if !ok { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(req.GetArguments(), "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
page := params.GetOptionalInt(req.GetArguments(), "page", 1) page, pageSize := params.GetPagination(req.GetArguments(), 30)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
} }
times, _, err := client.ListRepoTrackedTimes(owner, repo, gitea_sdk.ListTrackedTimesOptions{ times, _, err := client.ListRepoTrackedTimes(owner, repo, gitea_sdk.ListTrackedTimesOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
}) })
if err != nil { if err != nil {
@@ -346,7 +343,7 @@ func ListRepoTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
if len(times) == 0 { if len(times) == 0 {
return to.TextResult(fmt.Sprintf("No tracked times for repository %s/%s", owner, repo)) return to.TextResult(fmt.Sprintf("No tracked times for repository %s/%s", owner, repo))
} }
return to.TextResult(times) return to.TextResult(slimTrackedTimes(times))
} }
func GetMyTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetMyTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -362,5 +359,5 @@ func GetMyTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
if len(times) == 0 { if len(times) == 0 {
return to.TextResult("No tracked times found") return to.TextResult("No tracked times found")
} }
return to.TextResult(times) return to.TextResult(slimTrackedTimes(times))
} }

42
operation/user/slim.go Normal file
View File

@@ -0,0 +1,42 @@
package user
import (
gitea_sdk "code.gitea.io/sdk/gitea"
)
func slimUserDetail(u *gitea_sdk.User) map[string]any {
if u == nil {
return nil
}
return map[string]any{
"id": u.ID,
"login": u.UserName,
"full_name": u.FullName,
"email": u.Email,
"avatar_url": u.AvatarURL,
"html_url": u.HTMLURL,
"is_admin": u.IsAdmin,
}
}
func slimOrg(o *gitea_sdk.Organization) map[string]any {
if o == nil {
return nil
}
return map[string]any{
"id": o.ID,
"name": o.Name,
"full_name": o.FullName,
"description": o.Description,
"avatar_url": o.AvatarURL,
"website": o.Website,
}
}
func slimOrgs(orgs []*gitea_sdk.Organization) []map[string]any {
out := make([]map[string]any, 0, len(orgs))
for _, o := range orgs {
out = append(out, slimOrg(o))
}
return out
}

View File

@@ -0,0 +1,39 @@
package user
import (
"testing"
gitea_sdk "code.gitea.io/sdk/gitea"
)
func TestSlimUserDetail(t *testing.T) {
u := &gitea_sdk.User{
ID: 42,
UserName: "alice",
FullName: "Alice Smith",
Email: "alice@example.com",
AvatarURL: "https://gitea.com/avatars/42",
HTMLURL: "https://gitea.com/alice",
IsAdmin: true,
}
m := slimUserDetail(u)
if m["id"] != int64(42) {
t.Errorf("expected id 42, got %v", m["id"])
}
if m["login"] != "alice" {
t.Errorf("expected login alice, got %v", m["login"])
}
if m["full_name"] != "Alice Smith" {
t.Errorf("expected full_name Alice Smith, got %v", m["full_name"])
}
if m["is_admin"] != true {
t.Errorf("expected is_admin true, got %v", m["is_admin"])
}
}
func TestSlimUserDetail_Nil(t *testing.T) {
if m := slimUserDetail(nil); m != nil {
t.Errorf("expected nil for nil user, got %v", m)
}
}

View File

@@ -24,7 +24,7 @@ const (
// defaultPage is the default starting page number used for paginated organization listings. // defaultPage is the default starting page number used for paginated organization listings.
defaultPage = 1 defaultPage = 1
// defaultPageSize is the default number of organizations per page for paginated queries. // defaultPageSize is the default number of organizations per page for paginated queries.
defaultPageSize = 100 defaultPageSize = 30
) )
// Tool is the MCP tool manager instance for registering all MCP tools in this package. // Tool is the MCP tool manager instance for registering all MCP tools in this package.
@@ -66,16 +66,6 @@ func registerTools() {
} }
} }
// 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 {
v := params.GetOptionalInt(req.GetArguments(), name, int64(def))
if v < 1 {
return def
}
return int(v)
}
// GetUserInfoFn is the handler for "get_my_user_info" MCP tool requests. // GetUserInfoFn is the handler for "get_my_user_info" MCP tool requests.
// Logs invocation, fetches current user info from gitea, wraps result for MCP. // Logs invocation, fetches current user info from gitea, wraps result for MCP.
func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -88,7 +78,7 @@ func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get user info err: %v", err)) return to.ErrorResult(fmt.Errorf("get user info err: %v", err))
} }
return to.TextResult(user) return to.TextResult(slimUserDetail(user))
} }
// GetUserOrgsFn is the handler for "get_user_orgs" MCP tool requests. // GetUserOrgsFn is the handler for "get_user_orgs" MCP tool requests.
@@ -96,8 +86,7 @@ func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
// performs Gitea organization listing, and wraps the result for MCP. // performs Gitea organization listing, and wraps the result for MCP.
func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[User] Called GetUserOrgsFn") log.Debugf("[User] Called GetUserOrgsFn")
page := getIntArg(req, "page", defaultPage) page, pageSize := params.GetPagination(req.GetArguments(), defaultPageSize)
pageSize := getIntArg(req, "pageSize", defaultPageSize)
opt := gitea_sdk.ListOrgsOptions{ opt := gitea_sdk.ListOrgsOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
@@ -113,5 +102,5 @@ func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get user orgs err: %v", err)) return to.ErrorResult(fmt.Errorf("get user orgs err: %v", err))
} }
return to.TextResult(orgs) return to.TextResult(slimOrgs(orgs))
} }

View File

@@ -2,12 +2,12 @@ package wiki
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/url" "net/url"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "gitea.com/gitea/gitea-mcp/pkg/tool"
@@ -109,18 +109,19 @@ func init() {
func ListWikiPagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListWikiPagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListWikiPagesFn") log.Debugf("Called ListWikiPagesFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
// Use direct HTTP request because SDK does not support yet wikis // Use direct HTTP request because SDK does not support yet wikis
var result any var result any
_, err := gitea.DoJSON(ctx, "GET", fmt.Sprintf("repos/%s/%s/wiki/pages", url.QueryEscape(owner), url.QueryEscape(repo)), nil, nil, &result) _, err = gitea.DoJSON(ctx, "GET", fmt.Sprintf("repos/%s/%s/wiki/pages", url.QueryEscape(owner), url.QueryEscape(repo)), nil, nil, &result)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list wiki pages err: %v", err)) return to.ErrorResult(fmt.Errorf("list wiki pages err: %v", err))
} }
@@ -130,21 +131,22 @@ func ListWikiPagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
func GetWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetWikiPageFn") log.Debugf("Called GetWikiPageFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
pageName, ok := req.GetArguments()["pageName"].(string) pageName, err := params.GetString(args, "pageName")
if !ok { if err != nil {
return to.ErrorResult(errors.New("pageName is required")) return to.ErrorResult(err)
} }
var result any var result any
_, err := gitea.DoJSON(ctx, "GET", fmt.Sprintf("repos/%s/%s/wiki/page/%s", url.QueryEscape(owner), url.QueryEscape(repo), url.QueryEscape(pageName)), nil, nil, &result) _, err = gitea.DoJSON(ctx, "GET", fmt.Sprintf("repos/%s/%s/wiki/page/%s", url.QueryEscape(owner), url.QueryEscape(repo), url.QueryEscape(pageName)), nil, nil, &result)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get wiki page err: %v", err)) return to.ErrorResult(fmt.Errorf("get wiki page err: %v", err))
} }
@@ -154,21 +156,22 @@ func GetWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
func GetWikiRevisionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetWikiRevisionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetWikiRevisionsFn") log.Debugf("Called GetWikiRevisionsFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
pageName, ok := req.GetArguments()["pageName"].(string) pageName, err := params.GetString(args, "pageName")
if !ok { if err != nil {
return to.ErrorResult(errors.New("pageName is required")) return to.ErrorResult(err)
} }
var result any var result any
_, err := gitea.DoJSON(ctx, "GET", fmt.Sprintf("repos/%s/%s/wiki/revisions/%s", url.QueryEscape(owner), url.QueryEscape(repo), url.QueryEscape(pageName)), nil, nil, &result) _, err = gitea.DoJSON(ctx, "GET", fmt.Sprintf("repos/%s/%s/wiki/revisions/%s", url.QueryEscape(owner), url.QueryEscape(repo), url.QueryEscape(pageName)), nil, nil, &result)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get wiki revisions err: %v", err)) return to.ErrorResult(fmt.Errorf("get wiki revisions err: %v", err))
} }
@@ -178,24 +181,25 @@ func GetWikiRevisionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
func CreateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateWikiPageFn") log.Debugf("Called CreateWikiPageFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
title, ok := req.GetArguments()["title"].(string) title, err := params.GetString(args, "title")
if !ok { if err != nil {
return to.ErrorResult(errors.New("title is required")) return to.ErrorResult(err)
} }
contentBase64, ok := req.GetArguments()["content_base64"].(string) contentBase64, err := params.GetString(args, "content_base64")
if !ok { if err != nil {
return to.ErrorResult(errors.New("content_base64 is required")) return to.ErrorResult(err)
} }
message, _ := req.GetArguments()["message"].(string) message, _ := args["message"].(string)
if message == "" { if message == "" {
message = fmt.Sprintf("Create wiki page '%s'", title) message = fmt.Sprintf("Create wiki page '%s'", title)
} }
@@ -207,7 +211,7 @@ func CreateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
} }
var result any var result any
_, err := gitea.DoJSON(ctx, "POST", fmt.Sprintf("repos/%s/%s/wiki/new", url.QueryEscape(owner), url.QueryEscape(repo)), nil, requestBody, &result) _, err = gitea.DoJSON(ctx, "POST", fmt.Sprintf("repos/%s/%s/wiki/new", url.QueryEscape(owner), url.QueryEscape(repo)), nil, requestBody, &result)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create wiki page err: %v", err)) return to.ErrorResult(fmt.Errorf("create wiki page err: %v", err))
} }
@@ -217,21 +221,22 @@ func CreateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
func UpdateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func UpdateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UpdateWikiPageFn") log.Debugf("Called UpdateWikiPageFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
pageName, ok := req.GetArguments()["pageName"].(string) pageName, err := params.GetString(args, "pageName")
if !ok { if err != nil {
return to.ErrorResult(errors.New("pageName is required")) return to.ErrorResult(err)
} }
contentBase64, ok := req.GetArguments()["content_base64"].(string) contentBase64, err := params.GetString(args, "content_base64")
if !ok { if err != nil {
return to.ErrorResult(errors.New("content_base64 is required")) return to.ErrorResult(err)
} }
requestBody := map[string]string{ requestBody := map[string]string{
@@ -239,21 +244,21 @@ func UpdateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
} }
// If title is given, use it. Otherwise, keep current page name // If title is given, use it. Otherwise, keep current page name
if title, ok := req.GetArguments()["title"].(string); ok && title != "" { if title, ok := args["title"].(string); ok && title != "" {
requestBody["title"] = title requestBody["title"] = title
} else { } else {
// Utiliser pageName comme fallback pour éviter "unnamed" // Utiliser pageName comme fallback pour éviter "unnamed"
requestBody["title"] = pageName requestBody["title"] = pageName
} }
if message, ok := req.GetArguments()["message"].(string); ok && message != "" { if message, ok := args["message"].(string); ok && message != "" {
requestBody["message"] = message requestBody["message"] = message
} else { } else {
requestBody["message"] = fmt.Sprintf("Update wiki page '%s'", pageName) requestBody["message"] = fmt.Sprintf("Update wiki page '%s'", pageName)
} }
var result any var result any
_, err := gitea.DoJSON(ctx, "PATCH", fmt.Sprintf("repos/%s/%s/wiki/page/%s", url.QueryEscape(owner), url.QueryEscape(repo), url.QueryEscape(pageName)), nil, requestBody, &result) _, err = gitea.DoJSON(ctx, "PATCH", fmt.Sprintf("repos/%s/%s/wiki/page/%s", url.QueryEscape(owner), url.QueryEscape(repo), url.QueryEscape(pageName)), nil, requestBody, &result)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("update wiki page err: %v", err)) return to.ErrorResult(fmt.Errorf("update wiki page err: %v", err))
} }
@@ -263,20 +268,21 @@ func UpdateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
func DeleteWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteWikiPageFn") log.Debugf("Called DeleteWikiPageFn")
owner, ok := req.GetArguments()["owner"].(string) args := req.GetArguments()
if !ok { owner, err := params.GetString(args, "owner")
return to.ErrorResult(errors.New("owner is required")) if err != nil {
return to.ErrorResult(err)
} }
repo, ok := req.GetArguments()["repo"].(string) repo, err := params.GetString(args, "repo")
if !ok { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
pageName, ok := req.GetArguments()["pageName"].(string) pageName, err := params.GetString(args, "pageName")
if !ok { if err != nil {
return to.ErrorResult(errors.New("pageName is required")) return to.ErrorResult(err)
} }
_, err := gitea.DoJSON(ctx, "DELETE", fmt.Sprintf("repos/%s/%s/wiki/page/%s", url.QueryEscape(owner), url.QueryEscape(repo), url.QueryEscape(pageName)), nil, nil, nil) _, err = gitea.DoJSON(ctx, "DELETE", fmt.Sprintf("repos/%s/%s/wiki/page/%s", url.QueryEscape(owner), url.QueryEscape(repo), url.QueryEscape(pageName)), nil, nil, nil)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("delete wiki page err: %v", err)) return to.ErrorResult(fmt.Errorf("delete wiki page err: %v", err))
} }

View File

@@ -5,6 +5,47 @@ import (
"strconv" "strconv"
) )
// GetString extracts a required string parameter from MCP tool arguments.
func GetString(args map[string]any, key string) (string, error) {
val, ok := args[key].(string)
if !ok {
return "", fmt.Errorf("%s is required", key)
}
return val, nil
}
// GetOptionalString extracts an optional string parameter with a default value.
func GetOptionalString(args map[string]any, key, defaultVal string) string {
if val, ok := args[key].(string); ok {
return val
}
return defaultVal
}
// GetStringSlice extracts an optional string slice parameter from MCP tool arguments.
func GetStringSlice(args map[string]any, key string) []string {
val, ok := args[key]
if !ok {
return nil
}
sliceVal, ok := val.([]any)
if !ok {
return nil
}
out := make([]string, 0, len(sliceVal))
for _, item := range sliceVal {
if s, ok := item.(string); ok {
out = append(out, s)
}
}
return out
}
// GetPagination extracts page and pageSize parameters, returning them as ints.
func GetPagination(args map[string]any, defaultPageSize int64) (page, pageSize int) {
return int(GetOptionalInt(args, "page", 1)), int(GetOptionalInt(args, "pageSize", defaultPageSize))
}
// ToInt64 converts a value to int64, accepting both float64 (JSON number) and // ToInt64 converts a value to int64, accepting both float64 (JSON number) and
// string representations. Returns false if the value cannot be converted. // string representations. Returns false if the value cannot be converted.
func ToInt64(val any) (int64, bool) { func ToInt64(val any) (int64, bool) {

View File

@@ -8,13 +8,8 @@ import (
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
) )
type textResult struct {
Result any
}
func TextResult(v any) (*mcp.CallToolResult, error) { func TextResult(v any) (*mcp.CallToolResult, error) {
result := textResult{v} resultBytes, err := json.Marshal(v)
resultBytes, err := json.Marshal(result)
if err != nil { if err != nil {
return nil, fmt.Errorf("marshal result err: %v", err) return nil, fmt.Errorf("marshal result err: %v", err)
} }