package search import ( "context" "fmt" "strings" "gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/tool" gitea_sdk "code.gitea.io/sdk/gitea" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) var Tool = tool.New() const ( SearchUsersToolName = "search_users" SearchOrgTeamsToolName = "search_org_teams" SearchReposToolName = "search_repos" SearchIssuesToolName = "search_issues" ) var ( SearchUsersTool = mcp.NewTool( SearchUsersToolName, mcp.WithDescription("search users"), mcp.WithString("keyword", mcp.Required(), mcp.Description("Keyword")), mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)), mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(30)), ) SearOrgTeamsTool = mcp.NewTool( SearchOrgTeamsToolName, mcp.WithDescription("search organization teams"), mcp.WithString("org", mcp.Required(), mcp.Description("organization name")), mcp.WithString("query", mcp.Required(), mcp.Description("search organization teams")), mcp.WithBoolean("includeDescription", mcp.Description("include description?")), mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)), mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(30)), ) SearchReposTool = mcp.NewTool( SearchReposToolName, mcp.WithDescription("search repos"), mcp.WithString("keyword", mcp.Required(), mcp.Description("Keyword")), mcp.WithBoolean("keywordIsTopic", mcp.Description("KeywordIsTopic")), mcp.WithBoolean("keywordInDescription", mcp.Description("KeywordInDescription")), mcp.WithNumber("ownerID", mcp.Description("OwnerID")), mcp.WithBoolean("isPrivate", mcp.Description("IsPrivate")), mcp.WithBoolean("isArchived", mcp.Description("IsArchived")), mcp.WithString("sort", mcp.Description("Sort")), mcp.WithString("order", mcp.Description("Order")), mcp.WithNumber("page", mcp.Description("Page"), mcp.DefaultNumber(1)), mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(30)), ) SearchIssuesTool = mcp.NewTool( SearchIssuesToolName, mcp.WithDescription("Search for issues and pull requests across all accessible repositories"), mcp.WithString("query", mcp.Required(), mcp.Description("search keyword")), mcp.WithString("state", mcp.Description("filter by state: open, closed, all"), mcp.Enum("open", "closed", "all")), mcp.WithString("type", mcp.Description("filter by type: issues, pulls"), mcp.Enum("issues", "pulls")), mcp.WithString("labels", mcp.Description("comma-separated list of label names")), mcp.WithString("owner", mcp.Description("filter by repository owner")), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(30)), ) ) func init() { Tool.RegisterRead(server.ServerTool{ Tool: SearchUsersTool, Handler: UsersFn, }) Tool.RegisterRead(server.ServerTool{ Tool: SearOrgTeamsTool, Handler: OrgTeamsFn, }) Tool.RegisterRead(server.ServerTool{ Tool: SearchReposTool, Handler: ReposFn, }) Tool.RegisterRead(server.ServerTool{ Tool: SearchIssuesTool, Handler: IssuesFn, }) } func UsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called UsersFn") keyword, err := params.GetString(req.GetArguments(), "keyword") if err != nil { return to.ErrorResult(err) } page, pageSize := params.GetPagination(req.GetArguments(), 30) opt := gitea_sdk.SearchUsersOption{ KeyWord: keyword, ListOptions: gitea_sdk.ListOptions{ Page: page, PageSize: pageSize, }, } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } users, _, err := client.SearchUsers(opt) if err != nil { return to.ErrorResult(fmt.Errorf("search users err: %v", err)) } return to.TextResult(slimUserDetails(users)) } func OrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called OrgTeamsFn") org, err := params.GetString(req.GetArguments(), "org") if err != nil { return to.ErrorResult(err) } query, err := params.GetString(req.GetArguments(), "query") if err != nil { return to.ErrorResult(err) } includeDescription, _ := req.GetArguments()["includeDescription"].(bool) page, pageSize := params.GetPagination(req.GetArguments(), 30) opt := gitea_sdk.SearchTeamsOptions{ Query: query, IncludeDescription: includeDescription, ListOptions: gitea_sdk.ListOptions{ Page: page, PageSize: pageSize, }, } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } teams, _, err := client.SearchOrgTeams(org, &opt) if err != nil { return to.ErrorResult(fmt.Errorf("search organization teams error: %v", err)) } return to.TextResult(slimTeams(teams)) } func ReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called ReposFn") keyword, err := params.GetString(req.GetArguments(), "keyword") if err != nil { return to.ErrorResult(err) } keywordIsTopic, _ := req.GetArguments()["keywordIsTopic"].(bool) keywordInDescription, _ := req.GetArguments()["keywordInDescription"].(bool) ownerID := params.GetOptionalInt(req.GetArguments(), "ownerID", 0) var pIsPrivate *bool isPrivate, ok := req.GetArguments()["isPrivate"].(bool) if ok { pIsPrivate = new(isPrivate) } var pIsArchived *bool isArchived, ok := req.GetArguments()["isArchived"].(bool) if ok { pIsArchived = new(isArchived) } sort, _ := req.GetArguments()["sort"].(string) order, _ := req.GetArguments()["order"].(string) page, pageSize := params.GetPagination(req.GetArguments(), 30) opt := gitea_sdk.SearchRepoOptions{ Keyword: keyword, KeywordIsTopic: keywordIsTopic, KeywordInDescription: keywordInDescription, OwnerID: ownerID, IsPrivate: pIsPrivate, IsArchived: pIsArchived, Sort: sort, Order: order, ListOptions: gitea_sdk.ListOptions{ Page: page, PageSize: pageSize, }, } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } repos, _, err := client.SearchRepos(opt) if err != nil { return to.ErrorResult(fmt.Errorf("search repos error: %v", err)) } return to.TextResult(slimRepos(repos)) } func IssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called IssuesFn") args := req.GetArguments() query, err := params.GetString(args, "query") if err != nil { return to.ErrorResult(err) } page, pageSize := params.GetPagination(args, 30) opt := gitea_sdk.ListIssueOption{ KeyWord: query, ListOptions: gitea_sdk.ListOptions{ Page: page, PageSize: pageSize, }, } if state, ok := args["state"].(string); ok { opt.State = gitea_sdk.StateType(state) } if issueType, ok := args["type"].(string); ok { opt.Type = gitea_sdk.IssueType(issueType) } if labels, ok := args["labels"].(string); ok && labels != "" { opt.Labels = strings.Split(labels, ",") } if owner, ok := args["owner"].(string); ok { opt.Owner = owner } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } issues, _, err := client.ListIssues(opt) if err != nil { return to.ErrorResult(fmt.Errorf("search issues err: %v", err)) } return to.TextResult(slimIssues(issues)) }