octocode-mcp 2.3.15 → 2.3.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +463 -111
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -85,7 +85,14 @@ const ALLOWED_NPM_COMMANDS = [
|
|
|
85
85
|
'whoami',
|
|
86
86
|
];
|
|
87
87
|
// Allowed command prefixes - this prevents shell injection by restricting to safe commands
|
|
88
|
-
const ALLOWED_GH_COMMANDS = [
|
|
88
|
+
const ALLOWED_GH_COMMANDS = [
|
|
89
|
+
'search',
|
|
90
|
+
'api',
|
|
91
|
+
'auth',
|
|
92
|
+
'org',
|
|
93
|
+
'pr',
|
|
94
|
+
'repo',
|
|
95
|
+
];
|
|
89
96
|
function createSuccessResult(data) {
|
|
90
97
|
return {
|
|
91
98
|
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
@@ -247,10 +254,10 @@ async function executeGitHubCommand(command, args = [], options = {}) {
|
|
|
247
254
|
// Get shell configuration
|
|
248
255
|
const shellConfig = getShellConfig(options.windowsShell);
|
|
249
256
|
// Build command with validated prefix and properly escaped arguments
|
|
250
|
-
// For GitHub search commands, we need minimal escaping to avoid interfering with GitHub CLI
|
|
251
257
|
const escapedArgs = args.map((arg, index) => {
|
|
252
258
|
const isMainQueryArgument = command === 'search' && index === 1;
|
|
253
259
|
const isCliFlag = arg.startsWith('--');
|
|
260
|
+
const isApiPath = command === 'api' && arg.startsWith('/');
|
|
254
261
|
// CLI flags like --language=javascript, --repo=owner/repo need minimal escaping
|
|
255
262
|
if (isCliFlag) {
|
|
256
263
|
// Only escape CLI flags if they contain dangerous shell characters
|
|
@@ -259,6 +266,22 @@ async function executeGitHubCommand(command, args = [], options = {}) {
|
|
|
259
266
|
}
|
|
260
267
|
return arg;
|
|
261
268
|
}
|
|
269
|
+
// API paths with query parameters need proper escaping
|
|
270
|
+
if (isApiPath) {
|
|
271
|
+
// Always quote API paths that contain query parameters or special characters
|
|
272
|
+
if (arg.includes('?') || arg.includes('&') || /[\s*?[\]{}]/.test(arg)) {
|
|
273
|
+
if (shellConfig.type === 'unix') {
|
|
274
|
+
return `'${arg.replace(/'/g, "'\"'\"'")}'`;
|
|
275
|
+
}
|
|
276
|
+
else if (shellConfig.type === 'cmd') {
|
|
277
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
return `'${arg.replace(/'/g, "''")}'`;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return arg;
|
|
284
|
+
}
|
|
262
285
|
// For search queries, only escape if absolutely necessary for shell safety
|
|
263
286
|
if (isMainQueryArgument) {
|
|
264
287
|
// Only escape if the argument contains shell metacharacters that could be dangerous
|
|
@@ -294,6 +317,29 @@ async function executeGitHubCommand(command, args = [], options = {}) {
|
|
|
294
317
|
}
|
|
295
318
|
return executeGhCommand();
|
|
296
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Helper function to detect shell configuration errors vs real GitHub errors
|
|
322
|
+
*/
|
|
323
|
+
function isShellConfigurationError(errorText) {
|
|
324
|
+
if (!errorText)
|
|
325
|
+
return false;
|
|
326
|
+
// Preserve GitHub API errors
|
|
327
|
+
if (errorText.includes('HTTP 404') ||
|
|
328
|
+
errorText.includes('HTTP 403') ||
|
|
329
|
+
errorText.includes('Not Found') ||
|
|
330
|
+
errorText.includes('API rate limit')) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
// Shell configuration conflicts to ignore
|
|
334
|
+
return (errorText.includes('head: illegal option') ||
|
|
335
|
+
errorText.includes('head: |: No such file or directory') ||
|
|
336
|
+
errorText.includes('head: cat: No such file or directory') ||
|
|
337
|
+
/^head:\s+/.test(errorText) ||
|
|
338
|
+
/^\s*head:\s+/.test(errorText) ||
|
|
339
|
+
(errorText.includes('No such file or directory') &&
|
|
340
|
+
!errorText.includes('HTTP') &&
|
|
341
|
+
!errorText.includes('gh:')));
|
|
342
|
+
}
|
|
297
343
|
/**
|
|
298
344
|
* Execute shell commands with improved environment handling and error detection
|
|
299
345
|
*/
|
|
@@ -329,13 +375,8 @@ async function executeCommand(fullCommand, type, options = {}, shellConfig) {
|
|
|
329
375
|
: stderr &&
|
|
330
376
|
!stderr.includes('Warning:') &&
|
|
331
377
|
!stderr.includes('notice:') &&
|
|
332
|
-
// Ignore shell configuration conflicts
|
|
333
|
-
!stderr
|
|
334
|
-
!stderr.includes('head: illegal option') &&
|
|
335
|
-
!stderr.includes('head: |: No such file or directory') &&
|
|
336
|
-
!stderr.includes('head: cat: No such file or directory') &&
|
|
337
|
-
!/^head:\s+/.test(stderr) && // Ignore all head command errors (shell conflicts)
|
|
338
|
-
!/^\s*head:\s+/.test(stderr) && // Ignore head errors with leading whitespace
|
|
378
|
+
// Ignore shell configuration conflicts but preserve GitHub API errors
|
|
379
|
+
!isShellConfigurationError(stderr) &&
|
|
339
380
|
stderr.trim() !== '';
|
|
340
381
|
if (shouldTreatAsError) {
|
|
341
382
|
const errorType = type === 'npm' ? 'NPM command error' : 'GitHub CLI command error';
|
|
@@ -929,10 +970,7 @@ function createToolSuggestion(currentTool, suggestedTools) {
|
|
|
929
970
|
}
|
|
930
971
|
|
|
931
972
|
const API_STATUS_CHECK_TOOL_NAME = 'apiStatusCheck';
|
|
932
|
-
const DESCRIPTION$9 = `
|
|
933
|
-
|
|
934
|
-
- Github: check gh login status and list available organizations.
|
|
935
|
-
- Npm: check npm login status and list npm registry.`;
|
|
973
|
+
const DESCRIPTION$9 = `Check user status: GitHub/NPM connections, organizations, and current timestamp. Essential for understanding user's data access and API capabilities.`;
|
|
936
974
|
// Helper function to parse execution results with proper typing
|
|
937
975
|
function parseExecResult(result) {
|
|
938
976
|
if (!result.isError && result.content?.[0]?.text) {
|
|
@@ -1053,6 +1091,7 @@ function registerApiStatusCheckTool(server) {
|
|
|
1053
1091
|
}
|
|
1054
1092
|
return createResult({
|
|
1055
1093
|
data: {
|
|
1094
|
+
timestamp: new Date().toISOString(),
|
|
1056
1095
|
login: {
|
|
1057
1096
|
github: {
|
|
1058
1097
|
connected: githubConnected,
|
|
@@ -1090,24 +1129,12 @@ const GITHUB_SEARCH_CODE_TOOL_NAME = 'githubSearchCode';
|
|
|
1090
1129
|
const DESCRIPTION$8 = `Search code across GitHub repositories using GitHub's code search API via GitHub CLI.
|
|
1091
1130
|
|
|
1092
1131
|
SEARCH STRATEGY FOR BEST RESULTS:
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
-
|
|
1096
|
-
- queryTerms: Use minimal words for broader coverage
|
|
1097
|
-
|
|
1098
|
-
TERM OPTIMIZATION:
|
|
1099
|
-
- BEST: Single terms for maximum coverage
|
|
1100
|
-
- GOOD: 2-3 minimal terms
|
|
1101
|
-
- AVOID: Long phrases in queryTerms
|
|
1102
|
-
|
|
1103
|
-
MULTI-SEARCH STRATEGY:
|
|
1132
|
+
- use 'exactQuery' for exact phrase matching
|
|
1133
|
+
- use 'queryTerms' for minimal words for broader coverage (recommended: 2-3 terms)
|
|
1134
|
+
- queryTerms can be exacts strings or words (e.g. term1, "term2 exact string"...)
|
|
1104
1135
|
- Use separate searches for different aspects
|
|
1105
1136
|
- Separate searches provide broader coverage than complex queries
|
|
1106
|
-
|
|
1107
|
-
Filter Usage:
|
|
1108
|
-
- Use filters to narrow scope: language, owner, repo, filename
|
|
1109
|
-
- Combine filters strategically: language + owner for organization-wide searches
|
|
1110
|
-
- Never use filters on exploratory searches - use to refine results`;
|
|
1137
|
+
- Use filters to narrow scope (never use filters on exploratory searches - use to refine results)`;
|
|
1111
1138
|
function registerGitHubSearchCodeTool(server) {
|
|
1112
1139
|
server.registerTool(GITHUB_SEARCH_CODE_TOOL_NAME, {
|
|
1113
1140
|
description: DESCRIPTION$8,
|
|
@@ -111960,7 +111987,10 @@ async function fetchGitHubFileContent(params) {
|
|
|
111960
111987
|
const { owner, repo, branch, filePath } = params;
|
|
111961
111988
|
try {
|
|
111962
111989
|
// Try to fetch file content directly
|
|
111963
|
-
const
|
|
111990
|
+
const isCommitSha = branch.match(/^[0-9a-f]{40}$/);
|
|
111991
|
+
const apiPath = isCommitSha
|
|
111992
|
+
? `/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}` // Use contents API with ref for commit SHA
|
|
111993
|
+
: `/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}`; // Use contents API for branches/tags
|
|
111964
111994
|
const result = await executeGitHubCommand('api', [apiPath], {
|
|
111965
111995
|
cache: false,
|
|
111966
111996
|
});
|
|
@@ -112794,12 +112824,13 @@ function buildGitHubReposSearchCommand(params) {
|
|
|
112794
112824
|
}
|
|
112795
112825
|
|
|
112796
112826
|
const GITHUB_SEARCH_COMMITS_TOOL_NAME = 'githubSearchCommits';
|
|
112797
|
-
const DESCRIPTION$5 = `Search
|
|
112827
|
+
const DESCRIPTION$5 = `Search GitHub commits by message, author, or date. Returns SHAs for github_fetch_content (branch=SHA). Can fetch commit content changes (diffs/patches) when getChangesContent=true.
|
|
112798
112828
|
|
|
112799
|
-
|
|
112800
|
-
-
|
|
112801
|
-
-
|
|
112802
|
-
-
|
|
112829
|
+
SEARCH STRATEGY FOR BEST RESULTS:
|
|
112830
|
+
- Use minimal search terms for broader coverage (2-3 words max)
|
|
112831
|
+
- Separate searches for different aspects vs complex queries
|
|
112832
|
+
- Use filters to narrow scope after getting initial results
|
|
112833
|
+
- getChangesContent=true only when analyzing actual code changes`;
|
|
112803
112834
|
function registerGitHubSearchCommitsTool(server) {
|
|
112804
112835
|
server.registerTool(GITHUB_SEARCH_COMMITS_TOOL_NAME, {
|
|
112805
112836
|
description: DESCRIPTION$5,
|
|
@@ -112807,16 +112838,16 @@ function registerGitHubSearchCommitsTool(server) {
|
|
|
112807
112838
|
query: z
|
|
112808
112839
|
.string()
|
|
112809
112840
|
.optional()
|
|
112810
|
-
.describe('
|
|
112841
|
+
.describe('Commit message search terms (keep minimal for broader results)'),
|
|
112811
112842
|
// Repository filters
|
|
112812
112843
|
owner: z
|
|
112813
112844
|
.string()
|
|
112814
112845
|
.optional()
|
|
112815
|
-
.describe('Repository owner
|
|
112846
|
+
.describe('Repository owner (use with repo param)'),
|
|
112816
112847
|
repo: z
|
|
112817
112848
|
.string()
|
|
112818
112849
|
.optional()
|
|
112819
|
-
.describe('Repository name
|
|
112850
|
+
.describe('Repository name (use with owner param)'),
|
|
112820
112851
|
// Author filters
|
|
112821
112852
|
author: z
|
|
112822
112853
|
.string()
|
|
@@ -112844,11 +112875,11 @@ function registerGitHubSearchCommitsTool(server) {
|
|
|
112844
112875
|
'author-date': z
|
|
112845
112876
|
.string()
|
|
112846
112877
|
.optional()
|
|
112847
|
-
.describe('
|
|
112878
|
+
.describe('Filter by author date (e.g., >2020-01-01)'),
|
|
112848
112879
|
'committer-date': z
|
|
112849
112880
|
.string()
|
|
112850
112881
|
.optional()
|
|
112851
|
-
.describe('
|
|
112882
|
+
.describe('Filter by commit date (e.g., >2020-01-01)'),
|
|
112852
112883
|
// Hash filters
|
|
112853
112884
|
hash: z.string().optional().describe('Commit SHA (full or partial)'),
|
|
112854
112885
|
parent: z.string().optional().describe('Parent commit SHA'),
|
|
@@ -112871,19 +112902,24 @@ function registerGitHubSearchCommitsTool(server) {
|
|
|
112871
112902
|
.max(50)
|
|
112872
112903
|
.optional()
|
|
112873
112904
|
.default(25)
|
|
112874
|
-
.describe('
|
|
112905
|
+
.describe('Maximum number of results to fetch'),
|
|
112875
112906
|
sort: z
|
|
112876
112907
|
.enum(['author-date', 'committer-date'])
|
|
112877
112908
|
.optional()
|
|
112878
|
-
.describe('Sort by date
|
|
112909
|
+
.describe('Sort by date field'),
|
|
112879
112910
|
order: z
|
|
112880
112911
|
.enum(['asc', 'desc'])
|
|
112881
112912
|
.optional()
|
|
112882
112913
|
.default('desc')
|
|
112883
|
-
.describe('Sort order
|
|
112914
|
+
.describe('Sort order'),
|
|
112915
|
+
getChangesContent: z
|
|
112916
|
+
.boolean()
|
|
112917
|
+
.optional()
|
|
112918
|
+
.default(false)
|
|
112919
|
+
.describe('Get actual code diffs - only use when analyzing changes, not for commit identification'),
|
|
112884
112920
|
},
|
|
112885
112921
|
annotations: {
|
|
112886
|
-
title: 'GitHub Commit Search - Smart
|
|
112922
|
+
title: 'GitHub Commit Search - Smart & Effective',
|
|
112887
112923
|
readOnlyHint: true,
|
|
112888
112924
|
destructiveHint: false,
|
|
112889
112925
|
idempotentHint: true,
|
|
@@ -112901,12 +112937,65 @@ function registerGitHubSearchCommitsTool(server) {
|
|
|
112901
112937
|
const items = Array.isArray(commits) ? commits : [];
|
|
112902
112938
|
// Smart handling for no results - provide actionable suggestions
|
|
112903
112939
|
if (items.length === 0) {
|
|
112940
|
+
// Progressive simplification strategy based on current search complexity
|
|
112941
|
+
const simplificationSteps = [];
|
|
112942
|
+
let hasFilters = false;
|
|
112943
|
+
// Check for active filters
|
|
112944
|
+
const activeFilters = [];
|
|
112945
|
+
if (args.owner && args.repo)
|
|
112946
|
+
activeFilters.push('repo');
|
|
112947
|
+
if (args.author)
|
|
112948
|
+
activeFilters.push('author');
|
|
112949
|
+
if (args['author-email'])
|
|
112950
|
+
activeFilters.push('author-email');
|
|
112951
|
+
if (args.hash)
|
|
112952
|
+
activeFilters.push('hash');
|
|
112953
|
+
if (args['author-date'])
|
|
112954
|
+
activeFilters.push('date');
|
|
112955
|
+
if (args.visibility)
|
|
112956
|
+
activeFilters.push('visibility');
|
|
112957
|
+
hasFilters = activeFilters.length > 0;
|
|
112958
|
+
// Step 1: If complex query, simplify search terms
|
|
112959
|
+
if (args.query && args.query.trim().split(' ').length > 2) {
|
|
112960
|
+
const words = args.query.trim().split(' ');
|
|
112961
|
+
const simplified = words.slice(0, 1).join(' '); // Take first word only
|
|
112962
|
+
simplificationSteps.push(`Try simpler search: "${simplified}" instead of "${args.query}"`);
|
|
112963
|
+
}
|
|
112964
|
+
// Step 2: Remove filters progressively
|
|
112965
|
+
if (hasFilters) {
|
|
112966
|
+
if (activeFilters.length > 1) {
|
|
112967
|
+
simplificationSteps.push(`Remove some filters (currently: ${activeFilters.join(', ')}) and keep only the most important one`);
|
|
112968
|
+
}
|
|
112969
|
+
else {
|
|
112970
|
+
simplificationSteps.push(`Remove the ${activeFilters[0]} filter and search more broadly`);
|
|
112971
|
+
}
|
|
112972
|
+
}
|
|
112973
|
+
// Step 3: Alternative approaches
|
|
112974
|
+
if (!args.query && hasFilters) {
|
|
112975
|
+
simplificationSteps.push('Add basic search terms like "fix", "update", or "add" with your filters');
|
|
112976
|
+
}
|
|
112977
|
+
// Step 4: Ask for user guidance if no obvious simplification
|
|
112978
|
+
if (simplificationSteps.length === 0) {
|
|
112979
|
+
simplificationSteps.push("Try different keywords or ask the user to be more specific about what commits they're looking for");
|
|
112980
|
+
}
|
|
112904
112981
|
return createResult({
|
|
112905
|
-
error: createNoResultsError('commits')
|
|
112982
|
+
error: `${createNoResultsError('commits')}
|
|
112983
|
+
|
|
112984
|
+
Try these simplified searches:
|
|
112985
|
+
${simplificationSteps.map(step => `• ${step}`).join('\n')}
|
|
112986
|
+
|
|
112987
|
+
Or ask the user:
|
|
112988
|
+
• "What specific type of commits are you looking for?"
|
|
112989
|
+
• "Can you provide different keywords to search for?"
|
|
112990
|
+
• "Should I search in a specific repository instead?"
|
|
112991
|
+
|
|
112992
|
+
Alternative tools:
|
|
112993
|
+
• Use github_search_code for file-specific commits
|
|
112994
|
+
• Use github_search_repos to find repositories first`,
|
|
112906
112995
|
});
|
|
112907
112996
|
}
|
|
112908
112997
|
// Transform to optimized format
|
|
112909
|
-
const optimizedResult = transformCommitsToOptimizedFormat(items, args);
|
|
112998
|
+
const optimizedResult = await transformCommitsToOptimizedFormat(items, args);
|
|
112910
112999
|
return createResult({ data: optimizedResult });
|
|
112911
113000
|
}
|
|
112912
113001
|
catch (error) {
|
|
@@ -112930,22 +113019,75 @@ function registerGitHubSearchCommitsTool(server) {
|
|
|
112930
113019
|
/**
|
|
112931
113020
|
* Transform GitHub CLI response to optimized format
|
|
112932
113021
|
*/
|
|
112933
|
-
function transformCommitsToOptimizedFormat(items,
|
|
113022
|
+
async function transformCommitsToOptimizedFormat(items, params) {
|
|
112934
113023
|
// Extract repository info if single repo search
|
|
112935
113024
|
const singleRepo = extractSingleRepository(items);
|
|
113025
|
+
// Fetch diff information if requested and this is a repo-specific search
|
|
113026
|
+
const shouldFetchDiff = params.getChangesContent && params.owner && params.repo;
|
|
113027
|
+
const diffData = new Map();
|
|
113028
|
+
if (shouldFetchDiff && items.length > 0) {
|
|
113029
|
+
// Fetch diff info for each commit (limit to first 10 to avoid rate limits)
|
|
113030
|
+
const commitShas = items.slice(0, 10).map(item => item.sha);
|
|
113031
|
+
const diffPromises = commitShas.map(async (sha) => {
|
|
113032
|
+
try {
|
|
113033
|
+
const diffResult = await executeGitHubCommand('api', [`/repos/${params.owner}/${params.repo}/commits/${sha}`], { cache: false });
|
|
113034
|
+
if (!diffResult.isError) {
|
|
113035
|
+
const diffExecResult = JSON.parse(diffResult.content[0].text);
|
|
113036
|
+
return { sha, commitData: diffExecResult.result };
|
|
113037
|
+
}
|
|
113038
|
+
}
|
|
113039
|
+
catch (e) {
|
|
113040
|
+
// Ignore diff fetch errors
|
|
113041
|
+
}
|
|
113042
|
+
return { sha, commitData: null };
|
|
113043
|
+
});
|
|
113044
|
+
const diffResults = await Promise.all(diffPromises);
|
|
113045
|
+
diffResults.forEach(({ sha, commitData }) => {
|
|
113046
|
+
if (commitData) {
|
|
113047
|
+
diffData.set(sha, commitData);
|
|
113048
|
+
}
|
|
113049
|
+
});
|
|
113050
|
+
}
|
|
112936
113051
|
const optimizedCommits = items
|
|
112937
|
-
.map(item =>
|
|
112938
|
-
|
|
112939
|
-
|
|
112940
|
-
|
|
112941
|
-
|
|
112942
|
-
|
|
112943
|
-
|
|
112944
|
-
|
|
112945
|
-
|
|
112946
|
-
|
|
112947
|
-
|
|
112948
|
-
|
|
113052
|
+
.map(item => {
|
|
113053
|
+
const commitObj = {
|
|
113054
|
+
sha: item.sha, // Use as branch parameter in github_fetch_content
|
|
113055
|
+
message: getCommitTitle(item.commit?.message ?? ''),
|
|
113056
|
+
author: item.commit?.author?.name ?? item.author?.login ?? 'Unknown',
|
|
113057
|
+
date: toDDMMYYYY(item.commit?.author?.date ?? ''),
|
|
113058
|
+
repository: singleRepo
|
|
113059
|
+
? undefined
|
|
113060
|
+
: simplifyRepoUrl(item.repository?.url || ''),
|
|
113061
|
+
url: singleRepo
|
|
113062
|
+
? item.sha
|
|
113063
|
+
: `${simplifyRepoUrl(item.repository?.url || '')}@${item.sha}`,
|
|
113064
|
+
};
|
|
113065
|
+
// Add diff information if available
|
|
113066
|
+
if (shouldFetchDiff && diffData.has(item.sha)) {
|
|
113067
|
+
const commitData = diffData.get(item.sha);
|
|
113068
|
+
const files = commitData.files || [];
|
|
113069
|
+
commitObj.diff = {
|
|
113070
|
+
changed_files: files.length,
|
|
113071
|
+
additions: commitData.stats?.additions || 0,
|
|
113072
|
+
deletions: commitData.stats?.deletions || 0,
|
|
113073
|
+
total_changes: commitData.stats?.total || 0,
|
|
113074
|
+
files: files
|
|
113075
|
+
.map((f) => ({
|
|
113076
|
+
filename: f.filename,
|
|
113077
|
+
status: f.status,
|
|
113078
|
+
additions: f.additions,
|
|
113079
|
+
deletions: f.deletions,
|
|
113080
|
+
changes: f.changes,
|
|
113081
|
+
patch: f.patch
|
|
113082
|
+
? f.patch.substring(0, 1000) +
|
|
113083
|
+
(f.patch.length > 1000 ? '...' : '')
|
|
113084
|
+
: undefined,
|
|
113085
|
+
}))
|
|
113086
|
+
.slice(0, 5), // Limit to 5 files per commit
|
|
113087
|
+
};
|
|
113088
|
+
}
|
|
113089
|
+
return commitObj;
|
|
113090
|
+
})
|
|
112949
113091
|
.map(commit => {
|
|
112950
113092
|
// Remove undefined fields
|
|
112951
113093
|
const cleanCommit = {};
|
|
@@ -113067,13 +113209,13 @@ function buildGitHubCommitCliArgs(params) {
|
|
|
113067
113209
|
|
|
113068
113210
|
// TODO: add PR commeents. e.g, gh pr view <PR_NUMBER_OR_URL_OR_BRANCH> --comments
|
|
113069
113211
|
const GITHUB_SEARCH_PULL_REQUESTS_TOOL_NAME = 'githubSearchPullRequests';
|
|
113070
|
-
const DESCRIPTION$4 = `Search GitHub
|
|
113212
|
+
const DESCRIPTION$4 = `Search GitHub PRs by keywords, state, or author. Returns head/base SHAs for github_fetch_content (branch=SHA). Can fetch PR content changes (diffs/patches) when getChangesContent=true.
|
|
113071
113213
|
|
|
113072
|
-
|
|
113073
|
-
- Use
|
|
113074
|
-
-
|
|
113075
|
-
-
|
|
113076
|
-
-
|
|
113214
|
+
SEARCH STRATEGY FOR BEST RESULTS:
|
|
113215
|
+
- Use minimal search terms for broader coverage (2-3 words max)
|
|
113216
|
+
- Separate searches for different aspects vs complex queries
|
|
113217
|
+
- Use filters to narrow scope after getting initial results
|
|
113218
|
+
- getChangesContent=true only when analyzing actual code changes`;
|
|
113077
113219
|
function registerSearchGitHubPullRequestsTool(server) {
|
|
113078
113220
|
server.registerTool(GITHUB_SEARCH_PULL_REQUESTS_TOOL_NAME, {
|
|
113079
113221
|
description: DESCRIPTION$4,
|
|
@@ -113081,15 +113223,15 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
113081
113223
|
query: z
|
|
113082
113224
|
.string()
|
|
113083
113225
|
.min(1, 'Search query is required and cannot be empty')
|
|
113084
|
-
.describe('Search
|
|
113226
|
+
.describe('Search query for PR content (keep minimal for broader results)'),
|
|
113085
113227
|
owner: z
|
|
113086
113228
|
.string()
|
|
113087
113229
|
.optional()
|
|
113088
|
-
.describe('Repository owner
|
|
113230
|
+
.describe('Repository owner (use with repo param)'),
|
|
113089
113231
|
repo: z
|
|
113090
113232
|
.string()
|
|
113091
113233
|
.optional()
|
|
113092
|
-
.describe('Repository name
|
|
113234
|
+
.describe('Repository name (use with owner param)'),
|
|
113093
113235
|
author: z.string().optional().describe('GitHub username of PR author'),
|
|
113094
113236
|
assignee: z.string().optional().describe('GitHub username of assignee'),
|
|
113095
113237
|
mentions: z.string().optional().describe('PRs mentioning this user'),
|
|
@@ -113106,48 +113248,45 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
113106
113248
|
state: z
|
|
113107
113249
|
.enum(['open', 'closed'])
|
|
113108
113250
|
.optional()
|
|
113109
|
-
.describe('
|
|
113110
|
-
head: z.string().optional().describe('
|
|
113111
|
-
base: z.string().optional().describe('
|
|
113251
|
+
.describe('Filter by state: open or closed'),
|
|
113252
|
+
head: z.string().optional().describe('Filter on head branch name'),
|
|
113253
|
+
base: z.string().optional().describe('Filter on base branch name'),
|
|
113112
113254
|
language: z.string().optional().describe('Repository language'),
|
|
113113
113255
|
created: z
|
|
113114
113256
|
.string()
|
|
113115
113257
|
.optional()
|
|
113116
|
-
.describe('
|
|
113258
|
+
.describe('Filter by created date (e.g., >2020-01-01)'),
|
|
113117
113259
|
updated: z
|
|
113118
113260
|
.string()
|
|
113119
113261
|
.optional()
|
|
113120
|
-
.describe('
|
|
113262
|
+
.describe('Filter by updated date (e.g., >2020-01-01)'),
|
|
113121
113263
|
'merged-at': z
|
|
113122
113264
|
.string()
|
|
113123
113265
|
.optional()
|
|
113124
|
-
.describe('
|
|
113266
|
+
.describe('Filter by merged date (e.g., >2020-01-01)'),
|
|
113125
113267
|
closed: z
|
|
113126
113268
|
.string()
|
|
113127
113269
|
.optional()
|
|
113128
|
-
.describe('
|
|
113129
|
-
draft: z.boolean().optional().describe('
|
|
113270
|
+
.describe('Filter by closed date (e.g., >2020-01-01)'),
|
|
113271
|
+
draft: z.boolean().optional().describe('Filter by draft state'),
|
|
113130
113272
|
checks: z
|
|
113131
113273
|
.enum(['pending', 'success', 'failure'])
|
|
113132
113274
|
.optional()
|
|
113133
|
-
.describe('
|
|
113134
|
-
merged: z
|
|
113135
|
-
.boolean()
|
|
113136
|
-
.optional()
|
|
113137
|
-
.describe('Only merged PRs (true) or unmerged (false)'),
|
|
113275
|
+
.describe('Filter by checks status'),
|
|
113276
|
+
merged: z.boolean().optional().describe('Filter by merged state'),
|
|
113138
113277
|
review: z
|
|
113139
113278
|
.enum(['none', 'required', 'approved', 'changes_requested'])
|
|
113140
113279
|
.optional()
|
|
113141
|
-
.describe('
|
|
113142
|
-
app: z.string().optional().describe('GitHub App
|
|
113280
|
+
.describe('Filter by review status'),
|
|
113281
|
+
app: z.string().optional().describe('Filter by GitHub App author'),
|
|
113143
113282
|
archived: z
|
|
113144
113283
|
.boolean()
|
|
113145
113284
|
.optional()
|
|
113146
|
-
.describe('
|
|
113285
|
+
.describe('Filter by repository archived state'),
|
|
113147
113286
|
comments: z
|
|
113148
113287
|
.number()
|
|
113149
113288
|
.optional()
|
|
113150
|
-
.describe('
|
|
113289
|
+
.describe('Filter by number of comments'),
|
|
113151
113290
|
interactions: z
|
|
113152
113291
|
.number()
|
|
113153
113292
|
.optional()
|
|
@@ -113155,23 +113294,32 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
113155
113294
|
'team-mentions': z
|
|
113156
113295
|
.string()
|
|
113157
113296
|
.optional()
|
|
113158
|
-
.describe('
|
|
113297
|
+
.describe('Filter by team mentions'),
|
|
113159
113298
|
reactions: z
|
|
113160
113299
|
.number()
|
|
113161
113300
|
.optional()
|
|
113162
|
-
.describe('
|
|
113163
|
-
locked: z
|
|
113164
|
-
|
|
113165
|
-
|
|
113301
|
+
.describe('Filter by number of reactions'),
|
|
113302
|
+
locked: z
|
|
113303
|
+
.boolean()
|
|
113304
|
+
.optional()
|
|
113305
|
+
.describe('Filter by locked conversation status'),
|
|
113306
|
+
'no-assignee': z
|
|
113307
|
+
.boolean()
|
|
113308
|
+
.optional()
|
|
113309
|
+
.describe('Filter by missing assignee'),
|
|
113310
|
+
'no-label': z.boolean().optional().describe('Filter by missing label'),
|
|
113166
113311
|
'no-milestone': z
|
|
113167
113312
|
.boolean()
|
|
113168
113313
|
.optional()
|
|
113169
|
-
.describe('
|
|
113170
|
-
'no-project': z
|
|
113314
|
+
.describe('Filter by missing milestone'),
|
|
113315
|
+
'no-project': z
|
|
113316
|
+
.boolean()
|
|
113317
|
+
.optional()
|
|
113318
|
+
.describe('Filter by missing project'),
|
|
113171
113319
|
label: z
|
|
113172
113320
|
.union([z.string(), z.array(z.string())])
|
|
113173
113321
|
.optional()
|
|
113174
|
-
.describe('
|
|
113322
|
+
.describe('Filter by label'),
|
|
113175
113323
|
milestone: z.string().optional().describe('Milestone title'),
|
|
113176
113324
|
project: z.string().optional().describe('Project board owner/number'),
|
|
113177
113325
|
visibility: z
|
|
@@ -113179,17 +113327,17 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
113179
113327
|
.optional()
|
|
113180
113328
|
.describe('Repository visibility'),
|
|
113181
113329
|
match: z
|
|
113182
|
-
.enum(['title', 'body', 'comments'])
|
|
113330
|
+
.array(z.enum(['title', 'body', 'comments']))
|
|
113183
113331
|
.optional()
|
|
113184
|
-
.describe('
|
|
113332
|
+
.describe('Restrict search to specific fields'),
|
|
113185
113333
|
limit: z
|
|
113186
113334
|
.number()
|
|
113187
113335
|
.int()
|
|
113188
113336
|
.min(1)
|
|
113189
|
-
.max(
|
|
113337
|
+
.max(100)
|
|
113190
113338
|
.optional()
|
|
113191
|
-
.default(
|
|
113192
|
-
.describe('
|
|
113339
|
+
.default(30)
|
|
113340
|
+
.describe('Maximum number of results to fetch'),
|
|
113193
113341
|
sort: z
|
|
113194
113342
|
.enum([
|
|
113195
113343
|
'comments',
|
|
@@ -113205,15 +113353,20 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
113205
113353
|
'updated',
|
|
113206
113354
|
])
|
|
113207
113355
|
.optional()
|
|
113208
|
-
.describe('Sort
|
|
113356
|
+
.describe('Sort fetched results'),
|
|
113209
113357
|
order: z
|
|
113210
113358
|
.enum(['asc', 'desc'])
|
|
113211
113359
|
.optional()
|
|
113212
113360
|
.default('desc')
|
|
113213
|
-
.describe('
|
|
113361
|
+
.describe('Order of results (requires --sort)'),
|
|
113362
|
+
getChangesContent: z
|
|
113363
|
+
.boolean()
|
|
113364
|
+
.optional()
|
|
113365
|
+
.default(false)
|
|
113366
|
+
.describe('Get actual code diffs - only use when analyzing changes, not for PR identification'),
|
|
113214
113367
|
},
|
|
113215
113368
|
annotations: {
|
|
113216
|
-
title: 'GitHub PR Search -
|
|
113369
|
+
title: 'GitHub PR Search - Smart & Effective',
|
|
113217
113370
|
readOnlyHint: true,
|
|
113218
113371
|
destructiveHint: false,
|
|
113219
113372
|
idempotentHint: true,
|
|
@@ -113246,6 +113399,52 @@ async function searchGitHubPullRequests(params) {
|
|
|
113246
113399
|
const { command, args } = buildGitHubPullRequestsAPICommand(params);
|
|
113247
113400
|
const result = await executeGitHubCommand(command, args, { cache: false });
|
|
113248
113401
|
if (result.isError) {
|
|
113402
|
+
const errorMsg = result.content[0].text;
|
|
113403
|
+
// Enhanced error handling for repository-specific searches
|
|
113404
|
+
if (params.owner && params.repo) {
|
|
113405
|
+
// Handle 404 errors with repository and branch checking
|
|
113406
|
+
if (errorMsg.includes('404')) {
|
|
113407
|
+
// Single repository check to avoid duplicate API calls
|
|
113408
|
+
const repoCheckResult = await executeGitHubCommand('api', [`/repos/${params.owner}/${params.repo}`], { cache: false });
|
|
113409
|
+
if (repoCheckResult.isError) {
|
|
113410
|
+
// Repository doesn't exist
|
|
113411
|
+
return createResult({
|
|
113412
|
+
error: `Repository "${params.owner}/${params.repo}" not found. Use github_search_repositories to find the correct repository name.`,
|
|
113413
|
+
});
|
|
113414
|
+
}
|
|
113415
|
+
// Repository exists, check if it's a branch-related error
|
|
113416
|
+
if (params.head || params.base) {
|
|
113417
|
+
try {
|
|
113418
|
+
const repoData = JSON.parse(repoCheckResult.content[0].text);
|
|
113419
|
+
const defaultBranch = repoData.result?.default_branch || 'main';
|
|
113420
|
+
let branchSuggestion = '';
|
|
113421
|
+
if (params.head && params.head !== defaultBranch) {
|
|
113422
|
+
branchSuggestion = `\n\nNote: Branch "${params.head}" may not exist. Repository default branch is "${defaultBranch}".`;
|
|
113423
|
+
branchSuggestion += `\nTry searching without head branch filter or use head="${defaultBranch}".`;
|
|
113424
|
+
}
|
|
113425
|
+
if (params.base && params.base !== defaultBranch) {
|
|
113426
|
+
branchSuggestion += `\n\nNote: Branch "${params.base}" may not exist. Repository default branch is "${defaultBranch}".`;
|
|
113427
|
+
branchSuggestion += `\nTry searching without base branch filter or use base="${defaultBranch}".`;
|
|
113428
|
+
}
|
|
113429
|
+
if (branchSuggestion) {
|
|
113430
|
+
return createResult({
|
|
113431
|
+
error: createNoResultsError('pull_requests') + branchSuggestion,
|
|
113432
|
+
});
|
|
113433
|
+
}
|
|
113434
|
+
}
|
|
113435
|
+
catch (e) {
|
|
113436
|
+
// Continue with original error if parsing fails
|
|
113437
|
+
}
|
|
113438
|
+
}
|
|
113439
|
+
}
|
|
113440
|
+
// Handle rate limit errors
|
|
113441
|
+
if (errorMsg.includes('rate limit') ||
|
|
113442
|
+
errorMsg.includes('API rate limit')) {
|
|
113443
|
+
return createResult({
|
|
113444
|
+
error: ERROR_MESSAGES.RATE_LIMIT_EXCEEDED,
|
|
113445
|
+
});
|
|
113446
|
+
}
|
|
113447
|
+
}
|
|
113249
113448
|
return result;
|
|
113250
113449
|
}
|
|
113251
113450
|
const execResult = JSON.parse(result.content[0].text);
|
|
@@ -113255,8 +113454,97 @@ async function searchGitHubPullRequests(params) {
|
|
|
113255
113454
|
? execResult.result
|
|
113256
113455
|
: execResult.result?.items || [];
|
|
113257
113456
|
if (pullRequests.length === 0) {
|
|
113457
|
+
// Progressive simplification strategy based on current search complexity
|
|
113458
|
+
const simplificationSteps = [];
|
|
113459
|
+
let hasFilters = false;
|
|
113460
|
+
// Check for active filters
|
|
113461
|
+
const activeFilters = [];
|
|
113462
|
+
if (params.owner && params.repo)
|
|
113463
|
+
activeFilters.push('repo');
|
|
113464
|
+
if (params.author)
|
|
113465
|
+
activeFilters.push('author');
|
|
113466
|
+
if (params.state)
|
|
113467
|
+
activeFilters.push('state');
|
|
113468
|
+
if (params.label)
|
|
113469
|
+
activeFilters.push('label');
|
|
113470
|
+
if (params.head)
|
|
113471
|
+
activeFilters.push('head-branch');
|
|
113472
|
+
if (params.base)
|
|
113473
|
+
activeFilters.push('base-branch');
|
|
113474
|
+
if (params.assignee)
|
|
113475
|
+
activeFilters.push('assignee');
|
|
113476
|
+
if (params.created)
|
|
113477
|
+
activeFilters.push('date');
|
|
113478
|
+
hasFilters = activeFilters.length > 0;
|
|
113479
|
+
// Step 1: If complex query, simplify search terms
|
|
113480
|
+
if (params.query && params.query.trim().split(' ').length > 2) {
|
|
113481
|
+
const words = params.query.trim().split(' ');
|
|
113482
|
+
const simplified = words.slice(0, 1).join(' '); // Take first word only
|
|
113483
|
+
simplificationSteps.push(`Try simpler search: "${simplified}" instead of "${params.query}"`);
|
|
113484
|
+
}
|
|
113485
|
+
// Step 2: Remove filters progressively
|
|
113486
|
+
if (hasFilters) {
|
|
113487
|
+
if (activeFilters.length > 2) {
|
|
113488
|
+
simplificationSteps.push(`Remove some filters (currently: ${activeFilters.join(', ')}) and keep only the most important ones`);
|
|
113489
|
+
}
|
|
113490
|
+
else if (activeFilters.length > 1) {
|
|
113491
|
+
simplificationSteps.push(`Remove one filter (currently: ${activeFilters.join(', ')}) to broaden search`);
|
|
113492
|
+
}
|
|
113493
|
+
else {
|
|
113494
|
+
simplificationSteps.push(`Remove the ${activeFilters[0]} filter and search more broadly`);
|
|
113495
|
+
}
|
|
113496
|
+
}
|
|
113497
|
+
// Step 3: Alternative approaches
|
|
113498
|
+
if (!params.query && hasFilters) {
|
|
113499
|
+
simplificationSteps.push('Add basic search terms like "fix", "feature", "bug", or "update" with your filters');
|
|
113500
|
+
}
|
|
113501
|
+
// Step 4: Repository-specific guidance
|
|
113502
|
+
if (!params.owner && !params.repo) {
|
|
113503
|
+
simplificationSteps.push('Try searching in a specific repository first using owner and repo parameters');
|
|
113504
|
+
}
|
|
113505
|
+
// Step 5: Ask for user guidance if no obvious simplification
|
|
113506
|
+
if (simplificationSteps.length === 0) {
|
|
113507
|
+
simplificationSteps.push("Try different keywords or ask the user to be more specific about what PRs they're looking for");
|
|
113508
|
+
}
|
|
113258
113509
|
return createResult({
|
|
113259
|
-
error: createNoResultsError('pull_requests')
|
|
113510
|
+
error: `${createNoResultsError('pull_requests')}
|
|
113511
|
+
|
|
113512
|
+
Try these simplified searches:
|
|
113513
|
+
${simplificationSteps.map(step => `• ${step}`).join('\n')}
|
|
113514
|
+
|
|
113515
|
+
Or ask the user:
|
|
113516
|
+
• "What specific type of pull requests are you looking for?"
|
|
113517
|
+
• "Can you provide different keywords to search for?"
|
|
113518
|
+
• "Should I search in a specific repository instead?"
|
|
113519
|
+
• "Are you looking for open or closed PRs?"
|
|
113520
|
+
|
|
113521
|
+
Alternative tools:
|
|
113522
|
+
• Use github_search_code for PR-related file changes
|
|
113523
|
+
• Use github_search_repos to find repositories first`,
|
|
113524
|
+
});
|
|
113525
|
+
}
|
|
113526
|
+
// Fetch diff information if requested and this is a repo-specific search
|
|
113527
|
+
const shouldFetchDiff = params.getChangesContent && params.owner && params.repo;
|
|
113528
|
+
const diffData = new Map();
|
|
113529
|
+
if (shouldFetchDiff && pullRequests.length > 0) {
|
|
113530
|
+
// Fetch diff info for each PR (limit to first 10 to avoid rate limits)
|
|
113531
|
+
const prNumbers = pullRequests.slice(0, 10).map((pr) => pr.number);
|
|
113532
|
+
const diffPromises = prNumbers.map(async (prNumber) => {
|
|
113533
|
+
try {
|
|
113534
|
+
const diffResult = await executeGitHubCommand('api', [`/repos/${params.owner}/${params.repo}/pulls/${prNumber}/files`], { cache: false });
|
|
113535
|
+
if (!diffResult.isError) {
|
|
113536
|
+
const diffExecResult = JSON.parse(diffResult.content[0].text);
|
|
113537
|
+
return { prNumber, files: diffExecResult.result };
|
|
113538
|
+
}
|
|
113539
|
+
}
|
|
113540
|
+
catch (e) {
|
|
113541
|
+
// Ignore diff fetch errors
|
|
113542
|
+
}
|
|
113543
|
+
return { prNumber, files: [] };
|
|
113544
|
+
});
|
|
113545
|
+
const diffResults = await Promise.all(diffPromises);
|
|
113546
|
+
diffResults.forEach(({ prNumber, files }) => {
|
|
113547
|
+
diffData.set(prNumber, files);
|
|
113260
113548
|
});
|
|
113261
113549
|
}
|
|
113262
113550
|
const cleanPRs = pullRequests.map((pr) => {
|
|
@@ -113276,15 +113564,38 @@ async function searchGitHubPullRequests(params) {
|
|
|
113276
113564
|
reactions: 0, // Not available in list format
|
|
113277
113565
|
draft: pr.isDraft || false,
|
|
113278
113566
|
};
|
|
113279
|
-
// Add commit SHAs
|
|
113567
|
+
// Add commit SHAs for use with github_fetch_content
|
|
113568
|
+
// Use head_sha/base_sha as branch parameter to view PR files
|
|
113280
113569
|
if (pr.headRefName)
|
|
113281
113570
|
result.head = pr.headRefName;
|
|
113282
113571
|
if (pr.headRefOid)
|
|
113283
|
-
result.head_sha = pr.headRefOid;
|
|
113572
|
+
result.head_sha = pr.headRefOid; // Use as branch=SHA
|
|
113284
113573
|
if (pr.baseRefName)
|
|
113285
113574
|
result.base = pr.baseRefName;
|
|
113286
113575
|
if (pr.baseRefOid)
|
|
113287
|
-
result.base_sha = pr.baseRefOid;
|
|
113576
|
+
result.base_sha = pr.baseRefOid; // Use as branch=SHA
|
|
113577
|
+
// Add diff information if available
|
|
113578
|
+
if (shouldFetchDiff && diffData.has(pr.number)) {
|
|
113579
|
+
const files = diffData.get(pr.number);
|
|
113580
|
+
result.diff = {
|
|
113581
|
+
changed_files: files.length,
|
|
113582
|
+
additions: files.reduce((sum, f) => sum + (f.additions || 0), 0),
|
|
113583
|
+
deletions: files.reduce((sum, f) => sum + (f.deletions || 0), 0),
|
|
113584
|
+
files: files
|
|
113585
|
+
.map((f) => ({
|
|
113586
|
+
filename: f.filename,
|
|
113587
|
+
status: f.status,
|
|
113588
|
+
additions: f.additions,
|
|
113589
|
+
deletions: f.deletions,
|
|
113590
|
+
changes: f.changes,
|
|
113591
|
+
patch: f.patch
|
|
113592
|
+
? f.patch.substring(0, 1000) +
|
|
113593
|
+
(f.patch.length > 1000 ? '...' : '')
|
|
113594
|
+
: undefined,
|
|
113595
|
+
}))
|
|
113596
|
+
.slice(0, 5), // Limit to 5 files per PR
|
|
113597
|
+
};
|
|
113598
|
+
}
|
|
113288
113599
|
return result;
|
|
113289
113600
|
}
|
|
113290
113601
|
// Handle search API format
|
|
@@ -113311,6 +113622,28 @@ async function searchGitHubPullRequests(params) {
|
|
|
113311
113622
|
result.head = pr.head.ref;
|
|
113312
113623
|
if (pr.base?.ref)
|
|
113313
113624
|
result.base = pr.base.ref;
|
|
113625
|
+
// Add diff information if available (search API format)
|
|
113626
|
+
if (shouldFetchDiff && diffData.has(pr.number)) {
|
|
113627
|
+
const files = diffData.get(pr.number);
|
|
113628
|
+
result.diff = {
|
|
113629
|
+
changed_files: files.length,
|
|
113630
|
+
additions: files.reduce((sum, f) => sum + (f.additions || 0), 0),
|
|
113631
|
+
deletions: files.reduce((sum, f) => sum + (f.deletions || 0), 0),
|
|
113632
|
+
files: files
|
|
113633
|
+
.map((f) => ({
|
|
113634
|
+
filename: f.filename,
|
|
113635
|
+
status: f.status,
|
|
113636
|
+
additions: f.additions,
|
|
113637
|
+
deletions: f.deletions,
|
|
113638
|
+
changes: f.changes,
|
|
113639
|
+
patch: f.patch
|
|
113640
|
+
? f.patch.substring(0, 1000) +
|
|
113641
|
+
(f.patch.length > 1000 ? '...' : '')
|
|
113642
|
+
: undefined,
|
|
113643
|
+
}))
|
|
113644
|
+
.slice(0, 5), // Limit to 5 files per PR
|
|
113645
|
+
};
|
|
113646
|
+
}
|
|
113314
113647
|
return result;
|
|
113315
113648
|
});
|
|
113316
113649
|
const searchResult = {
|
|
@@ -113319,11 +113652,25 @@ async function searchGitHubPullRequests(params) {
|
|
|
113319
113652
|
? cleanPRs.length
|
|
113320
113653
|
: execResult.result?.total_count || cleanPRs.length,
|
|
113321
113654
|
};
|
|
113322
|
-
|
|
113655
|
+
// Add helpful context if filtering by branches that might not exist
|
|
113656
|
+
let additionalContext = '';
|
|
113657
|
+
if (cleanPRs.length === 0 &&
|
|
113658
|
+
params.owner &&
|
|
113659
|
+
params.repo &&
|
|
113660
|
+
(params.head || params.base)) {
|
|
113661
|
+
additionalContext =
|
|
113662
|
+
'\n\nNote: No PRs found with specified branch filters. Consider removing head/base filters or checking if branches exist.';
|
|
113663
|
+
}
|
|
113664
|
+
return createResult({
|
|
113665
|
+
data: searchResult,
|
|
113666
|
+
...(additionalContext && { message: additionalContext }),
|
|
113667
|
+
});
|
|
113323
113668
|
});
|
|
113324
113669
|
}
|
|
113325
113670
|
function buildGitHubPullRequestsAPICommand(params) {
|
|
113326
|
-
// For repository-specific searches, use gh pr list
|
|
113671
|
+
// For repository-specific searches, use gh pr list instead of search API
|
|
113672
|
+
// This provides commit SHAs (head_sha, base_sha) which are essential for
|
|
113673
|
+
// integrating with github_fetch_content to view PR code at specific commits
|
|
113327
113674
|
if (params.owner && params.repo) {
|
|
113328
113675
|
return buildGitHubPullRequestsListCommand(params);
|
|
113329
113676
|
}
|
|
@@ -113401,10 +113748,13 @@ function buildGitHubPullRequestsAPICommand(params) {
|
|
|
113401
113748
|
queryParts.push(`milestone:"${params.milestone}"`);
|
|
113402
113749
|
if (params.project)
|
|
113403
113750
|
queryParts.push(`project:${params.project}`);
|
|
113751
|
+
if (params.match) {
|
|
113752
|
+
params.match.forEach(field => queryParts.push(`in:${field}`));
|
|
113753
|
+
}
|
|
113404
113754
|
// Add type qualifier to search only pull requests
|
|
113405
113755
|
queryParts.push('type:pr');
|
|
113406
113756
|
const query = queryParts.filter(Boolean).join(' ');
|
|
113407
|
-
const limit = Math.min(params.limit ||
|
|
113757
|
+
const limit = Math.min(params.limit || 30, 100);
|
|
113408
113758
|
let apiPath = `search/issues?q=${encodeURIComponent(query)}&per_page=${limit}`;
|
|
113409
113759
|
if (params.sort)
|
|
113410
113760
|
apiPath += `&sort=${params.sort}`;
|
|
@@ -113420,7 +113770,7 @@ function buildGitHubPullRequestsListCommand(params) {
|
|
|
113420
113770
|
'--json',
|
|
113421
113771
|
'number,title,headRefName,headRefOid,baseRefName,baseRefOid,state,author,labels,createdAt,updatedAt,url,comments,isDraft',
|
|
113422
113772
|
'--limit',
|
|
113423
|
-
String(Math.min(params.limit ||
|
|
113773
|
+
String(Math.min(params.limit || 30, 100)),
|
|
113424
113774
|
];
|
|
113425
113775
|
// Add filters
|
|
113426
113776
|
if (params.state) {
|
|
@@ -113756,8 +114106,10 @@ async function viewRepositoryStructure(params) {
|
|
|
113756
114106
|
try {
|
|
113757
114107
|
// Clean up path
|
|
113758
114108
|
const cleanPath = path.startsWith('/') ? path.substring(1) : path;
|
|
113759
|
-
// Try the requested branch first
|
|
113760
|
-
const apiPath =
|
|
114109
|
+
// Try the requested branch first - handle empty path correctly
|
|
114110
|
+
const apiPath = cleanPath
|
|
114111
|
+
? `/repos/${owner}/${repo}/contents/${cleanPath}?ref=${branch}`
|
|
114112
|
+
: `/repos/${owner}/${repo}/contents?ref=${branch}`;
|
|
113761
114113
|
const result = await executeGitHubCommand('api', [apiPath], {
|
|
113762
114114
|
cache: false,
|
|
113763
114115
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "octocode-mcp",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.16",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server for advanced GitHub repository analysis, code discovery, and npm package exploration. Provides AI assistants with powerful tools to search, analyze, and understand codebases across GitHub and npm ecosystems.",
|
|
5
5
|
"author": "Guy Bary <guybary@gmail.com>",
|
|
6
6
|
"homepage": "https://octocode.ai",
|