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 (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), 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 (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), 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 (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), 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 (may be capped by the server's MAX_RESPONSE_ITEMS setting, default 50)"), 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)) }