githubSearchPullRequests
Search GitHub pull requests by keywords, state, or author to identify relevant PRs. Fetch head/base branch SHAs for detailed commit analysis or retrieve all PR commits with file changes for deeper insights.
Instructions
Search GitHub PRs by keywords, state, or author. Returns head/base SHAs for github_fetch_content (branch=SHA). Can fetch all PR commits with changes using getCommitData=true.
SEARCH STRATEGY FOR BEST RESULTS:
Use minimal search terms for broader coverage (2-3 words max)
Separate searches for different aspects vs complex queries
Use filters to narrow scope after getting initial results
Use getCommitData=true to get all commits in PR with file changes
Use github_search_commits with head_sha/base_sha from results to get code changes
COMMIT DATA FETCHING (getCommitData=true):
Fetches all commits in the PR using 'gh pr view --json commits'
For each commit, fetches detailed changes using GitHub API
Shows commit SHA, message, author, and file changes
Includes up to 10 commits per PR with detailed diffs
Each commit shows changed files with additions/deletions/patches
Example: Shows individual commits like "Fix bug in component" with specific file changes
EXAMPLE OUTPUT WITH getCommitData=true: { "commits": { "total_count": 3, "commits": [ { "sha": "abc123", "message": "Fix bug in component", "author": "username", "diff": { "changed_files": 2, "additions": 15, "deletions": 3, "files": [...] } } ] } }
NOTE: The head_sha and base_sha fields in the PR results can be used as the 'hash' parameter in github_search_commits to look up the exact commit and get actual code changes.
TOKEN OPTIMIZATION:
getCommitData=true is expensive in tokens. Use only when necessary.
Consider using github_search_commits with head_sha/base_sha instead for specific commits
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| app | No | Filter by GitHub App author | |
| archived | No | Filter by repository archived state | |
| assignee | No | GitHub username of assignee | |
| author | No | GitHub username of PR author | |
| base | No | Filter on base branch name | |
| checks | No | Filter by checks status | |
| closed | No | Filter by closed date (e.g., >2020-01-01) | |
| commenter | No | User who commented on PR | |
| comments | No | Include comment content in search results. This is a very expensive operation in tokens and should be used with caution. | |
| created | No | Filter by created date (e.g., >2020-01-01) | |
| draft | No | Filter by draft state | |
| getCommitData | No | Set to true to fetch all commits in the PR with their changes. Shows commit messages, authors, and file changes. WARNING: EXTREMELY expensive in tokens - fetches diff/patch content for each commit. | |
| head | No | Filter on head branch name | |
| interactions | No | Total interactions (reactions + comments) | |
| involves | No | User involved in any way | |
| label | No | Filter by label | |
| language | No | Repository language | |
| limit | No | Maximum number of results to fetch | |
| locked | No | Filter by locked conversation status | |
| match | No | Restrict search to specific fields | |
| mentions | No | PRs mentioning this user | |
| merged | No | Filter by merged state | |
| merged-at | No | Filter by merged date (e.g., >2020-01-01) | |
| milestone | No | Milestone title | |
| no-assignee | No | Filter by missing assignee | |
| no-label | No | Filter by missing label | |
| no-milestone | No | Filter by missing milestone | |
| no-project | No | Filter by missing project | |
| order | No | Order of results (requires --sort) | desc |
| owner | No | Repository owner (use with repo param) | |
| project | No | Project board owner/number | |
| query | Yes | Search query for PR content (keep minimal for broader results) | |
| reactions | No | Filter by number of reactions | |
| repo | No | Repository name (use with owner param) | |
| review | No | Filter by review status | |
| review-requested | No | User/team requested for review | |
| reviewed-by | No | User who reviewed the PR | |
| sort | No | Sort fetched results | |
| state | No | Filter by state: open or closed | |
| team-mentions | No | Filter by team mentions | |
| updated | No | Filter by updated date (e.g., >2020-01-01) | |
| visibility | No | Repository visibility |
Implementation Reference
- Zod schema for input validation of github_search_pull_requests tool, defining parameters like query, owner, repo, filters (state, assignee, etc.), sorting, limits, and output shaping options.export const GitHubPullRequestSearchBulkQuerySchema = createBulkQuerySchema( TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS, GitHubPullRequestSearchQuerySchema );
- Registers the 'github_search_pull_requests' tool with the MCP server, specifying schema, annotations, and security-wrapped handler function.export function registerSearchGitHubPullRequestsTool( server: McpServer, callback?: ToolInvocationCallback ) { return server.registerTool( TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS, { description: DESCRIPTIONS[TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS], inputSchema: GitHubPullRequestSearchBulkQuerySchema, annotations: { title: 'GitHub Pull Request Search', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true, }, }, withSecurityValidation( TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS, async ( args: { queries: GitHubPullRequestSearchQuery[]; }, authInfo, sessionId ): Promise<CallToolResult> => { let queries = args.queries || []; if (callback) { try { await callback(TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS, queries); } catch { // ignore } } const longQueryIndex = queries.findIndex(hasQueryLengthError); if (longQueryIndex !== -1) { queries = queries.map((q, i) => i === longQueryIndex ? addValidationError(q, VALIDATION_MESSAGES.QUERY_TOO_LONG) : q ); } if (queries.length > 0 && !queries.some(hasValidSearchParams)) { queries = queries.map((q, i) => i === 0 ? addValidationError(q, VALIDATION_MESSAGES.MISSING_PARAMS) : q ); } return searchMultipleGitHubPullRequests(queries, authInfo, sessionId); } ) ); }
- Bulk handler that processes multiple PR search queries, performs validation, calls the GitHub API, handles errors, and formats results.async function searchMultipleGitHubPullRequests( queries: GitHubPullRequestSearchQuery[], authInfo?: AuthInfo, sessionId?: string ): Promise<CallToolResult> { return executeBulkOperation( queries, async (query: GitHubPullRequestSearchQuery, _index: number) => { try { const validationError = (query as unknown as Record<string, unknown>) ?._validationError; if (validationError && typeof validationError === 'string') { return createErrorResult(query, validationError); } const apiResult = await searchGitHubPullRequestsAPI( query, authInfo, sessionId ); const apiError = handleApiError(apiResult, query); if (apiError) return apiError; const pullRequests = apiResult.pull_requests || []; return createSuccessResult( query, { pull_requests: pullRequests, total_count: apiResult.total_count || pullRequests.length, incomplete_results: apiResult.incomplete_results, }, pullRequests.length > 0, 'GITHUB_SEARCH_PULL_REQUESTS' ); } catch (error) { return handleCatchError(error, query); } }, { toolName: TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS, keysPriority: [ 'pull_requests', 'total_count', 'incomplete_results', 'error', ] satisfies Array<keyof PullRequestSearchResult>, } ); }
- Core implementation of GitHub Pull Request search using Octokit: supports search queries, repo-specific lists, single PR fetch; includes data transformation, caching, content sanitization, and optional file/comments/commits fetching.export async function searchGitHubPullRequestsAPI( params: GitHubPullRequestsSearchParams, authInfo?: AuthInfo, sessionId?: string ): Promise<PullRequestSearchResult> { const cacheKey = generateCacheKey('gh-api-prs', params, sessionId); const result = await withDataCache<PullRequestSearchResult>( cacheKey, async () => { return await searchGitHubPullRequestsAPIInternal( params, authInfo, sessionId ); }, { shouldCache: (value: PullRequestSearchResult) => !value.error, } ); return result; } async function searchGitHubPullRequestsAPIInternal( params: GitHubPullRequestsSearchParams, authInfo?: AuthInfo, _sessionId?: string ): Promise<PullRequestSearchResult> { try { if ( params.prNumber && params.owner && params.repo && !Array.isArray(params.owner) && !Array.isArray(params.repo) ) { return await fetchGitHubPullRequestByNumberAPIInternal(params, authInfo); } const octokit = await getOctokit(authInfo); const shouldUseSearch = shouldUseSearchForPRs(params); if ( !shouldUseSearch && params.owner && params.repo && !Array.isArray(params.owner) && !Array.isArray(params.repo) ) { // Use REST API for simple repository-specific searches (like gh pr list) return await searchPullRequestsWithREST(octokit, params); } const searchQuery = buildPullRequestSearchQuery(params); if (!searchQuery) { await logSessionError( TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS, SEARCH_ERRORS.NO_VALID_PARAMETERS.code ); return { pull_requests: [], total_count: 0, error: SEARCH_ERRORS.NO_VALID_PARAMETERS.message, hints: ['Provide search query or filters like owner/repo'], }; } const sortValue = params.sort && params.sort !== 'best-match' && params.sort !== 'created' ? params.sort : undefined; const searchResult = await octokit.rest.search.issuesAndPullRequests({ q: searchQuery, sort: sortValue as | 'comments' | 'reactions' | 'created' | 'updated' | undefined, order: params.order || 'desc', per_page: Math.min(params.limit || 30, 100), }); const pullRequests = searchResult.data.items?.filter( (item: Record<string, unknown>) => item.pull_request ) || []; const transformedPRs: GitHubPullRequestItem[] = await Promise.all( pullRequests.map(async (item: Record<string, unknown>) => { return await transformPullRequestItem(item, params, octokit); }) ); const owner = Array.isArray(params.owner) ? params.owner[0] : params.owner || ''; const repo = Array.isArray(params.repo) ? params.repo[0] : params.repo || ''; const formattedPRs = transformedPRs.map(pr => ({ id: 0, // We don't have this in our format number: pr.number, title: pr.title, url: pr.url, html_url: pr.url, state: pr.state as 'open' | 'closed', draft: pr.draft ?? false, merged: pr.state === 'closed' && !!pr.merged_at, created_at: pr.created_at, updated_at: pr.updated_at, closed_at: pr.closed_at ?? undefined, merged_at: pr.merged_at, author: { login: pr.author, id: 0, avatar_url: '', html_url: `https://github.com/${pr.author}`, }, head: { ref: pr.head || '', sha: pr.head_sha || '', repo: owner && repo ? `${owner}/${repo}` : '', }, base: { ref: pr.base || '', sha: pr.base_sha || '', repo: owner && repo ? `${owner}/${repo}` : '', }, body: pr.body, comments: pr.comments?.length || 0, review_comments: 0, commits: pr.commits?.length || 0, additions: pr.file_changes?.files.reduce((sum, file) => sum + file.additions, 0) || 0, deletions: pr.file_changes?.files.reduce((sum, file) => sum + file.deletions, 0) || 0, changed_files: pr.file_changes?.total_count || 0, ...(pr.file_changes && { file_changes: pr.file_changes.files?.map(file => ({ filename: file.filename, status: file.status, additions: file.additions, deletions: file.deletions, patch: file.patch, })), }), ...(pr.commits && { commit_details: pr.commits, }), })); return { pull_requests: formattedPRs, total_count: searchResult.data.total_count, incomplete_results: searchResult.data.incomplete_results, }; } catch (error: unknown) { const apiError = handleGitHubAPIError(error); await logSessionError( TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS, SEARCH_ERRORS.PULL_REQUEST_SEARCH_FAILED.code ); return { pull_requests: [], total_count: 0, error: SEARCH_ERRORS.PULL_REQUEST_SEARCH_FAILED.message(apiError.error), hints: [`Verify authentication and search parameters`], }; } }
- packages/octocode-mcp/src/tools/toolConfig.ts:57-63 (registration)Tool configuration entry that includes the registration function for github_search_pull_requests in the list of default tools.export const GITHUB_SEARCH_PULL_REQUESTS: ToolConfig = { name: TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS, description: getDescription(TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS), isDefault: true, type: 'history', fn: registerSearchGitHubPullRequestsTool, };