octocode-mcp 2.3.6 → 2.3.7
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/build/index.js +483 -232
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -107,9 +107,9 @@ function escapeShellArg(arg, shellType, isGitHubQuery // Flag to indicate if thi
|
|
|
107
107
|
const isWindows = platform() === 'win32';
|
|
108
108
|
shellType = isWindows ? 'cmd' : 'unix';
|
|
109
109
|
}
|
|
110
|
-
// Special handling for GitHub search queries
|
|
110
|
+
// Special handling for GitHub search queries to preserve AND logic
|
|
111
111
|
if (isGitHubQuery) {
|
|
112
|
-
// If the argument already contains quotes, preserve them
|
|
112
|
+
// If the argument already contains quotes, preserve them for exact phrases
|
|
113
113
|
if (arg.includes('"')) {
|
|
114
114
|
// For Unix-like shells, wrap the entire argument in single quotes
|
|
115
115
|
if (shellType === 'unix') {
|
|
@@ -122,6 +122,13 @@ function escapeShellArg(arg, shellType, isGitHubQuery // Flag to indicate if thi
|
|
|
122
122
|
// For PowerShell
|
|
123
123
|
return `'${arg.replace(/'/g, "''")}'`;
|
|
124
124
|
}
|
|
125
|
+
// For space-separated terms (AND search), minimize escaping
|
|
126
|
+
if (arg.includes(' ') && shellType === 'unix') {
|
|
127
|
+
// Only escape if contains dangerous shell characters
|
|
128
|
+
if (!/[;&|<>$`\\]/.test(arg)) {
|
|
129
|
+
return `"${arg}"`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
125
132
|
}
|
|
126
133
|
switch (shellType) {
|
|
127
134
|
case 'powershell':
|
|
@@ -158,16 +165,28 @@ function escapeWindowsCmdArg(arg) {
|
|
|
158
165
|
}
|
|
159
166
|
/**
|
|
160
167
|
* Escape arguments for Unix shells with special handling for GitHub CLI queries
|
|
168
|
+
* Preserves AND search logic by not over-escaping space-separated terms
|
|
161
169
|
*/
|
|
162
170
|
function escapeUnixShellArg(arg, isGitHubQuery) {
|
|
163
|
-
//
|
|
164
|
-
if (isGitHubQuery
|
|
165
|
-
//
|
|
171
|
+
// For GitHub search queries, we need to preserve AND logic
|
|
172
|
+
if (isGitHubQuery) {
|
|
173
|
+
// If the query contains quotes, preserve them for exact phrase matching
|
|
166
174
|
if (arg.includes('"')) {
|
|
175
|
+
// Use single quotes to wrap the entire query while preserving internal quotes
|
|
176
|
+
return `'${arg.replace(/'/g, "'\"'\"'")}'`;
|
|
177
|
+
}
|
|
178
|
+
// For space-separated terms (AND search), only escape if absolutely necessary
|
|
179
|
+
// GitHub CLI expects space-separated terms for AND logic
|
|
180
|
+
if (arg.includes(' ') && !/[;&|<>$`\\]/.test(arg)) {
|
|
181
|
+
// Only wrap in quotes if it contains shell metacharacters beyond spaces
|
|
182
|
+
return `"${arg}"`;
|
|
183
|
+
}
|
|
184
|
+
// For single terms or terms with special chars, escape normally
|
|
185
|
+
if (/[;&|<>$`\\]/.test(arg)) {
|
|
167
186
|
return `'${arg.replace(/'/g, "'\"'\"'")}'`;
|
|
168
187
|
}
|
|
169
|
-
//
|
|
170
|
-
return
|
|
188
|
+
// Simple terms don't need escaping
|
|
189
|
+
return arg;
|
|
171
190
|
}
|
|
172
191
|
// Standard Unix shell escaping for other arguments
|
|
173
192
|
if (/[^a-zA-Z0-9\-_./=@:]/.test(arg)) {
|
|
@@ -313,61 +332,6 @@ async function executeCommand(fullCommand, type, options = {}, shellConfig) {
|
|
|
313
332
|
}
|
|
314
333
|
}
|
|
315
334
|
|
|
316
|
-
var LogLevel;
|
|
317
|
-
(function (LogLevel) {
|
|
318
|
-
LogLevel["ERROR"] = "ERROR";
|
|
319
|
-
LogLevel["WARN"] = "WARN";
|
|
320
|
-
LogLevel["INFO"] = "INFO";
|
|
321
|
-
LogLevel["DEBUG"] = "DEBUG";
|
|
322
|
-
})(LogLevel || (LogLevel = {}));
|
|
323
|
-
class Logger {
|
|
324
|
-
appName;
|
|
325
|
-
timestamp;
|
|
326
|
-
constructor(appName = 'octocode-mcp', timestamp = true) {
|
|
327
|
-
this.appName = appName;
|
|
328
|
-
this.timestamp = timestamp;
|
|
329
|
-
}
|
|
330
|
-
formatMessage(level, message, ...args) {
|
|
331
|
-
const timestamp = this.timestamp ? `[${new Date().toISOString()}] ` : '';
|
|
332
|
-
const prefix = `${timestamp}[${this.appName}] [${level}]`;
|
|
333
|
-
const formattedArgs = args.length > 0
|
|
334
|
-
? ' ' +
|
|
335
|
-
args
|
|
336
|
-
.map(arg => {
|
|
337
|
-
if (arg instanceof Error) {
|
|
338
|
-
return `${arg.message}\n${arg.stack}`;
|
|
339
|
-
}
|
|
340
|
-
return typeof arg === 'object'
|
|
341
|
-
? JSON.stringify(arg, null, 2)
|
|
342
|
-
: String(arg);
|
|
343
|
-
})
|
|
344
|
-
.join(' ')
|
|
345
|
-
: '';
|
|
346
|
-
return `${prefix} ${message}${formattedArgs}`;
|
|
347
|
-
}
|
|
348
|
-
error(message, ...args) {
|
|
349
|
-
// eslint-disable-next-line no-console
|
|
350
|
-
console.error(this.formatMessage(LogLevel.ERROR, message, ...args));
|
|
351
|
-
}
|
|
352
|
-
warn(message, ...args) {
|
|
353
|
-
// eslint-disable-next-line no-console
|
|
354
|
-
console.warn(this.formatMessage(LogLevel.WARN, message, ...args));
|
|
355
|
-
}
|
|
356
|
-
info(message, ...args) {
|
|
357
|
-
// eslint-disable-next-line no-console
|
|
358
|
-
console.log(this.formatMessage(LogLevel.INFO, message, ...args));
|
|
359
|
-
}
|
|
360
|
-
debug(message, ...args) {
|
|
361
|
-
if (process.env.DEBUG === 'true' ||
|
|
362
|
-
process.env.NODE_ENV === 'development') {
|
|
363
|
-
// eslint-disable-next-line no-console
|
|
364
|
-
console.log(this.formatMessage(LogLevel.DEBUG, message, ...args));
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
// Singleton instance
|
|
369
|
-
const logger = new Logger();
|
|
370
|
-
|
|
371
335
|
function createResult(options) {
|
|
372
336
|
const { data, error } = options;
|
|
373
337
|
if (error) {
|
|
@@ -387,7 +351,6 @@ function createResult(options) {
|
|
|
387
351
|
};
|
|
388
352
|
}
|
|
389
353
|
catch (jsonError) {
|
|
390
|
-
logger.error('JSON serialization failed:', jsonError);
|
|
391
354
|
return {
|
|
392
355
|
content: [
|
|
393
356
|
{
|
|
@@ -783,11 +746,13 @@ async function viewRepositoryStructure(params) {
|
|
|
783
746
|
let usedBranch = branch;
|
|
784
747
|
let lastError = null;
|
|
785
748
|
let attemptCount = 0;
|
|
786
|
-
const maxAttempts =
|
|
749
|
+
const maxAttempts = branchesToTry.length;
|
|
750
|
+
const triedBranches = [];
|
|
787
751
|
for (const tryBranch of branchesToTry) {
|
|
788
752
|
if (attemptCount >= maxAttempts)
|
|
789
753
|
break;
|
|
790
754
|
attemptCount++;
|
|
755
|
+
triedBranches.push(tryBranch);
|
|
791
756
|
try {
|
|
792
757
|
const apiPath = `/repos/${owner}/${repo}/contents/${cleanPath}?ref=${tryBranch}`;
|
|
793
758
|
const result = await executeGitHubCommand('api', [apiPath], {
|
|
@@ -806,33 +771,52 @@ async function viewRepositoryStructure(params) {
|
|
|
806
771
|
}
|
|
807
772
|
catch (error) {
|
|
808
773
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
809
|
-
// Try next branch
|
|
810
774
|
continue;
|
|
811
775
|
}
|
|
812
776
|
}
|
|
813
777
|
if (items.length === 0) {
|
|
814
|
-
//
|
|
778
|
+
// Check repository existence only after content fetch fails
|
|
779
|
+
const repoCheckResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
|
|
780
|
+
cache: false,
|
|
781
|
+
});
|
|
782
|
+
if (repoCheckResult.isError) {
|
|
783
|
+
const repoErrorMsg = repoCheckResult.content[0].text;
|
|
784
|
+
if (repoErrorMsg.includes('404')) {
|
|
785
|
+
return createResult({
|
|
786
|
+
error: `Repository "${owner}/${repo}" not found. It might have been deleted, renamed, or made private. Use github_search_code to find current location.`,
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
else if (repoErrorMsg.includes('403')) {
|
|
790
|
+
return createResult({
|
|
791
|
+
error: `Repository "${owner}/${repo}" exists but access is denied. Repository might be private or archived. Use api_status_check to verify permissions.`,
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
}
|
|
815
795
|
const errorMsg = lastError?.message || 'Unknown error';
|
|
816
796
|
if (errorMsg.includes('404') || errorMsg.includes('Not Found')) {
|
|
817
797
|
if (path) {
|
|
798
|
+
const searchSuggestion = await suggestPathSearchFallback(owner, path);
|
|
818
799
|
return createResult({
|
|
819
|
-
error: `Path "${path}" not found
|
|
800
|
+
error: `Path "${path}" not found in any branch (tried: ${triedBranches.join(', ')}).${searchSuggestion}`,
|
|
820
801
|
});
|
|
821
802
|
}
|
|
822
803
|
else {
|
|
823
804
|
return createResult({
|
|
824
|
-
error: `Repository
|
|
805
|
+
error: `Repository "${owner}/${repo}" structure not accessible. Repository might be empty, private, or you might not have sufficient permissions. Use github_search_code with owner="${owner}" to find accessible repositories.`,
|
|
825
806
|
});
|
|
826
807
|
}
|
|
827
808
|
}
|
|
828
809
|
else if (errorMsg.includes('403') || errorMsg.includes('Forbidden')) {
|
|
829
810
|
return createResult({
|
|
830
|
-
error: `Access denied to ${owner}/${repo}. Repository
|
|
811
|
+
error: `Access denied to "${owner}/${repo}". Repository exists but might be private/archived. Use api_status_check to verify permissions, or github_search_code with owner="${owner}" to find accessible repositories.`,
|
|
831
812
|
});
|
|
832
813
|
}
|
|
833
814
|
else {
|
|
815
|
+
const searchSuggestion = path
|
|
816
|
+
? await suggestPathSearchFallback(owner, path)
|
|
817
|
+
: '';
|
|
834
818
|
return createResult({
|
|
835
|
-
error: `Failed to access ${owner}/${repo}. Check network connection`,
|
|
819
|
+
error: `Failed to access "${owner}/${repo}": ${errorMsg}. Check network connection and repository permissions.${searchSuggestion}`,
|
|
836
820
|
});
|
|
837
821
|
}
|
|
838
822
|
}
|
|
@@ -877,17 +861,19 @@ async function viewRepositoryStructure(params) {
|
|
|
877
861
|
});
|
|
878
862
|
}
|
|
879
863
|
catch (error) {
|
|
864
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
880
865
|
return createResult({
|
|
881
|
-
error: `Failed to access repository
|
|
866
|
+
error: `Failed to access repository "${owner}/${repo}": ${errorMessage}. Verify repository name, permissions, and network connection.`,
|
|
882
867
|
});
|
|
883
868
|
}
|
|
884
869
|
});
|
|
885
870
|
}
|
|
886
871
|
/**
|
|
887
872
|
* Smart branch detection with automatic fallback to common branch names.
|
|
873
|
+
* Now includes more comprehensive branch detection and better error handling.
|
|
888
874
|
*/
|
|
889
875
|
async function getSmartBranchFallback(owner, repo, requestedBranch) {
|
|
890
|
-
const branches = [requestedBranch];
|
|
876
|
+
const branches = new Set([requestedBranch]);
|
|
891
877
|
try {
|
|
892
878
|
// Try to get repository info to find default branch
|
|
893
879
|
const repoInfoResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
|
|
@@ -897,22 +883,48 @@ async function getSmartBranchFallback(owner, repo, requestedBranch) {
|
|
|
897
883
|
const execResult = JSON.parse(repoInfoResult.content[0].text);
|
|
898
884
|
const repoData = execResult.result;
|
|
899
885
|
const defaultBranch = repoData.default_branch;
|
|
900
|
-
if (defaultBranch
|
|
901
|
-
branches.
|
|
886
|
+
if (defaultBranch) {
|
|
887
|
+
branches.add(defaultBranch);
|
|
902
888
|
}
|
|
903
889
|
}
|
|
904
890
|
}
|
|
905
891
|
catch {
|
|
906
892
|
// If we can't get repo info, proceed with standard fallbacks
|
|
907
893
|
}
|
|
908
|
-
// Add
|
|
909
|
-
const commonBranches = ['main', 'master'
|
|
910
|
-
commonBranches.forEach(branch =>
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
894
|
+
// Add only main/master as fallback branches
|
|
895
|
+
const commonBranches = ['main', 'master'];
|
|
896
|
+
commonBranches.forEach(branch => branches.add(branch));
|
|
897
|
+
// Convert Set back to array, with requested branch first
|
|
898
|
+
const branchesArray = Array.from(branches);
|
|
899
|
+
branchesArray.sort((a, b) => {
|
|
900
|
+
if (a === requestedBranch)
|
|
901
|
+
return -1;
|
|
902
|
+
if (b === requestedBranch)
|
|
903
|
+
return 1;
|
|
904
|
+
return 0;
|
|
914
905
|
});
|
|
915
|
-
return
|
|
906
|
+
return branchesArray;
|
|
907
|
+
}
|
|
908
|
+
// Helper function to suggest path search strategy
|
|
909
|
+
async function suggestPathSearchFallback(owner, path) {
|
|
910
|
+
try {
|
|
911
|
+
// Extract last path segment and try to find in same organization
|
|
912
|
+
const pathSegment = path.split('/').pop() || path;
|
|
913
|
+
const searchResult = await executeGitHubCommand('api', [
|
|
914
|
+
`/search/code?q=${encodeURIComponent(pathSegment)}+in:path+org:${owner}`,
|
|
915
|
+
], { cache: false });
|
|
916
|
+
if (!searchResult.isError) {
|
|
917
|
+
const results = JSON.parse(searchResult.content[0].text);
|
|
918
|
+
if (results.total_count > 0) {
|
|
919
|
+
const firstMatch = results.items[0];
|
|
920
|
+
return ` Directory might be in ${firstMatch.repository.full_name}. Try these searches:\n1. github_search_code with query="${pathSegment}" owner="${owner}"\n2. github_search_code with query="path:${path}" owner="${owner}"`;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
catch {
|
|
925
|
+
// Fallback to generic message if search fails
|
|
926
|
+
}
|
|
927
|
+
return ` Try these searches:\n1. github_search_code with query="${path.split('/').pop()}" owner="${owner}"\n2. github_search_code with query="path:${path}"`;
|
|
916
928
|
}
|
|
917
929
|
|
|
918
930
|
const GITHUB_GET_FILE_CONTENT_TOOL_NAME = 'githubGetFileContent';
|
|
@@ -969,18 +981,39 @@ async function fetchGitHubFileContent(params) {
|
|
|
969
981
|
const { owner, repo, branch, filePath } = params;
|
|
970
982
|
try {
|
|
971
983
|
// Try to fetch file content directly
|
|
972
|
-
const apiPath = `/repos/${owner}/${repo}/contents/${filePath}
|
|
984
|
+
const apiPath = `/repos/${owner}/${repo}/contents/${filePath}`;
|
|
973
985
|
const result = await executeGitHubCommand('api', [apiPath], {
|
|
974
986
|
cache: false,
|
|
975
987
|
});
|
|
976
988
|
if (result.isError) {
|
|
977
989
|
const errorMsg = result.content[0].text;
|
|
990
|
+
// Check repository existence only after content fetch fails
|
|
991
|
+
const repoCheckResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
|
|
992
|
+
cache: false,
|
|
993
|
+
});
|
|
994
|
+
if (repoCheckResult.isError) {
|
|
995
|
+
const repoErrorMsg = repoCheckResult.content[0].text;
|
|
996
|
+
if (repoErrorMsg.includes('404')) {
|
|
997
|
+
return createResult({
|
|
998
|
+
error: `Repository "${owner}/${repo}" not found. It might have been deleted, renamed, or made private. Use github_search_code to find current location.`,
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
else if (repoErrorMsg.includes('403')) {
|
|
1002
|
+
return createResult({
|
|
1003
|
+
error: `Repository "${owner}/${repo}" exists but access is denied. Repository might be private or archived. Use api_status_check to verify permissions.`,
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
978
1007
|
// Try fallback branches if original branch fails
|
|
979
1008
|
if (errorMsg.includes('404') &&
|
|
980
1009
|
branch !== 'main' &&
|
|
981
1010
|
branch !== 'master') {
|
|
982
1011
|
const fallbackBranches = ['main', 'master'];
|
|
1012
|
+
const triedBranches = [branch];
|
|
983
1013
|
for (const fallbackBranch of fallbackBranches) {
|
|
1014
|
+
if (triedBranches.includes(fallbackBranch))
|
|
1015
|
+
continue;
|
|
1016
|
+
triedBranches.push(fallbackBranch);
|
|
984
1017
|
const fallbackPath = `/repos/${owner}/${repo}/contents/${filePath}?ref=${fallbackBranch}`;
|
|
985
1018
|
const fallbackResult = await executeGitHubCommand('api', [fallbackPath], {
|
|
986
1019
|
cache: false,
|
|
@@ -989,27 +1022,32 @@ async function fetchGitHubFileContent(params) {
|
|
|
989
1022
|
return await processFileContent(fallbackResult, owner, repo, fallbackBranch, filePath);
|
|
990
1023
|
}
|
|
991
1024
|
}
|
|
1025
|
+
return createResult({
|
|
1026
|
+
error: `File not found in any common branches (tried: ${triedBranches.join(', ')}). File might have been moved or deleted. Use github_search_code to find current location.`,
|
|
1027
|
+
});
|
|
992
1028
|
}
|
|
993
|
-
// Handle common errors
|
|
1029
|
+
// Handle common errors with more context
|
|
994
1030
|
if (errorMsg.includes('404')) {
|
|
1031
|
+
const searchSuggestion = await suggestCodeSearchFallback(owner, filePath);
|
|
995
1032
|
return createResult({
|
|
996
|
-
error:
|
|
1033
|
+
error: `File "${filePath}" not found in branch "${branch}". Use github_view_repo_structure to verify path.${searchSuggestion}`,
|
|
997
1034
|
});
|
|
998
1035
|
}
|
|
999
1036
|
else if (errorMsg.includes('403')) {
|
|
1000
1037
|
return createResult({
|
|
1001
|
-
error:
|
|
1038
|
+
error: `Access denied to "${filePath}" in "${owner}/${repo}". Repository or file might be private/archived. Use api_status_check to verify permissions, or github_search_code with query="path:${filePath}" owner="${owner}" to find in accessible repositories.`,
|
|
1002
1039
|
});
|
|
1003
1040
|
}
|
|
1004
1041
|
else if (errorMsg.includes('maxBuffer') ||
|
|
1005
1042
|
errorMsg.includes('stdout maxBuffer length exceeded')) {
|
|
1006
1043
|
return createResult({
|
|
1007
|
-
error:
|
|
1044
|
+
error: `File "${filePath}" is too large (>300KB). Use github_search_code with query="path:${filePath}" to search within the file or download directly from GitHub.`,
|
|
1008
1045
|
});
|
|
1009
1046
|
}
|
|
1010
1047
|
else {
|
|
1048
|
+
const searchSuggestion = await suggestCodeSearchFallback(owner, filePath);
|
|
1011
1049
|
return createResult({
|
|
1012
|
-
error:
|
|
1050
|
+
error: `Failed to fetch "${filePath}". Error: ${errorMsg}. Verify repository name, branch, and file path.${searchSuggestion}`,
|
|
1013
1051
|
});
|
|
1014
1052
|
}
|
|
1015
1053
|
}
|
|
@@ -1017,15 +1055,14 @@ async function fetchGitHubFileContent(params) {
|
|
|
1017
1055
|
}
|
|
1018
1056
|
catch (error) {
|
|
1019
1057
|
const errorMessage = error.message;
|
|
1020
|
-
// Handle maxBuffer errors that escape the main try-catch
|
|
1021
1058
|
if (errorMessage.includes('maxBuffer') ||
|
|
1022
1059
|
errorMessage.includes('stdout maxBuffer length exceeded')) {
|
|
1023
1060
|
return createResult({
|
|
1024
|
-
error:
|
|
1061
|
+
error: `File "${filePath}" is too large (>300KB). Use github_search_code to search within the file or download directly from GitHub.`,
|
|
1025
1062
|
});
|
|
1026
1063
|
}
|
|
1027
1064
|
return createResult({
|
|
1028
|
-
error:
|
|
1065
|
+
error: `Unexpected error fetching "${filePath}": ${errorMessage}. Check network connection and try again.`,
|
|
1029
1066
|
});
|
|
1030
1067
|
}
|
|
1031
1068
|
});
|
|
@@ -1088,9 +1125,39 @@ async function processFileContent(result, owner, repo, branch, filePath) {
|
|
|
1088
1125
|
},
|
|
1089
1126
|
});
|
|
1090
1127
|
}
|
|
1128
|
+
// Helper function to suggest code search strategy
|
|
1129
|
+
async function suggestCodeSearchFallback(owner, filePath) {
|
|
1130
|
+
try {
|
|
1131
|
+
// Extract filename and try to find in same organization
|
|
1132
|
+
const fileName = filePath.split('/').pop() || filePath;
|
|
1133
|
+
const searchResult = await executeGitHubCommand('api', [`/search/code?q=${encodeURIComponent(fileName)}+in:path+org:${owner}`], { cache: false });
|
|
1134
|
+
if (!searchResult.isError) {
|
|
1135
|
+
const results = JSON.parse(searchResult.content[0].text);
|
|
1136
|
+
if (results.total_count > 0) {
|
|
1137
|
+
const firstMatch = results.items[0];
|
|
1138
|
+
return ` File might be in ${firstMatch.repository.full_name}. Try these searches:\n1. github_search_code with query="${fileName}" owner="${owner}"\n2. github_search_code with query="path:${filePath}" owner="${owner}"`;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
catch {
|
|
1143
|
+
// Fallback to generic message if search fails
|
|
1144
|
+
}
|
|
1145
|
+
return ` Try these searches:\n1. github_search_code with query="${filePath.split('/').pop()}" owner="${owner}"\n2. github_search_code with query="path:${filePath}"`;
|
|
1146
|
+
}
|
|
1091
1147
|
|
|
1092
1148
|
const GITHUB_SEARCH_CODE_TOOL_NAME = 'githubSearchCode';
|
|
1093
|
-
const DESCRIPTION$6 = `Search code across GitHub repositories
|
|
1149
|
+
const DESCRIPTION$6 = `Search code across GitHub repositories using GitHub's code search API.
|
|
1150
|
+
|
|
1151
|
+
Search Syntax (all terms must be present with AND logic):
|
|
1152
|
+
- Multiple words: react lifecycle → finds files with BOTH "react" AND "lifecycle" (separate words)
|
|
1153
|
+
- Exact phrases: "error handling" → finds exact phrase "error handling" (quoted phrase)
|
|
1154
|
+
- Mixed: "async function" timeout → finds exact phrase "async function" AND word "timeout"
|
|
1155
|
+
- Complex: useState useEffect "custom hook" → finds "useState" AND "useEffect" AND exact phrase "custom hook"
|
|
1156
|
+
- Advanced: authentication authorization "jwt token" → finds "authentication" AND "authorization" AND exact phrase "jwt token"
|
|
1157
|
+
- Framework: "React.Component" "componentDidMount" → finds both exact phrases in same file
|
|
1158
|
+
|
|
1159
|
+
Key difference: Quotes create exact phrases, no quotes = individual words (all must be present).
|
|
1160
|
+
Start with 1-2 terms, add more to narrow results. Use filters for precision.`;
|
|
1094
1161
|
function registerGitHubSearchCodeTool(server) {
|
|
1095
1162
|
server.registerTool(GITHUB_SEARCH_CODE_TOOL_NAME, {
|
|
1096
1163
|
description: DESCRIPTION$6,
|
|
@@ -1098,31 +1165,36 @@ function registerGitHubSearchCodeTool(server) {
|
|
|
1098
1165
|
query: z
|
|
1099
1166
|
.string()
|
|
1100
1167
|
.min(1)
|
|
1101
|
-
.describe('Search terms.
|
|
1168
|
+
.describe('Search query with AND logic between terms. Multiple words require ALL to be present. Use quotes for exact phrases. Examples: react lifecycle (both words), "error handling" (exact phrase), async await "try catch" (words + phrase), "function definition" variable scope (phrase + words)'),
|
|
1102
1169
|
language: z
|
|
1103
1170
|
.string()
|
|
1104
1171
|
.optional()
|
|
1105
|
-
.describe('
|
|
1172
|
+
.describe('Programming language filter (javascript, python, typescript, go, etc). Narrows search to specific language files. Use for language-specific searches.'),
|
|
1106
1173
|
owner: z
|
|
1107
1174
|
.union([z.string(), z.array(z.string())])
|
|
1108
1175
|
.optional()
|
|
1109
|
-
.describe('Repository owner
|
|
1176
|
+
.describe('Repository owner or organization name(s) to search within. Format: "microsoft" or ["facebook", "google"]. Drastically narrows search scope. Do NOT use owner/repo format.'),
|
|
1177
|
+
repo: z
|
|
1178
|
+
.union([z.string(), z.array(z.string())])
|
|
1179
|
+
.optional()
|
|
1180
|
+
.describe('Filter on specific repository(ies). Format: "owner/repo" or ["microsoft/vscode", "facebook/react"]. Use for repository-specific searches.'),
|
|
1110
1181
|
filename: z
|
|
1111
1182
|
.string()
|
|
1112
1183
|
.optional()
|
|
1113
|
-
.describe('
|
|
1184
|
+
.describe('Target specific filename or pattern. Examples: "package.json", "*.test.js", "Dockerfile". Use for file-specific searches.'),
|
|
1114
1185
|
extension: z
|
|
1115
1186
|
.string()
|
|
1116
1187
|
.optional()
|
|
1117
|
-
.describe('File extension (
|
|
1188
|
+
.describe('File extension filter (js, py, ts, go, etc). Alternative to language parameter. Examples: "js", "py", "tsx".'),
|
|
1118
1189
|
match: z
|
|
1119
1190
|
.union([z.enum(['file', 'path']), z.array(z.enum(['file', 'path']))])
|
|
1120
1191
|
.optional()
|
|
1121
|
-
.describe('Search scope: "file" for content, "path" for filenames
|
|
1192
|
+
.describe('Search scope: "file" for file content (default), "path" for filenames/paths, or ["file", "path"] for both. Controls where to search for terms.'),
|
|
1122
1193
|
size: z
|
|
1123
1194
|
.string()
|
|
1195
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid size format. Use: ">10", ">=5", "<100", "<=50", "10..100", or exact number "50"')
|
|
1124
1196
|
.optional()
|
|
1125
|
-
.describe('File size in KB. Format: >10, <100,
|
|
1197
|
+
.describe('File size filter in KB. Format: ">10" (larger than), ">=5" (at least), "<100" (smaller than), "<=50" (at most), "10..50" (range), "25" (exact). Examples from docs: ">10", "10..50", "<100"'),
|
|
1126
1198
|
limit: z
|
|
1127
1199
|
.number()
|
|
1128
1200
|
.int()
|
|
@@ -1130,7 +1202,7 @@ function registerGitHubSearchCodeTool(server) {
|
|
|
1130
1202
|
.max(100)
|
|
1131
1203
|
.optional()
|
|
1132
1204
|
.default(30)
|
|
1133
|
-
.describe('
|
|
1205
|
+
.describe('Maximum number of results to return (1-100). Default: 30. Higher values may increase response time.'),
|
|
1134
1206
|
},
|
|
1135
1207
|
annotations: {
|
|
1136
1208
|
title: 'GitHub Code Search - Smart & Efficient',
|
|
@@ -1262,32 +1334,18 @@ function extractSingleRepository$1(items) {
|
|
|
1262
1334
|
}
|
|
1263
1335
|
/**
|
|
1264
1336
|
* Build command line arguments for GitHub CLI with improved parameter handling.
|
|
1265
|
-
*
|
|
1266
|
-
*
|
|
1267
|
-
* This function is refactored to correctly distinguish between search qualifiers
|
|
1268
|
-
* (like `language` and `extension`), which will be passed as separate arguments
|
|
1269
|
-
* to `gh search`, and command-line flags (like `--size` and `--limit`).
|
|
1337
|
+
* Preserves quoted phrases and supports both AND search and exact phrase matching.
|
|
1270
1338
|
*/
|
|
1271
1339
|
function buildGitHubCliArgs(params) {
|
|
1272
1340
|
const args = ['code'];
|
|
1273
|
-
//
|
|
1274
|
-
const
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
else {
|
|
1282
|
-
searchTerms.push(part);
|
|
1283
|
-
}
|
|
1284
|
-
});
|
|
1285
|
-
// Add search terms if any
|
|
1286
|
-
if (searchTerms.length > 0) {
|
|
1287
|
-
args.push(searchTerms.join(' '));
|
|
1288
|
-
}
|
|
1289
|
-
// Add extracted qualifiers
|
|
1290
|
-
qualifiers.forEach(qualifier => {
|
|
1341
|
+
// Parse query to preserve quoted phrases and extract qualifiers
|
|
1342
|
+
const { searchQuery, extractedQualifiers } = parseSearchQuery(params.query);
|
|
1343
|
+
// Add the main search query if present
|
|
1344
|
+
if (searchQuery) {
|
|
1345
|
+
args.push(searchQuery);
|
|
1346
|
+
}
|
|
1347
|
+
// Add extracted qualifiers from the query
|
|
1348
|
+
extractedQualifiers.forEach(qualifier => {
|
|
1291
1349
|
args.push(qualifier);
|
|
1292
1350
|
});
|
|
1293
1351
|
// Add explicit parameters as qualifiers
|
|
@@ -1300,6 +1358,10 @@ function buildGitHubCliArgs(params) {
|
|
|
1300
1358
|
const owners = Array.isArray(params.owner) ? params.owner : [params.owner];
|
|
1301
1359
|
owners.forEach(owner => args.push(`org:${owner}`));
|
|
1302
1360
|
}
|
|
1361
|
+
if (params.repo && !params.query.includes('repo:')) {
|
|
1362
|
+
const repos = Array.isArray(params.repo) ? params.repo : [params.repo];
|
|
1363
|
+
repos.forEach(repo => args.push(`repo:${repo}`));
|
|
1364
|
+
}
|
|
1303
1365
|
if (params.filename && !params.query.includes('filename:')) {
|
|
1304
1366
|
args.push(`filename:${params.filename}`);
|
|
1305
1367
|
}
|
|
@@ -1382,6 +1444,42 @@ function validateSearchParameters(params) {
|
|
|
1382
1444
|
// }
|
|
1383
1445
|
return null; // No validation errors
|
|
1384
1446
|
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Parse search query to preserve quoted phrases and extract qualifiers.
|
|
1449
|
+
* Handles:
|
|
1450
|
+
* - Quoted phrases: "error handling" -> kept as single unit
|
|
1451
|
+
* - Multiple terms: react lifecycle -> both terms for AND search
|
|
1452
|
+
* - Mixed: "error handling" debug -> phrase + term
|
|
1453
|
+
* - Qualifiers: language:javascript -> extracted separately
|
|
1454
|
+
*/
|
|
1455
|
+
function parseSearchQuery(query) {
|
|
1456
|
+
const qualifiers = [];
|
|
1457
|
+
const searchTerms = [];
|
|
1458
|
+
// Regular expression to match quoted strings or individual words/qualifiers
|
|
1459
|
+
const tokenRegex = /"([^"]+)"|([^\s]+)/g;
|
|
1460
|
+
let match;
|
|
1461
|
+
while ((match = tokenRegex.exec(query)) !== null) {
|
|
1462
|
+
const token = match[1] || match[2]; // match[1] is quoted content, match[2] is unquoted
|
|
1463
|
+
// Check if it's a qualifier (contains : but not inside quotes)
|
|
1464
|
+
if (!match[1] && token.includes(':') && /^[a-zA-Z]+:/.test(token)) {
|
|
1465
|
+
qualifiers.push(token);
|
|
1466
|
+
}
|
|
1467
|
+
else {
|
|
1468
|
+
// It's a search term (either quoted or unquoted)
|
|
1469
|
+
if (match[1]) {
|
|
1470
|
+
// Preserve quotes for exact phrase search
|
|
1471
|
+
searchTerms.push(`"${token}"`);
|
|
1472
|
+
}
|
|
1473
|
+
else {
|
|
1474
|
+
searchTerms.push(token);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
return {
|
|
1479
|
+
searchQuery: searchTerms.join(' '),
|
|
1480
|
+
extractedQualifiers: qualifiers,
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1385
1483
|
|
|
1386
1484
|
const GITHUB_SEARCH_COMMITS_TOOL_NAME = 'githubSearchCommits';
|
|
1387
1485
|
const DESCRIPTION$5 = `Search commit history across GitHub repositories. Supports filtering by repository, author, date ranges, and commit attributes. Parameters: query (optional), owner (optional - GitHub username/org, NOT owner/repo), repo (optional - repository name, use with owner for specific repo), author (optional), authorName (optional), authorEmail (optional), committer (optional), committerName (optional), committerEmail (optional), authorDate (optional), committerDate (optional), hash (optional), parent (optional), tree (optional), merge (optional), visibility (optional), limit (optional), sort (optional), order (optional).`;
|
|
@@ -1407,24 +1505,30 @@ function registerGitHubSearchCommitsTool(server) {
|
|
|
1407
1505
|
.string()
|
|
1408
1506
|
.optional()
|
|
1409
1507
|
.describe('GitHub username of commit author'),
|
|
1410
|
-
|
|
1508
|
+
'author-name': z
|
|
1411
1509
|
.string()
|
|
1412
1510
|
.optional()
|
|
1413
1511
|
.describe('Full name of commit author'),
|
|
1414
|
-
|
|
1512
|
+
'author-email': z
|
|
1513
|
+
.string()
|
|
1514
|
+
.optional()
|
|
1515
|
+
.describe('Email of commit author'),
|
|
1415
1516
|
// Committer filters
|
|
1416
1517
|
committer: z
|
|
1417
1518
|
.string()
|
|
1418
1519
|
.optional()
|
|
1419
1520
|
.describe('GitHub username of committer'),
|
|
1420
|
-
|
|
1421
|
-
|
|
1521
|
+
'committer-name': z
|
|
1522
|
+
.string()
|
|
1523
|
+
.optional()
|
|
1524
|
+
.describe('Full name of committer'),
|
|
1525
|
+
'committer-email': z.string().optional().describe('Email of committer'),
|
|
1422
1526
|
// Date filters
|
|
1423
|
-
|
|
1527
|
+
'author-date': z
|
|
1424
1528
|
.string()
|
|
1425
1529
|
.optional()
|
|
1426
1530
|
.describe('When authored. Format: >2020-01-01, <2023-12-31, 2020-01-01..2023-12-31'),
|
|
1427
|
-
|
|
1531
|
+
'committer-date': z
|
|
1428
1532
|
.string()
|
|
1429
1533
|
.optional()
|
|
1430
1534
|
.describe('When committed. Format: >2020-01-01, <2023-12-31, 2020-01-01..2023-12-31'),
|
|
@@ -1603,22 +1707,22 @@ function buildGitHubCommitCliArgs(params) {
|
|
|
1603
1707
|
// Author filters
|
|
1604
1708
|
if (params.author)
|
|
1605
1709
|
args.push(`--author=${params.author}`);
|
|
1606
|
-
if (params
|
|
1607
|
-
args.push(`--author-name=${params
|
|
1608
|
-
if (params
|
|
1609
|
-
args.push(`--author-email=${params
|
|
1710
|
+
if (params['author-name'])
|
|
1711
|
+
args.push(`--author-name=${params['author-name']}`);
|
|
1712
|
+
if (params['author-email'])
|
|
1713
|
+
args.push(`--author-email=${params['author-email']}`);
|
|
1610
1714
|
// Committer filters
|
|
1611
1715
|
if (params.committer)
|
|
1612
1716
|
args.push(`--committer=${params.committer}`);
|
|
1613
|
-
if (params
|
|
1614
|
-
args.push(`--committer-name=${params
|
|
1615
|
-
if (params
|
|
1616
|
-
args.push(`--committer-email=${params
|
|
1717
|
+
if (params['committer-name'])
|
|
1718
|
+
args.push(`--committer-name=${params['committer-name']}`);
|
|
1719
|
+
if (params['committer-email'])
|
|
1720
|
+
args.push(`--committer-email=${params['committer-email']}`);
|
|
1617
1721
|
// Date filters
|
|
1618
|
-
if (params
|
|
1619
|
-
args.push(`--author-date=${params
|
|
1620
|
-
if (params
|
|
1621
|
-
args.push(`--committer-date=${params
|
|
1722
|
+
if (params['author-date'])
|
|
1723
|
+
args.push(`--author-date=${params['author-date']}`);
|
|
1724
|
+
if (params['committer-date'])
|
|
1725
|
+
args.push(`--committer-date=${params['committer-date']}`);
|
|
1622
1726
|
// Hash filters
|
|
1623
1727
|
if (params.hash)
|
|
1624
1728
|
args.push(`--hash=${params.hash}`);
|
|
@@ -1628,7 +1732,7 @@ function buildGitHubCommitCliArgs(params) {
|
|
|
1628
1732
|
args.push(`--tree=${params.tree}`);
|
|
1629
1733
|
// State filters
|
|
1630
1734
|
if (params.merge !== undefined)
|
|
1631
|
-
args.push(`--merge
|
|
1735
|
+
args.push(`--merge`);
|
|
1632
1736
|
// Visibility
|
|
1633
1737
|
if (params.visibility)
|
|
1634
1738
|
args.push(`--visibility=${params.visibility}`);
|
|
@@ -1678,33 +1782,45 @@ function registerSearchGitHubIssuesTool(server) {
|
|
|
1678
1782
|
.describe('GitHub username of issue creator'),
|
|
1679
1783
|
closed: z
|
|
1680
1784
|
.string()
|
|
1785
|
+
.regex(/^(>=?\d{4}-\d{2}-\d{2}|<=?\d{4}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2}\.\.\d{4}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2})$/, 'Invalid date format. Use: ">2020-01-01", ">=2020-01-01", "<2023-12-31", "<=2023-12-31", "2020-01-01..2023-12-31", or exact date "2023-01-01"')
|
|
1681
1786
|
.optional()
|
|
1682
|
-
.describe('When closed. Format: >2020-01-01'),
|
|
1787
|
+
.describe('When closed. Format: ">2020-01-01" (after), ">=2020-01-01" (on or after), "<2023-12-31" (before), "2020-01-01..2023-12-31" (range)'),
|
|
1683
1788
|
commenter: z
|
|
1684
1789
|
.string()
|
|
1685
1790
|
.optional()
|
|
1686
1791
|
.describe('User who commented on issue'),
|
|
1687
1792
|
comments: z
|
|
1688
|
-
.
|
|
1793
|
+
.union([
|
|
1794
|
+
z.number().int().min(0),
|
|
1795
|
+
z
|
|
1796
|
+
.string()
|
|
1797
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">10", ">=5", "<20", "<=15", "5..20", or exact number "10"'),
|
|
1798
|
+
])
|
|
1689
1799
|
.optional()
|
|
1690
|
-
.describe('Comment count. Format: >10, <5, 5..10'),
|
|
1800
|
+
.describe('Comment count filter. Format: ">10" (many comments), ">=5" (at least 5), "<5" (few comments), "5..10" (range), "10" (exact)'),
|
|
1691
1801
|
created: z
|
|
1692
1802
|
.string()
|
|
1803
|
+
.regex(/^(>=?\d{4}-\d{2}-\d{2}|<=?\d{4}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2}\.\.\d{4}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2})$/, 'Invalid date format. Use: ">2020-01-01", ">=2020-01-01", "<2023-12-31", "<=2023-12-31", "2020-01-01..2023-12-31", or exact date "2023-01-01"')
|
|
1693
1804
|
.optional()
|
|
1694
|
-
.describe('When created. Format: >2020-01-01'),
|
|
1695
|
-
|
|
1805
|
+
.describe('When created. Format: ">2020-01-01" (after), ">=2020-01-01" (on or after), "<2023-12-31" (before), "2020-01-01..2023-12-31" (range)'),
|
|
1806
|
+
'include-prs': z
|
|
1696
1807
|
.boolean()
|
|
1697
1808
|
.optional()
|
|
1698
1809
|
.describe('Include pull requests. Default: false'),
|
|
1699
1810
|
interactions: z
|
|
1700
|
-
.
|
|
1811
|
+
.union([
|
|
1812
|
+
z.number().int().min(0),
|
|
1813
|
+
z
|
|
1814
|
+
.string()
|
|
1815
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">100", ">=50", "<200", "<=150", "50..200", or exact number "100"'),
|
|
1816
|
+
])
|
|
1701
1817
|
.optional()
|
|
1702
|
-
.describe('Total interactions (reactions + comments)'),
|
|
1818
|
+
.describe('Total interactions (reactions + comments) filter. Format: ">100" (highly engaged), ">=50" (moderately engaged), "<20" (low engagement), "50..200" (range)'),
|
|
1703
1819
|
involves: z.string().optional().describe('User involved in any way'),
|
|
1704
|
-
|
|
1705
|
-
.string()
|
|
1820
|
+
label: z
|
|
1821
|
+
.union([z.string(), z.array(z.string())])
|
|
1706
1822
|
.optional()
|
|
1707
|
-
.describe('Label names (bug, feature, etc.)'),
|
|
1823
|
+
.describe('Label names (bug, feature, etc.). Can be single string or array.'),
|
|
1708
1824
|
language: z.string().optional().describe('Repository language'),
|
|
1709
1825
|
locked: z.boolean().optional().describe('Conversation locked status'),
|
|
1710
1826
|
match: z
|
|
@@ -1713,27 +1829,39 @@ function registerSearchGitHubIssuesTool(server) {
|
|
|
1713
1829
|
.describe('Search scope. Default: title and body'),
|
|
1714
1830
|
mentions: z.string().optional().describe('Issues mentioning this user'),
|
|
1715
1831
|
milestone: z.string().optional().describe('Milestone name'),
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1832
|
+
'no-assignee': z
|
|
1833
|
+
.boolean()
|
|
1834
|
+
.optional()
|
|
1835
|
+
.describe('Issues without assignee'),
|
|
1836
|
+
'no-label': z.boolean().optional().describe('Issues without labels'),
|
|
1837
|
+
'no-milestone': z
|
|
1719
1838
|
.boolean()
|
|
1720
1839
|
.optional()
|
|
1721
1840
|
.describe('Issues without milestone'),
|
|
1722
|
-
|
|
1841
|
+
'no-project': z.boolean().optional().describe('Issues not in projects'),
|
|
1723
1842
|
project: z.string().optional().describe('Project board number'),
|
|
1724
1843
|
reactions: z
|
|
1725
|
-
.
|
|
1844
|
+
.union([
|
|
1845
|
+
z.number().int().min(0),
|
|
1846
|
+
z
|
|
1847
|
+
.string()
|
|
1848
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">10", ">=5", "<50", "<=25", "5..50", or exact number "10"'),
|
|
1849
|
+
])
|
|
1726
1850
|
.optional()
|
|
1727
|
-
.describe('Reaction count. Format: >10'),
|
|
1851
|
+
.describe('Reaction count filter. Format: ">10" (popular), ">=5" (some reactions), "<50" (moderate), "5..50" (range), "10" (exact)'),
|
|
1728
1852
|
state: z
|
|
1729
1853
|
.enum(['open', 'closed'])
|
|
1730
1854
|
.optional()
|
|
1731
1855
|
.describe('Issue state. Default: all'),
|
|
1732
|
-
|
|
1856
|
+
'team-mentions': z
|
|
1857
|
+
.string()
|
|
1858
|
+
.optional()
|
|
1859
|
+
.describe('Team mentioned in issue (@org/team-name)'),
|
|
1733
1860
|
updated: z
|
|
1734
1861
|
.string()
|
|
1862
|
+
.regex(/^(>=?\d{4}-\d{2}-\d{2}|<=?\d{4}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2}\.\.\d{4}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2})$/, 'Invalid date format. Use: ">2020-01-01", ">=2020-01-01", "<2023-12-31", "<=2023-12-31", "2020-01-01..2023-12-31", or exact date "2023-01-01"')
|
|
1735
1863
|
.optional()
|
|
1736
|
-
.describe('When updated. Format: >2020-01-01'),
|
|
1864
|
+
.describe('When updated. Format: ">2020-01-01" (after), ">=2020-01-01" (on or after), "<2023-12-31" (before), "2020-01-01..2023-12-31" (range)'),
|
|
1737
1865
|
visibility: z
|
|
1738
1866
|
.enum(['public', 'private', 'internal'])
|
|
1739
1867
|
.optional()
|
|
@@ -1889,23 +2017,40 @@ function buildGitHubIssuesAPICommand(params) {
|
|
|
1889
2017
|
queryParts.push(`${key}:${value}`);
|
|
1890
2018
|
});
|
|
1891
2019
|
// Special qualifiers - handle labels carefully
|
|
1892
|
-
if (params.
|
|
1893
|
-
|
|
2020
|
+
if (params.label) {
|
|
2021
|
+
const labels = Array.isArray(params.label) ? params.label : [params.label];
|
|
2022
|
+
labels.forEach(label => queryParts.push(`label:"${label}"`));
|
|
1894
2023
|
}
|
|
1895
2024
|
if (params.milestone)
|
|
1896
2025
|
queryParts.push(`milestone:"${params.milestone}"`);
|
|
1897
|
-
if (params
|
|
2026
|
+
if (params['no-assignee'])
|
|
1898
2027
|
queryParts.push('no:assignee');
|
|
1899
|
-
if (params
|
|
2028
|
+
if (params['no-label'])
|
|
1900
2029
|
queryParts.push('no:label');
|
|
1901
|
-
if (params
|
|
2030
|
+
if (params['no-milestone'])
|
|
1902
2031
|
queryParts.push('no:milestone');
|
|
2032
|
+
if (params['no-project'])
|
|
2033
|
+
queryParts.push('no:project');
|
|
1903
2034
|
if (params.archived !== undefined)
|
|
1904
2035
|
queryParts.push(`archived:${params.archived}`);
|
|
1905
2036
|
if (params.locked)
|
|
1906
2037
|
queryParts.push('is:locked');
|
|
1907
2038
|
if (params.visibility)
|
|
1908
2039
|
queryParts.push(`is:${params.visibility}`);
|
|
2040
|
+
if (params['include-prs'])
|
|
2041
|
+
queryParts.push('is:pr');
|
|
2042
|
+
if (params['team-mentions'])
|
|
2043
|
+
queryParts.push(`team:${params['team-mentions']}`);
|
|
2044
|
+
if (params.project)
|
|
2045
|
+
queryParts.push(`project:${params.project}`);
|
|
2046
|
+
if (params.app)
|
|
2047
|
+
queryParts.push(`app:${params.app}`);
|
|
2048
|
+
if (params.comments)
|
|
2049
|
+
queryParts.push(`comments:${params.comments}`);
|
|
2050
|
+
if (params.interactions)
|
|
2051
|
+
queryParts.push(`interactions:${params.interactions}`);
|
|
2052
|
+
if (params.reactions)
|
|
2053
|
+
queryParts.push(`reactions:${params.reactions}`);
|
|
1909
2054
|
// Extract qualifiers from original query and add them if not already set by params
|
|
1910
2055
|
if (baseQuery.includes('is:') && !params.state) {
|
|
1911
2056
|
const isMatch = baseQuery.match(/\bis:(open|closed)\b/i);
|
|
@@ -1913,7 +2058,7 @@ function buildGitHubIssuesAPICommand(params) {
|
|
|
1913
2058
|
queryParts.push(`state:${isMatch[1].toLowerCase()}`);
|
|
1914
2059
|
}
|
|
1915
2060
|
}
|
|
1916
|
-
if (baseQuery.includes('label:') && !params.
|
|
2061
|
+
if (baseQuery.includes('label:') && !params.label) {
|
|
1917
2062
|
const labelMatch = baseQuery.match(/\blabel:("[^"]*"|[^\s]+)/i);
|
|
1918
2063
|
if (labelMatch) {
|
|
1919
2064
|
const labelValue = labelMatch[1].replace(/"/g, '');
|
|
@@ -1960,8 +2105,11 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
1960
2105
|
mentions: z.string().optional().describe('PRs mentioning this user'),
|
|
1961
2106
|
commenter: z.string().optional().describe('User who commented on PR'),
|
|
1962
2107
|
involves: z.string().optional().describe('User involved in any way'),
|
|
1963
|
-
|
|
1964
|
-
|
|
2108
|
+
'reviewed-by': z
|
|
2109
|
+
.string()
|
|
2110
|
+
.optional()
|
|
2111
|
+
.describe('User who reviewed the PR'),
|
|
2112
|
+
'review-requested': z
|
|
1965
2113
|
.string()
|
|
1966
2114
|
.optional()
|
|
1967
2115
|
.describe('User/team requested for review'),
|
|
@@ -1983,7 +2131,7 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
1983
2131
|
.string()
|
|
1984
2132
|
.optional()
|
|
1985
2133
|
.describe('When updated. Format: >2020-01-01'),
|
|
1986
|
-
|
|
2134
|
+
'merged-at': z
|
|
1987
2135
|
.string()
|
|
1988
2136
|
.optional()
|
|
1989
2137
|
.describe('When merged. Format: >2020-01-01'),
|
|
@@ -2004,6 +2152,49 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
2004
2152
|
.enum(['none', 'required', 'approved', 'changes_requested'])
|
|
2005
2153
|
.optional()
|
|
2006
2154
|
.describe('Review status filter'),
|
|
2155
|
+
app: z.string().optional().describe('GitHub App that created the PR'),
|
|
2156
|
+
archived: z
|
|
2157
|
+
.boolean()
|
|
2158
|
+
.optional()
|
|
2159
|
+
.describe('Include archived repositories'),
|
|
2160
|
+
comments: z
|
|
2161
|
+
.number()
|
|
2162
|
+
.optional()
|
|
2163
|
+
.describe('Comment count filter. Format: >10, <5, 5..10'),
|
|
2164
|
+
interactions: z
|
|
2165
|
+
.number()
|
|
2166
|
+
.optional()
|
|
2167
|
+
.describe('Total interactions (reactions + comments)'),
|
|
2168
|
+
'team-mentions': z
|
|
2169
|
+
.string()
|
|
2170
|
+
.optional()
|
|
2171
|
+
.describe('Team mentioned in PR (@org/team-name)'),
|
|
2172
|
+
reactions: z
|
|
2173
|
+
.number()
|
|
2174
|
+
.optional()
|
|
2175
|
+
.describe('Reaction count filter. Format: >10'),
|
|
2176
|
+
locked: z.boolean().optional().describe('Conversation locked status'),
|
|
2177
|
+
'no-assignee': z.boolean().optional().describe('PRs without assignee'),
|
|
2178
|
+
'no-label': z.boolean().optional().describe('PRs without labels'),
|
|
2179
|
+
'no-milestone': z
|
|
2180
|
+
.boolean()
|
|
2181
|
+
.optional()
|
|
2182
|
+
.describe('PRs without milestone'),
|
|
2183
|
+
'no-project': z.boolean().optional().describe('PRs not in projects'),
|
|
2184
|
+
label: z
|
|
2185
|
+
.union([z.string(), z.array(z.string())])
|
|
2186
|
+
.optional()
|
|
2187
|
+
.describe('Label names. Can be single string or array.'),
|
|
2188
|
+
milestone: z.string().optional().describe('Milestone title'),
|
|
2189
|
+
project: z.string().optional().describe('Project board owner/number'),
|
|
2190
|
+
visibility: z
|
|
2191
|
+
.enum(['public', 'private', 'internal'])
|
|
2192
|
+
.optional()
|
|
2193
|
+
.describe('Repository visibility'),
|
|
2194
|
+
match: z
|
|
2195
|
+
.enum(['title', 'body', 'comments'])
|
|
2196
|
+
.optional()
|
|
2197
|
+
.describe('Search scope. Default: title and body'),
|
|
2007
2198
|
limit: z
|
|
2008
2199
|
.number()
|
|
2009
2200
|
.int()
|
|
@@ -2138,16 +2329,16 @@ function buildGitHubPullRequestsAPICommand(params) {
|
|
|
2138
2329
|
queryParts.push(`${key}:${value}`);
|
|
2139
2330
|
});
|
|
2140
2331
|
// Special qualifiers
|
|
2141
|
-
if (params
|
|
2142
|
-
queryParts.push(`reviewed-by:${params
|
|
2143
|
-
if (params
|
|
2144
|
-
queryParts.push(`review-requested:${params
|
|
2332
|
+
if (params['reviewed-by'])
|
|
2333
|
+
queryParts.push(`reviewed-by:${params['reviewed-by']}`);
|
|
2334
|
+
if (params['review-requested'])
|
|
2335
|
+
queryParts.push(`review-requested:${params['review-requested']}`);
|
|
2145
2336
|
if (params.head)
|
|
2146
2337
|
queryParts.push(`head:${params.head}`);
|
|
2147
2338
|
if (params.base)
|
|
2148
2339
|
queryParts.push(`base:${params.base}`);
|
|
2149
|
-
if (params
|
|
2150
|
-
queryParts.push(`merged:${params
|
|
2340
|
+
if (params['merged-at'])
|
|
2341
|
+
queryParts.push(`merged:${params['merged-at']}`);
|
|
2151
2342
|
if (params.draft !== undefined)
|
|
2152
2343
|
queryParts.push(`draft:${params.draft}`);
|
|
2153
2344
|
if (params.checks)
|
|
@@ -2156,6 +2347,39 @@ function buildGitHubPullRequestsAPICommand(params) {
|
|
|
2156
2347
|
queryParts.push(`is:${params.merged ? 'merged' : 'unmerged'}`);
|
|
2157
2348
|
if (params.review)
|
|
2158
2349
|
queryParts.push(`review:${params.review}`);
|
|
2350
|
+
// Additional parameters
|
|
2351
|
+
if (params.app)
|
|
2352
|
+
queryParts.push(`app:${params.app}`);
|
|
2353
|
+
if (params.archived !== undefined)
|
|
2354
|
+
queryParts.push(`archived:${params.archived}`);
|
|
2355
|
+
if (params.comments)
|
|
2356
|
+
queryParts.push(`comments:${params.comments}`);
|
|
2357
|
+
if (params.interactions)
|
|
2358
|
+
queryParts.push(`interactions:${params.interactions}`);
|
|
2359
|
+
if (params.reactions)
|
|
2360
|
+
queryParts.push(`reactions:${params.reactions}`);
|
|
2361
|
+
if (params.locked)
|
|
2362
|
+
queryParts.push('is:locked');
|
|
2363
|
+
if (params.visibility)
|
|
2364
|
+
queryParts.push(`is:${params.visibility}`);
|
|
2365
|
+
if (params['team-mentions'])
|
|
2366
|
+
queryParts.push(`team:${params['team-mentions']}`);
|
|
2367
|
+
if (params['no-assignee'])
|
|
2368
|
+
queryParts.push('no:assignee');
|
|
2369
|
+
if (params['no-label'])
|
|
2370
|
+
queryParts.push('no:label');
|
|
2371
|
+
if (params['no-milestone'])
|
|
2372
|
+
queryParts.push('no:milestone');
|
|
2373
|
+
if (params['no-project'])
|
|
2374
|
+
queryParts.push('no:project');
|
|
2375
|
+
if (params.label) {
|
|
2376
|
+
const labels = Array.isArray(params.label) ? params.label : [params.label];
|
|
2377
|
+
labels.forEach(label => queryParts.push(`label:"${label}"`));
|
|
2378
|
+
}
|
|
2379
|
+
if (params.milestone)
|
|
2380
|
+
queryParts.push(`milestone:"${params.milestone}"`);
|
|
2381
|
+
if (params.project)
|
|
2382
|
+
queryParts.push(`project:${params.project}`);
|
|
2159
2383
|
// Add type qualifier to search only pull requests
|
|
2160
2384
|
queryParts.push('type:pr');
|
|
2161
2385
|
const query = queryParts.filter(Boolean).join(' ');
|
|
@@ -2189,7 +2413,22 @@ function buildGitHubPullRequestsAPICommand(params) {
|
|
|
2189
2413
|
* TIP: Use limit parameter instead of adding more filters
|
|
2190
2414
|
*/
|
|
2191
2415
|
const GITHUB_SEARCH_REPOSITORIES_TOOL_NAME = 'githubSearchRepositories';
|
|
2192
|
-
const DESCRIPTION$2 = `
|
|
2416
|
+
const DESCRIPTION$2 = `Search GitHub repositories using GitHub's repository search API with comprehensive filtering.
|
|
2417
|
+
|
|
2418
|
+
Search Logic (AND operation for multiple terms):
|
|
2419
|
+
- Multiple words: "react typescript" → repositories containing BOTH "react" AND "typescript"
|
|
2420
|
+
- Exact phrases: "web framework" → repositories with exact phrase "web framework"
|
|
2421
|
+
- Mixed: "machine learning" python → exact phrase "machine learning" AND word "python"
|
|
2422
|
+
- Filter combinations: language:javascript stars:>1000 → JavaScript repos with 1000+ stars
|
|
2423
|
+
|
|
2424
|
+
Supported Filters: language, stars (ranges), topics, owner/org, license, dates (created/updated),
|
|
2425
|
+
size, forks, community metrics (good-first-issues, help-wanted), archived status, visibility.
|
|
2426
|
+
|
|
2427
|
+
Examples:
|
|
2428
|
+
- Popular projects: stars:">1000" language:typescript
|
|
2429
|
+
- Beginner-friendly: good-first-issues:">5" stars:"100..5000"
|
|
2430
|
+
- Recent quality: created:">2023-01-01" stars:">500"
|
|
2431
|
+
- Organization repos: owner:microsoft language:python`;
|
|
2193
2432
|
/**
|
|
2194
2433
|
* Extract owner/repo information from various query formats
|
|
2195
2434
|
*/
|
|
@@ -2227,65 +2466,83 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2227
2466
|
query: z
|
|
2228
2467
|
.string()
|
|
2229
2468
|
.optional()
|
|
2230
|
-
.describe('Search query.
|
|
2469
|
+
.describe('Search query with AND logic between terms. Multiple words require ALL to be present. Use quotes for exact phrases. Examples: react typescript (both words), "web framework" (exact phrase), "machine learning" python (phrase + word).'),
|
|
2231
2470
|
// CORE FILTERS (GitHub CLI flags)
|
|
2232
2471
|
owner: z
|
|
2233
2472
|
.union([z.string(), z.array(z.string())])
|
|
2234
2473
|
.optional()
|
|
2235
|
-
.describe('Repository owner or organization name
|
|
2474
|
+
.describe('Repository owner or organization name(s). Format: "microsoft" or ["facebook", "google"]. Do NOT use owner/repo format. Dramatically narrows search scope to specific organizations.'),
|
|
2236
2475
|
language: z
|
|
2237
2476
|
.string()
|
|
2238
2477
|
.optional()
|
|
2239
|
-
.describe('Programming language filter.
|
|
2478
|
+
.describe('Programming language filter (javascript, python, typescript, go, etc). Filters repositories by primary language. Essential for language-specific searches.'),
|
|
2240
2479
|
stars: z
|
|
2241
2480
|
.union([
|
|
2242
2481
|
z.number().int().min(0),
|
|
2243
|
-
z
|
|
2482
|
+
z
|
|
2483
|
+
.string()
|
|
2484
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">1000", ">=500", "<100", "<=50", "10..100", or exact number "50"'),
|
|
2244
2485
|
])
|
|
2245
2486
|
.optional()
|
|
2246
|
-
.describe('
|
|
2487
|
+
.describe('Star count filter. Format: ">1000" (more than), ">=500" (more than or equal), "<100" (less than), "<=50" (less than or equal), "100..1000" (range), "500" (exact). Examples from docs: ">1000", "100..5000", "<100"'),
|
|
2247
2488
|
topic: z
|
|
2248
2489
|
.union([z.string(), z.array(z.string())])
|
|
2249
2490
|
.optional()
|
|
2250
|
-
.describe('
|
|
2251
|
-
forks: z
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2491
|
+
.describe('Repository topics filter. Examples: "machine-learning", ["react", "typescript"]. Topics use kebab-case format (machine-learning, web-development).'),
|
|
2492
|
+
forks: z
|
|
2493
|
+
.union([
|
|
2494
|
+
z.number().int().min(0),
|
|
2495
|
+
z
|
|
2496
|
+
.string()
|
|
2497
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">100", ">=50", "<10", "<=5", "10..100", or exact number "5"'),
|
|
2498
|
+
])
|
|
2255
2499
|
.optional()
|
|
2256
|
-
.describe('
|
|
2500
|
+
.describe('Fork count filter. Format: ">100" (more than), ">=50" (more than or equal), "<10" (less than), "<=5" (less than or equal), "10..100" (range), "5" (exact). Examples from docs: ">100", "10..100", "<10"'),
|
|
2501
|
+
// Match CLI parameter name exactly
|
|
2502
|
+
'number-topics': z
|
|
2503
|
+
.union([
|
|
2504
|
+
z.number().int().min(0),
|
|
2505
|
+
z
|
|
2506
|
+
.string()
|
|
2507
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">5", ">=3", "<10", "<=2", "3..10", or exact number "5"'),
|
|
2508
|
+
])
|
|
2509
|
+
.optional()
|
|
2510
|
+
.describe('Number of topics filter. Format: ">5" (many topics), ">=3" (at least 3), "<10" (few topics), "1..3" (range), "5" (exact). Well-documented projects typically have 3-10 topics.'),
|
|
2257
2511
|
// QUALITY & STATE FILTERS
|
|
2258
2512
|
license: z
|
|
2259
2513
|
.union([z.string(), z.array(z.string())])
|
|
2260
2514
|
.optional()
|
|
2261
|
-
.describe('License filter.
|
|
2515
|
+
.describe('License filter. Examples: "mit", "apache-2.0", ["mit", "apache-2.0"]. Common licenses: mit, apache-2.0, gpl-3.0, bsd-3-clause.'),
|
|
2262
2516
|
archived: z
|
|
2263
2517
|
.boolean()
|
|
2264
2518
|
.optional()
|
|
2265
|
-
.describe('
|
|
2266
|
-
|
|
2519
|
+
.describe('Archive status filter. false (active repos only), true (archived repos only). Use false to find actively maintained projects.'),
|
|
2520
|
+
'include-forks': z
|
|
2267
2521
|
.enum(['false', 'true', 'only'])
|
|
2268
2522
|
.optional()
|
|
2269
|
-
.describe('
|
|
2523
|
+
.describe('Fork inclusion. "false" (exclude forks), "true" (include forks), "only" (forks only). Use "false" for original projects.'),
|
|
2270
2524
|
visibility: z
|
|
2271
2525
|
.enum(['public', 'private', 'internal'])
|
|
2272
2526
|
.optional()
|
|
2273
|
-
.describe('Repository visibility
|
|
2527
|
+
.describe('Repository visibility. "public" (open source), "private" (private repos you have access to), "internal" (organization internal).'),
|
|
2274
2528
|
// DATE & SIZE FILTERS
|
|
2275
2529
|
created: z
|
|
2276
2530
|
.string()
|
|
2531
|
+
.regex(/^(>=?\d{4}-\d{2}-\d{2}|<=?\d{4}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2}\.\.\d{4}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2})$/, 'Invalid date format. Use: ">2020-01-01", ">=2020-01-01", "<2023-12-31", "<=2023-12-31", "2020-01-01..2023-12-31", or exact date "2023-01-01"')
|
|
2277
2532
|
.optional()
|
|
2278
|
-
.describe('
|
|
2533
|
+
.describe('Repository creation date filter. Format: ">2020-01-01" (after), ">=2020-01-01" (on or after), "<2023-12-31" (before), "<=2023-12-31" (on or before), "2020-01-01..2023-12-31" (range), "2023-01-01" (exact). Examples from docs: ">2020-01-01", "2023-01-01..2023-12-31"'),
|
|
2279
2534
|
updated: z
|
|
2280
2535
|
.string()
|
|
2536
|
+
.regex(/^(>=?\d{4}-\d{2}-\d{2}|<=?\d{4}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2}\.\.\d{4}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2})$/, 'Invalid date format. Use: ">2024-01-01", ">=2024-01-01", "<2022-01-01", "<=2022-01-01", "2023-01-01..2024-12-31", or exact date "2024-01-01"')
|
|
2281
2537
|
.optional()
|
|
2282
|
-
.describe('
|
|
2538
|
+
.describe('Last updated date filter. Format: ">2024-01-01" (recently updated), ">=2024-01-01" (on or after), "<2022-01-01" (not recently updated), "2023-01-01..2024-12-31" (range). Essential for finding actively maintained projects. Examples from docs: ">2024-01-01", "2022-01-01..2023-12-31"'),
|
|
2283
2539
|
size: z
|
|
2284
2540
|
.string()
|
|
2541
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid size format. Use: ">1000", ">=500", "<100", "<=50", "100..1000", or exact number "500"')
|
|
2285
2542
|
.optional()
|
|
2286
|
-
.describe('Repository size filter in KB. Format: ">1000", "<500".'),
|
|
2543
|
+
.describe('Repository size filter in KB. Format: ">1000" (large projects), ">=500" (medium-large), "<100" (small projects), "<=50" (tiny), "100..1000" (medium range), "500" (exact). Examples from docs: ">1000", "100..1000", "<100"'),
|
|
2287
2544
|
// COMMUNITY FILTERS - Match CLI parameter names exactly
|
|
2288
|
-
|
|
2545
|
+
'good-first-issues': z
|
|
2289
2546
|
.union([
|
|
2290
2547
|
z.number().int().min(0),
|
|
2291
2548
|
z
|
|
@@ -2293,8 +2550,8 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2293
2550
|
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: number, ">5", ">=10", "<20", "<=15", or "5..20"'),
|
|
2294
2551
|
])
|
|
2295
2552
|
.optional()
|
|
2296
|
-
.describe('Good first issues count.
|
|
2297
|
-
|
|
2553
|
+
.describe('Good first issues count. Format: ">5" (many beginner issues), "1..10" (some beginner issues). Perfect for finding beginner-friendly open source projects.'),
|
|
2554
|
+
'help-wanted-issues': z
|
|
2298
2555
|
.union([
|
|
2299
2556
|
z.number().int().min(0),
|
|
2300
2557
|
z
|
|
@@ -2302,13 +2559,21 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2302
2559
|
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: number, ">5", ">=10", "<20", "<=15", or "5..20"'),
|
|
2303
2560
|
])
|
|
2304
2561
|
.optional()
|
|
2305
|
-
.describe('Help wanted issues count.
|
|
2306
|
-
followers: z
|
|
2562
|
+
.describe('Help wanted issues count. Format: ">10" (many help wanted), "1..5" (some help wanted). Great for finding projects actively seeking contributors.'),
|
|
2563
|
+
followers: z
|
|
2564
|
+
.union([
|
|
2565
|
+
z.number().int().min(0),
|
|
2566
|
+
z
|
|
2567
|
+
.string()
|
|
2568
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">1000", ">=500", "<100", "<=50", "100..1000", or exact number "500"'),
|
|
2569
|
+
])
|
|
2570
|
+
.optional()
|
|
2571
|
+
.describe('Repository owner followers count. Format: ">1000" (popular developers), ">=500" (established developers), "<100" (smaller developers), "100..1000" (range). Indicates developer/org reputation.'),
|
|
2307
2572
|
// SEARCH SCOPE
|
|
2308
2573
|
match: z
|
|
2309
2574
|
.enum(['name', 'description', 'readme'])
|
|
2310
2575
|
.optional()
|
|
2311
|
-
.describe('Search scope
|
|
2576
|
+
.describe('Search scope. "name" (repository names only), "description" (descriptions only), "readme" (README content). Default searches all fields.'),
|
|
2312
2577
|
// SORTING & LIMITS - Match CLI defaults exactly
|
|
2313
2578
|
sort: z
|
|
2314
2579
|
.enum([
|
|
@@ -2320,12 +2585,12 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2320
2585
|
])
|
|
2321
2586
|
.optional()
|
|
2322
2587
|
.default('best-match')
|
|
2323
|
-
.describe('Sort criteria
|
|
2588
|
+
.describe('Sort criteria. "stars" (popularity), "updated" (recent activity), "forks" (community engagement), "help-wanted-issues" (contribution opportunities), "best-match" (relevance).'),
|
|
2324
2589
|
order: z
|
|
2325
2590
|
.enum(['asc', 'desc'])
|
|
2326
2591
|
.optional()
|
|
2327
2592
|
.default('desc')
|
|
2328
|
-
.describe('Sort order direction.'),
|
|
2593
|
+
.describe('Sort order direction. "desc" (descending, highest first), "asc" (ascending, lowest first). Default is descending for most useful results.'),
|
|
2329
2594
|
limit: z
|
|
2330
2595
|
.number()
|
|
2331
2596
|
.int()
|
|
@@ -2333,7 +2598,7 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2333
2598
|
.max(100)
|
|
2334
2599
|
.optional()
|
|
2335
2600
|
.default(30)
|
|
2336
|
-
.describe('Maximum
|
|
2601
|
+
.describe('Maximum number of repositories to return (1-100). Default: 30. Higher values may increase response time.'),
|
|
2337
2602
|
},
|
|
2338
2603
|
annotations: {
|
|
2339
2604
|
title: 'GitHub Repository Search',
|
|
@@ -2534,13 +2799,13 @@ function buildGitHubReposSearchCommand(params) {
|
|
|
2534
2799
|
// CORE FILTERS
|
|
2535
2800
|
addArg('owner', 'owner', !hasEmbeddedQualifiers);
|
|
2536
2801
|
addArg('language', 'language', !hasEmbeddedQualifiers);
|
|
2537
|
-
addArg('forks', 'forks', !hasEmbeddedQualifiers);
|
|
2802
|
+
addArg('forks', 'forks', !hasEmbeddedQualifiers, value => typeof value === 'number' ? value.toString() : value.trim());
|
|
2538
2803
|
addArg('topic', 'topic', !hasEmbeddedQualifiers);
|
|
2539
|
-
addArg('
|
|
2804
|
+
addArg('number-topics', 'number-topics', true, value => typeof value === 'number' ? value.toString() : value.trim());
|
|
2540
2805
|
addArg('stars', 'stars', !hasEmbeddedQualifiers, value => typeof value === 'number' ? value.toString() : value.trim());
|
|
2541
2806
|
// QUALITY & STATE FILTERS
|
|
2542
2807
|
addArg('archived', 'archived');
|
|
2543
|
-
addArg('
|
|
2808
|
+
addArg('include-forks', 'include-forks');
|
|
2544
2809
|
addArg('visibility', 'visibility');
|
|
2545
2810
|
addArg('license', 'license');
|
|
2546
2811
|
// DATE & SIZE FILTERS
|
|
@@ -2548,9 +2813,9 @@ function buildGitHubReposSearchCommand(params) {
|
|
|
2548
2813
|
addArg('updated', 'updated');
|
|
2549
2814
|
addArg('size', 'size');
|
|
2550
2815
|
// COMMUNITY FILTERS
|
|
2551
|
-
addArg('
|
|
2552
|
-
addArg('
|
|
2553
|
-
addArg('followers', 'followers');
|
|
2816
|
+
addArg('good-first-issues', 'good-first-issues', true, value => typeof value === 'number' ? value.toString() : value);
|
|
2817
|
+
addArg('help-wanted-issues', 'help-wanted-issues', true, value => typeof value === 'number' ? value.toString() : value);
|
|
2818
|
+
addArg('followers', 'followers', true, value => typeof value === 'number' ? value.toString() : value.trim());
|
|
2554
2819
|
// SEARCH SCOPE
|
|
2555
2820
|
addArg('match', 'match');
|
|
2556
2821
|
// SORTING AND LIMITS
|
|
@@ -2667,7 +2932,6 @@ function parseNpmSearchOutput(output) {
|
|
|
2667
2932
|
return packages.map(normalizePackage);
|
|
2668
2933
|
}
|
|
2669
2934
|
catch (error) {
|
|
2670
|
-
logger.warn('Failed to parse NPM search results:', error);
|
|
2671
2935
|
return [];
|
|
2672
2936
|
}
|
|
2673
2937
|
}
|
|
@@ -2996,39 +3260,31 @@ function registerAllTools(server) {
|
|
|
2996
3260
|
},
|
|
2997
3261
|
{ name: NPM_VIEW_PACKAGE_TOOL_NAME, fn: registerNpmViewPackageTool },
|
|
2998
3262
|
];
|
|
2999
|
-
logger.info(`Registering ${toolRegistrations.length} tools...`);
|
|
3000
3263
|
let successCount = 0;
|
|
3001
3264
|
for (const tool of toolRegistrations) {
|
|
3002
3265
|
try {
|
|
3003
|
-
logger.debug(`Registering tool: ${tool.name}`);
|
|
3004
3266
|
tool.fn(server);
|
|
3005
3267
|
successCount++;
|
|
3006
|
-
logger.info(`✓ Successfully registered: ${tool.name}`);
|
|
3007
3268
|
}
|
|
3008
3269
|
catch (error) {
|
|
3009
|
-
logger.error(`✗ Failed to register ${tool.name}:`, error);
|
|
3010
3270
|
// Continue with other tools instead of failing completely
|
|
3011
3271
|
}
|
|
3012
3272
|
}
|
|
3013
3273
|
if (successCount === 0) {
|
|
3014
3274
|
throw new Error('No tools were successfully registered');
|
|
3015
3275
|
}
|
|
3016
|
-
logger.info(`All tools registration completed - ${successCount}/${toolRegistrations.length} successful`);
|
|
3017
3276
|
}
|
|
3018
3277
|
async function startServer() {
|
|
3019
3278
|
try {
|
|
3020
|
-
logger.info('Creating MCP server...');
|
|
3021
3279
|
const server = new McpServer(SERVER_CONFIG);
|
|
3022
3280
|
registerAllTools(server);
|
|
3023
3281
|
const transport = new StdioServerTransport();
|
|
3024
3282
|
await server.connect(transport);
|
|
3025
|
-
logger.info('=== Server Connected Successfully ===');
|
|
3026
3283
|
// Ensure all buffered output is sent
|
|
3027
3284
|
process.stdout.uncork();
|
|
3028
3285
|
process.stderr.uncork();
|
|
3029
|
-
const gracefulShutdown = async (
|
|
3286
|
+
const gracefulShutdown = async () => {
|
|
3030
3287
|
try {
|
|
3031
|
-
logger.info(`Received ${signal}, shutting down gracefully...`);
|
|
3032
3288
|
clearAllCache();
|
|
3033
3289
|
// Give server time to close properly
|
|
3034
3290
|
await Promise.race([
|
|
@@ -3038,35 +3294,30 @@ async function startServer() {
|
|
|
3038
3294
|
process.exit(0);
|
|
3039
3295
|
}
|
|
3040
3296
|
catch (error) {
|
|
3041
|
-
logger.error('Error during shutdown:', error);
|
|
3042
3297
|
process.exit(1);
|
|
3043
3298
|
}
|
|
3044
3299
|
};
|
|
3045
3300
|
// Handle process signals
|
|
3046
|
-
process.on('SIGINT',
|
|
3047
|
-
process.on('SIGTERM',
|
|
3301
|
+
process.on('SIGINT', gracefulShutdown);
|
|
3302
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
3048
3303
|
// Handle stdin close (important for MCP)
|
|
3049
3304
|
process.stdin.on('close', async () => {
|
|
3050
|
-
await gracefulShutdown(
|
|
3305
|
+
await gracefulShutdown();
|
|
3051
3306
|
});
|
|
3052
3307
|
// Handle uncaught errors
|
|
3053
|
-
process.on('uncaughtException',
|
|
3054
|
-
|
|
3055
|
-
gracefulShutdown('UNCAUGHT_EXCEPTION');
|
|
3308
|
+
process.on('uncaughtException', () => {
|
|
3309
|
+
gracefulShutdown();
|
|
3056
3310
|
});
|
|
3057
|
-
process.on('unhandledRejection', (
|
|
3058
|
-
|
|
3059
|
-
gracefulShutdown('UNHANDLED_REJECTION');
|
|
3311
|
+
process.on('unhandledRejection', () => {
|
|
3312
|
+
gracefulShutdown();
|
|
3060
3313
|
});
|
|
3061
3314
|
// Keep process alive
|
|
3062
3315
|
process.stdin.resume();
|
|
3063
3316
|
}
|
|
3064
3317
|
catch (error) {
|
|
3065
|
-
logger.error('Error details:', error);
|
|
3066
3318
|
process.exit(1);
|
|
3067
3319
|
}
|
|
3068
3320
|
}
|
|
3069
|
-
startServer().catch(
|
|
3070
|
-
logger.error('Error:', error);
|
|
3321
|
+
startServer().catch(() => {
|
|
3071
3322
|
process.exit(1);
|
|
3072
3323
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "octocode-mcp",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.7",
|
|
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://github.com/bgauryy/octocode-mcp#readme",
|