diff --git a/operation/issue/issue.go b/operation/issue/issue.go index 4c023a5..d74c3dc 100644 --- a/operation/issue/issue.go +++ b/operation/issue/issue.go @@ -40,6 +40,7 @@ const ( var ( ListRepoIssuesTool = mcp.NewTool( ListRepoIssuesToolName, + mcp.WithDescription("List issues in a repository (or pull requests, via the 'type' filter), filterable by state, labels, milestones, and update time range."), mcp.WithToolAnnotation(annotation.ReadOnly("List repository issues")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), diff --git a/operation/operation_test.go b/operation/operation_test.go index 55c642b..d3c28a5 100644 --- a/operation/operation_test.go +++ b/operation/operation_test.go @@ -2,8 +2,36 @@ package operation import ( "testing" + + "gitea.com/gitea/gitea-mcp/pkg/flag" ) +// TestAllToolsHaveDescriptions ensures every registered tool sets a non-empty +// Tool.Description. mcp-go only serializes the "description" field of a tool +// when it is non-empty, so an omitted description makes strict MCP clients +// (e.g. mcp-probe) reject the tools/list response with "missing field +// `description`". +func TestAllToolsHaveDescriptions(t *testing.T) { + origRO, origAllow := flag.ReadOnly, flag.AllowedTools + t.Cleanup(func() { + flag.ReadOnly, flag.AllowedTools = origRO, origAllow + }) + flag.ReadOnly = false + flag.AllowedTools = nil + + var missing []string + for _, d := range domainTools { + for _, st := range d.Tools() { + if st.Tool.Description == "" { + missing = append(missing, st.Tool.Name) + } + } + } + if len(missing) > 0 { + t.Errorf("tools missing a description: %v", missing) + } +} + func TestParseAuthToken(t *testing.T) { tests := []struct { name string diff --git a/operation/pull/pull.go b/operation/pull/pull.go index 36bcea2..eb725d5 100644 --- a/operation/pull/pull.go +++ b/operation/pull/pull.go @@ -31,6 +31,7 @@ const ( var ( ListRepoPullRequestsTool = mcp.NewTool( ListRepoPullRequestsToolName, + mcp.WithDescription("List pull requests in a repository, filterable by state and milestone, with configurable sort order (e.g. recently updated, most commented)."), mcp.WithToolAnnotation(annotation.ReadOnly("List pull requests")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), diff --git a/operation/repo/branch.go b/operation/repo/branch.go index b4e71ae..0775b9b 100644 --- a/operation/repo/branch.go +++ b/operation/repo/branch.go @@ -23,6 +23,7 @@ const ( var ( CreateBranchTool = mcp.NewTool( CreateBranchToolName, + mcp.WithDescription("Create a new branch in a repository, optionally from a specific source branch (defaults to the repository's default branch)."), mcp.WithToolAnnotation(annotation.Write("Create a new branch")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), @@ -32,6 +33,7 @@ var ( DeleteBranchTool = mcp.NewTool( DeleteBranchToolName, + mcp.WithDescription("Permanently delete a branch from a repository. This action is destructive and cannot be undone."), mcp.WithToolAnnotation(annotation.Destructive("Delete a branch")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), @@ -40,6 +42,7 @@ var ( ListBranchesTool = mcp.NewTool( ListBranchesToolName, + mcp.WithDescription("List all branches in a repository, paginated."), mcp.WithToolAnnotation(annotation.ReadOnly("List repository branches")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), diff --git a/operation/repo/commit.go b/operation/repo/commit.go index dc619e6..c3aa9d3 100644 --- a/operation/repo/commit.go +++ b/operation/repo/commit.go @@ -22,6 +22,7 @@ const ( var ( ListRepoCommitsTool = mcp.NewTool( ListRepoCommitsToolName, + mcp.WithDescription("List commits in a repository, optionally starting from a specific branch or SHA and filtered to commits touching a given file path."), mcp.WithToolAnnotation(annotation.ReadOnly("List repository commits")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), @@ -33,6 +34,7 @@ var ( GetCommitTool = mcp.NewTool( GetCommitToolName, + mcp.WithDescription("Get details for a single commit in a repository by its SHA."), mcp.WithToolAnnotation(annotation.ReadOnly("Get commit details")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), diff --git a/operation/repo/file.go b/operation/repo/file.go index 81e0018..c6f20a3 100644 --- a/operation/repo/file.go +++ b/operation/repo/file.go @@ -39,6 +39,7 @@ var ( GetDirContentTool = mcp.NewTool( GetDirToolName, + mcp.WithDescription("List the entries (files and subdirectories) in a repository directory at a given ref (branch, tag, or commit SHA)."), mcp.WithToolAnnotation(annotation.ReadOnly("Get directory contents")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), @@ -62,6 +63,7 @@ var ( DeleteFileTool = mcp.NewTool( DeleteFileToolName, + mcp.WithDescription("Delete a file from a repository by committing the removal to a branch. Requires the file's current SHA and a commit message."), mcp.WithToolAnnotation(annotation.Destructive("Delete a file")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), diff --git a/operation/repo/release.go b/operation/repo/release.go index 3d6f1e5..a61c575 100644 --- a/operation/repo/release.go +++ b/operation/repo/release.go @@ -25,6 +25,7 @@ const ( var ( CreateReleaseTool = mcp.NewTool( CreateReleaseToolName, + mcp.WithDescription("Create a new release in a repository from a tag, optionally marking it as a draft or pre-release."), mcp.WithToolAnnotation(annotation.Write("Create a release")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), @@ -38,6 +39,7 @@ var ( DeleteReleaseTool = mcp.NewTool( DeleteReleaseToolName, + mcp.WithDescription("Delete a release from a repository by its numeric ID. This action is destructive and cannot be undone."), mcp.WithToolAnnotation(annotation.Destructive("Delete a release")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), @@ -55,6 +57,7 @@ var ( GetLatestReleaseTool = mcp.NewTool( GetLatestReleaseToolName, + mcp.WithDescription("Get the most recent published (non-draft) release in a repository."), mcp.WithToolAnnotation(annotation.ReadOnly("Get latest release")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), @@ -62,6 +65,7 @@ var ( ListReleasesTool = mcp.NewTool( ListReleasesToolName, + mcp.WithDescription("List releases in a repository, optionally filtered to drafts or pre-releases."), mcp.WithToolAnnotation(annotation.ReadOnly("List releases")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), diff --git a/operation/repo/repo.go b/operation/repo/repo.go index fd8aac6..2f1f025 100644 --- a/operation/repo/repo.go +++ b/operation/repo/repo.go @@ -28,6 +28,7 @@ const ( var ( CreateRepoTool = mcp.NewTool( CreateRepoToolName, + mcp.WithDescription("Create a new Git repository, optionally under an organization (defaults to the authenticated user's account), with options for visibility, template, license, .gitignore, and initial README."), mcp.WithToolAnnotation(annotation.Write("Create a new repository")), mcp.WithString("name", mcp.Required()), mcp.WithString("description"), @@ -46,6 +47,7 @@ var ( ForkRepoTool = mcp.NewTool( ForkRepoToolName, + mcp.WithDescription("Fork an existing repository into the authenticated user's account or a target organization, optionally under a new name."), mcp.WithToolAnnotation(annotation.Write("Fork a repository")), mcp.WithString("user", mcp.Required(), mcp.Description("owner of source repo")), mcp.WithString("repo", mcp.Required()), @@ -55,6 +57,7 @@ var ( ListMyReposTool = mcp.NewTool( ListMyReposToolName, + mcp.WithDescription("List repositories owned by the authenticated user."), mcp.WithToolAnnotation(annotation.ReadOnly("List my repositories")), mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)), @@ -62,6 +65,7 @@ var ( ListOrgReposTool = mcp.NewTool( ListOrgReposToolName, + mcp.WithDescription("List repositories belonging to an organization."), mcp.WithToolAnnotation(annotation.ReadOnly("List organization repositories")), mcp.WithString("org", mcp.Required()), mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)), diff --git a/operation/repo/tag.go b/operation/repo/tag.go index 099953d..14c2579 100644 --- a/operation/repo/tag.go +++ b/operation/repo/tag.go @@ -24,6 +24,7 @@ const ( var ( CreateTagTool = mcp.NewTool( CreateTagToolName, + mcp.WithDescription("Create a new Git tag in a repository at a target commit, branch, or existing tag, with an optional annotation message."), mcp.WithToolAnnotation(annotation.Write("Create a tag")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), @@ -34,6 +35,7 @@ var ( DeleteTagTool = mcp.NewTool( DeleteTagToolName, + mcp.WithDescription("Permanently delete a tag from a repository. This action is destructive and cannot be undone."), mcp.WithToolAnnotation(annotation.Destructive("Delete a tag")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), @@ -42,6 +44,7 @@ var ( GetTagTool = mcp.NewTool( GetTagToolName, + mcp.WithDescription("Get details for a single tag in a repository by name."), mcp.WithToolAnnotation(annotation.ReadOnly("Get tag details")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), @@ -50,6 +53,7 @@ var ( ListTagsTool = mcp.NewTool( ListTagsToolName, + mcp.WithDescription("List all tags in a repository, paginated."), mcp.WithToolAnnotation(annotation.ReadOnly("List tags")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), diff --git a/operation/repo/tree.go b/operation/repo/tree.go index 00bcb74..732f492 100644 --- a/operation/repo/tree.go +++ b/operation/repo/tree.go @@ -20,6 +20,7 @@ const ( var GetRepoTreeTool = mcp.NewTool( GetRepoTreeToolName, + mcp.WithDescription("Get the file tree of a repository at a given ref (SHA, branch, or tag), optionally recursively."), mcp.WithToolAnnotation(annotation.ReadOnly("Get repository file tree")), mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)), diff --git a/operation/search/search.go b/operation/search/search.go index d8e7276..8de308b 100644 --- a/operation/search/search.go +++ b/operation/search/search.go @@ -29,6 +29,7 @@ const ( var ( SearchUsersTool = mcp.NewTool( SearchUsersToolName, + mcp.WithDescription("Search for Gitea users by username or full name."), mcp.WithToolAnnotation(annotation.ReadOnly("Search users")), mcp.WithString("query", mcp.Required()), mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), @@ -37,6 +38,7 @@ var ( SearOrgTeamsTool = mcp.NewTool( SearchOrgTeamsToolName, + mcp.WithDescription("Search for teams within an organization by name, optionally including each team's description in the results."), mcp.WithToolAnnotation(annotation.ReadOnly("Search organization teams")), mcp.WithString("org", mcp.Required()), mcp.WithString("query", mcp.Required()), @@ -47,6 +49,7 @@ var ( SearchReposTool = mcp.NewTool( SearchReposToolName, + mcp.WithDescription("Search for repositories by keyword, with filters for topic/description matching, owner, visibility, archived status, and sort order."), mcp.WithToolAnnotation(annotation.ReadOnly("Search repositories")), mcp.WithString("query", mcp.Required()), mcp.WithBoolean("keywordIsTopic"), diff --git a/operation/version/version.go b/operation/version/version.go index 77737a6..5f4d4e3 100644 --- a/operation/version/version.go +++ b/operation/version/version.go @@ -21,6 +21,7 @@ const ( var GetGiteaMCPServerVersionTool = mcp.NewTool( GetGiteaMCPServerVersion, + mcp.WithDescription("Get the running version of the Gitea MCP Server itself (not the Gitea instance it connects to)."), mcp.WithToolAnnotation(annotation.ReadOnly("Get server version")), )