octocode-mcp 2.3.7 → 2.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/build/index.js +429 -413
  2. package/package.json +1 -1
package/build/index.js CHANGED
@@ -238,10 +238,15 @@ async function executeGitHubCommand(command, args = [], options = {}) {
238
238
  index > 1 &&
239
239
  (arg.includes(':') || arg.startsWith('(')) &&
240
240
  !arg.startsWith('--');
241
- // Don't escape GitHub search qualifiers - they need to be passed as-is
242
- // This includes qualifiers like "language:typescript", "user:microsoft", "org:microsoft"
243
- // and complex expressions like "(user:microsoft OR org:microsoft)"
241
+ // GitHub search qualifiers need special handling
242
+ // Most qualifiers can be passed as-is, but those with shell metacharacters need escaping
244
243
  if (isGitHubQualifier) {
244
+ // Check if the qualifier contains shell metacharacters that need escaping
245
+ if (/[<>&|;`$\\]/.test(arg)) {
246
+ // Escape qualifiers that contain shell metacharacters like size:<1000, size:>500
247
+ return escapeShellArg(arg, shellConfig.type, false);
248
+ }
249
+ // Safe qualifiers like "language:typescript", "user:microsoft" can be passed as-is
245
250
  return arg;
246
251
  }
247
252
  return escapeShellArg(arg, shellConfig.type, isMainQueryArgument);
@@ -303,6 +308,14 @@ async function executeCommand(fullCommand, type, options = {}, shellConfig) {
303
308
  stderr.trim() !== '';
304
309
  if (shouldTreatAsError) {
305
310
  const errorType = type === 'npm' ? 'NPM command error' : 'GitHub CLI command error';
311
+ // Enhanced error messaging for common GitHub issues
312
+ if (type === 'github' && stderr.includes('404')) {
313
+ const isRepoNotFound = stderr.includes('Not Found');
314
+ const enhancedMessage = isRepoNotFound
315
+ ? `${stderr}\n\nThis is often due to incorrect repository name. Use github_search_code to find the correct repository.`
316
+ : stderr;
317
+ return createErrorResult(errorType, new Error(enhancedMessage));
318
+ }
306
319
  return createErrorResult(errorType, new Error(stderr));
307
320
  }
308
321
  // Try to parse stdout as JSON, fallback to string if not possible
@@ -545,7 +558,7 @@ function createSearchFailedError(type = 'code') {
545
558
  }
546
559
 
547
560
  const API_STATUS_CHECK_TOOL_NAME = 'apiStatusCheck';
548
- const DESCRIPTION$9 = `Check GitHub and NPM authentication status. Returns connected status and GitHub organizations for accessing private repositories. No parameters required.`;
561
+ const DESCRIPTION$9 = `Check GitHub and NPM authentication status and available organizations. Verifies API connections and returns user organizations for accessing private repositories. Essential for troubleshooting access issues. No parameters required.`;
549
562
  // Helper function to parse execution results with proper typing
550
563
  function parseExecResult(result) {
551
564
  if (!result.isError && result.content?.[0]?.text) {
@@ -677,284 +690,34 @@ function registerApiStatusCheckTool(server) {
677
690
  });
678
691
  }
679
692
 
680
- const GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME = 'githubViewRepoStructure';
681
- const DESCRIPTION$8 = `Explore repository structure and navigate directories. Auto-detects branches and provides file/folder listings with size information. Parameters: owner (required - GitHub username/org), repo (required - repository name), branch (required), path (optional).`;
682
- function registerViewRepositoryStructureTool(server) {
683
- server.registerTool(GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME, {
684
- description: DESCRIPTION$8,
685
- inputSchema: {
686
- owner: z
687
- .string()
688
- .min(1)
689
- .max(100)
690
- .regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/, 'Invalid GitHub username/org format')
691
- .describe(`Repository owner/org name (e.g., 'microsoft', 'google', NOT 'microsoft/vscode')`),
692
- repo: z
693
- .string()
694
- .min(1)
695
- .max(100)
696
- .regex(/^[a-zA-Z0-9._-]+$/, 'Invalid repository name format')
697
- .describe('Repository name (case-sensitive)'),
698
- branch: z
699
- .string()
700
- .min(1)
701
- .max(255)
702
- .regex(/^[^\s]+$/, 'Branch name cannot contain spaces')
703
- .describe('Branch name. Falls back to default branch if not found'),
704
- path: z
705
- .string()
706
- .optional()
707
- .default('')
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.'),
711
- },
712
- annotations: {
713
- title: 'GitHub Repository Explorer',
714
- readOnlyHint: true,
715
- destructiveHint: false,
716
- idempotentHint: true,
717
- openWorldHint: true,
718
- },
719
- }, async (args) => {
720
- try {
721
- const result = await viewRepositoryStructure(args);
722
- return result;
723
- }
724
- catch (error) {
725
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
726
- return createResult({
727
- error: `Failed to explore repository. ${errorMessage}. Verify repository exists and is accessible`,
728
- });
729
- }
730
- });
731
- }
732
- /**
733
- * Views the structure of a GitHub repository at a specific path.
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);
738
- return withCache(cacheKey, async () => {
739
- const { owner, repo, branch, path = '' } = params;
740
- try {
741
- // Clean up path
742
- const cleanPath = path.startsWith('/') ? path.substring(1) : path;
743
- // Try the requested branch first, then fallback to main/master
744
- const branchesToTry = await getSmartBranchFallback(owner, repo, branch);
745
- let items = [];
746
- let usedBranch = branch;
747
- let lastError = null;
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) {
778
- // Check repository existence only after content fetch fails
779
- const repoCheckResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
780
- cache: false,
781
- });
782
- if (repoCheckResult.isError) {
783
- const repoErrorMsg = repoCheckResult.content[0].text;
784
- if (repoErrorMsg.includes('404')) {
785
- return createResult({
786
- error: `Repository "${owner}/${repo}" not found. It might have been deleted, renamed, or made private. Use github_search_code to find current location.`,
787
- });
788
- }
789
- else if (repoErrorMsg.includes('403')) {
790
- return createResult({
791
- error: `Repository "${owner}/${repo}" exists but access is denied. Repository might be private or archived. Use api_status_check to verify permissions.`,
792
- });
793
- }
794
- }
795
- const errorMsg = lastError?.message || 'Unknown error';
796
- if (errorMsg.includes('404') || errorMsg.includes('Not Found')) {
797
- if (path) {
798
- const searchSuggestion = await suggestPathSearchFallback(owner, path);
799
- return createResult({
800
- error: `Path "${path}" not found in any branch (tried: ${triedBranches.join(', ')}).${searchSuggestion}`,
801
- });
802
- }
803
- else {
804
- return createResult({
805
- error: `Repository "${owner}/${repo}" structure not accessible. Repository might be empty, private, or you might not have sufficient permissions. Use github_search_code with owner="${owner}" to find accessible repositories.`,
806
- });
807
- }
808
- }
809
- else if (errorMsg.includes('403') || errorMsg.includes('Forbidden')) {
810
- return createResult({
811
- error: `Access denied to "${owner}/${repo}". Repository exists but might be private/archived. Use api_status_check to verify permissions, or github_search_code with owner="${owner}" to find accessible repositories.`,
812
- });
813
- }
814
- else {
815
- const searchSuggestion = path
816
- ? await suggestPathSearchFallback(owner, path)
817
- : '';
818
- return createResult({
819
- error: `Failed to access "${owner}/${repo}": ${errorMsg}. Check network connection and repository permissions.${searchSuggestion}`,
820
- });
821
- }
822
- }
823
- // Limit total items to 100 for efficiency
824
- const limitedItems = items.slice(0, 100);
825
- // Sort: directories first, then alphabetically
826
- limitedItems.sort((a, b) => {
827
- if (a.type !== b.type) {
828
- return a.type === 'dir' ? -1 : 1;
829
- }
830
- return a.name.localeCompare(b.name);
831
- });
832
- // Create simplified, token-efficient structure
833
- const files = limitedItems
834
- .filter(item => item.type === 'file')
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
693
  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).`;
694
+ const DESCRIPTION$8 = `Fetch file content from GitHub repositories. Automatically handles branch fallback (main/master) and files up to 300KB. Returns decoded file content with metadata. Parameters: owner (required - GitHub username/org), repo (required - repository name), branch (required), filePath (required).`;
932
695
  function registerFetchGitHubFileContentTool(server) {
933
696
  server.registerTool(GITHUB_GET_FILE_CONTENT_TOOL_NAME, {
934
- description: DESCRIPTION$7,
697
+ description: DESCRIPTION$8,
935
698
  inputSchema: {
936
699
  owner: z
937
700
  .string()
938
701
  .min(1)
939
702
  .max(100)
940
703
  .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')`),
704
+ .describe(`Repository owner/organization name (e.g., 'facebook', 'microsoft'). Do NOT include repository name here.`),
942
705
  repo: z
943
706
  .string()
944
707
  .min(1)
945
708
  .max(100)
946
709
  .regex(/^[a-zA-Z0-9._-]+$/)
947
- .describe(`Repository name only (e.g., 'vscode', 'react', NOT 'microsoft/vscode')`),
710
+ .describe(`Repository name only (e.g., 'react', 'vscode'). Do NOT include owner/org prefix.`),
948
711
  branch: z
949
712
  .string()
950
713
  .min(1)
951
714
  .max(255)
952
715
  .regex(/^[^\s]+$/)
953
- .describe(`Branch name. Falls back to main/master if not found`),
716
+ .describe(`Branch name (e.g., 'main', 'master'). Tool will automatically try 'main' and 'master' if specified branch is not found.`),
954
717
  filePath: z
955
718
  .string()
956
719
  .min(1)
957
- .describe(`Exact file path from repo root (e.g., src/index.js, README.md)`),
720
+ .describe(`File path from repository root (e.g., 'src/index.js', 'README.md', 'docs/api.md'). Do NOT start with slash.`),
958
721
  },
959
722
  annotations: {
960
723
  title: 'GitHub File Content - Direct Access',
@@ -995,7 +758,7 @@ async function fetchGitHubFileContent(params) {
995
758
  const repoErrorMsg = repoCheckResult.content[0].text;
996
759
  if (repoErrorMsg.includes('404')) {
997
760
  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.`,
761
+ error: `Repository "${owner}/${repo}" not found. This is often due to incorrect repository name. Steps to resolve:\n1. Use github_search_code with query="${filePath.split('/').pop()}" owner="${owner}" to find the correct repository\n2. Verify the exact repository name\n3. Check if the repository might have been renamed or moved`,
999
762
  });
1000
763
  }
1001
764
  else if (repoErrorMsg.includes('403')) {
@@ -1146,46 +909,52 @@ async function suggestCodeSearchFallback(owner, filePath) {
1146
909
  }
1147
910
 
1148
911
  const GITHUB_SEARCH_CODE_TOOL_NAME = 'githubSearchCode';
1149
- const DESCRIPTION$6 = `Search code across GitHub repositories using GitHub's code search API.
912
+ const DESCRIPTION$7 = `Search code across GitHub repositories using GitHub's code search API.
913
+
914
+ Never use filters and flags on exploretory searches and on initial searches.
915
+ Use filters and flags on subsequent searches when you know what to search for or if the user asks for it explicitly.
916
+ Using too many flags might make miss relevant results. Use filters and flags to narrow down the results when getting too many.
917
+
918
+ Search Syntax - ALL terms must be present (AND logic):
919
+ The search finds files that contain ALL specified terms. Each space-separated term is required to be present in the same file.
920
+ - Multiple terms: All individual words must exist in the file
921
+ - Quoted phrases: Exact phrase matching for multi-word expressions
922
+ - Mixed terms: Combination of individual words AND exact phrases, all must be present
923
+ - Additional filters: Language, owner, repository, filename, extension, size and other flags supported
1150
924
 
1151
- Search Syntax (all terms must be present with AND logic):
1152
- - Multiple words: react lifecycle finds files with BOTH "react" AND "lifecycle" (separate words)
1153
- - Exact phrases: "error handling" finds exact phrase "error handling" (quoted phrase)
1154
- - Mixed: "async function" timeout → finds exact phrase "async function" AND word "timeout"
1155
- - Complex: useState useEffect "custom hook" → finds "useState" AND "useEffect" AND exact phrase "custom hook"
1156
- - Advanced: authentication authorization "jwt token" → finds "authentication" AND "authorization" AND exact phrase "jwt token"
1157
- - Framework: "React.Component" "componentDidMount" → finds both exact phrases in same file
925
+ Key behavior: All search terms are combined with AND logic - every term must be found in the file.
926
+ Quotes create exact phrase matching, unquoted terms are individual word requirements.
927
+ Start with 1-2 terms, add more to narrow results. Use filters and flags for precision.
1158
928
 
1159
- Key difference: Quotes create exact phrases, no quotes = individual words (all must be present).
1160
- Start with 1-2 terms, add more to narrow results. Use filters for precision.`;
929
+ `;
1161
930
  function registerGitHubSearchCodeTool(server) {
1162
931
  server.registerTool(GITHUB_SEARCH_CODE_TOOL_NAME, {
1163
- description: DESCRIPTION$6,
932
+ description: DESCRIPTION$7,
1164
933
  inputSchema: {
1165
934
  query: z
1166
935
  .string()
1167
936
  .min(1)
1168
- .describe('Search query with AND logic between terms. Multiple words require ALL to be present. Use quotes for exact phrases. Examples: react lifecycle (both words), "error handling" (exact phrase), async await "try catch" (words + phrase), "function definition" variable scope (phrase + words)'),
937
+ .describe('Search query with AND logic between terms. Multiple words require ALL to be present. Use quotes for exact phrases.'),
1169
938
  language: z
1170
939
  .string()
1171
940
  .optional()
1172
- .describe('Programming language filter (javascript, python, typescript, go, etc). Narrows search to specific language files. Use for language-specific searches.'),
941
+ .describe('Programming language filter. Narrows search to specific language files. Use for language-specific searches.'),
1173
942
  owner: z
1174
943
  .union([z.string(), z.array(z.string())])
1175
944
  .optional()
1176
- .describe('Repository owner or organization name(s) to search within. Format: "microsoft" or ["facebook", "google"]. Drastically narrows search scope. Do NOT use owner/repo format.'),
945
+ .describe('Repository owner/organization name(s) to search within (e.g., "facebook", ["google", "microsoft"]). Use this to search within specific organizations. Do NOT use owner/repo format - just the organization/username.'),
1177
946
  repo: z
1178
947
  .union([z.string(), z.array(z.string())])
1179
948
  .optional()
1180
- .describe('Filter on specific repository(ies). Format: "owner/repo" or ["microsoft/vscode", "facebook/react"]. Use for repository-specific searches.'),
949
+ .describe('Filter on specific repository(ies). Must use full "owner/repo" format (e.g., "facebook/react", ["vuejs/vue", "angular/angular"]). Use this for searching within specific repositories.'),
1181
950
  filename: z
1182
951
  .string()
1183
952
  .optional()
1184
- .describe('Target specific filename or pattern. Examples: "package.json", "*.test.js", "Dockerfile". Use for file-specific searches.'),
953
+ .describe('Target specific filename or pattern. Use for file-specific searches.'),
1185
954
  extension: z
1186
955
  .string()
1187
956
  .optional()
1188
- .describe('File extension filter (js, py, ts, go, etc). Alternative to language parameter. Examples: "js", "py", "tsx".'),
957
+ .describe('File extension filter. Alternative to language parameter.'),
1189
958
  match: z
1190
959
  .union([z.enum(['file', 'path']), z.array(z.enum(['file', 'path']))])
1191
960
  .optional()
@@ -1194,7 +963,7 @@ function registerGitHubSearchCodeTool(server) {
1194
963
  .string()
1195
964
  .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid size format. Use: ">10", ">=5", "<100", "<=50", "10..100", or exact number "50"')
1196
965
  .optional()
1197
- .describe('File size filter in KB. Format: ">10" (larger than), ">=5" (at least), "<100" (smaller than), "<=50" (at most), "10..50" (range), "25" (exact). Examples from docs: ">10", "10..50", "<100"'),
966
+ .describe('File size filter in KB. Format: ">N" (larger than), "<N" (smaller than), "N..M" (range), "N" (exact).'),
1198
967
  limit: z
1199
968
  .number()
1200
969
  .int()
@@ -1308,6 +1077,10 @@ function transformToOptimizedFormat$1(items) {
1308
1077
  : [0, 0]) || [],
1309
1078
  })) || [],
1310
1079
  url: singleRepo ? item.path : simplifyGitHubUrl(item.url),
1080
+ repository: {
1081
+ nameWithOwner: item.repository.nameWithOwner,
1082
+ url: item.repository.url,
1083
+ },
1311
1084
  }));
1312
1085
  const result = {
1313
1086
  items: optimizedItems,
@@ -1482,10 +1255,10 @@ function parseSearchQuery(query) {
1482
1255
  }
1483
1256
 
1484
1257
  const GITHUB_SEARCH_COMMITS_TOOL_NAME = 'githubSearchCommits';
1485
- const DESCRIPTION$5 = `Search commit history across GitHub repositories. Supports filtering by repository, author, date ranges, and commit attributes. Parameters: query (optional), owner (optional - GitHub username/org, NOT owner/repo), repo (optional - repository name, use with owner for specific repo), author (optional), authorName (optional), authorEmail (optional), committer (optional), committerName (optional), committerEmail (optional), authorDate (optional), committerDate (optional), hash (optional), parent (optional), tree (optional), merge (optional), visibility (optional), limit (optional), sort (optional), order (optional).`;
1258
+ const DESCRIPTION$6 = `Search commit history across GitHub repositories. Find commits by message, author, date, or repository. Supports advanced filtering for comprehensive commit analysis. Returns commit SHA, message, author, and date information.`;
1486
1259
  function registerGitHubSearchCommitsTool(server) {
1487
1260
  server.registerTool(GITHUB_SEARCH_COMMITS_TOOL_NAME, {
1488
- description: DESCRIPTION$5,
1261
+ description: DESCRIPTION$6,
1489
1262
  inputSchema: {
1490
1263
  query: z
1491
1264
  .string()
@@ -1495,11 +1268,11 @@ function registerGitHubSearchCommitsTool(server) {
1495
1268
  owner: z
1496
1269
  .string()
1497
1270
  .optional()
1498
- .describe('Repository owner/org name only (e.g., "microsoft", "google", NOT "microsoft/vscode"). Use with repo parameter for repository-specific searches.'),
1271
+ .describe('Repository owner/organization name only (e.g., "facebook", "microsoft"). Do NOT include repository name. Must be used with repo parameter for repository-specific searches.'),
1499
1272
  repo: z
1500
1273
  .string()
1501
1274
  .optional()
1502
- .describe('Repository name only (e.g., "vscode", "react", NOT "owner/repo"). Must be used together with owner parameter.'),
1275
+ .describe('Repository name only (e.g., "react", "vscode"). Do NOT include owner prefix. Must be used together with owner parameter.'),
1503
1276
  // Author filters
1504
1277
  author: z
1505
1278
  .string()
@@ -1749,10 +1522,10 @@ function buildGitHubCommitCliArgs(params) {
1749
1522
  }
1750
1523
 
1751
1524
  const GITHUB_SEARCH_ISSUES_TOOL_NAME = 'githubSearchIssues';
1752
- const DESCRIPTION$4 = `Search GitHub issues for bug discovery and feature analysis. Supports filtering by state, labels, assignee, dates, and more. Parameters: query (required), owner (optional - GitHub username/org, NOT owner/repo), repo (optional - repository name, use with owner for specific repo), app (optional), archived (optional), assignee (optional), author (optional), closed (optional), commenter (optional), comments (optional), created (optional), includePrs (optional), interactions (optional), involves (optional), labels (optional), language (optional), locked (optional), match (optional), mentions (optional), milestone (optional), noAssignee (optional), noLabel (optional), noMilestone (optional), noProject (optional), project (optional), reactions (optional), state (optional), teamMentions (optional), updated (optional), visibility (optional), sort (optional), order (optional), limit (optional).`;
1525
+ const DESCRIPTION$5 = `Search GitHub issues for bug reports, feature requests, and discussions. Find issues by keywords, state, labels, author, or repository. Returns issue number, title, state, labels, and metadata for effective issue tracking and analysis.`;
1753
1526
  function registerSearchGitHubIssuesTool(server) {
1754
1527
  server.registerTool(GITHUB_SEARCH_ISSUES_TOOL_NAME, {
1755
- description: DESCRIPTION$4,
1528
+ description: DESCRIPTION$5,
1756
1529
  inputSchema: {
1757
1530
  query: z
1758
1531
  .string()
@@ -1762,11 +1535,11 @@ function registerSearchGitHubIssuesTool(server) {
1762
1535
  .string()
1763
1536
  .min(1)
1764
1537
  .optional()
1765
- .describe('Repository owner/org name only (e.g., "microsoft", "google", NOT "microsoft/vscode"). Use with repo parameter for repository-specific searches.'),
1538
+ .describe('Repository owner/organization name only (e.g., "facebook", "microsoft"). Do NOT include repository name. Must be used with repo parameter for repository-specific searches.'),
1766
1539
  repo: z
1767
1540
  .string()
1768
1541
  .optional()
1769
- .describe('Repository name only (e.g., "vscode", "react", NOT "owner/repo"). Must be used together with owner parameter.'),
1542
+ .describe('Repository name only (e.g., "react", "vscode"). Do NOT include owner prefix. Must be used together with owner parameter.'),
1770
1543
  app: z
1771
1544
  .string()
1772
1545
  .optional()
@@ -1820,7 +1593,7 @@ function registerSearchGitHubIssuesTool(server) {
1820
1593
  label: z
1821
1594
  .union([z.string(), z.array(z.string())])
1822
1595
  .optional()
1823
- .describe('Label names (bug, feature, etc.). Can be single string or array.'),
1596
+ .describe('Label names. Can be single string or array.'),
1824
1597
  language: z.string().optional().describe('Repository language'),
1825
1598
  locked: z.boolean().optional().describe('Conversation locked status'),
1826
1599
  match: z
@@ -2083,10 +1856,10 @@ function buildGitHubIssuesAPICommand(params) {
2083
1856
 
2084
1857
  // TODO: add PR commeents. e.g, gh pr view <PR_NUMBER_OR_URL_OR_BRANCH> --comments
2085
1858
  const GITHUB_SEARCH_PULL_REQUESTS_TOOL_NAME = 'githubSearchPullRequests';
2086
- const DESCRIPTION$3 = `Search pull requests for implementation discovery and code review analysis. Supports filtering by state, review status, branches, and more. Parameters: query (required), owner (optional - GitHub username/org, NOT owner/repo), repo (optional - repository name, use with owner for specific repo), author (optional), assignee (optional), mentions (optional), commenter (optional), involves (optional), reviewedBy (optional), reviewRequested (optional), state (optional), head (optional), base (optional), language (optional), created (optional), updated (optional), mergedAt (optional), closed (optional), draft (optional), checks (optional), merged (optional), review (optional), limit (optional), sort (optional), order (optional).`;
1859
+ const DESCRIPTION$4 = `Search GitHub pull requests for code changes, feature implementations, and bug fixes. Find PRs by keywords, state, author, review status, or repository. Returns PR number, title, state, branches, and review information for code review analysis.`;
2087
1860
  function registerSearchGitHubPullRequestsTool(server) {
2088
1861
  server.registerTool(GITHUB_SEARCH_PULL_REQUESTS_TOOL_NAME, {
2089
- description: DESCRIPTION$3,
1862
+ description: DESCRIPTION$4,
2090
1863
  inputSchema: {
2091
1864
  query: z
2092
1865
  .string()
@@ -2095,11 +1868,11 @@ function registerSearchGitHubPullRequestsTool(server) {
2095
1868
  owner: z
2096
1869
  .string()
2097
1870
  .optional()
2098
- .describe('Repository owner/org name only (e.g., "microsoft", "google", NOT "microsoft/vscode"). Use with repo parameter for repository-specific searches.'),
1871
+ .describe('Repository owner/organization name only (e.g., "facebook", "microsoft"). Do NOT include repository name. Must be used with repo parameter for repository-specific searches.'),
2099
1872
  repo: z
2100
1873
  .string()
2101
1874
  .optional()
2102
- .describe('Repository name only (e.g., "vscode", "react", NOT "owner/repo"). Must be used together with owner parameter.'),
1875
+ .describe('Repository name only (e.g., "react", "vscode"). Do NOT include owner prefix. Must be used together with owner parameter.'),
2103
1876
  author: z.string().optional().describe('GitHub username of PR author'),
2104
1877
  assignee: z.string().optional().describe('GitHub username of assignee'),
2105
1878
  mentions: z.string().optional().describe('PRs mentioning this user'),
@@ -2118,10 +1891,7 @@ function registerSearchGitHubPullRequestsTool(server) {
2118
1891
  .optional()
2119
1892
  .describe('PR state. Default: all'),
2120
1893
  head: z.string().optional().describe('Source branch name'),
2121
- base: z
2122
- .string()
2123
- .optional()
2124
- .describe('Target branch name (main, develop, etc.)'),
1894
+ base: z.string().optional().describe('Target branch name'),
2125
1895
  language: z.string().optional().describe('Repository language'),
2126
1896
  created: z
2127
1897
  .string()
@@ -2409,26 +2179,27 @@ function buildGitHubPullRequestsAPICommand(params) {
2409
2179
  * 4. Recent Quality:
2410
2180
  * { stars: ">1000", created: ">2023-01-01", limit: 10 }
2411
2181
  *
2182
+ * RESEARCH & EXPLORATION PATTERNS:
2183
+ *
2184
+ * 1. Topic-based Discovery (HIGHLY RECOMMENDED for unknown projects):
2185
+ * { topic: ["machine-learning", "nlp", "pytorch"], limit: 20 }
2186
+ * { topic: ["kubernetes", "monitoring"], stars: ">100", limit: 15 }
2187
+ *
2188
+ * 2. Exploratory Research Flow:
2189
+ * - Start with topics to discover repositories
2190
+ * - Then use githubViewRepoStructure to understand project layout
2191
+ * - Read README.md, docs/, and configuration files
2192
+ * - Finally use githubSearchCode for specific implementations
2193
+ *
2412
2194
  * AVOID: OR queries + language filter, 5+ filters, multi-word OR
2413
2195
  * TIP: Use limit parameter instead of adding more filters
2414
2196
  */
2415
2197
  const GITHUB_SEARCH_REPOSITORIES_TOOL_NAME = 'githubSearchRepositories';
2416
- const DESCRIPTION$2 = `Search GitHub repositories using GitHub's repository search API with comprehensive filtering.
2417
-
2418
- Search Logic (AND operation for multiple terms):
2419
- - Multiple words: "react typescript" → repositories containing BOTH "react" AND "typescript"
2420
- - Exact phrases: "web framework" → repositories with exact phrase "web framework"
2421
- - Mixed: "machine learning" python → exact phrase "machine learning" AND word "python"
2422
- - Filter combinations: language:javascript stars:>1000 → JavaScript repos with 1000+ stars
2198
+ const DESCRIPTION$3 = `Search GitHub repositories by name, description, topics, language, or organization. Find projects based on stars, forks, activity, and community metrics. Returns repository details including name, description, stars, language, and owner information for project discovery.
2423
2199
 
2424
- Supported Filters: language, stars (ranges), topics, owner/org, license, dates (created/updated),
2425
- size, forks, community metrics (good-first-issues, help-wanted), archived status, visibility.
2426
-
2427
- Examples:
2428
- - Popular projects: stars:">1000" language:typescript
2429
- - Beginner-friendly: good-first-issues:">5" stars:"100..5000"
2430
- - Recent quality: created:">2023-01-01" stars:">500"
2431
- - Organization repos: owner:microsoft language:python`;
2200
+ Search Syntax - ALL terms must be present (AND logic):
2201
+ Multiple search terms require ALL to be found in the repository (name, description, or README).
2202
+ Use quotes for exact phrase matching. Additional filters supported via parameters.`;
2432
2203
  /**
2433
2204
  * Extract owner/repo information from various query formats
2434
2205
  */
@@ -2461,21 +2232,21 @@ function extractOwnerRepoFromQuery(query) {
2461
2232
  }
2462
2233
  function registerSearchGitHubReposTool(server) {
2463
2234
  server.registerTool(GITHUB_SEARCH_REPOSITORIES_TOOL_NAME, {
2464
- description: DESCRIPTION$2,
2235
+ description: DESCRIPTION$3,
2465
2236
  inputSchema: {
2466
2237
  query: z
2467
2238
  .string()
2468
2239
  .optional()
2469
- .describe('Search query with AND logic between terms. Multiple words require ALL to be present. Use quotes for exact phrases. Examples: react typescript (both words), "web framework" (exact phrase), "machine learning" python (phrase + word).'),
2240
+ .describe('Search query with AND logic between terms. Multiple words require ALL to be present. Use quotes for exact phrases.'),
2470
2241
  // CORE FILTERS (GitHub CLI flags)
2471
2242
  owner: z
2472
2243
  .union([z.string(), z.array(z.string())])
2473
2244
  .optional()
2474
- .describe('Repository owner or organization name(s). Format: "microsoft" or ["facebook", "google"]. Do NOT use owner/repo format. Dramatically narrows search scope to specific organizations.'),
2245
+ .describe('Repository owner/organization name(s) (e.g., "facebook", ["google", "microsoft"]). Search within specific organizations. Do NOT use owner/repo format - just the organization/username.'),
2475
2246
  language: z
2476
2247
  .string()
2477
2248
  .optional()
2478
- .describe('Programming language filter (javascript, python, typescript, go, etc). Filters repositories by primary language. Essential for language-specific searches.'),
2249
+ .describe('Programming language filter. Filters repositories by primary language. Essential for language-specific searches.'),
2479
2250
  stars: z
2480
2251
  .union([
2481
2252
  z.number().int().min(0),
@@ -2484,11 +2255,11 @@ function registerSearchGitHubReposTool(server) {
2484
2255
  .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">1000", ">=500", "<100", "<=50", "10..100", or exact number "50"'),
2485
2256
  ])
2486
2257
  .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). Examples from docs: ">1000", "100..5000", "<100"'),
2258
+ .describe('Star count filter. Format: ">1000" (more than), ">=500" (more than or equal), "<100" (less than), "<=50" (less than or equal), "100..1000" (range), "500" (exact).'),
2488
2259
  topic: z
2489
2260
  .union([z.string(), z.array(z.string())])
2490
2261
  .optional()
2491
- .describe('Repository topics filter. Examples: "machine-learning", ["react", "typescript"]. Topics use kebab-case format (machine-learning, web-development).'),
2262
+ .describe('Repository topics filter. Excellent for discovering projects and understanding repository ecosystems. Topics use kebab-case format.'),
2492
2263
  forks: z
2493
2264
  .union([
2494
2265
  z.number().int().min(0),
@@ -2497,7 +2268,7 @@ function registerSearchGitHubReposTool(server) {
2497
2268
  .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">100", ">=50", "<10", "<=5", "10..100", or exact number "5"'),
2498
2269
  ])
2499
2270
  .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). Examples from docs: ">100", "10..100", "<10"'),
2271
+ .describe('Fork count filter. Format: ">100" (more than), ">=50" (more than or equal), "<10" (less than), "<=5" (less than or equal), "10..100" (range), "5" (exact).'),
2501
2272
  // Match CLI parameter name exactly
2502
2273
  'number-topics': z
2503
2274
  .union([
@@ -2507,40 +2278,40 @@ function registerSearchGitHubReposTool(server) {
2507
2278
  .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">5", ">=3", "<10", "<=2", "3..10", or exact number "5"'),
2508
2279
  ])
2509
2280
  .optional()
2510
- .describe('Number of topics filter. Format: ">5" (many topics), ">=3" (at least 3), "<10" (few topics), "1..3" (range), "5" (exact). Well-documented projects typically have 3-10 topics.'),
2281
+ .describe('Number of topics filter. Format: ">5" (many topics), ">=3" (at least 3), "<10" (few topics), "1..3" (range), "5" (exact).'),
2511
2282
  // QUALITY & STATE FILTERS
2512
2283
  license: z
2513
2284
  .union([z.string(), z.array(z.string())])
2514
2285
  .optional()
2515
- .describe('License filter. Examples: "mit", "apache-2.0", ["mit", "apache-2.0"]. Common licenses: mit, apache-2.0, gpl-3.0, bsd-3-clause.'),
2286
+ .describe('License filter.'),
2516
2287
  archived: z
2517
2288
  .boolean()
2518
2289
  .optional()
2519
- .describe('Archive status filter. false (active repos only), true (archived repos only). Use false to find actively maintained projects.'),
2290
+ .describe('Archive status filter. false (active repos only), true (archived repos only).'),
2520
2291
  'include-forks': z
2521
2292
  .enum(['false', 'true', 'only'])
2522
2293
  .optional()
2523
- .describe('Fork inclusion. "false" (exclude forks), "true" (include forks), "only" (forks only). Use "false" for original projects.'),
2294
+ .describe('Fork inclusion. "false" (exclude forks), "true" (include forks), "only" (forks only).'),
2524
2295
  visibility: z
2525
2296
  .enum(['public', 'private', 'internal'])
2526
2297
  .optional()
2527
- .describe('Repository visibility. "public" (open source), "private" (private repos you have access to), "internal" (organization internal).'),
2298
+ .describe('Repository visibility.'),
2528
2299
  // DATE & SIZE FILTERS
2529
2300
  created: z
2530
2301
  .string()
2531
2302
  .regex(/^(>=?\d{4}-\d{2}-\d{2}|<=?\d{4}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2}\.\.\d{4}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2})$/, 'Invalid date format. Use: ">2020-01-01", ">=2020-01-01", "<2023-12-31", "<=2023-12-31", "2020-01-01..2023-12-31", or exact date "2023-01-01"')
2532
2303
  .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). Examples from docs: ">2020-01-01", "2023-01-01..2023-12-31"'),
2304
+ .describe('Repository creation date filter. Format: ">2020-01-01" (after), ">=2020-01-01" (on or after), "<2023-12-31" (before), "<=2023-12-31" (on or before), "2020-01-01..2023-12-31" (range), "2023-01-01" (exact).'),
2534
2305
  updated: z
2535
2306
  .string()
2536
2307
  .regex(/^(>=?\d{4}-\d{2}-\d{2}|<=?\d{4}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2}\.\.\d{4}-\d{2}-\d{2}|\d{4}-\d{2}-\d{2})$/, 'Invalid date format. Use: ">2024-01-01", ">=2024-01-01", "<2022-01-01", "<=2022-01-01", "2023-01-01..2024-12-31", or exact date "2024-01-01"')
2537
2308
  .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). Essential for finding actively maintained projects. Examples from docs: ">2024-01-01", "2022-01-01..2023-12-31"'),
2309
+ .describe('Last updated date filter. Format: ">2024-01-01" (recently updated), ">=2024-01-01" (on or after), "<2022-01-01" (not recently updated), "2023-01-01..2024-12-31" (range).'),
2539
2310
  size: z
2540
2311
  .string()
2541
2312
  .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid size format. Use: ">1000", ">=500", "<100", "<=50", "100..1000", or exact number "500"')
2542
2313
  .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). Examples from docs: ">1000", "100..1000", "<100"'),
2314
+ .describe('Repository size filter in KB. Format: ">1000" (large projects), ">=500" (medium-large), "<100" (small projects), "<=50" (tiny), "100..1000" (medium range), "500" (exact).'),
2544
2315
  // COMMUNITY FILTERS - Match CLI parameter names exactly
2545
2316
  'good-first-issues': z
2546
2317
  .union([
@@ -2550,7 +2321,7 @@ function registerSearchGitHubReposTool(server) {
2550
2321
  .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: number, ">5", ">=10", "<20", "<=15", or "5..20"'),
2551
2322
  ])
2552
2323
  .optional()
2553
- .describe('Good first issues count. Format: ">5" (many beginner issues), "1..10" (some beginner issues). Perfect for finding beginner-friendly open source projects.'),
2324
+ .describe('Good first issues count. Format: ">5" (many beginner issues), "1..10" (some beginner issues).'),
2554
2325
  'help-wanted-issues': z
2555
2326
  .union([
2556
2327
  z.number().int().min(0),
@@ -2559,7 +2330,7 @@ function registerSearchGitHubReposTool(server) {
2559
2330
  .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: number, ">5", ">=10", "<20", "<=15", or "5..20"'),
2560
2331
  ])
2561
2332
  .optional()
2562
- .describe('Help wanted issues count. Format: ">10" (many help wanted), "1..5" (some help wanted). Great for finding projects actively seeking contributors.'),
2333
+ .describe('Help wanted issues count. Format: ">10" (many help wanted), "1..5" (some help wanted).'),
2563
2334
  followers: z
2564
2335
  .union([
2565
2336
  z.number().int().min(0),
@@ -2568,12 +2339,15 @@ function registerSearchGitHubReposTool(server) {
2568
2339
  .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">1000", ">=500", "<100", "<=50", "100..1000", or exact number "500"'),
2569
2340
  ])
2570
2341
  .optional()
2571
- .describe('Repository owner followers count. Format: ">1000" (popular developers), ">=500" (established developers), "<100" (smaller developers), "100..1000" (range). Indicates developer/org reputation.'),
2572
- // SEARCH SCOPE
2342
+ .describe('Repository owner followers count. Format: ">1000" (popular developers), ">=500" (established developers), "<100" (smaller developers), "100..1000" (range).'),
2343
+ // SEARCH SCOPE - Match CLI exactly
2573
2344
  match: z
2574
- .enum(['name', 'description', 'readme'])
2345
+ .union([
2346
+ z.enum(['name', 'description', 'readme']),
2347
+ z.array(z.enum(['name', 'description', 'readme'])),
2348
+ ])
2575
2349
  .optional()
2576
- .describe('Search scope. "name" (repository names only), "description" (descriptions only), "readme" (README content). Default searches all fields.'),
2350
+ .describe('Search scope. "name" (repository names only), "description" (descriptions only), "readme" (README content). Can be single value or array.'),
2577
2351
  // SORTING & LIMITS - Match CLI defaults exactly
2578
2352
  sort: z
2579
2353
  .enum([
@@ -2585,12 +2359,12 @@ function registerSearchGitHubReposTool(server) {
2585
2359
  ])
2586
2360
  .optional()
2587
2361
  .default('best-match')
2588
- .describe('Sort criteria. "stars" (popularity), "updated" (recent activity), "forks" (community engagement), "help-wanted-issues" (contribution opportunities), "best-match" (relevance).'),
2362
+ .describe('Sort criteria.'),
2589
2363
  order: z
2590
2364
  .enum(['asc', 'desc'])
2591
2365
  .optional()
2592
2366
  .default('desc')
2593
- .describe('Sort order direction. "desc" (descending, highest first), "asc" (ascending, lowest first). Default is descending for most useful results.'),
2367
+ .describe('Sort order direction.'),
2594
2368
  limit: z
2595
2369
  .number()
2596
2370
  .int()
@@ -2598,7 +2372,7 @@ function registerSearchGitHubReposTool(server) {
2598
2372
  .max(100)
2599
2373
  .optional()
2600
2374
  .default(30)
2601
- .describe('Maximum number of repositories to return (1-100). Default: 30. Higher values may increase response time.'),
2375
+ .describe('Maximum number of repositories to return (1-100).'),
2602
2376
  },
2603
2377
  annotations: {
2604
2378
  title: 'GitHub Repository Search',
@@ -2828,8 +2602,258 @@ function buildGitHubReposSearchCommand(params) {
2828
2602
  return { command: 'search', args };
2829
2603
  }
2830
2604
 
2605
+ const GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME = 'githubViewRepoStructure';
2606
+ const DESCRIPTION$2 = `Browse GitHub repository file structure and navigate directories. Essential for exploring project layout, finding documentation, configuration files, and source code. Returns file/folder listings with size information. Automatically handles branch detection. Parameters: owner (required - GitHub username/org), repo (required - repository name), branch (required), path (optional - directory path).`;
2607
+ function registerViewRepositoryStructureTool(server) {
2608
+ server.registerTool(GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME, {
2609
+ description: DESCRIPTION$2,
2610
+ inputSchema: {
2611
+ owner: z
2612
+ .string()
2613
+ .min(1)
2614
+ .max(100)
2615
+ .regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/, 'Invalid GitHub username/org format')
2616
+ .describe('Repository owner/organization name (e.g., "facebook", "microsoft"). Do NOT include repository name.'),
2617
+ repo: z
2618
+ .string()
2619
+ .min(1)
2620
+ .max(100)
2621
+ .regex(/^[a-zA-Z0-9._-]+$/, 'Invalid repository name format')
2622
+ .describe(`Repository name under a organization. `),
2623
+ branch: z
2624
+ .string()
2625
+ .min(1)
2626
+ .max(255)
2627
+ .regex(/^[^\s]+$/, 'Branch name cannot contain spaces')
2628
+ .describe('Branch name (e.g., "main", "master", "develop"). Tool will automatically try default branch if specified branch is not found.'),
2629
+ path: z
2630
+ .string()
2631
+ .optional()
2632
+ .default('')
2633
+ .refine(path => !path.includes('..'), 'Path traversal not allowed')
2634
+ .refine(path => path.length <= 500, 'Path too long')
2635
+ .describe('Directory path within repository (e.g., "src", "docs", "src/components"). Leave empty for root directory. Do NOT start with slash.'),
2636
+ },
2637
+ annotations: {
2638
+ title: 'GitHub Repository Explorer',
2639
+ readOnlyHint: true,
2640
+ destructiveHint: false,
2641
+ idempotentHint: true,
2642
+ openWorldHint: true,
2643
+ },
2644
+ }, async (args) => {
2645
+ try {
2646
+ const result = await viewRepositoryStructure(args);
2647
+ return result;
2648
+ }
2649
+ catch (error) {
2650
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
2651
+ return createResult({
2652
+ error: `Failed to explore repository. ${errorMessage}. Verify repository exists and is accessible`,
2653
+ });
2654
+ }
2655
+ });
2656
+ }
2657
+ /**
2658
+ * Views the structure of a GitHub repository at a specific path.
2659
+ * Optimized for code analysis workflows with smart defaults and clear errors.
2660
+ */
2661
+ async function viewRepositoryStructure(params) {
2662
+ const cacheKey = generateCacheKey('gh-repo-structure', params);
2663
+ return withCache(cacheKey, async () => {
2664
+ const { owner, repo, branch, path = '' } = params;
2665
+ try {
2666
+ // Clean up path
2667
+ const cleanPath = path.startsWith('/') ? path.substring(1) : path;
2668
+ // Try the requested branch first, then fallback to main/master
2669
+ const branchesToTry = await getSmartBranchFallback(owner, repo, branch);
2670
+ let items = [];
2671
+ let usedBranch = branch;
2672
+ let lastError = null;
2673
+ let attemptCount = 0;
2674
+ const maxAttempts = branchesToTry.length;
2675
+ const triedBranches = [];
2676
+ for (const tryBranch of branchesToTry) {
2677
+ if (attemptCount >= maxAttempts)
2678
+ break;
2679
+ attemptCount++;
2680
+ triedBranches.push(tryBranch);
2681
+ try {
2682
+ const apiPath = `/repos/${owner}/${repo}/contents/${cleanPath}?ref=${tryBranch}`;
2683
+ const result = await executeGitHubCommand('api', [apiPath], {
2684
+ cache: false,
2685
+ });
2686
+ if (!result.isError) {
2687
+ const execResult = JSON.parse(result.content[0].text);
2688
+ const apiItems = execResult.result;
2689
+ items = Array.isArray(apiItems) ? apiItems : [apiItems];
2690
+ usedBranch = tryBranch;
2691
+ break;
2692
+ }
2693
+ else {
2694
+ lastError = new Error(result.content[0].text);
2695
+ }
2696
+ }
2697
+ catch (error) {
2698
+ lastError = error instanceof Error ? error : new Error(String(error));
2699
+ continue;
2700
+ }
2701
+ }
2702
+ if (items.length === 0) {
2703
+ // Check repository existence only after content fetch fails
2704
+ const repoCheckResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
2705
+ cache: false,
2706
+ });
2707
+ if (repoCheckResult.isError) {
2708
+ const repoErrorMsg = repoCheckResult.content[0].text;
2709
+ if (repoErrorMsg.includes('404')) {
2710
+ return createResult({
2711
+ error: `Repository "${owner}/${repo}" not found. It might have been deleted, renamed, or made private. Use github_search_code to find current location.`,
2712
+ });
2713
+ }
2714
+ else if (repoErrorMsg.includes('403')) {
2715
+ return createResult({
2716
+ error: `Repository "${owner}/${repo}" exists but access is denied. Repository might be private or archived. Use api_status_check to verify permissions.`,
2717
+ });
2718
+ }
2719
+ }
2720
+ const errorMsg = lastError?.message || 'Unknown error';
2721
+ if (errorMsg.includes('404') || errorMsg.includes('Not Found')) {
2722
+ if (path) {
2723
+ const searchSuggestion = await suggestPathSearchFallback(owner, path);
2724
+ return createResult({
2725
+ error: `Path "${path}" not found in any branch (tried: ${triedBranches.join(', ')}).${searchSuggestion}`,
2726
+ });
2727
+ }
2728
+ else {
2729
+ return createResult({
2730
+ error: `Repository "${owner}/${repo}" structure not accessible. Repository might be empty, private, or you might not have sufficient permissions. Use github_search_code with owner="${owner}" to find accessible repositories.`,
2731
+ });
2732
+ }
2733
+ }
2734
+ else if (errorMsg.includes('403') || errorMsg.includes('Forbidden')) {
2735
+ return createResult({
2736
+ error: `Access denied to "${owner}/${repo}". Repository exists but might be private/archived. Use api_status_check to verify permissions, or github_search_code with owner="${owner}" to find accessible repositories.`,
2737
+ });
2738
+ }
2739
+ else {
2740
+ const searchSuggestion = path
2741
+ ? await suggestPathSearchFallback(owner, path)
2742
+ : '';
2743
+ return createResult({
2744
+ error: `Failed to access "${owner}/${repo}": ${errorMsg}. Check network connection and repository permissions.${searchSuggestion}`,
2745
+ });
2746
+ }
2747
+ }
2748
+ // Limit total items to 100 for efficiency
2749
+ const limitedItems = items.slice(0, 100);
2750
+ // Sort: directories first, then alphabetically
2751
+ limitedItems.sort((a, b) => {
2752
+ if (a.type !== b.type) {
2753
+ return a.type === 'dir' ? -1 : 1;
2754
+ }
2755
+ return a.name.localeCompare(b.name);
2756
+ });
2757
+ // Create simplified, token-efficient structure
2758
+ const files = limitedItems
2759
+ .filter(item => item.type === 'file')
2760
+ .map(item => ({
2761
+ name: item.name,
2762
+ size: item.size,
2763
+ url: item.path, // Use path for fetching
2764
+ }));
2765
+ const folders = limitedItems
2766
+ .filter(item => item.type === 'dir')
2767
+ .map(item => ({
2768
+ name: item.name,
2769
+ url: item.path, // Use path for browsing
2770
+ }));
2771
+ return createResult({
2772
+ data: {
2773
+ repository: `${owner}/${repo}`,
2774
+ branch: usedBranch,
2775
+ path: cleanPath || '/',
2776
+ githubBasePath: `https://api.github.com/repos/${owner}/${repo}/contents/`,
2777
+ files: {
2778
+ count: files.length,
2779
+ files: files,
2780
+ },
2781
+ folders: {
2782
+ count: folders.length,
2783
+ folders: folders,
2784
+ },
2785
+ },
2786
+ });
2787
+ }
2788
+ catch (error) {
2789
+ const errorMessage = error instanceof Error ? error.message : String(error);
2790
+ return createResult({
2791
+ error: `Failed to access repository "${owner}/${repo}": ${errorMessage}. Verify repository name, permissions, and network connection.`,
2792
+ });
2793
+ }
2794
+ });
2795
+ }
2796
+ /**
2797
+ * Smart branch detection with automatic fallback to common branch names.
2798
+ * Now includes more comprehensive branch detection and better error handling.
2799
+ */
2800
+ async function getSmartBranchFallback(owner, repo, requestedBranch) {
2801
+ const branches = new Set([requestedBranch]);
2802
+ try {
2803
+ // Try to get repository info to find default branch
2804
+ const repoInfoResult = await executeGitHubCommand('api', [`/repos/${owner}/${repo}`], {
2805
+ cache: false,
2806
+ });
2807
+ if (!repoInfoResult.isError) {
2808
+ const execResult = JSON.parse(repoInfoResult.content[0].text);
2809
+ const repoData = execResult.result;
2810
+ const defaultBranch = repoData.default_branch;
2811
+ if (defaultBranch) {
2812
+ branches.add(defaultBranch);
2813
+ }
2814
+ }
2815
+ }
2816
+ catch {
2817
+ // If we can't get repo info, proceed with standard fallbacks
2818
+ }
2819
+ // Add only main/master as fallback branches
2820
+ const commonBranches = ['main', 'master'];
2821
+ commonBranches.forEach(branch => branches.add(branch));
2822
+ // Convert Set back to array, with requested branch first
2823
+ const branchesArray = Array.from(branches);
2824
+ branchesArray.sort((a, b) => {
2825
+ if (a === requestedBranch)
2826
+ return -1;
2827
+ if (b === requestedBranch)
2828
+ return 1;
2829
+ return 0;
2830
+ });
2831
+ return branchesArray;
2832
+ }
2833
+ // Helper function to suggest path search strategy
2834
+ async function suggestPathSearchFallback(owner, path) {
2835
+ try {
2836
+ // Extract last path segment and try to find in same organization
2837
+ const pathSegment = path.split('/').pop() || path;
2838
+ const searchResult = await executeGitHubCommand('api', [
2839
+ `/search/code?q=${encodeURIComponent(pathSegment)}+in:path+org:${owner}`,
2840
+ ], { cache: false });
2841
+ if (!searchResult.isError) {
2842
+ const results = JSON.parse(searchResult.content[0].text);
2843
+ if (results.total_count > 0) {
2844
+ const firstMatch = results.items[0];
2845
+ return ` Directory might be in ${firstMatch.repository.full_name}. Try these searches:\n1. github_search_code with query="${pathSegment}" owner="${owner}"\n2. github_search_code with query="path:${path}" owner="${owner}"`;
2846
+ }
2847
+ }
2848
+ }
2849
+ catch {
2850
+ // Fallback to generic message if search fails
2851
+ }
2852
+ return ` Try these searches:\n1. github_search_code with query="${path.split('/').pop()}" owner="${owner}"\n2. github_search_code with query="path:${path}"`;
2853
+ }
2854
+
2831
2855
  const NPM_PACKAGE_SEARCH_TOOL_NAME = 'npmPackageSearch';
2832
- const DESCRIPTION$1 = `Search NPM packages with fuzzy matching. Supports multiple search terms and aggregates results. Use functional keywords like "react hooks", "auth", or "testing". Parameters: queries (required, string or array), searchLimit (optional).`;
2856
+ const DESCRIPTION$1 = `Search NPM packages by name or functionality keywords. Supports multiple search terms for comprehensive package discovery. Returns package name, version, description, keywords, and repository information. Best for finding JavaScript/TypeScript libraries and tools.`;
2833
2857
  const MAX_DESCRIPTION_LENGTH = 100;
2834
2858
  const MAX_KEYWORDS = 10;
2835
2859
  function registerNpmSearchTool(server) {
@@ -2838,7 +2862,7 @@ function registerNpmSearchTool(server) {
2838
2862
  inputSchema: {
2839
2863
  queries: z
2840
2864
  .union([z.string(), z.array(z.string())])
2841
- .describe('Search terms for packages. Use functionality keywords: "react hooks", "cli tool", "testing"'),
2865
+ .describe('Search terms for NPM packages (e.g., "react hooks", ["typescript", "eslint"], "data visualization"). Use functionality keywords rather than exact package names for best results.'),
2842
2866
  searchLimit: z
2843
2867
  .number()
2844
2868
  .int()
@@ -2937,7 +2961,7 @@ function parseNpmSearchOutput(output) {
2937
2961
  }
2938
2962
 
2939
2963
  const NPM_VIEW_PACKAGE_TOOL_NAME = 'npmViewPackage';
2940
- const DESCRIPTION = `View detailed NPM package information including repository URL, exports, version history, dependencies, and download stats. Returns optimized metadata for code navigation. Parameters: packageName (required).`;
2964
+ const DESCRIPTION = `Get detailed NPM package information including version, description, repository URL, size, and download statistics. Essential for understanding package details before installation. Returns optimized metadata for package evaluation and code navigation.`;
2941
2965
  function registerNpmViewPackageTool(server) {
2942
2966
  server.registerTool(NPM_VIEW_PACKAGE_TOOL_NAME, {
2943
2967
  description: DESCRIPTION,
@@ -2945,7 +2969,7 @@ function registerNpmViewPackageTool(server) {
2945
2969
  packageName: z
2946
2970
  .string()
2947
2971
  .min(1)
2948
- .describe('NPM package name (e.g., "react", "express", "@types/node")'),
2972
+ .describe('NPM package name (e.g., "react", "express", "@types/node"). Include @ prefix for scoped packages.'),
2949
2973
  },
2950
2974
  annotations: {
2951
2975
  title: 'NPM Package Analyzer',
@@ -3095,133 +3119,125 @@ using gh cli for github and npm cli for packages.
3095
3119
 
3096
3120
  CRITICAL SEARCH PRINCIPLES:
3097
3121
 
3098
- ## 1. PROGRESSIVE SEARCH STRATEGY (MOST IMPORTANT):
3099
- a) START BROAD: Begin with simple, general terms (1-2 words max)
3100
- b) ANALYZE RESULTS: Learn from what you find (repo names, owners, common patterns)
3101
- c) REFINE GRADUALLY: Add filters only after understanding the landscape
3122
+ PROGRESSIVE SEARCH STRATEGY (MOST IMPORTANT):
3123
+ a) START BROAD: Begin with simple, general queries and terms (1-2 words max)
3124
+ b) ANALYZE RESULTS: Learn from what you find
3125
+ c) REFINE GRADUALLY: Add filters only after understanding the landscape and if needed (only if the user asks for it explicitly)
3102
3126
  d) MULTIPLE ANGLES: Try different search terms if first approach yields no results
3103
3127
 
3104
- ## 2. SEARCH PROGRESSION EXAMPLES:
3105
- - User asks about "React state management"
3106
- 1st search: "state" or "redux" (BROAD)
3107
- 2nd search: "react state" with language:javascript (REFINED)
3108
- 3rd search: owner:facebook with specific terms (TARGETED)
3109
-
3110
- - User asks about "authentication libraries"
3111
- 1st search: "auth" or "authentication" (BROAD)
3112
- 2nd search: Add language filter based on results
3113
- 3rd search: Focus on specific owners/topics found
3114
-
3115
- ## 3. HANDLING NO RESULTS:
3116
- - NEVER give up after one failed search
3117
- - Try progressively BROADER terms
3118
- - Remove ALL filters and try core keywords
3119
- - Search for related concepts (e.g., "auth" → "login" → "session")
3120
- - Check different owners/organizations
3121
- - For code search: try searching in popular repos first
3128
+ HANDLING NO RESULTS:
3129
+ - NEVER give up after one failed search.
3130
+ - Act on fallbacks messages and try to understand the user's intent.
3131
+ - Try progressively BROADER terms (simplify queries and remove filters)
3122
3132
 
3123
- ## 4. SMART FILTER USAGE:
3133
+ SMART FILTER USAGE:
3124
3134
  - NO FILTERS on first search (unless user specifies)
3125
3135
  - Add ONE filter at a time based on results
3126
- - Common progression: query → +language → +stars → +owner
3136
+ - Common progression: query → +language → +stars → +owner (only if user asks for it explicitly)
3127
3137
  - Reserve complex filters for final refinement
3128
3138
 
3129
- ## 5. TOOL SYNERGY:
3130
- - Use repository search to find relevant repos FIRST
3131
- - Then use code search within those repos
3132
- - Check npm packages for JavaScript/TypeScript queries
3133
- - Verify access with api_status_check for private repos
3134
-
3135
- ## 6. RESEARCH BEST PRACTICES:
3139
+ RESEARCH BEST PRACTICES:
3136
3140
  - Conduct COMPREHENSIVE research with multiple searches
3137
3141
  - Learn from each search to improve the next
3138
3142
  - Provide context about your search strategy
3139
3143
  - Always verify technical details with actual code
3140
3144
 
3141
- ## 7. COMPLEX ANALYSIS PATTERNS:
3145
+ COMPLEX ANALYSIS PATTERNS:
3142
3146
 
3143
- ### Multi-Framework Comparison (e.g., React vs Vue):
3144
- 1. Search repos separately: "react", then "vue"
3145
- 2. Find core implementation files: "scheduler" in React, "reactivity" in Vue
3146
- 3. Search specific features: "concurrent", "fiber", "proxy"
3147
+ Multi-terms Comparison
3148
+ 1. Search repos separately: "repoA", then "repoB"
3149
+ 2. Review repositories structure and files
3150
+ 3. Review code and documentation
3147
3151
  4. Compare similar functionalities across repos
3152
+ 5. Use filters and flags to narrow down the results
3148
3153
 
3149
- ### Architecture Analysis (e.g., State Management):
3150
- 1. Start broad: "state" or "store"
3151
- 2. Identify major libraries: Redux, Zustand, Jotai
3152
- 3. Search implementation patterns: "reducer", "atom", "selector"
3153
- 4. Analyze each approach separately, then compare
3154
-
3155
- ### Evolution Tracking (e.g., Feature History):
3154
+ Evolution Tracking (e.g., Feature History):
3156
3155
  1. Use commit search with broad terms first
3157
3156
  2. Narrow by date ranges progressively
3158
3157
  3. Track changes in specific files over time
3159
3158
  4. Identify key contributors and their patterns
3160
3159
 
3161
- ### Performance Analysis:
3162
- 1. Search for benchmarks: "benchmark", "perf", "performance"
3163
- 2. Look for optimization commits: "optimize", "faster", "improve"
3164
- 3. Find profiling code: "profile", "measure", "timing"
3165
- 4. Compare implementation strategies
3166
-
3167
- ## 8. CROSS-REPOSITORY INTELLIGENCE:
3160
+ CROSS-REPOSITORY INTELLIGENCE:
3168
3161
  - When comparing frameworks, search EACH separately first
3169
3162
  - Build mental model of each codebase structure
3170
- - Use discovered patterns to refine searches
3171
- - Connect findings across repositories for insights
3163
+ - Use discovered patterns to refine searches and connect findings across repositories for insights
3164
+
3165
+ RESEARCH WORKFLOW BEST PRACTICES:
3166
+
3167
+ Discovery Phase (Unknown Projects/Topics):
3168
+ - START with ${GITHUB_SEARCH_REPOSITORIES_TOOL_NAME} using TOPICS
3169
+ - Topics are underutilized but extremely powerful for discovery
3170
+ - Example: { topic: ["react", "typescript", "testing"], stars: ">500" }
3171
+
3172
+ Understanding Phase
3173
+ - ALWAYS use ${GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME} first to verify repository exists
3174
+ - CRITICAL: Many failures are due to incorrect repository names - verify before proceeding
3175
+ - Check README.md, docs/, configuration files
3176
+ - Understand project structure before searching code
3177
+ - Never make assumptions about repository names or file locations - always verify
3178
+
3179
+ Implementation Search (Specific Code):
3180
+ - Fetch importnat files using ${GITHUB_GET_FILE_CONTENT_TOOL_NAME}
3181
+ - Use ${GITHUB_SEARCH_CODE_TOOL_NAME} with NARROW queries
3182
+ - Start precise, broaden only if needed
3183
+ - Use exact phrases when you know patterns
3184
+ - Verify dependences and imports
3185
+
3186
+ File Access Validation Workflow:
3187
+ 1. VERIFY repository name first with ${GITHUB_SEARCH_CODE_TOOL_NAME} if unsure
3188
+ 2. Use ${GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME} to confirm repository structure
3189
+ 3. Navigate to parent directory first to understand layout
3190
+ 4. Only then use ${GITHUB_GET_FILE_CONTENT_TOOL_NAME} with confirmed paths
3191
+ 5. Most "file not found" errors are due to incorrect repository names
3172
3192
 
3173
- ## 9. TOOL-SPECIFIC BEST PRACTICES:
3193
+ TOOL-SPECIFIC BEST PRACTICES:
3174
3194
 
3175
- ### ${API_STATUS_CHECK_TOOL_NAME}:
3195
+ ${API_STATUS_CHECK_TOOL_NAME}:
3176
3196
  - Run FIRST when dealing with private repositories
3177
3197
  - Use organizations list to scope searches
3178
3198
  - Verify authentication before extensive searches
3179
3199
 
3180
- ### ${GITHUB_SEARCH_REPOSITORIES_TOOL_NAME}:
3200
+ ${GITHUB_SEARCH_REPOSITORIES_TOOL_NAME}:
3181
3201
  - Start with topic/language, add stars/forks filters later
3182
3202
  - Use date ranges for trending analysis
3183
3203
  - Combine multiple searches for comprehensive discovery
3184
3204
 
3185
- ### ${GITHUB_SEARCH_CODE_TOOL_NAME}:
3205
+ ${GITHUB_SEARCH_CODE_TOOL_NAME}:
3186
3206
  - Begin with function/class names, not full signatures
3187
3207
  - Use extension filters for targeted searches
3188
3208
  - Try partial matches before exact phrases
3189
3209
 
3190
- ### ${GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME}:
3210
+ ${GITHUB_VIEW_REPO_STRUCTURE_TOOL_NAME}:
3191
3211
  - Navigate from root, then drill down
3192
3212
  - Use for understanding project organization
3193
3213
  - Check common paths: src/, lib/, packages/
3194
3214
 
3195
- ### ${GITHUB_GET_FILE_CONTENT_TOOL_NAME}:
3215
+ ${GITHUB_GET_FILE_CONTENT_TOOL_NAME}:
3196
3216
  - Verify file paths with repo structure first
3197
3217
  - Use for implementation details and documentation
3198
- - Remember 300KB limit for large files
3199
3218
 
3200
- ### ${GITHUB_SEARCH_COMMITS_TOOL_NAME}:
3201
- - Search by feature keywords, not commit hashes
3202
- - Use author filter for contributor analysis
3219
+ ${GITHUB_SEARCH_COMMITS_TOOL_NAME}:
3220
+ - Search by feature keywords
3203
3221
  - Date ranges help track feature evolution
3204
3222
 
3205
- ### ${GITHUB_SEARCH_ISSUES_TOOL_NAME} & ${GITHUB_SEARCH_PULL_REQUESTS_TOOL_NAME}:
3206
- - Search for problem descriptions, not solutions
3223
+ ${GITHUB_SEARCH_ISSUES_TOOL_NAME} & ${GITHUB_SEARCH_PULL_REQUESTS_TOOL_NAME}:
3224
+ - Search for problem descriptions
3207
3225
  - Use state filters progressively
3208
- - Labels reveal project categorization
3209
3226
 
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
3227
+ ${NPM_PACKAGE_SEARCH_TOOL_NAME}:
3228
+ - Search npm package in registry - use one term at a time (partial or exact)
3214
3229
 
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
3230
+ ${NPM_VIEW_PACKAGE_TOOL_NAME}:
3231
+ - Returns essential data like github path and package metadata like dependencies, exports...
3219
3232
 
3220
- ## 10. CHAIN OF THOUGHT OPTIMIZATION:
3233
+ CHAIN OF THOUGHT OPTIMIZATION:
3221
3234
  - Plan search sequence before executing
3222
3235
  - Document reasoning for each search refinement
3223
3236
  - Build knowledge progressively, don't jump to specifics
3224
3237
  - Validate findings with multiple sources
3238
+ - Ask user for clarification if needed
3239
+ - Learn from results and refine search strategy
3240
+ - Output high level summary of the search and results based on data and not assumptions
3225
3241
  `;
3226
3242
 
3227
3243
  const SERVER_CONFIG = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octocode-mcp",
3
- "version": "2.3.7",
3
+ "version": "2.3.8",
4
4
  "description": "Model Context Protocol (MCP) server for advanced GitHub repository analysis, code discovery, and npm package exploration. Provides AI assistants with powerful tools to search, analyze, and understand codebases across GitHub and npm ecosystems.",
5
5
  "author": "Guy Bary <guybary@gmail.com>",
6
6
  "homepage": "https://github.com/bgauryy/octocode-mcp#readme",