octocode-mcp 2.3.6 → 2.3.8
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 +789 -522
- 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)) {
|
|
@@ -219,10 +238,15 @@ async function executeGitHubCommand(command, args = [], options = {}) {
|
|
|
219
238
|
index > 1 &&
|
|
220
239
|
(arg.includes(':') || arg.startsWith('(')) &&
|
|
221
240
|
!arg.startsWith('--');
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
// and complex expressions like "(user:microsoft OR org:microsoft)"
|
|
241
|
+
// GitHub search qualifiers need special handling
|
|
242
|
+
// Most qualifiers can be passed as-is, but those with shell metacharacters need escaping
|
|
225
243
|
if (isGitHubQualifier) {
|
|
244
|
+
// Check if the qualifier contains shell metacharacters that need escaping
|
|
245
|
+
if (/[<>&|;`$\\]/.test(arg)) {
|
|
246
|
+
// Escape qualifiers that contain shell metacharacters like size:<1000, size:>500
|
|
247
|
+
return escapeShellArg(arg, shellConfig.type, false);
|
|
248
|
+
}
|
|
249
|
+
// Safe qualifiers like "language:typescript", "user:microsoft" can be passed as-is
|
|
226
250
|
return arg;
|
|
227
251
|
}
|
|
228
252
|
return escapeShellArg(arg, shellConfig.type, isMainQueryArgument);
|
|
@@ -284,6 +308,14 @@ async function executeCommand(fullCommand, type, options = {}, shellConfig) {
|
|
|
284
308
|
stderr.trim() !== '';
|
|
285
309
|
if (shouldTreatAsError) {
|
|
286
310
|
const errorType = type === 'npm' ? 'NPM command error' : 'GitHub CLI command error';
|
|
311
|
+
// Enhanced error messaging for common GitHub issues
|
|
312
|
+
if (type === 'github' && stderr.includes('404')) {
|
|
313
|
+
const isRepoNotFound = stderr.includes('Not Found');
|
|
314
|
+
const enhancedMessage = isRepoNotFound
|
|
315
|
+
? `${stderr}\n\nThis is often due to incorrect repository name. Use github_search_code to find the correct repository.`
|
|
316
|
+
: stderr;
|
|
317
|
+
return createErrorResult(errorType, new Error(enhancedMessage));
|
|
318
|
+
}
|
|
287
319
|
return createErrorResult(errorType, new Error(stderr));
|
|
288
320
|
}
|
|
289
321
|
// Try to parse stdout as JSON, fallback to string if not possible
|
|
@@ -313,61 +345,6 @@ async function executeCommand(fullCommand, type, options = {}, shellConfig) {
|
|
|
313
345
|
}
|
|
314
346
|
}
|
|
315
347
|
|
|
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
348
|
function createResult(options) {
|
|
372
349
|
const { data, error } = options;
|
|
373
350
|
if (error) {
|
|
@@ -387,7 +364,6 @@ function createResult(options) {
|
|
|
387
364
|
};
|
|
388
365
|
}
|
|
389
366
|
catch (jsonError) {
|
|
390
|
-
logger.error('JSON serialization failed:', jsonError);
|
|
391
367
|
return {
|
|
392
368
|
content: [
|
|
393
369
|
{
|
|
@@ -582,7 +558,7 @@ function createSearchFailedError(type = 'code') {
|
|
|
582
558
|
}
|
|
583
559
|
|
|
584
560
|
const API_STATUS_CHECK_TOOL_NAME = 'apiStatusCheck';
|
|
585
|
-
const DESCRIPTION$9 = `Check GitHub and NPM authentication status.
|
|
561
|
+
const DESCRIPTION$9 = `Check GitHub and NPM authentication status and available organizations. Verifies API connections and returns user organizations for accessing private repositories. Essential for troubleshooting access issues. No parameters required.`;
|
|
586
562
|
// Helper function to parse execution results with proper typing
|
|
587
563
|
function parseExecResult(result) {
|
|
588
564
|
if (!result.isError && result.content?.[0]?.text) {
|
|
@@ -714,235 +690,34 @@ function registerApiStatusCheckTool(server) {
|
|
|
714
690
|
});
|
|
715
691
|
}
|
|
716
692
|
|
|
717
|
-
const GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME = 'githubViewRepoStructure';
|
|
718
|
-
const DESCRIPTION$8 = `Explore repository structure and navigate directories. Auto-detects branches and provides file/folder listings with size information. Parameters: owner (required - GitHub username/org), repo (required - repository name), branch (required), path (optional).`;
|
|
719
|
-
function registerViewRepositoryStructureTool(server) {
|
|
720
|
-
server.registerTool(GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME, {
|
|
721
|
-
description: DESCRIPTION$8,
|
|
722
|
-
inputSchema: {
|
|
723
|
-
owner: z
|
|
724
|
-
.string()
|
|
725
|
-
.min(1)
|
|
726
|
-
.max(100)
|
|
727
|
-
.regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/, 'Invalid GitHub username/org format')
|
|
728
|
-
.describe(`Repository owner/org name (e.g., 'microsoft', 'google', NOT 'microsoft/vscode')`),
|
|
729
|
-
repo: z
|
|
730
|
-
.string()
|
|
731
|
-
.min(1)
|
|
732
|
-
.max(100)
|
|
733
|
-
.regex(/^[a-zA-Z0-9._-]+$/, 'Invalid repository name format')
|
|
734
|
-
.describe('Repository name (case-sensitive)'),
|
|
735
|
-
branch: z
|
|
736
|
-
.string()
|
|
737
|
-
.min(1)
|
|
738
|
-
.max(255)
|
|
739
|
-
.regex(/^[^\s]+$/, 'Branch name cannot contain spaces')
|
|
740
|
-
.describe('Branch name. Falls back to default branch if not found'),
|
|
741
|
-
path: z
|
|
742
|
-
.string()
|
|
743
|
-
.optional()
|
|
744
|
-
.default('')
|
|
745
|
-
.refine(path => !path.includes('..'), 'Path traversal not allowed')
|
|
746
|
-
.refine(path => path.length <= 500, 'Path too long')
|
|
747
|
-
.describe('Directory path within repository. Leave empty for root.'),
|
|
748
|
-
},
|
|
749
|
-
annotations: {
|
|
750
|
-
title: 'GitHub Repository Explorer',
|
|
751
|
-
readOnlyHint: true,
|
|
752
|
-
destructiveHint: false,
|
|
753
|
-
idempotentHint: true,
|
|
754
|
-
openWorldHint: true,
|
|
755
|
-
},
|
|
756
|
-
}, async (args) => {
|
|
757
|
-
try {
|
|
758
|
-
const result = await viewRepositoryStructure(args);
|
|
759
|
-
return result;
|
|
760
|
-
}
|
|
761
|
-
catch (error) {
|
|
762
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
763
|
-
return createResult({
|
|
764
|
-
error: `Failed to explore repository. ${errorMessage}. Verify repository exists and is accessible`,
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
/**
|
|
770
|
-
* Views the structure of a GitHub repository at a specific path.
|
|
771
|
-
* Optimized for code analysis workflows with smart defaults and clear errors.
|
|
772
|
-
*/
|
|
773
|
-
async function viewRepositoryStructure(params) {
|
|
774
|
-
const cacheKey = generateCacheKey('gh-repo-structure', params);
|
|
775
|
-
return withCache(cacheKey, async () => {
|
|
776
|
-
const { owner, repo, branch, path = '' } = params;
|
|
777
|
-
try {
|
|
778
|
-
// Clean up path
|
|
779
|
-
const cleanPath = path.startsWith('/') ? path.substring(1) : path;
|
|
780
|
-
// Try the requested branch first, then fallback to main/master
|
|
781
|
-
const branchesToTry = await getSmartBranchFallback(owner, repo, branch);
|
|
782
|
-
let items = [];
|
|
783
|
-
let usedBranch = branch;
|
|
784
|
-
let lastError = null;
|
|
785
|
-
let attemptCount = 0;
|
|
786
|
-
const maxAttempts = 3; // Prevent infinite loops
|
|
787
|
-
for (const tryBranch of branchesToTry) {
|
|
788
|
-
if (attemptCount >= maxAttempts)
|
|
789
|
-
break;
|
|
790
|
-
attemptCount++;
|
|
791
|
-
try {
|
|
792
|
-
const apiPath = `/repos/${owner}/${repo}/contents/${cleanPath}?ref=${tryBranch}`;
|
|
793
|
-
const result = await executeGitHubCommand('api', [apiPath], {
|
|
794
|
-
cache: false,
|
|
795
|
-
});
|
|
796
|
-
if (!result.isError) {
|
|
797
|
-
const execResult = JSON.parse(result.content[0].text);
|
|
798
|
-
const apiItems = execResult.result;
|
|
799
|
-
items = Array.isArray(apiItems) ? apiItems : [apiItems];
|
|
800
|
-
usedBranch = tryBranch;
|
|
801
|
-
break;
|
|
802
|
-
}
|
|
803
|
-
else {
|
|
804
|
-
lastError = new Error(result.content[0].text);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
catch (error) {
|
|
808
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
809
|
-
// Try next branch
|
|
810
|
-
continue;
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
if (items.length === 0) {
|
|
814
|
-
// Use the most descriptive error message
|
|
815
|
-
const errorMsg = lastError?.message || 'Unknown error';
|
|
816
|
-
if (errorMsg.includes('404') || errorMsg.includes('Not Found')) {
|
|
817
|
-
if (path) {
|
|
818
|
-
return createResult({
|
|
819
|
-
error: `Path "${path}" not found. Verify the path or use github_search_code to find files`,
|
|
820
|
-
});
|
|
821
|
-
}
|
|
822
|
-
else {
|
|
823
|
-
return createResult({
|
|
824
|
-
error: `Repository not found: ${owner}/${repo}. Check spelling and case sensitivity`,
|
|
825
|
-
});
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
else if (errorMsg.includes('403') || errorMsg.includes('Forbidden')) {
|
|
829
|
-
return createResult({
|
|
830
|
-
error: `Access denied to ${owner}/${repo}. Repository may be private - use api_status_check`,
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
else {
|
|
834
|
-
return createResult({
|
|
835
|
-
error: `Failed to access ${owner}/${repo}. Check network connection`,
|
|
836
|
-
});
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
// Limit total items to 100 for efficiency
|
|
840
|
-
const limitedItems = items.slice(0, 100);
|
|
841
|
-
// Sort: directories first, then alphabetically
|
|
842
|
-
limitedItems.sort((a, b) => {
|
|
843
|
-
if (a.type !== b.type) {
|
|
844
|
-
return a.type === 'dir' ? -1 : 1;
|
|
845
|
-
}
|
|
846
|
-
return a.name.localeCompare(b.name);
|
|
847
|
-
});
|
|
848
|
-
// Create simplified, token-efficient structure
|
|
849
|
-
const files = limitedItems
|
|
850
|
-
.filter(item => item.type === 'file')
|
|
851
|
-
.map(item => ({
|
|
852
|
-
name: item.name,
|
|
853
|
-
size: item.size,
|
|
854
|
-
url: item.path, // Use path for fetching
|
|
855
|
-
}));
|
|
856
|
-
const folders = limitedItems
|
|
857
|
-
.filter(item => item.type === 'dir')
|
|
858
|
-
.map(item => ({
|
|
859
|
-
name: item.name,
|
|
860
|
-
url: item.path, // Use path for browsing
|
|
861
|
-
}));
|
|
862
|
-
return createResult({
|
|
863
|
-
data: {
|
|
864
|
-
repository: `${owner}/${repo}`,
|
|
865
|
-
branch: usedBranch,
|
|
866
|
-
path: cleanPath || '/',
|
|
867
|
-
githubBasePath: `https://api.github.com/repos/${owner}/${repo}/contents/`,
|
|
868
|
-
files: {
|
|
869
|
-
count: files.length,
|
|
870
|
-
files: files,
|
|
871
|
-
},
|
|
872
|
-
folders: {
|
|
873
|
-
count: folders.length,
|
|
874
|
-
folders: folders,
|
|
875
|
-
},
|
|
876
|
-
},
|
|
877
|
-
});
|
|
878
|
-
}
|
|
879
|
-
catch (error) {
|
|
880
|
-
return createResult({
|
|
881
|
-
error: `Failed to access repository. ${error}. Verify repository name and authentication`,
|
|
882
|
-
});
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
|
-
}
|
|
886
|
-
/**
|
|
887
|
-
* Smart branch detection with automatic fallback to common branch names.
|
|
888
|
-
*/
|
|
889
|
-
async function getSmartBranchFallback(owner, repo, requestedBranch) {
|
|
890
|
-
const branches = [requestedBranch];
|
|
891
|
-
try {
|
|
892
|
-
// Try to get repository info to find default branch
|
|
893
|
-
const repoInfoResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
|
|
894
|
-
cache: false,
|
|
895
|
-
});
|
|
896
|
-
if (!repoInfoResult.isError) {
|
|
897
|
-
const execResult = JSON.parse(repoInfoResult.content[0].text);
|
|
898
|
-
const repoData = execResult.result;
|
|
899
|
-
const defaultBranch = repoData.default_branch;
|
|
900
|
-
if (defaultBranch && !branches.includes(defaultBranch)) {
|
|
901
|
-
branches.push(defaultBranch);
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
catch {
|
|
906
|
-
// If we can't get repo info, proceed with standard fallbacks
|
|
907
|
-
}
|
|
908
|
-
// Add common branch names if not already included
|
|
909
|
-
const commonBranches = ['main', 'master', 'develop', 'dev'];
|
|
910
|
-
commonBranches.forEach(branch => {
|
|
911
|
-
if (!branches.includes(branch)) {
|
|
912
|
-
branches.push(branch);
|
|
913
|
-
}
|
|
914
|
-
});
|
|
915
|
-
return branches;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
693
|
const GITHUB_GET_FILE_CONTENT_TOOL_NAME = 'githubGetFileContent';
|
|
919
|
-
const DESCRIPTION$
|
|
694
|
+
const DESCRIPTION$8 = `Fetch file content from GitHub repositories. Automatically handles branch fallback (main/master) and files up to 300KB. Returns decoded file content with metadata. Parameters: owner (required - GitHub username/org), repo (required - repository name), branch (required), filePath (required).`;
|
|
920
695
|
function registerFetchGitHubFileContentTool(server) {
|
|
921
696
|
server.registerTool(GITHUB_GET_FILE_CONTENT_TOOL_NAME, {
|
|
922
|
-
description: DESCRIPTION$
|
|
697
|
+
description: DESCRIPTION$8,
|
|
923
698
|
inputSchema: {
|
|
924
699
|
owner: z
|
|
925
700
|
.string()
|
|
926
701
|
.min(1)
|
|
927
702
|
.max(100)
|
|
928
703
|
.regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/)
|
|
929
|
-
.describe(`Repository owner/
|
|
704
|
+
.describe(`Repository owner/organization name (e.g., 'facebook', 'microsoft'). Do NOT include repository name here.`),
|
|
930
705
|
repo: z
|
|
931
706
|
.string()
|
|
932
707
|
.min(1)
|
|
933
708
|
.max(100)
|
|
934
709
|
.regex(/^[a-zA-Z0-9._-]+$/)
|
|
935
|
-
.describe(`Repository name only (e.g., '
|
|
710
|
+
.describe(`Repository name only (e.g., 'react', 'vscode'). Do NOT include owner/org prefix.`),
|
|
936
711
|
branch: z
|
|
937
712
|
.string()
|
|
938
713
|
.min(1)
|
|
939
714
|
.max(255)
|
|
940
715
|
.regex(/^[^\s]+$/)
|
|
941
|
-
.describe(`Branch name.
|
|
716
|
+
.describe(`Branch name (e.g., 'main', 'master'). Tool will automatically try 'main' and 'master' if specified branch is not found.`),
|
|
942
717
|
filePath: z
|
|
943
718
|
.string()
|
|
944
719
|
.min(1)
|
|
945
|
-
.describe(`
|
|
720
|
+
.describe(`File path from repository root (e.g., 'src/index.js', 'README.md', 'docs/api.md'). Do NOT start with slash.`),
|
|
946
721
|
},
|
|
947
722
|
annotations: {
|
|
948
723
|
title: 'GitHub File Content - Direct Access',
|
|
@@ -969,18 +744,39 @@ async function fetchGitHubFileContent(params) {
|
|
|
969
744
|
const { owner, repo, branch, filePath } = params;
|
|
970
745
|
try {
|
|
971
746
|
// Try to fetch file content directly
|
|
972
|
-
const apiPath = `/repos/${owner}/${repo}/contents/${filePath}
|
|
747
|
+
const apiPath = `/repos/${owner}/${repo}/contents/${filePath}`;
|
|
973
748
|
const result = await executeGitHubCommand('api', [apiPath], {
|
|
974
749
|
cache: false,
|
|
975
750
|
});
|
|
976
751
|
if (result.isError) {
|
|
977
752
|
const errorMsg = result.content[0].text;
|
|
753
|
+
// Check repository existence only after content fetch fails
|
|
754
|
+
const repoCheckResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
|
|
755
|
+
cache: false,
|
|
756
|
+
});
|
|
757
|
+
if (repoCheckResult.isError) {
|
|
758
|
+
const repoErrorMsg = repoCheckResult.content[0].text;
|
|
759
|
+
if (repoErrorMsg.includes('404')) {
|
|
760
|
+
return createResult({
|
|
761
|
+
error: `Repository "${owner}/${repo}" not found. This is often due to incorrect repository name. Steps to resolve:\n1. Use github_search_code with query="${filePath.split('/').pop()}" owner="${owner}" to find the correct repository\n2. Verify the exact repository name\n3. Check if the repository might have been renamed or moved`,
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
else if (repoErrorMsg.includes('403')) {
|
|
765
|
+
return createResult({
|
|
766
|
+
error: `Repository "${owner}/${repo}" exists but access is denied. Repository might be private or archived. Use api_status_check to verify permissions.`,
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
}
|
|
978
770
|
// Try fallback branches if original branch fails
|
|
979
771
|
if (errorMsg.includes('404') &&
|
|
980
772
|
branch !== 'main' &&
|
|
981
773
|
branch !== 'master') {
|
|
982
774
|
const fallbackBranches = ['main', 'master'];
|
|
775
|
+
const triedBranches = [branch];
|
|
983
776
|
for (const fallbackBranch of fallbackBranches) {
|
|
777
|
+
if (triedBranches.includes(fallbackBranch))
|
|
778
|
+
continue;
|
|
779
|
+
triedBranches.push(fallbackBranch);
|
|
984
780
|
const fallbackPath = `/repos/${owner}/${repo}/contents/${filePath}?ref=${fallbackBranch}`;
|
|
985
781
|
const fallbackResult = await executeGitHubCommand('api', [fallbackPath], {
|
|
986
782
|
cache: false,
|
|
@@ -989,27 +785,32 @@ async function fetchGitHubFileContent(params) {
|
|
|
989
785
|
return await processFileContent(fallbackResult, owner, repo, fallbackBranch, filePath);
|
|
990
786
|
}
|
|
991
787
|
}
|
|
788
|
+
return createResult({
|
|
789
|
+
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.`,
|
|
790
|
+
});
|
|
992
791
|
}
|
|
993
|
-
// Handle common errors
|
|
792
|
+
// Handle common errors with more context
|
|
994
793
|
if (errorMsg.includes('404')) {
|
|
794
|
+
const searchSuggestion = await suggestCodeSearchFallback(owner, filePath);
|
|
995
795
|
return createResult({
|
|
996
|
-
error:
|
|
796
|
+
error: `File "${filePath}" not found in branch "${branch}". Use github_view_repo_structure to verify path.${searchSuggestion}`,
|
|
997
797
|
});
|
|
998
798
|
}
|
|
999
799
|
else if (errorMsg.includes('403')) {
|
|
1000
800
|
return createResult({
|
|
1001
|
-
error:
|
|
801
|
+
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
802
|
});
|
|
1003
803
|
}
|
|
1004
804
|
else if (errorMsg.includes('maxBuffer') ||
|
|
1005
805
|
errorMsg.includes('stdout maxBuffer length exceeded')) {
|
|
1006
806
|
return createResult({
|
|
1007
|
-
error:
|
|
807
|
+
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
808
|
});
|
|
1009
809
|
}
|
|
1010
810
|
else {
|
|
811
|
+
const searchSuggestion = await suggestCodeSearchFallback(owner, filePath);
|
|
1011
812
|
return createResult({
|
|
1012
|
-
error:
|
|
813
|
+
error: `Failed to fetch "${filePath}". Error: ${errorMsg}. Verify repository name, branch, and file path.${searchSuggestion}`,
|
|
1013
814
|
});
|
|
1014
815
|
}
|
|
1015
816
|
}
|
|
@@ -1017,15 +818,14 @@ async function fetchGitHubFileContent(params) {
|
|
|
1017
818
|
}
|
|
1018
819
|
catch (error) {
|
|
1019
820
|
const errorMessage = error.message;
|
|
1020
|
-
// Handle maxBuffer errors that escape the main try-catch
|
|
1021
821
|
if (errorMessage.includes('maxBuffer') ||
|
|
1022
822
|
errorMessage.includes('stdout maxBuffer length exceeded')) {
|
|
1023
823
|
return createResult({
|
|
1024
|
-
error:
|
|
824
|
+
error: `File "${filePath}" is too large (>300KB). Use github_search_code to search within the file or download directly from GitHub.`,
|
|
1025
825
|
});
|
|
1026
826
|
}
|
|
1027
827
|
return createResult({
|
|
1028
|
-
error:
|
|
828
|
+
error: `Unexpected error fetching "${filePath}": ${errorMessage}. Check network connection and try again.`,
|
|
1029
829
|
});
|
|
1030
830
|
}
|
|
1031
831
|
});
|
|
@@ -1088,41 +888,82 @@ async function processFileContent(result, owner, repo, branch, filePath) {
|
|
|
1088
888
|
},
|
|
1089
889
|
});
|
|
1090
890
|
}
|
|
891
|
+
// Helper function to suggest code search strategy
|
|
892
|
+
async function suggestCodeSearchFallback(owner, filePath) {
|
|
893
|
+
try {
|
|
894
|
+
// Extract filename and try to find in same organization
|
|
895
|
+
const fileName = filePath.split('/').pop() || filePath;
|
|
896
|
+
const searchResult = await executeGitHubCommand('api', [`/search/code?q=${encodeURIComponent(fileName)}+in:path+org:${owner}`], { cache: false });
|
|
897
|
+
if (!searchResult.isError) {
|
|
898
|
+
const results = JSON.parse(searchResult.content[0].text);
|
|
899
|
+
if (results.total_count > 0) {
|
|
900
|
+
const firstMatch = results.items[0];
|
|
901
|
+
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}"`;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
catch {
|
|
906
|
+
// Fallback to generic message if search fails
|
|
907
|
+
}
|
|
908
|
+
return ` Try these searches:\n1. github_search_code with query="${filePath.split('/').pop()}" owner="${owner}"\n2. github_search_code with query="path:${filePath}"`;
|
|
909
|
+
}
|
|
1091
910
|
|
|
1092
911
|
const GITHUB_SEARCH_CODE_TOOL_NAME = 'githubSearchCode';
|
|
1093
|
-
const DESCRIPTION$
|
|
912
|
+
const DESCRIPTION$7 = `Search code across GitHub repositories using GitHub's code search API.
|
|
913
|
+
|
|
914
|
+
Never use filters and flags on exploretory searches and on initial searches.
|
|
915
|
+
Use filters and flags on subsequent searches when you know what to search for or if the user asks for it explicitly.
|
|
916
|
+
Using too many flags might make miss relevant results. Use filters and flags to narrow down the results when getting too many.
|
|
917
|
+
|
|
918
|
+
Search Syntax - ALL terms must be present (AND logic):
|
|
919
|
+
The search finds files that contain ALL specified terms. Each space-separated term is required to be present in the same file.
|
|
920
|
+
- Multiple terms: All individual words must exist in the file
|
|
921
|
+
- Quoted phrases: Exact phrase matching for multi-word expressions
|
|
922
|
+
- Mixed terms: Combination of individual words AND exact phrases, all must be present
|
|
923
|
+
- Additional filters: Language, owner, repository, filename, extension, size and other flags supported
|
|
924
|
+
|
|
925
|
+
Key behavior: All search terms are combined with AND logic - every term must be found in the file.
|
|
926
|
+
Quotes create exact phrase matching, unquoted terms are individual word requirements.
|
|
927
|
+
Start with 1-2 terms, add more to narrow results. Use filters and flags for precision.
|
|
928
|
+
|
|
929
|
+
`;
|
|
1094
930
|
function registerGitHubSearchCodeTool(server) {
|
|
1095
931
|
server.registerTool(GITHUB_SEARCH_CODE_TOOL_NAME, {
|
|
1096
|
-
description: DESCRIPTION$
|
|
932
|
+
description: DESCRIPTION$7,
|
|
1097
933
|
inputSchema: {
|
|
1098
934
|
query: z
|
|
1099
935
|
.string()
|
|
1100
936
|
.min(1)
|
|
1101
|
-
.describe('Search
|
|
937
|
+
.describe('Search query with AND logic between terms. Multiple words require ALL to be present. Use quotes for exact phrases.'),
|
|
1102
938
|
language: z
|
|
1103
939
|
.string()
|
|
1104
940
|
.optional()
|
|
1105
|
-
.describe('
|
|
941
|
+
.describe('Programming language filter. Narrows search to specific language files. Use for language-specific searches.'),
|
|
1106
942
|
owner: z
|
|
1107
943
|
.union([z.string(), z.array(z.string())])
|
|
1108
944
|
.optional()
|
|
1109
|
-
.describe('Repository owner/
|
|
945
|
+
.describe('Repository owner/organization name(s) to search within (e.g., "facebook", ["google", "microsoft"]). Use this to search within specific organizations. Do NOT use owner/repo format - just the organization/username.'),
|
|
946
|
+
repo: z
|
|
947
|
+
.union([z.string(), z.array(z.string())])
|
|
948
|
+
.optional()
|
|
949
|
+
.describe('Filter on specific repository(ies). Must use full "owner/repo" format (e.g., "facebook/react", ["vuejs/vue", "angular/angular"]). Use this for searching within specific repositories.'),
|
|
1110
950
|
filename: z
|
|
1111
951
|
.string()
|
|
1112
952
|
.optional()
|
|
1113
|
-
.describe('
|
|
953
|
+
.describe('Target specific filename or pattern. Use for file-specific searches.'),
|
|
1114
954
|
extension: z
|
|
1115
955
|
.string()
|
|
1116
956
|
.optional()
|
|
1117
|
-
.describe('File extension
|
|
957
|
+
.describe('File extension filter. Alternative to language parameter.'),
|
|
1118
958
|
match: z
|
|
1119
959
|
.union([z.enum(['file', 'path']), z.array(z.enum(['file', 'path']))])
|
|
1120
960
|
.optional()
|
|
1121
|
-
.describe('Search scope: "file" for content, "path" for filenames
|
|
961
|
+
.describe('Search scope: "file" for file content (default), "path" for filenames/paths, or ["file", "path"] for both. Controls where to search for terms.'),
|
|
1122
962
|
size: z
|
|
1123
963
|
.string()
|
|
964
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid size format. Use: ">10", ">=5", "<100", "<=50", "10..100", or exact number "50"')
|
|
1124
965
|
.optional()
|
|
1125
|
-
.describe('File size in KB. Format: >
|
|
966
|
+
.describe('File size filter in KB. Format: ">N" (larger than), "<N" (smaller than), "N..M" (range), "N" (exact).'),
|
|
1126
967
|
limit: z
|
|
1127
968
|
.number()
|
|
1128
969
|
.int()
|
|
@@ -1130,7 +971,7 @@ function registerGitHubSearchCodeTool(server) {
|
|
|
1130
971
|
.max(100)
|
|
1131
972
|
.optional()
|
|
1132
973
|
.default(30)
|
|
1133
|
-
.describe('
|
|
974
|
+
.describe('Maximum number of results to return (1-100). Default: 30. Higher values may increase response time.'),
|
|
1134
975
|
},
|
|
1135
976
|
annotations: {
|
|
1136
977
|
title: 'GitHub Code Search - Smart & Efficient',
|
|
@@ -1236,6 +1077,10 @@ function transformToOptimizedFormat$1(items) {
|
|
|
1236
1077
|
: [0, 0]) || [],
|
|
1237
1078
|
})) || [],
|
|
1238
1079
|
url: singleRepo ? item.path : simplifyGitHubUrl(item.url),
|
|
1080
|
+
repository: {
|
|
1081
|
+
nameWithOwner: item.repository.nameWithOwner,
|
|
1082
|
+
url: item.repository.url,
|
|
1083
|
+
},
|
|
1239
1084
|
}));
|
|
1240
1085
|
const result = {
|
|
1241
1086
|
items: optimizedItems,
|
|
@@ -1262,32 +1107,18 @@ function extractSingleRepository$1(items) {
|
|
|
1262
1107
|
}
|
|
1263
1108
|
/**
|
|
1264
1109
|
* 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`).
|
|
1110
|
+
* Preserves quoted phrases and supports both AND search and exact phrase matching.
|
|
1270
1111
|
*/
|
|
1271
1112
|
function buildGitHubCliArgs(params) {
|
|
1272
1113
|
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 => {
|
|
1114
|
+
// Parse query to preserve quoted phrases and extract qualifiers
|
|
1115
|
+
const { searchQuery, extractedQualifiers } = parseSearchQuery(params.query);
|
|
1116
|
+
// Add the main search query if present
|
|
1117
|
+
if (searchQuery) {
|
|
1118
|
+
args.push(searchQuery);
|
|
1119
|
+
}
|
|
1120
|
+
// Add extracted qualifiers from the query
|
|
1121
|
+
extractedQualifiers.forEach(qualifier => {
|
|
1291
1122
|
args.push(qualifier);
|
|
1292
1123
|
});
|
|
1293
1124
|
// Add explicit parameters as qualifiers
|
|
@@ -1300,6 +1131,10 @@ function buildGitHubCliArgs(params) {
|
|
|
1300
1131
|
const owners = Array.isArray(params.owner) ? params.owner : [params.owner];
|
|
1301
1132
|
owners.forEach(owner => args.push(`org:${owner}`));
|
|
1302
1133
|
}
|
|
1134
|
+
if (params.repo && !params.query.includes('repo:')) {
|
|
1135
|
+
const repos = Array.isArray(params.repo) ? params.repo : [params.repo];
|
|
1136
|
+
repos.forEach(repo => args.push(`repo:${repo}`));
|
|
1137
|
+
}
|
|
1303
1138
|
if (params.filename && !params.query.includes('filename:')) {
|
|
1304
1139
|
args.push(`filename:${params.filename}`);
|
|
1305
1140
|
}
|
|
@@ -1382,12 +1217,48 @@ function validateSearchParameters(params) {
|
|
|
1382
1217
|
// }
|
|
1383
1218
|
return null; // No validation errors
|
|
1384
1219
|
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Parse search query to preserve quoted phrases and extract qualifiers.
|
|
1222
|
+
* Handles:
|
|
1223
|
+
* - Quoted phrases: "error handling" -> kept as single unit
|
|
1224
|
+
* - Multiple terms: react lifecycle -> both terms for AND search
|
|
1225
|
+
* - Mixed: "error handling" debug -> phrase + term
|
|
1226
|
+
* - Qualifiers: language:javascript -> extracted separately
|
|
1227
|
+
*/
|
|
1228
|
+
function parseSearchQuery(query) {
|
|
1229
|
+
const qualifiers = [];
|
|
1230
|
+
const searchTerms = [];
|
|
1231
|
+
// Regular expression to match quoted strings or individual words/qualifiers
|
|
1232
|
+
const tokenRegex = /"([^"]+)"|([^\s]+)/g;
|
|
1233
|
+
let match;
|
|
1234
|
+
while ((match = tokenRegex.exec(query)) !== null) {
|
|
1235
|
+
const token = match[1] || match[2]; // match[1] is quoted content, match[2] is unquoted
|
|
1236
|
+
// Check if it's a qualifier (contains : but not inside quotes)
|
|
1237
|
+
if (!match[1] && token.includes(':') && /^[a-zA-Z]+:/.test(token)) {
|
|
1238
|
+
qualifiers.push(token);
|
|
1239
|
+
}
|
|
1240
|
+
else {
|
|
1241
|
+
// It's a search term (either quoted or unquoted)
|
|
1242
|
+
if (match[1]) {
|
|
1243
|
+
// Preserve quotes for exact phrase search
|
|
1244
|
+
searchTerms.push(`"${token}"`);
|
|
1245
|
+
}
|
|
1246
|
+
else {
|
|
1247
|
+
searchTerms.push(token);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return {
|
|
1252
|
+
searchQuery: searchTerms.join(' '),
|
|
1253
|
+
extractedQualifiers: qualifiers,
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1385
1256
|
|
|
1386
1257
|
const GITHUB_SEARCH_COMMITS_TOOL_NAME = 'githubSearchCommits';
|
|
1387
|
-
const DESCRIPTION$
|
|
1258
|
+
const DESCRIPTION$6 = `Search commit history across GitHub repositories. Find commits by message, author, date, or repository. Supports advanced filtering for comprehensive commit analysis. Returns commit SHA, message, author, and date information.`;
|
|
1388
1259
|
function registerGitHubSearchCommitsTool(server) {
|
|
1389
1260
|
server.registerTool(GITHUB_SEARCH_COMMITS_TOOL_NAME, {
|
|
1390
|
-
description: DESCRIPTION$
|
|
1261
|
+
description: DESCRIPTION$6,
|
|
1391
1262
|
inputSchema: {
|
|
1392
1263
|
query: z
|
|
1393
1264
|
.string()
|
|
@@ -1397,34 +1268,40 @@ function registerGitHubSearchCommitsTool(server) {
|
|
|
1397
1268
|
owner: z
|
|
1398
1269
|
.string()
|
|
1399
1270
|
.optional()
|
|
1400
|
-
.describe('Repository owner/
|
|
1271
|
+
.describe('Repository owner/organization name only (e.g., "facebook", "microsoft"). Do NOT include repository name. Must be used with repo parameter for repository-specific searches.'),
|
|
1401
1272
|
repo: z
|
|
1402
1273
|
.string()
|
|
1403
1274
|
.optional()
|
|
1404
|
-
.describe('Repository name only (e.g., "
|
|
1275
|
+
.describe('Repository name only (e.g., "react", "vscode"). Do NOT include owner prefix. Must be used together with owner parameter.'),
|
|
1405
1276
|
// Author filters
|
|
1406
1277
|
author: z
|
|
1407
1278
|
.string()
|
|
1408
1279
|
.optional()
|
|
1409
1280
|
.describe('GitHub username of commit author'),
|
|
1410
|
-
|
|
1281
|
+
'author-name': z
|
|
1411
1282
|
.string()
|
|
1412
1283
|
.optional()
|
|
1413
1284
|
.describe('Full name of commit author'),
|
|
1414
|
-
|
|
1285
|
+
'author-email': z
|
|
1286
|
+
.string()
|
|
1287
|
+
.optional()
|
|
1288
|
+
.describe('Email of commit author'),
|
|
1415
1289
|
// Committer filters
|
|
1416
1290
|
committer: z
|
|
1417
1291
|
.string()
|
|
1418
1292
|
.optional()
|
|
1419
1293
|
.describe('GitHub username of committer'),
|
|
1420
|
-
|
|
1421
|
-
|
|
1294
|
+
'committer-name': z
|
|
1295
|
+
.string()
|
|
1296
|
+
.optional()
|
|
1297
|
+
.describe('Full name of committer'),
|
|
1298
|
+
'committer-email': z.string().optional().describe('Email of committer'),
|
|
1422
1299
|
// Date filters
|
|
1423
|
-
|
|
1300
|
+
'author-date': z
|
|
1424
1301
|
.string()
|
|
1425
1302
|
.optional()
|
|
1426
1303
|
.describe('When authored. Format: >2020-01-01, <2023-12-31, 2020-01-01..2023-12-31'),
|
|
1427
|
-
|
|
1304
|
+
'committer-date': z
|
|
1428
1305
|
.string()
|
|
1429
1306
|
.optional()
|
|
1430
1307
|
.describe('When committed. Format: >2020-01-01, <2023-12-31, 2020-01-01..2023-12-31'),
|
|
@@ -1603,22 +1480,22 @@ function buildGitHubCommitCliArgs(params) {
|
|
|
1603
1480
|
// Author filters
|
|
1604
1481
|
if (params.author)
|
|
1605
1482
|
args.push(`--author=${params.author}`);
|
|
1606
|
-
if (params
|
|
1607
|
-
args.push(`--author-name=${params
|
|
1608
|
-
if (params
|
|
1609
|
-
args.push(`--author-email=${params
|
|
1483
|
+
if (params['author-name'])
|
|
1484
|
+
args.push(`--author-name=${params['author-name']}`);
|
|
1485
|
+
if (params['author-email'])
|
|
1486
|
+
args.push(`--author-email=${params['author-email']}`);
|
|
1610
1487
|
// Committer filters
|
|
1611
1488
|
if (params.committer)
|
|
1612
1489
|
args.push(`--committer=${params.committer}`);
|
|
1613
|
-
if (params
|
|
1614
|
-
args.push(`--committer-name=${params
|
|
1615
|
-
if (params
|
|
1616
|
-
args.push(`--committer-email=${params
|
|
1490
|
+
if (params['committer-name'])
|
|
1491
|
+
args.push(`--committer-name=${params['committer-name']}`);
|
|
1492
|
+
if (params['committer-email'])
|
|
1493
|
+
args.push(`--committer-email=${params['committer-email']}`);
|
|
1617
1494
|
// Date filters
|
|
1618
|
-
if (params
|
|
1619
|
-
args.push(`--author-date=${params
|
|
1620
|
-
if (params
|
|
1621
|
-
args.push(`--committer-date=${params
|
|
1495
|
+
if (params['author-date'])
|
|
1496
|
+
args.push(`--author-date=${params['author-date']}`);
|
|
1497
|
+
if (params['committer-date'])
|
|
1498
|
+
args.push(`--committer-date=${params['committer-date']}`);
|
|
1622
1499
|
// Hash filters
|
|
1623
1500
|
if (params.hash)
|
|
1624
1501
|
args.push(`--hash=${params.hash}`);
|
|
@@ -1628,7 +1505,7 @@ function buildGitHubCommitCliArgs(params) {
|
|
|
1628
1505
|
args.push(`--tree=${params.tree}`);
|
|
1629
1506
|
// State filters
|
|
1630
1507
|
if (params.merge !== undefined)
|
|
1631
|
-
args.push(`--merge
|
|
1508
|
+
args.push(`--merge`);
|
|
1632
1509
|
// Visibility
|
|
1633
1510
|
if (params.visibility)
|
|
1634
1511
|
args.push(`--visibility=${params.visibility}`);
|
|
@@ -1645,10 +1522,10 @@ function buildGitHubCommitCliArgs(params) {
|
|
|
1645
1522
|
}
|
|
1646
1523
|
|
|
1647
1524
|
const GITHUB_SEARCH_ISSUES_TOOL_NAME = 'githubSearchIssues';
|
|
1648
|
-
const DESCRIPTION$
|
|
1525
|
+
const DESCRIPTION$5 = `Search GitHub issues for bug reports, feature requests, and discussions. Find issues by keywords, state, labels, author, or repository. Returns issue number, title, state, labels, and metadata for effective issue tracking and analysis.`;
|
|
1649
1526
|
function registerSearchGitHubIssuesTool(server) {
|
|
1650
1527
|
server.registerTool(GITHUB_SEARCH_ISSUES_TOOL_NAME, {
|
|
1651
|
-
description: DESCRIPTION$
|
|
1528
|
+
description: DESCRIPTION$5,
|
|
1652
1529
|
inputSchema: {
|
|
1653
1530
|
query: z
|
|
1654
1531
|
.string()
|
|
@@ -1658,11 +1535,11 @@ function registerSearchGitHubIssuesTool(server) {
|
|
|
1658
1535
|
.string()
|
|
1659
1536
|
.min(1)
|
|
1660
1537
|
.optional()
|
|
1661
|
-
.describe('Repository owner/
|
|
1538
|
+
.describe('Repository owner/organization name only (e.g., "facebook", "microsoft"). Do NOT include repository name. Must be used with repo parameter for repository-specific searches.'),
|
|
1662
1539
|
repo: z
|
|
1663
1540
|
.string()
|
|
1664
1541
|
.optional()
|
|
1665
|
-
.describe('Repository name only (e.g., "
|
|
1542
|
+
.describe('Repository name only (e.g., "react", "vscode"). Do NOT include owner prefix. Must be used together with owner parameter.'),
|
|
1666
1543
|
app: z
|
|
1667
1544
|
.string()
|
|
1668
1545
|
.optional()
|
|
@@ -1678,33 +1555,45 @@ function registerSearchGitHubIssuesTool(server) {
|
|
|
1678
1555
|
.describe('GitHub username of issue creator'),
|
|
1679
1556
|
closed: z
|
|
1680
1557
|
.string()
|
|
1558
|
+
.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
1559
|
.optional()
|
|
1682
|
-
.describe('When closed. Format: >2020-01-01'),
|
|
1560
|
+
.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
1561
|
commenter: z
|
|
1684
1562
|
.string()
|
|
1685
1563
|
.optional()
|
|
1686
1564
|
.describe('User who commented on issue'),
|
|
1687
1565
|
comments: z
|
|
1688
|
-
.
|
|
1566
|
+
.union([
|
|
1567
|
+
z.number().int().min(0),
|
|
1568
|
+
z
|
|
1569
|
+
.string()
|
|
1570
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">10", ">=5", "<20", "<=15", "5..20", or exact number "10"'),
|
|
1571
|
+
])
|
|
1689
1572
|
.optional()
|
|
1690
|
-
.describe('Comment count. Format: >10, <5, 5..10'),
|
|
1573
|
+
.describe('Comment count filter. Format: ">10" (many comments), ">=5" (at least 5), "<5" (few comments), "5..10" (range), "10" (exact)'),
|
|
1691
1574
|
created: z
|
|
1692
1575
|
.string()
|
|
1576
|
+
.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
1577
|
.optional()
|
|
1694
|
-
.describe('When created. Format: >2020-01-01'),
|
|
1695
|
-
|
|
1578
|
+
.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)'),
|
|
1579
|
+
'include-prs': z
|
|
1696
1580
|
.boolean()
|
|
1697
1581
|
.optional()
|
|
1698
1582
|
.describe('Include pull requests. Default: false'),
|
|
1699
1583
|
interactions: z
|
|
1700
|
-
.
|
|
1584
|
+
.union([
|
|
1585
|
+
z.number().int().min(0),
|
|
1586
|
+
z
|
|
1587
|
+
.string()
|
|
1588
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">100", ">=50", "<200", "<=150", "50..200", or exact number "100"'),
|
|
1589
|
+
])
|
|
1701
1590
|
.optional()
|
|
1702
|
-
.describe('Total interactions (reactions + comments)'),
|
|
1591
|
+
.describe('Total interactions (reactions + comments) filter. Format: ">100" (highly engaged), ">=50" (moderately engaged), "<20" (low engagement), "50..200" (range)'),
|
|
1703
1592
|
involves: z.string().optional().describe('User involved in any way'),
|
|
1704
|
-
|
|
1705
|
-
.string()
|
|
1593
|
+
label: z
|
|
1594
|
+
.union([z.string(), z.array(z.string())])
|
|
1706
1595
|
.optional()
|
|
1707
|
-
.describe('Label names
|
|
1596
|
+
.describe('Label names. Can be single string or array.'),
|
|
1708
1597
|
language: z.string().optional().describe('Repository language'),
|
|
1709
1598
|
locked: z.boolean().optional().describe('Conversation locked status'),
|
|
1710
1599
|
match: z
|
|
@@ -1713,27 +1602,39 @@ function registerSearchGitHubIssuesTool(server) {
|
|
|
1713
1602
|
.describe('Search scope. Default: title and body'),
|
|
1714
1603
|
mentions: z.string().optional().describe('Issues mentioning this user'),
|
|
1715
1604
|
milestone: z.string().optional().describe('Milestone name'),
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1605
|
+
'no-assignee': z
|
|
1606
|
+
.boolean()
|
|
1607
|
+
.optional()
|
|
1608
|
+
.describe('Issues without assignee'),
|
|
1609
|
+
'no-label': z.boolean().optional().describe('Issues without labels'),
|
|
1610
|
+
'no-milestone': z
|
|
1719
1611
|
.boolean()
|
|
1720
1612
|
.optional()
|
|
1721
1613
|
.describe('Issues without milestone'),
|
|
1722
|
-
|
|
1614
|
+
'no-project': z.boolean().optional().describe('Issues not in projects'),
|
|
1723
1615
|
project: z.string().optional().describe('Project board number'),
|
|
1724
1616
|
reactions: z
|
|
1725
|
-
.
|
|
1617
|
+
.union([
|
|
1618
|
+
z.number().int().min(0),
|
|
1619
|
+
z
|
|
1620
|
+
.string()
|
|
1621
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">10", ">=5", "<50", "<=25", "5..50", or exact number "10"'),
|
|
1622
|
+
])
|
|
1726
1623
|
.optional()
|
|
1727
|
-
.describe('Reaction count. Format: >10'),
|
|
1624
|
+
.describe('Reaction count filter. Format: ">10" (popular), ">=5" (some reactions), "<50" (moderate), "5..50" (range), "10" (exact)'),
|
|
1728
1625
|
state: z
|
|
1729
1626
|
.enum(['open', 'closed'])
|
|
1730
1627
|
.optional()
|
|
1731
1628
|
.describe('Issue state. Default: all'),
|
|
1732
|
-
|
|
1629
|
+
'team-mentions': z
|
|
1630
|
+
.string()
|
|
1631
|
+
.optional()
|
|
1632
|
+
.describe('Team mentioned in issue (@org/team-name)'),
|
|
1733
1633
|
updated: z
|
|
1734
1634
|
.string()
|
|
1635
|
+
.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
1636
|
.optional()
|
|
1736
|
-
.describe('When updated. Format: >2020-01-01'),
|
|
1637
|
+
.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
1638
|
visibility: z
|
|
1738
1639
|
.enum(['public', 'private', 'internal'])
|
|
1739
1640
|
.optional()
|
|
@@ -1889,23 +1790,40 @@ function buildGitHubIssuesAPICommand(params) {
|
|
|
1889
1790
|
queryParts.push(`${key}:${value}`);
|
|
1890
1791
|
});
|
|
1891
1792
|
// Special qualifiers - handle labels carefully
|
|
1892
|
-
if (params.
|
|
1893
|
-
|
|
1793
|
+
if (params.label) {
|
|
1794
|
+
const labels = Array.isArray(params.label) ? params.label : [params.label];
|
|
1795
|
+
labels.forEach(label => queryParts.push(`label:"${label}"`));
|
|
1894
1796
|
}
|
|
1895
1797
|
if (params.milestone)
|
|
1896
1798
|
queryParts.push(`milestone:"${params.milestone}"`);
|
|
1897
|
-
if (params
|
|
1799
|
+
if (params['no-assignee'])
|
|
1898
1800
|
queryParts.push('no:assignee');
|
|
1899
|
-
if (params
|
|
1801
|
+
if (params['no-label'])
|
|
1900
1802
|
queryParts.push('no:label');
|
|
1901
|
-
if (params
|
|
1803
|
+
if (params['no-milestone'])
|
|
1902
1804
|
queryParts.push('no:milestone');
|
|
1805
|
+
if (params['no-project'])
|
|
1806
|
+
queryParts.push('no:project');
|
|
1903
1807
|
if (params.archived !== undefined)
|
|
1904
1808
|
queryParts.push(`archived:${params.archived}`);
|
|
1905
1809
|
if (params.locked)
|
|
1906
1810
|
queryParts.push('is:locked');
|
|
1907
1811
|
if (params.visibility)
|
|
1908
1812
|
queryParts.push(`is:${params.visibility}`);
|
|
1813
|
+
if (params['include-prs'])
|
|
1814
|
+
queryParts.push('is:pr');
|
|
1815
|
+
if (params['team-mentions'])
|
|
1816
|
+
queryParts.push(`team:${params['team-mentions']}`);
|
|
1817
|
+
if (params.project)
|
|
1818
|
+
queryParts.push(`project:${params.project}`);
|
|
1819
|
+
if (params.app)
|
|
1820
|
+
queryParts.push(`app:${params.app}`);
|
|
1821
|
+
if (params.comments)
|
|
1822
|
+
queryParts.push(`comments:${params.comments}`);
|
|
1823
|
+
if (params.interactions)
|
|
1824
|
+
queryParts.push(`interactions:${params.interactions}`);
|
|
1825
|
+
if (params.reactions)
|
|
1826
|
+
queryParts.push(`reactions:${params.reactions}`);
|
|
1909
1827
|
// Extract qualifiers from original query and add them if not already set by params
|
|
1910
1828
|
if (baseQuery.includes('is:') && !params.state) {
|
|
1911
1829
|
const isMatch = baseQuery.match(/\bis:(open|closed)\b/i);
|
|
@@ -1913,7 +1831,7 @@ function buildGitHubIssuesAPICommand(params) {
|
|
|
1913
1831
|
queryParts.push(`state:${isMatch[1].toLowerCase()}`);
|
|
1914
1832
|
}
|
|
1915
1833
|
}
|
|
1916
|
-
if (baseQuery.includes('label:') && !params.
|
|
1834
|
+
if (baseQuery.includes('label:') && !params.label) {
|
|
1917
1835
|
const labelMatch = baseQuery.match(/\blabel:("[^"]*"|[^\s]+)/i);
|
|
1918
1836
|
if (labelMatch) {
|
|
1919
1837
|
const labelValue = labelMatch[1].replace(/"/g, '');
|
|
@@ -1938,10 +1856,10 @@ function buildGitHubIssuesAPICommand(params) {
|
|
|
1938
1856
|
|
|
1939
1857
|
// TODO: add PR commeents. e.g, gh pr view <PR_NUMBER_OR_URL_OR_BRANCH> --comments
|
|
1940
1858
|
const GITHUB_SEARCH_PULL_REQUESTS_TOOL_NAME = 'githubSearchPullRequests';
|
|
1941
|
-
const DESCRIPTION$
|
|
1859
|
+
const DESCRIPTION$4 = `Search GitHub pull requests for code changes, feature implementations, and bug fixes. Find PRs by keywords, state, author, review status, or repository. Returns PR number, title, state, branches, and review information for code review analysis.`;
|
|
1942
1860
|
function registerSearchGitHubPullRequestsTool(server) {
|
|
1943
1861
|
server.registerTool(GITHUB_SEARCH_PULL_REQUESTS_TOOL_NAME, {
|
|
1944
|
-
description: DESCRIPTION$
|
|
1862
|
+
description: DESCRIPTION$4,
|
|
1945
1863
|
inputSchema: {
|
|
1946
1864
|
query: z
|
|
1947
1865
|
.string()
|
|
@@ -1950,18 +1868,21 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
1950
1868
|
owner: z
|
|
1951
1869
|
.string()
|
|
1952
1870
|
.optional()
|
|
1953
|
-
.describe('Repository owner/
|
|
1871
|
+
.describe('Repository owner/organization name only (e.g., "facebook", "microsoft"). Do NOT include repository name. Must be used with repo parameter for repository-specific searches.'),
|
|
1954
1872
|
repo: z
|
|
1955
1873
|
.string()
|
|
1956
1874
|
.optional()
|
|
1957
|
-
.describe('Repository name only (e.g., "
|
|
1875
|
+
.describe('Repository name only (e.g., "react", "vscode"). Do NOT include owner prefix. Must be used together with owner parameter.'),
|
|
1958
1876
|
author: z.string().optional().describe('GitHub username of PR author'),
|
|
1959
1877
|
assignee: z.string().optional().describe('GitHub username of assignee'),
|
|
1960
1878
|
mentions: z.string().optional().describe('PRs mentioning this user'),
|
|
1961
1879
|
commenter: z.string().optional().describe('User who commented on PR'),
|
|
1962
1880
|
involves: z.string().optional().describe('User involved in any way'),
|
|
1963
|
-
|
|
1964
|
-
|
|
1881
|
+
'reviewed-by': z
|
|
1882
|
+
.string()
|
|
1883
|
+
.optional()
|
|
1884
|
+
.describe('User who reviewed the PR'),
|
|
1885
|
+
'review-requested': z
|
|
1965
1886
|
.string()
|
|
1966
1887
|
.optional()
|
|
1967
1888
|
.describe('User/team requested for review'),
|
|
@@ -1970,10 +1891,7 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
1970
1891
|
.optional()
|
|
1971
1892
|
.describe('PR state. Default: all'),
|
|
1972
1893
|
head: z.string().optional().describe('Source branch name'),
|
|
1973
|
-
base: z
|
|
1974
|
-
.string()
|
|
1975
|
-
.optional()
|
|
1976
|
-
.describe('Target branch name (main, develop, etc.)'),
|
|
1894
|
+
base: z.string().optional().describe('Target branch name'),
|
|
1977
1895
|
language: z.string().optional().describe('Repository language'),
|
|
1978
1896
|
created: z
|
|
1979
1897
|
.string()
|
|
@@ -1983,7 +1901,7 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
1983
1901
|
.string()
|
|
1984
1902
|
.optional()
|
|
1985
1903
|
.describe('When updated. Format: >2020-01-01'),
|
|
1986
|
-
|
|
1904
|
+
'merged-at': z
|
|
1987
1905
|
.string()
|
|
1988
1906
|
.optional()
|
|
1989
1907
|
.describe('When merged. Format: >2020-01-01'),
|
|
@@ -2004,6 +1922,49 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
2004
1922
|
.enum(['none', 'required', 'approved', 'changes_requested'])
|
|
2005
1923
|
.optional()
|
|
2006
1924
|
.describe('Review status filter'),
|
|
1925
|
+
app: z.string().optional().describe('GitHub App that created the PR'),
|
|
1926
|
+
archived: z
|
|
1927
|
+
.boolean()
|
|
1928
|
+
.optional()
|
|
1929
|
+
.describe('Include archived repositories'),
|
|
1930
|
+
comments: z
|
|
1931
|
+
.number()
|
|
1932
|
+
.optional()
|
|
1933
|
+
.describe('Comment count filter. Format: >10, <5, 5..10'),
|
|
1934
|
+
interactions: z
|
|
1935
|
+
.number()
|
|
1936
|
+
.optional()
|
|
1937
|
+
.describe('Total interactions (reactions + comments)'),
|
|
1938
|
+
'team-mentions': z
|
|
1939
|
+
.string()
|
|
1940
|
+
.optional()
|
|
1941
|
+
.describe('Team mentioned in PR (@org/team-name)'),
|
|
1942
|
+
reactions: z
|
|
1943
|
+
.number()
|
|
1944
|
+
.optional()
|
|
1945
|
+
.describe('Reaction count filter. Format: >10'),
|
|
1946
|
+
locked: z.boolean().optional().describe('Conversation locked status'),
|
|
1947
|
+
'no-assignee': z.boolean().optional().describe('PRs without assignee'),
|
|
1948
|
+
'no-label': z.boolean().optional().describe('PRs without labels'),
|
|
1949
|
+
'no-milestone': z
|
|
1950
|
+
.boolean()
|
|
1951
|
+
.optional()
|
|
1952
|
+
.describe('PRs without milestone'),
|
|
1953
|
+
'no-project': z.boolean().optional().describe('PRs not in projects'),
|
|
1954
|
+
label: z
|
|
1955
|
+
.union([z.string(), z.array(z.string())])
|
|
1956
|
+
.optional()
|
|
1957
|
+
.describe('Label names. Can be single string or array.'),
|
|
1958
|
+
milestone: z.string().optional().describe('Milestone title'),
|
|
1959
|
+
project: z.string().optional().describe('Project board owner/number'),
|
|
1960
|
+
visibility: z
|
|
1961
|
+
.enum(['public', 'private', 'internal'])
|
|
1962
|
+
.optional()
|
|
1963
|
+
.describe('Repository visibility'),
|
|
1964
|
+
match: z
|
|
1965
|
+
.enum(['title', 'body', 'comments'])
|
|
1966
|
+
.optional()
|
|
1967
|
+
.describe('Search scope. Default: title and body'),
|
|
2007
1968
|
limit: z
|
|
2008
1969
|
.number()
|
|
2009
1970
|
.int()
|
|
@@ -2138,16 +2099,16 @@ function buildGitHubPullRequestsAPICommand(params) {
|
|
|
2138
2099
|
queryParts.push(`${key}:${value}`);
|
|
2139
2100
|
});
|
|
2140
2101
|
// Special qualifiers
|
|
2141
|
-
if (params
|
|
2142
|
-
queryParts.push(`reviewed-by:${params
|
|
2143
|
-
if (params
|
|
2144
|
-
queryParts.push(`review-requested:${params
|
|
2102
|
+
if (params['reviewed-by'])
|
|
2103
|
+
queryParts.push(`reviewed-by:${params['reviewed-by']}`);
|
|
2104
|
+
if (params['review-requested'])
|
|
2105
|
+
queryParts.push(`review-requested:${params['review-requested']}`);
|
|
2145
2106
|
if (params.head)
|
|
2146
2107
|
queryParts.push(`head:${params.head}`);
|
|
2147
2108
|
if (params.base)
|
|
2148
2109
|
queryParts.push(`base:${params.base}`);
|
|
2149
|
-
if (params
|
|
2150
|
-
queryParts.push(`merged:${params
|
|
2110
|
+
if (params['merged-at'])
|
|
2111
|
+
queryParts.push(`merged:${params['merged-at']}`);
|
|
2151
2112
|
if (params.draft !== undefined)
|
|
2152
2113
|
queryParts.push(`draft:${params.draft}`);
|
|
2153
2114
|
if (params.checks)
|
|
@@ -2156,6 +2117,39 @@ function buildGitHubPullRequestsAPICommand(params) {
|
|
|
2156
2117
|
queryParts.push(`is:${params.merged ? 'merged' : 'unmerged'}`);
|
|
2157
2118
|
if (params.review)
|
|
2158
2119
|
queryParts.push(`review:${params.review}`);
|
|
2120
|
+
// Additional parameters
|
|
2121
|
+
if (params.app)
|
|
2122
|
+
queryParts.push(`app:${params.app}`);
|
|
2123
|
+
if (params.archived !== undefined)
|
|
2124
|
+
queryParts.push(`archived:${params.archived}`);
|
|
2125
|
+
if (params.comments)
|
|
2126
|
+
queryParts.push(`comments:${params.comments}`);
|
|
2127
|
+
if (params.interactions)
|
|
2128
|
+
queryParts.push(`interactions:${params.interactions}`);
|
|
2129
|
+
if (params.reactions)
|
|
2130
|
+
queryParts.push(`reactions:${params.reactions}`);
|
|
2131
|
+
if (params.locked)
|
|
2132
|
+
queryParts.push('is:locked');
|
|
2133
|
+
if (params.visibility)
|
|
2134
|
+
queryParts.push(`is:${params.visibility}`);
|
|
2135
|
+
if (params['team-mentions'])
|
|
2136
|
+
queryParts.push(`team:${params['team-mentions']}`);
|
|
2137
|
+
if (params['no-assignee'])
|
|
2138
|
+
queryParts.push('no:assignee');
|
|
2139
|
+
if (params['no-label'])
|
|
2140
|
+
queryParts.push('no:label');
|
|
2141
|
+
if (params['no-milestone'])
|
|
2142
|
+
queryParts.push('no:milestone');
|
|
2143
|
+
if (params['no-project'])
|
|
2144
|
+
queryParts.push('no:project');
|
|
2145
|
+
if (params.label) {
|
|
2146
|
+
const labels = Array.isArray(params.label) ? params.label : [params.label];
|
|
2147
|
+
labels.forEach(label => queryParts.push(`label:"${label}"`));
|
|
2148
|
+
}
|
|
2149
|
+
if (params.milestone)
|
|
2150
|
+
queryParts.push(`milestone:"${params.milestone}"`);
|
|
2151
|
+
if (params.project)
|
|
2152
|
+
queryParts.push(`project:${params.project}`);
|
|
2159
2153
|
// Add type qualifier to search only pull requests
|
|
2160
2154
|
queryParts.push('type:pr');
|
|
2161
2155
|
const query = queryParts.filter(Boolean).join(' ');
|
|
@@ -2185,11 +2179,27 @@ function buildGitHubPullRequestsAPICommand(params) {
|
|
|
2185
2179
|
* 4. Recent Quality:
|
|
2186
2180
|
* { stars: ">1000", created: ">2023-01-01", limit: 10 }
|
|
2187
2181
|
*
|
|
2182
|
+
* RESEARCH & EXPLORATION PATTERNS:
|
|
2183
|
+
*
|
|
2184
|
+
* 1. Topic-based Discovery (HIGHLY RECOMMENDED for unknown projects):
|
|
2185
|
+
* { topic: ["machine-learning", "nlp", "pytorch"], limit: 20 }
|
|
2186
|
+
* { topic: ["kubernetes", "monitoring"], stars: ">100", limit: 15 }
|
|
2187
|
+
*
|
|
2188
|
+
* 2. Exploratory Research Flow:
|
|
2189
|
+
* - Start with topics to discover repositories
|
|
2190
|
+
* - Then use githubViewRepoStructure to understand project layout
|
|
2191
|
+
* - Read README.md, docs/, and configuration files
|
|
2192
|
+
* - Finally use githubSearchCode for specific implementations
|
|
2193
|
+
*
|
|
2188
2194
|
* AVOID: OR queries + language filter, 5+ filters, multi-word OR
|
|
2189
2195
|
* TIP: Use limit parameter instead of adding more filters
|
|
2190
2196
|
*/
|
|
2191
2197
|
const GITHUB_SEARCH_REPOSITORIES_TOOL_NAME = 'githubSearchRepositories';
|
|
2192
|
-
const DESCRIPTION$
|
|
2198
|
+
const DESCRIPTION$3 = `Search GitHub repositories by name, description, topics, language, or organization. Find projects based on stars, forks, activity, and community metrics. Returns repository details including name, description, stars, language, and owner information for project discovery.
|
|
2199
|
+
|
|
2200
|
+
Search Syntax - ALL terms must be present (AND logic):
|
|
2201
|
+
Multiple search terms require ALL to be found in the repository (name, description, or README).
|
|
2202
|
+
Use quotes for exact phrase matching. Additional filters supported via parameters.`;
|
|
2193
2203
|
/**
|
|
2194
2204
|
* Extract owner/repo information from various query formats
|
|
2195
2205
|
*/
|
|
@@ -2222,70 +2232,88 @@ function extractOwnerRepoFromQuery(query) {
|
|
|
2222
2232
|
}
|
|
2223
2233
|
function registerSearchGitHubReposTool(server) {
|
|
2224
2234
|
server.registerTool(GITHUB_SEARCH_REPOSITORIES_TOOL_NAME, {
|
|
2225
|
-
description: DESCRIPTION$
|
|
2235
|
+
description: DESCRIPTION$3,
|
|
2226
2236
|
inputSchema: {
|
|
2227
2237
|
query: z
|
|
2228
2238
|
.string()
|
|
2229
2239
|
.optional()
|
|
2230
|
-
.describe('Search query
|
|
2240
|
+
.describe('Search query with AND logic between terms. Multiple words require ALL to be present. Use quotes for exact phrases.'),
|
|
2231
2241
|
// CORE FILTERS (GitHub CLI flags)
|
|
2232
2242
|
owner: z
|
|
2233
2243
|
.union([z.string(), z.array(z.string())])
|
|
2234
2244
|
.optional()
|
|
2235
|
-
.describe('Repository owner
|
|
2245
|
+
.describe('Repository owner/organization name(s) (e.g., "facebook", ["google", "microsoft"]). Search within specific organizations. Do NOT use owner/repo format - just the organization/username.'),
|
|
2236
2246
|
language: z
|
|
2237
2247
|
.string()
|
|
2238
2248
|
.optional()
|
|
2239
|
-
.describe('Programming language filter.
|
|
2249
|
+
.describe('Programming language filter. Filters repositories by primary language. Essential for language-specific searches.'),
|
|
2240
2250
|
stars: z
|
|
2241
2251
|
.union([
|
|
2242
2252
|
z.number().int().min(0),
|
|
2243
|
-
z
|
|
2253
|
+
z
|
|
2254
|
+
.string()
|
|
2255
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">1000", ">=500", "<100", "<=50", "10..100", or exact number "50"'),
|
|
2244
2256
|
])
|
|
2245
2257
|
.optional()
|
|
2246
|
-
.describe('
|
|
2258
|
+
.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).'),
|
|
2247
2259
|
topic: z
|
|
2248
2260
|
.union([z.string(), z.array(z.string())])
|
|
2249
2261
|
.optional()
|
|
2250
|
-
.describe('
|
|
2251
|
-
forks: z
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2262
|
+
.describe('Repository topics filter. Excellent for discovering projects and understanding repository ecosystems. Topics use kebab-case format.'),
|
|
2263
|
+
forks: z
|
|
2264
|
+
.union([
|
|
2265
|
+
z.number().int().min(0),
|
|
2266
|
+
z
|
|
2267
|
+
.string()
|
|
2268
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">100", ">=50", "<10", "<=5", "10..100", or exact number "5"'),
|
|
2269
|
+
])
|
|
2270
|
+
.optional()
|
|
2271
|
+
.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).'),
|
|
2272
|
+
// Match CLI parameter name exactly
|
|
2273
|
+
'number-topics': z
|
|
2274
|
+
.union([
|
|
2275
|
+
z.number().int().min(0),
|
|
2276
|
+
z
|
|
2277
|
+
.string()
|
|
2278
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">5", ">=3", "<10", "<=2", "3..10", or exact number "5"'),
|
|
2279
|
+
])
|
|
2255
2280
|
.optional()
|
|
2256
|
-
.describe('
|
|
2281
|
+
.describe('Number of topics filter. Format: ">5" (many topics), ">=3" (at least 3), "<10" (few topics), "1..3" (range), "5" (exact).'),
|
|
2257
2282
|
// QUALITY & STATE FILTERS
|
|
2258
2283
|
license: z
|
|
2259
2284
|
.union([z.string(), z.array(z.string())])
|
|
2260
2285
|
.optional()
|
|
2261
|
-
.describe('License filter.
|
|
2286
|
+
.describe('License filter.'),
|
|
2262
2287
|
archived: z
|
|
2263
2288
|
.boolean()
|
|
2264
2289
|
.optional()
|
|
2265
|
-
.describe('
|
|
2266
|
-
|
|
2290
|
+
.describe('Archive status filter. false (active repos only), true (archived repos only).'),
|
|
2291
|
+
'include-forks': z
|
|
2267
2292
|
.enum(['false', 'true', 'only'])
|
|
2268
2293
|
.optional()
|
|
2269
|
-
.describe('
|
|
2294
|
+
.describe('Fork inclusion. "false" (exclude forks), "true" (include forks), "only" (forks only).'),
|
|
2270
2295
|
visibility: z
|
|
2271
2296
|
.enum(['public', 'private', 'internal'])
|
|
2272
2297
|
.optional()
|
|
2273
|
-
.describe('Repository visibility
|
|
2298
|
+
.describe('Repository visibility.'),
|
|
2274
2299
|
// DATE & SIZE FILTERS
|
|
2275
2300
|
created: z
|
|
2276
2301
|
.string()
|
|
2302
|
+
.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
2303
|
.optional()
|
|
2278
|
-
.describe('
|
|
2304
|
+
.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).'),
|
|
2279
2305
|
updated: z
|
|
2280
2306
|
.string()
|
|
2307
|
+
.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
2308
|
.optional()
|
|
2282
|
-
.describe('
|
|
2309
|
+
.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).'),
|
|
2283
2310
|
size: z
|
|
2284
2311
|
.string()
|
|
2312
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid size format. Use: ">1000", ">=500", "<100", "<=50", "100..1000", or exact number "500"')
|
|
2285
2313
|
.optional()
|
|
2286
|
-
.describe('Repository size filter in KB. Format: ">1000", "<500".'),
|
|
2314
|
+
.describe('Repository size filter in KB. Format: ">1000" (large projects), ">=500" (medium-large), "<100" (small projects), "<=50" (tiny), "100..1000" (medium range), "500" (exact).'),
|
|
2287
2315
|
// COMMUNITY FILTERS - Match CLI parameter names exactly
|
|
2288
|
-
|
|
2316
|
+
'good-first-issues': z
|
|
2289
2317
|
.union([
|
|
2290
2318
|
z.number().int().min(0),
|
|
2291
2319
|
z
|
|
@@ -2293,8 +2321,8 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2293
2321
|
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: number, ">5", ">=10", "<20", "<=15", or "5..20"'),
|
|
2294
2322
|
])
|
|
2295
2323
|
.optional()
|
|
2296
|
-
.describe('Good first issues count.
|
|
2297
|
-
|
|
2324
|
+
.describe('Good first issues count. Format: ">5" (many beginner issues), "1..10" (some beginner issues).'),
|
|
2325
|
+
'help-wanted-issues': z
|
|
2298
2326
|
.union([
|
|
2299
2327
|
z.number().int().min(0),
|
|
2300
2328
|
z
|
|
@@ -2302,13 +2330,24 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2302
2330
|
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: number, ">5", ">=10", "<20", "<=15", or "5..20"'),
|
|
2303
2331
|
])
|
|
2304
2332
|
.optional()
|
|
2305
|
-
.describe('Help wanted issues count.
|
|
2306
|
-
followers: z
|
|
2307
|
-
|
|
2333
|
+
.describe('Help wanted issues count. Format: ">10" (many help wanted), "1..5" (some help wanted).'),
|
|
2334
|
+
followers: z
|
|
2335
|
+
.union([
|
|
2336
|
+
z.number().int().min(0),
|
|
2337
|
+
z
|
|
2338
|
+
.string()
|
|
2339
|
+
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">1000", ">=500", "<100", "<=50", "100..1000", or exact number "500"'),
|
|
2340
|
+
])
|
|
2341
|
+
.optional()
|
|
2342
|
+
.describe('Repository owner followers count. Format: ">1000" (popular developers), ">=500" (established developers), "<100" (smaller developers), "100..1000" (range).'),
|
|
2343
|
+
// SEARCH SCOPE - Match CLI exactly
|
|
2308
2344
|
match: z
|
|
2309
|
-
.
|
|
2345
|
+
.union([
|
|
2346
|
+
z.enum(['name', 'description', 'readme']),
|
|
2347
|
+
z.array(z.enum(['name', 'description', 'readme'])),
|
|
2348
|
+
])
|
|
2310
2349
|
.optional()
|
|
2311
|
-
.describe('Search scope
|
|
2350
|
+
.describe('Search scope. "name" (repository names only), "description" (descriptions only), "readme" (README content). Can be single value or array.'),
|
|
2312
2351
|
// SORTING & LIMITS - Match CLI defaults exactly
|
|
2313
2352
|
sort: z
|
|
2314
2353
|
.enum([
|
|
@@ -2320,7 +2359,7 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2320
2359
|
])
|
|
2321
2360
|
.optional()
|
|
2322
2361
|
.default('best-match')
|
|
2323
|
-
.describe('Sort criteria
|
|
2362
|
+
.describe('Sort criteria.'),
|
|
2324
2363
|
order: z
|
|
2325
2364
|
.enum(['asc', 'desc'])
|
|
2326
2365
|
.optional()
|
|
@@ -2333,7 +2372,7 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2333
2372
|
.max(100)
|
|
2334
2373
|
.optional()
|
|
2335
2374
|
.default(30)
|
|
2336
|
-
.describe('Maximum
|
|
2375
|
+
.describe('Maximum number of repositories to return (1-100).'),
|
|
2337
2376
|
},
|
|
2338
2377
|
annotations: {
|
|
2339
2378
|
title: 'GitHub Repository Search',
|
|
@@ -2534,13 +2573,13 @@ function buildGitHubReposSearchCommand(params) {
|
|
|
2534
2573
|
// CORE FILTERS
|
|
2535
2574
|
addArg('owner', 'owner', !hasEmbeddedQualifiers);
|
|
2536
2575
|
addArg('language', 'language', !hasEmbeddedQualifiers);
|
|
2537
|
-
addArg('forks', 'forks', !hasEmbeddedQualifiers);
|
|
2576
|
+
addArg('forks', 'forks', !hasEmbeddedQualifiers, value => typeof value === 'number' ? value.toString() : value.trim());
|
|
2538
2577
|
addArg('topic', 'topic', !hasEmbeddedQualifiers);
|
|
2539
|
-
addArg('
|
|
2578
|
+
addArg('number-topics', 'number-topics', true, value => typeof value === 'number' ? value.toString() : value.trim());
|
|
2540
2579
|
addArg('stars', 'stars', !hasEmbeddedQualifiers, value => typeof value === 'number' ? value.toString() : value.trim());
|
|
2541
2580
|
// QUALITY & STATE FILTERS
|
|
2542
2581
|
addArg('archived', 'archived');
|
|
2543
|
-
addArg('
|
|
2582
|
+
addArg('include-forks', 'include-forks');
|
|
2544
2583
|
addArg('visibility', 'visibility');
|
|
2545
2584
|
addArg('license', 'license');
|
|
2546
2585
|
// DATE & SIZE FILTERS
|
|
@@ -2548,9 +2587,9 @@ function buildGitHubReposSearchCommand(params) {
|
|
|
2548
2587
|
addArg('updated', 'updated');
|
|
2549
2588
|
addArg('size', 'size');
|
|
2550
2589
|
// COMMUNITY FILTERS
|
|
2551
|
-
addArg('
|
|
2552
|
-
addArg('
|
|
2553
|
-
addArg('followers', 'followers');
|
|
2590
|
+
addArg('good-first-issues', 'good-first-issues', true, value => typeof value === 'number' ? value.toString() : value);
|
|
2591
|
+
addArg('help-wanted-issues', 'help-wanted-issues', true, value => typeof value === 'number' ? value.toString() : value);
|
|
2592
|
+
addArg('followers', 'followers', true, value => typeof value === 'number' ? value.toString() : value.trim());
|
|
2554
2593
|
// SEARCH SCOPE
|
|
2555
2594
|
addArg('match', 'match');
|
|
2556
2595
|
// SORTING AND LIMITS
|
|
@@ -2563,8 +2602,258 @@ function buildGitHubReposSearchCommand(params) {
|
|
|
2563
2602
|
return { command: 'search', args };
|
|
2564
2603
|
}
|
|
2565
2604
|
|
|
2605
|
+
const GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME = 'githubViewRepoStructure';
|
|
2606
|
+
const DESCRIPTION$2 = `Browse GitHub repository file structure and navigate directories. Essential for exploring project layout, finding documentation, configuration files, and source code. Returns file/folder listings with size information. Automatically handles branch detection. Parameters: owner (required - GitHub username/org), repo (required - repository name), branch (required), path (optional - directory path).`;
|
|
2607
|
+
function registerViewRepositoryStructureTool(server) {
|
|
2608
|
+
server.registerTool(GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME, {
|
|
2609
|
+
description: DESCRIPTION$2,
|
|
2610
|
+
inputSchema: {
|
|
2611
|
+
owner: z
|
|
2612
|
+
.string()
|
|
2613
|
+
.min(1)
|
|
2614
|
+
.max(100)
|
|
2615
|
+
.regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/, 'Invalid GitHub username/org format')
|
|
2616
|
+
.describe('Repository owner/organization name (e.g., "facebook", "microsoft"). Do NOT include repository name.'),
|
|
2617
|
+
repo: z
|
|
2618
|
+
.string()
|
|
2619
|
+
.min(1)
|
|
2620
|
+
.max(100)
|
|
2621
|
+
.regex(/^[a-zA-Z0-9._-]+$/, 'Invalid repository name format')
|
|
2622
|
+
.describe(`Repository name under a organization. `),
|
|
2623
|
+
branch: z
|
|
2624
|
+
.string()
|
|
2625
|
+
.min(1)
|
|
2626
|
+
.max(255)
|
|
2627
|
+
.regex(/^[^\s]+$/, 'Branch name cannot contain spaces')
|
|
2628
|
+
.describe('Branch name (e.g., "main", "master", "develop"). Tool will automatically try default branch if specified branch is not found.'),
|
|
2629
|
+
path: z
|
|
2630
|
+
.string()
|
|
2631
|
+
.optional()
|
|
2632
|
+
.default('')
|
|
2633
|
+
.refine(path => !path.includes('..'), 'Path traversal not allowed')
|
|
2634
|
+
.refine(path => path.length <= 500, 'Path too long')
|
|
2635
|
+
.describe('Directory path within repository (e.g., "src", "docs", "src/components"). Leave empty for root directory. Do NOT start with slash.'),
|
|
2636
|
+
},
|
|
2637
|
+
annotations: {
|
|
2638
|
+
title: 'GitHub Repository Explorer',
|
|
2639
|
+
readOnlyHint: true,
|
|
2640
|
+
destructiveHint: false,
|
|
2641
|
+
idempotentHint: true,
|
|
2642
|
+
openWorldHint: true,
|
|
2643
|
+
},
|
|
2644
|
+
}, async (args) => {
|
|
2645
|
+
try {
|
|
2646
|
+
const result = await viewRepositoryStructure(args);
|
|
2647
|
+
return result;
|
|
2648
|
+
}
|
|
2649
|
+
catch (error) {
|
|
2650
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
2651
|
+
return createResult({
|
|
2652
|
+
error: `Failed to explore repository. ${errorMessage}. Verify repository exists and is accessible`,
|
|
2653
|
+
});
|
|
2654
|
+
}
|
|
2655
|
+
});
|
|
2656
|
+
}
|
|
2657
|
+
/**
|
|
2658
|
+
* Views the structure of a GitHub repository at a specific path.
|
|
2659
|
+
* Optimized for code analysis workflows with smart defaults and clear errors.
|
|
2660
|
+
*/
|
|
2661
|
+
async function viewRepositoryStructure(params) {
|
|
2662
|
+
const cacheKey = generateCacheKey('gh-repo-structure', params);
|
|
2663
|
+
return withCache(cacheKey, async () => {
|
|
2664
|
+
const { owner, repo, branch, path = '' } = params;
|
|
2665
|
+
try {
|
|
2666
|
+
// Clean up path
|
|
2667
|
+
const cleanPath = path.startsWith('/') ? path.substring(1) : path;
|
|
2668
|
+
// Try the requested branch first, then fallback to main/master
|
|
2669
|
+
const branchesToTry = await getSmartBranchFallback(owner, repo, branch);
|
|
2670
|
+
let items = [];
|
|
2671
|
+
let usedBranch = branch;
|
|
2672
|
+
let lastError = null;
|
|
2673
|
+
let attemptCount = 0;
|
|
2674
|
+
const maxAttempts = branchesToTry.length;
|
|
2675
|
+
const triedBranches = [];
|
|
2676
|
+
for (const tryBranch of branchesToTry) {
|
|
2677
|
+
if (attemptCount >= maxAttempts)
|
|
2678
|
+
break;
|
|
2679
|
+
attemptCount++;
|
|
2680
|
+
triedBranches.push(tryBranch);
|
|
2681
|
+
try {
|
|
2682
|
+
const apiPath = `/repos/${owner}/${repo}/contents/${cleanPath}?ref=${tryBranch}`;
|
|
2683
|
+
const result = await executeGitHubCommand('api', [apiPath], {
|
|
2684
|
+
cache: false,
|
|
2685
|
+
});
|
|
2686
|
+
if (!result.isError) {
|
|
2687
|
+
const execResult = JSON.parse(result.content[0].text);
|
|
2688
|
+
const apiItems = execResult.result;
|
|
2689
|
+
items = Array.isArray(apiItems) ? apiItems : [apiItems];
|
|
2690
|
+
usedBranch = tryBranch;
|
|
2691
|
+
break;
|
|
2692
|
+
}
|
|
2693
|
+
else {
|
|
2694
|
+
lastError = new Error(result.content[0].text);
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
catch (error) {
|
|
2698
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2699
|
+
continue;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
if (items.length === 0) {
|
|
2703
|
+
// Check repository existence only after content fetch fails
|
|
2704
|
+
const repoCheckResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
|
|
2705
|
+
cache: false,
|
|
2706
|
+
});
|
|
2707
|
+
if (repoCheckResult.isError) {
|
|
2708
|
+
const repoErrorMsg = repoCheckResult.content[0].text;
|
|
2709
|
+
if (repoErrorMsg.includes('404')) {
|
|
2710
|
+
return createResult({
|
|
2711
|
+
error: `Repository "${owner}/${repo}" not found. It might have been deleted, renamed, or made private. Use github_search_code to find current location.`,
|
|
2712
|
+
});
|
|
2713
|
+
}
|
|
2714
|
+
else if (repoErrorMsg.includes('403')) {
|
|
2715
|
+
return createResult({
|
|
2716
|
+
error: `Repository "${owner}/${repo}" exists but access is denied. Repository might be private or archived. Use api_status_check to verify permissions.`,
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
const errorMsg = lastError?.message || 'Unknown error';
|
|
2721
|
+
if (errorMsg.includes('404') || errorMsg.includes('Not Found')) {
|
|
2722
|
+
if (path) {
|
|
2723
|
+
const searchSuggestion = await suggestPathSearchFallback(owner, path);
|
|
2724
|
+
return createResult({
|
|
2725
|
+
error: `Path "${path}" not found in any branch (tried: ${triedBranches.join(', ')}).${searchSuggestion}`,
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
else {
|
|
2729
|
+
return createResult({
|
|
2730
|
+
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.`,
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
else if (errorMsg.includes('403') || errorMsg.includes('Forbidden')) {
|
|
2735
|
+
return createResult({
|
|
2736
|
+
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.`,
|
|
2737
|
+
});
|
|
2738
|
+
}
|
|
2739
|
+
else {
|
|
2740
|
+
const searchSuggestion = path
|
|
2741
|
+
? await suggestPathSearchFallback(owner, path)
|
|
2742
|
+
: '';
|
|
2743
|
+
return createResult({
|
|
2744
|
+
error: `Failed to access "${owner}/${repo}": ${errorMsg}. Check network connection and repository permissions.${searchSuggestion}`,
|
|
2745
|
+
});
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
// Limit total items to 100 for efficiency
|
|
2749
|
+
const limitedItems = items.slice(0, 100);
|
|
2750
|
+
// Sort: directories first, then alphabetically
|
|
2751
|
+
limitedItems.sort((a, b) => {
|
|
2752
|
+
if (a.type !== b.type) {
|
|
2753
|
+
return a.type === 'dir' ? -1 : 1;
|
|
2754
|
+
}
|
|
2755
|
+
return a.name.localeCompare(b.name);
|
|
2756
|
+
});
|
|
2757
|
+
// Create simplified, token-efficient structure
|
|
2758
|
+
const files = limitedItems
|
|
2759
|
+
.filter(item => item.type === 'file')
|
|
2760
|
+
.map(item => ({
|
|
2761
|
+
name: item.name,
|
|
2762
|
+
size: item.size,
|
|
2763
|
+
url: item.path, // Use path for fetching
|
|
2764
|
+
}));
|
|
2765
|
+
const folders = limitedItems
|
|
2766
|
+
.filter(item => item.type === 'dir')
|
|
2767
|
+
.map(item => ({
|
|
2768
|
+
name: item.name,
|
|
2769
|
+
url: item.path, // Use path for browsing
|
|
2770
|
+
}));
|
|
2771
|
+
return createResult({
|
|
2772
|
+
data: {
|
|
2773
|
+
repository: `${owner}/${repo}`,
|
|
2774
|
+
branch: usedBranch,
|
|
2775
|
+
path: cleanPath || '/',
|
|
2776
|
+
githubBasePath: `https://api.github.com/repos/${owner}/${repo}/contents/`,
|
|
2777
|
+
files: {
|
|
2778
|
+
count: files.length,
|
|
2779
|
+
files: files,
|
|
2780
|
+
},
|
|
2781
|
+
folders: {
|
|
2782
|
+
count: folders.length,
|
|
2783
|
+
folders: folders,
|
|
2784
|
+
},
|
|
2785
|
+
},
|
|
2786
|
+
});
|
|
2787
|
+
}
|
|
2788
|
+
catch (error) {
|
|
2789
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2790
|
+
return createResult({
|
|
2791
|
+
error: `Failed to access repository "${owner}/${repo}": ${errorMessage}. Verify repository name, permissions, and network connection.`,
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
});
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Smart branch detection with automatic fallback to common branch names.
|
|
2798
|
+
* Now includes more comprehensive branch detection and better error handling.
|
|
2799
|
+
*/
|
|
2800
|
+
async function getSmartBranchFallback(owner, repo, requestedBranch) {
|
|
2801
|
+
const branches = new Set([requestedBranch]);
|
|
2802
|
+
try {
|
|
2803
|
+
// Try to get repository info to find default branch
|
|
2804
|
+
const repoInfoResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
|
|
2805
|
+
cache: false,
|
|
2806
|
+
});
|
|
2807
|
+
if (!repoInfoResult.isError) {
|
|
2808
|
+
const execResult = JSON.parse(repoInfoResult.content[0].text);
|
|
2809
|
+
const repoData = execResult.result;
|
|
2810
|
+
const defaultBranch = repoData.default_branch;
|
|
2811
|
+
if (defaultBranch) {
|
|
2812
|
+
branches.add(defaultBranch);
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
catch {
|
|
2817
|
+
// If we can't get repo info, proceed with standard fallbacks
|
|
2818
|
+
}
|
|
2819
|
+
// Add only main/master as fallback branches
|
|
2820
|
+
const commonBranches = ['main', 'master'];
|
|
2821
|
+
commonBranches.forEach(branch => branches.add(branch));
|
|
2822
|
+
// Convert Set back to array, with requested branch first
|
|
2823
|
+
const branchesArray = Array.from(branches);
|
|
2824
|
+
branchesArray.sort((a, b) => {
|
|
2825
|
+
if (a === requestedBranch)
|
|
2826
|
+
return -1;
|
|
2827
|
+
if (b === requestedBranch)
|
|
2828
|
+
return 1;
|
|
2829
|
+
return 0;
|
|
2830
|
+
});
|
|
2831
|
+
return branchesArray;
|
|
2832
|
+
}
|
|
2833
|
+
// Helper function to suggest path search strategy
|
|
2834
|
+
async function suggestPathSearchFallback(owner, path) {
|
|
2835
|
+
try {
|
|
2836
|
+
// Extract last path segment and try to find in same organization
|
|
2837
|
+
const pathSegment = path.split('/').pop() || path;
|
|
2838
|
+
const searchResult = await executeGitHubCommand('api', [
|
|
2839
|
+
`/search/code?q=${encodeURIComponent(pathSegment)}+in:path+org:${owner}`,
|
|
2840
|
+
], { cache: false });
|
|
2841
|
+
if (!searchResult.isError) {
|
|
2842
|
+
const results = JSON.parse(searchResult.content[0].text);
|
|
2843
|
+
if (results.total_count > 0) {
|
|
2844
|
+
const firstMatch = results.items[0];
|
|
2845
|
+
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}"`;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
catch {
|
|
2850
|
+
// Fallback to generic message if search fails
|
|
2851
|
+
}
|
|
2852
|
+
return ` Try these searches:\n1. github_search_code with query="${path.split('/').pop()}" owner="${owner}"\n2. github_search_code with query="path:${path}"`;
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2566
2855
|
const NPM_PACKAGE_SEARCH_TOOL_NAME = 'npmPackageSearch';
|
|
2567
|
-
const DESCRIPTION$1 = `Search NPM packages
|
|
2856
|
+
const DESCRIPTION$1 = `Search NPM packages by name or functionality keywords. Supports multiple search terms for comprehensive package discovery. Returns package name, version, description, keywords, and repository information. Best for finding JavaScript/TypeScript libraries and tools.`;
|
|
2568
2857
|
const MAX_DESCRIPTION_LENGTH = 100;
|
|
2569
2858
|
const MAX_KEYWORDS = 10;
|
|
2570
2859
|
function registerNpmSearchTool(server) {
|
|
@@ -2573,7 +2862,7 @@ function registerNpmSearchTool(server) {
|
|
|
2573
2862
|
inputSchema: {
|
|
2574
2863
|
queries: z
|
|
2575
2864
|
.union([z.string(), z.array(z.string())])
|
|
2576
|
-
.describe('Search terms for packages.
|
|
2865
|
+
.describe('Search terms for NPM packages (e.g., "react hooks", ["typescript", "eslint"], "data visualization"). Use functionality keywords rather than exact package names for best results.'),
|
|
2577
2866
|
searchLimit: z
|
|
2578
2867
|
.number()
|
|
2579
2868
|
.int()
|
|
@@ -2667,13 +2956,12 @@ function parseNpmSearchOutput(output) {
|
|
|
2667
2956
|
return packages.map(normalizePackage);
|
|
2668
2957
|
}
|
|
2669
2958
|
catch (error) {
|
|
2670
|
-
logger.warn('Failed to parse NPM search results:', error);
|
|
2671
2959
|
return [];
|
|
2672
2960
|
}
|
|
2673
2961
|
}
|
|
2674
2962
|
|
|
2675
2963
|
const NPM_VIEW_PACKAGE_TOOL_NAME = 'npmViewPackage';
|
|
2676
|
-
const DESCRIPTION = `
|
|
2964
|
+
const DESCRIPTION = `Get detailed NPM package information including version, description, repository URL, size, and download statistics. Essential for understanding package details before installation. Returns optimized metadata for package evaluation and code navigation.`;
|
|
2677
2965
|
function registerNpmViewPackageTool(server) {
|
|
2678
2966
|
server.registerTool(NPM_VIEW_PACKAGE_TOOL_NAME, {
|
|
2679
2967
|
description: DESCRIPTION,
|
|
@@ -2681,7 +2969,7 @@ function registerNpmViewPackageTool(server) {
|
|
|
2681
2969
|
packageName: z
|
|
2682
2970
|
.string()
|
|
2683
2971
|
.min(1)
|
|
2684
|
-
.describe('NPM package name (e.g., "react", "express", "@types/node")'),
|
|
2972
|
+
.describe('NPM package name (e.g., "react", "express", "@types/node"). Include @ prefix for scoped packages.'),
|
|
2685
2973
|
},
|
|
2686
2974
|
annotations: {
|
|
2687
2975
|
title: 'NPM Package Analyzer',
|
|
@@ -2831,133 +3119,125 @@ using gh cli for github and npm cli for packages.
|
|
|
2831
3119
|
|
|
2832
3120
|
CRITICAL SEARCH PRINCIPLES:
|
|
2833
3121
|
|
|
2834
|
-
|
|
2835
|
-
a) START BROAD: Begin with simple, general terms (1-2 words max)
|
|
2836
|
-
b) ANALYZE RESULTS: Learn from what you find
|
|
2837
|
-
c) REFINE GRADUALLY: Add filters only after understanding the landscape
|
|
3122
|
+
PROGRESSIVE SEARCH STRATEGY (MOST IMPORTANT):
|
|
3123
|
+
a) START BROAD: Begin with simple, general queries and terms (1-2 words max)
|
|
3124
|
+
b) ANALYZE RESULTS: Learn from what you find
|
|
3125
|
+
c) REFINE GRADUALLY: Add filters only after understanding the landscape and if needed (only if the user asks for it explicitly)
|
|
2838
3126
|
d) MULTIPLE ANGLES: Try different search terms if first approach yields no results
|
|
2839
3127
|
|
|
2840
|
-
|
|
2841
|
-
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
3rd search: owner:facebook with specific terms (TARGETED)
|
|
2845
|
-
|
|
2846
|
-
- User asks about "authentication libraries"
|
|
2847
|
-
1st search: "auth" or "authentication" (BROAD)
|
|
2848
|
-
2nd search: Add language filter based on results
|
|
2849
|
-
3rd search: Focus on specific owners/topics found
|
|
3128
|
+
HANDLING NO RESULTS:
|
|
3129
|
+
- NEVER give up after one failed search.
|
|
3130
|
+
- Act on fallbacks messages and try to understand the user's intent.
|
|
3131
|
+
- Try progressively BROADER terms (simplify queries and remove filters)
|
|
2850
3132
|
|
|
2851
|
-
|
|
2852
|
-
- NEVER give up after one failed search
|
|
2853
|
-
- Try progressively BROADER terms
|
|
2854
|
-
- Remove ALL filters and try core keywords
|
|
2855
|
-
- Search for related concepts (e.g., "auth" → "login" → "session")
|
|
2856
|
-
- Check different owners/organizations
|
|
2857
|
-
- For code search: try searching in popular repos first
|
|
2858
|
-
|
|
2859
|
-
## 4. SMART FILTER USAGE:
|
|
3133
|
+
SMART FILTER USAGE:
|
|
2860
3134
|
- NO FILTERS on first search (unless user specifies)
|
|
2861
3135
|
- Add ONE filter at a time based on results
|
|
2862
|
-
- Common progression: query → +language → +stars → +owner
|
|
3136
|
+
- Common progression: query → +language → +stars → +owner (only if user asks for it explicitly)
|
|
2863
3137
|
- Reserve complex filters for final refinement
|
|
2864
3138
|
|
|
2865
|
-
|
|
2866
|
-
- Use repository search to find relevant repos FIRST
|
|
2867
|
-
- Then use code search within those repos
|
|
2868
|
-
- Check npm packages for JavaScript/TypeScript queries
|
|
2869
|
-
- Verify access with api_status_check for private repos
|
|
2870
|
-
|
|
2871
|
-
## 6. RESEARCH BEST PRACTICES:
|
|
3139
|
+
RESEARCH BEST PRACTICES:
|
|
2872
3140
|
- Conduct COMPREHENSIVE research with multiple searches
|
|
2873
3141
|
- Learn from each search to improve the next
|
|
2874
3142
|
- Provide context about your search strategy
|
|
2875
3143
|
- Always verify technical details with actual code
|
|
2876
3144
|
|
|
2877
|
-
|
|
3145
|
+
COMPLEX ANALYSIS PATTERNS:
|
|
2878
3146
|
|
|
2879
|
-
|
|
2880
|
-
1. Search repos separately: "
|
|
2881
|
-
2.
|
|
2882
|
-
3.
|
|
3147
|
+
Multi-terms Comparison
|
|
3148
|
+
1. Search repos separately: "repoA", then "repoB"
|
|
3149
|
+
2. Review repositories structure and files
|
|
3150
|
+
3. Review code and documentation
|
|
2883
3151
|
4. Compare similar functionalities across repos
|
|
3152
|
+
5. Use filters and flags to narrow down the results
|
|
2884
3153
|
|
|
2885
|
-
|
|
2886
|
-
1. Start broad: "state" or "store"
|
|
2887
|
-
2. Identify major libraries: Redux, Zustand, Jotai
|
|
2888
|
-
3. Search implementation patterns: "reducer", "atom", "selector"
|
|
2889
|
-
4. Analyze each approach separately, then compare
|
|
2890
|
-
|
|
2891
|
-
### Evolution Tracking (e.g., Feature History):
|
|
3154
|
+
Evolution Tracking (e.g., Feature History):
|
|
2892
3155
|
1. Use commit search with broad terms first
|
|
2893
3156
|
2. Narrow by date ranges progressively
|
|
2894
3157
|
3. Track changes in specific files over time
|
|
2895
3158
|
4. Identify key contributors and their patterns
|
|
2896
3159
|
|
|
2897
|
-
|
|
2898
|
-
1. Search for benchmarks: "benchmark", "perf", "performance"
|
|
2899
|
-
2. Look for optimization commits: "optimize", "faster", "improve"
|
|
2900
|
-
3. Find profiling code: "profile", "measure", "timing"
|
|
2901
|
-
4. Compare implementation strategies
|
|
2902
|
-
|
|
2903
|
-
## 8. CROSS-REPOSITORY INTELLIGENCE:
|
|
3160
|
+
CROSS-REPOSITORY INTELLIGENCE:
|
|
2904
3161
|
- When comparing frameworks, search EACH separately first
|
|
2905
3162
|
- Build mental model of each codebase structure
|
|
2906
|
-
- Use discovered patterns to refine searches
|
|
2907
|
-
|
|
3163
|
+
- Use discovered patterns to refine searches and connect findings across repositories for insights
|
|
3164
|
+
|
|
3165
|
+
RESEARCH WORKFLOW BEST PRACTICES:
|
|
3166
|
+
|
|
3167
|
+
Discovery Phase (Unknown Projects/Topics):
|
|
3168
|
+
- START with ${GITHUB_SEARCH_REPOSITORIES_TOOL_NAME} using TOPICS
|
|
3169
|
+
- Topics are underutilized but extremely powerful for discovery
|
|
3170
|
+
- Example: { topic: ["react", "typescript", "testing"], stars: ">500" }
|
|
3171
|
+
|
|
3172
|
+
Understanding Phase
|
|
3173
|
+
- ALWAYS use ${GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME} first to verify repository exists
|
|
3174
|
+
- CRITICAL: Many failures are due to incorrect repository names - verify before proceeding
|
|
3175
|
+
- Check README.md, docs/, configuration files
|
|
3176
|
+
- Understand project structure before searching code
|
|
3177
|
+
- Never make assumptions about repository names or file locations - always verify
|
|
3178
|
+
|
|
3179
|
+
Implementation Search (Specific Code):
|
|
3180
|
+
- Fetch importnat files using ${GITHUB_GET_FILE_CONTENT_TOOL_NAME}
|
|
3181
|
+
- Use ${GITHUB_SEARCH_CODE_TOOL_NAME} with NARROW queries
|
|
3182
|
+
- Start precise, broaden only if needed
|
|
3183
|
+
- Use exact phrases when you know patterns
|
|
3184
|
+
- Verify dependences and imports
|
|
3185
|
+
|
|
3186
|
+
File Access Validation Workflow:
|
|
3187
|
+
1. VERIFY repository name first with ${GITHUB_SEARCH_CODE_TOOL_NAME} if unsure
|
|
3188
|
+
2. Use ${GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME} to confirm repository structure
|
|
3189
|
+
3. Navigate to parent directory first to understand layout
|
|
3190
|
+
4. Only then use ${GITHUB_GET_FILE_CONTENT_TOOL_NAME} with confirmed paths
|
|
3191
|
+
5. Most "file not found" errors are due to incorrect repository names
|
|
2908
3192
|
|
|
2909
|
-
|
|
3193
|
+
TOOL-SPECIFIC BEST PRACTICES:
|
|
2910
3194
|
|
|
2911
|
-
|
|
3195
|
+
${API_STATUS_CHECK_TOOL_NAME}:
|
|
2912
3196
|
- Run FIRST when dealing with private repositories
|
|
2913
3197
|
- Use organizations list to scope searches
|
|
2914
3198
|
- Verify authentication before extensive searches
|
|
2915
3199
|
|
|
2916
|
-
|
|
3200
|
+
${GITHUB_SEARCH_REPOSITORIES_TOOL_NAME}:
|
|
2917
3201
|
- Start with topic/language, add stars/forks filters later
|
|
2918
3202
|
- Use date ranges for trending analysis
|
|
2919
3203
|
- Combine multiple searches for comprehensive discovery
|
|
2920
3204
|
|
|
2921
|
-
|
|
3205
|
+
${GITHUB_SEARCH_CODE_TOOL_NAME}:
|
|
2922
3206
|
- Begin with function/class names, not full signatures
|
|
2923
3207
|
- Use extension filters for targeted searches
|
|
2924
3208
|
- Try partial matches before exact phrases
|
|
2925
3209
|
|
|
2926
|
-
|
|
3210
|
+
${GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME}:
|
|
2927
3211
|
- Navigate from root, then drill down
|
|
2928
3212
|
- Use for understanding project organization
|
|
2929
3213
|
- Check common paths: src/, lib/, packages/
|
|
2930
3214
|
|
|
2931
|
-
|
|
3215
|
+
${GITHUB_GET_FILE_CONTENT_TOOL_NAME}:
|
|
2932
3216
|
- Verify file paths with repo structure first
|
|
2933
3217
|
- Use for implementation details and documentation
|
|
2934
|
-
- Remember 300KB limit for large files
|
|
2935
3218
|
|
|
2936
|
-
|
|
2937
|
-
- Search by feature keywords
|
|
2938
|
-
- Use author filter for contributor analysis
|
|
3219
|
+
${GITHUB_SEARCH_COMMITS_TOOL_NAME}:
|
|
3220
|
+
- Search by feature keywords
|
|
2939
3221
|
- Date ranges help track feature evolution
|
|
2940
3222
|
|
|
2941
|
-
|
|
2942
|
-
- Search for problem descriptions
|
|
3223
|
+
${GITHUB_SEARCH_ISSUES_TOOL_NAME} & ${GITHUB_SEARCH_PULL_REQUESTS_TOOL_NAME}:
|
|
3224
|
+
- Search for problem descriptions
|
|
2943
3225
|
- Use state filters progressively
|
|
2944
|
-
- Labels reveal project categorization
|
|
2945
3226
|
|
|
2946
|
-
|
|
2947
|
-
-
|
|
2948
|
-
- Search multiple related terms in parallel
|
|
2949
|
-
- Aggregate results for comprehensive view
|
|
3227
|
+
${NPM_PACKAGE_SEARCH_TOOL_NAME}:
|
|
3228
|
+
- Search npm package in registry - use one term at a time (partial or exact)
|
|
2950
3229
|
|
|
2951
|
-
|
|
2952
|
-
-
|
|
2953
|
-
- Review exports for API understanding
|
|
2954
|
-
- Use version history to gauge stability
|
|
3230
|
+
${NPM_VIEW_PACKAGE_TOOL_NAME}:
|
|
3231
|
+
- Returns essential data like github path and package metadata like dependencies, exports...
|
|
2955
3232
|
|
|
2956
|
-
|
|
3233
|
+
CHAIN OF THOUGHT OPTIMIZATION:
|
|
2957
3234
|
- Plan search sequence before executing
|
|
2958
3235
|
- Document reasoning for each search refinement
|
|
2959
3236
|
- Build knowledge progressively, don't jump to specifics
|
|
2960
3237
|
- Validate findings with multiple sources
|
|
3238
|
+
- Ask user for clarification if needed
|
|
3239
|
+
- Learn from results and refine search strategy
|
|
3240
|
+
- Output high level summary of the search and results based on data and not assumptions
|
|
2961
3241
|
`;
|
|
2962
3242
|
|
|
2963
3243
|
const SERVER_CONFIG = {
|
|
@@ -2996,39 +3276,31 @@ function registerAllTools(server) {
|
|
|
2996
3276
|
},
|
|
2997
3277
|
{ name: NPM_VIEW_PACKAGE_TOOL_NAME, fn: registerNpmViewPackageTool },
|
|
2998
3278
|
];
|
|
2999
|
-
logger.info(`Registering ${toolRegistrations.length} tools...`);
|
|
3000
3279
|
let successCount = 0;
|
|
3001
3280
|
for (const tool of toolRegistrations) {
|
|
3002
3281
|
try {
|
|
3003
|
-
logger.debug(`Registering tool: ${tool.name}`);
|
|
3004
3282
|
tool.fn(server);
|
|
3005
3283
|
successCount++;
|
|
3006
|
-
logger.info(`✓ Successfully registered: ${tool.name}`);
|
|
3007
3284
|
}
|
|
3008
3285
|
catch (error) {
|
|
3009
|
-
logger.error(`✗ Failed to register ${tool.name}:`, error);
|
|
3010
3286
|
// Continue with other tools instead of failing completely
|
|
3011
3287
|
}
|
|
3012
3288
|
}
|
|
3013
3289
|
if (successCount === 0) {
|
|
3014
3290
|
throw new Error('No tools were successfully registered');
|
|
3015
3291
|
}
|
|
3016
|
-
logger.info(`All tools registration completed - ${successCount}/${toolRegistrations.length} successful`);
|
|
3017
3292
|
}
|
|
3018
3293
|
async function startServer() {
|
|
3019
3294
|
try {
|
|
3020
|
-
logger.info('Creating MCP server...');
|
|
3021
3295
|
const server = new McpServer(SERVER_CONFIG);
|
|
3022
3296
|
registerAllTools(server);
|
|
3023
3297
|
const transport = new StdioServerTransport();
|
|
3024
3298
|
await server.connect(transport);
|
|
3025
|
-
logger.info('=== Server Connected Successfully ===');
|
|
3026
3299
|
// Ensure all buffered output is sent
|
|
3027
3300
|
process.stdout.uncork();
|
|
3028
3301
|
process.stderr.uncork();
|
|
3029
|
-
const gracefulShutdown = async (
|
|
3302
|
+
const gracefulShutdown = async () => {
|
|
3030
3303
|
try {
|
|
3031
|
-
logger.info(`Received ${signal}, shutting down gracefully...`);
|
|
3032
3304
|
clearAllCache();
|
|
3033
3305
|
// Give server time to close properly
|
|
3034
3306
|
await Promise.race([
|
|
@@ -3038,35 +3310,30 @@ async function startServer() {
|
|
|
3038
3310
|
process.exit(0);
|
|
3039
3311
|
}
|
|
3040
3312
|
catch (error) {
|
|
3041
|
-
logger.error('Error during shutdown:', error);
|
|
3042
3313
|
process.exit(1);
|
|
3043
3314
|
}
|
|
3044
3315
|
};
|
|
3045
3316
|
// Handle process signals
|
|
3046
|
-
process.on('SIGINT',
|
|
3047
|
-
process.on('SIGTERM',
|
|
3317
|
+
process.on('SIGINT', gracefulShutdown);
|
|
3318
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
3048
3319
|
// Handle stdin close (important for MCP)
|
|
3049
3320
|
process.stdin.on('close', async () => {
|
|
3050
|
-
await gracefulShutdown(
|
|
3321
|
+
await gracefulShutdown();
|
|
3051
3322
|
});
|
|
3052
3323
|
// Handle uncaught errors
|
|
3053
|
-
process.on('uncaughtException',
|
|
3054
|
-
|
|
3055
|
-
gracefulShutdown('UNCAUGHT_EXCEPTION');
|
|
3324
|
+
process.on('uncaughtException', () => {
|
|
3325
|
+
gracefulShutdown();
|
|
3056
3326
|
});
|
|
3057
|
-
process.on('unhandledRejection', (
|
|
3058
|
-
|
|
3059
|
-
gracefulShutdown('UNHANDLED_REJECTION');
|
|
3327
|
+
process.on('unhandledRejection', () => {
|
|
3328
|
+
gracefulShutdown();
|
|
3060
3329
|
});
|
|
3061
3330
|
// Keep process alive
|
|
3062
3331
|
process.stdin.resume();
|
|
3063
3332
|
}
|
|
3064
3333
|
catch (error) {
|
|
3065
|
-
logger.error('Error details:', error);
|
|
3066
3334
|
process.exit(1);
|
|
3067
3335
|
}
|
|
3068
3336
|
}
|
|
3069
|
-
startServer().catch(
|
|
3070
|
-
logger.error('Error:', error);
|
|
3337
|
+
startServer().catch(() => {
|
|
3071
3338
|
process.exit(1);
|
|
3072
3339
|
});
|