From 2e67d5ebf3897b3e9b09c5a1b10a6dcbd09ea0a4 Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 14 May 2026 06:24:51 +0000 Subject: [PATCH] Trim tool schemas, add param aliases, new PR methods (#191) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tool list size reduced by 26.6% (43,032 → 31,599 bytes on the `tools/list` JSON-RPC response). - Trim redundant tool/param descriptions; shared description constants for `owner`/`repo`/`page`/`per_page`. - Schemas now use github-mcp-server param names directly: `issue_number` (was `index` on issue tools), `pull_number` (was `index` on PR tools), `path` (was `filePath`), `query` (was `keyword` on user/repo search), `per_page` (was `perPage`). - New PR read methods `get_files` and `get_status`; new PR write method `update_branch` (update PR branch from base). - `list_org_repos` now uses `per_page` (was `pageSize`). - `milestone_write` accepts `update` and `edit`. - `create_branch` `old_branch` is optional; Gitea defaults to the repo default branch. - Fix `list_commits` handler to honour optional `page`/`per_page` schema (was erroring out when callers omitted them). --- This PR was written with the help of Claude Opus 4.7 Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/191 Reviewed-by: Lunny Xiao Co-authored-by: silverwind Co-committed-by: silverwind --- operation/actions/config.go | 34 ++-- operation/actions/runs.go | 42 ++--- operation/issue/issue.go | 77 ++++---- operation/issue/issue_test.go | 4 +- operation/label/label.go | 38 ++-- operation/milestone/milestone.go | 36 ++-- operation/notification/notification.go | 34 ++-- operation/packages/packages.go | 30 ++-- operation/packages/packages_test.go | 8 +- operation/pull/pull.go | 237 +++++++++++++++++-------- operation/pull/pull_test.go | 50 +++--- operation/repo/branch.go | 25 ++- operation/repo/commit.go | 33 ++-- operation/repo/file.go | 56 +++--- operation/repo/release.go | 52 +++--- operation/repo/repo.go | 61 +++---- operation/repo/tag.go | 36 ++-- operation/repo/tree.go | 13 +- operation/search/search.go | 59 +++--- operation/search/search_test.go | 4 +- operation/timetracking/timetracking.go | 40 ++--- operation/user/user.go | 10 +- operation/version/version.go | 1 - operation/wiki/wiki.go | 24 +-- pkg/annotation/annotation.go | 16 +- pkg/params/params.go | 14 +- pkg/params/params_test.go | 11 ++ 27 files changed, 555 insertions(+), 490 deletions(-) diff --git a/operation/actions/config.go b/operation/actions/config.go index d707f85..5b925b6 100644 --- a/operation/actions/config.go +++ b/operation/actions/config.go @@ -48,29 +48,29 @@ func toSecretMetas(secrets []*gitea_sdk.Secret) []secretMeta { var ( ActionsConfigReadTool = mcp.NewTool( ActionsConfigReadToolName, - mcp.WithDescription("Read Actions secrets and variables configuration."), + mcp.WithDescription("Read Actions secrets and variables."), mcp.WithToolAnnotation(annotation.ReadOnly("Read Actions secrets and variables")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list_repo_secrets", "list_org_secrets", "list_repo_variables", "get_repo_variable", "list_org_variables", "get_org_variable")), - mcp.WithString("owner", mcp.Description("repository owner (required for repo methods)")), - mcp.WithString("repo", mcp.Description("repository name (required for repo methods)")), - mcp.WithString("org", mcp.Description("organization name (required for org methods)")), - mcp.WithString("name", mcp.Description("variable name (required for get methods)")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30), mcp.Min(1)), + mcp.WithString("method", mcp.Required(), mcp.Enum("list_repo_secrets", "list_org_secrets", "list_repo_variables", "get_repo_variable", "list_org_variables", "get_org_variable")), + mcp.WithString("owner", mcp.Description("for repo methods")), + mcp.WithString("repo", mcp.Description("for repo methods")), + mcp.WithString("org", mcp.Description("for org methods")), + mcp.WithString("name", mcp.Description("for get methods")), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)), ) ActionsConfigWriteTool = mcp.NewTool( ActionsConfigWriteToolName, - mcp.WithDescription("Manage Actions secrets and variables: create, update, or delete."), + mcp.WithDescription("Write Actions secrets and variables: upsert, create, update, delete."), mcp.WithToolAnnotation(annotation.Destructive("Manage Actions secrets and variables")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("upsert_repo_secret", "delete_repo_secret", "upsert_org_secret", "delete_org_secret", "create_repo_variable", "update_repo_variable", "delete_repo_variable", "create_org_variable", "update_org_variable", "delete_org_variable")), - mcp.WithString("owner", mcp.Description("repository owner (required for repo methods)")), - mcp.WithString("repo", mcp.Description("repository name (required for repo methods)")), - mcp.WithString("org", mcp.Description("organization name (required for org methods)")), - mcp.WithString("name", mcp.Description("secret or variable name (required for most methods)")), - mcp.WithString("data", mcp.Description("secret value (required for upsert secret methods)")), - mcp.WithString("value", mcp.Description("variable value (required for create/update variable methods)")), - mcp.WithString("description", mcp.Description("description for secret or variable")), + mcp.WithString("method", mcp.Required(), mcp.Enum("upsert_repo_secret", "delete_repo_secret", "upsert_org_secret", "delete_org_secret", "create_repo_variable", "update_repo_variable", "delete_repo_variable", "create_org_variable", "update_org_variable", "delete_org_variable")), + mcp.WithString("owner", mcp.Description("for repo methods")), + mcp.WithString("repo", mcp.Description("for repo methods")), + mcp.WithString("org", mcp.Description("for org methods")), + mcp.WithString("name", mcp.Description("secret or variable name")), + mcp.WithString("data", mcp.Description("secret value (upsert)")), + mcp.WithString("value", mcp.Description("variable value")), + mcp.WithString("description"), ) ) diff --git a/operation/actions/runs.go b/operation/actions/runs.go index 6e9f9f8..6c4f800 100644 --- a/operation/actions/runs.go +++ b/operation/actions/runs.go @@ -28,33 +28,33 @@ const ( var ( ActionsRunReadTool = mcp.NewTool( ActionsRunReadToolName, - mcp.WithDescription("Read Actions workflow, run, and job data. Use method 'list_workflows'/'get_workflow' for workflows, 'list_runs'/'get_run' for runs, 'list_jobs'/'list_run_jobs' for jobs, 'get_job_log_preview'/'download_job_log' for logs."), + mcp.WithDescription("Read Actions workflows, runs, jobs, and logs."), mcp.WithToolAnnotation(annotation.ReadOnly("Read Actions workflow, run, and job data")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list_workflows", "get_workflow", "list_runs", "get_run", "list_jobs", "list_run_jobs", "get_job_log_preview", "download_job_log")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("workflow_id", mcp.Description("workflow ID or filename (required for 'get_workflow')")), - mcp.WithNumber("run_id", mcp.Description("run ID (required for 'get_run', 'list_run_jobs')")), - mcp.WithNumber("job_id", mcp.Description("job ID (required for 'get_job_log_preview', 'download_job_log')")), - mcp.WithString("status", mcp.Description("optional status filter (for 'list_runs', 'list_jobs')")), - mcp.WithNumber("tail_lines", mcp.Description("number of lines from end of log (for 'get_job_log_preview')"), mcp.DefaultNumber(200), mcp.Min(1)), - mcp.WithNumber("max_bytes", mcp.Description("max bytes to return (for 'get_job_log_preview')"), mcp.DefaultNumber(65536), mcp.Min(1024)), - mcp.WithString("output_path", mcp.Description("output file path (for 'download_job_log')")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30), mcp.Min(1)), + mcp.WithString("method", mcp.Required(), mcp.Enum("list_workflows", "get_workflow", "list_runs", "get_run", "list_jobs", "list_run_jobs", "get_job_log_preview", "download_job_log")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("workflow_id", mcp.Description("ID or filename (for 'get_workflow')")), + mcp.WithNumber("run_id", mcp.Description("for 'get_run'/'list_run_jobs'")), + mcp.WithNumber("job_id", mcp.Description("for log methods")), + mcp.WithString("status", mcp.Description("filter for 'list_runs'/'list_jobs'")), + mcp.WithNumber("tail_lines", mcp.Description("log tail lines"), mcp.DefaultNumber(200), mcp.Min(1)), + mcp.WithNumber("max_bytes", mcp.Description("max log bytes"), mcp.DefaultNumber(65536), mcp.Min(1024)), + mcp.WithString("output_path", mcp.Description("for 'download_job_log'")), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)), ) ActionsRunWriteTool = mcp.NewTool( ActionsRunWriteToolName, - mcp.WithDescription("Trigger, cancel, or rerun Actions workflows."), + mcp.WithDescription("Write Actions runs: dispatch, cancel, rerun."), mcp.WithToolAnnotation(annotation.Write("Trigger, cancel, or rerun Actions workflows")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("dispatch_workflow", "cancel_run", "rerun_run")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("workflow_id", mcp.Description("workflow ID or filename (required for 'dispatch_workflow')")), - mcp.WithString("ref", mcp.Description("git ref branch or tag (required for 'dispatch_workflow')")), - mcp.WithObject("inputs", mcp.Description("workflow inputs object (for 'dispatch_workflow')")), - mcp.WithNumber("run_id", mcp.Description("run ID (required for 'cancel_run', 'rerun_run')")), + mcp.WithString("method", mcp.Required(), mcp.Enum("dispatch_workflow", "cancel_run", "rerun_run")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("workflow_id", mcp.Description("ID or filename (for 'dispatch_workflow')")), + mcp.WithString("ref", mcp.Description("branch or tag (for 'dispatch_workflow')")), + mcp.WithObject("inputs", mcp.Description("for 'dispatch_workflow'")), + mcp.WithNumber("run_id", mcp.Description("for 'cancel_run'/'rerun_run'")), ) ) diff --git a/operation/issue/issue.go b/operation/issue/issue.go index 6e5c38b..afef03b 100644 --- a/operation/issue/issue.go +++ b/operation/issue/issue.go @@ -40,47 +40,46 @@ const ( var ( ListRepoIssuesTool = mcp.NewTool( ListRepoIssuesToolName, - mcp.WithDescription("List repository issues"), mcp.WithToolAnnotation(annotation.ReadOnly("List repository issues")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("state", mcp.Description("issue state"), mcp.DefaultString("all")), - mcp.WithArray("labels", mcp.Description("filter by label names"), mcp.Items(map[string]any{"type": "string"})), - mcp.WithString("since", mcp.Description("filter issues updated after this ISO 8601 timestamp")), - mcp.WithString("before", mcp.Description("filter issues updated before this ISO 8601 timestamp")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30)), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("state", mcp.DefaultString("all")), + mcp.WithArray("labels", mcp.Description("label name filter"), mcp.Items(map[string]any{"type": "string"})), + mcp.WithString("since", mcp.Description("updated after ISO 8601")), + mcp.WithString("before", mcp.Description("updated before ISO 8601")), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)), ) IssueReadTool = mcp.NewTool( IssueReadToolName, - mcp.WithDescription("Get information about a specific issue. Use method 'get' for issue details, 'get_comments' for issue comments, 'get_labels' for issue labels."), + mcp.WithDescription("Read issue: details, comments, or labels."), mcp.WithToolAnnotation(annotation.ReadOnly("Read issue details")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("get", "get_comments", "get_labels")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")), + mcp.WithString("method", mcp.Required(), mcp.Enum("get", "get_comments", "get_labels")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithNumber("issue_number", mcp.Required()), ) IssueWriteTool = mcp.NewTool( IssueWriteToolName, - mcp.WithDescription("Create or update issues and comments, manage labels. Use method 'create' to create an issue, 'update' to edit, 'add_comment'/'edit_comment' for comments, 'add_labels'/'remove_label'/'replace_labels'/'clear_labels' for label management."), + mcp.WithDescription("Write issues: create, update, manage comments and labels."), mcp.WithToolAnnotation(annotation.Write("Create or update issues, comments, and labels")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("create", "update", "add_comment", "edit_comment", "add_labels", "remove_label", "replace_labels", "clear_labels")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithNumber("index", mcp.Description("issue index (required for all methods except 'create')")), - mcp.WithString("title", mcp.Description("issue title (required for 'create')")), - mcp.WithString("body", mcp.Description("issue/comment body (required for 'create', 'add_comment', 'edit_comment')")), - mcp.WithArray("assignees", mcp.Description("usernames to assign (for 'create', 'update')"), mcp.Items(map[string]any{"type": "string"})), - mcp.WithNumber("milestone", mcp.Description("milestone number (for 'create', 'update')")), - mcp.WithString("state", mcp.Description("issue state, one of open, closed, all (for 'update')")), - mcp.WithNumber("commentID", mcp.Description("id of issue comment (required for 'edit_comment')")), - mcp.WithArray("labels", mcp.Description("array of label IDs (for 'create', 'add_labels', 'replace_labels')"), mcp.Items(map[string]any{"type": "number"})), - mcp.WithNumber("label_id", mcp.Description("label ID to remove (required for 'remove_label')")), - mcp.WithString("ref", mcp.Description("branch name to associate with the issue (for 'create', 'update')")), - mcp.WithString("deadline", mcp.Description("due date in ISO 8601 format (for 'create', 'update')")), - mcp.WithBoolean("remove_deadline", mcp.Description("unset due date (for 'update')")), + mcp.WithString("method", mcp.Required(), mcp.Enum("create", "update", "add_comment", "edit_comment", "add_labels", "remove_label", "replace_labels", "clear_labels")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithNumber("issue_number", mcp.Description("required except for 'create'")), + mcp.WithString("title", mcp.Description("required for 'create'")), + mcp.WithString("body", mcp.Description("required for 'create'/'add_comment'/'edit_comment'")), + mcp.WithArray("assignees", mcp.Items(map[string]any{"type": "string"})), + mcp.WithNumber("milestone"), + mcp.WithString("state", mcp.Enum("open", "closed", "all")), + mcp.WithNumber("commentID", mcp.Description("for 'edit_comment'")), + mcp.WithArray("labels", mcp.Description("label IDs"), mcp.Items(map[string]any{"type": "number"})), + mcp.WithNumber("label_id", mcp.Description("for 'remove_label'")), + mcp.WithString("ref", mcp.Description("branch to associate")), + mcp.WithString("deadline", mcp.Description("ISO 8601")), + mcp.WithBoolean("remove_deadline"), ) ) @@ -155,7 +154,7 @@ func getIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } @@ -267,7 +266,7 @@ func createIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } @@ -300,7 +299,7 @@ func editIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } @@ -388,7 +387,7 @@ func getIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*m if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } @@ -416,7 +415,7 @@ func getIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } @@ -444,7 +443,7 @@ func addIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } @@ -474,7 +473,7 @@ func replaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } @@ -504,7 +503,7 @@ func clearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } @@ -530,7 +529,7 @@ func removeIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } diff --git a/operation/issue/issue_test.go b/operation/issue/issue_test.go index 1f59441..26b0827 100644 --- a/operation/issue/issue_test.go +++ b/operation/issue/issue_test.go @@ -201,7 +201,7 @@ func Test_getIssueByIndexFn_includesAttachments(t *testing.T) { defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }() req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{ - "owner": owner, "repo": repo, "index": float64(42), + "owner": owner, "repo": repo, "issue_number": float64(42), }}} res, err := getIssueByIndexFn(context.Background(), req) if err != nil { @@ -250,7 +250,7 @@ func Test_getIssueCommentsByIndexFn_includesAttachments(t *testing.T) { defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }() req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{ - "owner": owner, "repo": repo, "index": float64(7), + "owner": owner, "repo": repo, "issue_number": float64(7), }}} res, err := getIssueCommentsByIndexFn(context.Background(), req) if err != nil { diff --git a/operation/label/label.go b/operation/label/label.go index ebae338..2a55e5e 100644 --- a/operation/label/label.go +++ b/operation/label/label.go @@ -26,31 +26,31 @@ const ( var ( LabelReadTool = mcp.NewTool( LabelReadToolName, - mcp.WithDescription("Read label information. Use method 'list_repo_labels' to list repository labels, 'get_repo_label' to get a specific repo label, 'list_org_labels' to list organization labels."), + mcp.WithDescription("Read repo or org labels."), mcp.WithToolAnnotation(annotation.ReadOnly("Read labels")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list_repo_labels", "get_repo_label", "list_org_labels")), - mcp.WithString("owner", mcp.Description("repository owner (required for repo methods)")), - mcp.WithString("repo", mcp.Description("repository name (required for repo methods)")), - mcp.WithString("org", mcp.Description("organization name (required for 'list_org')")), - mcp.WithNumber("id", mcp.Description("label ID (required for 'get_repo')")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30)), + mcp.WithString("method", mcp.Required(), mcp.Enum("list_repo_labels", "get_repo_label", "list_org_labels")), + mcp.WithString("owner", mcp.Description("for repo methods")), + mcp.WithString("repo", mcp.Description("for repo methods")), + mcp.WithString("org", mcp.Description("for org methods")), + mcp.WithNumber("id", mcp.Description("label ID (for 'get_repo_label')")), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)), ) LabelWriteTool = mcp.NewTool( LabelWriteToolName, - mcp.WithDescription("Create, edit, or delete labels for repositories or organizations."), + mcp.WithDescription("Write labels (repo or org): create, edit, delete."), mcp.WithToolAnnotation(annotation.Destructive("Create, update, or delete labels")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("create_repo_label", "edit_repo_label", "delete_repo_label", "create_org_label", "edit_org_label", "delete_org_label")), - mcp.WithString("owner", mcp.Description("repository owner (required for repo methods)")), - mcp.WithString("repo", mcp.Description("repository name (required for repo methods)")), - mcp.WithString("org", mcp.Description("organization name (required for org methods)")), - mcp.WithNumber("id", mcp.Description("label ID (required for edit/delete methods)")), - mcp.WithString("name", mcp.Description("label name (required for create, optional for edit)")), - mcp.WithString("color", mcp.Description("label color hex code e.g. #RRGGBB (required for create, optional for edit)")), - mcp.WithString("description", mcp.Description("label description")), - mcp.WithBoolean("exclusive", mcp.Description("whether the label is exclusive (org labels only)")), - mcp.WithBoolean("is_archived", mcp.Description("whether the label is archived (for create/edit repo label methods)")), + mcp.WithString("method", mcp.Required(), mcp.Enum("create_repo_label", "edit_repo_label", "delete_repo_label", "create_org_label", "edit_org_label", "delete_org_label")), + mcp.WithString("owner", mcp.Description("for repo methods")), + mcp.WithString("repo", mcp.Description("for repo methods")), + mcp.WithString("org", mcp.Description("for org methods")), + mcp.WithNumber("id", mcp.Description("for edit/delete")), + mcp.WithString("name", mcp.Description("required for create")), + mcp.WithString("color", mcp.Description("hex (#RRGGBB); required for create")), + mcp.WithString("description"), + mcp.WithBoolean("exclusive", mcp.Description("exclusive (org only)")), + mcp.WithBoolean("is_archived", mcp.Description("archived (repo only)")), ) ) diff --git a/operation/milestone/milestone.go b/operation/milestone/milestone.go index 140059a..6d81d02 100644 --- a/operation/milestone/milestone.go +++ b/operation/milestone/milestone.go @@ -26,30 +26,30 @@ const ( var ( MilestoneReadTool = mcp.NewTool( MilestoneReadToolName, - mcp.WithDescription("Read milestone information. Use method 'get' to get a specific milestone, 'list' to list milestones."), + mcp.WithDescription("Read milestones: get one or list."), mcp.WithToolAnnotation(annotation.ReadOnly("Read milestones")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("get", "list")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithNumber("id", mcp.Description("milestone id (required for 'get')")), - mcp.WithString("state", mcp.Description("milestone state (for 'list')"), mcp.DefaultString("all")), - mcp.WithString("name", mcp.Description("milestone name filter (for 'list')")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30)), + mcp.WithString("method", mcp.Required(), mcp.Enum("get", "list")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithNumber("id", mcp.Description("for 'get'")), + mcp.WithString("state", mcp.DefaultString("all")), + mcp.WithString("name", mcp.Description("name filter (for 'list')")), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)), ) MilestoneWriteTool = mcp.NewTool( MilestoneWriteToolName, - mcp.WithDescription("Create, edit, or delete milestones."), + mcp.WithDescription("Write milestones: create, update, delete."), mcp.WithToolAnnotation(annotation.Destructive("Create, update, or delete milestones")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("create", "edit", "delete")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithNumber("id", mcp.Description("milestone id (required for 'edit', 'delete')")), - mcp.WithString("title", mcp.Description("milestone title (required for 'create')")), - mcp.WithString("description", mcp.Description("milestone description")), + mcp.WithString("method", mcp.Required(), mcp.Enum("create", "update", "edit", "delete")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithNumber("id", mcp.Description("for 'update'/'delete'")), + mcp.WithString("title", mcp.Description("for 'create'")), + mcp.WithString("description"), mcp.WithString("due_on", mcp.Description("due date")), - mcp.WithString("state", mcp.Description("milestone state, one of open, closed (for 'edit')")), + mcp.WithString("state", mcp.Enum("open", "closed")), ) ) @@ -87,6 +87,8 @@ func milestoneWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo switch method { case "create": return createMilestoneFn(ctx, req) + case "update": + return editMilestoneFn(ctx, req) case "edit": return editMilestoneFn(ctx, req) case "delete": diff --git a/operation/notification/notification.go b/operation/notification/notification.go index ef0383d..7cedd14 100644 --- a/operation/notification/notification.go +++ b/operation/notification/notification.go @@ -27,29 +27,29 @@ const ( var ( NotificationReadTool = mcp.NewTool( NotificationReadToolName, - mcp.WithDescription("Get notifications. Use method 'list' to list notifications (optionally scoped to a repo), 'get' to get a single notification thread by ID."), + mcp.WithDescription("Read notifications: list (optionally scoped to a repo) or get a thread by ID."), mcp.WithToolAnnotation(annotation.ReadOnly("Read notifications")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list", "get")), - mcp.WithString("owner", mcp.Description("repository owner (for 'list' to scope to a repo)")), - mcp.WithString("repo", mcp.Description("repository name (for 'list' to scope to a repo)")), - mcp.WithNumber("id", mcp.Description("notification thread ID (required for 'get')")), - mcp.WithString("status", mcp.Description("filter by status (for 'list')"), mcp.Enum("unread", "read", "pinned")), - mcp.WithString("subject_type", mcp.Description("filter by subject type (for 'list')"), mcp.Enum("Issue", "Pull", "Commit", "Repository")), - mcp.WithString("since", mcp.Description("filter notifications updated after this ISO 8601 timestamp (for 'list')")), - mcp.WithString("before", mcp.Description("filter notifications updated before this ISO 8601 timestamp (for 'list')")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30)), + mcp.WithString("method", mcp.Required(), mcp.Enum("list", "get")), + mcp.WithString("owner", mcp.Description("scope 'list' to a repo")), + mcp.WithString("repo", mcp.Description("scope 'list' to a repo")), + mcp.WithNumber("id", mcp.Description("thread ID (for 'get')")), + mcp.WithString("status", mcp.Enum("unread", "read", "pinned")), + mcp.WithString("subject_type", mcp.Enum("Issue", "Pull", "Commit", "Repository")), + mcp.WithString("since", mcp.Description("updated after ISO 8601")), + mcp.WithString("before", mcp.Description("updated before ISO 8601")), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)), ) NotificationWriteTool = mcp.NewTool( NotificationWriteToolName, - mcp.WithDescription("Manage notifications. Use method 'mark_read' to mark a single notification as read, 'mark_all_read' to mark all notifications as read (optionally scoped to a repo)."), + mcp.WithDescription("Mark a notification or all notifications as read."), mcp.WithToolAnnotation(annotation.Write("Manage notifications")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("mark_read", "mark_all_read")), - mcp.WithNumber("id", mcp.Description("notification thread ID (required for 'mark_read')")), - mcp.WithString("owner", mcp.Description("repository owner (for 'mark_all_read' to scope to a repo)")), - mcp.WithString("repo", mcp.Description("repository name (for 'mark_all_read' to scope to a repo)")), - mcp.WithString("last_read_at", mcp.Description("ISO 8601 timestamp, marks notifications before this time as read (for 'mark_all_read', defaults to now)")), + mcp.WithString("method", mcp.Required(), mcp.Enum("mark_read", "mark_all_read")), + mcp.WithNumber("id", mcp.Description("thread ID (for 'mark_read')")), + mcp.WithString("owner", mcp.Description("scope 'mark_all_read' to a repo")), + mcp.WithString("repo", mcp.Description("scope 'mark_all_read' to a repo")), + mcp.WithString("last_read_at", mcp.Description("ISO 8601; defaults to now")), ) ) diff --git a/operation/packages/packages.go b/operation/packages/packages.go index 37b2c66..1e707cf 100644 --- a/operation/packages/packages.go +++ b/operation/packages/packages.go @@ -29,26 +29,26 @@ var ( PackageReadTool = mcp.NewTool( PackageReadToolName, mcp.WithToolAnnotation(annotation.ReadOnly("Read package registry")), - mcp.WithDescription("Read package registry information. Use method 'list' to list all packages of an owner (returns one entry per version, use 'q' or 'type' to filter), 'list_versions' to list versions of a specific package, 'get' to get details of a specific package version."), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list", "list_versions", "get")), - mcp.WithString("owner", mcp.Required(), mcp.Description("package owner (user or org)")), - mcp.WithString("type", mcp.Description("package type, e.g. container, npm, maven, pypi, cargo, generic (optional filter for 'list', required for 'list_versions' and 'get')")), - mcp.WithString("name", mcp.Description("package name, slashes encoded automatically e.g. 'my-repo/my-image' (required for 'list_versions' and 'get')")), - mcp.WithString("version", mcp.Description("package version (required for 'get')")), - mcp.WithString("q", mcp.Description("search query (for 'list')")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30), mcp.Min(1)), + mcp.WithDescription("Read package registry: list packages (one entry per version, filter via 'q'/'type'), list versions, or get a version."), + mcp.WithString("method", mcp.Required(), mcp.Enum("list", "list_versions", "get")), + mcp.WithString("owner", mcp.Required(), mcp.Description("user or org")), + mcp.WithString("type", mcp.Description("container/npm/maven/pypi/cargo/generic; required except 'list'")), + mcp.WithString("name", mcp.Description("slashes auto-encoded; required except 'list'")), + mcp.WithString("version", mcp.Description("for 'get'")), + mcp.WithString("q", mcp.Description("search query")), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)), ) PackageWriteTool = mcp.NewTool( PackageWriteToolName, mcp.WithToolAnnotation(annotation.Destructive("Delete a package version")), - mcp.WithDescription("Modify the package registry. Use method 'delete' to delete a specific package version. This is destructive and irreversible."), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("delete")), - mcp.WithString("owner", mcp.Required(), mcp.Description("package owner (user or org)")), - mcp.WithString("type", mcp.Required(), mcp.Description("package type, e.g. container, npm, maven, pypi, cargo, generic")), - mcp.WithString("name", mcp.Required(), mcp.Description("package name, slashes encoded automatically e.g. 'my-repo/my-image'")), - mcp.WithString("version", mcp.Required(), mcp.Description("package version")), + mcp.WithDescription("Delete a package version (irreversible)."), + mcp.WithString("method", mcp.Required(), mcp.Enum("delete")), + mcp.WithString("owner", mcp.Required(), mcp.Description("user or org")), + mcp.WithString("type", mcp.Required(), mcp.Description("container/npm/maven/pypi/cargo/generic")), + mcp.WithString("name", mcp.Required(), mcp.Description("slashes auto-encoded")), + mcp.WithString("version", mcp.Required()), ) ) diff --git a/operation/packages/packages_test.go b/operation/packages/packages_test.go index 200ef65..0027db8 100644 --- a/operation/packages/packages_test.go +++ b/operation/packages/packages_test.go @@ -94,10 +94,10 @@ func TestPackageReadList(t *testing.T) { t.Run("with pagination", func(t *testing.T) { req := mcp.CallToolRequest{} req.Params.Arguments = map[string]any{ - "method": "list", - "owner": "test-org", - "page": float64(2), - "perPage": float64(10), + "method": "list", + "owner": "test-org", + "page": float64(2), + "per_page": float64(10), } _, err := packageReadFn(ctx, req) diff --git a/operation/pull/pull.go b/operation/pull/pull.go index 949e376..d784c01 100644 --- a/operation/pull/pull.go +++ b/operation/pull/pull.go @@ -30,82 +30,81 @@ const ( var ( ListRepoPullRequestsTool = mcp.NewTool( ListRepoPullRequestsToolName, - mcp.WithDescription("List repository pull requests"), mcp.WithToolAnnotation(annotation.ReadOnly("List pull requests")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("state", mcp.Description("state"), mcp.Enum("open", "closed", "all"), mcp.DefaultString("all")), - mcp.WithString("sort", mcp.Description("sort"), mcp.Enum("oldest", "recentupdate", "leastupdate", "mostcomment", "leastcomment", "priority"), mcp.DefaultString("recentupdate")), - mcp.WithNumber("milestone", mcp.Description("milestone")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30)), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("state", mcp.Enum("open", "closed", "all"), mcp.DefaultString("all")), + mcp.WithString("sort", mcp.Enum("oldest", "recentupdate", "leastupdate", "mostcomment", "leastcomment", "priority"), mcp.DefaultString("recentupdate")), + mcp.WithNumber("milestone"), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)), ) PullRequestReadTool = mcp.NewTool( PullRequestReadToolName, - mcp.WithDescription("Get pull request information. Use method 'get' for PR details, 'get_diff' for diff, 'get_reviews'/'get_review'/'get_review_comments' for review data."), + mcp.WithDescription("Read pull request: details, diff, changed files, head commit status, reviews."), mcp.WithToolAnnotation(annotation.ReadOnly("Read pull request details")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("get", "get_diff", "get_reviews", "get_review", "get_review_comments")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithNumber("index", mcp.Required(), mcp.Description("pull request index")), - mcp.WithNumber("review_id", mcp.Description("review ID (required for 'get_review', 'get_review_comments')")), - mcp.WithBoolean("binary", mcp.Description("whether to include binary file changes (for 'get_diff')")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30)), + mcp.WithString("method", mcp.Required(), mcp.Enum("get", "get_diff", "get_files", "get_status", "get_reviews", "get_review", "get_review_comments")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithNumber("pull_number", mcp.Required()), + mcp.WithNumber("review_id", mcp.Description("for 'get_review'/'get_review_comments'")), + mcp.WithBoolean("binary", mcp.Description("include binary diff")), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)), ) PullRequestWriteTool = mcp.NewTool( PullRequestWriteToolName, - mcp.WithDescription("Create, update, close, reopen, or merge pull requests, manage reviewers."), + mcp.WithDescription("Write pull requests: create, update, close, reopen, merge, update branch from base, manage reviewers."), mcp.WithToolAnnotation(annotation.Write("Create, update, close, reopen, or merge pull requests")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("create", "update", "close", "reopen", "merge", "add_reviewers", "remove_reviewers")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithNumber("index", mcp.Description("pull request index (required for all methods except 'create')")), - mcp.WithString("title", mcp.Description("PR title (required for 'create', optional for 'update', 'merge')")), - mcp.WithString("body", mcp.Description("PR body (required for 'create', optional for 'update')")), - mcp.WithString("head", mcp.Description("PR head branch (required for 'create')")), - mcp.WithString("base", mcp.Description("PR base branch (required for 'create', optional for 'update')")), - mcp.WithString("assignee", mcp.Description("username to assign (for 'update')")), - mcp.WithArray("assignees", mcp.Description("usernames to assign (for 'update')"), mcp.Items(map[string]any{"type": "string"})), - mcp.WithNumber("milestone", mcp.Description("milestone number (for 'update')")), - mcp.WithString("state", mcp.Description("PR state (for 'update')"), mcp.Enum("open", "closed")), - mcp.WithBoolean("allow_maintainer_edit", mcp.Description("allow maintainer to edit (for 'update')")), - mcp.WithArray("labels", mcp.Description("array of label IDs (for 'create', 'update')"), mcp.Items(map[string]any{"type": "number"})), - mcp.WithString("deadline", mcp.Description("due date in ISO 8601 format (for 'create', 'update')")), - mcp.WithBoolean("remove_deadline", mcp.Description("unset due date (for 'update')")), - mcp.WithString("merge_style", mcp.Description("merge style (for 'merge')"), mcp.Enum("merge", "rebase", "rebase-merge", "squash", "fast-forward-only"), mcp.DefaultString("merge")), - mcp.WithString("message", mcp.Description("merge commit message (for 'merge') or dismissal reason")), - mcp.WithBoolean("delete_branch", mcp.Description("delete branch after merge (for 'merge')")), - mcp.WithBoolean("force_merge", mcp.Description("force merge even if checks are not passing (for 'merge')")), - mcp.WithBoolean("merge_when_checks_succeed", mcp.Description("auto-merge when checks succeed (for 'merge')")), - mcp.WithString("head_commit_id", mcp.Description("expected head commit SHA for merge conflict detection (for 'merge')")), - mcp.WithArray("reviewers", mcp.Description("reviewer usernames (for 'add_reviewers', 'remove_reviewers')"), mcp.Items(map[string]any{"type": "string"})), - mcp.WithArray("team_reviewers", mcp.Description("team reviewer names (for 'add_reviewers', 'remove_reviewers')"), mcp.Items(map[string]any{"type": "string"})), - mcp.WithBoolean("draft", mcp.Description("mark PR as draft (for 'create', 'update'). Gitea uses a 'WIP: ' title prefix for drafts.")), + mcp.WithString("method", mcp.Required(), mcp.Enum("create", "update", "close", "reopen", "merge", "update_branch", "add_reviewers", "remove_reviewers")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithNumber("pull_number", mcp.Description("required except for 'create'")), + mcp.WithString("title", mcp.Description("required for 'create'; optional for 'update'/'merge'")), + mcp.WithString("body", mcp.Description("required for 'create'; optional for 'update'")), + mcp.WithString("head", mcp.Description("head branch (required for 'create')")), + mcp.WithString("base", mcp.Description("base branch (required for 'create')")), + mcp.WithString("assignee", mcp.Description("for 'update'")), + mcp.WithArray("assignees", mcp.Description("for 'update'"), mcp.Items(map[string]any{"type": "string"})), + mcp.WithNumber("milestone", mcp.Description("for 'update'")), + mcp.WithString("state", mcp.Description("for 'update'"), mcp.Enum("open", "closed")), + mcp.WithBoolean("allow_maintainer_edit", mcp.Description("for 'update'")), + mcp.WithArray("labels", mcp.Description("label IDs"), mcp.Items(map[string]any{"type": "number"})), + mcp.WithString("deadline", mcp.Description("ISO 8601")), + mcp.WithBoolean("remove_deadline", mcp.Description("for 'update'")), + mcp.WithString("merge_style", mcp.Description("for 'merge'"), mcp.Enum("merge", "rebase", "rebase-merge", "squash", "fast-forward-only"), mcp.DefaultString("merge")), + mcp.WithString("message", mcp.Description("merge commit message or dismissal reason")), + mcp.WithBoolean("delete_branch", mcp.Description("for 'merge'")), + mcp.WithBoolean("force_merge", mcp.Description("merge even if checks fail")), + mcp.WithBoolean("merge_when_checks_succeed", mcp.Description("for 'merge'")), + mcp.WithString("head_commit_id", mcp.Description("expected head SHA for conflict detection")), + mcp.WithArray("reviewers", mcp.Description("for 'add_reviewers'/'remove_reviewers'"), mcp.Items(map[string]any{"type": "string"})), + mcp.WithArray("team_reviewers", mcp.Description("for 'add_reviewers'/'remove_reviewers'"), mcp.Items(map[string]any{"type": "string"})), + mcp.WithBoolean("draft", mcp.Description("uses 'WIP: ' title prefix")), ) PullRequestReviewWriteTool = mcp.NewTool( PullRequestReviewWriteToolName, - mcp.WithDescription("Manage pull request reviews: create, submit, delete, or dismiss."), + mcp.WithDescription("Write PR reviews: create, submit, delete, dismiss."), mcp.WithToolAnnotation(annotation.Write("Submit a pull request review")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("create", "submit", "delete", "dismiss")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithNumber("index", mcp.Required(), mcp.Description("pull request index")), - mcp.WithNumber("review_id", mcp.Description("review ID (required for 'submit', 'delete', 'dismiss')")), - mcp.WithString("state", mcp.Description("review state"), mcp.Enum("APPROVED", "REQUEST_CHANGES", "COMMENT", "PENDING")), - mcp.WithString("body", mcp.Description("review body/comment")), - mcp.WithString("commit_id", mcp.Description("commit SHA to review (for 'create')")), - mcp.WithString("message", mcp.Description("dismissal reason (for 'dismiss')")), - mcp.WithArray("comments", mcp.Description("inline review comments (for 'create')"), mcp.Items(map[string]any{ + mcp.WithString("method", mcp.Required(), mcp.Enum("create", "submit", "delete", "dismiss")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithNumber("pull_number", mcp.Required()), + mcp.WithNumber("review_id", mcp.Description("required except for 'create'")), + mcp.WithString("state", mcp.Enum("APPROVED", "REQUEST_CHANGES", "COMMENT", "PENDING")), + mcp.WithString("body"), + mcp.WithString("commit_id", mcp.Description("for 'create'")), + mcp.WithString("message", mcp.Description("dismissal reason")), + mcp.WithArray("comments", mcp.Description("inline comments (for 'create')"), mcp.Items(map[string]any{ "type": "object", "properties": map[string]any{ - "path": map[string]any{"type": "string", "description": "file path to comment on"}, - "body": map[string]any{"type": "string", "description": "comment body"}, - "old_line_num": map[string]any{"type": "number", "description": "line number in the old file (for deletions/changes)"}, - "new_line_num": map[string]any{"type": "number", "description": "line number in the new file (for additions/changes)"}, + "path": map[string]any{"type": "string"}, + "body": map[string]any{"type": "string"}, + "old_line_num": map[string]any{"type": "number", "description": "old-file line (deletions)"}, + "new_line_num": map[string]any{"type": "number", "description": "new-file line (additions)"}, }, })), ) @@ -140,6 +139,10 @@ func pullRequestReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT return getPullRequestByIndexFn(ctx, req) case "get_diff": return getPullRequestDiffFn(ctx, req) + case "get_files": + return getPullRequestFilesFn(ctx, req) + case "get_status": + return getPullRequestStatusFn(ctx, req) case "get_reviews": return listPullRequestReviewsFn(ctx, req) case "get_review": @@ -167,6 +170,8 @@ func pullRequestWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call return reopenPullRequestFn(ctx, req) case "merge": return mergePullRequestFn(ctx, req) + case "update_branch": + return updatePullRequestBranchFn(ctx, req) case "add_reviewers": return createPullRequestReviewerFn(ctx, req) case "remove_reviewers": @@ -185,7 +190,7 @@ func closePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "pull_number") if err != nil { return to.ErrorResult(err) } @@ -215,7 +220,7 @@ func reopenPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "pull_number") if err != nil { return to.ErrorResult(err) } @@ -266,7 +271,7 @@ func getPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(args, "index") + index, err := params.GetIndex(args, "pull_number") if err != nil { return to.ErrorResult(err) } @@ -303,7 +308,7 @@ func getPullRequestDiffFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(args, "index") + index, err := params.GetIndex(args, "pull_number") if err != nil { return to.ErrorResult(err) } @@ -446,7 +451,7 @@ func createPullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) ( if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(args, "index") + index, err := params.GetIndex(args, "pull_number") if err != nil { return to.ErrorResult(err) } @@ -489,7 +494,7 @@ func deletePullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) ( if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(args, "index") + index, err := params.GetIndex(args, "pull_number") if err != nil { return to.ErrorResult(err) } @@ -532,7 +537,7 @@ func listPullRequestReviewsFn(ctx context.Context, req mcp.CallToolRequest) (*mc if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(args, "index") + index, err := params.GetIndex(args, "pull_number") if err != nil { return to.ErrorResult(err) } @@ -567,7 +572,7 @@ func getPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp. if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(args, "index") + index, err := params.GetIndex(args, "pull_number") if err != nil { return to.ErrorResult(err) } @@ -600,7 +605,7 @@ func listPullRequestReviewCommentsFn(ctx context.Context, req mcp.CallToolReques if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(args, "index") + index, err := params.GetIndex(args, "pull_number") if err != nil { return to.ErrorResult(err) } @@ -633,7 +638,7 @@ func createPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(args, "index") + index, err := params.GetIndex(args, "pull_number") if err != nil { return to.ErrorResult(err) } @@ -698,7 +703,7 @@ func submitPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(args, "index") + index, err := params.GetIndex(args, "pull_number") if err != nil { return to.ErrorResult(err) } @@ -742,7 +747,7 @@ func deletePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(args, "index") + index, err := params.GetIndex(args, "pull_number") if err != nil { return to.ErrorResult(err) } @@ -782,7 +787,7 @@ func dismissPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (* if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(args, "index") + index, err := params.GetIndex(args, "pull_number") if err != nil { return to.ErrorResult(err) } @@ -827,7 +832,7 @@ func mergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(args, "index") + index, err := params.GetIndex(args, "pull_number") if err != nil { return to.ErrorResult(err) } @@ -891,7 +896,7 @@ func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(args, "index") + index, err := params.GetIndex(args, "pull_number") if err != nil { return to.ErrorResult(err) } @@ -960,3 +965,89 @@ func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT return to.TextResult(slimPullRequest(pr)) } + +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 { + 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) + } + + path := fmt.Sprintf("repos/%s/%s/pulls/%d/update", url.PathEscape(owner), url.PathEscape(repo), index) + if _, err := gitea.DoJSON(ctx, "POST", path, nil, nil, nil); err != nil { + return to.ErrorResult(fmt.Errorf("update %v/%v/pr/%v branch err: %v", owner, repo, index, err)) + } + return to.TextResult(map[string]any{"message": "branch updated from base"}) +} + +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 { + 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) + } + page, pageSize := params.GetPagination(args, 30) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + files, _, err := client.ListPullRequestFiles(owner, repo, index, gitea_sdk.ListPullRequestFilesOptions{ + ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: pageSize}, + }) + if err != nil { + return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v files err: %v", owner, repo, index, err)) + } + return to.TextResult(files) +} + +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 { + 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) + } + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + pr, _, err := client.GetPullRequest(owner, repo, index) + if err != nil { + return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, index, err)) + } + if pr.Head == nil || pr.Head.Sha == "" { + return to.ErrorResult(fmt.Errorf("pr %v/%v/%v has no head SHA", owner, repo, index)) + } + + status, _, err := client.GetCombinedStatus(owner, repo, pr.Head.Sha) + if err != nil { + return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v status err: %v", owner, repo, index, err)) + } + return to.TextResult(status) +} diff --git a/operation/pull/pull_test.go b/operation/pull/pull_test.go index 7da727a..389b9a6 100644 --- a/operation/pull/pull_test.go +++ b/operation/pull/pull_test.go @@ -80,11 +80,11 @@ func Test_editPullRequestFn(t *testing.T) { req := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Arguments: map[string]any{ - "owner": owner, - "repo": repo, - "index": ii.val, - "title": "WIP: my feature", - "state": "open", + "owner": owner, + "repo": repo, + "pull_number": ii.val, + "title": "WIP: my feature", + "state": "open", }, }, } @@ -195,7 +195,7 @@ func Test_mergePullRequestFn(t *testing.T) { Arguments: map[string]any{ "owner": owner, "repo": repo, - "index": ii.val, + "pull_number": ii.val, "merge_style": "squash", "title": "feat: my squashed commit", "message": "Squash merge of PR #5", @@ -308,7 +308,7 @@ func Test_mergePullRequestFn_newParams(t *testing.T) { Arguments: map[string]any{ "owner": owner, "repo": repo, - "index": float64(index), + "pull_number": float64(index), "merge_style": "merge", "force_merge": true, "merge_when_checks_succeed": true, @@ -616,9 +616,9 @@ func Test_editPullRequestFn_draft(t *testing.T) { }() args := map[string]any{ - "owner": owner, - "repo": repo, - "index": float64(index), + "owner": owner, + "repo": repo, + "pull_number": float64(index), } if tc.title != "" { args["title"] = tc.title @@ -720,10 +720,10 @@ func Test_getPullRequestDiffFn(t *testing.T) { req := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Arguments: map[string]any{ - "owner": owner, - "repo": repo, - "index": ii.val, - "binary": true, + "owner": owner, + "repo": repo, + "pull_number": ii.val, + "binary": true, }, }, } @@ -805,7 +805,7 @@ func Test_getPullRequestByIndexFn_includesAttachments(t *testing.T) { defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }() req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{ - "owner": owner, "repo": repo, "index": float64(index), + "owner": owner, "repo": repo, "pull_number": float64(index), }}} res, err := getPullRequestByIndexFn(context.Background(), req) if err != nil { @@ -853,7 +853,7 @@ func Test_getPullRequestByIndexFn_emptyAssetsLeavesBody(t *testing.T) { defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }() req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{ - "owner": owner, "repo": repo, "index": float64(index), + "owner": owner, "repo": repo, "pull_number": float64(index), }}} res, err := getPullRequestByIndexFn(context.Background(), req) if err != nil { @@ -897,7 +897,7 @@ func Test_getPullRequestByIndexFn_assetsFailureNonFatal(t *testing.T) { defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }() req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{ - "owner": owner, "repo": repo, "index": float64(index), + "owner": owner, "repo": repo, "pull_number": float64(index), }}} res, err := getPullRequestByIndexFn(context.Background(), req) if err != nil { @@ -954,10 +954,10 @@ func Test_closePullRequestFn(t *testing.T) { req := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Arguments: map[string]any{ - "method": "close", - "owner": owner, - "repo": repo, - "index": float64(index), + "method": "close", + "owner": owner, + "repo": repo, + "pull_number": float64(index), }, }, } @@ -1018,10 +1018,10 @@ func Test_reopenPullRequestFn(t *testing.T) { req := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Arguments: map[string]any{ - "method": "reopen", - "owner": owner, - "repo": repo, - "index": float64(index), + "method": "reopen", + "owner": owner, + "repo": repo, + "pull_number": float64(index), }, }, } diff --git a/operation/repo/branch.go b/operation/repo/branch.go index 664b13f..d230c89 100644 --- a/operation/repo/branch.go +++ b/operation/repo/branch.go @@ -24,31 +24,28 @@ const ( var ( CreateBranchTool = mcp.NewTool( CreateBranchToolName, - mcp.WithDescription("Create branch"), mcp.WithToolAnnotation(annotation.Write("Create a new branch")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("branch", mcp.Required(), mcp.Description("Name of the branch to create")), - mcp.WithString("old_branch", mcp.Required(), mcp.Description("Name of the old branch to create from")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("branch", mcp.Required()), + mcp.WithString("old_branch", mcp.Description("source branch (default: repo default)")), ) DeleteBranchTool = mcp.NewTool( DeleteBranchToolName, - mcp.WithDescription("Delete branch"), mcp.WithToolAnnotation(annotation.Destructive("Delete a branch")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("branch", mcp.Required(), mcp.Description("Name of the branch to delete")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("branch", mcp.Required()), ) ListBranchesTool = mcp.NewTool( ListBranchesToolName, - mcp.WithDescription("List branches"), mcp.WithToolAnnotation(annotation.ReadOnly("List repository branches")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30)), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)), ) ) diff --git a/operation/repo/commit.go b/operation/repo/commit.go index da313ab..a4a9170 100644 --- a/operation/repo/commit.go +++ b/operation/repo/commit.go @@ -23,23 +23,21 @@ const ( var ( ListRepoCommitsTool = mcp.NewTool( ListRepoCommitsToolName, - mcp.WithDescription("List repository commits"), mcp.WithToolAnnotation(annotation.ReadOnly("List repository commits")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("sha", mcp.Description("SHA or branch to start listing commits from")), - mcp.WithString("path", mcp.Description("path indicates that only commits that include the path's file/dir should be returned.")), - mcp.WithNumber("page", mcp.Required(), mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), - mcp.WithNumber("perPage", mcp.Required(), mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30), mcp.Min(1)), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("sha", mcp.Description("starting SHA or branch")), + mcp.WithString("path", mcp.Description("only commits touching this path")), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)), ) GetCommitTool = mcp.NewTool( GetCommitToolName, - mcp.WithDescription("Get details of a specific commit"), mcp.WithToolAnnotation(annotation.ReadOnly("Get commit details")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("sha", mcp.Required(), mcp.Description("commit SHA")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("sha", mcp.Required()), ) ) @@ -65,20 +63,13 @@ func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT if err != nil { return to.ErrorResult(err) } - page, err := params.GetIndex(args, "page") - if err != nil { - return to.ErrorResult(err) - } - pageSize, err := params.GetIndex(args, "perPage") - if err != nil { - return to.ErrorResult(err) - } + page, pageSize := params.GetPagination(args, 30) sha, _ := args["sha"].(string) path, _ := args["path"].(string) opt := gitea_sdk.ListCommitOptions{ ListOptions: gitea_sdk.ListOptions{ - Page: int(page), - PageSize: int(pageSize), + Page: page, + PageSize: pageSize, }, SHA: sha, Path: path, diff --git a/operation/repo/file.go b/operation/repo/file.go index 6193d36..edb90a8 100644 --- a/operation/repo/file.go +++ b/operation/repo/file.go @@ -29,49 +29,47 @@ const ( var ( GetFileContentTool = mcp.NewTool( GetFileToolName, - mcp.WithDescription("Get file Content and Metadata"), + mcp.WithDescription("Get file content and metadata"), mcp.WithToolAnnotation(annotation.ReadOnly("Get file content")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("ref", mcp.Required(), mcp.Description("ref can be branch/tag/commit")), - mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")), - mcp.WithBoolean("withLines", mcp.Description("whether to return file content with lines")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("ref", mcp.Required(), mcp.Description("branch, tag, or commit SHA")), + mcp.WithString("path", mcp.Required()), + mcp.WithBoolean("withLines", mcp.Description("return numbered lines")), ) GetDirContentTool = mcp.NewTool( GetDirToolName, - mcp.WithDescription("Get a list of entries in a directory"), mcp.WithToolAnnotation(annotation.ReadOnly("Get directory contents")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("ref", mcp.Required(), mcp.Description("ref can be branch/tag/commit")), - mcp.WithString("filePath", mcp.Required(), mcp.Description("directory path")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("ref", mcp.Required(), mcp.Description("branch, tag, or commit SHA")), + mcp.WithString("path", mcp.Required()), ) CreateOrUpdateFileTool = mcp.NewTool( CreateOrUpdateFileToolName, - mcp.WithDescription("Create or update a file. If sha is provided, updates the existing file; otherwise creates a new file."), + mcp.WithDescription("Create or update a file (provide sha to update an existing file)."), mcp.WithToolAnnotation(annotation.Write("Create or update a file")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")), - mcp.WithString("content", mcp.Required(), mcp.Description("file content")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("path", mcp.Required()), + mcp.WithString("content", mcp.Required()), mcp.WithString("message", mcp.Required(), mcp.Description("commit message")), - mcp.WithString("branch_name", mcp.Required(), mcp.Description("branch name")), - mcp.WithString("sha", mcp.Description("SHA of the existing file (required for update, omit for create)")), - mcp.WithString("new_branch_name", mcp.Description("new branch name (for create only)")), + mcp.WithString("branch_name", mcp.Required()), + mcp.WithString("sha", mcp.Description("existing file SHA (omit to create)")), + mcp.WithString("new_branch_name", mcp.Description("new branch (create only)")), ) DeleteFileTool = mcp.NewTool( DeleteFileToolName, - mcp.WithDescription("Delete file"), mcp.WithToolAnnotation(annotation.Destructive("Delete a file")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("path", mcp.Required()), mcp.WithString("message", mcp.Required(), mcp.Description("commit message")), - mcp.WithString("branch_name", mcp.Required(), mcp.Description("branch name")), - mcp.WithString("sha", mcp.Required(), mcp.Description("sha")), + mcp.WithString("branch_name", mcp.Required()), + mcp.WithString("sha", mcp.Required()), ) ) @@ -111,7 +109,7 @@ func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo return to.ErrorResult(err) } ref, _ := args["ref"].(string) - filePath, err := params.GetString(args, "filePath") + filePath, err := params.GetString(args, "path") if err != nil { return to.ErrorResult(err) } @@ -175,7 +173,7 @@ func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo return to.ErrorResult(err) } ref, _ := args["ref"].(string) - filePath, err := params.GetString(args, "filePath") + filePath, err := params.GetString(args, "path") if err != nil { return to.ErrorResult(err) } @@ -201,7 +199,7 @@ func CreateOrUpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca if err != nil { return to.ErrorResult(err) } - filePath, err := params.GetString(args, "filePath") + filePath, err := params.GetString(args, "path") if err != nil { return to.ErrorResult(err) } @@ -261,7 +259,7 @@ func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe if err != nil { return to.ErrorResult(err) } - filePath, err := params.GetString(args, "filePath") + filePath, err := params.GetString(args, "path") if err != nil { return to.ErrorResult(err) } diff --git a/operation/repo/release.go b/operation/repo/release.go index 432e531..731653e 100644 --- a/operation/repo/release.go +++ b/operation/repo/release.go @@ -26,54 +26,50 @@ const ( var ( CreateReleaseTool = mcp.NewTool( CreateReleaseToolName, - mcp.WithDescription("Create release"), mcp.WithToolAnnotation(annotation.Write("Create a release")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")), - mcp.WithString("target", mcp.Required(), mcp.Description("target commitish")), - mcp.WithString("title", mcp.Required(), mcp.Description("release title")), - mcp.WithBoolean("is_draft", mcp.Description("Whether the release is draft"), mcp.DefaultBool(false)), - mcp.WithBoolean("is_pre_release", mcp.Description("Whether the release is pre-release"), mcp.DefaultBool(false)), - mcp.WithString("body", mcp.Description("release body")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("tag_name", mcp.Required()), + mcp.WithString("target", mcp.Required(), mcp.Description("commitish")), + mcp.WithString("title", mcp.Required()), + mcp.WithBoolean("is_draft"), + mcp.WithBoolean("is_pre_release"), + mcp.WithString("body"), ) DeleteReleaseTool = mcp.NewTool( DeleteReleaseToolName, - mcp.WithDescription("Delete release"), mcp.WithToolAnnotation(annotation.Destructive("Delete a release")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithNumber("id", mcp.Required(), mcp.Description("release id")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithNumber("id", mcp.Required()), ) GetReleaseTool = mcp.NewTool( GetReleaseToolName, - mcp.WithDescription("Get release"), + mcp.WithDescription("Get a release by ID"), mcp.WithToolAnnotation(annotation.ReadOnly("Get release details")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithNumber("id", mcp.Required(), mcp.Description("release id")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithNumber("id", mcp.Required()), ) GetLatestReleaseTool = mcp.NewTool( GetLatestReleaseToolName, - mcp.WithDescription("Get latest release"), mcp.WithToolAnnotation(annotation.ReadOnly("Get latest release")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), ) ListReleasesTool = mcp.NewTool( ListReleasesToolName, - mcp.WithDescription("List releases"), mcp.WithToolAnnotation(annotation.ReadOnly("List releases")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithBoolean("is_draft", mcp.Description("Whether the release is draft"), mcp.DefaultBool(false)), - mcp.WithBoolean("is_pre_release", mcp.Description("Whether the release is pre-release"), mcp.DefaultBool(false)), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(20), mcp.Min(1)), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithBoolean("is_draft"), + mcp.WithBoolean("is_pre_release"), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(20), mcp.Min(1)), ) ) @@ -248,7 +244,7 @@ func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool pIsPreRelease = new(isPreRelease) } page := params.GetOptionalInt(args, "page", 1) - pageSize := params.GetOptionalInt(args, "perPage", 20) + pageSize := params.GetOptionalInt(args, "per_page", 20) client, err := gitea.ClientFromContext(ctx) if err != nil { diff --git a/operation/repo/repo.go b/operation/repo/repo.go index c22c960..c892d82 100644 --- a/operation/repo/repo.go +++ b/operation/repo/repo.go @@ -29,48 +29,44 @@ const ( var ( CreateRepoTool = mcp.NewTool( CreateRepoToolName, - mcp.WithDescription("Create repository in personal account or organization"), mcp.WithToolAnnotation(annotation.Write("Create a new repository")), - mcp.WithString("name", mcp.Required(), mcp.Description("Name of the repository to create")), - mcp.WithString("description", mcp.Description("Description of the repository to create")), - mcp.WithBoolean("private", mcp.Description("Whether the repository is private")), - mcp.WithString("issue_labels", mcp.Description("Issue Label set to use")), - mcp.WithBoolean("auto_init", mcp.Description("Whether the repository should be auto-intialized?")), - mcp.WithBoolean("template", mcp.Description("Whether the repository is template")), - mcp.WithString("gitignores", mcp.Description("Gitignores to use")), - mcp.WithString("license", mcp.Description("License to use")), - mcp.WithString("readme", mcp.Description("Readme of the repository to create")), - mcp.WithString("default_branch", mcp.Description("DefaultBranch of the repository (used when initializes and in template)")), - mcp.WithString("trust_model", mcp.Description("Trust model for verifying GPG signatures"), mcp.Enum("default", "collaborator", "committer", "collaboratorcommitter")), - mcp.WithString("object_format_name", mcp.Description("Object format: sha1 or sha256"), mcp.Enum("sha1", "sha256")), - mcp.WithString("organization", mcp.Description("Organization name to create repository in (optional - defaults to personal account)")), + mcp.WithString("name", mcp.Required()), + mcp.WithString("description"), + mcp.WithBoolean("private"), + mcp.WithString("issue_labels"), + mcp.WithBoolean("auto_init"), + mcp.WithBoolean("template"), + mcp.WithString("gitignores"), + mcp.WithString("license"), + mcp.WithString("readme"), + mcp.WithString("default_branch"), + mcp.WithString("trust_model", mcp.Enum("default", "collaborator", "committer", "collaboratorcommitter")), + mcp.WithString("object_format_name", mcp.Enum("sha1", "sha256")), + mcp.WithString("organization", mcp.Description("defaults to personal account")), ) ForkRepoTool = mcp.NewTool( ForkRepoToolName, - mcp.WithDescription("Fork repository"), mcp.WithToolAnnotation(annotation.Write("Fork a repository")), - mcp.WithString("user", mcp.Required(), mcp.Description("User name of the repository to fork")), - mcp.WithString("repo", mcp.Required(), mcp.Description("Repository name to fork")), - mcp.WithString("organization", mcp.Description("Organization name to fork")), - mcp.WithString("name", mcp.Description("Name of the forked repository")), + mcp.WithString("user", mcp.Required(), mcp.Description("owner of source repo")), + mcp.WithString("repo", mcp.Required()), + mcp.WithString("organization", mcp.Description("target org")), + mcp.WithString("name", mcp.Description("fork name")), ) ListMyReposTool = mcp.NewTool( ListMyReposToolName, - mcp.WithDescription("List my repositories"), mcp.WithToolAnnotation(annotation.ReadOnly("List my repositories")), - mcp.WithNumber("page", mcp.Required(), mcp.Description("Page number"), mcp.DefaultNumber(1), mcp.Min(1)), - mcp.WithNumber("perPage", mcp.Required(), mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30), mcp.Min(1)), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)), ) ListOrgReposTool = mcp.NewTool( ListOrgReposToolName, - mcp.WithDescription("List repositories of an organization"), mcp.WithToolAnnotation(annotation.ReadOnly("List organization repositories")), - mcp.WithString("org", mcp.Required(), mcp.Description("Organization name")), - mcp.WithNumber("page", mcp.Required(), mcp.Description("Page number"), mcp.DefaultNumber(1), mcp.Min(1)), - mcp.WithNumber("pageSize", mcp.Required(), mcp.Description("Page size number"), mcp.DefaultNumber(100), mcp.Min(1)), + mcp.WithString("org", mcp.Required()), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(100), mcp.Min(1)), ) ) @@ -210,18 +206,11 @@ func ListOrgReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool if !ok { return to.ErrorResult(errors.New("organization name is required")) } - page, ok := req.GetArguments()["page"].(float64) - if !ok { - page = 1 - } - pageSize, ok := req.GetArguments()["pageSize"].(float64) - if !ok { - pageSize = 100 - } + page, pageSize := params.GetPagination(req.GetArguments(), 100) opt := gitea_sdk.ListOrgReposOptions{ ListOptions: gitea_sdk.ListOptions{ - Page: int(page), - PageSize: int(pageSize), + Page: page, + PageSize: pageSize, }, } client, err := gitea.ClientFromContext(ctx) diff --git a/operation/repo/tag.go b/operation/repo/tag.go index 67f6b7f..6446111 100644 --- a/operation/repo/tag.go +++ b/operation/repo/tag.go @@ -25,41 +25,37 @@ const ( var ( CreateTagTool = mcp.NewTool( CreateTagToolName, - mcp.WithDescription("Create tag"), mcp.WithToolAnnotation(annotation.Write("Create a tag")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")), - mcp.WithString("target", mcp.Description("target commitish"), mcp.DefaultString("")), - mcp.WithString("message", mcp.Description("tag message"), mcp.DefaultString("")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("tag_name", mcp.Required()), + mcp.WithString("target", mcp.Description("commitish")), + mcp.WithString("message", mcp.Description("tag message")), ) DeleteTagTool = mcp.NewTool( DeleteTagToolName, - mcp.WithDescription("Delete tag"), mcp.WithToolAnnotation(annotation.Destructive("Delete a tag")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("tag_name", mcp.Required()), ) GetTagTool = mcp.NewTool( GetTagToolName, - mcp.WithDescription("Get tag"), mcp.WithToolAnnotation(annotation.ReadOnly("Get tag details")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("tag_name", mcp.Required()), ) ListTagsTool = mcp.NewTool( ListTagsToolName, - mcp.WithDescription("List tags"), mcp.WithToolAnnotation(annotation.ReadOnly("List tags")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(20), mcp.Min(1)), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(20), mcp.Min(1)), ) ) @@ -184,7 +180,7 @@ func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu return to.ErrorResult(err) } page := params.GetOptionalInt(args, "page", 1) - pageSize := params.GetOptionalInt(args, "perPage", 20) + pageSize := params.GetOptionalInt(args, "per_page", 20) client, err := gitea.ClientFromContext(ctx) if err != nil { diff --git a/operation/repo/tree.go b/operation/repo/tree.go index 4c76360..862ac66 100644 --- a/operation/repo/tree.go +++ b/operation/repo/tree.go @@ -21,14 +21,13 @@ const ( var GetRepoTreeTool = mcp.NewTool( GetRepoTreeToolName, - mcp.WithDescription("Get the file tree of a repository"), mcp.WithToolAnnotation(annotation.ReadOnly("Get repository file tree")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("tree_sha", mcp.Required(), mcp.Description("SHA, branch name, or tag name")), - mcp.WithBoolean("recursive", mcp.Description("whether to get the tree recursively")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30)), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("tree_sha", mcp.Required(), mcp.Description("SHA, branch, or tag")), + mcp.WithBoolean("recursive"), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)), ) func init() { diff --git a/operation/search/search.go b/operation/search/search.go index c405861..78da76d 100644 --- a/operation/search/search.go +++ b/operation/search/search.go @@ -29,51 +29,48 @@ const ( var ( SearchUsersTool = mcp.NewTool( SearchUsersToolName, - mcp.WithDescription("search users"), mcp.WithToolAnnotation(annotation.ReadOnly("Search users")), - mcp.WithString("keyword", mcp.Required(), mcp.Description("Keyword")), - mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30)), + mcp.WithString("query", mcp.Required()), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)), ) SearOrgTeamsTool = mcp.NewTool( SearchOrgTeamsToolName, - mcp.WithDescription("search organization teams"), mcp.WithToolAnnotation(annotation.ReadOnly("Search organization teams")), - mcp.WithString("org", mcp.Required(), mcp.Description("organization name")), - mcp.WithString("query", mcp.Required(), mcp.Description("search organization teams")), - mcp.WithBoolean("includeDescription", mcp.Description("include description?")), - mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30)), + mcp.WithString("org", mcp.Required()), + mcp.WithString("query", mcp.Required()), + mcp.WithBoolean("includeDescription"), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)), ) SearchReposTool = mcp.NewTool( SearchReposToolName, - mcp.WithDescription("search repos"), mcp.WithToolAnnotation(annotation.ReadOnly("Search repositories")), - mcp.WithString("keyword", mcp.Required(), mcp.Description("Keyword")), - mcp.WithBoolean("keywordIsTopic", mcp.Description("KeywordIsTopic")), - mcp.WithBoolean("keywordInDescription", mcp.Description("KeywordInDescription")), - mcp.WithNumber("ownerID", mcp.Description("OwnerID")), - mcp.WithBoolean("isPrivate", mcp.Description("IsPrivate")), - mcp.WithBoolean("isArchived", mcp.Description("IsArchived")), - mcp.WithString("sort", mcp.Description("Sort")), - mcp.WithString("order", mcp.Description("Order")), - mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30)), + mcp.WithString("query", mcp.Required()), + mcp.WithBoolean("keywordIsTopic"), + mcp.WithBoolean("keywordInDescription"), + mcp.WithNumber("ownerID"), + mcp.WithBoolean("isPrivate"), + mcp.WithBoolean("isArchived"), + mcp.WithString("sort"), + mcp.WithString("order"), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)), ) SearchIssuesTool = mcp.NewTool( SearchIssuesToolName, - mcp.WithDescription("Search for issues and pull requests across all accessible repositories"), + mcp.WithDescription("Search issues and PRs across repositories"), mcp.WithToolAnnotation(annotation.ReadOnly("Search issues")), - mcp.WithString("query", mcp.Required(), mcp.Description("search keyword")), - mcp.WithString("state", mcp.Description("filter by state: open, closed, all"), mcp.Enum("open", "closed", "all")), - mcp.WithString("type", mcp.Description("filter by type: issues, pulls"), mcp.Enum("issues", "pulls")), - mcp.WithString("labels", mcp.Description("comma-separated list of label names")), - mcp.WithString("owner", mcp.Description("filter by repository owner")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30)), + mcp.WithString("query", mcp.Required()), + mcp.WithString("state", mcp.Enum("open", "closed", "all")), + mcp.WithString("type", mcp.Enum("issues", "pulls")), + mcp.WithString("labels", mcp.Description("comma-separated")), + mcp.WithString("owner", mcp.Description("filter by owner")), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)), ) ) @@ -98,7 +95,7 @@ func init() { func UsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called UsersFn") - keyword, err := params.GetString(req.GetArguments(), "keyword") + keyword, err := params.GetString(req.GetArguments(), "query") if err != nil { return to.ErrorResult(err) } @@ -154,7 +151,7 @@ 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(), "keyword") + keyword, err := params.GetString(req.GetArguments(), "query") if err != nil { return to.ErrorResult(err) } diff --git a/operation/search/search_test.go b/operation/search/search_test.go index 81e37e3..1dce5d9 100644 --- a/operation/search/search_test.go +++ b/operation/search/search_test.go @@ -16,7 +16,7 @@ func TestSearchToolsRequiredFields(t *testing.T) { { name: "search_users", tool: SearchUsersTool, - required: []string{"keyword"}, + required: []string{"query"}, }, { name: "search_org_teams", @@ -26,7 +26,7 @@ func TestSearchToolsRequiredFields(t *testing.T) { { name: "search_repos", tool: SearchReposTool, - required: []string{"keyword"}, + required: []string{"query"}, }, } diff --git a/operation/timetracking/timetracking.go b/operation/timetracking/timetracking.go index 9fca68f..30fe558 100644 --- a/operation/timetracking/timetracking.go +++ b/operation/timetracking/timetracking.go @@ -27,26 +27,26 @@ const ( var ( TimetrackingReadTool = mcp.NewTool( TimetrackingReadToolName, - mcp.WithDescription("Read time tracking data. Use method 'list_issue_times' for issue times, 'list_repo_times' for repository times, 'get_my_stopwatches' for active stopwatches, 'get_my_times' for all your tracked times."), + mcp.WithDescription("Read time tracking: issue times, repo times, active stopwatches, your tracked times."), mcp.WithToolAnnotation(annotation.ReadOnly("Read tracked time")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list_issue_times", "list_repo_times", "get_my_stopwatches", "get_my_times")), - mcp.WithString("owner", mcp.Description("repository owner (required for 'list_issue_times', 'list_repo_times')")), - mcp.WithString("repo", mcp.Description("repository name (required for 'list_issue_times', 'list_repo_times')")), - mcp.WithNumber("index", mcp.Description("issue index (required for 'list_issue_times')")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(30)), + mcp.WithString("method", mcp.Required(), mcp.Enum("list_issue_times", "list_repo_times", "get_my_stopwatches", "get_my_times")), + mcp.WithString("owner", mcp.Description("for list_* methods")), + mcp.WithString("repo", mcp.Description("for list_* methods")), + mcp.WithNumber("issue_number", mcp.Description("for 'list_issue_times'")), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)), ) TimetrackingWriteTool = mcp.NewTool( TimetrackingWriteToolName, - mcp.WithDescription("Manage time tracking: stopwatches and tracked time entries."), + mcp.WithDescription("Write time tracking: stopwatches and entries."), mcp.WithToolAnnotation(annotation.Write("Add or manage tracked time")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("start_stopwatch", "stop_stopwatch", "delete_stopwatch", "add_time", "delete_time")), - mcp.WithString("owner", mcp.Description("repository owner (required for all methods)")), - mcp.WithString("repo", mcp.Description("repository name (required for all methods)")), - mcp.WithNumber("index", mcp.Description("issue index (required for all methods)")), - mcp.WithNumber("time", mcp.Description("time to add in seconds (required for 'add_time')")), - mcp.WithNumber("id", mcp.Description("tracked time entry ID (required for 'delete_time')")), + mcp.WithString("method", mcp.Required(), mcp.Enum("start_stopwatch", "stop_stopwatch", "delete_stopwatch", "add_time", "delete_time")), + mcp.WithString("owner", mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Description(params.RepoDesc)), + mcp.WithNumber("issue_number"), + mcp.WithNumber("time", mcp.Description("seconds (for 'add_time')")), + mcp.WithNumber("id", mcp.Description("entry ID (for 'delete_time')")), ) ) @@ -107,7 +107,7 @@ func startStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } @@ -132,7 +132,7 @@ func stopStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } @@ -157,7 +157,7 @@ func deleteStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } @@ -200,7 +200,7 @@ func listTrackedTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } @@ -235,7 +235,7 @@ func addTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo if err != nil { return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } @@ -268,7 +268,7 @@ func deleteTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal return to.ErrorResult(err) } - index, err := params.GetIndex(req.GetArguments(), "index") + index, err := params.GetIndex(req.GetArguments(), "issue_number") if err != nil { return to.ErrorResult(err) } diff --git a/operation/user/user.go b/operation/user/user.go index b79b6dc..4a087e8 100644 --- a/operation/user/user.go +++ b/operation/user/user.go @@ -36,18 +36,18 @@ var ( // It is registered with a specific name and a description string. GetMyUserInfoTool = mcp.NewTool( GetMyUserInfoToolName, - mcp.WithDescription("Get my user info"), + 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 "perPage" arguments with default values specified above. + // It supports pagination via "page" and "per_page" arguments with default values specified above. GetUserOrgsTool = mcp.NewTool( GetUserOrgsToolName, - mcp.WithDescription("Get organizations associated with the authenticated user"), + mcp.WithDescription("List current user's organizations"), mcp.WithToolAnnotation(annotation.ReadOnly("Get user organizations")), - mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(defaultPage)), - mcp.WithNumber("perPage", mcp.Description("results per page (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), mcp.DefaultNumber(defaultPageSize)), + mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(defaultPage)), + mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(defaultPageSize)), ) ) diff --git a/operation/version/version.go b/operation/version/version.go index 7e8cbbb..d0ca8df 100644 --- a/operation/version/version.go +++ b/operation/version/version.go @@ -22,7 +22,6 @@ const ( var GetGiteaMCPServerVersionTool = mcp.NewTool( GetGiteaMCPServerVersion, - mcp.WithDescription("Get Gitea MCP Server Version"), mcp.WithToolAnnotation(annotation.ReadOnly("Get server version")), ) diff --git a/operation/wiki/wiki.go b/operation/wiki/wiki.go index c28cf1a..64ecb26 100644 --- a/operation/wiki/wiki.go +++ b/operation/wiki/wiki.go @@ -27,24 +27,24 @@ const ( var ( WikiReadTool = mcp.NewTool( WikiReadToolName, - mcp.WithDescription("Read wiki page information. Use method 'list' to list pages, 'get' to get page content, 'get_revisions' for revision history."), + mcp.WithDescription("Read wiki: list pages, get content, revision history."), mcp.WithToolAnnotation(annotation.ReadOnly("Read wiki pages")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list", "get", "get_revisions")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("pageName", mcp.Description("wiki page name (required for 'get', 'get_revisions')")), + mcp.WithString("method", mcp.Required(), mcp.Enum("list", "get", "get_revisions")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("pageName", mcp.Description("for 'get'/'get_revisions'")), ) WikiWriteTool = mcp.NewTool( WikiWriteToolName, - mcp.WithDescription("Create, update, or delete wiki pages."), + mcp.WithDescription("Write wiki pages: create, update, delete."), mcp.WithToolAnnotation(annotation.Destructive("Create, update, or delete wiki pages")), - mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("create", "update", "delete")), - mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), - mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), - mcp.WithString("pageName", mcp.Description("wiki page name (required for 'update', 'delete')")), - mcp.WithString("title", mcp.Description("wiki page title (required for 'create', optional for 'update')")), - mcp.WithString("content", mcp.Description("page content (required for 'create', 'update')")), + mcp.WithString("method", mcp.Required(), mcp.Enum("create", "update", "delete")), + mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), + mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), + mcp.WithString("pageName", mcp.Description("for 'update'/'delete'")), + mcp.WithString("title", mcp.Description("for 'create'")), + mcp.WithString("content", mcp.Description("for 'create'/'update'")), mcp.WithString("message", mcp.Description("commit message")), ) ) diff --git a/pkg/annotation/annotation.go b/pkg/annotation/annotation.go index fd9596d..33b5ab5 100644 --- a/pkg/annotation/annotation.go +++ b/pkg/annotation/annotation.go @@ -6,27 +6,17 @@ 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, - } + 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, - } + 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, - } + return mcp.ToolAnnotation{Title: title, ReadOnlyHint: &f, DestructiveHint: &t} } diff --git a/pkg/params/params.go b/pkg/params/params.go index 0edb31a..466f852 100644 --- a/pkg/params/params.go +++ b/pkg/params/params.go @@ -6,6 +6,16 @@ import ( "time" ) +// Shared parameter description strings used across tools. Extracted to avoid +// repeating the same boilerplate in every tool schema (saves tokens in the +// tool list sent to MCP clients). +const ( + OwnerDesc = "repo owner" + RepoDesc = "repo name" + PageDesc = "page" + PaginationDesc = "results per page" +) + // GetString extracts a required string parameter from MCP tool arguments. func GetString(args map[string]any, key string) (string, error) { val, ok := args[key].(string) @@ -42,9 +52,9 @@ func GetStringSlice(args map[string]any, key string) []string { return out } -// GetPagination extracts page and perPage parameters, returning them as ints. +// 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, "perPage", defaultPageSize)) + return int(GetOptionalInt(args, "page", 1)), int(GetOptionalInt(args, "per_page", defaultPageSize)) } // ToInt64 converts a value to int64, accepting both float64 (JSON number) and diff --git a/pkg/params/params_test.go b/pkg/params/params_test.go index 85f4606..e3fa444 100644 --- a/pkg/params/params_test.go +++ b/pkg/params/params_test.go @@ -5,6 +5,17 @@ import ( "testing" ) +func TestGetPagination(t *testing.T) { + page, perPage := GetPagination(map[string]any{"page": float64(2), "per_page": float64(40)}, 30) + if page != 2 || perPage != 40 { + t.Errorf("GetPagination = (%d, %d), want (2, 40)", page, perPage) + } + page, perPage = GetPagination(map[string]any{}, 30) + if page != 1 || perPage != 30 { + t.Errorf("GetPagination defaults = (%d, %d), want (1, 30)", page, perPage) + } +} + func TestToInt64(t *testing.T) { tests := []struct { name string