diff --git a/operation/actions/config.go b/operation/actions/config.go index 5b925b6..cd0a858 100644 --- a/operation/actions/config.go +++ b/operation/actions/config.go @@ -2,7 +2,6 @@ package actions import ( "context" - "errors" "fmt" "net/url" "strconv" @@ -10,7 +9,6 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/to" @@ -133,17 +131,14 @@ func configWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR } } -// Secret functions - func listRepoActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listRepoActionSecretsFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } page, pageSize := params.GetPagination(req.GetArguments(), 30) @@ -163,22 +158,21 @@ func listRepoActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp } func upsertRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called upsertRepoActionSecretFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } name, err := params.GetString(req.GetArguments(), "name") - if err != nil || name == "" { - return to.ErrorResult(errors.New("name is required")) + if err != nil { + return to.ErrorResult(err) } data, err := params.GetString(req.GetArguments(), "data") - if err != nil || data == "" { - return to.ErrorResult(errors.New("data is required")) + if err != nil { + return to.ErrorResult(err) } description, _ := req.GetArguments()["description"].(string) @@ -198,18 +192,17 @@ func upsertRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mc } func deleteRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called deleteRepoActionSecretFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } name, err := params.GetString(req.GetArguments(), "name") - if err != nil || name == "" { - return to.ErrorResult(errors.New("name is required")) + if err != nil { + return to.ErrorResult(err) } client, err := gitea.ClientFromContext(ctx) @@ -224,10 +217,9 @@ func deleteRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mc } func listOrgActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listOrgActionSecretsFn") org, err := params.GetString(req.GetArguments(), "org") - if err != nil || org == "" { - return to.ErrorResult(errors.New("org is required")) + if err != nil { + return to.ErrorResult(err) } page, pageSize := params.GetPagination(req.GetArguments(), 30) @@ -247,18 +239,17 @@ func listOrgActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp. } func upsertOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called upsertOrgActionSecretFn") org, err := params.GetString(req.GetArguments(), "org") - if err != nil || org == "" { - return to.ErrorResult(errors.New("org is required")) + if err != nil { + return to.ErrorResult(err) } name, err := params.GetString(req.GetArguments(), "name") - if err != nil || name == "" { - return to.ErrorResult(errors.New("name is required")) + if err != nil { + return to.ErrorResult(err) } data, err := params.GetString(req.GetArguments(), "data") - if err != nil || data == "" { - return to.ErrorResult(errors.New("data is required")) + if err != nil { + return to.ErrorResult(err) } description, _ := req.GetArguments()["description"].(string) @@ -278,14 +269,13 @@ func upsertOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp } func deleteOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called deleteOrgActionSecretFn") org, err := params.GetString(req.GetArguments(), "org") - if err != nil || org == "" { - return to.ErrorResult(errors.New("org is required")) + if err != nil { + return to.ErrorResult(err) } name, err := params.GetString(req.GetArguments(), "name") - if err != nil || name == "" { - return to.ErrorResult(errors.New("name is required")) + if err != nil { + return to.ErrorResult(err) } escapedOrg := url.PathEscape(org) @@ -297,17 +287,14 @@ func deleteOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp return to.TextResult(map[string]any{"message": "secret deleted"}) } -// Variable functions - func listRepoActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listRepoActionVariablesFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } page, pageSize := params.GetPagination(req.GetArguments(), 30) @@ -324,18 +311,17 @@ func listRepoActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*m } func getRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getRepoActionVariableFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } name, err := params.GetString(req.GetArguments(), "name") - if err != nil || name == "" { - return to.ErrorResult(errors.New("name is required")) + if err != nil { + return to.ErrorResult(err) } client, err := gitea.ClientFromContext(ctx) @@ -350,22 +336,21 @@ func getRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp } func createRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called createRepoActionVariableFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } name, err := params.GetString(req.GetArguments(), "name") - if err != nil || name == "" { - return to.ErrorResult(errors.New("name is required")) + if err != nil { + return to.ErrorResult(err) } value, err := params.GetString(req.GetArguments(), "value") - if err != nil || value == "" { - return to.ErrorResult(errors.New("value is required")) + if err != nil { + return to.ErrorResult(err) } client, err := gitea.ClientFromContext(ctx) @@ -380,22 +365,21 @@ func createRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (* } func updateRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called updateRepoActionVariableFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } name, err := params.GetString(req.GetArguments(), "name") - if err != nil || name == "" { - return to.ErrorResult(errors.New("name is required")) + if err != nil { + return to.ErrorResult(err) } value, err := params.GetString(req.GetArguments(), "value") - if err != nil || value == "" { - return to.ErrorResult(errors.New("value is required")) + if err != nil { + return to.ErrorResult(err) } client, err := gitea.ClientFromContext(ctx) @@ -410,18 +394,17 @@ func updateRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (* } func deleteRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called deleteRepoActionVariableFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } name, err := params.GetString(req.GetArguments(), "name") - if err != nil || name == "" { - return to.ErrorResult(errors.New("name is required")) + if err != nil { + return to.ErrorResult(err) } client, err := gitea.ClientFromContext(ctx) @@ -436,10 +419,9 @@ func deleteRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (* } func listOrgActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listOrgActionVariablesFn") org, err := params.GetString(req.GetArguments(), "org") - if err != nil || org == "" { - return to.ErrorResult(errors.New("org is required")) + if err != nil { + return to.ErrorResult(err) } page, pageSize := params.GetPagination(req.GetArguments(), 30) @@ -457,14 +439,13 @@ func listOrgActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*mc } func getOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getOrgActionVariableFn") org, err := params.GetString(req.GetArguments(), "org") - if err != nil || org == "" { - return to.ErrorResult(errors.New("org is required")) + if err != nil { + return to.ErrorResult(err) } name, err := params.GetString(req.GetArguments(), "name") - if err != nil || name == "" { - return to.ErrorResult(errors.New("name is required")) + if err != nil { + return to.ErrorResult(err) } client, err := gitea.ClientFromContext(ctx) @@ -479,18 +460,17 @@ func getOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp. } func createOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called createOrgActionVariableFn") org, err := params.GetString(req.GetArguments(), "org") - if err != nil || org == "" { - return to.ErrorResult(errors.New("org is required")) + if err != nil { + return to.ErrorResult(err) } name, err := params.GetString(req.GetArguments(), "name") - if err != nil || name == "" { - return to.ErrorResult(errors.New("name is required")) + if err != nil { + return to.ErrorResult(err) } value, err := params.GetString(req.GetArguments(), "value") - if err != nil || value == "" { - return to.ErrorResult(errors.New("value is required")) + if err != nil { + return to.ErrorResult(err) } description, _ := req.GetArguments()["description"].(string) @@ -510,18 +490,17 @@ func createOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*m } func updateOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called updateOrgActionVariableFn") org, err := params.GetString(req.GetArguments(), "org") - if err != nil || org == "" { - return to.ErrorResult(errors.New("org is required")) + if err != nil { + return to.ErrorResult(err) } name, err := params.GetString(req.GetArguments(), "name") - if err != nil || name == "" { - return to.ErrorResult(errors.New("name is required")) + if err != nil { + return to.ErrorResult(err) } value, err := params.GetString(req.GetArguments(), "value") - if err != nil || value == "" { - return to.ErrorResult(errors.New("value is required")) + if err != nil { + return to.ErrorResult(err) } description, _ := req.GetArguments()["description"].(string) @@ -540,14 +519,13 @@ func updateOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*m } func deleteOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called deleteOrgActionVariableFn") org, err := params.GetString(req.GetArguments(), "org") - if err != nil || org == "" { - return to.ErrorResult(errors.New("org is required")) + if err != nil { + return to.ErrorResult(err) } name, err := params.GetString(req.GetArguments(), "name") - if err != nil || name == "" { - return to.ErrorResult(errors.New("name is required")) + if err != nil { + return to.ErrorResult(err) } _, err = gitea.DoJSON(ctx, "DELETE", fmt.Sprintf("orgs/%s/actions/variables/%s", url.PathEscape(org), url.PathEscape(name)), nil, nil, nil) diff --git a/operation/actions/runs.go b/operation/actions/runs.go index 6c4f800..781dca2 100644 --- a/operation/actions/runs.go +++ b/operation/actions/runs.go @@ -12,7 +12,6 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/to" @@ -125,14 +124,13 @@ func doJSONWithFallback(ctx context.Context, method string, paths []string, quer } func listRepoActionWorkflowsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listRepoActionWorkflowsFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } page, pageSize := params.GetPagination(req.GetArguments(), 30) query := url.Values{} @@ -153,18 +151,17 @@ func listRepoActionWorkflowsFn(ctx context.Context, req mcp.CallToolRequest) (*m } func getRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getRepoActionWorkflowFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } workflowID, err := params.GetString(req.GetArguments(), "workflow_id") - if err != nil || workflowID == "" { - return to.ErrorResult(errors.New("workflow_id is required")) + if err != nil { + return to.ErrorResult(err) } var result any @@ -181,22 +178,21 @@ func getRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp } func dispatchRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called dispatchRepoActionWorkflowFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } workflowID, err := params.GetString(req.GetArguments(), "workflow_id") - if err != nil || workflowID == "" { - return to.ErrorResult(errors.New("workflow_id is required")) + if err != nil { + return to.ErrorResult(err) } ref, err := params.GetString(req.GetArguments(), "ref") - if err != nil || ref == "" { - return to.ErrorResult(errors.New("ref is required")) + if err != nil { + return to.ErrorResult(err) } var inputs map[string]any @@ -231,14 +227,13 @@ func dispatchRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) } func listRepoActionRunsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listRepoActionRunsFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } page, pageSize := params.GetPagination(req.GetArguments(), 30) statusFilter, _ := req.GetArguments()["status"].(string) @@ -264,14 +259,13 @@ func listRepoActionRunsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca } func getRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getRepoActionRunFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } runID, err := params.GetIndex(req.GetArguments(), "run_id") if err != nil || runID <= 0 { @@ -292,14 +286,13 @@ func getRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call } func cancelRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called cancelRepoActionRunFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } runID, err := params.GetIndex(req.GetArguments(), "run_id") if err != nil || runID <= 0 { @@ -319,14 +312,13 @@ func cancelRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.C } func rerunRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called rerunRepoActionRunFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } runID, err := params.GetIndex(req.GetArguments(), "run_id") if err != nil || runID <= 0 { @@ -351,14 +343,13 @@ func rerunRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca } func listRepoActionJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listRepoActionJobsFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } page, pageSize := params.GetPagination(req.GetArguments(), 30) statusFilter, _ := req.GetArguments()["status"].(string) @@ -384,14 +375,13 @@ func listRepoActionJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca } func listRepoActionRunJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listRepoActionRunJobsFn") owner, err := params.GetString(req.GetArguments(), "owner") - if err != nil || owner == "" { - return to.ErrorResult(errors.New("owner is required")) + if err != nil { + return to.ErrorResult(err) } repo, err := params.GetString(req.GetArguments(), "repo") - if err != nil || repo == "" { - return to.ErrorResult(errors.New("repo is required")) + if err != nil { + return to.ErrorResult(err) } runID, err := params.GetIndex(req.GetArguments(), "run_id") if err != nil || runID <= 0 { @@ -416,8 +406,6 @@ func listRepoActionRunJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp return to.TextResult(slimActionJobs(result)) } -// Log functions (merged from logs.go) - func logPaths(owner, repo string, jobID int64) []string { return []string{ fmt.Sprintf("repos/%s/%s/actions/jobs/%d/logs", url.PathEscape(owner), url.PathEscape(repo), jobID), @@ -473,7 +461,6 @@ func limitBytes(data []byte, maxBytes int) ([]byte, bool) { } func getRepoActionJobLogPreviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getRepoActionJobLogPreviewFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -508,7 +495,6 @@ func getRepoActionJobLogPreviewFn(ctx context.Context, req mcp.CallToolRequest) } func downloadRepoActionJobLogFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called downloadRepoActionJobLogFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) diff --git a/operation/issue/issue.go b/operation/issue/issue.go index afef03b..2872a5c 100644 --- a/operation/issue/issue.go +++ b/operation/issue/issue.go @@ -7,8 +7,8 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" + "gitea.com/gitea/gitea-mcp/pkg/slim" "gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/tool" @@ -145,7 +145,6 @@ func issueWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe } func getIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getIssueByIndexFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -164,12 +163,11 @@ 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)) } m := slimIssue(&issue.Issue) - m["body"] = bodyWithAttachments(issue.Body, issue.Assets) + m["body"] = slim.BodyWithAttachments(issue.Body, issue.Assets) return to.TextResult(m) } func listRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called ListIssuesFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -210,7 +208,6 @@ func listRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo } func createIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called createIssueFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -257,7 +254,6 @@ func createIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR } func createIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called createIssueCommentFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -290,7 +286,6 @@ func createIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca } func editIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called editIssueFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -304,32 +299,25 @@ func editIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes return to.ErrorResult(err) } - opt := gitea_sdk.EditIssueOption{} - - title, ok := req.GetArguments()["title"].(string) - if ok { + args := req.GetArguments() + opt := gitea_sdk.EditIssueOption{ + Body: params.GetPresentStringPtr(args, "body"), + Ref: params.GetPresentStringPtr(args, "ref"), + Assignees: params.GetStringSlice(args, "assignees"), + Deadline: params.GetOptionalTime(args, "deadline"), + RemoveDeadline: params.GetOptionalBoolPtr(args, "remove_deadline"), + } + if title, ok := args["title"].(string); ok { opt.Title = title } - body, ok := req.GetArguments()["body"].(string) - if ok { - opt.Body = new(body) - } - opt.Assignees = params.GetStringSlice(req.GetArguments(), "assignees") - if val, exists := req.GetArguments()["milestone"]; exists { + if val, exists := args["milestone"]; exists { if milestone, ok := params.ToInt64(val); ok { - opt.Milestone = new(milestone) + opt.Milestone = &milestone } } - state, ok := req.GetArguments()["state"].(string) - if ok { - opt.State = new(gitea_sdk.StateType(state)) - } - if ref, ok := req.GetArguments()["ref"].(string); ok { - opt.Ref = &ref - } - opt.Deadline = params.GetOptionalTime(req.GetArguments(), "deadline") - if removeDeadline, ok := req.GetArguments()["remove_deadline"].(bool); ok { - opt.RemoveDeadline = &removeDeadline + if state, ok := args["state"].(string); ok { + s := gitea_sdk.StateType(state) + opt.State = &s } client, err := gitea.ClientFromContext(ctx) @@ -345,7 +333,6 @@ func editIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes } func editIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called editIssueCommentFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -378,7 +365,6 @@ func editIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call } func getIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getIssueCommentsByIndexFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -399,14 +385,13 @@ func getIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*m out := make([]map[string]any, 0, len(comments)) for i := range comments { m := slimComment(&comments[i].Comment) - m["body"] = bodyWithAttachments(comments[i].Body, comments[i].Assets) + m["body"] = slim.BodyWithAttachments(comments[i].Body, comments[i].Assets) out = append(out, m) } return to.TextResult(out) } func getIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getIssueLabelsFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -428,13 +413,10 @@ func getIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo if err != nil { return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/labels err: %v", owner, repo, index, err)) } - return to.TextResult(slimLabels(labels)) + return to.TextResult(slim.Labels(labels)) } -// Issue label operations (moved from label package) - func addIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called addIssueLabelsFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -460,11 +442,10 @@ func addIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo if err != nil { return to.ErrorResult(fmt.Errorf("add labels to %v/%v/issue/%v err: %v", owner, repo, index, err)) } - return to.TextResult(slimLabels(issueLabels)) + return to.TextResult(slim.Labels(issueLabels)) } func replaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called replaceIssueLabelsFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -490,11 +471,10 @@ func replaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca if err != nil { return to.ErrorResult(fmt.Errorf("replace labels on %v/%v/issue/%v err: %v", owner, repo, index, err)) } - return to.TextResult(slimLabels(issueLabels)) + return to.TextResult(slim.Labels(issueLabels)) } func clearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called clearIssueLabelsFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -520,7 +500,6 @@ func clearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call } func removeIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called removeIssueLabelFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) diff --git a/operation/issue/slim.go b/operation/issue/slim.go index 0d440d7..bbeb9bd 100644 --- a/operation/issue/slim.go +++ b/operation/issue/slim.go @@ -1,63 +1,11 @@ package issue import ( - "fmt" - "strings" + "gitea.com/gitea/gitea-mcp/pkg/slim" 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 bodyWithAttachments(body string, atts []*gitea_sdk.Attachment) string { - links := make([]string, 0, len(atts)) - for _, a := range atts { - if a == nil || a.DownloadURL == "" { - continue - } - links = append(links, fmt.Sprintf("[%s](%s)", a.Name, a.DownloadURL)) - } - if len(links) == 0 { - return body - } - joined := strings.Join(links, "\n") - if body == "" { - return joined - } - return body + "\n\n" + joined -} - func slimIssue(i *gitea_sdk.Issue) map[string]any { if i == nil { return nil @@ -68,15 +16,15 @@ func slimIssue(i *gitea_sdk.Issue) map[string]any { "body": i.Body, "state": i.State, "html_url": i.HTMLURL, - "user": userLogin(i.Poster), - "labels": labelNames(i.Labels), + "user": slim.UserLogin(i.Poster), + "labels": slim.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) + m["assignees"] = slim.UserLogins(i.Assignees) } if i.Milestone != nil { m["milestone"] = map[string]any{ @@ -107,13 +55,13 @@ func slimIssues(issues []*gitea_sdk.Issue) []map[string]any { "title": i.Title, "state": i.State, "html_url": i.HTMLURL, - "user": userLogin(i.Poster), + "user": slim.UserLogin(i.Poster), "comments": i.Comments, "created_at": i.Created, "updated_at": i.Updated, } if len(i.Labels) > 0 { - m["labels"] = labelNames(i.Labels) + m["labels"] = slim.LabelNames(i.Labels) } if i.Ref != "" { m["ref"] = i.Ref @@ -133,26 +81,9 @@ func slimComment(c *gitea_sdk.Comment) map[string]any { return map[string]any{ "id": c.ID, "body": c.Body, - "user": userLogin(c.Poster), + "user": slim.UserLogin(c.Poster), "html_url": c.HTMLURL, "created_at": c.Created, "updated_at": c.Updated, } } - -func slimLabels(labels []*gitea_sdk.Label) []map[string]any { - out := make([]map[string]any, 0, len(labels)) - for _, l := range labels { - if l == nil { - continue - } - out = append(out, map[string]any{ - "id": l.ID, - "name": l.Name, - "color": l.Color, - "description": l.Description, - "exclusive": l.Exclusive, - }) - } - return out -} diff --git a/operation/issue/slim_test.go b/operation/issue/slim_test.go index 2beacd1..12a421e 100644 --- a/operation/issue/slim_test.go +++ b/operation/issue/slim_test.go @@ -40,29 +40,6 @@ func TestSlimIssue(t *testing.T) { } } -func TestBodyWithAttachments(t *testing.T) { - atts := []*gitea_sdk.Attachment{ - {Name: "shot.png", DownloadURL: "https://example/shot.png"}, - {Name: "log.txt", DownloadURL: "https://example/log.txt"}, - } - got := bodyWithAttachments("see attached", atts) - want := "see attached\n\n[shot.png](https://example/shot.png)\n[log.txt](https://example/log.txt)" - if got != want { - t.Errorf("got %q, want %q", got, want) - } - - if got := bodyWithAttachments("only body", nil); got != "only body" { - t.Errorf("nil attachments should return body unchanged, got %q", got) - } - if got := bodyWithAttachments("", atts); got != "[shot.png](https://example/shot.png)\n[log.txt](https://example/log.txt)" { - t.Errorf("empty body should drop separator, got %q", got) - } - skipped := []*gitea_sdk.Attachment{nil, {Name: "noop", DownloadURL: ""}} - if got := bodyWithAttachments("body", skipped); got != "body" { - t.Errorf("nil/empty-URL attachments should be skipped, got %q", got) - } -} - func TestSlimIssues_ListIsSlimmer(t *testing.T) { i := &gitea_sdk.Issue{ Index: 1, diff --git a/operation/label/label.go b/operation/label/label.go index 2a55e5e..41ddc07 100644 --- a/operation/label/label.go +++ b/operation/label/label.go @@ -6,8 +6,8 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" + "gitea.com/gitea/gitea-mcp/pkg/slim" "gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/tool" @@ -108,7 +108,6 @@ func labelWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe } func listRepoLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listRepoLabelsFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -133,11 +132,10 @@ func listRepoLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo if err != nil { return to.ErrorResult(fmt.Errorf("list %v/%v/labels err: %v", owner, repo, err)) } - return to.TextResult(slimLabels(labels)) + return to.TextResult(slim.Labels(labels)) } func getRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getRepoLabelFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -159,11 +157,10 @@ func getRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool if err != nil { return to.ErrorResult(fmt.Errorf("get %v/%v/label/%v err: %v", owner, repo, id, err)) } - return to.TextResult(slimLabel(label)) + return to.TextResult(slim.Label(label)) } func createRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called createRepoLabelFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -199,11 +196,10 @@ func createRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT if err != nil { return to.ErrorResult(fmt.Errorf("create %v/%v/label err: %v", owner, repo, err)) } - return to.TextResult(slimLabel(label)) + return to.TextResult(slim.Label(label)) } func editRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called editRepoLabelFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -217,18 +213,12 @@ func editRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo return to.ErrorResult(err) } - opt := gitea_sdk.EditLabelOption{} - if name, ok := req.GetArguments()["name"].(string); ok { - opt.Name = new(name) - } - if color, ok := req.GetArguments()["color"].(string); ok { - opt.Color = new(color) - } - if description, ok := req.GetArguments()["description"].(string); ok { - opt.Description = new(description) - } - if isArchived, ok := req.GetArguments()["is_archived"].(bool); ok { - opt.IsArchived = &isArchived + args := req.GetArguments() + opt := gitea_sdk.EditLabelOption{ + Name: params.GetOptionalStringPtr(args, "name"), + Color: params.GetOptionalStringPtr(args, "color"), + Description: params.GetPresentStringPtr(args, "description"), + IsArchived: params.GetOptionalBoolPtr(args, "is_archived"), } client, err := gitea.ClientFromContext(ctx) @@ -239,11 +229,10 @@ func editRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo if err != nil { return to.ErrorResult(fmt.Errorf("edit %v/%v/label/%v err: %v", owner, repo, id, err)) } - return to.TextResult(slimLabel(label)) + return to.TextResult(slim.Label(label)) } func deleteRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called deleteRepoLabelFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -269,7 +258,6 @@ func deleteRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT } func listOrgLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listOrgLabelsFn") org, err := params.GetString(req.GetArguments(), "org") if err != nil { return to.ErrorResult(err) @@ -290,11 +278,10 @@ func listOrgLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo if err != nil { return to.ErrorResult(fmt.Errorf("list %v/labels err: %v", org, err)) } - return to.TextResult(slimLabels(labels)) + return to.TextResult(slim.Labels(labels)) } func createOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called createOrgLabelFn") org, err := params.GetString(req.GetArguments(), "org") if err != nil { return to.ErrorResult(err) @@ -325,11 +312,10 @@ func createOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo if err != nil { return to.ErrorResult(fmt.Errorf("create %v/labels err: %v", org, err)) } - return to.TextResult(slimLabel(label)) + return to.TextResult(slim.Label(label)) } func editOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called editOrgLabelFn") org, err := params.GetString(req.GetArguments(), "org") if err != nil { return to.ErrorResult(err) @@ -339,18 +325,12 @@ func editOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool return to.ErrorResult(err) } - opt := gitea_sdk.EditOrgLabelOption{} - if name, ok := req.GetArguments()["name"].(string); ok { - opt.Name = new(name) - } - if color, ok := req.GetArguments()["color"].(string); ok { - opt.Color = new(color) - } - if description, ok := req.GetArguments()["description"].(string); ok { - opt.Description = new(description) - } - if exclusive, ok := req.GetArguments()["exclusive"].(bool); ok { - opt.Exclusive = new(exclusive) + args := req.GetArguments() + opt := gitea_sdk.EditOrgLabelOption{ + Name: params.GetOptionalStringPtr(args, "name"), + Color: params.GetOptionalStringPtr(args, "color"), + Description: params.GetPresentStringPtr(args, "description"), + Exclusive: params.GetOptionalBoolPtr(args, "exclusive"), } client, err := gitea.ClientFromContext(ctx) @@ -361,11 +341,10 @@ func editOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool if err != nil { return to.ErrorResult(fmt.Errorf("edit %v/labels/%v err: %v", org, id, err)) } - return to.TextResult(slimLabel(label)) + return to.TextResult(slim.Label(label)) } func deleteOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called deleteOrgLabelFn") org, err := params.GetString(req.GetArguments(), "org") if err != nil { return to.ErrorResult(err) diff --git a/operation/label/slim.go b/operation/label/slim.go index 315105a..651677a 100644 --- a/operation/label/slim.go +++ b/operation/label/slim.go @@ -1,26 +1 @@ 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 -} diff --git a/operation/label/slim_test.go b/operation/label/slim_test.go deleted file mode 100644 index 9eec9bd..0000000 --- a/operation/label/slim_test.go +++ /dev/null @@ -1,25 +0,0 @@ -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"]) - } -} diff --git a/operation/milestone/milestone.go b/operation/milestone/milestone.go index 6d81d02..feba58d 100644 --- a/operation/milestone/milestone.go +++ b/operation/milestone/milestone.go @@ -6,7 +6,6 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "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/tool" @@ -99,7 +98,6 @@ func milestoneWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo } func getMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getMilestoneFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -125,7 +123,6 @@ func getMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool } func listMilestonesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listMilestonesFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -157,7 +154,6 @@ func listMilestonesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo } func createMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called createMilestoneFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -194,7 +190,6 @@ func createMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT } func editMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called editMilestoneFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -208,21 +203,18 @@ func editMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo return to.ErrorResult(err) } - opt := gitea_sdk.EditMilestoneOption{} - - title, ok := req.GetArguments()["title"].(string) - if ok { + args := req.GetArguments() + opt := gitea_sdk.EditMilestoneOption{ + Description: params.GetPresentStringPtr(args, "description"), + Deadline: params.GetOptionalTime(args, "due_on"), + } + if title, ok := args["title"].(string); ok { opt.Title = title } - description, ok := req.GetArguments()["description"].(string) - if ok { - opt.Description = new(description) + if state, ok := args["state"].(string); ok { + s := gitea_sdk.StateType(state) + opt.State = &s } - state, ok := req.GetArguments()["state"].(string) - if ok { - opt.State = new(gitea_sdk.StateType(state)) - } - opt.Deadline = params.GetOptionalTime(req.GetArguments(), "due_on") client, err := gitea.ClientFromContext(ctx) if err != nil { @@ -237,7 +229,6 @@ func editMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo } func deleteMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called deleteMilestoneFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) diff --git a/operation/notification/notification.go b/operation/notification/notification.go index 7cedd14..e3913d1 100644 --- a/operation/notification/notification.go +++ b/operation/notification/notification.go @@ -7,7 +7,6 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "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/tool" @@ -97,7 +96,6 @@ func notificationWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal } func listNotificationsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listNotificationsFn") args := req.GetArguments() page, pageSize := params.GetPagination(args, 30) opt := gitea_sdk.ListNotificationOptions{ @@ -142,7 +140,6 @@ func listNotificationsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal } func getNotificationFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getNotificationFn") id, err := params.GetIndex(req.GetArguments(), "id") if err != nil { return to.ErrorResult(err) @@ -159,7 +156,6 @@ func getNotificationFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT } func markNotificationReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called markNotificationReadFn") id, err := params.GetIndex(req.GetArguments(), "id") if err != nil { return to.ErrorResult(err) @@ -179,7 +175,6 @@ func markNotificationReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp. } func markAllNotificationsReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called markAllNotificationsReadFn") args := req.GetArguments() lastReadAt := time.Now() if t := params.GetOptionalTime(args, "last_read_at"); t != nil { diff --git a/operation/operation.go b/operation/operation.go index e5a9df6..c92a201 100644 --- a/operation/operation.go +++ b/operation/operation.go @@ -46,7 +46,6 @@ func RegisterTool(s *server.MCPServer) { for _, t := range domainTools { s.AddTools(t.Tools()...) } - s.DeleteTools("") tool.WarnUnmatchedAllowedTools(domainTools...) } @@ -97,7 +96,7 @@ func Run() error { case "http": httpServer := server.NewStreamableHTTPServer( mcpServer, - server.WithLogger(log.New()), + server.WithLogger(log.Default().Sugar()), server.WithHeartbeatInterval(30*time.Second), server.WithHTTPContextFunc(getContextWithToken), ) diff --git a/operation/packages/packages.go b/operation/packages/packages.go index 1e707cf..335bb77 100644 --- a/operation/packages/packages.go +++ b/operation/packages/packages.go @@ -9,7 +9,6 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "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/tool" @@ -110,7 +109,6 @@ func escapePackageName(name string) string { } func listPackagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listPackagesFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -138,7 +136,6 @@ func listPackagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool } func listPackageVersionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listPackageVersionsFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -168,7 +165,6 @@ func listPackageVersionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.C } func getPackageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getPackageFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -197,7 +193,6 @@ func getPackageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe } func deletePackageVersionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called deletePackageVersionFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { diff --git a/operation/pull/pull.go b/operation/pull/pull.go index d784c01..d9dca9a 100644 --- a/operation/pull/pull.go +++ b/operation/pull/pull.go @@ -10,6 +10,7 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" + "gitea.com/gitea/gitea-mcp/pkg/slim" "gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/tool" @@ -261,7 +262,6 @@ func pullRequestReviewWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mc } func getPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getPullRequestByIndexFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -293,12 +293,11 @@ func getPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp } m := slimPullRequest(pr) - m["body"] = bodyWithAttachments(pr.Body, assets) + m["body"] = slim.BodyWithAttachments(pr.Body, assets) return to.TextResult(m) } func getPullRequestDiffFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getPullRequestDiffFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -329,7 +328,6 @@ func getPullRequestDiffFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca } func listRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called ListRepoPullRequests") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -387,7 +385,6 @@ func applyDraftPrefix(title string, isDraft bool) string { } func createPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called createPullRequestFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -440,8 +437,9 @@ func createPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal return to.TextResult(slimPullRequest(pr)) } -func createPullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called createPullRequestReviewerFn") +type reviewerOp func(client *gitea_sdk.Client, owner, repo string, index int64, opt gitea_sdk.PullReviewRequestOptions) (*gitea_sdk.Response, error) + +func pullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest, verb string, op reviewerOp) (*mcp.CallToolResult, error) { args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -464,70 +462,31 @@ func createPullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) ( return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } - _, err = client.CreateReviewRequests(owner, repo, index, gitea_sdk.PullReviewRequestOptions{ + if _, err := op(client, owner, repo, index, gitea_sdk.PullReviewRequestOptions{ Reviewers: reviewers, TeamReviewers: teamReviewers, - }) - if err != nil { - return to.ErrorResult(fmt.Errorf("create review requests for %v/%v/pr/%v err: %v", owner, repo, index, err)) + }); err != nil { + return to.ErrorResult(fmt.Errorf("%s review requests for %v/%v/pr/%v err: %v", verb, owner, repo, index, err)) } - successMsg := map[string]any{ - "message": "Successfully created review requests", + return to.TextResult(map[string]any{ + "message": fmt.Sprintf("Successfully %sd review requests", verb), "reviewers": reviewers, "team_reviewers": teamReviewers, "pr_index": index, "repository": fmt.Sprintf("%s/%s", owner, repo), - } + }) +} - return to.TextResult(successMsg) +func createPullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return pullRequestReviewerFn(ctx, req, "create", (*gitea_sdk.Client).CreateReviewRequests) } func deletePullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called deletePullRequestReviewerFn") - args := req.GetArguments() - owner, err := params.GetString(args, "owner") - if err != nil { - return to.ErrorResult(err) - } - repo, err := params.GetString(args, "repo") - if err != nil { - return to.ErrorResult(err) - } - index, err := params.GetIndex(args, "pull_number") - if err != nil { - return to.ErrorResult(err) - } - - reviewers := params.GetStringSlice(args, "reviewers") - teamReviewers := params.GetStringSlice(args, "team_reviewers") - - client, err := gitea.ClientFromContext(ctx) - if err != nil { - return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) - } - - _, err = client.DeleteReviewRequests(owner, repo, index, gitea_sdk.PullReviewRequestOptions{ - Reviewers: reviewers, - TeamReviewers: teamReviewers, - }) - if err != nil { - return to.ErrorResult(fmt.Errorf("delete review requests for %v/%v/pr/%v err: %v", owner, repo, index, err)) - } - - successMsg := map[string]any{ - "message": "Successfully deleted review requests", - "reviewers": reviewers, - "team_reviewers": teamReviewers, - "pr_index": index, - "repository": fmt.Sprintf("%s/%s", owner, repo), - } - - return to.TextResult(successMsg) + return pullRequestReviewerFn(ctx, req, "delete", (*gitea_sdk.Client).DeleteReviewRequests) } func listPullRequestReviewsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listPullRequestReviewsFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -562,7 +521,6 @@ func listPullRequestReviewsFn(ctx context.Context, req mcp.CallToolRequest) (*mc } func getPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getPullRequestReviewFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -595,7 +553,6 @@ func getPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp. } func listPullRequestReviewCommentsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listPullRequestReviewCommentsFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -628,7 +585,6 @@ func listPullRequestReviewCommentsFn(ctx context.Context, req mcp.CallToolReques } func createPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called createPullRequestReviewFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -693,7 +649,6 @@ func createPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m } func submitPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called submitPullRequestReviewFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -737,7 +692,6 @@ func submitPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m } func deletePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called deletePullRequestReviewFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -777,7 +731,6 @@ func deletePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m } func dismissPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called dismissPullRequestReviewFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -822,7 +775,6 @@ func dismissPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (* } func mergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called mergePullRequestFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -886,7 +838,6 @@ func mergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call } func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called editPullRequestFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -922,9 +873,10 @@ func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT } opt.Title = applyDraftPrefix(opt.Title, draft) } - if body, ok := args["body"].(string); ok { - opt.Body = new(body) - } + opt.Body = params.GetPresentStringPtr(args, "body") + opt.AllowMaintainerEdit = params.GetOptionalBoolPtr(args, "allow_maintainer_edit") + opt.RemoveDeadline = params.GetOptionalBoolPtr(args, "remove_deadline") + opt.Deadline = params.GetOptionalTime(args, "deadline") if base, ok := args["base"].(string); ok { opt.Base = base } @@ -940,18 +892,12 @@ func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT } } if state, ok := args["state"].(string); ok { - opt.State = new(gitea_sdk.StateType(state)) - } - if allowMaintainerEdit, ok := args["allow_maintainer_edit"].(bool); ok { - opt.AllowMaintainerEdit = new(allowMaintainerEdit) + s := gitea_sdk.StateType(state) + opt.State = &s } if labelIDs, err := params.GetInt64Slice(args, "labels"); err == nil { opt.Labels = labelIDs } - opt.Deadline = params.GetOptionalTime(args, "deadline") - if removeDeadline, ok := args["remove_deadline"].(bool); ok { - opt.RemoveDeadline = &removeDeadline - } client, err := gitea.ClientFromContext(ctx) if err != nil { @@ -967,7 +913,6 @@ func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT } func updatePullRequestBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called updatePullRequestBranchFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -990,7 +935,6 @@ func updatePullRequestBranchFn(ctx context.Context, req mcp.CallToolRequest) (*m } func getPullRequestFilesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getPullRequestFilesFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -1019,7 +963,6 @@ func getPullRequestFilesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.C } func getPullRequestStatusFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getPullRequestStatusFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { diff --git a/operation/pull/slim.go b/operation/pull/slim.go index 3c311c2..312b051 100644 --- a/operation/pull/slim.go +++ b/operation/pull/slim.go @@ -1,63 +1,11 @@ package pull import ( - "fmt" - "strings" + "gitea.com/gitea/gitea-mcp/pkg/slim" gitea_sdk "code.gitea.io/sdk/gitea" ) -func bodyWithAttachments(body string, atts []*gitea_sdk.Attachment) string { - links := make([]string, 0, len(atts)) - for _, a := range atts { - if a == nil || a.DownloadURL == "" { - continue - } - links = append(links, fmt.Sprintf("[%s](%s)", a.Name, a.DownloadURL)) - } - if len(links) == 0 { - return body - } - joined := strings.Join(links, "\n") - if body == "" { - return joined - } - return body + "\n\n" + joined -} - -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 @@ -81,8 +29,8 @@ func slimPullRequest(pr *gitea_sdk.PullRequest) map[string]any { "merged": pr.HasMerged, "mergeable": pr.Mergeable, "html_url": pr.HTMLURL, - "user": userLogin(pr.Poster), - "labels": labelNames(pr.Labels), + "user": slim.UserLogin(pr.Poster), + "labels": slim.LabelNames(pr.Labels), "comments": pr.Comments, "created_at": pr.Created, "updated_at": pr.Updated, @@ -91,7 +39,7 @@ func slimPullRequest(pr *gitea_sdk.PullRequest) map[string]any { if pr.HasMerged { m["merged_at"] = pr.Merged m["merge_commit_sha"] = pr.MergedCommitID - m["merged_by"] = userLogin(pr.MergedBy) + m["merged_by"] = slim.UserLogin(pr.MergedBy) } if pr.Head != nil { head := map[string]any{"ref": pr.Head.Ref, "sha": pr.Head.Sha} @@ -117,7 +65,7 @@ func slimPullRequest(pr *gitea_sdk.PullRequest) map[string]any { m["changed_files"] = *pr.ChangedFiles } if len(pr.Assignees) > 0 { - m["assignees"] = userLogins(pr.Assignees) + m["assignees"] = slim.UserLogins(pr.Assignees) } if pr.Milestone != nil { m["milestone"] = pr.Milestone.Title @@ -141,7 +89,7 @@ func slimPullRequests(prs []*gitea_sdk.PullRequest) []map[string]any { "draft": pr.Draft, "merged": pr.HasMerged, "html_url": pr.HTMLURL, - "user": userLogin(pr.Poster), + "user": slim.UserLogin(pr.Poster), "created_at": pr.Created, "updated_at": pr.Updated, } @@ -152,7 +100,7 @@ func slimPullRequests(prs []*gitea_sdk.PullRequest) []map[string]any { m["base"] = pr.Base.Ref } if len(pr.Labels) > 0 { - m["labels"] = labelNames(pr.Labels) + m["labels"] = slim.LabelNames(pr.Labels) } out = append(out, m) } @@ -167,7 +115,7 @@ func slimReview(r *gitea_sdk.PullReview) map[string]any { "id": r.ID, "state": r.State, "body": r.Body, - "user": userLogin(r.Reviewer), + "user": slim.UserLogin(r.Reviewer), "comments_count": r.CodeCommentsCount, "submitted_at": r.Submitted, "html_url": r.HTMLURL, @@ -196,7 +144,7 @@ func slimReviewComment(c *gitea_sdk.PullReviewComment) map[string]any { "position": c.LineNum, "old_position": c.OldLineNum, "diff_hunk": c.DiffHunk, - "user": userLogin(c.Reviewer), + "user": slim.UserLogin(c.Reviewer), "html_url": c.HTMLURL, "created_at": c.Created, "updated_at": c.Updated, diff --git a/operation/repo/branch.go b/operation/repo/branch.go index d230c89..1290216 100644 --- a/operation/repo/branch.go +++ b/operation/repo/branch.go @@ -6,7 +6,6 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/to" @@ -65,7 +64,6 @@ func init() { } func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called CreateBranchFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -93,11 +91,10 @@ func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool return to.ErrorResult(fmt.Errorf("create branch error: %v", err)) } - return mcp.NewToolResultText("Branch Created"), nil + return to.TextResult("Branch Created") } func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called DeleteBranchFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -124,7 +121,6 @@ func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool } func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called ListBranchesFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { diff --git a/operation/repo/commit.go b/operation/repo/commit.go index a4a9170..b2e8d2f 100644 --- a/operation/repo/commit.go +++ b/operation/repo/commit.go @@ -6,7 +6,6 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/to" @@ -53,7 +52,6 @@ func init() { } func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called ListRepoCommitsFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -86,7 +84,6 @@ func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT } func GetCommitFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called GetCommitFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { diff --git a/operation/repo/file.go b/operation/repo/file.go index edb90a8..7c47283 100644 --- a/operation/repo/file.go +++ b/operation/repo/file.go @@ -10,7 +10,6 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/to" @@ -98,7 +97,6 @@ type ContentLine struct { } func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called GetFileFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -162,7 +160,6 @@ func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo } func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called GetDirContentFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -189,7 +186,6 @@ func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo } func CreateOrUpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called CreateOrUpdateFileFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -249,7 +245,6 @@ func CreateOrUpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca } func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called DeleteFileFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { diff --git a/operation/repo/release.go b/operation/repo/release.go index 731653e..eec263d 100644 --- a/operation/repo/release.go +++ b/operation/repo/release.go @@ -6,7 +6,6 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/to" @@ -97,7 +96,6 @@ func init() { } func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called CreateReleasesFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -136,14 +134,13 @@ func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo IsPrerelease: isPreRelease, }) if err != nil { - return nil, fmt.Errorf("create release error: %v", err) + return to.ErrorResult(fmt.Errorf("create release error: %v", err)) } - return mcp.NewToolResultText("Release Created"), nil + return to.TextResult("Release Created") } func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called DeleteReleaseFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -164,14 +161,13 @@ func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo } _, err = client.DeleteRelease(owner, repo, id) if err != nil { - return nil, fmt.Errorf("delete release error: %v", err) + return to.ErrorResult(fmt.Errorf("delete release error: %v", err)) } return to.TextResult("Release deleted successfully") } func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called GetReleaseFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -192,14 +188,13 @@ func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe } release, _, err := client.GetRelease(owner, repo, id) if err != nil { - return nil, fmt.Errorf("get release error: %v", err) + return to.ErrorResult(fmt.Errorf("get release error: %v", err)) } return to.TextResult(slimRelease(release)) } func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called GetLatestReleaseFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -216,14 +211,13 @@ func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call } release, _, err := client.GetLatestRelease(owner, repo) if err != nil { - return nil, fmt.Errorf("get latest release error: %v", err) + return to.ErrorResult(fmt.Errorf("get latest release error: %v", err)) } return to.TextResult(slimRelease(release)) } func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called ListReleasesFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -233,18 +227,7 @@ func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool if err != nil { return to.ErrorResult(err) } - var pIsDraft *bool - isDraft, ok := args["is_draft"].(bool) - if ok { - pIsDraft = new(isDraft) - } - var pIsPreRelease *bool - isPreRelease, ok := args["is_pre_release"].(bool) - if ok { - pIsPreRelease = new(isPreRelease) - } - page := params.GetOptionalInt(args, "page", 1) - pageSize := params.GetOptionalInt(args, "per_page", 20) + page, pageSize := params.GetPagination(args, 20) client, err := gitea.ClientFromContext(ctx) if err != nil { @@ -252,14 +235,14 @@ func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool } releases, _, err := client.ListReleases(owner, repo, gitea_sdk.ListReleasesOptions{ ListOptions: gitea_sdk.ListOptions{ - Page: int(page), - PageSize: int(pageSize), + Page: page, + PageSize: pageSize, }, - IsDraft: pIsDraft, - IsPreRelease: pIsPreRelease, + IsDraft: params.GetOptionalBoolPtr(args, "is_draft"), + IsPreRelease: params.GetOptionalBoolPtr(args, "is_pre_release"), }) if err != nil { - return nil, fmt.Errorf("list releases error: %v", err) + return to.ErrorResult(fmt.Errorf("list releases error: %v", err)) } return to.TextResult(slimReleases(releases)) diff --git a/operation/repo/repo.go b/operation/repo/repo.go index c892d82..65aa177 100644 --- a/operation/repo/repo.go +++ b/operation/repo/repo.go @@ -2,13 +2,12 @@ package repo import ( "context" - "errors" "fmt" "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" + "gitea.com/gitea/gitea-mcp/pkg/slim" "gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/tool" @@ -90,7 +89,6 @@ func init() { } func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called CreateRepoFn") args := req.GetArguments() name, err := params.GetString(args, "name") if err != nil { @@ -140,11 +138,10 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe return to.ErrorResult(fmt.Errorf("create repository '%s' err: %v", name, err)) } } - return to.TextResult(slimRepo(repo)) + return to.TextResult(slim.Repo(repo)) } func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called ForkRepoFn") args := req.GetArguments() user, err := params.GetString(args, "user") if err != nil { @@ -154,19 +151,9 @@ func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu if err != nil { return to.ErrorResult(err) } - organization, ok := args["organization"].(string) - organizationPtr := new(organization) - if !ok || organization == "" { - organizationPtr = nil - } - name, ok := args["name"].(string) - namePtr := new(name) - if !ok || name == "" { - namePtr = nil - } opt := gitea_sdk.CreateForkOption{ - Organization: organizationPtr, - Name: namePtr, + Organization: params.GetOptionalStringPtr(args, "organization"), + Name: params.GetOptionalStringPtr(args, "name"), } client, err := gitea.ClientFromContext(ctx) if err != nil { @@ -180,7 +167,6 @@ func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu } func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called ListMyReposFn") page, pageSize := params.GetPagination(req.GetArguments(), 30) opt := gitea_sdk.ListReposOptions{ ListOptions: gitea_sdk.ListOptions{ @@ -197,14 +183,13 @@ func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR return to.ErrorResult(fmt.Errorf("list my repositories error: %v", err)) } - return to.TextResult(slimRepos(repos)) + return to.TextResult(slim.Repos(repos)) } func ListOrgReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called ListOrgReposFn") - org, ok := req.GetArguments()["org"].(string) - if !ok { - return to.ErrorResult(errors.New("organization name is required")) + org, err := params.GetString(req.GetArguments(), "org") + if err != nil { + return to.ErrorResult(err) } page, pageSize := params.GetPagination(req.GetArguments(), 100) opt := gitea_sdk.ListOrgReposOptions{ diff --git a/operation/repo/slim.go b/operation/repo/slim.go index 10482ee..49afadc 100644 --- a/operation/repo/slim.go +++ b/operation/repo/slim.go @@ -1,56 +1,11 @@ package repo import ( + "gitea.com/gitea/gitea-mcp/pkg/slim" + 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 @@ -144,7 +99,7 @@ func slimRelease(r *gitea_sdk.Release) map[string]any { "draft": r.IsDraft, "prerelease": r.IsPrerelease, "html_url": r.HTMLURL, - "author": userLogin(r.Publisher), + "author": slim.UserLogin(r.Publisher), "created_at": r.CreatedAt, "published_at": r.PublishedAt, } diff --git a/operation/repo/slim_test.go b/operation/repo/slim_test.go index 60bbc7a..527094c 100644 --- a/operation/repo/slim_test.go +++ b/operation/repo/slim_test.go @@ -6,39 +6,6 @@ import ( 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", diff --git a/operation/repo/tag.go b/operation/repo/tag.go index 6446111..5ff1667 100644 --- a/operation/repo/tag.go +++ b/operation/repo/tag.go @@ -6,7 +6,6 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/to" @@ -79,7 +78,6 @@ func init() { } func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called CreateTagFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -106,14 +104,13 @@ func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes Message: message, }) if err != nil { - return nil, fmt.Errorf("create tag error: %v", err) + return to.ErrorResult(fmt.Errorf("create tag error: %v", err)) } - return mcp.NewToolResultText("Tag Created"), nil + return to.TextResult("Tag Created") } func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called DeleteTagFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -134,14 +131,13 @@ func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes } _, err = client.DeleteTag(owner, repo, tagName) if err != nil { - return nil, fmt.Errorf("delete tag error: %v", err) + return to.ErrorResult(fmt.Errorf("delete tag error: %v", err)) } return to.TextResult("Tag deleted") } func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called GetTagFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -162,14 +158,13 @@ func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult } tag, _, err := client.GetTag(owner, repo, tagName) if err != nil { - return nil, fmt.Errorf("get tag error: %v", err) + return to.ErrorResult(fmt.Errorf("get tag error: %v", err)) } return to.TextResult(slimTag(tag)) } func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called ListTagsFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -193,7 +188,7 @@ func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu }, }) if err != nil { - return nil, fmt.Errorf("list tags error: %v", err) + return to.ErrorResult(fmt.Errorf("list tags error: %v", err)) } return to.TextResult(slimTags(tags)) diff --git a/operation/repo/tree.go b/operation/repo/tree.go index 862ac66..ea72a2b 100644 --- a/operation/repo/tree.go +++ b/operation/repo/tree.go @@ -6,7 +6,6 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/to" @@ -38,7 +37,6 @@ func init() { } func GetRepoTreeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called GetRepoTreeFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { diff --git a/operation/search/search.go b/operation/search/search.go index 78da76d..68b39d9 100644 --- a/operation/search/search.go +++ b/operation/search/search.go @@ -7,8 +7,8 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" + "gitea.com/gitea/gitea-mcp/pkg/slim" "gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/tool" @@ -94,7 +94,6 @@ func init() { } func UsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called UsersFn") keyword, err := params.GetString(req.GetArguments(), "query") if err != nil { return to.ErrorResult(err) @@ -119,7 +118,6 @@ func UsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, } func OrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called OrgTeamsFn") org, err := params.GetString(req.GetArguments(), "org") if err != nil { return to.ErrorResult(err) @@ -150,34 +148,23 @@ func OrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu } func ReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called ReposFn") keyword, err := params.GetString(req.GetArguments(), "query") if err != nil { return to.ErrorResult(err) } - keywordIsTopic, _ := req.GetArguments()["keywordIsTopic"].(bool) - keywordInDescription, _ := req.GetArguments()["keywordInDescription"].(bool) - ownerID := params.GetOptionalInt(req.GetArguments(), "ownerID", 0) - var pIsPrivate *bool - isPrivate, ok := req.GetArguments()["isPrivate"].(bool) - if ok { - pIsPrivate = new(isPrivate) - } - var pIsArchived *bool - isArchived, ok := req.GetArguments()["isArchived"].(bool) - if ok { - pIsArchived = new(isArchived) - } - sort, _ := req.GetArguments()["sort"].(string) - order, _ := req.GetArguments()["order"].(string) - page, pageSize := params.GetPagination(req.GetArguments(), 30) + args := req.GetArguments() + keywordIsTopic, _ := args["keywordIsTopic"].(bool) + keywordInDescription, _ := args["keywordInDescription"].(bool) + sort, _ := args["sort"].(string) + order, _ := args["order"].(string) + page, pageSize := params.GetPagination(args, 30) opt := gitea_sdk.SearchRepoOptions{ Keyword: keyword, KeywordIsTopic: keywordIsTopic, KeywordInDescription: keywordInDescription, - OwnerID: ownerID, - IsPrivate: pIsPrivate, - IsArchived: pIsArchived, + OwnerID: params.GetOptionalInt(args, "ownerID", 0), + IsPrivate: params.GetOptionalBoolPtr(args, "isPrivate"), + IsArchived: params.GetOptionalBoolPtr(args, "isArchived"), Sort: sort, Order: order, ListOptions: gitea_sdk.ListOptions{ @@ -193,11 +180,10 @@ func ReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, if err != nil { return to.ErrorResult(fmt.Errorf("search repos error: %v", err)) } - return to.TextResult(slimRepos(repos)) + return to.TextResult(slim.Repos(repos)) } func IssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called IssuesFn") args := req.GetArguments() query, err := params.GetString(args, "query") if err != nil { diff --git a/operation/search/slim.go b/operation/search/slim.go index 6a84eef..5640373 100644 --- a/operation/search/slim.go +++ b/operation/search/slim.go @@ -1,28 +1,15 @@ package search import ( + "gitea.com/gitea/gitea-mcp/pkg/slim" + 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)) + out = append(out, slim.UserDetail(u)) } return out } @@ -47,66 +34,6 @@ func slimTeams(teams []*gitea_sdk.Team) []map[string]any { 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 -} - -func userLogin(u *gitea_sdk.User) string { - if u == nil { - return "" - } - return u.UserName -} - -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 slimIssues(issues []*gitea_sdk.Issue) []map[string]any { out := make([]map[string]any, 0, len(issues)) for _, i := range issues { @@ -118,13 +45,13 @@ func slimIssues(issues []*gitea_sdk.Issue) []map[string]any { "title": i.Title, "state": i.State, "html_url": i.HTMLURL, - "user": userLogin(i.Poster), + "user": slim.UserLogin(i.Poster), "comments": i.Comments, "created_at": i.Created, "updated_at": i.Updated, } if len(i.Labels) > 0 { - m["labels"] = labelNames(i.Labels) + m["labels"] = slim.LabelNames(i.Labels) } if i.Repository != nil { m["repository"] = i.Repository.FullName diff --git a/operation/timetracking/timetracking.go b/operation/timetracking/timetracking.go index 30fe558..ea1df03 100644 --- a/operation/timetracking/timetracking.go +++ b/operation/timetracking/timetracking.go @@ -7,7 +7,6 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "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/tool" @@ -95,10 +94,7 @@ func writeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, } } -// Stopwatch handler functions - func startStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called startStopwatchFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -123,7 +119,6 @@ func startStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo } func stopStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called stopStopwatchFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -148,7 +143,6 @@ func stopStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo } func deleteStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called deleteStopwatchFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -173,7 +167,6 @@ func deleteStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT } func getMyStopwatchesFn(ctx context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getMyStopwatchesFn") client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) @@ -188,10 +181,7 @@ func getMyStopwatchesFn(ctx context.Context, _ mcp.CallToolRequest) (*mcp.CallTo return to.TextResult(slimStopWatches(stopwatches)) } -// Tracked time handler functions - func listTrackedTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listTrackedTimesFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -226,7 +216,6 @@ func listTrackedTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call } func addTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called addTrackedTimeFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -258,7 +247,6 @@ func addTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo } func deleteTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called deleteTrackedTimeFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -288,7 +276,6 @@ func deleteTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal } func listRepoTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listRepoTimesFn") owner, err := params.GetString(req.GetArguments(), "owner") if err != nil { return to.ErrorResult(err) @@ -319,7 +306,6 @@ func listRepoTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo } func getMyTimesFn(ctx context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getMyTimesFn") client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) diff --git a/operation/user/slim.go b/operation/user/slim.go index 421a20f..63a036b 100644 --- a/operation/user/slim.go +++ b/operation/user/slim.go @@ -4,21 +4,6 @@ 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 diff --git a/operation/user/slim_test.go b/operation/user/slim_test.go deleted file mode 100644 index bc32887..0000000 --- a/operation/user/slim_test.go +++ /dev/null @@ -1,39 +0,0 @@ -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) - } -} diff --git a/operation/user/user.go b/operation/user/user.go index 4a087e8..9334c62 100644 --- a/operation/user/user.go +++ b/operation/user/user.go @@ -6,8 +6,8 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" + "gitea.com/gitea/gitea-mcp/pkg/slim" "gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/tool" @@ -17,62 +17,34 @@ import ( ) const ( - // GetMyUserInfoToolName is the unique tool name used for MCP registration and lookup of the get_me command. GetMyUserInfoToolName = "get_me" - // GetUserOrgsToolName is the unique tool name used for MCP registration and lookup of the get_user_orgs command. - GetUserOrgsToolName = "get_user_orgs" - - // defaultPage is the default starting page number used for paginated organization listings. - defaultPage = 1 - // defaultPageSize is the default number of organizations per page for paginated queries. - defaultPageSize = 30 + GetUserOrgsToolName = "get_user_orgs" ) -// Tool is the MCP tool manager instance for registering all MCP tools in this package. var Tool = tool.New() var ( - // GetMyUserInfoTool is the MCP tool for retrieving the current user's info. - // It is registered with a specific name and a description string. GetMyUserInfoTool = mcp.NewTool( GetMyUserInfoToolName, mcp.WithDescription("Get current user"), mcp.WithToolAnnotation(annotation.ReadOnly("Get current user information")), ) - // GetUserOrgsTool is the MCP tool for listing organizations for the authenticated user. - // It supports pagination via "page" and "per_page" arguments with default values specified above. GetUserOrgsTool = mcp.NewTool( GetUserOrgsToolName, mcp.WithDescription("List current user's organizations"), mcp.WithToolAnnotation(annotation.ReadOnly("Get user organizations")), - mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(defaultPage)), - mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(defaultPageSize)), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)), ) ) -// init registers all MCP tools in Tool at package initialization. -// This function ensures the handler functions are registered before server usage. func init() { - registerTools() + Tool.RegisterRead(server.ServerTool{Tool: GetMyUserInfoTool, Handler: GetUserInfoFn}) + Tool.RegisterRead(server.ServerTool{Tool: GetUserOrgsTool, Handler: GetUserOrgsFn}) } -// registerTools registers all local MCP tool definitions and their handler functions. -// To add new functionality, append your tool/handler pair to the tools slice below. -func registerTools() { - tools := []server.ServerTool{ - {Tool: GetMyUserInfoTool, Handler: GetUserInfoFn}, - {Tool: GetUserOrgsTool, Handler: GetUserOrgsFn}, - } - for _, t := range tools { - Tool.RegisterRead(t) - } -} - -// GetUserInfoFn is the handler for "get_me" MCP tool requests. -// Logs invocation, fetches current user info from gitea, wraps result for MCP. func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("[User] Called GetUserInfoFn") client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) @@ -81,15 +53,11 @@ func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR if err != nil { return to.ErrorResult(fmt.Errorf("get user info err: %v", err)) } - return to.TextResult(slimUserDetail(user)) + return to.TextResult(slim.UserDetail(user)) } -// GetUserOrgsFn is the handler for "get_user_orgs" MCP tool requests. -// Logs invocation, pulls validated pagination arguments from request, -// performs Gitea organization listing, and wraps the result for MCP. func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("[User] Called GetUserOrgsFn") - page, pageSize := params.GetPagination(req.GetArguments(), defaultPageSize) + page, pageSize := params.GetPagination(req.GetArguments(), 30) opt := gitea_sdk.ListOrgsOptions{ ListOptions: gitea_sdk.ListOptions{ diff --git a/operation/version/version.go b/operation/version/version.go index d0ca8df..77737a6 100644 --- a/operation/version/version.go +++ b/operation/version/version.go @@ -6,7 +6,6 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/flag" - "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/tool" @@ -33,7 +32,6 @@ func init() { } func GetGiteaMCPServerVersionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called GetGiteaMCPServerVersionFn") version := flag.Version if version == "" { version = "dev" diff --git a/operation/wiki/wiki.go b/operation/wiki/wiki.go index 64ecb26..a8edee5 100644 --- a/operation/wiki/wiki.go +++ b/operation/wiki/wiki.go @@ -8,7 +8,6 @@ import ( "gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/gitea" - "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/tool" @@ -95,7 +94,6 @@ func wikiWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes } func listWikiPagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called listWikiPagesFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -116,7 +114,6 @@ func listWikiPagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo } func getWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getWikiPageFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -141,7 +138,6 @@ func getWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR } func getWikiRevisionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called getWikiRevisionsFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -166,7 +162,6 @@ func getWikiRevisionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call } func createWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called createWikiPageFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -206,7 +201,6 @@ func createWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo } func updateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called updateWikiPageFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { @@ -252,7 +246,6 @@ func updateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo } func deleteWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - log.Debugf("Called deleteWikiPageFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { diff --git a/pkg/annotation/annotation.go b/pkg/annotation/annotation.go index 33b5ab5..c078419 100644 --- a/pkg/annotation/annotation.go +++ b/pkg/annotation/annotation.go @@ -1,21 +1,17 @@ -// Package annotation provides shared MCP tool annotation helpers. package annotation import "github.com/mark3labs/mcp-go/mcp" -// ReadOnly returns a ToolAnnotation for read-only tools. func ReadOnly(title string) mcp.ToolAnnotation { t := true return mcp.ToolAnnotation{Title: title, ReadOnlyHint: &t} } -// Write returns a ToolAnnotation for write tools. func Write(title string) mcp.ToolAnnotation { f := false return mcp.ToolAnnotation{Title: title, ReadOnlyHint: &f} } -// Destructive returns a ToolAnnotation for destructive write tools. func Destructive(title string) mcp.ToolAnnotation { f, t := false, true return mcp.ToolAnnotation{Title: title, ReadOnlyHint: &f, DestructiveHint: &t} diff --git a/pkg/gitea/gitea.go b/pkg/gitea/gitea.go index 32c6716..06c3a5c 100644 --- a/pkg/gitea/gitea.go +++ b/pkg/gitea/gitea.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "sync" mcpContext "gitea.com/gitea/gitea-mcp/pkg/context" "gitea.com/gitea/gitea-mcp/pkg/flag" @@ -13,21 +14,39 @@ import ( "code.gitea.io/sdk/gitea" ) +var ( + clientCache sync.Map // token -> *gitea.Client + sharedTransOnce sync.Once + sharedTrans *http.Transport +) + +func sharedTransport() *http.Transport { + sharedTransOnce.Do(func() { + sharedTrans = http.DefaultTransport.(*http.Transport).Clone() + if flag.Insecure { + sharedTrans.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec // user-requested insecure mode + } + }) + return sharedTrans +} + +// NewClient returns a cached *gitea.Client keyed by host+token. The SDK's per-client +// version cache and the shared transport let us reuse keep-alive connections +// and avoid the SDK's /api/v1/version preflight on every tool call. func NewClient(token string) (*gitea.Client, error) { - httpClient := &http.Client{ - Transport: http.DefaultTransport, - CheckRedirect: checkRedirect, + key := flag.Host + "\x00" + token + if v, ok := clientCache.Load(key); ok { + return v.(*gitea.Client), nil } + httpClient := &http.Client{ + Transport: sharedTransport(), + CheckRedirect: checkRedirect, + } opts := []gitea.ClientOption{ gitea.SetToken(token), + gitea.SetHTTPClient(httpClient), } - if flag.Insecure { - httpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{ - InsecureSkipVerify: true, - } - } - opts = append(opts, gitea.SetHTTPClient(httpClient)) if flag.Debug { opts = append(opts, gitea.SetDebugMode()) } @@ -35,10 +54,10 @@ func NewClient(token string) (*gitea.Client, error) { if err != nil { return nil, fmt.Errorf("create gitea client err: %w", err) } - - // Set user agent for the client client.SetUserAgent("gitea-mcp-server/" + flag.Version) - return client, nil + + actual, _ := clientCache.LoadOrStore(key, client) + return actual.(*gitea.Client), nil } // checkRedirect prevents Go from silently changing mutating requests (POST, PATCH, etc.) diff --git a/pkg/gitea/rest.go b/pkg/gitea/rest.go index af4a990..ba745da 100644 --- a/pkg/gitea/rest.go +++ b/pkg/gitea/rest.go @@ -3,7 +3,6 @@ package gitea import ( "bytes" "context" - "crypto/tls" "encoding/json" "errors" "fmt" @@ -11,12 +10,18 @@ import ( "net/http" "net/url" "strings" + "sync" "time" mcpContext "gitea.com/gitea/gitea-mcp/pkg/context" "gitea.com/gitea/gitea-mcp/pkg/flag" ) +const ( + httpClientTimeout = 60 * time.Second + errBodySnippetSize = 8192 +) + type HTTPError struct { StatusCode int Body string @@ -38,16 +43,20 @@ func tokenFromContext(ctx context.Context) string { return flag.Token } -func newRESTHTTPClient() *http.Client { - transport := http.DefaultTransport.(*http.Transport).Clone() - if flag.Insecure { - transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec // user-requested insecure mode - } - return &http.Client{ - Transport: transport, - Timeout: 60 * time.Second, - CheckRedirect: checkRedirect, - } +var ( + restClientOnce sync.Once + restClient *http.Client +) + +func restHTTPClient() *http.Client { + restClientOnce.Do(func() { + restClient = &http.Client{ + Transport: sharedTransport(), + Timeout: httpClientTimeout, + CheckRedirect: checkRedirect, + } + }) + return restClient } func buildAPIURL(path string, query url.Values) (string, error) { @@ -96,7 +105,7 @@ func DoJSON(ctx context.Context, method, path string, query url.Values, body, re req.Header.Set("Content-Type", "application/json") } - client := newRESTHTTPClient() + client := restHTTPClient() resp, err := client.Do(req) if err != nil { return 0, fmt.Errorf("do request: %w", err) @@ -104,7 +113,7 @@ func DoJSON(ctx context.Context, method, path string, query url.Values, body, re defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { - bodySnippet, _ := io.ReadAll(io.LimitReader(resp.Body, 8192)) + bodySnippet, _ := io.ReadAll(io.LimitReader(resp.Body, errBodySnippetSize)) return resp.StatusCode, &HTTPError{StatusCode: resp.StatusCode, Body: strings.TrimSpace(string(bodySnippet))} } @@ -151,7 +160,7 @@ func DoBytes(ctx context.Context, method, path string, query url.Values, body an req.Header.Set("Content-Type", "application/json") } - client := newRESTHTTPClient() + client := restHTTPClient() resp, err := client.Do(req) if err != nil { return nil, 0, fmt.Errorf("do request: %w", err) @@ -165,8 +174,8 @@ func DoBytes(ctx context.Context, method, path string, query url.Values, body an if resp.StatusCode < 200 || resp.StatusCode >= 300 { bodySnippet := respBytes - if len(bodySnippet) > 8192 { - bodySnippet = bodySnippet[:8192] + if len(bodySnippet) > errBodySnippetSize { + bodySnippet = bodySnippet[:errBodySnippetSize] } return nil, resp.StatusCode, &HTTPError{StatusCode: resp.StatusCode, Body: strings.TrimSpace(string(bodySnippet))} } diff --git a/pkg/log/log.go b/pkg/log/log.go index e4c82a9..24404f2 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -79,24 +79,6 @@ func SetDefault(logger *zap.Logger) { } } -func New() *Logger { - return &Logger{ - defaultLogger: Default(), - } -} - -type Logger struct { - defaultLogger *zap.Logger -} - -func (l *Logger) Infof(msg string, args ...any) { - l.defaultLogger.Sugar().Infof(msg, args...) -} - -func (l *Logger) Errorf(msg string, args ...any) { - l.defaultLogger.Sugar().Errorf(msg, args...) -} - func Debug(msg string, fields ...zap.Field) { Default().Debug(msg, fields...) } diff --git a/pkg/params/params.go b/pkg/params/params.go index 466f852..ae0bf8b 100644 --- a/pkg/params/params.go +++ b/pkg/params/params.go @@ -16,16 +16,15 @@ const ( PaginationDesc = "results per page" ) -// GetString extracts a required string parameter from MCP tool arguments. +// GetString extracts a required string parameter. Empty strings are treated as missing. func GetString(args map[string]any, key string) (string, error) { val, ok := args[key].(string) - if !ok { + if !ok || val == "" { 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 @@ -33,7 +32,6 @@ func GetOptionalString(args map[string]any, key, defaultVal string) string { 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 { @@ -52,13 +50,11 @@ func GetStringSlice(args map[string]any, key string) []string { return out } -// GetPagination extracts page and per_page 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, "per_page", defaultPageSize)) } -// ToInt64 converts a value to int64, accepting both float64 (JSON number) and -// string representations. Returns false if the value cannot be converted. +// ToInt64 accepts float64 (JSON number) and string representations. func ToInt64(val any) (int64, bool) { switch v := val.(type) { case float64: @@ -74,10 +70,8 @@ func ToInt64(val any) (int64, bool) { } } -// GetIndex extracts a required integer parameter from MCP tool arguments. -// It accepts both numeric (float64 from JSON) and string representations. -// This provides better UX for LLM callers that may naturally use strings -// for identifiers like issue/PR numbers. +// GetIndex extracts a required integer. Accepts numeric or string forms — LLM callers +// often pass identifiers like issue/PR numbers as strings. func GetIndex(args map[string]any, key string) (int64, error) { val, exists := args[key] if !exists { @@ -95,7 +89,6 @@ func GetIndex(args map[string]any, key string) (int64, error) { return 0, fmt.Errorf("%s must be a number or numeric string", key) } -// GetInt64Slice extracts a required int64 slice parameter from MCP tool arguments. func GetInt64Slice(args map[string]any, key string) ([]int64, error) { raw, ok := args[key].([]any) if !ok { @@ -112,7 +105,7 @@ func GetInt64Slice(args map[string]any, key string) ([]int64, error) { return out, nil } -// GetOptionalTime extracts an optional RFC3339 timestamp parameter, returning nil if missing or unparseable. +// GetOptionalTime parses RFC3339, returning nil if missing or unparseable. func GetOptionalTime(args map[string]any, key string) *time.Time { val, ok := args[key].(string) if !ok { @@ -124,9 +117,6 @@ func GetOptionalTime(args map[string]any, key string) *time.Time { return nil } -// GetOptionalInt extracts an optional integer parameter from MCP tool arguments. -// Returns defaultVal if the key is missing or the value cannot be parsed. -// Accepts both float64 (JSON number) and string representations. func GetOptionalInt(args map[string]any, key string, defaultVal int64) int64 { val, exists := args[key] if !exists { @@ -137,3 +127,30 @@ func GetOptionalInt(args map[string]any, key string, defaultVal int64) int64 { } return defaultVal } + +// GetOptionalBoolPtr is for SDK fields where nil/false/true are distinct (e.g. "no change" vs "set to false"). +func GetOptionalBoolPtr(args map[string]any, key string) *bool { + if v, ok := args[key].(bool); ok { + return &v + } + return nil +} + +// GetOptionalStringPtr returns nil when the key is missing OR the value is an empty string. +// Use this for create/fork-style fields where "" is meaningless (e.g. fork target name). +func GetOptionalStringPtr(args map[string]any, key string) *string { + if v, ok := args[key].(string); ok && v != "" { + return &v + } + return nil +} + +// GetPresentStringPtr returns &v whenever the key is present as a string, including "". +// Use this for PATCH-style fields where the SDK distinguishes "no change" (nil) from +// "set to empty" (&""), e.g. clearing an issue body or label description. +func GetPresentStringPtr(args map[string]any, key string) *string { + if v, ok := args[key].(string); ok { + return &v + } + return nil +} diff --git a/pkg/params/params_test.go b/pkg/params/params_test.go index e3fa444..55ebd02 100644 --- a/pkg/params/params_test.go +++ b/pkg/params/params_test.go @@ -73,6 +73,42 @@ func TestGetOptionalInt(t *testing.T) { } } +func TestGetOptionalStringPtr(t *testing.T) { + if p := GetOptionalStringPtr(map[string]any{}, "k"); p != nil { + t.Errorf("missing key: got %v, want nil", p) + } + if p := GetOptionalStringPtr(map[string]any{"k": ""}, "k"); p != nil { + t.Errorf("empty string: got %v, want nil", p) + } + if p := GetOptionalStringPtr(map[string]any{"k": 42}, "k"); p != nil { + t.Errorf("non-string: got %v, want nil", p) + } + if p := GetOptionalStringPtr(map[string]any{"k": nil}, "k"); p != nil { + t.Errorf("nil value (JSON null): got %v, want nil", p) + } + if p := GetOptionalStringPtr(map[string]any{"k": "x"}, "k"); p == nil || *p != "x" { + t.Errorf("non-empty: got %v, want &\"x\"", p) + } +} + +func TestGetPresentStringPtr(t *testing.T) { + if p := GetPresentStringPtr(map[string]any{}, "k"); p != nil { + t.Errorf("missing key: got %v, want nil", p) + } + if p := GetPresentStringPtr(map[string]any{"k": 42}, "k"); p != nil { + t.Errorf("non-string: got %v, want nil", p) + } + if p := GetPresentStringPtr(map[string]any{"k": nil}, "k"); p != nil { + t.Errorf("nil value (JSON null): got %v, want nil", p) + } + if p := GetPresentStringPtr(map[string]any{"k": ""}, "k"); p == nil || *p != "" { + t.Errorf("empty string: got %v, want &\"\"", p) + } + if p := GetPresentStringPtr(map[string]any{"k": "x"}, "k"); p == nil || *p != "x" { + t.Errorf("non-empty: got %v, want &\"x\"", p) + } +} + func TestGetIndex(t *testing.T) { tests := []struct { name string diff --git a/pkg/slim/slim.go b/pkg/slim/slim.go new file mode 100644 index 0000000..1a74de6 --- /dev/null +++ b/pkg/slim/slim.go @@ -0,0 +1,135 @@ +package slim + +import ( + "fmt" + "strings" + + 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 BodyWithAttachments(body string, atts []*gitea_sdk.Attachment) string { + links := make([]string, 0, len(atts)) + for _, a := range atts { + if a == nil || a.DownloadURL == "" { + continue + } + links = append(links, fmt.Sprintf("[%s](%s)", a.Name, a.DownloadURL)) + } + if len(links) == 0 { + return body + } + joined := strings.Join(links, "\n") + if body == "" { + return joined + } + return body + "\n\n" + joined +} + +func UserDetail(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 Repo(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 Repos(repos []*gitea_sdk.Repository) []map[string]any { + out := make([]map[string]any, 0, len(repos)) + for _, r := range repos { + out = append(out, Repo(r)) + } + return out +} + +func Label(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 Labels(labels []*gitea_sdk.Label) []map[string]any { + out := make([]map[string]any, 0, len(labels)) + for _, l := range labels { + out = append(out, Label(l)) + } + return out +} diff --git a/pkg/slim/slim_test.go b/pkg/slim/slim_test.go new file mode 100644 index 0000000..470bd4c --- /dev/null +++ b/pkg/slim/slim_test.go @@ -0,0 +1,110 @@ +package slim + +import ( + "testing" + + gitea_sdk "code.gitea.io/sdk/gitea" +) + +func TestUserDetail(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 := UserDetail(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 TestUserDetail_Nil(t *testing.T) { + if m := UserDetail(nil); m != nil { + t.Errorf("expected nil for nil user, got %v", m) + } +} + +func TestLabel(t *testing.T) { + l := &gitea_sdk.Label{ + ID: 1, + Name: "bug", + Color: "#d73a4a", + Description: "Something isn't working", + Exclusive: false, + } + + m := Label(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"]) + } +} + +func TestRepo(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", + Language: "Go", + Stars: 10, + Forks: 2, + Owner: &gitea_sdk.User{UserName: "org"}, + Topics: []string{"mcp", "gitea"}, + } + + m := Repo(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 TestBodyWithAttachments(t *testing.T) { + atts := []*gitea_sdk.Attachment{ + {Name: "shot.png", DownloadURL: "https://example/shot.png"}, + {Name: "log.txt", DownloadURL: "https://example/log.txt"}, + } + got := BodyWithAttachments("see attached", atts) + want := "see attached\n\n[shot.png](https://example/shot.png)\n[log.txt](https://example/log.txt)" + if got != want { + t.Errorf("got %q, want %q", got, want) + } + + if got := BodyWithAttachments("only body", nil); got != "only body" { + t.Errorf("nil attachments should return body unchanged, got %q", got) + } + if got := BodyWithAttachments("", atts); got != "[shot.png](https://example/shot.png)\n[log.txt](https://example/log.txt)" { + t.Errorf("empty body should drop separator, got %q", got) + } + skipped := []*gitea_sdk.Attachment{nil, {Name: "noop", DownloadURL: ""}} + if got := BodyWithAttachments("body", skipped); got != "body" { + t.Errorf("nil/empty-URL attachments should be skipped, got %q", got) + } +} diff --git a/pkg/to/to.go b/pkg/to/to.go index 682404e..b56dd23 100644 --- a/pkg/to/to.go +++ b/pkg/to/to.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "gitea.com/gitea/gitea-mcp/pkg/flag" "gitea.com/gitea/gitea-mcp/pkg/log" "github.com/mark3labs/mcp-go/mcp" @@ -14,11 +15,13 @@ func TextResult(v any) (*mcp.CallToolResult, error) { if err != nil { return nil, fmt.Errorf("marshal result err: %v", err) } - log.Debugf("Text Result: %s", string(resultBytes)) + if flag.Debug { + log.Debugf("Text Result: %s", string(resultBytes)) + } return mcp.NewToolResultText(string(resultBytes)), nil } func ErrorResult(err error) (*mcp.CallToolResult, error) { - log.Errorf(err.Error()) + log.Errorf("%s", err.Error()) return nil, err }