githubSearchPullRequests
Search and retrieve GitHub pull requests to review changes, analyze implementation history, and examine code diffs and discussions.
Instructions
Search or fetch Pull Requests (metadata, diffs, discussions)
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| queries | Yes | Research queries for githubSearchPullRequests (1-3 queries per call for optimal resource management). Review schema before use for optimal results |
Implementation Reference
- Core execution logic for the githubSearchPullRequests tool. Handles bulk queries, validation errors, API calls to GitHub, result processing, pagination hints, and error handling.
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(validationError, query); } const apiResult = await searchGitHubPullRequestsAPI( query, authInfo, sessionId ); const apiError = handleApiError(apiResult, query); if (apiError) return apiError; const pullRequests = apiResult.pull_requests || []; const hasContent = pullRequests.length > 0; // Generate pagination hints const paginationHints: string[] = []; if (apiResult.pagination) { const { currentPage, totalPages, totalMatches, hasMore } = apiResult.pagination; paginationHints.push( `Page ${currentPage}/${totalPages} (showing ${pullRequests.length} of ${totalMatches} PRs)` ); if (hasMore) { paginationHints.push(`Next: page=${currentPage + 1}`); } if (currentPage > 1) { paginationHints.push(`Previous: page=${currentPage - 1}`); } if (!hasMore) { paginationHints.push('Final page'); } if (totalPages > 2) { paginationHints.push( `Jump to: page=1 (first) or page=${totalPages} (last)` ); } } // Use unified pattern: context for dynamic hints, extraHints for pagination return createSuccessResult( query, { owner: query.owner, repo: query.repo, pull_requests: pullRequests, total_count: apiResult.total_count || pullRequests.length, ...(apiResult.pagination && { pagination: apiResult.pagination }), }, hasContent, TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS, { hintContext: { matchCount: pullRequests.length }, extraHints: paginationHints, } ); } catch (error) { return handleCatchError(error, query); } }, { toolName: TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS, keysPriority: [ 'owner', 'repo', 'pull_requests', 'pagination', 'total_count', 'error', ] satisfies Array<keyof PullRequestSearchResult>, } ); } - Registers the tool with the MCP server, specifying name, description, input schema, annotations, and the 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 || []; await invokeCallbackSafely( callback, TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS, queries ); 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); } ) ); } - Zod schema defining the input parameters for GitHub pull request search queries, including search terms, scopes, filters, sorting, pagination, and output options.
export const GitHubPullRequestSearchQuerySchema = BaseQuerySchema.extend({ query: z .string() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.search.query), owner: z .string() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.scope.owner), repo: z.string().optional().describe(GITHUB_SEARCH_PULL_REQUESTS.scope.repo), prNumber: z .number() .int() .positive() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.scope.prNumber), state: z .enum(['open', 'closed']) .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.state), assignee: z .string() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.assignee), author: z .string() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.author), commenter: z .string() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.commenter), involves: z .string() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.involves), mentions: z .string() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.mentions), 'review-requested': z .string() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters['review-requested']), 'reviewed-by': z .string() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters['reviewed-by']), label: z .union([z.string(), z.array(z.string())]) .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.label), 'no-label': z .boolean() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters['no-label']), 'no-milestone': z .boolean() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters['no-milestone']), 'no-project': z .boolean() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters['no-project']), 'no-assignee': z .boolean() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters['no-assignee']), head: z .string() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.head), base: z .string() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.base), created: DateRangeSchema.shape.created, updated: DateRangeSchema.shape.updated, closed: z .string() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.closed), 'merged-at': z .string() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters['merged-at']), comments: z .union([ z.number().int().min(0), z.string().regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/), ]) .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.comments), reactions: z .union([ z.number().int().min(0), z.string().regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/), ]) .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.reactions), interactions: z .union([ z.number().int().min(0), z.string().regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/), ]) .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.interactions), merged: z .boolean() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.merged), draft: z .boolean() .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.filters.draft), match: PRMatchScopeSchema, sort: z .enum(['created', 'updated', 'best-match']) .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.sorting.sort), order: z .enum(['asc', 'desc']) .optional() .default('desc') .describe(GITHUB_SEARCH_PULL_REQUESTS.sorting.order), limit: z .number() .min(1) .max(10) .default(5) .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.resultLimit.limit), page: z .number() .int() .min(1) .max(10) .default(1) .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.pagination.page), withComments: z .boolean() .default(false) .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.outputShaping.withComments), withCommits: z .boolean() .default(false) .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.outputShaping.withCommits), type: z .enum(['metadata', 'fullContent', 'partialContent']) .default('metadata') .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.outputShaping.type), partialContentMetadata: z .array( z.object({ file: z.string(), additions: z.array(z.number()).optional(), deletions: z.array(z.number()).optional(), }) ) .optional() .describe(GITHUB_SEARCH_PULL_REQUESTS.outputShaping.partialContentMetadata), }); export const GitHubPullRequestSearchBulkQuerySchema = createBulkQuerySchema( TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS, GitHubPullRequestSearchQuerySchema ); - packages/octocode-mcp/src/tools/toolConfig.ts:72-79 (registration)Tool configuration entry that references the registration function and metadata for githubSearchPullRequests.
export const GITHUB_SEARCH_PULL_REQUESTS: ToolConfig = { name: TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS, description: getDescription(TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS), isDefault: true, isLocal: false, type: 'history', fn: registerSearchGitHubPullRequestsTool, }; - Low-level API function that performs the actual GitHub GraphQL/REST search for pull requests based on the query.
export async function searchGitHubPullRequestsAPI( params: GitHubPullRequestsSearchParams, authInfo?: AuthInfo, sessionId?: string ): Promise<PullRequestSearchResult> { // Cache key excludes context fields (mainResearchGoal, researchGoal, reasoning) // as they don't affect the API response const cacheKey = generateCacheKey( 'gh-api-prs', { query: params.query, owner: params.owner, repo: params.repo, prNumber: params.prNumber, state: params.state, draft: params.draft, merged: params.merged, author: params.author, assignee: params.assignee, mentions: params.mentions, commenter: params.commenter, involves: params.involves, 'reviewed-by': params['reviewed-by'], 'review-requested': params['review-requested'], head: params.head, base: params.base, created: params.created, updated: params.updated, 'merged-at': params['merged-at'], closed: params.closed, comments: params.comments, reactions: params.reactions, interactions: params.interactions, label: params.label, 'no-assignee': params['no-assignee'], 'no-label': params['no-label'], 'no-milestone': params['no-milestone'], 'no-project': params['no-project'], match: params.match, sort: params.sort, order: params.order, limit: params.limit, page: params.page, withComments: params.withComments, withCommits: params.withCommits, type: params.type, partialContentMetadata: params.partialContentMetadata, }, sessionId ); const result = await withDataCache<PullRequestSearchResult>( cacheKey, async () => { return await searchGitHubPullRequestsAPIInternal( params, authInfo, sessionId ); }, { shouldCache: (value: PullRequestSearchResult) => !value.error, } ); return result; } async function searchGitHubPullRequestsAPIInternal(