octocode-mcp 2.3.7 → 2.3.9
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 +1502 -696
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -168,11 +168,13 @@ function escapeWindowsCmdArg(arg) {
|
|
|
168
168
|
* Preserves AND search logic by not over-escaping space-separated terms
|
|
169
169
|
*/
|
|
170
170
|
function escapeUnixShellArg(arg, isGitHubQuery) {
|
|
171
|
-
// For GitHub search queries, we need to preserve AND logic
|
|
171
|
+
// For GitHub search queries, we need to preserve AND logic and quoted phrases
|
|
172
172
|
if (isGitHubQuery) {
|
|
173
|
-
// If the query contains quotes, preserve them for
|
|
173
|
+
// If the query contains quotes, we need to preserve them for GitHub CLI
|
|
174
|
+
// but escape the entire argument for the shell
|
|
174
175
|
if (arg.includes('"')) {
|
|
175
176
|
// Use single quotes to wrap the entire query while preserving internal quotes
|
|
177
|
+
// This allows GitHub CLI to see: "quoted phrase" other terms
|
|
176
178
|
return `'${arg.replace(/'/g, "'\"'\"'")}'`;
|
|
177
179
|
}
|
|
178
180
|
// For space-separated terms (AND search), only escape if absolutely necessary
|
|
@@ -230,18 +232,30 @@ async function executeGitHubCommand(command, args = [], options = {}) {
|
|
|
230
232
|
// Get shell configuration
|
|
231
233
|
const shellConfig = getShellConfig(options.windowsShell);
|
|
232
234
|
// Build command with validated prefix and properly escaped arguments
|
|
233
|
-
// For GitHub search commands,
|
|
234
|
-
//
|
|
235
|
+
// For GitHub search commands, we need to distinguish between:
|
|
236
|
+
// 1. Main query (index 1) - needs special escaping for AND logic
|
|
237
|
+
// 2. CLI flags (--flag=value) - standard escaping
|
|
238
|
+
// 3. Search qualifiers (key:value) - minimal escaping
|
|
235
239
|
const escapedArgs = args.map((arg, index) => {
|
|
236
240
|
const isMainQueryArgument = command === 'search' && index === 1;
|
|
241
|
+
const isCliFlag = arg.startsWith('--');
|
|
237
242
|
const isGitHubQualifier = command === 'search' &&
|
|
238
243
|
index > 1 &&
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
|
|
244
|
+
!isCliFlag &&
|
|
245
|
+
(arg.includes(':') || arg.startsWith('('));
|
|
246
|
+
// CLI flags like --language=javascript, --repo=owner/repo need standard escaping
|
|
247
|
+
if (isCliFlag) {
|
|
248
|
+
return escapeShellArg(arg, shellConfig.type, false);
|
|
249
|
+
}
|
|
250
|
+
// GitHub search qualifiers need special handling
|
|
251
|
+
// Most qualifiers can be passed as-is, but those with shell metacharacters need escaping
|
|
244
252
|
if (isGitHubQualifier) {
|
|
253
|
+
// Check if the qualifier contains shell metacharacters that need escaping
|
|
254
|
+
if (/[<>&|;`$\\]/.test(arg)) {
|
|
255
|
+
// Escape qualifiers that contain shell metacharacters like size:<1000, size:>500
|
|
256
|
+
return escapeShellArg(arg, shellConfig.type, false);
|
|
257
|
+
}
|
|
258
|
+
// Safe qualifiers like "language:typescript", "user:microsoft" can be passed as-is
|
|
245
259
|
return arg;
|
|
246
260
|
}
|
|
247
261
|
return escapeShellArg(arg, shellConfig.type, isMainQueryArgument);
|
|
@@ -303,6 +317,14 @@ async function executeCommand(fullCommand, type, options = {}, shellConfig) {
|
|
|
303
317
|
stderr.trim() !== '';
|
|
304
318
|
if (shouldTreatAsError) {
|
|
305
319
|
const errorType = type === 'npm' ? 'NPM command error' : 'GitHub CLI command error';
|
|
320
|
+
// Enhanced error messaging for common GitHub issues
|
|
321
|
+
if (type === 'github' && stderr.includes('404')) {
|
|
322
|
+
const isRepoNotFound = stderr.includes('Not Found');
|
|
323
|
+
const enhancedMessage = isRepoNotFound
|
|
324
|
+
? `${stderr}\n\nThis is often due to incorrect repository name. Use github_search_code to find the correct repository.`
|
|
325
|
+
: stderr;
|
|
326
|
+
return createErrorResult(errorType, new Error(enhancedMessage));
|
|
327
|
+
}
|
|
306
328
|
return createErrorResult(errorType, new Error(stderr));
|
|
307
329
|
}
|
|
308
330
|
// Try to parse stdout as JSON, fallback to string if not possible
|
|
@@ -445,24 +467,16 @@ const ERROR_MESSAGES = {
|
|
|
445
467
|
ISSUE_SEARCH_FAILED: 'Issue search failed - check auth or simplify query',
|
|
446
468
|
PR_SEARCH_FAILED: 'PR search failed - check access and query syntax',
|
|
447
469
|
PACKAGE_SEARCH_FAILED: 'Package search failed - try different keywords',
|
|
448
|
-
// GitHub CLI errors
|
|
449
|
-
CLI_INVALID_RESPONSE: 'GitHub CLI invalid response - check "gh version" and update',
|
|
450
|
-
// Timeout errors
|
|
451
|
-
SEARCH_TIMEOUT: 'Search timed out - add filters (language, owner) or use specific terms',
|
|
452
470
|
// Query validation errors
|
|
453
471
|
QUERY_TOO_LONG: 'Query too long (max 256 chars) - simplify search terms',
|
|
454
472
|
QUERY_REQUIRED: 'Query required - provide search keywords',
|
|
455
|
-
EMPTY_QUERY: 'Empty query - try "useState", "authentication", or language:python',
|
|
456
|
-
QUERY_TOO_LONG_1000: 'Query too long (max 1000 chars) - use key terms like "error handling"',
|
|
457
|
-
REPO_OR_OWNER_NOT_FOUND: 'Repository/owner not found - check spelling, visibility, and permissions',
|
|
458
|
-
// Query syntax errors
|
|
459
|
-
INVALID_QUERY_SYNTAX: 'Invalid syntax - Boolean operators not supported, use quotes for phrases',
|
|
460
|
-
// Size/format validation errors
|
|
461
|
-
INVALID_SIZE_FORMAT: 'Invalid size format - use >N, <N, or N..M without quotes',
|
|
462
|
-
INVALID_SEARCH_SCOPE: 'Invalid scope - use "file" for content, "path" for filenames',
|
|
463
473
|
// API Status check errors
|
|
464
474
|
API_STATUS_CHECK_FAILED: 'API Status Check Failed',
|
|
465
|
-
|
|
475
|
+
// NPM Errors
|
|
476
|
+
NPM_PACKAGE_NOT_FOUND: 'Package not found on NPM registry',
|
|
477
|
+
NPM_CONNECTION_FAILED: 'NPM registry connection failed',
|
|
478
|
+
NPM_CLI_ERROR: 'NPM CLI issue detected',
|
|
479
|
+
NPM_PERMISSION_ERROR: 'NPM permission issue'};
|
|
466
480
|
const SUGGESTIONS = {
|
|
467
481
|
// Code search suggestions
|
|
468
482
|
CODE_SEARCH_NO_RESULTS: `No results found. Try this progression:
|
|
@@ -486,6 +500,31 @@ const SUGGESTIONS = {
|
|
|
486
500
|
2. Break compound words: "authlib" → "auth"
|
|
487
501
|
3. Search by use case: "user login" vs "authentication"
|
|
488
502
|
4. Try category terms: "framework", "tool", "library"`,
|
|
503
|
+
// File Content Suggestions
|
|
504
|
+
FILE_NOT_FOUND_RECOVERY: `Quick fixes:
|
|
505
|
+
• Use github_view_repo_structure to verify path exists
|
|
506
|
+
• Check for typos in file path
|
|
507
|
+
• Try different branch (main/master/develop)`,
|
|
508
|
+
FILE_TOO_LARGE_RECOVERY: `Alternative strategies:
|
|
509
|
+
• Use github_search_code to search within the file
|
|
510
|
+
• Download directly from GitHub
|
|
511
|
+
• Use github_view_repo_structure to find smaller related files
|
|
512
|
+
• Look for configuration or summary files instead`,
|
|
513
|
+
// Repository Suggestions
|
|
514
|
+
REPOSITORY_NOT_FOUND_RECOVERY: `This is often due to incorrect repository name. Steps to resolve:
|
|
515
|
+
1. Use github_search_repositories to find the correct repository
|
|
516
|
+
2. Verify the exact repository name
|
|
517
|
+
3. Check if the repository might have been renamed or moved`,
|
|
518
|
+
// NPM Suggestions
|
|
519
|
+
NPM_DISCOVERY_STRATEGIES: `Discovery strategies:
|
|
520
|
+
• Functional search: "validation", "testing", "charts"
|
|
521
|
+
• Ecosystem search: "react", "typescript", "node"
|
|
522
|
+
• Use github_search_repositories for related projects`,
|
|
523
|
+
NPM_PACKAGE_NAME_ALTERNATIVES: `Try these alternatives:
|
|
524
|
+
• Try with dashes instead of underscores
|
|
525
|
+
• Try without dashes
|
|
526
|
+
• Try scoped package format
|
|
527
|
+
• Use npm_package_search for discovery`,
|
|
489
528
|
};
|
|
490
529
|
// Helper function to get error message with context-specific suggestions
|
|
491
530
|
function getErrorWithSuggestion(options) {
|
|
@@ -543,9 +582,348 @@ function createSearchFailedError(type = 'code') {
|
|
|
543
582
|
return ERROR_MESSAGES.SEARCH_FAILED;
|
|
544
583
|
}
|
|
545
584
|
}
|
|
585
|
+
function createNpmPackageNotFoundError(packageName) {
|
|
586
|
+
const suggestions = [];
|
|
587
|
+
if (packageName.includes('_')) {
|
|
588
|
+
suggestions.push(`• Try with dashes: "${packageName.replace(/_/g, '-')}"`);
|
|
589
|
+
}
|
|
590
|
+
if (packageName.includes('-')) {
|
|
591
|
+
suggestions.push(`• Try without dashes: "${packageName.replace(/-/g, '')}"`);
|
|
592
|
+
}
|
|
593
|
+
if (!packageName.includes('/') && packageName.length > 2) {
|
|
594
|
+
suggestions.push(`• Try scoped format: "@${packageName.slice(0, 3)}/${packageName}"`);
|
|
595
|
+
}
|
|
596
|
+
return `${ERROR_MESSAGES.NPM_PACKAGE_NOT_FOUND}: "${packageName}"
|
|
597
|
+
|
|
598
|
+
${suggestions.length > 0 ? suggestions.join('\n') + '\n\n' : ''}${SUGGESTIONS.NPM_PACKAGE_NAME_ALTERNATIVES}
|
|
599
|
+
|
|
600
|
+
${SUGGESTIONS.NPM_DISCOVERY_STRATEGIES}`;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Tool relationship map for standardized cross-tool suggestions
|
|
605
|
+
* Defines how tools connect and when to suggest alternatives
|
|
606
|
+
*/
|
|
607
|
+
const TOOL_NAMES = {
|
|
608
|
+
API_STATUS_CHECK: 'api_status_check',
|
|
609
|
+
GITHUB_FETCH_CONTENT: 'github_fetch_content',
|
|
610
|
+
GITHUB_SEARCH_CODE: 'github_search_code',
|
|
611
|
+
GITHUB_SEARCH_COMMITS: 'github_search_commits',
|
|
612
|
+
GITHUB_SEARCH_ISSUES: 'github_search_issues',
|
|
613
|
+
GITHUB_SEARCH_PULL_REQUESTS: 'github_search_pull_requests',
|
|
614
|
+
GITHUB_SEARCH_REPOSITORIES: 'github_search_repositories',
|
|
615
|
+
GITHUB_VIEW_REPO_STRUCTURE: 'github_view_repo_structure',
|
|
616
|
+
NPM_PACKAGE_SEARCH: 'npm_package_search',
|
|
617
|
+
NPM_VIEW_PACKAGE: 'npm_view_package',
|
|
618
|
+
};
|
|
619
|
+
const TOOL_RELATIONSHIPS = {
|
|
620
|
+
[TOOL_NAMES.NPM_PACKAGE_SEARCH]: {
|
|
621
|
+
fallbackTools: [
|
|
622
|
+
{
|
|
623
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_REPOSITORIES,
|
|
624
|
+
reason: 'to find repositories by topic or language',
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_CODE,
|
|
628
|
+
reason: 'to search for package usage examples',
|
|
629
|
+
},
|
|
630
|
+
],
|
|
631
|
+
nextSteps: [
|
|
632
|
+
{
|
|
633
|
+
tool: TOOL_NAMES.NPM_VIEW_PACKAGE,
|
|
634
|
+
reason: 'to get detailed package information and repository URL',
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
tool: TOOL_NAMES.GITHUB_VIEW_REPO_STRUCTURE,
|
|
638
|
+
reason: 'to explore the package source code structure',
|
|
639
|
+
},
|
|
640
|
+
],
|
|
641
|
+
},
|
|
642
|
+
[TOOL_NAMES.NPM_VIEW_PACKAGE]: {
|
|
643
|
+
fallbackTools: [
|
|
644
|
+
{
|
|
645
|
+
tool: TOOL_NAMES.NPM_PACKAGE_SEARCH,
|
|
646
|
+
reason: 'to discover similar packages',
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_REPOSITORIES,
|
|
650
|
+
reason: 'to find the repository directly',
|
|
651
|
+
},
|
|
652
|
+
],
|
|
653
|
+
nextSteps: [
|
|
654
|
+
{
|
|
655
|
+
tool: TOOL_NAMES.GITHUB_VIEW_REPO_STRUCTURE,
|
|
656
|
+
reason: 'to explore repository structure',
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
tool: TOOL_NAMES.GITHUB_FETCH_CONTENT,
|
|
660
|
+
reason: 'to read specific files like README or package.json',
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_CODE,
|
|
664
|
+
reason: 'to find usage examples',
|
|
665
|
+
},
|
|
666
|
+
],
|
|
667
|
+
},
|
|
668
|
+
[TOOL_NAMES.GITHUB_SEARCH_REPOSITORIES]: {
|
|
669
|
+
fallbackTools: [
|
|
670
|
+
{
|
|
671
|
+
tool: TOOL_NAMES.NPM_PACKAGE_SEARCH,
|
|
672
|
+
reason: 'if searching for packages or libraries',
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_CODE,
|
|
676
|
+
reason: 'to search within repository contents',
|
|
677
|
+
},
|
|
678
|
+
],
|
|
679
|
+
nextSteps: [
|
|
680
|
+
{
|
|
681
|
+
tool: TOOL_NAMES.GITHUB_VIEW_REPO_STRUCTURE,
|
|
682
|
+
reason: 'to explore repository contents',
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_ISSUES,
|
|
686
|
+
reason: 'to check open issues and discussions',
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
tool: TOOL_NAMES.NPM_VIEW_PACKAGE,
|
|
690
|
+
reason: 'if repository is an NPM package',
|
|
691
|
+
},
|
|
692
|
+
],
|
|
693
|
+
},
|
|
694
|
+
[TOOL_NAMES.GITHUB_SEARCH_CODE]: {
|
|
695
|
+
fallbackTools: [
|
|
696
|
+
{
|
|
697
|
+
tool: TOOL_NAMES.NPM_PACKAGE_SEARCH,
|
|
698
|
+
reason: 'if searching for package implementations',
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_REPOSITORIES,
|
|
702
|
+
reason: 'to find repositories by topic',
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
tool: TOOL_NAMES.API_STATUS_CHECK,
|
|
706
|
+
reason: 'if no results (might be private repos)',
|
|
707
|
+
condition: 'no_results',
|
|
708
|
+
},
|
|
709
|
+
],
|
|
710
|
+
nextSteps: [
|
|
711
|
+
{
|
|
712
|
+
tool: TOOL_NAMES.GITHUB_FETCH_CONTENT,
|
|
713
|
+
reason: 'to view full file contents',
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
tool: TOOL_NAMES.GITHUB_VIEW_REPO_STRUCTURE,
|
|
717
|
+
reason: 'to explore repository structure',
|
|
718
|
+
},
|
|
719
|
+
],
|
|
720
|
+
prerequisites: [
|
|
721
|
+
{
|
|
722
|
+
tool: TOOL_NAMES.GITHUB_VIEW_REPO_STRUCTURE,
|
|
723
|
+
reason: 'to verify file paths before searching',
|
|
724
|
+
},
|
|
725
|
+
],
|
|
726
|
+
},
|
|
727
|
+
[TOOL_NAMES.GITHUB_FETCH_CONTENT]: {
|
|
728
|
+
fallbackTools: [
|
|
729
|
+
{
|
|
730
|
+
tool: TOOL_NAMES.GITHUB_VIEW_REPO_STRUCTURE,
|
|
731
|
+
reason: 'to verify correct file path',
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_CODE,
|
|
735
|
+
reason: 'to search for similar files',
|
|
736
|
+
},
|
|
737
|
+
],
|
|
738
|
+
nextSteps: [
|
|
739
|
+
{
|
|
740
|
+
tool: TOOL_NAMES.GITHUB_VIEW_REPO_STRUCTURE,
|
|
741
|
+
reason: 'to explore related files',
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_CODE,
|
|
745
|
+
reason: 'to find usage examples',
|
|
746
|
+
},
|
|
747
|
+
],
|
|
748
|
+
prerequisites: [
|
|
749
|
+
{
|
|
750
|
+
tool: TOOL_NAMES.GITHUB_VIEW_REPO_STRUCTURE,
|
|
751
|
+
reason: 'to verify file exists and get correct path',
|
|
752
|
+
},
|
|
753
|
+
],
|
|
754
|
+
},
|
|
755
|
+
[TOOL_NAMES.GITHUB_VIEW_REPO_STRUCTURE]: {
|
|
756
|
+
fallbackTools: [
|
|
757
|
+
{
|
|
758
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_REPOSITORIES,
|
|
759
|
+
reason: 'to find the correct repository',
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
tool: TOOL_NAMES.API_STATUS_CHECK,
|
|
763
|
+
reason: 'if access denied (check authentication)',
|
|
764
|
+
condition: 'access_denied',
|
|
765
|
+
},
|
|
766
|
+
],
|
|
767
|
+
nextSteps: [
|
|
768
|
+
{
|
|
769
|
+
tool: TOOL_NAMES.GITHUB_FETCH_CONTENT,
|
|
770
|
+
reason: 'to read specific files',
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_CODE,
|
|
774
|
+
reason: 'to search within the repository',
|
|
775
|
+
},
|
|
776
|
+
],
|
|
777
|
+
},
|
|
778
|
+
[TOOL_NAMES.GITHUB_SEARCH_COMMITS]: {
|
|
779
|
+
fallbackTools: [
|
|
780
|
+
{
|
|
781
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_REPOSITORIES,
|
|
782
|
+
reason: 'to find repositories first',
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_ISSUES,
|
|
786
|
+
reason: 'to find related discussions',
|
|
787
|
+
},
|
|
788
|
+
],
|
|
789
|
+
nextSteps: [
|
|
790
|
+
{
|
|
791
|
+
tool: TOOL_NAMES.GITHUB_FETCH_CONTENT,
|
|
792
|
+
reason: 'to view files changed in commits',
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_CODE,
|
|
796
|
+
reason: 'to find current implementation',
|
|
797
|
+
},
|
|
798
|
+
],
|
|
799
|
+
},
|
|
800
|
+
[TOOL_NAMES.GITHUB_SEARCH_ISSUES]: {
|
|
801
|
+
fallbackTools: [
|
|
802
|
+
{
|
|
803
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS,
|
|
804
|
+
reason: 'to find related PRs',
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_REPOSITORIES,
|
|
808
|
+
reason: 'to find the repository first',
|
|
809
|
+
},
|
|
810
|
+
],
|
|
811
|
+
nextSteps: [
|
|
812
|
+
{
|
|
813
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS,
|
|
814
|
+
reason: 'to find solutions or implementations',
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_CODE,
|
|
818
|
+
reason: 'to find related code',
|
|
819
|
+
},
|
|
820
|
+
],
|
|
821
|
+
},
|
|
822
|
+
[TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS]: {
|
|
823
|
+
fallbackTools: [
|
|
824
|
+
{
|
|
825
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_ISSUES,
|
|
826
|
+
reason: 'to find related issues',
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_COMMITS,
|
|
830
|
+
reason: 'to find related commits',
|
|
831
|
+
},
|
|
832
|
+
],
|
|
833
|
+
nextSteps: [
|
|
834
|
+
{
|
|
835
|
+
tool: TOOL_NAMES.GITHUB_FETCH_CONTENT,
|
|
836
|
+
reason: 'to view PR changes',
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_CODE,
|
|
840
|
+
reason: 'to find current implementation',
|
|
841
|
+
},
|
|
842
|
+
],
|
|
843
|
+
},
|
|
844
|
+
[TOOL_NAMES.API_STATUS_CHECK]: {
|
|
845
|
+
fallbackTools: [],
|
|
846
|
+
nextSteps: [
|
|
847
|
+
{
|
|
848
|
+
tool: TOOL_NAMES.GITHUB_SEARCH_REPOSITORIES,
|
|
849
|
+
reason: 'to search accessible repositories',
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
tool: TOOL_NAMES.NPM_PACKAGE_SEARCH,
|
|
853
|
+
reason: 'to search public packages',
|
|
854
|
+
},
|
|
855
|
+
],
|
|
856
|
+
},
|
|
857
|
+
};
|
|
858
|
+
/**
|
|
859
|
+
* Get tool suggestions based on current context
|
|
860
|
+
*/
|
|
861
|
+
function getToolSuggestions(currentTool, context) {
|
|
862
|
+
const relationships = TOOL_RELATIONSHIPS[currentTool];
|
|
863
|
+
if (!relationships) {
|
|
864
|
+
return { fallback: [], nextSteps: [], prerequisites: [] };
|
|
865
|
+
}
|
|
866
|
+
// Filter fallback tools based on context
|
|
867
|
+
const fallback = relationships.fallbackTools.filter(item => {
|
|
868
|
+
if (!item.condition)
|
|
869
|
+
return true;
|
|
870
|
+
switch (item.condition) {
|
|
871
|
+
case 'no_results':
|
|
872
|
+
return context.errorType === 'no_results';
|
|
873
|
+
case 'access_denied':
|
|
874
|
+
return context.errorType === 'access_denied';
|
|
875
|
+
default:
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
// Return next steps only if operation was successful
|
|
880
|
+
const nextSteps = context.hasResults ? relationships.nextSteps : [];
|
|
881
|
+
return {
|
|
882
|
+
fallback,
|
|
883
|
+
nextSteps,
|
|
884
|
+
prerequisites: relationships.prerequisites || [],
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Shared validation utilities for MCP tools
|
|
890
|
+
* Centralized validation logic to reduce duplication across tools
|
|
891
|
+
*/
|
|
892
|
+
/**
|
|
893
|
+
* Extracts owner/repo from various query formats
|
|
894
|
+
* Handles: owner/repo, https://github.com/owner/repo, github.com/owner/repo
|
|
895
|
+
*/
|
|
896
|
+
/**
|
|
897
|
+
* Creates a standardized tool suggestion message
|
|
898
|
+
*/
|
|
899
|
+
function createToolSuggestion(currentTool, suggestedTools) {
|
|
900
|
+
if (suggestedTools.length === 0) {
|
|
901
|
+
return '';
|
|
902
|
+
}
|
|
903
|
+
const suggestions = suggestedTools
|
|
904
|
+
.map(({ tool, reason }) => `• Use ${tool} ${reason}`)
|
|
905
|
+
.join('\n');
|
|
906
|
+
return `\n\nCurrent tool: ${currentTool}\nAlternative tools:\n${suggestions}`;
|
|
907
|
+
}
|
|
546
908
|
|
|
547
909
|
const API_STATUS_CHECK_TOOL_NAME = 'apiStatusCheck';
|
|
548
|
-
const DESCRIPTION$9 = `
|
|
910
|
+
const DESCRIPTION$9 = `Verify API connections and discover available organizations. FIRST STEP for private repository research.
|
|
911
|
+
|
|
912
|
+
AUTHENTICATION VERIFICATION:
|
|
913
|
+
- Check GitHub and NPM CLI authentication status
|
|
914
|
+
- Troubleshoot access issues before extensive searches
|
|
915
|
+
- Verify API connectivity and permissions
|
|
916
|
+
|
|
917
|
+
ORGANIZATION DISCOVERY:
|
|
918
|
+
- List available GitHub organizations for scoped searches
|
|
919
|
+
- Identify accessible private repositories
|
|
920
|
+
- Guide repository search strategy based on permissions
|
|
921
|
+
|
|
922
|
+
WORKFLOW OPTIMIZATION:
|
|
923
|
+
- Run first when dealing with private/organizational repositories
|
|
924
|
+
- Prevents access errors in subsequent tool usage
|
|
925
|
+
- Informs search scope and strategy decisions
|
|
926
|
+
- Essential for comprehensive organizational research`;
|
|
549
927
|
// Helper function to parse execution results with proper typing
|
|
550
928
|
function parseExecResult(result) {
|
|
551
929
|
if (!result.isError && result.content?.[0]?.text) {
|
|
@@ -651,6 +1029,19 @@ function registerApiStatusCheckTool(server) {
|
|
|
651
1029
|
}
|
|
652
1030
|
npmConnected = false;
|
|
653
1031
|
}
|
|
1032
|
+
const { nextSteps } = getToolSuggestions(TOOL_NAMES.API_STATUS_CHECK, {
|
|
1033
|
+
hasResults: true,
|
|
1034
|
+
});
|
|
1035
|
+
const hints = [
|
|
1036
|
+
'Use user organizations to search private repositories when requested - verify access by checking query and repository structure',
|
|
1037
|
+
];
|
|
1038
|
+
// Add tool suggestions as hints
|
|
1039
|
+
if (nextSteps.length > 0) {
|
|
1040
|
+
hints.push('Next steps:');
|
|
1041
|
+
nextSteps.forEach(({ tool, reason }) => {
|
|
1042
|
+
hints.push(`- ${tool}: ${reason}`);
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
654
1045
|
return createResult({
|
|
655
1046
|
data: {
|
|
656
1047
|
login: {
|
|
@@ -662,55 +1053,77 @@ function registerApiStatusCheckTool(server) {
|
|
|
662
1053
|
connected: npmConnected,
|
|
663
1054
|
registry: registry || 'https://registry.npmjs.org/',
|
|
664
1055
|
},
|
|
665
|
-
hints
|
|
666
|
-
'Use user organizations to search private repositories when requested - verify access by checking query and repository structure',
|
|
667
|
-
],
|
|
1056
|
+
hints,
|
|
668
1057
|
},
|
|
669
1058
|
},
|
|
670
1059
|
});
|
|
671
1060
|
}
|
|
672
1061
|
catch (error) {
|
|
1062
|
+
const { nextSteps } = getToolSuggestions(TOOL_NAMES.API_STATUS_CHECK, {
|
|
1063
|
+
});
|
|
1064
|
+
const toolSuggestions = createToolSuggestion(TOOL_NAMES.API_STATUS_CHECK, nextSteps);
|
|
673
1065
|
return createResult({
|
|
674
|
-
error:
|
|
1066
|
+
error: getErrorWithSuggestion({
|
|
1067
|
+
baseError: [
|
|
1068
|
+
ERROR_MESSAGES.API_STATUS_CHECK_FAILED,
|
|
1069
|
+
`Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1070
|
+
'',
|
|
1071
|
+
'This usually indicates a system configuration issue. Please verify GitHub CLI and NPM are properly installed.',
|
|
1072
|
+
],
|
|
1073
|
+
suggestion: toolSuggestions,
|
|
1074
|
+
}),
|
|
675
1075
|
});
|
|
676
1076
|
}
|
|
677
1077
|
});
|
|
678
1078
|
}
|
|
679
1079
|
|
|
680
|
-
const
|
|
681
|
-
const DESCRIPTION$8 = `
|
|
682
|
-
|
|
683
|
-
|
|
1080
|
+
const GITHUB_GET_FILE_CONTENT_TOOL_NAME = 'githubGetFileContent';
|
|
1081
|
+
const DESCRIPTION$8 = `Access GitHub file content for implementation analysis. USE AFTER repository structure validation.
|
|
1082
|
+
|
|
1083
|
+
IMPLEMENTATION ANALYSIS:
|
|
1084
|
+
- Examine configuration files, documentation, key source code
|
|
1085
|
+
- Understand actual implementations and patterns
|
|
1086
|
+
- Extract technical details and architecture decisions
|
|
1087
|
+
|
|
1088
|
+
VALIDATION REQUIREMENTS:
|
|
1089
|
+
- ALWAYS verify repository structure first with githubViewRepoStructure
|
|
1090
|
+
- Confirm file paths exist before accessing
|
|
1091
|
+
- Navigate from known structure to specific files
|
|
1092
|
+
|
|
1093
|
+
CONTENT CAPABILITIES:
|
|
1094
|
+
- Handles text files up to 300KB efficiently
|
|
1095
|
+
- Automatic branch fallback (main/master)
|
|
1096
|
+
- Provides decoded content with metadata
|
|
1097
|
+
- Optimized for code analysis workflows`;
|
|
1098
|
+
function registerFetchGitHubFileContentTool(server) {
|
|
1099
|
+
server.registerTool(GITHUB_GET_FILE_CONTENT_TOOL_NAME, {
|
|
684
1100
|
description: DESCRIPTION$8,
|
|
685
1101
|
inputSchema: {
|
|
686
1102
|
owner: z
|
|
687
1103
|
.string()
|
|
688
1104
|
.min(1)
|
|
689
1105
|
.max(100)
|
|
690
|
-
.regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])
|
|
691
|
-
.describe(`Repository owner/
|
|
1106
|
+
.regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/)
|
|
1107
|
+
.describe(`Repository owner/organization name (e.g., 'facebook', 'microsoft'). Do NOT include repository name here.`),
|
|
692
1108
|
repo: z
|
|
693
1109
|
.string()
|
|
694
1110
|
.min(1)
|
|
695
1111
|
.max(100)
|
|
696
|
-
.regex(/^[a-zA-Z0-9._-]
|
|
697
|
-
.describe(
|
|
1112
|
+
.regex(/^[a-zA-Z0-9._-]+$/)
|
|
1113
|
+
.describe(`Repository name only (e.g., 'react', 'vscode'). Do NOT include owner/org prefix.`),
|
|
698
1114
|
branch: z
|
|
699
1115
|
.string()
|
|
700
1116
|
.min(1)
|
|
701
1117
|
.max(255)
|
|
702
|
-
.regex(/^[^\s]
|
|
703
|
-
.describe(
|
|
704
|
-
|
|
1118
|
+
.regex(/^[^\s]+$/)
|
|
1119
|
+
.describe(`Branch name (e.g., 'main', 'master'). Tool will automatically try 'main' and 'master' if specified branch is not found.`),
|
|
1120
|
+
filePath: z
|
|
705
1121
|
.string()
|
|
706
|
-
.
|
|
707
|
-
.
|
|
708
|
-
.refine(path => !path.includes('..'), 'Path traversal not allowed')
|
|
709
|
-
.refine(path => path.length <= 500, 'Path too long')
|
|
710
|
-
.describe('Directory path within repository. Leave empty for root.'),
|
|
1122
|
+
.min(1)
|
|
1123
|
+
.describe(`File path from repository root (e.g., 'src/index.js', 'README.md', 'docs/api.md'). Do NOT start with slash.`),
|
|
711
1124
|
},
|
|
712
1125
|
annotations: {
|
|
713
|
-
title: 'GitHub
|
|
1126
|
+
title: 'GitHub File Content - Direct Access',
|
|
714
1127
|
readOnlyHint: true,
|
|
715
1128
|
destructiveHint: false,
|
|
716
1129
|
idempotentHint: true,
|
|
@@ -718,63 +1131,28 @@ function registerViewRepositoryStructureTool(server) {
|
|
|
718
1131
|
},
|
|
719
1132
|
}, async (args) => {
|
|
720
1133
|
try {
|
|
721
|
-
const result = await
|
|
1134
|
+
const result = await fetchGitHubFileContent(args);
|
|
722
1135
|
return result;
|
|
723
1136
|
}
|
|
724
1137
|
catch (error) {
|
|
725
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
726
1138
|
return createResult({
|
|
727
|
-
error:
|
|
1139
|
+
error: 'Failed to fetch file. Verify path with github_get_contents first',
|
|
728
1140
|
});
|
|
729
1141
|
}
|
|
730
1142
|
});
|
|
731
1143
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
* Optimized for code analysis workflows with smart defaults and clear errors.
|
|
735
|
-
*/
|
|
736
|
-
async function viewRepositoryStructure(params) {
|
|
737
|
-
const cacheKey = generateCacheKey('gh-repo-structure', params);
|
|
1144
|
+
async function fetchGitHubFileContent(params) {
|
|
1145
|
+
const cacheKey = generateCacheKey('gh-file-content', params);
|
|
738
1146
|
return withCache(cacheKey, async () => {
|
|
739
|
-
const { owner, repo, branch,
|
|
1147
|
+
const { owner, repo, branch, filePath } = params;
|
|
740
1148
|
try {
|
|
741
|
-
//
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
let attemptCount = 0;
|
|
749
|
-
const maxAttempts = branchesToTry.length;
|
|
750
|
-
const triedBranches = [];
|
|
751
|
-
for (const tryBranch of branchesToTry) {
|
|
752
|
-
if (attemptCount >= maxAttempts)
|
|
753
|
-
break;
|
|
754
|
-
attemptCount++;
|
|
755
|
-
triedBranches.push(tryBranch);
|
|
756
|
-
try {
|
|
757
|
-
const apiPath = `/repos/${owner}/${repo}/contents/${cleanPath}?ref=${tryBranch}`;
|
|
758
|
-
const result = await executeGitHubCommand('api', [apiPath], {
|
|
759
|
-
cache: false,
|
|
760
|
-
});
|
|
761
|
-
if (!result.isError) {
|
|
762
|
-
const execResult = JSON.parse(result.content[0].text);
|
|
763
|
-
const apiItems = execResult.result;
|
|
764
|
-
items = Array.isArray(apiItems) ? apiItems : [apiItems];
|
|
765
|
-
usedBranch = tryBranch;
|
|
766
|
-
break;
|
|
767
|
-
}
|
|
768
|
-
else {
|
|
769
|
-
lastError = new Error(result.content[0].text);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
catch (error) {
|
|
773
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
774
|
-
continue;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
if (items.length === 0) {
|
|
1149
|
+
// Try to fetch file content directly
|
|
1150
|
+
const apiPath = `/repos/${owner}/${repo}/contents/${filePath}`;
|
|
1151
|
+
const result = await executeGitHubCommand('api', [apiPath], {
|
|
1152
|
+
cache: false,
|
|
1153
|
+
});
|
|
1154
|
+
if (result.isError) {
|
|
1155
|
+
const errorMsg = result.content[0].text;
|
|
778
1156
|
// Check repository existence only after content fetch fails
|
|
779
1157
|
const repoCheckResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
|
|
780
1158
|
cache: false,
|
|
@@ -783,7 +1161,7 @@ async function viewRepositoryStructure(params) {
|
|
|
783
1161
|
const repoErrorMsg = repoCheckResult.content[0].text;
|
|
784
1162
|
if (repoErrorMsg.includes('404')) {
|
|
785
1163
|
return createResult({
|
|
786
|
-
error: `Repository "${owner}/${repo}" not found.
|
|
1164
|
+
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`,
|
|
787
1165
|
});
|
|
788
1166
|
}
|
|
789
1167
|
else if (repoErrorMsg.includes('403')) {
|
|
@@ -792,277 +1170,102 @@ async function viewRepositoryStructure(params) {
|
|
|
792
1170
|
});
|
|
793
1171
|
}
|
|
794
1172
|
}
|
|
795
|
-
|
|
796
|
-
if (errorMsg.includes('404')
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1173
|
+
// Try fallback branches if original branch fails
|
|
1174
|
+
if (errorMsg.includes('404') &&
|
|
1175
|
+
branch !== 'main' &&
|
|
1176
|
+
branch !== 'master') {
|
|
1177
|
+
const fallbackBranches = ['main', 'master'];
|
|
1178
|
+
const triedBranches = [branch];
|
|
1179
|
+
for (const fallbackBranch of fallbackBranches) {
|
|
1180
|
+
if (triedBranches.includes(fallbackBranch))
|
|
1181
|
+
continue;
|
|
1182
|
+
triedBranches.push(fallbackBranch);
|
|
1183
|
+
const fallbackPath = `/repos/${owner}/${repo}/contents/${filePath}?ref=${fallbackBranch}`;
|
|
1184
|
+
const fallbackResult = await executeGitHubCommand('api', [fallbackPath], {
|
|
1185
|
+
cache: false,
|
|
806
1186
|
});
|
|
1187
|
+
if (!fallbackResult.isError) {
|
|
1188
|
+
return await processFileContent(fallbackResult, owner, repo, fallbackBranch, filePath);
|
|
1189
|
+
}
|
|
807
1190
|
}
|
|
1191
|
+
return createResult({
|
|
1192
|
+
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.`,
|
|
1193
|
+
});
|
|
808
1194
|
}
|
|
809
|
-
|
|
1195
|
+
// Handle common errors with more context
|
|
1196
|
+
if (errorMsg.includes('404')) {
|
|
1197
|
+
const searchSuggestion = await suggestCodeSearchFallback(owner, filePath);
|
|
810
1198
|
return createResult({
|
|
811
|
-
error: `
|
|
1199
|
+
error: `File "${filePath}" not found in branch "${branch}".
|
|
1200
|
+
|
|
1201
|
+
Quick fixes:
|
|
1202
|
+
• Use github_view_repo_structure to verify path exists
|
|
1203
|
+
• Check for typos in file path
|
|
1204
|
+
• Try different branch (main/master/develop)${searchSuggestion}
|
|
1205
|
+
|
|
1206
|
+
Alternative strategies:
|
|
1207
|
+
• Use github_search_code with query="filename:${filePath.split('/').pop()}" owner="${owner}"
|
|
1208
|
+
• Use github_search_code with query="path:${filePath}" to find similar paths`,
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
else if (errorMsg.includes('403')) {
|
|
1212
|
+
return createResult({
|
|
1213
|
+
error: `Access denied to "${filePath}" in "${owner}/${repo}".
|
|
1214
|
+
|
|
1215
|
+
Possible causes & solutions:
|
|
1216
|
+
• Private repository: use api_status_check to verify permissions
|
|
1217
|
+
• File in private directory: check repository access level
|
|
1218
|
+
• Rate limiting: wait 5-10 minutes and retry
|
|
1219
|
+
• Authentication: run gh auth login
|
|
1220
|
+
|
|
1221
|
+
Alternative approaches:
|
|
1222
|
+
• Use github_search_code with query="path:${filePath}" owner="${owner}"
|
|
1223
|
+
• Use github_view_repo_structure to explore accessible paths
|
|
1224
|
+
• Check repository on GitHub.com for public access`,
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
else if (errorMsg.includes('maxBuffer') ||
|
|
1228
|
+
errorMsg.includes('stdout maxBuffer length exceeded')) {
|
|
1229
|
+
return createResult({
|
|
1230
|
+
error: `File "${filePath}" is too large (>300KB).
|
|
1231
|
+
|
|
1232
|
+
Alternative strategies:
|
|
1233
|
+
• Use github_search_code to search within the file
|
|
1234
|
+
• Download directly from: https://github.com/${owner}/${repo}/blob/${branch}/${filePath}
|
|
1235
|
+
• Use github_view_repo_structure to find smaller related files
|
|
1236
|
+
• Look for configuration or summary files instead`,
|
|
812
1237
|
});
|
|
813
1238
|
}
|
|
814
1239
|
else {
|
|
815
|
-
const searchSuggestion =
|
|
816
|
-
? await suggestPathSearchFallback(owner, path)
|
|
817
|
-
: '';
|
|
1240
|
+
const searchSuggestion = await suggestCodeSearchFallback(owner, filePath);
|
|
818
1241
|
return createResult({
|
|
819
|
-
error: `Failed to
|
|
1242
|
+
error: `Failed to fetch "${filePath}": ${errorMsg}
|
|
1243
|
+
|
|
1244
|
+
Troubleshooting steps:
|
|
1245
|
+
1. Verify repository exists: github_view_repo_structure
|
|
1246
|
+
2. Check network connection and GitHub status
|
|
1247
|
+
3. Verify authentication: gh auth status
|
|
1248
|
+
4. Try different branch names${searchSuggestion}
|
|
1249
|
+
|
|
1250
|
+
Recovery strategies:
|
|
1251
|
+
• Use github_search_code for content discovery
|
|
1252
|
+
• Try github_search_repos to find similar repositories
|
|
1253
|
+
• Check file on GitHub.com: https://github.com/${owner}/${repo}/blob/${branch}/${filePath}`,
|
|
820
1254
|
});
|
|
821
1255
|
}
|
|
822
1256
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
.map(item => ({
|
|
836
|
-
name: item.name,
|
|
837
|
-
size: item.size,
|
|
838
|
-
url: item.path, // Use path for fetching
|
|
839
|
-
}));
|
|
840
|
-
const folders = limitedItems
|
|
841
|
-
.filter(item => item.type === 'dir')
|
|
842
|
-
.map(item => ({
|
|
843
|
-
name: item.name,
|
|
844
|
-
url: item.path, // Use path for browsing
|
|
845
|
-
}));
|
|
846
|
-
return createResult({
|
|
847
|
-
data: {
|
|
848
|
-
repository: `${owner}/${repo}`,
|
|
849
|
-
branch: usedBranch,
|
|
850
|
-
path: cleanPath || '/',
|
|
851
|
-
githubBasePath: `https://api.github.com/repos/${owner}/${repo}/contents/`,
|
|
852
|
-
files: {
|
|
853
|
-
count: files.length,
|
|
854
|
-
files: files,
|
|
855
|
-
},
|
|
856
|
-
folders: {
|
|
857
|
-
count: folders.length,
|
|
858
|
-
folders: folders,
|
|
859
|
-
},
|
|
860
|
-
},
|
|
861
|
-
});
|
|
862
|
-
}
|
|
863
|
-
catch (error) {
|
|
864
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
865
|
-
return createResult({
|
|
866
|
-
error: `Failed to access repository "${owner}/${repo}": ${errorMessage}. Verify repository name, permissions, and network connection.`,
|
|
867
|
-
});
|
|
868
|
-
}
|
|
869
|
-
});
|
|
870
|
-
}
|
|
871
|
-
/**
|
|
872
|
-
* Smart branch detection with automatic fallback to common branch names.
|
|
873
|
-
* Now includes more comprehensive branch detection and better error handling.
|
|
874
|
-
*/
|
|
875
|
-
async function getSmartBranchFallback(owner, repo, requestedBranch) {
|
|
876
|
-
const branches = new Set([requestedBranch]);
|
|
877
|
-
try {
|
|
878
|
-
// Try to get repository info to find default branch
|
|
879
|
-
const repoInfoResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
|
|
880
|
-
cache: false,
|
|
881
|
-
});
|
|
882
|
-
if (!repoInfoResult.isError) {
|
|
883
|
-
const execResult = JSON.parse(repoInfoResult.content[0].text);
|
|
884
|
-
const repoData = execResult.result;
|
|
885
|
-
const defaultBranch = repoData.default_branch;
|
|
886
|
-
if (defaultBranch) {
|
|
887
|
-
branches.add(defaultBranch);
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
catch {
|
|
892
|
-
// If we can't get repo info, proceed with standard fallbacks
|
|
893
|
-
}
|
|
894
|
-
// Add only main/master as fallback branches
|
|
895
|
-
const commonBranches = ['main', 'master'];
|
|
896
|
-
commonBranches.forEach(branch => branches.add(branch));
|
|
897
|
-
// Convert Set back to array, with requested branch first
|
|
898
|
-
const branchesArray = Array.from(branches);
|
|
899
|
-
branchesArray.sort((a, b) => {
|
|
900
|
-
if (a === requestedBranch)
|
|
901
|
-
return -1;
|
|
902
|
-
if (b === requestedBranch)
|
|
903
|
-
return 1;
|
|
904
|
-
return 0;
|
|
905
|
-
});
|
|
906
|
-
return branchesArray;
|
|
907
|
-
}
|
|
908
|
-
// Helper function to suggest path search strategy
|
|
909
|
-
async function suggestPathSearchFallback(owner, path) {
|
|
910
|
-
try {
|
|
911
|
-
// Extract last path segment and try to find in same organization
|
|
912
|
-
const pathSegment = path.split('/').pop() || path;
|
|
913
|
-
const searchResult = await executeGitHubCommand('api', [
|
|
914
|
-
`/search/code?q=${encodeURIComponent(pathSegment)}+in:path+org:${owner}`,
|
|
915
|
-
], { cache: false });
|
|
916
|
-
if (!searchResult.isError) {
|
|
917
|
-
const results = JSON.parse(searchResult.content[0].text);
|
|
918
|
-
if (results.total_count > 0) {
|
|
919
|
-
const firstMatch = results.items[0];
|
|
920
|
-
return ` Directory might be in ${firstMatch.repository.full_name}. Try these searches:\n1. github_search_code with query="${pathSegment}" owner="${owner}"\n2. github_search_code with query="path:${path}" owner="${owner}"`;
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
catch {
|
|
925
|
-
// Fallback to generic message if search fails
|
|
926
|
-
}
|
|
927
|
-
return ` Try these searches:\n1. github_search_code with query="${path.split('/').pop()}" owner="${owner}"\n2. github_search_code with query="path:${path}"`;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
const GITHUB_GET_FILE_CONTENT_TOOL_NAME = 'githubGetFileContent';
|
|
931
|
-
const DESCRIPTION$7 = `Fetch file content from GitHub repositories. Use ${GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME} first to explore repository structure and find exact file paths. Supports automatic branch fallback (main/master) and handles files up to 300KB. Parameters: owner (required - GitHub username/org), repo (required - repository name), branch (required), filePath (required).`;
|
|
932
|
-
function registerFetchGitHubFileContentTool(server) {
|
|
933
|
-
server.registerTool(GITHUB_GET_FILE_CONTENT_TOOL_NAME, {
|
|
934
|
-
description: DESCRIPTION$7,
|
|
935
|
-
inputSchema: {
|
|
936
|
-
owner: z
|
|
937
|
-
.string()
|
|
938
|
-
.min(1)
|
|
939
|
-
.max(100)
|
|
940
|
-
.regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/)
|
|
941
|
-
.describe(`Repository owner/org name (e.g., 'microsoft', 'google', NOT 'microsoft/vscode')`),
|
|
942
|
-
repo: z
|
|
943
|
-
.string()
|
|
944
|
-
.min(1)
|
|
945
|
-
.max(100)
|
|
946
|
-
.regex(/^[a-zA-Z0-9._-]+$/)
|
|
947
|
-
.describe(`Repository name only (e.g., 'vscode', 'react', NOT 'microsoft/vscode')`),
|
|
948
|
-
branch: z
|
|
949
|
-
.string()
|
|
950
|
-
.min(1)
|
|
951
|
-
.max(255)
|
|
952
|
-
.regex(/^[^\s]+$/)
|
|
953
|
-
.describe(`Branch name. Falls back to main/master if not found`),
|
|
954
|
-
filePath: z
|
|
955
|
-
.string()
|
|
956
|
-
.min(1)
|
|
957
|
-
.describe(`Exact file path from repo root (e.g., src/index.js, README.md)`),
|
|
958
|
-
},
|
|
959
|
-
annotations: {
|
|
960
|
-
title: 'GitHub File Content - Direct Access',
|
|
961
|
-
readOnlyHint: true,
|
|
962
|
-
destructiveHint: false,
|
|
963
|
-
idempotentHint: true,
|
|
964
|
-
openWorldHint: true,
|
|
965
|
-
},
|
|
966
|
-
}, async (args) => {
|
|
967
|
-
try {
|
|
968
|
-
const result = await fetchGitHubFileContent(args);
|
|
969
|
-
return result;
|
|
970
|
-
}
|
|
971
|
-
catch (error) {
|
|
972
|
-
return createResult({
|
|
973
|
-
error: 'Failed to fetch file. Verify path with github_get_contents first',
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
|
-
async function fetchGitHubFileContent(params) {
|
|
979
|
-
const cacheKey = generateCacheKey('gh-file-content', params);
|
|
980
|
-
return withCache(cacheKey, async () => {
|
|
981
|
-
const { owner, repo, branch, filePath } = params;
|
|
982
|
-
try {
|
|
983
|
-
// Try to fetch file content directly
|
|
984
|
-
const apiPath = `/repos/${owner}/${repo}/contents/${filePath}`;
|
|
985
|
-
const result = await executeGitHubCommand('api', [apiPath], {
|
|
986
|
-
cache: false,
|
|
987
|
-
});
|
|
988
|
-
if (result.isError) {
|
|
989
|
-
const errorMsg = result.content[0].text;
|
|
990
|
-
// Check repository existence only after content fetch fails
|
|
991
|
-
const repoCheckResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
|
|
992
|
-
cache: false,
|
|
993
|
-
});
|
|
994
|
-
if (repoCheckResult.isError) {
|
|
995
|
-
const repoErrorMsg = repoCheckResult.content[0].text;
|
|
996
|
-
if (repoErrorMsg.includes('404')) {
|
|
997
|
-
return createResult({
|
|
998
|
-
error: `Repository "${owner}/${repo}" not found. It might have been deleted, renamed, or made private. Use github_search_code to find current location.`,
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
else if (repoErrorMsg.includes('403')) {
|
|
1002
|
-
return createResult({
|
|
1003
|
-
error: `Repository "${owner}/${repo}" exists but access is denied. Repository might be private or archived. Use api_status_check to verify permissions.`,
|
|
1004
|
-
});
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
// Try fallback branches if original branch fails
|
|
1008
|
-
if (errorMsg.includes('404') &&
|
|
1009
|
-
branch !== 'main' &&
|
|
1010
|
-
branch !== 'master') {
|
|
1011
|
-
const fallbackBranches = ['main', 'master'];
|
|
1012
|
-
const triedBranches = [branch];
|
|
1013
|
-
for (const fallbackBranch of fallbackBranches) {
|
|
1014
|
-
if (triedBranches.includes(fallbackBranch))
|
|
1015
|
-
continue;
|
|
1016
|
-
triedBranches.push(fallbackBranch);
|
|
1017
|
-
const fallbackPath = `/repos/${owner}/${repo}/contents/${filePath}?ref=${fallbackBranch}`;
|
|
1018
|
-
const fallbackResult = await executeGitHubCommand('api', [fallbackPath], {
|
|
1019
|
-
cache: false,
|
|
1020
|
-
});
|
|
1021
|
-
if (!fallbackResult.isError) {
|
|
1022
|
-
return await processFileContent(fallbackResult, owner, repo, fallbackBranch, filePath);
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
return createResult({
|
|
1026
|
-
error: `File not found in any common branches (tried: ${triedBranches.join(', ')}). File might have been moved or deleted. Use github_search_code to find current location.`,
|
|
1027
|
-
});
|
|
1028
|
-
}
|
|
1029
|
-
// Handle common errors with more context
|
|
1030
|
-
if (errorMsg.includes('404')) {
|
|
1031
|
-
const searchSuggestion = await suggestCodeSearchFallback(owner, filePath);
|
|
1032
|
-
return createResult({
|
|
1033
|
-
error: `File "${filePath}" not found in branch "${branch}". Use github_view_repo_structure to verify path.${searchSuggestion}`,
|
|
1034
|
-
});
|
|
1035
|
-
}
|
|
1036
|
-
else if (errorMsg.includes('403')) {
|
|
1037
|
-
return createResult({
|
|
1038
|
-
error: `Access denied to "${filePath}" in "${owner}/${repo}". Repository or file might be private/archived. Use api_status_check to verify permissions, or github_search_code with query="path:${filePath}" owner="${owner}" to find in accessible repositories.`,
|
|
1039
|
-
});
|
|
1040
|
-
}
|
|
1041
|
-
else if (errorMsg.includes('maxBuffer') ||
|
|
1042
|
-
errorMsg.includes('stdout maxBuffer length exceeded')) {
|
|
1043
|
-
return createResult({
|
|
1044
|
-
error: `File "${filePath}" is too large (>300KB). Use github_search_code with query="path:${filePath}" to search within the file or download directly from GitHub.`,
|
|
1045
|
-
});
|
|
1046
|
-
}
|
|
1047
|
-
else {
|
|
1048
|
-
const searchSuggestion = await suggestCodeSearchFallback(owner, filePath);
|
|
1049
|
-
return createResult({
|
|
1050
|
-
error: `Failed to fetch "${filePath}". Error: ${errorMsg}. Verify repository name, branch, and file path.${searchSuggestion}`,
|
|
1051
|
-
});
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
return await processFileContent(result, owner, repo, branch, filePath);
|
|
1055
|
-
}
|
|
1056
|
-
catch (error) {
|
|
1057
|
-
const errorMessage = error.message;
|
|
1058
|
-
if (errorMessage.includes('maxBuffer') ||
|
|
1059
|
-
errorMessage.includes('stdout maxBuffer length exceeded')) {
|
|
1060
|
-
return createResult({
|
|
1061
|
-
error: `File "${filePath}" is too large (>300KB). Use github_search_code to search within the file or download directly from GitHub.`,
|
|
1062
|
-
});
|
|
1063
|
-
}
|
|
1064
|
-
return createResult({
|
|
1065
|
-
error: `Unexpected error fetching "${filePath}": ${errorMessage}. Check network connection and try again.`,
|
|
1257
|
+
return await processFileContent(result, owner, repo, branch, filePath);
|
|
1258
|
+
}
|
|
1259
|
+
catch (error) {
|
|
1260
|
+
const errorMessage = error.message;
|
|
1261
|
+
if (errorMessage.includes('maxBuffer') ||
|
|
1262
|
+
errorMessage.includes('stdout maxBuffer length exceeded')) {
|
|
1263
|
+
return createResult({
|
|
1264
|
+
error: `File "${filePath}" is too large (>300KB). Use github_search_code to search within the file or download directly from GitHub.`,
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
return createResult({
|
|
1268
|
+
error: `Unexpected error fetching "${filePath}": ${errorMessage}. Check network connection and try again.`,
|
|
1066
1269
|
});
|
|
1067
1270
|
}
|
|
1068
1271
|
});
|
|
@@ -1146,55 +1349,73 @@ async function suggestCodeSearchFallback(owner, filePath) {
|
|
|
1146
1349
|
}
|
|
1147
1350
|
|
|
1148
1351
|
const GITHUB_SEARCH_CODE_TOOL_NAME = 'githubSearchCode';
|
|
1149
|
-
const DESCRIPTION$
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
-
|
|
1155
|
-
-
|
|
1156
|
-
-
|
|
1157
|
-
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1352
|
+
const DESCRIPTION$7 = `Search code across GitHub repositories using GitHub's code search API via GitHub CLI.
|
|
1353
|
+
|
|
1354
|
+
SEARCH STRATEGY FOR BEST RESULTS:
|
|
1355
|
+
|
|
1356
|
+
TERM OPTIMIZATION (IMPORTANT):
|
|
1357
|
+
- BEST: Single terms for maximum coverage and relevance
|
|
1358
|
+
- GOOD: 2 terms when you need both to be present
|
|
1359
|
+
- RESTRICTIVE: 3+ terms - very specific but may miss relevant results
|
|
1360
|
+
- Example: "useState" (best coverage) vs "react hook useState" (specific but restrictive)
|
|
1361
|
+
|
|
1362
|
+
MULTI-SEARCH STRATEGY:
|
|
1363
|
+
- Use SEPARATE searches for different aspects instead of complex single queries
|
|
1364
|
+
- Example: Search "authentication" separately, then "login" separately
|
|
1365
|
+
- Separate searches provide broader coverage than restrictive AND logic
|
|
1366
|
+
|
|
1367
|
+
Search Logic:
|
|
1368
|
+
- Multiple terms = ALL must be present (AND logic) - very restrictive
|
|
1369
|
+
- Single terms = maximum results and coverage
|
|
1370
|
+
- Quoted phrases = exact phrase matching
|
|
1371
|
+
- Mixed queries: "exact phrase" additional_term (phrase + term)
|
|
1372
|
+
|
|
1373
|
+
Quote Usage:
|
|
1374
|
+
- Single terms: NO quotes (useState, authentication)
|
|
1375
|
+
- Multi-word phrases: WITH quotes ("error handling", "user authentication")
|
|
1376
|
+
- Mixed queries: "exact phrase" single_term another_term
|
|
1377
|
+
|
|
1378
|
+
Filter Usage:
|
|
1379
|
+
- All filters use GitHub CLI flags (--language, --owner, --repo, etc.)
|
|
1380
|
+
- Combine filters to narrow scope: language + owner, repo + filename
|
|
1381
|
+
- Never use filters on exploratory searches - use to refine when too many results`;
|
|
1161
1382
|
function registerGitHubSearchCodeTool(server) {
|
|
1162
1383
|
server.registerTool(GITHUB_SEARCH_CODE_TOOL_NAME, {
|
|
1163
|
-
description: DESCRIPTION$
|
|
1384
|
+
description: DESCRIPTION$7,
|
|
1164
1385
|
inputSchema: {
|
|
1165
1386
|
query: z
|
|
1166
1387
|
.string()
|
|
1167
1388
|
.min(1)
|
|
1168
|
-
.describe('Search query with AND logic between terms. Multiple words require ALL to be present. Use quotes for exact phrases. Examples:
|
|
1389
|
+
.describe('Search query with AND logic between terms. OPTIMIZATION: Single terms give best coverage, 2 terms when both needed, 3+ terms very restrictive. Multiple words require ALL to be present. Use quotes for exact phrases. Examples: "useState" (best coverage), "error handling" (exact phrase), "react hook useState" (specific but restrictive).'),
|
|
1169
1390
|
language: z
|
|
1170
1391
|
.string()
|
|
1171
1392
|
.optional()
|
|
1172
|
-
.describe('Programming language filter
|
|
1393
|
+
.describe('Programming language filter. Uses --language CLI flag. Narrows search to specific language files. Use for language-specific searches.'),
|
|
1173
1394
|
owner: z
|
|
1174
1395
|
.union([z.string(), z.array(z.string())])
|
|
1175
1396
|
.optional()
|
|
1176
|
-
.describe('Repository owner
|
|
1397
|
+
.describe('Repository owner/organization name(s) to search within (e.g., "facebook", ["google", "microsoft"]). Uses --owner CLI flag for organization-wide search. Can be combined with repo parameter for specific repository search. Do NOT use owner/repo format - just the organization/username.'),
|
|
1177
1398
|
repo: z
|
|
1178
1399
|
.union([z.string(), z.array(z.string())])
|
|
1179
1400
|
.optional()
|
|
1180
|
-
.describe('Filter on specific repository(ies).
|
|
1401
|
+
.describe('Filter on specific repository(ies). Uses --repo CLI flag. Two usage patterns: (1) Use with owner parameter - provide just repo name (e.g., owner="facebook", repo="react" → --repo=facebook/react), or (2) Use alone - provide full "owner/repo" format (e.g., "facebook/react" → --repo=facebook/react).'),
|
|
1181
1402
|
filename: z
|
|
1182
1403
|
.string()
|
|
1183
1404
|
.optional()
|
|
1184
|
-
.describe('Target specific filename or pattern.
|
|
1405
|
+
.describe('Target specific filename or pattern. Uses --filename CLI flag. Use for file-specific searches.'),
|
|
1185
1406
|
extension: z
|
|
1186
1407
|
.string()
|
|
1187
1408
|
.optional()
|
|
1188
|
-
.describe('File extension filter
|
|
1409
|
+
.describe('File extension filter. Uses --extension CLI flag. Alternative to language parameter.'),
|
|
1189
1410
|
match: z
|
|
1190
|
-
.
|
|
1411
|
+
.enum(['file', 'path'])
|
|
1191
1412
|
.optional()
|
|
1192
|
-
.describe('Search scope: "file" for file content (default), "path" for filenames/paths
|
|
1413
|
+
.describe('Search scope: "file" for file content (default), "path" for filenames/paths. Uses --match CLI flag. Single value only - multiple scopes not supported by GitHub CLI.'),
|
|
1193
1414
|
size: z
|
|
1194
1415
|
.string()
|
|
1195
1416
|
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid size format. Use: ">10", ">=5", "<100", "<=50", "10..100", or exact number "50"')
|
|
1196
1417
|
.optional()
|
|
1197
|
-
.describe('File size filter in KB. Format: ">
|
|
1418
|
+
.describe('File size filter in KB. Uses --size CLI flag. Format: ">N" (larger than), "<N" (smaller than), "N..M" (range), "N" (exact).'),
|
|
1198
1419
|
limit: z
|
|
1199
1420
|
.number()
|
|
1200
1421
|
.int()
|
|
@@ -1213,11 +1434,6 @@ function registerGitHubSearchCodeTool(server) {
|
|
|
1213
1434
|
},
|
|
1214
1435
|
}, async (args) => {
|
|
1215
1436
|
try {
|
|
1216
|
-
// Validate parameter combinations
|
|
1217
|
-
const validationError = validateSearchParameters(args);
|
|
1218
|
-
if (validationError) {
|
|
1219
|
-
return createResult({ error: validationError });
|
|
1220
|
-
}
|
|
1221
1437
|
const result = await searchGitHubCode(args);
|
|
1222
1438
|
if (result.isError) {
|
|
1223
1439
|
return result;
|
|
@@ -1252,45 +1468,79 @@ function registerGitHubSearchCodeTool(server) {
|
|
|
1252
1468
|
});
|
|
1253
1469
|
}
|
|
1254
1470
|
/**
|
|
1255
|
-
* Handles various search errors and returns a formatted CallToolResult.
|
|
1471
|
+
* Handles various search errors and returns a formatted CallToolResult with smart fallbacks.
|
|
1256
1472
|
*/
|
|
1257
1473
|
function handleSearchError(errorMessage) {
|
|
1258
|
-
//
|
|
1259
|
-
if (errorMessage.includes('
|
|
1474
|
+
// Rate limit with smart timing guidance
|
|
1475
|
+
if (errorMessage.includes('rate limit') || errorMessage.includes('403')) {
|
|
1260
1476
|
return createResult({
|
|
1261
|
-
error:
|
|
1477
|
+
error: `GitHub API rate limit reached. Try again in 5-10 minutes, or use these strategies:
|
|
1478
|
+
• Search fewer terms per query
|
|
1479
|
+
• Use owner/repo filters to narrow scope
|
|
1480
|
+
• Try npm package search for package-related queries
|
|
1481
|
+
• Use separate searches instead of complex queries`,
|
|
1262
1482
|
});
|
|
1263
1483
|
}
|
|
1264
|
-
|
|
1484
|
+
// Authentication with clear next steps
|
|
1485
|
+
if (errorMessage.includes('authentication') || errorMessage.includes('401')) {
|
|
1265
1486
|
return createResult({
|
|
1266
|
-
error:
|
|
1487
|
+
error: `GitHub authentication required. Fix with:
|
|
1488
|
+
1. Run: gh auth login
|
|
1489
|
+
2. Verify access: gh auth status
|
|
1490
|
+
3. For private repos: use api_status_check to verify org access`,
|
|
1267
1491
|
});
|
|
1268
1492
|
}
|
|
1269
|
-
|
|
1493
|
+
// Network/timeout with fallback suggestions
|
|
1494
|
+
if (errorMessage.includes('timed out') || errorMessage.includes('network')) {
|
|
1270
1495
|
return createResult({
|
|
1271
|
-
error:
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
error: ERROR_MESSAGES.SEARCH_TIMEOUT,
|
|
1496
|
+
error: `Network timeout. Try these alternatives:
|
|
1497
|
+
• Reduce search scope with owner or language filters
|
|
1498
|
+
• Use github_search_repos to find repositories first
|
|
1499
|
+
• Try npm package search for package discovery
|
|
1500
|
+
• Check network connection and retry`,
|
|
1277
1501
|
});
|
|
1278
1502
|
}
|
|
1503
|
+
// Invalid query with specific fixes
|
|
1279
1504
|
if (errorMessage.includes('validation failed') ||
|
|
1280
1505
|
errorMessage.includes('Invalid query')) {
|
|
1281
1506
|
return createResult({
|
|
1282
|
-
error:
|
|
1507
|
+
error: `Invalid search query. Common fixes:
|
|
1508
|
+
• Remove special characters: ()[]{}*?^$|.\\
|
|
1509
|
+
• Use quotes only for exact phrases: "error handling"
|
|
1510
|
+
• Avoid escaped quotes: use term instead of "term"
|
|
1511
|
+
• Try broader terms: "react" instead of "React.Component"`,
|
|
1283
1512
|
});
|
|
1284
1513
|
}
|
|
1514
|
+
// Repository not found with discovery suggestions
|
|
1285
1515
|
if (errorMessage.includes('repository not found') ||
|
|
1286
1516
|
errorMessage.includes('owner not found')) {
|
|
1287
1517
|
return createResult({
|
|
1288
|
-
error:
|
|
1518
|
+
error: `Repository/owner not found. Discovery strategies:
|
|
1519
|
+
• Use github_search_repos to find correct names
|
|
1520
|
+
• Check for typos in owner/repo names
|
|
1521
|
+
• Try without owner filter for broader search
|
|
1522
|
+
• Use npm package search if looking for packages`,
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
// JSON parsing with system guidance
|
|
1526
|
+
if (errorMessage.includes('JSON')) {
|
|
1527
|
+
return createResult({
|
|
1528
|
+
error: `GitHub CLI response parsing failed. System issue - try:
|
|
1529
|
+
• Update GitHub CLI: gh extension upgrade
|
|
1530
|
+
• Retry in a few moments
|
|
1531
|
+
• Use github_search_repos as alternative
|
|
1532
|
+
• Check gh auth status for authentication`,
|
|
1289
1533
|
});
|
|
1290
1534
|
}
|
|
1291
|
-
// Generic fallback with
|
|
1535
|
+
// Generic fallback with progressive strategy
|
|
1292
1536
|
return createResult({
|
|
1293
|
-
error: `Code search failed: ${errorMessage}
|
|
1537
|
+
error: `Code search failed: ${errorMessage}
|
|
1538
|
+
|
|
1539
|
+
Progressive recovery strategy:
|
|
1540
|
+
1. Try broader search terms
|
|
1541
|
+
2. Use github_search_repos to find repositories
|
|
1542
|
+
3. Use npm package search for package-related queries
|
|
1543
|
+
4. Check github CLI status: gh auth status`,
|
|
1294
1544
|
});
|
|
1295
1545
|
}
|
|
1296
1546
|
/**
|
|
@@ -1308,6 +1558,10 @@ function transformToOptimizedFormat$1(items) {
|
|
|
1308
1558
|
: [0, 0]) || [],
|
|
1309
1559
|
})) || [],
|
|
1310
1560
|
url: singleRepo ? item.path : simplifyGitHubUrl(item.url),
|
|
1561
|
+
repository: {
|
|
1562
|
+
nameWithOwner: item.repository.nameWithOwner,
|
|
1563
|
+
url: item.repository.url,
|
|
1564
|
+
},
|
|
1311
1565
|
}));
|
|
1312
1566
|
const result = {
|
|
1313
1567
|
items: optimizedItems,
|
|
@@ -1333,8 +1587,8 @@ function extractSingleRepository$1(items) {
|
|
|
1333
1587
|
return allSameRepo ? firstRepo : null;
|
|
1334
1588
|
}
|
|
1335
1589
|
/**
|
|
1336
|
-
* Build command line arguments for GitHub CLI
|
|
1337
|
-
*
|
|
1590
|
+
* Build command line arguments for GitHub CLI following the exact CLI format.
|
|
1591
|
+
* Uses proper flags (--flag=value) instead of qualifiers where appropriate.
|
|
1338
1592
|
*/
|
|
1339
1593
|
function buildGitHubCliArgs(params) {
|
|
1340
1594
|
const args = ['code'];
|
|
@@ -1344,39 +1598,52 @@ function buildGitHubCliArgs(params) {
|
|
|
1344
1598
|
if (searchQuery) {
|
|
1345
1599
|
args.push(searchQuery);
|
|
1346
1600
|
}
|
|
1347
|
-
// Add extracted qualifiers from the query
|
|
1601
|
+
// Add extracted qualifiers from the query (these should remain as qualifiers)
|
|
1348
1602
|
extractedQualifiers.forEach(qualifier => {
|
|
1349
1603
|
args.push(qualifier);
|
|
1350
1604
|
});
|
|
1351
|
-
// Add explicit parameters as
|
|
1605
|
+
// Add explicit parameters as CLI flags (following GitHub CLI format)
|
|
1352
1606
|
if (params.language && !params.query.includes('language:')) {
|
|
1353
|
-
args.push(
|
|
1607
|
+
args.push(`--language=${params.language}`);
|
|
1608
|
+
}
|
|
1609
|
+
// Handle owner and repo parameters properly
|
|
1610
|
+
if (params.repo && !params.query.includes('repo:')) {
|
|
1611
|
+
const repos = Array.isArray(params.repo) ? params.repo : [params.repo];
|
|
1612
|
+
repos.forEach(repo => {
|
|
1613
|
+
// If both owner and repo are provided, combine them for --repo flag
|
|
1614
|
+
if (params.owner && !repo.includes('/')) {
|
|
1615
|
+
const owners = Array.isArray(params.owner)
|
|
1616
|
+
? params.owner
|
|
1617
|
+
: [params.owner];
|
|
1618
|
+
owners.forEach(owner => args.push(`--repo=${owner}/${repo}`));
|
|
1619
|
+
}
|
|
1620
|
+
else {
|
|
1621
|
+
// Repo is already in owner/repo format or no owner provided
|
|
1622
|
+
args.push(`--repo=${repo}`);
|
|
1623
|
+
}
|
|
1624
|
+
});
|
|
1354
1625
|
}
|
|
1355
|
-
if (params.owner &&
|
|
1626
|
+
else if (params.owner &&
|
|
1356
1627
|
!params.query.includes('org:') &&
|
|
1357
1628
|
!params.query.includes('user:')) {
|
|
1629
|
+
// Only owner provided, no repo - use --owner flag for organization-wide search
|
|
1358
1630
|
const owners = Array.isArray(params.owner) ? params.owner : [params.owner];
|
|
1359
|
-
owners.forEach(owner => args.push(
|
|
1360
|
-
}
|
|
1361
|
-
if (params.repo && !params.query.includes('repo:')) {
|
|
1362
|
-
const repos = Array.isArray(params.repo) ? params.repo : [params.repo];
|
|
1363
|
-
repos.forEach(repo => args.push(`repo:${repo}`));
|
|
1631
|
+
owners.forEach(owner => args.push(`--owner=${owner}`));
|
|
1364
1632
|
}
|
|
1365
1633
|
if (params.filename && !params.query.includes('filename:')) {
|
|
1366
|
-
args.push(
|
|
1634
|
+
args.push(`--filename=${params.filename}`);
|
|
1367
1635
|
}
|
|
1368
1636
|
if (params.extension && !params.query.includes('extension:')) {
|
|
1369
|
-
args.push(
|
|
1637
|
+
args.push(`--extension=${params.extension}`);
|
|
1370
1638
|
}
|
|
1371
1639
|
if (params.size && !params.query.includes('size:')) {
|
|
1372
|
-
args.push(
|
|
1640
|
+
args.push(`--size=${params.size}`);
|
|
1373
1641
|
}
|
|
1374
|
-
// Handle match parameter
|
|
1642
|
+
// Handle match parameter - use --match flag
|
|
1375
1643
|
if (params.match) {
|
|
1376
|
-
|
|
1377
|
-
args.push(`in:${matches.join(',')}`);
|
|
1644
|
+
args.push(`--match=${params.match}`);
|
|
1378
1645
|
}
|
|
1379
|
-
// Add limit
|
|
1646
|
+
// Add limit flag
|
|
1380
1647
|
if (params.limit) {
|
|
1381
1648
|
args.push(`--limit=${params.limit}`);
|
|
1382
1649
|
}
|
|
@@ -1396,69 +1663,38 @@ async function searchGitHubCode(params) {
|
|
|
1396
1663
|
}
|
|
1397
1664
|
catch (error) {
|
|
1398
1665
|
const errorMessage = error.message || '';
|
|
1399
|
-
return handleSearchError(errorMessage);
|
|
1666
|
+
return handleSearchError(errorMessage);
|
|
1400
1667
|
}
|
|
1401
1668
|
});
|
|
1402
1669
|
}
|
|
1403
1670
|
/**
|
|
1404
|
-
*
|
|
1671
|
+
* Parse search query to preserve quoted phrases and extract qualifiers.
|
|
1672
|
+
* Handles:
|
|
1673
|
+
* - Quoted phrases: "error handling" -> kept as single unit
|
|
1674
|
+
* - Multiple terms: react lifecycle -> both terms for AND search
|
|
1675
|
+
* - Mixed: "error handling" debug -> phrase + term
|
|
1676
|
+
* - Qualifiers: language:javascript -> extracted separately
|
|
1677
|
+
* - Quote escaping issues: automatically fixes common mistakes
|
|
1405
1678
|
*/
|
|
1406
|
-
function
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1679
|
+
function parseSearchQuery(query) {
|
|
1680
|
+
const qualifiers = [];
|
|
1681
|
+
const searchTerms = [];
|
|
1682
|
+
// Clean up common quote escaping issues
|
|
1683
|
+
let cleanedQuery = query;
|
|
1684
|
+
// Fix escaped quotes that shouldn't be escaped
|
|
1685
|
+
if (cleanedQuery.includes('\\"') && !cleanedQuery.includes(' ')) {
|
|
1686
|
+
cleanedQuery = cleanedQuery.replace(/\\"/g, '');
|
|
1413
1687
|
}
|
|
1414
|
-
//
|
|
1415
|
-
if (
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1688
|
+
// Fix single-word queries wrapped in quotes unnecessarily
|
|
1689
|
+
if (cleanedQuery.startsWith('"') &&
|
|
1690
|
+
cleanedQuery.endsWith('"') &&
|
|
1691
|
+
!cleanedQuery.slice(1, -1).includes(' ')) {
|
|
1692
|
+
cleanedQuery = cleanedQuery.slice(1, -1);
|
|
1419
1693
|
}
|
|
1420
|
-
if (Array.isArray(params.owner)) {
|
|
1421
|
-
const hasSlashFormat = params.owner.some(owner => owner.includes('/'));
|
|
1422
|
-
if (hasSlashFormat) {
|
|
1423
|
-
return 'Owner parameter should contain only usernames/org names, not owner/repo format. For repository-specific searches, use repo: qualifier in the query.';
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
// Add validation for file size limit
|
|
1427
|
-
if (params.size) {
|
|
1428
|
-
if (!/^([<>]\d+|\d+\.\.\d+)$/.test(params.size)) {
|
|
1429
|
-
return ERROR_MESSAGES.INVALID_SIZE_FORMAT;
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
// Validate search scope
|
|
1433
|
-
if (params.match) {
|
|
1434
|
-
const validScopes = ['file', 'path'];
|
|
1435
|
-
const scopes = Array.isArray(params.match) ? params.match : [params.match];
|
|
1436
|
-
if (!scopes.every(scope => validScopes.includes(scope))) {
|
|
1437
|
-
return ERROR_MESSAGES.INVALID_SEARCH_SCOPE;
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
// Note about repository limitations (This is a note, not a hard error)
|
|
1441
|
-
// This return statement was returning null before, so it shouldn't be an issue
|
|
1442
|
-
// if (params.repo || params.owner) {
|
|
1443
|
-
// return null; // Return warning about repository limitations
|
|
1444
|
-
// }
|
|
1445
|
-
return null; // No validation errors
|
|
1446
|
-
}
|
|
1447
|
-
/**
|
|
1448
|
-
* Parse search query to preserve quoted phrases and extract qualifiers.
|
|
1449
|
-
* Handles:
|
|
1450
|
-
* - Quoted phrases: "error handling" -> kept as single unit
|
|
1451
|
-
* - Multiple terms: react lifecycle -> both terms for AND search
|
|
1452
|
-
* - Mixed: "error handling" debug -> phrase + term
|
|
1453
|
-
* - Qualifiers: language:javascript -> extracted separately
|
|
1454
|
-
*/
|
|
1455
|
-
function parseSearchQuery(query) {
|
|
1456
|
-
const qualifiers = [];
|
|
1457
|
-
const searchTerms = [];
|
|
1458
1694
|
// Regular expression to match quoted strings or individual words/qualifiers
|
|
1459
1695
|
const tokenRegex = /"([^"]+)"|([^\s]+)/g;
|
|
1460
1696
|
let match;
|
|
1461
|
-
while ((match = tokenRegex.exec(
|
|
1697
|
+
while ((match = tokenRegex.exec(cleanedQuery)) !== null) {
|
|
1462
1698
|
const token = match[1] || match[2]; // match[1] is quoted content, match[2] is unquoted
|
|
1463
1699
|
// Check if it's a qualifier (contains : but not inside quotes)
|
|
1464
1700
|
if (!match[1] && token.includes(':') && /^[a-zA-Z]+:/.test(token)) {
|
|
@@ -1482,10 +1718,10 @@ function parseSearchQuery(query) {
|
|
|
1482
1718
|
}
|
|
1483
1719
|
|
|
1484
1720
|
const GITHUB_SEARCH_COMMITS_TOOL_NAME = 'githubSearchCommits';
|
|
1485
|
-
const DESCRIPTION$
|
|
1721
|
+
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.`;
|
|
1486
1722
|
function registerGitHubSearchCommitsTool(server) {
|
|
1487
1723
|
server.registerTool(GITHUB_SEARCH_COMMITS_TOOL_NAME, {
|
|
1488
|
-
description: DESCRIPTION$
|
|
1724
|
+
description: DESCRIPTION$6,
|
|
1489
1725
|
inputSchema: {
|
|
1490
1726
|
query: z
|
|
1491
1727
|
.string()
|
|
@@ -1495,11 +1731,11 @@ function registerGitHubSearchCommitsTool(server) {
|
|
|
1495
1731
|
owner: z
|
|
1496
1732
|
.string()
|
|
1497
1733
|
.optional()
|
|
1498
|
-
.describe('Repository owner/
|
|
1734
|
+
.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.'),
|
|
1499
1735
|
repo: z
|
|
1500
1736
|
.string()
|
|
1501
1737
|
.optional()
|
|
1502
|
-
.describe('Repository name only (e.g., "
|
|
1738
|
+
.describe('Repository name only (e.g., "react", "vscode"). Do NOT include owner prefix. Must be used together with owner parameter.'),
|
|
1503
1739
|
// Author filters
|
|
1504
1740
|
author: z
|
|
1505
1741
|
.string()
|
|
@@ -1749,10 +1985,10 @@ function buildGitHubCommitCliArgs(params) {
|
|
|
1749
1985
|
}
|
|
1750
1986
|
|
|
1751
1987
|
const GITHUB_SEARCH_ISSUES_TOOL_NAME = 'githubSearchIssues';
|
|
1752
|
-
const DESCRIPTION$
|
|
1988
|
+
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.`;
|
|
1753
1989
|
function registerSearchGitHubIssuesTool(server) {
|
|
1754
1990
|
server.registerTool(GITHUB_SEARCH_ISSUES_TOOL_NAME, {
|
|
1755
|
-
description: DESCRIPTION$
|
|
1991
|
+
description: DESCRIPTION$5,
|
|
1756
1992
|
inputSchema: {
|
|
1757
1993
|
query: z
|
|
1758
1994
|
.string()
|
|
@@ -1762,11 +1998,11 @@ function registerSearchGitHubIssuesTool(server) {
|
|
|
1762
1998
|
.string()
|
|
1763
1999
|
.min(1)
|
|
1764
2000
|
.optional()
|
|
1765
|
-
.describe('Repository owner/
|
|
2001
|
+
.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.'),
|
|
1766
2002
|
repo: z
|
|
1767
2003
|
.string()
|
|
1768
2004
|
.optional()
|
|
1769
|
-
.describe('Repository name only (e.g., "
|
|
2005
|
+
.describe('Repository name only (e.g., "react", "vscode"). Do NOT include owner prefix. Must be used together with owner parameter.'),
|
|
1770
2006
|
app: z
|
|
1771
2007
|
.string()
|
|
1772
2008
|
.optional()
|
|
@@ -1820,7 +2056,7 @@ function registerSearchGitHubIssuesTool(server) {
|
|
|
1820
2056
|
label: z
|
|
1821
2057
|
.union([z.string(), z.array(z.string())])
|
|
1822
2058
|
.optional()
|
|
1823
|
-
.describe('Label names
|
|
2059
|
+
.describe('Label names. Can be single string or array.'),
|
|
1824
2060
|
language: z.string().optional().describe('Repository language'),
|
|
1825
2061
|
locked: z.boolean().optional().describe('Conversation locked status'),
|
|
1826
2062
|
match: z
|
|
@@ -1961,6 +2197,51 @@ async function searchGitHubIssues(params) {
|
|
|
1961
2197
|
comments: issue.comments,
|
|
1962
2198
|
reactions: issue.reactions?.total_count || 0,
|
|
1963
2199
|
}));
|
|
2200
|
+
// Smart fallback suggestions for no results
|
|
2201
|
+
if (cleanIssues.length === 0) {
|
|
2202
|
+
const fallbackSuggestions = [];
|
|
2203
|
+
// Analyze search parameters for specific suggestions
|
|
2204
|
+
if (params.state === 'closed') {
|
|
2205
|
+
fallbackSuggestions.push('• Try state:open or remove state filter');
|
|
2206
|
+
}
|
|
2207
|
+
if (params.author) {
|
|
2208
|
+
fallbackSuggestions.push('• Remove author filter for broader search');
|
|
2209
|
+
fallbackSuggestions.push(`• Use github_search_code to find ${params.author}'s contributions`);
|
|
2210
|
+
}
|
|
2211
|
+
if (params.label) {
|
|
2212
|
+
const labels = Array.isArray(params.label)
|
|
2213
|
+
? params.label
|
|
2214
|
+
: [params.label];
|
|
2215
|
+
fallbackSuggestions.push(`• Try broader labels or remove label filter`);
|
|
2216
|
+
fallbackSuggestions.push(`• Search for label variations: ${labels.map(l => `"${l}"`).join(', ')}`);
|
|
2217
|
+
}
|
|
2218
|
+
if (params.owner && params.repo) {
|
|
2219
|
+
fallbackSuggestions.push('• Check repository name spelling');
|
|
2220
|
+
fallbackSuggestions.push('• Use github_view_repo_structure to verify repository exists');
|
|
2221
|
+
}
|
|
2222
|
+
else if (params.owner) {
|
|
2223
|
+
fallbackSuggestions.push('• Remove owner filter for global search');
|
|
2224
|
+
fallbackSuggestions.push('• Use github_search_repos to find organization repositories');
|
|
2225
|
+
}
|
|
2226
|
+
if (params.created || params.updated) {
|
|
2227
|
+
fallbackSuggestions.push('• Expand date range or remove date filters');
|
|
2228
|
+
}
|
|
2229
|
+
// Add general alternatives
|
|
2230
|
+
fallbackSuggestions.push('• Try broader search terms');
|
|
2231
|
+
fallbackSuggestions.push('• Use github_search_pull_requests for related development activity');
|
|
2232
|
+
fallbackSuggestions.push('• Use github_search_code to find implementation patterns');
|
|
2233
|
+
return createResult({
|
|
2234
|
+
error: `No issues found for query: "${params.query}"
|
|
2235
|
+
|
|
2236
|
+
Try these alternatives:
|
|
2237
|
+
${fallbackSuggestions.join('\n')}
|
|
2238
|
+
|
|
2239
|
+
Discovery strategies:
|
|
2240
|
+
• Broader terms: "error" instead of "TypeError"
|
|
2241
|
+
• Remove filters: state, labels, author
|
|
2242
|
+
• Related searches: pull requests, code implementations`,
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
1964
2245
|
const searchResult = {
|
|
1965
2246
|
results: cleanIssues,
|
|
1966
2247
|
};
|
|
@@ -2083,10 +2364,10 @@ function buildGitHubIssuesAPICommand(params) {
|
|
|
2083
2364
|
|
|
2084
2365
|
// TODO: add PR commeents. e.g, gh pr view <PR_NUMBER_OR_URL_OR_BRANCH> --comments
|
|
2085
2366
|
const GITHUB_SEARCH_PULL_REQUESTS_TOOL_NAME = 'githubSearchPullRequests';
|
|
2086
|
-
const DESCRIPTION$
|
|
2367
|
+
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.`;
|
|
2087
2368
|
function registerSearchGitHubPullRequestsTool(server) {
|
|
2088
2369
|
server.registerTool(GITHUB_SEARCH_PULL_REQUESTS_TOOL_NAME, {
|
|
2089
|
-
description: DESCRIPTION$
|
|
2370
|
+
description: DESCRIPTION$4,
|
|
2090
2371
|
inputSchema: {
|
|
2091
2372
|
query: z
|
|
2092
2373
|
.string()
|
|
@@ -2095,11 +2376,11 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
2095
2376
|
owner: z
|
|
2096
2377
|
.string()
|
|
2097
2378
|
.optional()
|
|
2098
|
-
.describe('Repository owner/
|
|
2379
|
+
.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.'),
|
|
2099
2380
|
repo: z
|
|
2100
2381
|
.string()
|
|
2101
2382
|
.optional()
|
|
2102
|
-
.describe('Repository name only (e.g., "
|
|
2383
|
+
.describe('Repository name only (e.g., "react", "vscode"). Do NOT include owner prefix. Must be used together with owner parameter.'),
|
|
2103
2384
|
author: z.string().optional().describe('GitHub username of PR author'),
|
|
2104
2385
|
assignee: z.string().optional().describe('GitHub username of assignee'),
|
|
2105
2386
|
mentions: z.string().optional().describe('PRs mentioning this user'),
|
|
@@ -2118,10 +2399,7 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
2118
2399
|
.optional()
|
|
2119
2400
|
.describe('PR state. Default: all'),
|
|
2120
2401
|
head: z.string().optional().describe('Source branch name'),
|
|
2121
|
-
base: z
|
|
2122
|
-
.string()
|
|
2123
|
-
.optional()
|
|
2124
|
-
.describe('Target branch name (main, develop, etc.)'),
|
|
2402
|
+
base: z.string().optional().describe('Target branch name'),
|
|
2125
2403
|
language: z.string().optional().describe('Repository language'),
|
|
2126
2404
|
created: z
|
|
2127
2405
|
.string()
|
|
@@ -2409,26 +2687,38 @@ function buildGitHubPullRequestsAPICommand(params) {
|
|
|
2409
2687
|
* 4. Recent Quality:
|
|
2410
2688
|
* { stars: ">1000", created: ">2023-01-01", limit: 10 }
|
|
2411
2689
|
*
|
|
2690
|
+
* RESEARCH & EXPLORATION PATTERNS:
|
|
2691
|
+
*
|
|
2692
|
+
* 1. Topic-based Discovery (HIGHLY RECOMMENDED for unknown projects):
|
|
2693
|
+
* { topic: ["machine-learning", "nlp", "pytorch"], limit: 20 }
|
|
2694
|
+
* { topic: ["kubernetes", "monitoring"], stars: ">100", limit: 15 }
|
|
2695
|
+
*
|
|
2696
|
+
* 2. Exploratory Research Flow:
|
|
2697
|
+
* - Start with topics to discover repositories
|
|
2698
|
+
* - Then use githubViewRepoStructure to understand project layout
|
|
2699
|
+
* - Read README.md, docs/, and configuration files
|
|
2700
|
+
* - Finally use githubSearchCode for specific implementations
|
|
2701
|
+
*
|
|
2412
2702
|
* AVOID: OR queries + language filter, 5+ filters, multi-word OR
|
|
2413
2703
|
* TIP: Use limit parameter instead of adding more filters
|
|
2414
2704
|
*/
|
|
2415
2705
|
const GITHUB_SEARCH_REPOSITORIES_TOOL_NAME = 'githubSearchRepositories';
|
|
2416
|
-
const DESCRIPTION$
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
-
|
|
2420
|
-
-
|
|
2421
|
-
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
-
|
|
2430
|
-
-
|
|
2431
|
-
-
|
|
2706
|
+
const DESCRIPTION$3 = `Search GitHub repositories for project discovery and ecosystem analysis. TOPICS are the most powerful discovery feature.
|
|
2707
|
+
|
|
2708
|
+
TOPIC-DRIVEN DISCOVERY:
|
|
2709
|
+
- Topics reveal ecosystem relationships and quality indicators
|
|
2710
|
+
- Combine topics for targeted discovery: ["framework", "use-case", "language"]
|
|
2711
|
+
- Topics beat keyword searches for unknown project exploration
|
|
2712
|
+
|
|
2713
|
+
PROGRESSIVE SEARCH STRATEGY:
|
|
2714
|
+
- Start with topic combinations for broad discovery
|
|
2715
|
+
- Add quality filters (stars, activity) for refinement
|
|
2716
|
+
- Use multiple separate searches vs complex single queries
|
|
2717
|
+
|
|
2718
|
+
RESEARCH INTEGRATION:
|
|
2719
|
+
- Repository discovery → structure analysis → implementation patterns
|
|
2720
|
+
- Quality assessment via community metrics and activity
|
|
2721
|
+
- Bridge to code search and file analysis tools`;
|
|
2432
2722
|
/**
|
|
2433
2723
|
* Extract owner/repo information from various query formats
|
|
2434
2724
|
*/
|
|
@@ -2461,21 +2751,21 @@ function extractOwnerRepoFromQuery(query) {
|
|
|
2461
2751
|
}
|
|
2462
2752
|
function registerSearchGitHubReposTool(server) {
|
|
2463
2753
|
server.registerTool(GITHUB_SEARCH_REPOSITORIES_TOOL_NAME, {
|
|
2464
|
-
description: DESCRIPTION$
|
|
2754
|
+
description: DESCRIPTION$3,
|
|
2465
2755
|
inputSchema: {
|
|
2466
2756
|
query: z
|
|
2467
2757
|
.string()
|
|
2468
2758
|
.optional()
|
|
2469
|
-
.describe('Search query with AND logic between terms. Multiple words require ALL to be present. Use quotes for exact phrases.
|
|
2759
|
+
.describe('Search query with AND logic between terms. Multiple words require ALL to be present. Use quotes for exact phrases.'),
|
|
2470
2760
|
// CORE FILTERS (GitHub CLI flags)
|
|
2471
2761
|
owner: z
|
|
2472
2762
|
.union([z.string(), z.array(z.string())])
|
|
2473
2763
|
.optional()
|
|
2474
|
-
.describe('Repository owner
|
|
2764
|
+
.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.'),
|
|
2475
2765
|
language: z
|
|
2476
2766
|
.string()
|
|
2477
2767
|
.optional()
|
|
2478
|
-
.describe('Programming language filter
|
|
2768
|
+
.describe('Programming language filter. Filters repositories by primary language. Essential for language-specific searches.'),
|
|
2479
2769
|
stars: z
|
|
2480
2770
|
.union([
|
|
2481
2771
|
z.number().int().min(0),
|
|
@@ -2484,11 +2774,11 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2484
2774
|
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">1000", ">=500", "<100", "<=50", "10..100", or exact number "50"'),
|
|
2485
2775
|
])
|
|
2486
2776
|
.optional()
|
|
2487
|
-
.describe('Star count filter. Format: ">1000" (more than), ">=500" (more than or equal), "<100" (less than), "<=50" (less than or equal), "100..1000" (range), "500" (exact).
|
|
2777
|
+
.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).'),
|
|
2488
2778
|
topic: z
|
|
2489
2779
|
.union([z.string(), z.array(z.string())])
|
|
2490
2780
|
.optional()
|
|
2491
|
-
.describe('Repository topics filter.
|
|
2781
|
+
.describe('Repository topics filter. Excellent for discovering projects and understanding repository ecosystems. Topics use kebab-case format.'),
|
|
2492
2782
|
forks: z
|
|
2493
2783
|
.union([
|
|
2494
2784
|
z.number().int().min(0),
|
|
@@ -2497,7 +2787,7 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2497
2787
|
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">100", ">=50", "<10", "<=5", "10..100", or exact number "5"'),
|
|
2498
2788
|
])
|
|
2499
2789
|
.optional()
|
|
2500
|
-
.describe('Fork count filter. Format: ">100" (more than), ">=50" (more than or equal), "<10" (less than), "<=5" (less than or equal), "10..100" (range), "5" (exact).
|
|
2790
|
+
.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).'),
|
|
2501
2791
|
// Match CLI parameter name exactly
|
|
2502
2792
|
'number-topics': z
|
|
2503
2793
|
.union([
|
|
@@ -2507,40 +2797,40 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2507
2797
|
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">5", ">=3", "<10", "<=2", "3..10", or exact number "5"'),
|
|
2508
2798
|
])
|
|
2509
2799
|
.optional()
|
|
2510
|
-
.describe('Number of topics filter. Format: ">5" (many topics), ">=3" (at least 3), "<10" (few topics), "1..3" (range), "5" (exact).
|
|
2800
|
+
.describe('Number of topics filter. Format: ">5" (many topics), ">=3" (at least 3), "<10" (few topics), "1..3" (range), "5" (exact).'),
|
|
2511
2801
|
// QUALITY & STATE FILTERS
|
|
2512
2802
|
license: z
|
|
2513
2803
|
.union([z.string(), z.array(z.string())])
|
|
2514
2804
|
.optional()
|
|
2515
|
-
.describe('License filter.
|
|
2805
|
+
.describe('License filter.'),
|
|
2516
2806
|
archived: z
|
|
2517
2807
|
.boolean()
|
|
2518
2808
|
.optional()
|
|
2519
|
-
.describe('Archive status filter. false (active repos only), true (archived repos only).
|
|
2809
|
+
.describe('Archive status filter. false (active repos only), true (archived repos only).'),
|
|
2520
2810
|
'include-forks': z
|
|
2521
2811
|
.enum(['false', 'true', 'only'])
|
|
2522
2812
|
.optional()
|
|
2523
|
-
.describe('Fork inclusion. "false" (exclude forks), "true" (include forks), "only" (forks only).
|
|
2813
|
+
.describe('Fork inclusion. "false" (exclude forks), "true" (include forks), "only" (forks only).'),
|
|
2524
2814
|
visibility: z
|
|
2525
2815
|
.enum(['public', 'private', 'internal'])
|
|
2526
2816
|
.optional()
|
|
2527
|
-
.describe('Repository visibility.
|
|
2817
|
+
.describe('Repository visibility.'),
|
|
2528
2818
|
// DATE & SIZE FILTERS
|
|
2529
2819
|
created: z
|
|
2530
2820
|
.string()
|
|
2531
2821
|
.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"')
|
|
2532
2822
|
.optional()
|
|
2533
|
-
.describe('Repository creation date filter. Format: ">2020-01-01" (after), ">=2020-01-01" (on or after), "<2023-12-31" (before), "<=2023-12-31" (on or before), "2020-01-01..2023-12-31" (range), "2023-01-01" (exact).
|
|
2823
|
+
.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).'),
|
|
2534
2824
|
updated: z
|
|
2535
2825
|
.string()
|
|
2536
2826
|
.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"')
|
|
2537
2827
|
.optional()
|
|
2538
|
-
.describe('Last updated date filter. Format: ">2024-01-01" (recently updated), ">=2024-01-01" (on or after), "<2022-01-01" (not recently updated), "2023-01-01..2024-12-31" (range).
|
|
2828
|
+
.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).'),
|
|
2539
2829
|
size: z
|
|
2540
2830
|
.string()
|
|
2541
2831
|
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid size format. Use: ">1000", ">=500", "<100", "<=50", "100..1000", or exact number "500"')
|
|
2542
2832
|
.optional()
|
|
2543
|
-
.describe('Repository size filter in KB. Format: ">1000" (large projects), ">=500" (medium-large), "<100" (small projects), "<=50" (tiny), "100..1000" (medium range), "500" (exact).
|
|
2833
|
+
.describe('Repository size filter in KB. Format: ">1000" (large projects), ">=500" (medium-large), "<100" (small projects), "<=50" (tiny), "100..1000" (medium range), "500" (exact).'),
|
|
2544
2834
|
// COMMUNITY FILTERS - Match CLI parameter names exactly
|
|
2545
2835
|
'good-first-issues': z
|
|
2546
2836
|
.union([
|
|
@@ -2550,7 +2840,7 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2550
2840
|
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: number, ">5", ">=10", "<20", "<=15", or "5..20"'),
|
|
2551
2841
|
])
|
|
2552
2842
|
.optional()
|
|
2553
|
-
.describe('Good first issues count. Format: ">5" (many beginner issues), "1..10" (some beginner issues).
|
|
2843
|
+
.describe('Good first issues count. Format: ">5" (many beginner issues), "1..10" (some beginner issues).'),
|
|
2554
2844
|
'help-wanted-issues': z
|
|
2555
2845
|
.union([
|
|
2556
2846
|
z.number().int().min(0),
|
|
@@ -2559,7 +2849,7 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2559
2849
|
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: number, ">5", ">=10", "<20", "<=15", or "5..20"'),
|
|
2560
2850
|
])
|
|
2561
2851
|
.optional()
|
|
2562
|
-
.describe('Help wanted issues count. Format: ">10" (many help wanted), "1..5" (some help wanted).
|
|
2852
|
+
.describe('Help wanted issues count. Format: ">10" (many help wanted), "1..5" (some help wanted).'),
|
|
2563
2853
|
followers: z
|
|
2564
2854
|
.union([
|
|
2565
2855
|
z.number().int().min(0),
|
|
@@ -2568,12 +2858,15 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2568
2858
|
.regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">1000", ">=500", "<100", "<=50", "100..1000", or exact number "500"'),
|
|
2569
2859
|
])
|
|
2570
2860
|
.optional()
|
|
2571
|
-
.describe('Repository owner followers count. Format: ">1000" (popular developers), ">=500" (established developers), "<100" (smaller developers), "100..1000" (range).
|
|
2572
|
-
// SEARCH SCOPE
|
|
2861
|
+
.describe('Repository owner followers count. Format: ">1000" (popular developers), ">=500" (established developers), "<100" (smaller developers), "100..1000" (range).'),
|
|
2862
|
+
// SEARCH SCOPE - Match CLI exactly
|
|
2573
2863
|
match: z
|
|
2574
|
-
.
|
|
2864
|
+
.union([
|
|
2865
|
+
z.enum(['name', 'description', 'readme']),
|
|
2866
|
+
z.array(z.enum(['name', 'description', 'readme'])),
|
|
2867
|
+
])
|
|
2575
2868
|
.optional()
|
|
2576
|
-
.describe('Search scope. "name" (repository names only), "description" (descriptions only), "readme" (README content).
|
|
2869
|
+
.describe('Search scope. "name" (repository names only), "description" (descriptions only), "readme" (README content). Can be single value or array.'),
|
|
2577
2870
|
// SORTING & LIMITS - Match CLI defaults exactly
|
|
2578
2871
|
sort: z
|
|
2579
2872
|
.enum([
|
|
@@ -2585,12 +2878,12 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2585
2878
|
])
|
|
2586
2879
|
.optional()
|
|
2587
2880
|
.default('best-match')
|
|
2588
|
-
.describe('Sort criteria.
|
|
2881
|
+
.describe('Sort criteria.'),
|
|
2589
2882
|
order: z
|
|
2590
2883
|
.enum(['asc', 'desc'])
|
|
2591
2884
|
.optional()
|
|
2592
2885
|
.default('desc')
|
|
2593
|
-
.describe('Sort order direction.
|
|
2886
|
+
.describe('Sort order direction.'),
|
|
2594
2887
|
limit: z
|
|
2595
2888
|
.number()
|
|
2596
2889
|
.int()
|
|
@@ -2598,7 +2891,7 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2598
2891
|
.max(100)
|
|
2599
2892
|
.optional()
|
|
2600
2893
|
.default(30)
|
|
2601
|
-
.describe('Maximum number of repositories to return (1-100).
|
|
2894
|
+
.describe('Maximum number of repositories to return (1-100).'),
|
|
2602
2895
|
},
|
|
2603
2896
|
annotations: {
|
|
2604
2897
|
title: 'GitHub Repository Search',
|
|
@@ -2637,34 +2930,87 @@ function registerSearchGitHubReposTool(server) {
|
|
|
2637
2930
|
enhancedArgs.forks;
|
|
2638
2931
|
if (!hasPrimaryFilter) {
|
|
2639
2932
|
return createResult({
|
|
2640
|
-
error:
|
|
2933
|
+
error: `Repository search requires at least one filter. Try these patterns:
|
|
2934
|
+
• Topic discovery: { topic: ["react", "typescript"] }
|
|
2935
|
+
• Quality search: { stars: ">1000", language: "javascript" }
|
|
2936
|
+
• Organization: { owner: "microsoft", language: "python" }
|
|
2937
|
+
• Recent projects: { created: ">2023-01-01", stars: ">100" }`,
|
|
2641
2938
|
});
|
|
2642
2939
|
}
|
|
2643
2940
|
// First attempt: Search with current parameters
|
|
2644
2941
|
const result = await searchGitHubRepos(enhancedArgs);
|
|
2942
|
+
if (result.isError) {
|
|
2943
|
+
const errorMsg = result.content?.[0]?.text || '';
|
|
2944
|
+
// Smart fallbacks based on error type
|
|
2945
|
+
if (errorMsg.includes('rate limit')) {
|
|
2946
|
+
return createResult({
|
|
2947
|
+
error: `GitHub API rate limit. Smart alternatives:
|
|
2948
|
+
• Try npm package search for package discovery
|
|
2949
|
+
• Use broader filters (remove stars/forks constraints)
|
|
2950
|
+
• Search fewer organizations at once
|
|
2951
|
+
• Wait 5-10 minutes and retry`,
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2954
|
+
if (errorMsg.includes('authentication')) {
|
|
2955
|
+
return createResult({
|
|
2956
|
+
error: `Authentication required. Quick fix:
|
|
2957
|
+
1. Run: gh auth login
|
|
2958
|
+
2. For private repos: use api_status_check to verify access
|
|
2959
|
+
3. Public repos should work without auth - check query`,
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
return result; // Return original error for other cases
|
|
2963
|
+
}
|
|
2964
|
+
// Check if we got results
|
|
2965
|
+
const resultData = JSON.parse(result.content[0].text);
|
|
2966
|
+
const hasResults = resultData.total_count > 0;
|
|
2967
|
+
// Smart fallback strategies for no results
|
|
2968
|
+
if (!hasResults) {
|
|
2969
|
+
const fallbackSuggestions = [];
|
|
2970
|
+
if (enhancedArgs.query) {
|
|
2971
|
+
fallbackSuggestions.push('• Try broader search terms or remove query filter');
|
|
2972
|
+
}
|
|
2973
|
+
if (enhancedArgs.language) {
|
|
2974
|
+
fallbackSuggestions.push('• Remove language filter for broader discovery');
|
|
2975
|
+
}
|
|
2976
|
+
if (enhancedArgs.stars || enhancedArgs.forks) {
|
|
2977
|
+
fallbackSuggestions.push('• Lower quality thresholds (stars/forks)');
|
|
2978
|
+
}
|
|
2979
|
+
if (!enhancedArgs.topic) {
|
|
2980
|
+
fallbackSuggestions.push('• Try topic-based search: { topic: ["web", "api"] }');
|
|
2981
|
+
}
|
|
2982
|
+
if (enhancedArgs.owner) {
|
|
2983
|
+
fallbackSuggestions.push('• Search without owner filter for global discovery');
|
|
2984
|
+
fallbackSuggestions.push('• Use npm package search if looking for packages');
|
|
2985
|
+
}
|
|
2986
|
+
return createResult({
|
|
2987
|
+
error: `No repositories found. Try these alternatives:
|
|
2988
|
+
${fallbackSuggestions.join('\n')}
|
|
2989
|
+
|
|
2990
|
+
Discovery patterns:
|
|
2991
|
+
• Topic exploration: { topic: ["react"] }
|
|
2992
|
+
• Quality discovery: { stars: ">100", language: "python" }
|
|
2993
|
+
• Recent activity: { updated: ">2024-01-01" }`,
|
|
2994
|
+
});
|
|
2995
|
+
}
|
|
2645
2996
|
// Fallback for private repositories: If no results and owner is specified, try with private visibility
|
|
2646
|
-
if (!
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
enhancedArgs
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
...privateData,
|
|
2664
|
-
note: 'Found results in private repositories within the specified organization.',
|
|
2665
|
-
},
|
|
2666
|
-
});
|
|
2667
|
-
}
|
|
2997
|
+
if (enhancedArgs.owner && !enhancedArgs.visibility) {
|
|
2998
|
+
// Try searching with private visibility for organization repos
|
|
2999
|
+
const privateSearchArgs = {
|
|
3000
|
+
...enhancedArgs,
|
|
3001
|
+
visibility: 'private',
|
|
3002
|
+
};
|
|
3003
|
+
const privateResult = await searchGitHubRepos(privateSearchArgs);
|
|
3004
|
+
if (!privateResult.isError) {
|
|
3005
|
+
const privateData = JSON.parse(privateResult.content[0].text);
|
|
3006
|
+
if (privateData.total_count > 0) {
|
|
3007
|
+
// Return private results with note
|
|
3008
|
+
return createResult({
|
|
3009
|
+
data: {
|
|
3010
|
+
...privateData,
|
|
3011
|
+
note: 'Found results in private repositories within the specified organization.',
|
|
3012
|
+
},
|
|
3013
|
+
});
|
|
2668
3014
|
}
|
|
2669
3015
|
}
|
|
2670
3016
|
}
|
|
@@ -2819,17 +3165,300 @@ function buildGitHubReposSearchCommand(params) {
|
|
|
2819
3165
|
// SEARCH SCOPE
|
|
2820
3166
|
addArg('match', 'match');
|
|
2821
3167
|
// SORTING AND LIMITS
|
|
2822
|
-
addArg('limit', 'limit');
|
|
2823
|
-
addArg('order', 'order');
|
|
2824
3168
|
const sortBy = params.sort || 'best-match';
|
|
2825
3169
|
if (sortBy !== 'best-match') {
|
|
2826
3170
|
args.push(`--sort=${sortBy}`);
|
|
2827
3171
|
}
|
|
3172
|
+
addArg('order', 'order');
|
|
3173
|
+
// Always add limit with default of 30
|
|
3174
|
+
const limit = params.limit || 30;
|
|
3175
|
+
args.push(`--limit=${limit}`);
|
|
2828
3176
|
return { command: 'search', args };
|
|
2829
3177
|
}
|
|
2830
3178
|
|
|
3179
|
+
const GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME = 'githubViewRepoStructure';
|
|
3180
|
+
const DESCRIPTION$2 = `Explore GitHub repository structure and validate repository access. ESSENTIAL for understanding project organization.
|
|
3181
|
+
|
|
3182
|
+
REPOSITORY VALIDATION:
|
|
3183
|
+
- Verify repository existence and accessibility
|
|
3184
|
+
- Navigate from root to understand project layout
|
|
3185
|
+
- Identify key directories and file patterns
|
|
3186
|
+
|
|
3187
|
+
PROJECT UNDERSTANDING:
|
|
3188
|
+
- Discover configuration files, documentation, source structure
|
|
3189
|
+
- Validate paths before accessing specific files
|
|
3190
|
+
- Understand architecture and organization patterns
|
|
3191
|
+
|
|
3192
|
+
WORKFLOW INTEGRATION:
|
|
3193
|
+
- First step after repository discovery
|
|
3194
|
+
- Guides subsequent file access and code search
|
|
3195
|
+
- Prevents "file not found" errors through validation
|
|
3196
|
+
- Essential for comprehensive repository analysis`;
|
|
3197
|
+
function registerViewRepositoryStructureTool(server) {
|
|
3198
|
+
server.registerTool(GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME, {
|
|
3199
|
+
description: DESCRIPTION$2,
|
|
3200
|
+
inputSchema: {
|
|
3201
|
+
owner: z
|
|
3202
|
+
.string()
|
|
3203
|
+
.min(1)
|
|
3204
|
+
.max(100)
|
|
3205
|
+
.regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/, 'Invalid GitHub username/org format')
|
|
3206
|
+
.describe('Repository owner/organization name (e.g., "facebook", "microsoft"). Do NOT include repository name.'),
|
|
3207
|
+
repo: z
|
|
3208
|
+
.string()
|
|
3209
|
+
.min(1)
|
|
3210
|
+
.max(100)
|
|
3211
|
+
.regex(/^[a-zA-Z0-9._-]+$/, 'Invalid repository name format')
|
|
3212
|
+
.describe(`Repository name under a organization. `),
|
|
3213
|
+
branch: z
|
|
3214
|
+
.string()
|
|
3215
|
+
.min(1)
|
|
3216
|
+
.max(255)
|
|
3217
|
+
.regex(/^[^\s]+$/, 'Branch name cannot contain spaces')
|
|
3218
|
+
.describe('Branch name (e.g., "main", "master", "develop"). Tool will automatically try default branch if specified branch is not found.'),
|
|
3219
|
+
path: z
|
|
3220
|
+
.string()
|
|
3221
|
+
.optional()
|
|
3222
|
+
.default('')
|
|
3223
|
+
.refine(path => !path.includes('..'), 'Path traversal not allowed')
|
|
3224
|
+
.refine(path => path.length <= 500, 'Path too long')
|
|
3225
|
+
.describe('Directory path within repository (e.g., "src", "docs", "src/components"). Leave empty for root directory. Do NOT start with slash.'),
|
|
3226
|
+
},
|
|
3227
|
+
annotations: {
|
|
3228
|
+
title: 'GitHub Repository Explorer',
|
|
3229
|
+
readOnlyHint: true,
|
|
3230
|
+
destructiveHint: false,
|
|
3231
|
+
idempotentHint: true,
|
|
3232
|
+
openWorldHint: true,
|
|
3233
|
+
},
|
|
3234
|
+
}, async (args) => {
|
|
3235
|
+
try {
|
|
3236
|
+
const result = await viewRepositoryStructure(args);
|
|
3237
|
+
return result;
|
|
3238
|
+
}
|
|
3239
|
+
catch (error) {
|
|
3240
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
3241
|
+
return createResult({
|
|
3242
|
+
error: `Failed to explore repository. ${errorMessage}. Verify repository exists and is accessible`,
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3245
|
+
});
|
|
3246
|
+
}
|
|
3247
|
+
/**
|
|
3248
|
+
* Views the structure of a GitHub repository at a specific path.
|
|
3249
|
+
* Optimized for code analysis workflows with smart defaults and clear errors.
|
|
3250
|
+
*/
|
|
3251
|
+
async function viewRepositoryStructure(params) {
|
|
3252
|
+
const cacheKey = generateCacheKey('gh-repo-structure', params);
|
|
3253
|
+
return withCache(cacheKey, async () => {
|
|
3254
|
+
const { owner, repo, branch, path = '' } = params;
|
|
3255
|
+
try {
|
|
3256
|
+
// Clean up path
|
|
3257
|
+
const cleanPath = path.startsWith('/') ? path.substring(1) : path;
|
|
3258
|
+
// Try the requested branch first, then fallback to main/master
|
|
3259
|
+
const branchesToTry = await getSmartBranchFallback(owner, repo, branch);
|
|
3260
|
+
let items = [];
|
|
3261
|
+
let usedBranch = branch;
|
|
3262
|
+
let lastError = null;
|
|
3263
|
+
let attemptCount = 0;
|
|
3264
|
+
const maxAttempts = branchesToTry.length;
|
|
3265
|
+
const triedBranches = [];
|
|
3266
|
+
for (const tryBranch of branchesToTry) {
|
|
3267
|
+
if (attemptCount >= maxAttempts)
|
|
3268
|
+
break;
|
|
3269
|
+
attemptCount++;
|
|
3270
|
+
triedBranches.push(tryBranch);
|
|
3271
|
+
try {
|
|
3272
|
+
const apiPath = `/repos/${owner}/${repo}/contents/${cleanPath}?ref=${tryBranch}`;
|
|
3273
|
+
const result = await executeGitHubCommand('api', [apiPath], {
|
|
3274
|
+
cache: false,
|
|
3275
|
+
});
|
|
3276
|
+
if (!result.isError) {
|
|
3277
|
+
const execResult = JSON.parse(result.content[0].text);
|
|
3278
|
+
const apiItems = execResult.result;
|
|
3279
|
+
items = Array.isArray(apiItems) ? apiItems : [apiItems];
|
|
3280
|
+
usedBranch = tryBranch;
|
|
3281
|
+
break;
|
|
3282
|
+
}
|
|
3283
|
+
else {
|
|
3284
|
+
lastError = new Error(result.content[0].text);
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
catch (error) {
|
|
3288
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3289
|
+
continue;
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
if (items.length === 0) {
|
|
3293
|
+
// Check repository existence only after content fetch fails
|
|
3294
|
+
const repoCheckResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
|
|
3295
|
+
cache: false,
|
|
3296
|
+
});
|
|
3297
|
+
if (repoCheckResult.isError) {
|
|
3298
|
+
const repoErrorMsg = repoCheckResult.content[0].text;
|
|
3299
|
+
if (repoErrorMsg.includes('404')) {
|
|
3300
|
+
return createResult({
|
|
3301
|
+
error: `Repository "${owner}/${repo}" not found. It might have been deleted, renamed, or made private. Use github_search_code to find current location.`,
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
else if (repoErrorMsg.includes('403')) {
|
|
3305
|
+
return createResult({
|
|
3306
|
+
error: `Repository "${owner}/${repo}" exists but access is denied. Repository might be private or archived. Use api_status_check to verify permissions.`,
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
const errorMsg = lastError?.message || 'Unknown error';
|
|
3311
|
+
if (errorMsg.includes('404') || errorMsg.includes('Not Found')) {
|
|
3312
|
+
if (path) {
|
|
3313
|
+
const searchSuggestion = await suggestPathSearchFallback(owner, path);
|
|
3314
|
+
return createResult({
|
|
3315
|
+
error: `Path "${path}" not found in any branch (tried: ${triedBranches.join(', ')}).${searchSuggestion}`,
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
else {
|
|
3319
|
+
return createResult({
|
|
3320
|
+
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.`,
|
|
3321
|
+
});
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
else if (errorMsg.includes('403') || errorMsg.includes('Forbidden')) {
|
|
3325
|
+
return createResult({
|
|
3326
|
+
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.`,
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
else {
|
|
3330
|
+
const searchSuggestion = path
|
|
3331
|
+
? await suggestPathSearchFallback(owner, path)
|
|
3332
|
+
: '';
|
|
3333
|
+
return createResult({
|
|
3334
|
+
error: `Failed to access "${owner}/${repo}": ${errorMsg}. Check network connection and repository permissions.${searchSuggestion}`,
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
// Limit total items to 100 for efficiency
|
|
3339
|
+
const limitedItems = items.slice(0, 100);
|
|
3340
|
+
// Sort: directories first, then alphabetically
|
|
3341
|
+
limitedItems.sort((a, b) => {
|
|
3342
|
+
if (a.type !== b.type) {
|
|
3343
|
+
return a.type === 'dir' ? -1 : 1;
|
|
3344
|
+
}
|
|
3345
|
+
return a.name.localeCompare(b.name);
|
|
3346
|
+
});
|
|
3347
|
+
// Create simplified, token-efficient structure
|
|
3348
|
+
const files = limitedItems
|
|
3349
|
+
.filter(item => item.type === 'file')
|
|
3350
|
+
.map(item => ({
|
|
3351
|
+
name: item.name,
|
|
3352
|
+
size: item.size,
|
|
3353
|
+
url: item.path, // Use path for fetching
|
|
3354
|
+
}));
|
|
3355
|
+
const folders = limitedItems
|
|
3356
|
+
.filter(item => item.type === 'dir')
|
|
3357
|
+
.map(item => ({
|
|
3358
|
+
name: item.name,
|
|
3359
|
+
url: item.path, // Use path for browsing
|
|
3360
|
+
}));
|
|
3361
|
+
return createResult({
|
|
3362
|
+
data: {
|
|
3363
|
+
repository: `${owner}/${repo}`,
|
|
3364
|
+
branch: usedBranch,
|
|
3365
|
+
path: cleanPath || '/',
|
|
3366
|
+
githubBasePath: `https://api.github.com/repos/${owner}/${repo}/contents/`,
|
|
3367
|
+
files: {
|
|
3368
|
+
count: files.length,
|
|
3369
|
+
files: files,
|
|
3370
|
+
},
|
|
3371
|
+
folders: {
|
|
3372
|
+
count: folders.length,
|
|
3373
|
+
folders: folders,
|
|
3374
|
+
},
|
|
3375
|
+
},
|
|
3376
|
+
});
|
|
3377
|
+
}
|
|
3378
|
+
catch (error) {
|
|
3379
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3380
|
+
return createResult({
|
|
3381
|
+
error: `Failed to access repository "${owner}/${repo}": ${errorMessage}. Verify repository name, permissions, and network connection.`,
|
|
3382
|
+
});
|
|
3383
|
+
}
|
|
3384
|
+
});
|
|
3385
|
+
}
|
|
3386
|
+
/**
|
|
3387
|
+
* Smart branch detection with automatic fallback to common branch names.
|
|
3388
|
+
* Now includes more comprehensive branch detection and better error handling.
|
|
3389
|
+
*/
|
|
3390
|
+
async function getSmartBranchFallback(owner, repo, requestedBranch) {
|
|
3391
|
+
const branches = new Set([requestedBranch]);
|
|
3392
|
+
try {
|
|
3393
|
+
// Try to get repository info to find default branch
|
|
3394
|
+
const repoInfoResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
|
|
3395
|
+
cache: false,
|
|
3396
|
+
});
|
|
3397
|
+
if (!repoInfoResult.isError) {
|
|
3398
|
+
const execResult = JSON.parse(repoInfoResult.content[0].text);
|
|
3399
|
+
const repoData = execResult.result;
|
|
3400
|
+
const defaultBranch = repoData.default_branch;
|
|
3401
|
+
if (defaultBranch) {
|
|
3402
|
+
branches.add(defaultBranch);
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
catch {
|
|
3407
|
+
// If we can't get repo info, proceed with standard fallbacks
|
|
3408
|
+
}
|
|
3409
|
+
// Add only main/master as fallback branches
|
|
3410
|
+
const commonBranches = ['main', 'master'];
|
|
3411
|
+
commonBranches.forEach(branch => branches.add(branch));
|
|
3412
|
+
// Convert Set back to array, with requested branch first
|
|
3413
|
+
const branchesArray = Array.from(branches);
|
|
3414
|
+
branchesArray.sort((a, b) => {
|
|
3415
|
+
if (a === requestedBranch)
|
|
3416
|
+
return -1;
|
|
3417
|
+
if (b === requestedBranch)
|
|
3418
|
+
return 1;
|
|
3419
|
+
return 0;
|
|
3420
|
+
});
|
|
3421
|
+
return branchesArray;
|
|
3422
|
+
}
|
|
3423
|
+
// Helper function to suggest path search strategy
|
|
3424
|
+
async function suggestPathSearchFallback(owner, path) {
|
|
3425
|
+
try {
|
|
3426
|
+
// Extract last path segment and try to find in same organization
|
|
3427
|
+
const pathSegment = path.split('/').pop() || path;
|
|
3428
|
+
const searchResult = await executeGitHubCommand('api', [
|
|
3429
|
+
`/search/code?q=${encodeURIComponent(pathSegment)}+in:path+org:${owner}`,
|
|
3430
|
+
], { cache: false });
|
|
3431
|
+
if (!searchResult.isError) {
|
|
3432
|
+
const results = JSON.parse(searchResult.content[0].text);
|
|
3433
|
+
if (results.total_count > 0) {
|
|
3434
|
+
const firstMatch = results.items[0];
|
|
3435
|
+
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}"`;
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
catch {
|
|
3440
|
+
// Fallback to generic message if search fails
|
|
3441
|
+
}
|
|
3442
|
+
return ` Try these searches:\n1. github_search_code with query="${path.split('/').pop()}" owner="${owner}"\n2. github_search_code with query="path:${path}"`;
|
|
3443
|
+
}
|
|
3444
|
+
|
|
2831
3445
|
const NPM_PACKAGE_SEARCH_TOOL_NAME = 'npmPackageSearch';
|
|
2832
|
-
const DESCRIPTION$1 = `Search NPM packages
|
|
3446
|
+
const DESCRIPTION$1 = `Search NPM packages by functionality keywords. PRIMARY ENTRY POINT for package-related queries.
|
|
3447
|
+
|
|
3448
|
+
PACKAGE-FIRST STRATEGY:
|
|
3449
|
+
- Start here when users mention: libraries, dependencies, installations, alternatives
|
|
3450
|
+
- Use broad functional terms for discovery (not exact package names)
|
|
3451
|
+
- Bridge to GitHub tools via repository URLs from results
|
|
3452
|
+
|
|
3453
|
+
SEARCH APPROACH:
|
|
3454
|
+
- Single functional terms work best for discovery
|
|
3455
|
+
- Multiple searches for different aspects/use-cases
|
|
3456
|
+
- Reveals ecosystem alternatives and quality indicators
|
|
3457
|
+
|
|
3458
|
+
INTEGRATION WORKFLOW:
|
|
3459
|
+
- Package Discovery → Repository Analysis → Implementation Patterns
|
|
3460
|
+
- npmPackageSearch → npmViewPackage → GitHub repository tools
|
|
3461
|
+
- Compare alternatives by searching different functional terms`;
|
|
2833
3462
|
const MAX_DESCRIPTION_LENGTH = 100;
|
|
2834
3463
|
const MAX_KEYWORDS = 10;
|
|
2835
3464
|
function registerNpmSearchTool(server) {
|
|
@@ -2838,7 +3467,7 @@ function registerNpmSearchTool(server) {
|
|
|
2838
3467
|
inputSchema: {
|
|
2839
3468
|
queries: z
|
|
2840
3469
|
.union([z.string(), z.array(z.string())])
|
|
2841
|
-
.describe('Search terms for packages.
|
|
3470
|
+
.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.'),
|
|
2842
3471
|
searchLimit: z
|
|
2843
3472
|
.number()
|
|
2844
3473
|
.int()
|
|
@@ -2864,28 +3493,136 @@ function registerNpmSearchTool(server) {
|
|
|
2864
3493
|
const allPackages = [];
|
|
2865
3494
|
// Search for each query term
|
|
2866
3495
|
for (const query of queries) {
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
3496
|
+
try {
|
|
3497
|
+
const result = await executeNpmCommand('search', [query, `--searchlimit=${searchLimit}`, '--json'], { cache: true });
|
|
3498
|
+
if (!result.isError && result.content?.[0]?.text) {
|
|
3499
|
+
const packages = parseNpmSearchOutput(result.content[0].text);
|
|
3500
|
+
allPackages.push(...packages);
|
|
3501
|
+
}
|
|
3502
|
+
else if (result.isError) {
|
|
3503
|
+
// Individual query failures are handled silently, continue with others
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
catch (queryError) {
|
|
3507
|
+
// Continue with other queries even if one fails
|
|
2871
3508
|
}
|
|
2872
3509
|
}
|
|
2873
3510
|
const deduplicatedPackages = deduplicatePackages(allPackages);
|
|
2874
3511
|
if (deduplicatedPackages.length > 0) {
|
|
3512
|
+
const { nextSteps } = getToolSuggestions(TOOL_NAMES.NPM_PACKAGE_SEARCH, { hasResults: true });
|
|
3513
|
+
const hints = [];
|
|
3514
|
+
if (nextSteps.length > 0) {
|
|
3515
|
+
hints.push('Next steps:');
|
|
3516
|
+
nextSteps.forEach(({ tool, reason }) => {
|
|
3517
|
+
hints.push(`• Use ${tool} ${reason}`);
|
|
3518
|
+
});
|
|
3519
|
+
}
|
|
2875
3520
|
return createResult({
|
|
2876
3521
|
data: {
|
|
2877
3522
|
total_count: deduplicatedPackages.length,
|
|
2878
3523
|
results: deduplicatedPackages,
|
|
3524
|
+
hints: hints.length > 0 ? hints : undefined,
|
|
2879
3525
|
},
|
|
2880
3526
|
});
|
|
2881
3527
|
}
|
|
3528
|
+
// Smart fallback suggestions based on query patterns
|
|
3529
|
+
const hasSpecificTerms = queries.some(q => q.includes('-') || q.includes('@') || q.length > 15);
|
|
3530
|
+
const hasFrameworkTerms = queries.some(q => ['react', 'vue', 'angular', 'express', 'fastify'].some(fw => q.toLowerCase().includes(fw)));
|
|
3531
|
+
let fallbackSuggestions = [
|
|
3532
|
+
'• Try broader functional terms: "testing" instead of "jest-unit-test"',
|
|
3533
|
+
'• Remove version numbers or specific constraints',
|
|
3534
|
+
'• Use single keywords: "http" instead of "http-client-library"',
|
|
3535
|
+
];
|
|
3536
|
+
if (hasSpecificTerms) {
|
|
3537
|
+
fallbackSuggestions = [
|
|
3538
|
+
'• Use simpler terms: "validation" instead of "schema-validation-library"',
|
|
3539
|
+
'• Try category terms: "database", "testing", "auth"',
|
|
3540
|
+
...fallbackSuggestions.slice(1),
|
|
3541
|
+
];
|
|
3542
|
+
}
|
|
3543
|
+
if (hasFrameworkTerms) {
|
|
3544
|
+
fallbackSuggestions.unshift('• Try specific framework searches: "react hooks", "vue components"');
|
|
3545
|
+
}
|
|
3546
|
+
// Add GitHub integration suggestions
|
|
3547
|
+
fallbackSuggestions.push('• Use github_search_repos with topic filters for project discovery');
|
|
3548
|
+
fallbackSuggestions.push('• Check npm registry status: https://status.npmjs.org');
|
|
3549
|
+
const { fallback } = getToolSuggestions(TOOL_NAMES.NPM_PACKAGE_SEARCH, {
|
|
3550
|
+
errorType: 'no_results',
|
|
3551
|
+
});
|
|
3552
|
+
const toolSuggestions = createToolSuggestion(TOOL_NAMES.NPM_PACKAGE_SEARCH, fallback);
|
|
2882
3553
|
return createResult({
|
|
2883
|
-
error:
|
|
3554
|
+
error: getErrorWithSuggestion({
|
|
3555
|
+
baseError: createNpmPackageNotFoundError(queries.join(', ')),
|
|
3556
|
+
suggestion: [fallbackSuggestions.join('\n'), toolSuggestions],
|
|
3557
|
+
}),
|
|
2884
3558
|
});
|
|
2885
3559
|
}
|
|
2886
3560
|
catch (error) {
|
|
3561
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3562
|
+
// Network/connectivity issues
|
|
3563
|
+
if (errorMsg.includes('network') ||
|
|
3564
|
+
errorMsg.includes('timeout') ||
|
|
3565
|
+
errorMsg.includes('ENOTFOUND')) {
|
|
3566
|
+
const { fallback } = getToolSuggestions(TOOL_NAMES.NPM_PACKAGE_SEARCH, { });
|
|
3567
|
+
return createResult({
|
|
3568
|
+
error: getErrorWithSuggestion({
|
|
3569
|
+
baseError: ERROR_MESSAGES.NPM_CONNECTION_FAILED,
|
|
3570
|
+
suggestion: [
|
|
3571
|
+
'• Check internet connection and npm registry status',
|
|
3572
|
+
'• Try fewer search terms to reduce load',
|
|
3573
|
+
'• Retry in a few moments',
|
|
3574
|
+
createToolSuggestion(TOOL_NAMES.NPM_PACKAGE_SEARCH, fallback),
|
|
3575
|
+
],
|
|
3576
|
+
}),
|
|
3577
|
+
});
|
|
3578
|
+
}
|
|
3579
|
+
// NPM CLI issues
|
|
3580
|
+
if (errorMsg.includes('command not found') ||
|
|
3581
|
+
errorMsg.includes('npm')) {
|
|
3582
|
+
const { fallback } = getToolSuggestions(TOOL_NAMES.NPM_PACKAGE_SEARCH, { });
|
|
3583
|
+
return createResult({
|
|
3584
|
+
error: getErrorWithSuggestion({
|
|
3585
|
+
baseError: ERROR_MESSAGES.NPM_CLI_ERROR,
|
|
3586
|
+
suggestion: [
|
|
3587
|
+
'• Verify NPM installation: npm --version',
|
|
3588
|
+
'• Update NPM: npm install -g npm@latest',
|
|
3589
|
+
'• Check PATH environment variable',
|
|
3590
|
+
createToolSuggestion(TOOL_NAMES.NPM_PACKAGE_SEARCH, fallback),
|
|
3591
|
+
],
|
|
3592
|
+
}),
|
|
3593
|
+
});
|
|
3594
|
+
}
|
|
3595
|
+
// Permission/auth issues
|
|
3596
|
+
if (errorMsg.includes('permission') ||
|
|
3597
|
+
errorMsg.includes('403') ||
|
|
3598
|
+
errorMsg.includes('401')) {
|
|
3599
|
+
const { fallback } = getToolSuggestions(TOOL_NAMES.NPM_PACKAGE_SEARCH, { errorType: 'access_denied' });
|
|
3600
|
+
return createResult({
|
|
3601
|
+
error: getErrorWithSuggestion({
|
|
3602
|
+
baseError: ERROR_MESSAGES.NPM_PERMISSION_ERROR,
|
|
3603
|
+
suggestion: [
|
|
3604
|
+
'• Check npm login status: npm whoami',
|
|
3605
|
+
'• Use public registry search without auth',
|
|
3606
|
+
'• Verify npm registry configuration',
|
|
3607
|
+
createToolSuggestion(TOOL_NAMES.NPM_PACKAGE_SEARCH, fallback),
|
|
3608
|
+
],
|
|
3609
|
+
}),
|
|
3610
|
+
});
|
|
3611
|
+
}
|
|
3612
|
+
const { fallback } = getToolSuggestions(TOOL_NAMES.NPM_PACKAGE_SEARCH, {
|
|
3613
|
+
});
|
|
2887
3614
|
return createResult({
|
|
2888
|
-
error:
|
|
3615
|
+
error: getErrorWithSuggestion({
|
|
3616
|
+
baseError: ERROR_MESSAGES.PACKAGE_SEARCH_FAILED,
|
|
3617
|
+
suggestion: [
|
|
3618
|
+
`Error details: ${errorMsg}`,
|
|
3619
|
+
'',
|
|
3620
|
+
'Fallback strategies:',
|
|
3621
|
+
'• Check npm status and retry',
|
|
3622
|
+
'• Use broader search terms',
|
|
3623
|
+
createToolSuggestion(TOOL_NAMES.NPM_PACKAGE_SEARCH, fallback),
|
|
3624
|
+
],
|
|
3625
|
+
}),
|
|
2889
3626
|
});
|
|
2890
3627
|
}
|
|
2891
3628
|
});
|
|
@@ -2937,7 +3674,25 @@ function parseNpmSearchOutput(output) {
|
|
|
2937
3674
|
}
|
|
2938
3675
|
|
|
2939
3676
|
const NPM_VIEW_PACKAGE_TOOL_NAME = 'npmViewPackage';
|
|
2940
|
-
const DESCRIPTION = `
|
|
3677
|
+
const DESCRIPTION = `Analyze NPM packages for repository discovery and dependency insights. BRIDGE to GitHub ecosystem.
|
|
3678
|
+
|
|
3679
|
+
PACKAGE ANALYSIS CAPABILITIES:
|
|
3680
|
+
- Repository URL discovery for GitHub exploration
|
|
3681
|
+
- Version history and release patterns
|
|
3682
|
+
- Export analysis for implementation understanding
|
|
3683
|
+
- Dependency metadata for ecosystem mapping
|
|
3684
|
+
|
|
3685
|
+
GITHUB INTEGRATION:
|
|
3686
|
+
- Provides repository links for GitHub repository tools
|
|
3687
|
+
- Connects package metadata to source code analysis
|
|
3688
|
+
- Enables package-to-implementation research workflows
|
|
3689
|
+
- Essential for dependency and alternative evaluation
|
|
3690
|
+
|
|
3691
|
+
USE CASES:
|
|
3692
|
+
- Repository discovery from package names
|
|
3693
|
+
- Version analysis and security assessment
|
|
3694
|
+
- Export structure for integration planning
|
|
3695
|
+
- Dependency research and ecosystem exploration`;
|
|
2941
3696
|
function registerNpmViewPackageTool(server) {
|
|
2942
3697
|
server.registerTool(NPM_VIEW_PACKAGE_TOOL_NAME, {
|
|
2943
3698
|
description: DESCRIPTION,
|
|
@@ -2945,7 +3700,7 @@ function registerNpmViewPackageTool(server) {
|
|
|
2945
3700
|
packageName: z
|
|
2946
3701
|
.string()
|
|
2947
3702
|
.min(1)
|
|
2948
|
-
.describe('NPM package name (e.g., "react", "express", "@types/node")'),
|
|
3703
|
+
.describe('NPM package name (e.g., "react", "express", "@types/node"). Include @ prefix for scoped packages.'),
|
|
2949
3704
|
},
|
|
2950
3705
|
annotations: {
|
|
2951
3706
|
title: 'NPM Package Analyzer',
|
|
@@ -2968,18 +3723,87 @@ function registerNpmViewPackageTool(server) {
|
|
|
2968
3723
|
}
|
|
2969
3724
|
catch (error) {
|
|
2970
3725
|
const errorMessage = error.message || '';
|
|
2971
|
-
|
|
3726
|
+
// Package not found with smart discovery
|
|
3727
|
+
if (errorMessage.includes('not found') ||
|
|
3728
|
+
errorMessage.includes('404')) {
|
|
3729
|
+
const packageName = args.packageName;
|
|
3730
|
+
const suggestions = [];
|
|
3731
|
+
// Check for common naming patterns
|
|
3732
|
+
if (packageName.includes('_')) {
|
|
3733
|
+
suggestions.push(`• Try with dashes: "${packageName.replace(/_/g, '-')}"`);
|
|
3734
|
+
}
|
|
3735
|
+
if (packageName.includes('-')) {
|
|
3736
|
+
suggestions.push(`• Try without dashes: "${packageName.replace(/-/g, '')}"`);
|
|
3737
|
+
}
|
|
3738
|
+
if (!packageName.startsWith('@') && packageName.includes('/')) {
|
|
3739
|
+
suggestions.push(`• Try scoped package: "@${packageName}"`);
|
|
3740
|
+
}
|
|
3741
|
+
if (packageName.startsWith('@')) {
|
|
3742
|
+
suggestions.push(`• Try without scope: "${packageName.split('/')[1]}"`);
|
|
3743
|
+
}
|
|
3744
|
+
// Add discovery alternatives
|
|
3745
|
+
suggestions.push('• Use npm_package_search for discovery');
|
|
3746
|
+
suggestions.push('• Use github_search_repos to find source repository');
|
|
3747
|
+
suggestions.push('• Check exact spelling on npmjs.com');
|
|
3748
|
+
return createResult({
|
|
3749
|
+
error: `Package "${packageName}" not found on NPM registry.
|
|
3750
|
+
|
|
3751
|
+
Try these alternatives:
|
|
3752
|
+
${suggestions.join('\n')}
|
|
3753
|
+
|
|
3754
|
+
Discovery workflow:
|
|
3755
|
+
1. Use npm_package_search with functional terms
|
|
3756
|
+
2. Use github_search_repos for related projects
|
|
3757
|
+
3. Verify exact package name on npmjs.com`,
|
|
3758
|
+
});
|
|
3759
|
+
}
|
|
3760
|
+
// Network issues with fallback strategies
|
|
3761
|
+
if (errorMessage.includes('network') ||
|
|
3762
|
+
errorMessage.includes('timeout') ||
|
|
3763
|
+
errorMessage.includes('ENOTFOUND')) {
|
|
3764
|
+
return createResult({
|
|
3765
|
+
error: `NPM registry connection failed. Alternative strategies:
|
|
3766
|
+
• Check internet connection and npm registry status
|
|
3767
|
+
• Use github_search_repos to find package repository
|
|
3768
|
+
• Try npm_package_search for broader discovery
|
|
3769
|
+
• Visit https://npmjs.com/${args.packageName} directly
|
|
3770
|
+
• Retry in a few moments`,
|
|
3771
|
+
});
|
|
3772
|
+
}
|
|
3773
|
+
// NPM CLI issues
|
|
3774
|
+
if (errorMessage.includes('command not found') ||
|
|
3775
|
+
errorMessage.includes('npm')) {
|
|
2972
3776
|
return createResult({
|
|
2973
|
-
error:
|
|
3777
|
+
error: `NPM CLI issue. Quick fixes:
|
|
3778
|
+
• Verify NPM installation: npm --version
|
|
3779
|
+
• Update NPM: npm install -g npm@latest
|
|
3780
|
+
• Use github_search_repos to find package repository
|
|
3781
|
+
• Check PATH environment variable
|
|
3782
|
+
• Try web interface: https://npmjs.com/${args.packageName}`,
|
|
2974
3783
|
});
|
|
2975
3784
|
}
|
|
2976
|
-
|
|
3785
|
+
// Permission/registry issues
|
|
3786
|
+
if (errorMessage.includes('permission') ||
|
|
3787
|
+
errorMessage.includes('403') ||
|
|
3788
|
+
errorMessage.includes('401')) {
|
|
2977
3789
|
return createResult({
|
|
2978
|
-
error:
|
|
3790
|
+
error: `NPM registry access issue. Try these solutions:
|
|
3791
|
+
• Check npm configuration: npm config get registry
|
|
3792
|
+
• Use public registry: npm config set registry https://registry.npmjs.org/
|
|
3793
|
+
• Try github_search_repos for package source code
|
|
3794
|
+
• Visit package page: https://npmjs.com/${args.packageName}`,
|
|
2979
3795
|
});
|
|
2980
3796
|
}
|
|
3797
|
+
// Generic error with comprehensive fallbacks
|
|
2981
3798
|
return createResult({
|
|
2982
|
-
error:
|
|
3799
|
+
error: `Failed to fetch package "${args.packageName}": ${errorMessage}
|
|
3800
|
+
|
|
3801
|
+
Fallback strategies:
|
|
3802
|
+
• Use npm_package_search for similar packages
|
|
3803
|
+
• Use github_search_repos with package name as query
|
|
3804
|
+
• Check package on web: https://npmjs.com/${args.packageName}
|
|
3805
|
+
• Verify npm status: https://status.npmjs.org
|
|
3806
|
+
• Try alternative package managers (yarn info, pnpm view)`,
|
|
2983
3807
|
});
|
|
2984
3808
|
}
|
|
2985
3809
|
});
|
|
@@ -3090,138 +3914,120 @@ async function viewNpmPackage(packageName) {
|
|
|
3090
3914
|
});
|
|
3091
3915
|
}
|
|
3092
3916
|
|
|
3093
|
-
const PROMPT_SYSTEM_PROMPT = `You are an expert code research assistant
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
-
|
|
3127
|
-
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
-
|
|
3132
|
-
-
|
|
3133
|
-
- Verify
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
-
|
|
3137
|
-
-
|
|
3138
|
-
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
-
|
|
3169
|
-
-
|
|
3170
|
-
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
-
|
|
3192
|
-
- Use for
|
|
3193
|
-
- Check
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
-
|
|
3197
|
-
-
|
|
3198
|
-
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
-
|
|
3203
|
-
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
-
|
|
3207
|
-
- Use state filters progressively
|
|
3208
|
-
- Labels reveal project categorization
|
|
3209
|
-
|
|
3210
|
-
### ${NPM_PACKAGE_SEARCH_TOOL_NAME}:
|
|
3211
|
-
- Use functional terms: "router", "validator", "parser"
|
|
3212
|
-
- Search multiple related terms in parallel
|
|
3213
|
-
- Aggregate results for comprehensive view
|
|
3214
|
-
|
|
3215
|
-
### ${NPM_VIEW_PACKAGE_TOOL_NAME}:
|
|
3216
|
-
- Check repository field for source code access
|
|
3217
|
-
- Review exports for API understanding
|
|
3218
|
-
- Use version history to gauge stability
|
|
3219
|
-
|
|
3220
|
-
## 10. CHAIN OF THOUGHT OPTIMIZATION:
|
|
3221
|
-
- Plan search sequence before executing
|
|
3222
|
-
- Document reasoning for each search refinement
|
|
3223
|
-
- Build knowledge progressively, don't jump to specifics
|
|
3224
|
-
- Validate findings with multiple sources
|
|
3917
|
+
const PROMPT_SYSTEM_PROMPT = `You are an expert code research assistant specialized in comprehensive package and repository analysis using GitHub CLI and NPM CLI.
|
|
3918
|
+
|
|
3919
|
+
CORE RESEARCH PHILOSOPHY:
|
|
3920
|
+
- PACKAGE-FIRST: When packages mentioned → start with NPM tools → bridge to GitHub
|
|
3921
|
+
- REPOSITORY-FIRST: When repos mentioned → start with GitHub tools → explore dependencies
|
|
3922
|
+
- CROSS-REFERENCE: Always connect packages to repositories and repositories to packages
|
|
3923
|
+
- PROGRESSIVE: Start broad, refine gradually, use multiple separate searches
|
|
3924
|
+
|
|
3925
|
+
CRITICAL SEARCH STRATEGIES:
|
|
3926
|
+
|
|
3927
|
+
MULTI-SEARCH APPROACH (MOST EFFECTIVE):
|
|
3928
|
+
a) SEPARATE SEARCHES: Individual terms beat complex queries
|
|
3929
|
+
b) PROGRESSIVE REFINEMENT: Start broad → analyze → narrow down
|
|
3930
|
+
c) CROSS-TOOL VALIDATION: Verify findings across GitHub and NPM
|
|
3931
|
+
d) TOPIC DISCOVERY: Use topics as primary discovery mechanism
|
|
3932
|
+
|
|
3933
|
+
PACKAGE-CENTRIC WORKFLOWS:
|
|
3934
|
+
When users mention: libraries, dependencies, packages, installations, versions
|
|
3935
|
+
→ START with ${NPM_PACKAGE_SEARCH_TOOL_NAME} → ${NPM_VIEW_PACKAGE_TOOL_NAME} → GitHub tools
|
|
3936
|
+
|
|
3937
|
+
NPM-TO-GITHUB INTEGRATION PATTERNS:
|
|
3938
|
+
1. Package Discovery: ${NPM_PACKAGE_SEARCH_TOOL_NAME} → get repository URLs → ${GITHUB_SEARCH_REPOSITORIES_TOOL_NAME}
|
|
3939
|
+
2. Dependency Analysis: ${NPM_VIEW_PACKAGE_TOOL_NAME} → repository structure → implementation patterns
|
|
3940
|
+
3. Alternative Research: Multiple ${NPM_PACKAGE_SEARCH_TOOL_NAME} → compare repositories → evaluate quality
|
|
3941
|
+
4. Version Investigation: ${NPM_VIEW_PACKAGE_TOOL_NAME} → commit history → feature evolution
|
|
3942
|
+
|
|
3943
|
+
REPOSITORY-CENTRIC WORKFLOWS:
|
|
3944
|
+
When users mention: codebases, implementations, source code, organizations
|
|
3945
|
+
→ START with ${GITHUB_SEARCH_REPOSITORIES_TOOL_NAME} using TOPICS → drill down to specifics
|
|
3946
|
+
|
|
3947
|
+
COMPREHENSIVE RESEARCH PATTERNS:
|
|
3948
|
+
|
|
3949
|
+
Discovery Phase (Unknown Territory):
|
|
3950
|
+
- TOPICS FIRST: ${GITHUB_SEARCH_REPOSITORIES_TOOL_NAME} with topic combinations
|
|
3951
|
+
- Topics reveal ecosystems, relationships, quality indicators
|
|
3952
|
+
- Example topics: ["framework-name", "use-case", "language"]
|
|
3953
|
+
|
|
3954
|
+
Understanding Phase:
|
|
3955
|
+
- Repository Structure: ${GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME} for project layout
|
|
3956
|
+
- Package Metadata: ${NPM_VIEW_PACKAGE_TOOL_NAME} for dependencies and exports
|
|
3957
|
+
- Cross-validation: Verify package-repo connections
|
|
3958
|
+
|
|
3959
|
+
Implementation Phase:
|
|
3960
|
+
- Code Patterns: ${GITHUB_SEARCH_CODE_TOOL_NAME} with SEPARATE single-term searches
|
|
3961
|
+
- File Access: ${GITHUB_GET_FILE_CONTENT_TOOL_NAME} for specific implementations
|
|
3962
|
+
- Historical Context: ${GITHUB_SEARCH_COMMITS_TOOL_NAME} for feature evolution
|
|
3963
|
+
|
|
3964
|
+
TOOL INTEGRATION BEST PRACTICES:
|
|
3965
|
+
|
|
3966
|
+
${NPM_PACKAGE_SEARCH_TOOL_NAME}:
|
|
3967
|
+
- Primary entry for package-related queries
|
|
3968
|
+
- Use broad functional terms, not exact package names
|
|
3969
|
+
- Bridge to GitHub via repository URLs
|
|
3970
|
+
|
|
3971
|
+
${NPM_VIEW_PACKAGE_TOOL_NAME}:
|
|
3972
|
+
- Essential for repository discovery and dependency analysis
|
|
3973
|
+
- Provides GitHub repository links for further exploration
|
|
3974
|
+
- Critical for version and export analysis
|
|
3975
|
+
|
|
3976
|
+
${GITHUB_SEARCH_REPOSITORIES_TOOL_NAME}:
|
|
3977
|
+
- TOPICS are the most powerful discovery feature
|
|
3978
|
+
- Start with topic/language combinations
|
|
3979
|
+
- Quality indicators: stars, activity, community
|
|
3980
|
+
|
|
3981
|
+
${GITHUB_SEARCH_CODE_TOOL_NAME}:
|
|
3982
|
+
- SEPARATE searches outperform complex queries
|
|
3983
|
+
- Single terms without quotes for broad discovery
|
|
3984
|
+
- Multi-word phrases WITH quotes for exact patterns
|
|
3985
|
+
|
|
3986
|
+
${GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME}:
|
|
3987
|
+
- Always verify repository existence and structure first
|
|
3988
|
+
- Navigate from root to understand project organization
|
|
3989
|
+
- Essential before accessing specific files
|
|
3990
|
+
|
|
3991
|
+
${GITHUB_GET_FILE_CONTENT_TOOL_NAME}:
|
|
3992
|
+
- Use AFTER structure verification
|
|
3993
|
+
- Focus on configuration, documentation, key implementations
|
|
3994
|
+
- Validate paths through structure exploration
|
|
3995
|
+
|
|
3996
|
+
${GITHUB_SEARCH_COMMITS_TOOL_NAME}:
|
|
3997
|
+
- Feature evolution and implementation history
|
|
3998
|
+
- Author patterns and development activity
|
|
3999
|
+
- Date ranges for tracking changes
|
|
4000
|
+
|
|
4001
|
+
${GITHUB_SEARCH_ISSUES_TOOL_NAME} & ${GITHUB_SEARCH_PULL_REQUESTS_TOOL_NAME}:
|
|
4002
|
+
- Problem-solution discovery
|
|
4003
|
+
- Implementation discussions and decisions
|
|
4004
|
+
- Community feedback and feature requests
|
|
4005
|
+
|
|
4006
|
+
${API_STATUS_CHECK_TOOL_NAME}:
|
|
4007
|
+
- First step for private repository access
|
|
4008
|
+
- Organization discovery for scoped searches
|
|
4009
|
+
- Authentication troubleshooting
|
|
4010
|
+
|
|
4011
|
+
INTELLIGENT SEARCH PROGRESSION:
|
|
4012
|
+
|
|
4013
|
+
No Results Strategy:
|
|
4014
|
+
- BROADEN search terms (remove filters)
|
|
4015
|
+
- Try ALTERNATIVE tool (NPM ↔ GitHub)
|
|
4016
|
+
- Use TOPICS for discovery
|
|
4017
|
+
- Check SPELLING and exact names
|
|
4018
|
+
|
|
4019
|
+
Quality Research Indicators:
|
|
4020
|
+
- Cross-tool consistency (NPM package ↔ GitHub repo)
|
|
4021
|
+
- Community metrics (stars, downloads, issues)
|
|
4022
|
+
- Recent activity (commits, releases, discussions)
|
|
4023
|
+
- Documentation quality and completeness
|
|
4024
|
+
|
|
4025
|
+
CHAIN OF RESEARCH OPTIMIZATION:
|
|
4026
|
+
- Plan multi-tool sequences before execution
|
|
4027
|
+
- Connect findings across NPM and GitHub ecosystems
|
|
4028
|
+
- Build comprehensive understanding progressively
|
|
4029
|
+
- Validate technical details with actual code
|
|
4030
|
+
- Provide actionable insights based on data patterns
|
|
3225
4031
|
`;
|
|
3226
4032
|
|
|
3227
4033
|
const SERVER_CONFIG = {
|