Trim tool schemas, add param aliases, new PR methods (#191)

- 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 <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
This commit is contained in:
silverwind
2026-05-14 06:24:51 +00:00
committed by silverwind
parent a77b54acdd
commit f35372173e
27 changed files with 555 additions and 490 deletions
+17 -17
View File
@@ -48,29 +48,29 @@ func toSecretMetas(secrets []*gitea_sdk.Secret) []secretMeta {
var ( var (
ActionsConfigReadTool = mcp.NewTool( ActionsConfigReadTool = mcp.NewTool(
ActionsConfigReadToolName, 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.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("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("repository owner (required for repo methods)")), mcp.WithString("owner", mcp.Description("for repo methods")),
mcp.WithString("repo", mcp.Description("repository name (required for repo methods)")), mcp.WithString("repo", mcp.Description("for repo methods")),
mcp.WithString("org", mcp.Description("organization name (required for org methods)")), mcp.WithString("org", mcp.Description("for org methods")),
mcp.WithString("name", mcp.Description("variable name (required for get methods)")), mcp.WithString("name", mcp.Description("for get methods")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)),
) )
ActionsConfigWriteTool = mcp.NewTool( ActionsConfigWriteTool = mcp.NewTool(
ActionsConfigWriteToolName, 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.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("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("repository owner (required for repo methods)")), mcp.WithString("owner", mcp.Description("for repo methods")),
mcp.WithString("repo", mcp.Description("repository name (required for repo methods)")), mcp.WithString("repo", mcp.Description("for repo methods")),
mcp.WithString("org", mcp.Description("organization name (required for org methods)")), mcp.WithString("org", mcp.Description("for org methods")),
mcp.WithString("name", mcp.Description("secret or variable name (required for most methods)")), mcp.WithString("name", mcp.Description("secret or variable name")),
mcp.WithString("data", mcp.Description("secret value (required for upsert secret methods)")), mcp.WithString("data", mcp.Description("secret value (upsert)")),
mcp.WithString("value", mcp.Description("variable value (required for create/update variable methods)")), mcp.WithString("value", mcp.Description("variable value")),
mcp.WithString("description", mcp.Description("description for secret or variable")), mcp.WithString("description"),
) )
) )
+21 -21
View File
@@ -28,33 +28,33 @@ const (
var ( var (
ActionsRunReadTool = mcp.NewTool( ActionsRunReadTool = mcp.NewTool(
ActionsRunReadToolName, 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.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("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("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("workflow_id", mcp.Description("workflow ID or filename (required for 'get_workflow')")), mcp.WithString("workflow_id", mcp.Description("ID or filename (for 'get_workflow')")),
mcp.WithNumber("run_id", mcp.Description("run ID (required for 'get_run', 'list_run_jobs')")), mcp.WithNumber("run_id", mcp.Description("for 'get_run'/'list_run_jobs'")),
mcp.WithNumber("job_id", mcp.Description("job ID (required for 'get_job_log_preview', 'download_job_log')")), mcp.WithNumber("job_id", mcp.Description("for log methods")),
mcp.WithString("status", mcp.Description("optional status filter (for 'list_runs', 'list_jobs')")), mcp.WithString("status", mcp.Description("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("tail_lines", mcp.Description("log tail lines"), 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.WithNumber("max_bytes", mcp.Description("max log bytes"), mcp.DefaultNumber(65536), mcp.Min(1024)),
mcp.WithString("output_path", mcp.Description("output file path (for 'download_job_log')")), mcp.WithString("output_path", mcp.Description("for 'download_job_log'")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)),
) )
ActionsRunWriteTool = mcp.NewTool( ActionsRunWriteTool = mcp.NewTool(
ActionsRunWriteToolName, 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.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("method", mcp.Required(), mcp.Enum("dispatch_workflow", "cancel_run", "rerun_run")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("workflow_id", mcp.Description("workflow ID or filename (required for 'dispatch_workflow')")), mcp.WithString("workflow_id", mcp.Description("ID or filename (for 'dispatch_workflow')")),
mcp.WithString("ref", mcp.Description("git ref branch or tag (required for 'dispatch_workflow')")), mcp.WithString("ref", mcp.Description("branch or tag (for 'dispatch_workflow')")),
mcp.WithObject("inputs", mcp.Description("workflow inputs object (for 'dispatch_workflow')")), mcp.WithObject("inputs", mcp.Description("for 'dispatch_workflow'")),
mcp.WithNumber("run_id", mcp.Description("run ID (required for 'cancel_run', 'rerun_run')")), mcp.WithNumber("run_id", mcp.Description("for 'cancel_run'/'rerun_run'")),
) )
) )
+38 -39
View File
@@ -40,47 +40,46 @@ const (
var ( var (
ListRepoIssuesTool = mcp.NewTool( ListRepoIssuesTool = mcp.NewTool(
ListRepoIssuesToolName, ListRepoIssuesToolName,
mcp.WithDescription("List repository issues"),
mcp.WithToolAnnotation(annotation.ReadOnly("List repository issues")), mcp.WithToolAnnotation(annotation.ReadOnly("List repository issues")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("state", mcp.Description("issue state"), mcp.DefaultString("all")), mcp.WithString("state", mcp.DefaultString("all")),
mcp.WithArray("labels", mcp.Description("filter by label names"), mcp.Items(map[string]any{"type": "string"})), mcp.WithArray("labels", mcp.Description("label name filter"), mcp.Items(map[string]any{"type": "string"})),
mcp.WithString("since", mcp.Description("filter issues updated after this ISO 8601 timestamp")), mcp.WithString("since", mcp.Description("updated after ISO 8601")),
mcp.WithString("before", mcp.Description("filter issues updated before this ISO 8601 timestamp")), mcp.WithString("before", mcp.Description("updated before ISO 8601")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
) )
IssueReadTool = mcp.NewTool( IssueReadTool = mcp.NewTool(
IssueReadToolName, 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.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("method", mcp.Required(), mcp.Enum("get", "get_comments", "get_labels")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")), mcp.WithNumber("issue_number", mcp.Required()),
) )
IssueWriteTool = mcp.NewTool( IssueWriteTool = mcp.NewTool(
IssueWriteToolName, 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.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("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("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithNumber("index", mcp.Description("issue index (required for all methods except 'create')")), mcp.WithNumber("issue_number", mcp.Description("required except for 'create'")),
mcp.WithString("title", mcp.Description("issue title (required for 'create')")), mcp.WithString("title", mcp.Description("required for 'create'")),
mcp.WithString("body", mcp.Description("issue/comment body (required for 'create', 'add_comment', 'edit_comment')")), mcp.WithString("body", mcp.Description("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.WithArray("assignees", mcp.Items(map[string]any{"type": "string"})),
mcp.WithNumber("milestone", mcp.Description("milestone number (for 'create', 'update')")), mcp.WithNumber("milestone"),
mcp.WithString("state", mcp.Description("issue state, one of open, closed, all (for 'update')")), mcp.WithString("state", mcp.Enum("open", "closed", "all")),
mcp.WithNumber("commentID", mcp.Description("id of issue comment (required for 'edit_comment')")), mcp.WithNumber("commentID", mcp.Description("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.WithArray("labels", mcp.Description("label IDs"), mcp.Items(map[string]any{"type": "number"})),
mcp.WithNumber("label_id", mcp.Description("label ID to remove (required for 'remove_label')")), mcp.WithNumber("label_id", mcp.Description("for 'remove_label'")),
mcp.WithString("ref", mcp.Description("branch name to associate with the issue (for 'create', 'update')")), mcp.WithString("ref", mcp.Description("branch to associate")),
mcp.WithString("deadline", mcp.Description("due date in ISO 8601 format (for 'create', 'update')")), mcp.WithString("deadline", mcp.Description("ISO 8601")),
mcp.WithBoolean("remove_deadline", mcp.Description("unset due date (for 'update')")), mcp.WithBoolean("remove_deadline"),
) )
) )
@@ -155,7 +154,7 @@ func getIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -267,7 +266,7 @@ func createIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -300,7 +299,7 @@ func editIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -388,7 +387,7 @@ func getIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*m
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -416,7 +415,7 @@ func getIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -444,7 +443,7 @@ func addIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -474,7 +473,7 @@ func replaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -504,7 +503,7 @@ func clearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -530,7 +529,7 @@ func removeIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
+2 -2
View File
@@ -201,7 +201,7 @@ func Test_getIssueByIndexFn_includesAttachments(t *testing.T) {
defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }() defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }()
req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{ 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) res, err := getIssueByIndexFn(context.Background(), req)
if err != nil { 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 }() defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }()
req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{ 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) res, err := getIssueCommentsByIndexFn(context.Background(), req)
if err != nil { if err != nil {
+19 -19
View File
@@ -26,31 +26,31 @@ const (
var ( var (
LabelReadTool = mcp.NewTool( LabelReadTool = mcp.NewTool(
LabelReadToolName, 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.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("method", mcp.Required(), mcp.Enum("list_repo_labels", "get_repo_label", "list_org_labels")),
mcp.WithString("owner", mcp.Description("repository owner (required for repo methods)")), mcp.WithString("owner", mcp.Description("for repo methods")),
mcp.WithString("repo", mcp.Description("repository name (required for repo methods)")), mcp.WithString("repo", mcp.Description("for repo methods")),
mcp.WithString("org", mcp.Description("organization name (required for 'list_org')")), mcp.WithString("org", mcp.Description("for org methods")),
mcp.WithNumber("id", mcp.Description("label ID (required for 'get_repo')")), mcp.WithNumber("id", mcp.Description("label ID (for 'get_repo_label')")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
) )
LabelWriteTool = mcp.NewTool( LabelWriteTool = mcp.NewTool(
LabelWriteToolName, 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.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("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("repository owner (required for repo methods)")), mcp.WithString("owner", mcp.Description("for repo methods")),
mcp.WithString("repo", mcp.Description("repository name (required for repo methods)")), mcp.WithString("repo", mcp.Description("for repo methods")),
mcp.WithString("org", mcp.Description("organization name (required for org methods)")), mcp.WithString("org", mcp.Description("for org methods")),
mcp.WithNumber("id", mcp.Description("label ID (required for edit/delete methods)")), mcp.WithNumber("id", mcp.Description("for edit/delete")),
mcp.WithString("name", mcp.Description("label name (required for create, optional for edit)")), mcp.WithString("name", mcp.Description("required for create")),
mcp.WithString("color", mcp.Description("label color hex code e.g. #RRGGBB (required for create, optional for edit)")), mcp.WithString("color", mcp.Description("hex (#RRGGBB); required for create")),
mcp.WithString("description", mcp.Description("label description")), mcp.WithString("description"),
mcp.WithBoolean("exclusive", mcp.Description("whether the label is exclusive (org labels only)")), mcp.WithBoolean("exclusive", mcp.Description("exclusive (org only)")),
mcp.WithBoolean("is_archived", mcp.Description("whether the label is archived (for create/edit repo label methods)")), mcp.WithBoolean("is_archived", mcp.Description("archived (repo only)")),
) )
) )
+19 -17
View File
@@ -26,30 +26,30 @@ const (
var ( var (
MilestoneReadTool = mcp.NewTool( MilestoneReadTool = mcp.NewTool(
MilestoneReadToolName, 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.WithToolAnnotation(annotation.ReadOnly("Read milestones")),
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("get", "list")), mcp.WithString("method", mcp.Required(), mcp.Enum("get", "list")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithNumber("id", mcp.Description("milestone id (required for 'get')")), mcp.WithNumber("id", mcp.Description("for 'get'")),
mcp.WithString("state", mcp.Description("milestone state (for 'list')"), mcp.DefaultString("all")), mcp.WithString("state", mcp.DefaultString("all")),
mcp.WithString("name", mcp.Description("milestone name filter (for 'list')")), mcp.WithString("name", mcp.Description("name filter (for 'list')")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
) )
MilestoneWriteTool = mcp.NewTool( MilestoneWriteTool = mcp.NewTool(
MilestoneWriteToolName, 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.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("method", mcp.Required(), mcp.Enum("create", "update", "edit", "delete")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithNumber("id", mcp.Description("milestone id (required for 'edit', 'delete')")), mcp.WithNumber("id", mcp.Description("for 'update'/'delete'")),
mcp.WithString("title", mcp.Description("milestone title (required for 'create')")), mcp.WithString("title", mcp.Description("for 'create'")),
mcp.WithString("description", mcp.Description("milestone description")), mcp.WithString("description"),
mcp.WithString("due_on", mcp.Description("due date")), 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 { switch method {
case "create": case "create":
return createMilestoneFn(ctx, req) return createMilestoneFn(ctx, req)
case "update":
return editMilestoneFn(ctx, req)
case "edit": case "edit":
return editMilestoneFn(ctx, req) return editMilestoneFn(ctx, req)
case "delete": case "delete":
+17 -17
View File
@@ -27,29 +27,29 @@ const (
var ( var (
NotificationReadTool = mcp.NewTool( NotificationReadTool = mcp.NewTool(
NotificationReadToolName, 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.WithToolAnnotation(annotation.ReadOnly("Read notifications")),
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list", "get")), mcp.WithString("method", mcp.Required(), mcp.Enum("list", "get")),
mcp.WithString("owner", mcp.Description("repository owner (for 'list' to scope to a repo)")), mcp.WithString("owner", mcp.Description("scope 'list' to a repo")),
mcp.WithString("repo", mcp.Description("repository name (for 'list' to scope to a repo)")), mcp.WithString("repo", mcp.Description("scope 'list' to a repo")),
mcp.WithNumber("id", mcp.Description("notification thread ID (required for 'get')")), mcp.WithNumber("id", mcp.Description("thread ID (for 'get')")),
mcp.WithString("status", mcp.Description("filter by status (for 'list')"), mcp.Enum("unread", "read", "pinned")), mcp.WithString("status", 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("subject_type", mcp.Enum("Issue", "Pull", "Commit", "Repository")),
mcp.WithString("since", mcp.Description("filter notifications updated after this ISO 8601 timestamp (for 'list')")), mcp.WithString("since", mcp.Description("updated after ISO 8601")),
mcp.WithString("before", mcp.Description("filter notifications updated before this ISO 8601 timestamp (for 'list')")), mcp.WithString("before", mcp.Description("updated before ISO 8601")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
) )
NotificationWriteTool = mcp.NewTool( NotificationWriteTool = mcp.NewTool(
NotificationWriteToolName, 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.WithToolAnnotation(annotation.Write("Manage notifications")),
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("mark_read", "mark_all_read")), mcp.WithString("method", mcp.Required(), mcp.Enum("mark_read", "mark_all_read")),
mcp.WithNumber("id", mcp.Description("notification thread ID (required for 'mark_read')")), mcp.WithNumber("id", mcp.Description("thread ID (for 'mark_read')")),
mcp.WithString("owner", mcp.Description("repository owner (for 'mark_all_read' to scope to a repo)")), mcp.WithString("owner", mcp.Description("scope 'mark_all_read' to a repo")),
mcp.WithString("repo", mcp.Description("repository name (for 'mark_all_read' to scope to a repo)")), mcp.WithString("repo", mcp.Description("scope 'mark_all_read' 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("last_read_at", mcp.Description("ISO 8601; defaults to now")),
) )
) )
+15 -15
View File
@@ -29,26 +29,26 @@ var (
PackageReadTool = mcp.NewTool( PackageReadTool = mcp.NewTool(
PackageReadToolName, PackageReadToolName,
mcp.WithToolAnnotation(annotation.ReadOnly("Read package registry")), 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.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.Description("operation to perform"), mcp.Enum("list", "list_versions", "get")), mcp.WithString("method", mcp.Required(), mcp.Enum("list", "list_versions", "get")),
mcp.WithString("owner", mcp.Required(), mcp.Description("package owner (user or org)")), mcp.WithString("owner", mcp.Required(), mcp.Description("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("type", mcp.Description("container/npm/maven/pypi/cargo/generic; required except 'list'")),
mcp.WithString("name", mcp.Description("package name, slashes encoded automatically e.g. 'my-repo/my-image' (required for 'list_versions' and 'get')")), mcp.WithString("name", mcp.Description("slashes auto-encoded; required except 'list'")),
mcp.WithString("version", mcp.Description("package version (required for 'get')")), mcp.WithString("version", mcp.Description("for 'get'")),
mcp.WithString("q", mcp.Description("search query (for 'list')")), mcp.WithString("q", mcp.Description("search query")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)),
) )
PackageWriteTool = mcp.NewTool( PackageWriteTool = mcp.NewTool(
PackageWriteToolName, PackageWriteToolName,
mcp.WithToolAnnotation(annotation.Destructive("Delete a package version")), 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.WithDescription("Delete a package version (irreversible)."),
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("delete")), mcp.WithString("method", mcp.Required(), mcp.Enum("delete")),
mcp.WithString("owner", mcp.Required(), mcp.Description("package owner (user or org)")), mcp.WithString("owner", mcp.Required(), mcp.Description("user or org")),
mcp.WithString("type", mcp.Required(), mcp.Description("package type, e.g. container, npm, maven, pypi, cargo, generic")), mcp.WithString("type", mcp.Required(), mcp.Description("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("name", mcp.Required(), mcp.Description("slashes auto-encoded")),
mcp.WithString("version", mcp.Required(), mcp.Description("package version")), mcp.WithString("version", mcp.Required()),
) )
) )
+4 -4
View File
@@ -94,10 +94,10 @@ func TestPackageReadList(t *testing.T) {
t.Run("with pagination", func(t *testing.T) { t.Run("with pagination", func(t *testing.T) {
req := mcp.CallToolRequest{} req := mcp.CallToolRequest{}
req.Params.Arguments = map[string]any{ req.Params.Arguments = map[string]any{
"method": "list", "method": "list",
"owner": "test-org", "owner": "test-org",
"page": float64(2), "page": float64(2),
"perPage": float64(10), "per_page": float64(10),
} }
_, err := packageReadFn(ctx, req) _, err := packageReadFn(ctx, req)
+164 -73
View File
@@ -30,82 +30,81 @@ const (
var ( var (
ListRepoPullRequestsTool = mcp.NewTool( ListRepoPullRequestsTool = mcp.NewTool(
ListRepoPullRequestsToolName, ListRepoPullRequestsToolName,
mcp.WithDescription("List repository pull requests"),
mcp.WithToolAnnotation(annotation.ReadOnly("List pull requests")), mcp.WithToolAnnotation(annotation.ReadOnly("List pull requests")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("state", mcp.Description("state"), mcp.Enum("open", "closed", "all"), mcp.DefaultString("all")), mcp.WithString("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.WithString("sort", mcp.Enum("oldest", "recentupdate", "leastupdate", "mostcomment", "leastcomment", "priority"), mcp.DefaultString("recentupdate")),
mcp.WithNumber("milestone", mcp.Description("milestone")), mcp.WithNumber("milestone"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
) )
PullRequestReadTool = mcp.NewTool( PullRequestReadTool = mcp.NewTool(
PullRequestReadToolName, 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.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("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("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithNumber("index", mcp.Required(), mcp.Description("pull request index")), mcp.WithNumber("pull_number", mcp.Required()),
mcp.WithNumber("review_id", mcp.Description("review ID (required for 'get_review', 'get_review_comments')")), mcp.WithNumber("review_id", mcp.Description("for 'get_review'/'get_review_comments'")),
mcp.WithBoolean("binary", mcp.Description("whether to include binary file changes (for 'get_diff')")), mcp.WithBoolean("binary", mcp.Description("include binary diff")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
) )
PullRequestWriteTool = mcp.NewTool( PullRequestWriteTool = mcp.NewTool(
PullRequestWriteToolName, 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.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("method", mcp.Required(), mcp.Enum("create", "update", "close", "reopen", "merge", "update_branch", "add_reviewers", "remove_reviewers")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithNumber("index", mcp.Description("pull request index (required for all methods except 'create')")), mcp.WithNumber("pull_number", mcp.Description("required except for 'create'")),
mcp.WithString("title", mcp.Description("PR title (required for 'create', optional for 'update', 'merge')")), mcp.WithString("title", mcp.Description("required for 'create'; optional for 'update'/'merge'")),
mcp.WithString("body", mcp.Description("PR body (required for 'create', optional for 'update')")), mcp.WithString("body", mcp.Description("required for 'create'; optional for 'update'")),
mcp.WithString("head", mcp.Description("PR head branch (required for 'create')")), mcp.WithString("head", mcp.Description("head branch (required for 'create')")),
mcp.WithString("base", mcp.Description("PR base branch (required for 'create', optional for 'update')")), mcp.WithString("base", mcp.Description("base branch (required for 'create')")),
mcp.WithString("assignee", mcp.Description("username to assign (for 'update')")), mcp.WithString("assignee", mcp.Description("for 'update'")),
mcp.WithArray("assignees", mcp.Description("usernames to assign (for 'update')"), mcp.Items(map[string]any{"type": "string"})), mcp.WithArray("assignees", mcp.Description("for 'update'"), mcp.Items(map[string]any{"type": "string"})),
mcp.WithNumber("milestone", mcp.Description("milestone number (for 'update')")), mcp.WithNumber("milestone", mcp.Description("for 'update'")),
mcp.WithString("state", mcp.Description("PR state (for 'update')"), mcp.Enum("open", "closed")), mcp.WithString("state", mcp.Description("for 'update'"), mcp.Enum("open", "closed")),
mcp.WithBoolean("allow_maintainer_edit", mcp.Description("allow maintainer to edit (for 'update')")), mcp.WithBoolean("allow_maintainer_edit", mcp.Description("for 'update'")),
mcp.WithArray("labels", mcp.Description("array of label IDs (for 'create', 'update')"), mcp.Items(map[string]any{"type": "number"})), mcp.WithArray("labels", mcp.Description("label IDs"), mcp.Items(map[string]any{"type": "number"})),
mcp.WithString("deadline", mcp.Description("due date in ISO 8601 format (for 'create', 'update')")), mcp.WithString("deadline", mcp.Description("ISO 8601")),
mcp.WithBoolean("remove_deadline", mcp.Description("unset due date (for 'update')")), mcp.WithBoolean("remove_deadline", mcp.Description("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("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 (for 'merge') or dismissal reason")), mcp.WithString("message", mcp.Description("merge commit message or dismissal reason")),
mcp.WithBoolean("delete_branch", mcp.Description("delete branch after merge (for 'merge')")), mcp.WithBoolean("delete_branch", mcp.Description("for 'merge'")),
mcp.WithBoolean("force_merge", mcp.Description("force merge even if checks are not passing (for 'merge')")), mcp.WithBoolean("force_merge", mcp.Description("merge even if checks fail")),
mcp.WithBoolean("merge_when_checks_succeed", mcp.Description("auto-merge when checks succeed (for 'merge')")), mcp.WithBoolean("merge_when_checks_succeed", mcp.Description("for 'merge'")),
mcp.WithString("head_commit_id", mcp.Description("expected head commit SHA for merge conflict detection (for 'merge')")), mcp.WithString("head_commit_id", mcp.Description("expected head SHA for conflict detection")),
mcp.WithArray("reviewers", mcp.Description("reviewer usernames (for 'add_reviewers', 'remove_reviewers')"), mcp.Items(map[string]any{"type": "string"})), mcp.WithArray("reviewers", mcp.Description("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.WithArray("team_reviewers", mcp.Description("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.WithBoolean("draft", mcp.Description("uses 'WIP: ' title prefix")),
) )
PullRequestReviewWriteTool = mcp.NewTool( PullRequestReviewWriteTool = mcp.NewTool(
PullRequestReviewWriteToolName, 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.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("method", mcp.Required(), mcp.Enum("create", "submit", "delete", "dismiss")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithNumber("index", mcp.Required(), mcp.Description("pull request index")), mcp.WithNumber("pull_number", mcp.Required()),
mcp.WithNumber("review_id", mcp.Description("review ID (required for 'submit', 'delete', 'dismiss')")), mcp.WithNumber("review_id", mcp.Description("required except for 'create'")),
mcp.WithString("state", mcp.Description("review state"), mcp.Enum("APPROVED", "REQUEST_CHANGES", "COMMENT", "PENDING")), mcp.WithString("state", mcp.Enum("APPROVED", "REQUEST_CHANGES", "COMMENT", "PENDING")),
mcp.WithString("body", mcp.Description("review body/comment")), mcp.WithString("body"),
mcp.WithString("commit_id", mcp.Description("commit SHA to review (for 'create')")), mcp.WithString("commit_id", mcp.Description("for 'create'")),
mcp.WithString("message", mcp.Description("dismissal reason (for 'dismiss')")), mcp.WithString("message", mcp.Description("dismissal reason")),
mcp.WithArray("comments", mcp.Description("inline review comments (for 'create')"), mcp.Items(map[string]any{ mcp.WithArray("comments", mcp.Description("inline comments (for 'create')"), mcp.Items(map[string]any{
"type": "object", "type": "object",
"properties": map[string]any{ "properties": map[string]any{
"path": map[string]any{"type": "string", "description": "file path to comment on"}, "path": map[string]any{"type": "string"},
"body": map[string]any{"type": "string", "description": "comment body"}, "body": map[string]any{"type": "string"},
"old_line_num": map[string]any{"type": "number", "description": "line number in the old file (for deletions/changes)"}, "old_line_num": map[string]any{"type": "number", "description": "old-file line (deletions)"},
"new_line_num": map[string]any{"type": "number", "description": "line number in the new file (for additions/changes)"}, "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) return getPullRequestByIndexFn(ctx, req)
case "get_diff": case "get_diff":
return getPullRequestDiffFn(ctx, req) return getPullRequestDiffFn(ctx, req)
case "get_files":
return getPullRequestFilesFn(ctx, req)
case "get_status":
return getPullRequestStatusFn(ctx, req)
case "get_reviews": case "get_reviews":
return listPullRequestReviewsFn(ctx, req) return listPullRequestReviewsFn(ctx, req)
case "get_review": case "get_review":
@@ -167,6 +170,8 @@ func pullRequestWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
return reopenPullRequestFn(ctx, req) return reopenPullRequestFn(ctx, req)
case "merge": case "merge":
return mergePullRequestFn(ctx, req) return mergePullRequestFn(ctx, req)
case "update_branch":
return updatePullRequestBranchFn(ctx, req)
case "add_reviewers": case "add_reviewers":
return createPullRequestReviewerFn(ctx, req) return createPullRequestReviewerFn(ctx, req)
case "remove_reviewers": case "remove_reviewers":
@@ -185,7 +190,7 @@ func closePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -215,7 +220,7 @@ func reopenPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -266,7 +271,7 @@ func getPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(args, "index") index, err := params.GetIndex(args, "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -303,7 +308,7 @@ func getPullRequestDiffFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(args, "index") index, err := params.GetIndex(args, "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -446,7 +451,7 @@ func createPullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(args, "index") index, err := params.GetIndex(args, "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -489,7 +494,7 @@ func deletePullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(args, "index") index, err := params.GetIndex(args, "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -532,7 +537,7 @@ func listPullRequestReviewsFn(ctx context.Context, req mcp.CallToolRequest) (*mc
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(args, "index") index, err := params.GetIndex(args, "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -567,7 +572,7 @@ func getPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(args, "index") index, err := params.GetIndex(args, "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -600,7 +605,7 @@ func listPullRequestReviewCommentsFn(ctx context.Context, req mcp.CallToolReques
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(args, "index") index, err := params.GetIndex(args, "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -633,7 +638,7 @@ func createPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(args, "index") index, err := params.GetIndex(args, "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -698,7 +703,7 @@ func submitPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(args, "index") index, err := params.GetIndex(args, "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -742,7 +747,7 @@ func deletePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(args, "index") index, err := params.GetIndex(args, "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -782,7 +787,7 @@ func dismissPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(args, "index") index, err := params.GetIndex(args, "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -827,7 +832,7 @@ func mergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(args, "index") index, err := params.GetIndex(args, "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -891,7 +896,7 @@ func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(args, "index") index, err := params.GetIndex(args, "pull_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -960,3 +965,89 @@ func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
return to.TextResult(slimPullRequest(pr)) 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)
}
+25 -25
View File
@@ -80,11 +80,11 @@ func Test_editPullRequestFn(t *testing.T) {
req := mcp.CallToolRequest{ req := mcp.CallToolRequest{
Params: mcp.CallToolParams{ Params: mcp.CallToolParams{
Arguments: map[string]any{ Arguments: map[string]any{
"owner": owner, "owner": owner,
"repo": repo, "repo": repo,
"index": ii.val, "pull_number": ii.val,
"title": "WIP: my feature", "title": "WIP: my feature",
"state": "open", "state": "open",
}, },
}, },
} }
@@ -195,7 +195,7 @@ func Test_mergePullRequestFn(t *testing.T) {
Arguments: map[string]any{ Arguments: map[string]any{
"owner": owner, "owner": owner,
"repo": repo, "repo": repo,
"index": ii.val, "pull_number": ii.val,
"merge_style": "squash", "merge_style": "squash",
"title": "feat: my squashed commit", "title": "feat: my squashed commit",
"message": "Squash merge of PR #5", "message": "Squash merge of PR #5",
@@ -308,7 +308,7 @@ func Test_mergePullRequestFn_newParams(t *testing.T) {
Arguments: map[string]any{ Arguments: map[string]any{
"owner": owner, "owner": owner,
"repo": repo, "repo": repo,
"index": float64(index), "pull_number": float64(index),
"merge_style": "merge", "merge_style": "merge",
"force_merge": true, "force_merge": true,
"merge_when_checks_succeed": true, "merge_when_checks_succeed": true,
@@ -616,9 +616,9 @@ func Test_editPullRequestFn_draft(t *testing.T) {
}() }()
args := map[string]any{ args := map[string]any{
"owner": owner, "owner": owner,
"repo": repo, "repo": repo,
"index": float64(index), "pull_number": float64(index),
} }
if tc.title != "" { if tc.title != "" {
args["title"] = tc.title args["title"] = tc.title
@@ -720,10 +720,10 @@ func Test_getPullRequestDiffFn(t *testing.T) {
req := mcp.CallToolRequest{ req := mcp.CallToolRequest{
Params: mcp.CallToolParams{ Params: mcp.CallToolParams{
Arguments: map[string]any{ Arguments: map[string]any{
"owner": owner, "owner": owner,
"repo": repo, "repo": repo,
"index": ii.val, "pull_number": ii.val,
"binary": true, "binary": true,
}, },
}, },
} }
@@ -805,7 +805,7 @@ func Test_getPullRequestByIndexFn_includesAttachments(t *testing.T) {
defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }() defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }()
req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{ 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) res, err := getPullRequestByIndexFn(context.Background(), req)
if err != nil { 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 }() defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }()
req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{ 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) res, err := getPullRequestByIndexFn(context.Background(), req)
if err != nil { 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 }() defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }()
req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{ 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) res, err := getPullRequestByIndexFn(context.Background(), req)
if err != nil { if err != nil {
@@ -954,10 +954,10 @@ func Test_closePullRequestFn(t *testing.T) {
req := mcp.CallToolRequest{ req := mcp.CallToolRequest{
Params: mcp.CallToolParams{ Params: mcp.CallToolParams{
Arguments: map[string]any{ Arguments: map[string]any{
"method": "close", "method": "close",
"owner": owner, "owner": owner,
"repo": repo, "repo": repo,
"index": float64(index), "pull_number": float64(index),
}, },
}, },
} }
@@ -1018,10 +1018,10 @@ func Test_reopenPullRequestFn(t *testing.T) {
req := mcp.CallToolRequest{ req := mcp.CallToolRequest{
Params: mcp.CallToolParams{ Params: mcp.CallToolParams{
Arguments: map[string]any{ Arguments: map[string]any{
"method": "reopen", "method": "reopen",
"owner": owner, "owner": owner,
"repo": repo, "repo": repo,
"index": float64(index), "pull_number": float64(index),
}, },
}, },
} }
+11 -14
View File
@@ -24,31 +24,28 @@ const (
var ( var (
CreateBranchTool = mcp.NewTool( CreateBranchTool = mcp.NewTool(
CreateBranchToolName, CreateBranchToolName,
mcp.WithDescription("Create branch"),
mcp.WithToolAnnotation(annotation.Write("Create a new branch")), mcp.WithToolAnnotation(annotation.Write("Create a new branch")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("branch", mcp.Required(), mcp.Description("Name of the branch to create")), mcp.WithString("branch", mcp.Required()),
mcp.WithString("old_branch", mcp.Required(), mcp.Description("Name of the old branch to create from")), mcp.WithString("old_branch", mcp.Description("source branch (default: repo default)")),
) )
DeleteBranchTool = mcp.NewTool( DeleteBranchTool = mcp.NewTool(
DeleteBranchToolName, DeleteBranchToolName,
mcp.WithDescription("Delete branch"),
mcp.WithToolAnnotation(annotation.Destructive("Delete a branch")), mcp.WithToolAnnotation(annotation.Destructive("Delete a branch")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("branch", mcp.Required(), mcp.Description("Name of the branch to delete")), mcp.WithString("branch", mcp.Required()),
) )
ListBranchesTool = mcp.NewTool( ListBranchesTool = mcp.NewTool(
ListBranchesToolName, ListBranchesToolName,
mcp.WithDescription("List branches"),
mcp.WithToolAnnotation(annotation.ReadOnly("List repository branches")), mcp.WithToolAnnotation(annotation.ReadOnly("List repository branches")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
) )
) )
+12 -21
View File
@@ -23,23 +23,21 @@ const (
var ( var (
ListRepoCommitsTool = mcp.NewTool( ListRepoCommitsTool = mcp.NewTool(
ListRepoCommitsToolName, ListRepoCommitsToolName,
mcp.WithDescription("List repository commits"),
mcp.WithToolAnnotation(annotation.ReadOnly("List repository commits")), mcp.WithToolAnnotation(annotation.ReadOnly("List repository commits")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("sha", mcp.Description("SHA or branch to start listing commits from")), mcp.WithString("sha", mcp.Description("starting SHA or branch")),
mcp.WithString("path", mcp.Description("path indicates that only commits that include the path's file/dir should be returned.")), mcp.WithString("path", mcp.Description("only commits touching this path")),
mcp.WithNumber("page", mcp.Required(), mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)),
) )
GetCommitTool = mcp.NewTool( GetCommitTool = mcp.NewTool(
GetCommitToolName, GetCommitToolName,
mcp.WithDescription("Get details of a specific commit"),
mcp.WithToolAnnotation(annotation.ReadOnly("Get commit details")), mcp.WithToolAnnotation(annotation.ReadOnly("Get commit details")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("sha", mcp.Required(), mcp.Description("commit SHA")), mcp.WithString("sha", mcp.Required()),
) )
) )
@@ -65,20 +63,13 @@ func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
page, err := params.GetIndex(args, "page") page, pageSize := params.GetPagination(args, 30)
if err != nil {
return to.ErrorResult(err)
}
pageSize, err := params.GetIndex(args, "perPage")
if err != nil {
return to.ErrorResult(err)
}
sha, _ := args["sha"].(string) sha, _ := args["sha"].(string)
path, _ := args["path"].(string) path, _ := args["path"].(string)
opt := gitea_sdk.ListCommitOptions{ opt := gitea_sdk.ListCommitOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
SHA: sha, SHA: sha,
Path: path, Path: path,
+27 -29
View File
@@ -29,49 +29,47 @@ const (
var ( var (
GetFileContentTool = mcp.NewTool( GetFileContentTool = mcp.NewTool(
GetFileToolName, GetFileToolName,
mcp.WithDescription("Get file Content and Metadata"), mcp.WithDescription("Get file content and metadata"),
mcp.WithToolAnnotation(annotation.ReadOnly("Get file content")), mcp.WithToolAnnotation(annotation.ReadOnly("Get file content")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("ref", mcp.Required(), mcp.Description("ref can be branch/tag/commit")), mcp.WithString("ref", mcp.Required(), mcp.Description("branch, tag, or commit SHA")),
mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")), mcp.WithString("path", mcp.Required()),
mcp.WithBoolean("withLines", mcp.Description("whether to return file content with lines")), mcp.WithBoolean("withLines", mcp.Description("return numbered lines")),
) )
GetDirContentTool = mcp.NewTool( GetDirContentTool = mcp.NewTool(
GetDirToolName, GetDirToolName,
mcp.WithDescription("Get a list of entries in a directory"),
mcp.WithToolAnnotation(annotation.ReadOnly("Get directory contents")), mcp.WithToolAnnotation(annotation.ReadOnly("Get directory contents")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("ref", mcp.Required(), mcp.Description("ref can be branch/tag/commit")), mcp.WithString("ref", mcp.Required(), mcp.Description("branch, tag, or commit SHA")),
mcp.WithString("filePath", mcp.Required(), mcp.Description("directory path")), mcp.WithString("path", mcp.Required()),
) )
CreateOrUpdateFileTool = mcp.NewTool( CreateOrUpdateFileTool = mcp.NewTool(
CreateOrUpdateFileToolName, 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.WithToolAnnotation(annotation.Write("Create or update a file")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")), mcp.WithString("path", mcp.Required()),
mcp.WithString("content", mcp.Required(), mcp.Description("file content")), mcp.WithString("content", mcp.Required()),
mcp.WithString("message", mcp.Required(), mcp.Description("commit message")), mcp.WithString("message", mcp.Required(), mcp.Description("commit message")),
mcp.WithString("branch_name", mcp.Required(), mcp.Description("branch name")), mcp.WithString("branch_name", mcp.Required()),
mcp.WithString("sha", mcp.Description("SHA of the existing file (required for update, omit for create)")), mcp.WithString("sha", mcp.Description("existing file SHA (omit to create)")),
mcp.WithString("new_branch_name", mcp.Description("new branch name (for create only)")), mcp.WithString("new_branch_name", mcp.Description("new branch (create only)")),
) )
DeleteFileTool = mcp.NewTool( DeleteFileTool = mcp.NewTool(
DeleteFileToolName, DeleteFileToolName,
mcp.WithDescription("Delete file"),
mcp.WithToolAnnotation(annotation.Destructive("Delete a file")), mcp.WithToolAnnotation(annotation.Destructive("Delete a file")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")), mcp.WithString("path", mcp.Required()),
mcp.WithString("message", mcp.Required(), mcp.Description("commit message")), mcp.WithString("message", mcp.Required(), mcp.Description("commit message")),
mcp.WithString("branch_name", mcp.Required(), mcp.Description("branch name")), mcp.WithString("branch_name", mcp.Required()),
mcp.WithString("sha", mcp.Required(), mcp.Description("sha")), mcp.WithString("sha", mcp.Required()),
) )
) )
@@ -111,7 +109,7 @@ func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
return to.ErrorResult(err) return to.ErrorResult(err)
} }
ref, _ := args["ref"].(string) ref, _ := args["ref"].(string)
filePath, err := params.GetString(args, "filePath") filePath, err := params.GetString(args, "path")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -175,7 +173,7 @@ func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
return to.ErrorResult(err) return to.ErrorResult(err)
} }
ref, _ := args["ref"].(string) ref, _ := args["ref"].(string)
filePath, err := params.GetString(args, "filePath") filePath, err := params.GetString(args, "path")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -201,7 +199,7 @@ func CreateOrUpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
filePath, err := params.GetString(args, "filePath") filePath, err := params.GetString(args, "path")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -261,7 +259,7 @@ func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
filePath, err := params.GetString(args, "filePath") filePath, err := params.GetString(args, "path")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
+24 -28
View File
@@ -26,54 +26,50 @@ const (
var ( var (
CreateReleaseTool = mcp.NewTool( CreateReleaseTool = mcp.NewTool(
CreateReleaseToolName, CreateReleaseToolName,
mcp.WithDescription("Create release"),
mcp.WithToolAnnotation(annotation.Write("Create a release")), mcp.WithToolAnnotation(annotation.Write("Create a release")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")), mcp.WithString("tag_name", mcp.Required()),
mcp.WithString("target", mcp.Required(), mcp.Description("target commitish")), mcp.WithString("target", mcp.Required(), mcp.Description("commitish")),
mcp.WithString("title", mcp.Required(), mcp.Description("release title")), mcp.WithString("title", mcp.Required()),
mcp.WithBoolean("is_draft", mcp.Description("Whether the release is draft"), mcp.DefaultBool(false)), mcp.WithBoolean("is_draft"),
mcp.WithBoolean("is_pre_release", mcp.Description("Whether the release is pre-release"), mcp.DefaultBool(false)), mcp.WithBoolean("is_pre_release"),
mcp.WithString("body", mcp.Description("release body")), mcp.WithString("body"),
) )
DeleteReleaseTool = mcp.NewTool( DeleteReleaseTool = mcp.NewTool(
DeleteReleaseToolName, DeleteReleaseToolName,
mcp.WithDescription("Delete release"),
mcp.WithToolAnnotation(annotation.Destructive("Delete a release")), mcp.WithToolAnnotation(annotation.Destructive("Delete a release")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithNumber("id", mcp.Required(), mcp.Description("release id")), mcp.WithNumber("id", mcp.Required()),
) )
GetReleaseTool = mcp.NewTool( GetReleaseTool = mcp.NewTool(
GetReleaseToolName, GetReleaseToolName,
mcp.WithDescription("Get release"), mcp.WithDescription("Get a release by ID"),
mcp.WithToolAnnotation(annotation.ReadOnly("Get release details")), mcp.WithToolAnnotation(annotation.ReadOnly("Get release details")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithNumber("id", mcp.Required(), mcp.Description("release id")), mcp.WithNumber("id", mcp.Required()),
) )
GetLatestReleaseTool = mcp.NewTool( GetLatestReleaseTool = mcp.NewTool(
GetLatestReleaseToolName, GetLatestReleaseToolName,
mcp.WithDescription("Get latest release"),
mcp.WithToolAnnotation(annotation.ReadOnly("Get latest release")), mcp.WithToolAnnotation(annotation.ReadOnly("Get latest release")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
) )
ListReleasesTool = mcp.NewTool( ListReleasesTool = mcp.NewTool(
ListReleasesToolName, ListReleasesToolName,
mcp.WithDescription("List releases"),
mcp.WithToolAnnotation(annotation.ReadOnly("List releases")), mcp.WithToolAnnotation(annotation.ReadOnly("List releases")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithBoolean("is_draft", mcp.Description("Whether the release is draft"), mcp.DefaultBool(false)), mcp.WithBoolean("is_draft"),
mcp.WithBoolean("is_pre_release", mcp.Description("Whether the release is pre-release"), mcp.DefaultBool(false)), mcp.WithBoolean("is_pre_release"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.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) pIsPreRelease = new(isPreRelease)
} }
page := params.GetOptionalInt(args, "page", 1) page := params.GetOptionalInt(args, "page", 1)
pageSize := params.GetOptionalInt(args, "perPage", 20) pageSize := params.GetOptionalInt(args, "per_page", 20)
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
+25 -36
View File
@@ -29,48 +29,44 @@ const (
var ( var (
CreateRepoTool = mcp.NewTool( CreateRepoTool = mcp.NewTool(
CreateRepoToolName, CreateRepoToolName,
mcp.WithDescription("Create repository in personal account or organization"),
mcp.WithToolAnnotation(annotation.Write("Create a new repository")), mcp.WithToolAnnotation(annotation.Write("Create a new repository")),
mcp.WithString("name", mcp.Required(), mcp.Description("Name of the repository to create")), mcp.WithString("name", mcp.Required()),
mcp.WithString("description", mcp.Description("Description of the repository to create")), mcp.WithString("description"),
mcp.WithBoolean("private", mcp.Description("Whether the repository is private")), mcp.WithBoolean("private"),
mcp.WithString("issue_labels", mcp.Description("Issue Label set to use")), mcp.WithString("issue_labels"),
mcp.WithBoolean("auto_init", mcp.Description("Whether the repository should be auto-intialized?")), mcp.WithBoolean("auto_init"),
mcp.WithBoolean("template", mcp.Description("Whether the repository is template")), mcp.WithBoolean("template"),
mcp.WithString("gitignores", mcp.Description("Gitignores to use")), mcp.WithString("gitignores"),
mcp.WithString("license", mcp.Description("License to use")), mcp.WithString("license"),
mcp.WithString("readme", mcp.Description("Readme of the repository to create")), mcp.WithString("readme"),
mcp.WithString("default_branch", mcp.Description("DefaultBranch of the repository (used when initializes and in template)")), mcp.WithString("default_branch"),
mcp.WithString("trust_model", mcp.Description("Trust model for verifying GPG signatures"), mcp.Enum("default", "collaborator", "committer", "collaboratorcommitter")), mcp.WithString("trust_model", mcp.Enum("default", "collaborator", "committer", "collaboratorcommitter")),
mcp.WithString("object_format_name", mcp.Description("Object format: sha1 or sha256"), mcp.Enum("sha1", "sha256")), mcp.WithString("object_format_name", mcp.Enum("sha1", "sha256")),
mcp.WithString("organization", mcp.Description("Organization name to create repository in (optional - defaults to personal account)")), mcp.WithString("organization", mcp.Description("defaults to personal account")),
) )
ForkRepoTool = mcp.NewTool( ForkRepoTool = mcp.NewTool(
ForkRepoToolName, ForkRepoToolName,
mcp.WithDescription("Fork repository"),
mcp.WithToolAnnotation(annotation.Write("Fork a repository")), mcp.WithToolAnnotation(annotation.Write("Fork a repository")),
mcp.WithString("user", mcp.Required(), mcp.Description("User name of the repository to fork")), mcp.WithString("user", mcp.Required(), mcp.Description("owner of source repo")),
mcp.WithString("repo", mcp.Required(), mcp.Description("Repository name to fork")), mcp.WithString("repo", mcp.Required()),
mcp.WithString("organization", mcp.Description("Organization name to fork")), mcp.WithString("organization", mcp.Description("target org")),
mcp.WithString("name", mcp.Description("Name of the forked repository")), mcp.WithString("name", mcp.Description("fork name")),
) )
ListMyReposTool = mcp.NewTool( ListMyReposTool = mcp.NewTool(
ListMyReposToolName, ListMyReposToolName,
mcp.WithDescription("List my repositories"),
mcp.WithToolAnnotation(annotation.ReadOnly("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("page", mcp.Description(params.PageDesc), 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("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)),
) )
ListOrgReposTool = mcp.NewTool( ListOrgReposTool = mcp.NewTool(
ListOrgReposToolName, ListOrgReposToolName,
mcp.WithDescription("List repositories of an organization"),
mcp.WithToolAnnotation(annotation.ReadOnly("List organization repositories")), mcp.WithToolAnnotation(annotation.ReadOnly("List organization repositories")),
mcp.WithString("org", mcp.Required(), mcp.Description("Organization name")), mcp.WithString("org", mcp.Required()),
mcp.WithNumber("page", mcp.Required(), mcp.Description("Page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("pageSize", mcp.Required(), mcp.Description("Page size number"), mcp.DefaultNumber(100), 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 { if !ok {
return to.ErrorResult(errors.New("organization name is required")) return to.ErrorResult(errors.New("organization name is required"))
} }
page, ok := req.GetArguments()["page"].(float64) page, pageSize := params.GetPagination(req.GetArguments(), 100)
if !ok {
page = 1
}
pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok {
pageSize = 100
}
opt := gitea_sdk.ListOrgReposOptions{ opt := gitea_sdk.ListOrgReposOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
} }
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
+16 -20
View File
@@ -25,41 +25,37 @@ const (
var ( var (
CreateTagTool = mcp.NewTool( CreateTagTool = mcp.NewTool(
CreateTagToolName, CreateTagToolName,
mcp.WithDescription("Create tag"),
mcp.WithToolAnnotation(annotation.Write("Create a tag")), mcp.WithToolAnnotation(annotation.Write("Create a tag")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")), mcp.WithString("tag_name", mcp.Required()),
mcp.WithString("target", mcp.Description("target commitish"), mcp.DefaultString("")), mcp.WithString("target", mcp.Description("commitish")),
mcp.WithString("message", mcp.Description("tag message"), mcp.DefaultString("")), mcp.WithString("message", mcp.Description("tag message")),
) )
DeleteTagTool = mcp.NewTool( DeleteTagTool = mcp.NewTool(
DeleteTagToolName, DeleteTagToolName,
mcp.WithDescription("Delete tag"),
mcp.WithToolAnnotation(annotation.Destructive("Delete a tag")), mcp.WithToolAnnotation(annotation.Destructive("Delete a tag")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")), mcp.WithString("tag_name", mcp.Required()),
) )
GetTagTool = mcp.NewTool( GetTagTool = mcp.NewTool(
GetTagToolName, GetTagToolName,
mcp.WithDescription("Get tag"),
mcp.WithToolAnnotation(annotation.ReadOnly("Get tag details")), mcp.WithToolAnnotation(annotation.ReadOnly("Get tag details")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")), mcp.WithString("tag_name", mcp.Required()),
) )
ListTagsTool = mcp.NewTool( ListTagsTool = mcp.NewTool(
ListTagsToolName, ListTagsToolName,
mcp.WithDescription("List tags"),
mcp.WithToolAnnotation(annotation.ReadOnly("List tags")), mcp.WithToolAnnotation(annotation.ReadOnly("List tags")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.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) return to.ErrorResult(err)
} }
page := params.GetOptionalInt(args, "page", 1) page := params.GetOptionalInt(args, "page", 1)
pageSize := params.GetOptionalInt(args, "perPage", 20) pageSize := params.GetOptionalInt(args, "per_page", 20)
client, err := gitea.ClientFromContext(ctx) client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
+6 -7
View File
@@ -21,14 +21,13 @@ const (
var GetRepoTreeTool = mcp.NewTool( var GetRepoTreeTool = mcp.NewTool(
GetRepoTreeToolName, GetRepoTreeToolName,
mcp.WithDescription("Get the file tree of a repository"),
mcp.WithToolAnnotation(annotation.ReadOnly("Get repository file tree")), mcp.WithToolAnnotation(annotation.ReadOnly("Get repository file tree")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("tree_sha", mcp.Required(), mcp.Description("SHA, branch name, or tag name")), mcp.WithString("tree_sha", mcp.Required(), mcp.Description("SHA, branch, or tag")),
mcp.WithBoolean("recursive", mcp.Description("whether to get the tree recursively")), mcp.WithBoolean("recursive"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
) )
func init() { func init() {
+28 -31
View File
@@ -29,51 +29,48 @@ const (
var ( var (
SearchUsersTool = mcp.NewTool( SearchUsersTool = mcp.NewTool(
SearchUsersToolName, SearchUsersToolName,
mcp.WithDescription("search users"),
mcp.WithToolAnnotation(annotation.ReadOnly("Search users")), mcp.WithToolAnnotation(annotation.ReadOnly("Search users")),
mcp.WithString("keyword", mcp.Required(), mcp.Description("Keyword")), mcp.WithString("query", mcp.Required()),
mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
) )
SearOrgTeamsTool = mcp.NewTool( SearOrgTeamsTool = mcp.NewTool(
SearchOrgTeamsToolName, SearchOrgTeamsToolName,
mcp.WithDescription("search organization teams"),
mcp.WithToolAnnotation(annotation.ReadOnly("Search organization teams")), mcp.WithToolAnnotation(annotation.ReadOnly("Search organization teams")),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")), mcp.WithString("org", mcp.Required()),
mcp.WithString("query", mcp.Required(), mcp.Description("search organization teams")), mcp.WithString("query", mcp.Required()),
mcp.WithBoolean("includeDescription", mcp.Description("include description?")), mcp.WithBoolean("includeDescription"),
mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
) )
SearchReposTool = mcp.NewTool( SearchReposTool = mcp.NewTool(
SearchReposToolName, SearchReposToolName,
mcp.WithDescription("search repos"),
mcp.WithToolAnnotation(annotation.ReadOnly("Search repositories")), mcp.WithToolAnnotation(annotation.ReadOnly("Search repositories")),
mcp.WithString("keyword", mcp.Required(), mcp.Description("Keyword")), mcp.WithString("query", mcp.Required()),
mcp.WithBoolean("keywordIsTopic", mcp.Description("KeywordIsTopic")), mcp.WithBoolean("keywordIsTopic"),
mcp.WithBoolean("keywordInDescription", mcp.Description("KeywordInDescription")), mcp.WithBoolean("keywordInDescription"),
mcp.WithNumber("ownerID", mcp.Description("OwnerID")), mcp.WithNumber("ownerID"),
mcp.WithBoolean("isPrivate", mcp.Description("IsPrivate")), mcp.WithBoolean("isPrivate"),
mcp.WithBoolean("isArchived", mcp.Description("IsArchived")), mcp.WithBoolean("isArchived"),
mcp.WithString("sort", mcp.Description("Sort")), mcp.WithString("sort"),
mcp.WithString("order", mcp.Description("Order")), mcp.WithString("order"),
mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
) )
SearchIssuesTool = mcp.NewTool( SearchIssuesTool = mcp.NewTool(
SearchIssuesToolName, 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.WithToolAnnotation(annotation.ReadOnly("Search issues")),
mcp.WithString("query", mcp.Required(), mcp.Description("search keyword")), mcp.WithString("query", mcp.Required()),
mcp.WithString("state", mcp.Description("filter by state: open, closed, all"), mcp.Enum("open", "closed", "all")), mcp.WithString("state", mcp.Enum("open", "closed", "all")),
mcp.WithString("type", mcp.Description("filter by type: issues, pulls"), mcp.Enum("issues", "pulls")), mcp.WithString("type", mcp.Enum("issues", "pulls")),
mcp.WithString("labels", mcp.Description("comma-separated list of label names")), mcp.WithString("labels", mcp.Description("comma-separated")),
mcp.WithString("owner", mcp.Description("filter by repository owner")), mcp.WithString("owner", mcp.Description("filter by owner")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.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) { func UsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UsersFn") log.Debugf("Called UsersFn")
keyword, err := params.GetString(req.GetArguments(), "keyword") keyword, err := params.GetString(req.GetArguments(), "query")
if err != nil { if err != nil {
return to.ErrorResult(err) 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) { func ReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ReposFn") log.Debugf("Called ReposFn")
keyword, err := params.GetString(req.GetArguments(), "keyword") keyword, err := params.GetString(req.GetArguments(), "query")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
+2 -2
View File
@@ -16,7 +16,7 @@ func TestSearchToolsRequiredFields(t *testing.T) {
{ {
name: "search_users", name: "search_users",
tool: SearchUsersTool, tool: SearchUsersTool,
required: []string{"keyword"}, required: []string{"query"},
}, },
{ {
name: "search_org_teams", name: "search_org_teams",
@@ -26,7 +26,7 @@ func TestSearchToolsRequiredFields(t *testing.T) {
{ {
name: "search_repos", name: "search_repos",
tool: SearchReposTool, tool: SearchReposTool,
required: []string{"keyword"}, required: []string{"query"},
}, },
} }
+20 -20
View File
@@ -27,26 +27,26 @@ const (
var ( var (
TimetrackingReadTool = mcp.NewTool( TimetrackingReadTool = mcp.NewTool(
TimetrackingReadToolName, 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.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("method", mcp.Required(), 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("owner", mcp.Description("for list_* methods")),
mcp.WithString("repo", mcp.Description("repository name (required for 'list_issue_times', 'list_repo_times')")), mcp.WithString("repo", mcp.Description("for list_* methods")),
mcp.WithNumber("index", mcp.Description("issue index (required for 'list_issue_times')")), mcp.WithNumber("issue_number", mcp.Description("for 'list_issue_times'")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
) )
TimetrackingWriteTool = mcp.NewTool( TimetrackingWriteTool = mcp.NewTool(
TimetrackingWriteToolName, 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.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("method", mcp.Required(), 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("owner", mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Description("repository name (required for all methods)")), mcp.WithString("repo", mcp.Description(params.RepoDesc)),
mcp.WithNumber("index", mcp.Description("issue index (required for all methods)")), mcp.WithNumber("issue_number"),
mcp.WithNumber("time", mcp.Description("time to add in seconds (required for 'add_time')")), mcp.WithNumber("time", mcp.Description("seconds (for 'add_time')")),
mcp.WithNumber("id", mcp.Description("tracked time entry ID (required for 'delete_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 { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -132,7 +132,7 @@ func stopStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -157,7 +157,7 @@ func deleteStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -200,7 +200,7 @@ func listTrackedTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -235,7 +235,7 @@ func addTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
@@ -268,7 +268,7 @@ func deleteTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
return to.ErrorResult(err) return to.ErrorResult(err)
} }
index, err := params.GetIndex(req.GetArguments(), "index") index, err := params.GetIndex(req.GetArguments(), "issue_number")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
+5 -5
View File
@@ -36,18 +36,18 @@ var (
// It is registered with a specific name and a description string. // It is registered with a specific name and a description string.
GetMyUserInfoTool = mcp.NewTool( GetMyUserInfoTool = mcp.NewTool(
GetMyUserInfoToolName, GetMyUserInfoToolName,
mcp.WithDescription("Get my user info"), mcp.WithDescription("Get current user"),
mcp.WithToolAnnotation(annotation.ReadOnly("Get current user information")), mcp.WithToolAnnotation(annotation.ReadOnly("Get current user information")),
) )
// GetUserOrgsTool is the MCP tool for listing organizations for the authenticated user. // 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( GetUserOrgsTool = mcp.NewTool(
GetUserOrgsToolName, 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.WithToolAnnotation(annotation.ReadOnly("Get user organizations")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(defaultPage)), mcp.WithNumber("page", mcp.Description(params.PageDesc), 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("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(defaultPageSize)),
) )
) )
-1
View File
@@ -22,7 +22,6 @@ const (
var GetGiteaMCPServerVersionTool = mcp.NewTool( var GetGiteaMCPServerVersionTool = mcp.NewTool(
GetGiteaMCPServerVersion, GetGiteaMCPServerVersion,
mcp.WithDescription("Get Gitea MCP Server Version"),
mcp.WithToolAnnotation(annotation.ReadOnly("Get server version")), mcp.WithToolAnnotation(annotation.ReadOnly("Get server version")),
) )
+12 -12
View File
@@ -27,24 +27,24 @@ const (
var ( var (
WikiReadTool = mcp.NewTool( WikiReadTool = mcp.NewTool(
WikiReadToolName, 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.WithToolAnnotation(annotation.ReadOnly("Read wiki pages")),
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list", "get", "get_revisions")), mcp.WithString("method", mcp.Required(), mcp.Enum("list", "get", "get_revisions")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("pageName", mcp.Description("wiki page name (required for 'get', 'get_revisions')")), mcp.WithString("pageName", mcp.Description("for 'get'/'get_revisions'")),
) )
WikiWriteTool = mcp.NewTool( WikiWriteTool = mcp.NewTool(
WikiWriteToolName, 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.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("method", mcp.Required(), mcp.Enum("create", "update", "delete")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
mcp.WithString("pageName", mcp.Description("wiki page name (required for 'update', 'delete')")), mcp.WithString("pageName", mcp.Description("for 'update'/'delete'")),
mcp.WithString("title", mcp.Description("wiki page title (required for 'create', optional for 'update')")), mcp.WithString("title", mcp.Description("for 'create'")),
mcp.WithString("content", mcp.Description("page content (required for 'create', 'update')")), mcp.WithString("content", mcp.Description("for 'create'/'update'")),
mcp.WithString("message", mcp.Description("commit message")), mcp.WithString("message", mcp.Description("commit message")),
) )
) )
+3 -13
View File
@@ -6,27 +6,17 @@ import "github.com/mark3labs/mcp-go/mcp"
// ReadOnly returns a ToolAnnotation for read-only tools. // ReadOnly returns a ToolAnnotation for read-only tools.
func ReadOnly(title string) mcp.ToolAnnotation { func ReadOnly(title string) mcp.ToolAnnotation {
t := true t := true
return mcp.ToolAnnotation{ return mcp.ToolAnnotation{Title: title, ReadOnlyHint: &t}
Title: title,
ReadOnlyHint: &t,
}
} }
// Write returns a ToolAnnotation for write tools. // Write returns a ToolAnnotation for write tools.
func Write(title string) mcp.ToolAnnotation { func Write(title string) mcp.ToolAnnotation {
f := false f := false
return mcp.ToolAnnotation{ return mcp.ToolAnnotation{Title: title, ReadOnlyHint: &f}
Title: title,
ReadOnlyHint: &f,
}
} }
// Destructive returns a ToolAnnotation for destructive write tools. // Destructive returns a ToolAnnotation for destructive write tools.
func Destructive(title string) mcp.ToolAnnotation { func Destructive(title string) mcp.ToolAnnotation {
f, t := false, true f, t := false, true
return mcp.ToolAnnotation{ return mcp.ToolAnnotation{Title: title, ReadOnlyHint: &f, DestructiveHint: &t}
Title: title,
ReadOnlyHint: &f,
DestructiveHint: &t,
}
} }
+12 -2
View File
@@ -6,6 +6,16 @@ import (
"time" "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. // GetString extracts a required string parameter from MCP tool arguments.
func GetString(args map[string]any, key string) (string, error) { func GetString(args map[string]any, key string) (string, error) {
val, ok := args[key].(string) val, ok := args[key].(string)
@@ -42,9 +52,9 @@ func GetStringSlice(args map[string]any, key string) []string {
return out 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) { 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 // ToInt64 converts a value to int64, accepting both float64 (JSON number) and
+11
View File
@@ -5,6 +5,17 @@ import (
"testing" "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) { func TestToInt64(t *testing.T) {
tests := []struct { tests := []struct {
name string name string