octocode-mcp 2.3.1 → 2.3.4

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 (3) hide show
  1. package/README.md +53 -20
  2. package/build/index.js +594 -425
  3. package/package.json +27 -27
package/build/index.js CHANGED
@@ -3,90 +3,48 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { exec } from 'child_process';
5
5
  import { promisify } from 'util';
6
+ import { platform } from 'os';
6
7
  import NodeCache from 'node-cache';
7
8
  import crypto from 'crypto';
8
9
  import z from 'zod';
9
10
 
10
- const TOOL_NAMES = {
11
- // System & API Status
12
- API_STATUS_CHECK: 'api_status_check',
13
- // GitHub
14
- GITHUB_SEARCH_CODE: 'github_search_code',
15
- GITHUB_SEARCH_REPOS: 'github_search_repositories',
16
- GITHUB_SEARCH_COMMITS: 'github_search_commits',
17
- GITHUB_SEARCH_ISSUES: 'github_search_issues',
18
- GITHUB_SEARCH_PULL_REQUESTS: 'github_search_pull_requests',
19
- GITHUB_GET_CONTENTS: 'github_get_contents',
20
- GITHUB_GET_FILE_CONTENT: 'github_get_file_content',
21
- // NPM
22
- NPM_PACKAGE_SEARCH: 'npm_package_search',
23
- NPM_VIEW_PACKAGE: 'npm_view_package',
24
- };
25
- const PROMPT_SYSTEM_PROMPT = `You are a smart code research assistant with semantic search capabilities using gh and npm cli under the hood.
26
- You are able to search code, repositories, issues, pull requests, commits, and packages.
27
- Using Github: You are able to search code, repositories, issues, pull requests, commits
28
- Using NPM: You are able to search packages, view package metadata, and view package dependencies.
11
+ const PROMPT_SYSTEM_PROMPT = `Expert code research assistant for GitHub/NPM ecosystems (public/private). Use powerful semantic search for efficient code discovery.
29
12
 
30
- - ALWAYS START with ${TOOL_NAMES.API_STATUS_CHECK} before evaluating any user query (check npm and GitHub authentication and use GitHub organizations)
31
- - Understand users query from semantic understanding and choose the right tools for the job.
13
+ CORE TOOLS:
14
+ GitHub: Code, repos, issues, PRs, commits - supports boolean logic (AND/OR/NOT) and exact phrases
15
+ NPM: Package search (fuzzy only, no boolean) + metadata (repo URLs, exports, dependencies)
16
+ API Status: Check connectivity + user's GitHub organizations for private access
32
17
 
33
- TOOLS SEARCH STRATEGY:
34
- - ${TOOL_NAMES.GITHUB_SEARCH_REPOS} - START SHALLOW & GO BROAD: Use topics for exploratory discovery, then narrow down
35
- - ${TOOL_NAMES.GITHUB_SEARCH_CODE} - Find implementation patterns with SMART BOOLEAN OPERATORS (AND, OR, NOT)
36
- - ${TOOL_NAMES.NPM_PACKAGE_SEARCH} - Package ecosystem discovery
37
- - ${TOOL_NAMES.NPM_VIEW_PACKAGE} - Complete package analysis with exports for file discovery
38
- - ${TOOL_NAMES.GITHUB_SEARCH_ISSUES} + ${TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS} - Understanding development patterns
39
- - ${TOOL_NAMES.GITHUB_GET_CONTENTS} ${TOOL_NAMES.GITHUB_GET_FILE_CONTENT} - ALWAYS verify file existence before fetching
18
+ SEARCH STRATEGIES:
19
+ GitHub Code/Issues/PRs/Commits:
20
+ OR (broad): "useState OR setState" - finds alternatives
21
+ AND (precise): "react AND hooks" - requires both terms
22
+ NOT (filter): "auth NOT test" - excludes noise
23
+ Quotes (exact): "useEffect cleanup" - literal phrases
24
+ Combine with filters: language, path, owner for focus
40
25
 
41
- BEST PRACTICES:
42
- - Leverage smart boolean operators if possible in tools and advanced filters for smart research
43
- - For repo discovery: START SHALLOW with topics, then go BROAD to explore ecosystems
44
- - For code search: Be focused and specific with boolean operators
45
- - Always check documentation and examples when available
46
- - Add referenced and high quality research data from docs and quality code
47
- - Be creative and smart and use tools efficiently to gather most data
48
- - If query is short (e.g. where is the git repo of X package) make simple query and use tools to gather data fast
49
- - For broad research make steps and think about the best way to gather data`;
50
- const TOOL_DESCRIPTIONS = {
51
- [TOOL_NAMES.API_STATUS_CHECK]: `**MANDATORY FIRST STEP** - Check GitHub & NPM authentication status before any user query evaluation.
52
- Returns connectivity status and discovers user organizations - CRITICAL for accessing private/organizational repositories.
53
- **DO NOT PROCEED** with searches if both GitHub and NPM are disconnected.
54
- **USE ORGANIZATIONS** returned in 'github.organizations' array for targeted owner/org parameters in all search tools.
55
- Essential for enterprise code exploration and accessing company-specific repositories that require organizational membership.`,
56
- [TOOL_NAMES.NPM_PACKAGE_SEARCH]: `Search NPM packages by keyword. Use for package ecosystem discovery.`,
57
- [TOOL_NAMES.NPM_VIEW_PACKAGE]: `Get comprehensive package metadata essential for GitHub searches and code analysis. Returns complete package context:
58
- repositoryGitUrl - Direct GitHub repo link for accurate searches
59
- • exports - Critical for discovering available files and import paths
60
- • dependencies/devDependencies - Full ecosystem understanding
61
- • versions with dates - Historical evolution context
62
- The exports field is invaluable for GitHub file discovery - shows exact paths before fetching. Always use when finding packages in code.`,
63
- [TOOL_NAMES.GITHUB_SEARCH_CODE]: `SEMANTIC CODE DISCOVERY: Search code with SMART BOOLEAN OPERATORS (AND, OR, NOT).
64
- Prioritize intelligent boolean combinations: "logger AND debug NOT test", "config OR settings", "error handling AND typescript".
65
- Format: "term AND term" language:js path:src. Filters: owner/org/user, repo, extension, filename, language, path, size, limit, match scope.
66
- Use for finding actual implementation patterns and code examples.
67
- CRITICAL: When packages found in results or from user input, use ${TOOL_NAMES.NPM_VIEW_PACKAGE} for metadata/paths.`,
68
- [TOOL_NAMES.GITHUB_SEARCH_REPOS]: `Search repositories by name/description. START SHALLOW AND GO BROAD (contrary to code search).
69
- Use topics for EXPLORATORY discovery: topic:["cli","typescript","api"] to find ecosystem patterns.
70
- PRIMARY FILTERS work alone: owner, language, stars, topic, forks. SECONDARY FILTERS require query/primary filter: license, created, archived, includeForks, updated, visibility, match.
71
-
72
- SMART REPOS SEARCH PATTERNS: Use topic:["cli","typescript"] for semantic discovery. Use stars:">100" for quality. Use owner:"microsoft" for organization repos. Query supports GitHub syntax: "language:Go OR language:Rust".
73
-
74
- CRITICAL: When finding packages, use ${TOOL_NAMES.NPM_VIEW_PACKAGE} for metadata and repository paths.`,
75
- [TOOL_NAMES.GITHUB_GET_CONTENTS]: `Browse repository structure and verify file existence. ALWAYS use before github_get_file_content to confirm files exist and understand organization.`,
76
- [TOOL_NAMES.GITHUB_GET_FILE_CONTENT]: `Read file content. REQUIRES exact path verification from github_get_contents first. If fetching fails, check file existence with github_get_contents.`,
77
- [TOOL_NAMES.GITHUB_SEARCH_ISSUES]: `Find GitHub issues and problems with rich metadata (labels, reactions, comments, state).
78
- Discover pain points, feature requests, bug patterns, and community discussions.
79
- Filter by state, labels, assignee, or date ranges. Use for understanding project health and common user issues.`,
80
- [TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS]: `Find pull requests and implementations with detailed metadata.
81
- Discover how features were implemented, code review patterns, and development workflows.
82
- Filter by state, author, reviewer, or merge status. Essential for understanding project development practices.`,
83
- [TOOL_NAMES.GITHUB_SEARCH_COMMITS]: `Search commit history. Use for understanding code evolution and development patterns.`,
84
- };
26
+ NPM Search:
27
+ Space-separated keywords only: "react state management"
28
+ No boolean operators supported
29
+ Use npm_view_package for direct package metadata GitHub repo URL
30
+
31
+ OPTIMIZATION:
32
+ Check user's GitHub orgs for private repo access
33
+ npm_view_package gives repo URL instantly - avoid GitHub repo search
34
+ Use targeted searches, avoid redundant tool calls
35
+ Combine tools strategically: NPM → GitHub file content
36
+ Discovery: comprehensive multi-tool approach
37
+ Direct queries: quick targeted approach
38
+
39
+ CLI Help using CLI (use when needed to check failures)
40
+ gh <command> --help
41
+ npm <command> --help
42
+
43
+ Always provide code snippets and documentation references.`;
85
44
 
86
- // CONSOLIDATED ERROR & SUCCESS HANDLING
87
45
  function createResult(data, isError = false, suggestions) {
88
46
  const text = isError
89
- ? `${data}${suggestions ? ` | Try: ${suggestions.join(', ')}` : ''}`
47
+ ? `${data}${''}`
90
48
  : JSON.stringify(data, null, 2);
91
49
  return {
92
50
  content: [{ type: 'text', text }],
@@ -110,56 +68,6 @@ function parseJsonResponse(responseText, fallback = null) {
110
68
  return { data: (fallback || responseText), parsed: false };
111
69
  }
112
70
  }
113
- /**
114
- * Generate fallback suggestions for no results - ensures no tool suggests itself
115
- */
116
- function getNoResultsSuggestions(currentTool) {
117
- const suggestions = [];
118
- // Tool-specific fallbacks
119
- switch (currentTool) {
120
- case TOOL_NAMES.GITHUB_SEARCH_REPOS:
121
- suggestions.push(TOOL_NAMES.GITHUB_SEARCH_CODE, TOOL_NAMES.NPM_PACKAGE_SEARCH);
122
- break;
123
- case TOOL_NAMES.GITHUB_SEARCH_CODE:
124
- suggestions.push(TOOL_NAMES.GITHUB_SEARCH_REPOS, TOOL_NAMES.GITHUB_SEARCH_ISSUES);
125
- break;
126
- case TOOL_NAMES.NPM_PACKAGE_SEARCH:
127
- suggestions.push(TOOL_NAMES.GITHUB_SEARCH_REPOS, TOOL_NAMES.GITHUB_SEARCH_CODE);
128
- break;
129
- case TOOL_NAMES.GITHUB_SEARCH_ISSUES:
130
- suggestions.push(TOOL_NAMES.GITHUB_SEARCH_CODE, TOOL_NAMES.GITHUB_SEARCH_REPOS);
131
- break;
132
- case TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS:
133
- suggestions.push(TOOL_NAMES.GITHUB_SEARCH_ISSUES, TOOL_NAMES.GITHUB_SEARCH_CODE);
134
- break;
135
- case TOOL_NAMES.GITHUB_SEARCH_COMMITS:
136
- suggestions.push(TOOL_NAMES.GITHUB_SEARCH_CODE, TOOL_NAMES.GITHUB_SEARCH_REPOS);
137
- break;
138
- case TOOL_NAMES.GITHUB_GET_CONTENTS:
139
- case TOOL_NAMES.GITHUB_GET_FILE_CONTENT:
140
- suggestions.push(TOOL_NAMES.GITHUB_SEARCH_REPOS, TOOL_NAMES.GITHUB_SEARCH_CODE);
141
- break;
142
- default:
143
- // Fallback for any other tools
144
- suggestions.push(TOOL_NAMES.GITHUB_SEARCH_REPOS, TOOL_NAMES.GITHUB_SEARCH_CODE);
145
- }
146
- return suggestions.slice(0, 3);
147
- }
148
- /**
149
- * Generate fallback suggestions for errors - ensures no tool suggests itself
150
- */
151
- function getErrorSuggestions(currentTool) {
152
- const suggestions = [];
153
- // Always suggest API status check first (unless it's the current tool)
154
- if (currentTool !== TOOL_NAMES.API_STATUS_CHECK) {
155
- suggestions.push(TOOL_NAMES.API_STATUS_CHECK);
156
- }
157
- // Add discovery alternatives
158
- if (currentTool !== TOOL_NAMES.GITHUB_SEARCH_REPOS) {
159
- suggestions.push(TOOL_NAMES.GITHUB_SEARCH_REPOS);
160
- }
161
- return suggestions.slice(0, 3);
162
- }
163
71
  /**
164
72
  * Determines if a string needs quoting for GitHub search
165
73
  */
@@ -229,6 +137,86 @@ function isValidNpmCommand(command) {
229
137
  function isValidGhCommand(command) {
230
138
  return ALLOWED_GH_COMMANDS.includes(command);
231
139
  }
140
+ /**
141
+ * Get platform-specific shell configuration with PowerShell support
142
+ */
143
+ function getShellConfig(preferredWindowsShell) {
144
+ const isWindows = platform() === 'win32';
145
+ if (!isWindows) {
146
+ return {
147
+ shell: '/bin/sh',
148
+ shellEnv: '/bin/sh',
149
+ type: 'unix',
150
+ };
151
+ }
152
+ // Windows shell selection
153
+ const usesPowerShell = preferredWindowsShell === 'powershell';
154
+ if (usesPowerShell) {
155
+ return {
156
+ shell: 'powershell.exe',
157
+ shellEnv: 'powershell.exe',
158
+ type: 'powershell',
159
+ };
160
+ }
161
+ return {
162
+ shell: 'cmd.exe',
163
+ shellEnv: 'cmd.exe',
164
+ type: 'cmd',
165
+ };
166
+ }
167
+ /**
168
+ * Escape shell arguments to prevent shell injection and handle special characters
169
+ * Cross-platform compatible escaping for Windows CMD, PowerShell, and Unix shells
170
+ */
171
+ function escapeShellArg(arg, shellType) {
172
+ // Auto-detect shell type if not provided
173
+ if (!shellType) {
174
+ const isWindows = platform() === 'win32';
175
+ shellType = isWindows ? 'cmd' : 'unix';
176
+ }
177
+ switch (shellType) {
178
+ case 'powershell':
179
+ return escapePowerShellArg(arg);
180
+ case 'cmd':
181
+ return escapeWindowsCmdArg(arg);
182
+ case 'unix':
183
+ default:
184
+ return escapeUnixShellArg(arg);
185
+ }
186
+ }
187
+ /**
188
+ * Escape arguments for PowerShell
189
+ * PowerShell uses single quotes for literal strings and has special escaping rules
190
+ */
191
+ function escapePowerShellArg(arg) {
192
+ // PowerShell special characters that need escaping
193
+ if (/[\s&<>|;`$@"'()[\]{}]/.test(arg)) {
194
+ // Use single quotes for literal strings in PowerShell
195
+ // Escape single quotes by doubling them
196
+ return `'${arg.replace(/'/g, "''")}'`;
197
+ }
198
+ return arg;
199
+ }
200
+ /**
201
+ * Escape arguments for Windows CMD
202
+ */
203
+ function escapeWindowsCmdArg(arg) {
204
+ // Windows CMD escaping
205
+ if (/[\s&<>|^"]/.test(arg)) {
206
+ return `"${arg.replace(/"/g, '""')}"`;
207
+ }
208
+ return arg;
209
+ }
210
+ /**
211
+ * Escape arguments for Unix shells (/bin/sh, bash, etc.)
212
+ */
213
+ function escapeUnixShellArg(arg) {
214
+ // Unix shell escaping
215
+ if (/[^\w\-._/:=@]/.test(arg)) {
216
+ return `'${arg.replace(/'/g, "'\"'\"'")}'`;
217
+ }
218
+ return arg;
219
+ }
232
220
  /**
233
221
  * Execute NPM commands safely by validating against allowed commands
234
222
  * Security: Only executes commands that start with "npm {ALLOWED_COMMAND}"
@@ -238,11 +226,18 @@ async function executeNpmCommand(command, args = [], options = {}) {
238
226
  if (!isValidNpmCommand(command)) {
239
227
  return createErrorResult('Command not registered', new Error(`NPM command '${command}' is not in the allowed list`));
240
228
  }
241
- // Build command with validated prefix - no sanitization needed since we control the prefix
242
- const fullCommand = `npm ${command} ${args.join(' ')}`;
243
- const executeNpmCommand = () => executeCommand(fullCommand, 'npm', options);
229
+ // Get shell configuration
230
+ const shellConfig = getShellConfig(options.windowsShell);
231
+ // Build command with validated prefix and properly escaped arguments
232
+ const escapedArgs = args.map(arg => escapeShellArg(arg, shellConfig.type));
233
+ const fullCommand = `npm ${command} ${escapedArgs.join(' ')}`;
234
+ const executeNpmCommand = () => executeCommand(fullCommand, 'npm', options, shellConfig);
244
235
  if (options.cache) {
245
- const cacheKey = generateCacheKey('npm-exec', { command, args });
236
+ const cacheKey = generateCacheKey('npm-exec', {
237
+ command,
238
+ args,
239
+ shell: shellConfig.type,
240
+ });
246
241
  return withCache(cacheKey, executeNpmCommand);
247
242
  }
248
243
  return executeNpmCommand();
@@ -256,11 +251,18 @@ async function executeGitHubCommand(command, args = [], options = {}) {
256
251
  if (!isValidGhCommand(command)) {
257
252
  return createErrorResult('Command not registered', new Error(`GitHub command '${command}' is not in the allowed list`));
258
253
  }
259
- // Build command with validated prefix - no sanitization needed since we control the prefix
260
- const fullCommand = `gh ${command} ${args.join(' ')}`;
261
- const executeGhCommand = () => executeCommand(fullCommand, 'github', options);
254
+ // Get shell configuration
255
+ const shellConfig = getShellConfig(options.windowsShell);
256
+ // Build command with validated prefix and properly escaped arguments
257
+ const escapedArgs = args.map(arg => escapeShellArg(arg, shellConfig.type));
258
+ const fullCommand = `gh ${command} ${escapedArgs.join(' ')}`;
259
+ const executeGhCommand = () => executeCommand(fullCommand, 'github', options, shellConfig);
262
260
  if (options.cache) {
263
- const cacheKey = generateCacheKey('gh-exec', { command, args });
261
+ const cacheKey = generateCacheKey('gh-exec', {
262
+ command,
263
+ args,
264
+ shell: shellConfig.type,
265
+ });
264
266
  return withCache(cacheKey, executeGhCommand);
265
267
  }
266
268
  return executeGhCommand();
@@ -269,21 +271,23 @@ async function executeGitHubCommand(command, args = [], options = {}) {
269
271
  * Execute shell commands with timeout and error handling
270
272
  * Security: Should only be called with pre-validated command prefixes
271
273
  */
272
- async function executeCommand(fullCommand, type, options = {}) {
274
+ async function executeCommand(fullCommand, type, options = {}, shellConfig) {
273
275
  try {
274
276
  const defaultTimeout = type === 'npm' ? 30000 : 60000;
277
+ const config = shellConfig || getShellConfig(options.windowsShell);
275
278
  const execOptions = {
276
279
  timeout: options.timeout || defaultTimeout,
280
+ maxBuffer: 5 * 1024 * 1024, // 5MB buffer limit (increased from default 1MB)
277
281
  cwd: options.cwd,
278
282
  env: {
279
283
  ...process.env,
280
284
  ...options.env,
281
- // Ensure clean shell environment
282
- SHELL: '/bin/sh',
285
+ // Ensure clean shell environment - cross-platform compatible
286
+ SHELL: config.shellEnv,
283
287
  PATH: process.env.PATH,
284
288
  },
285
289
  encoding: 'utf-8',
286
- shell: '/bin/sh', // Use sh instead of default shell
290
+ shell: config.shell, // Use platform-appropriate shell
287
291
  };
288
292
  const { stdout, stderr } = await safeExecAsync(fullCommand, execOptions);
289
293
  // Handle different warning patterns for npm vs gh
@@ -303,6 +307,9 @@ async function executeCommand(fullCommand, type, options = {}) {
303
307
  result: stdout,
304
308
  timestamp: new Date().toISOString(),
305
309
  type,
310
+ platform: platform(),
311
+ shell: config.shell,
312
+ shellType: config.type,
306
313
  ...(stderr && { warning: stderr }), // Include warnings but don't treat as error
307
314
  });
308
315
  }
@@ -314,10 +321,13 @@ async function executeCommand(fullCommand, type, options = {}) {
314
321
  }
315
322
  }
316
323
 
324
+ const TOOL_NAME$9 = 'api_status_check';
325
+ const DESCRIPTION$9 = `Gets the list of user github organizations (in case the tool needs to use them in "owner" fields in github search) and checks users gh cli and npm cli login status.
326
+ Use when user asks on specific implementaon in his organization (e.g. - I work at Wix, or search Wix code about X) or when cli is failing.`;
317
327
  function registerApiStatusCheckTool(server) {
318
- server.tool(TOOL_NAMES.API_STATUS_CHECK, TOOL_DESCRIPTIONS[TOOL_NAMES.API_STATUS_CHECK], {}, {
319
- title: TOOL_NAMES.API_STATUS_CHECK,
320
- description: TOOL_DESCRIPTIONS[TOOL_NAMES.API_STATUS_CHECK],
328
+ server.tool(TOOL_NAME$9, DESCRIPTION$9, {}, {
329
+ title: 'Check API Connections and Github Organizations',
330
+ description: DESCRIPTION$9,
321
331
  readOnlyHint: true,
322
332
  destructiveHint: false,
323
333
  idempotentHint: true,
@@ -433,64 +443,56 @@ function registerApiStatusCheckTool(server) {
433
443
  });
434
444
  }
435
445
  catch (error) {
436
- return createResult(`API status check failed: ${error.message}`, true);
446
+ return createResult('API status check failed - verify GitHub CLI and NPM are installed and accessible', true);
437
447
  }
438
448
  });
439
449
  }
440
450
 
441
- /**
442
- * Registers the GitHub Code Search tool with the MCP server.
443
- *
444
- * This tool provides semantic code search across GitHub repositories using the GitHub CLI.
445
- * It supports advanced search features like boolean operators, qualifiers, and filters.
446
- *
447
- * Key features:
448
- * - Boolean operator support (AND, OR, NOT) in queries
449
- * - GitHub qualifier support (language:, path:, etc.)
450
- * - Repository, owner, and organization filtering
451
- * - File type, size, and visibility filtering
452
- * - Multiple match scopes and filtering options
453
- */
451
+ const TOOL_NAME$8 = 'github_search_code';
452
+ const DESCRIPTION$8 = `Search code across GitHub repositories using strategic boolean operators and filters with "gh code search" command.
453
+
454
+ SEARCH PATTERNS:
455
+ OR (broad): "useState hook" "useState OR hook" (auto-default for multi-word)
456
+ AND (precise): "react AND hooks" (both terms required)
457
+ NOT (filter): "auth NOT test" (exclude unwanted)
458
+ Exact phrases: "useState hook" (quoted for literal match)
459
+ Combine with filters: language, owner, path for laser focus
460
+
461
+ RESTRICTIVENESS: OR (broadest) < AND < Exact Phrase (most precise)`;
454
462
  function registerGitHubSearchCodeTool(server) {
455
- server.tool(TOOL_NAMES.GITHUB_SEARCH_CODE, TOOL_DESCRIPTIONS[TOOL_NAMES.GITHUB_SEARCH_CODE], {
463
+ server.tool(TOOL_NAME$8, DESCRIPTION$8, {
456
464
  query: z
457
465
  .string()
458
466
  .min(1)
459
- .describe('Search query with boolean operators (AND, OR, NOT) and qualifiers. ' +
460
- 'Examples: "react lifecycle", "error handling", "logger AND debug", "config OR settings", "main NOT test". ' +
461
- 'Use quotes for exact phrases. Supports GitHub search syntax.'),
467
+ .describe('Search query with strategic boolean operators. SEARCH PATTERNS: OR (auto-default): "useState hook" → "useState OR hook" for BROADEST discovery. AND (explicit): "react AND hooks" requires BOTH terms for RESTRICTIVE intersection. EXACT PHRASE (escaped quotes): "useState hook" finds literal sequence for MOST PRECISE targeting. NOT (filtering): "auth NOT test" excludes unwanted results. USAGE GUIDE: Use OR for exploration/alternatives, AND for specific combinations, exact phrases for documentation/APIs, NOT for removing noise. RESTRICTIVENESS: OR < AND < Exact Phrase. No parentheses - simple boolean logic only.'),
462
468
  owner: z
463
469
  .union([z.string(), z.array(z.string())])
464
470
  .optional()
465
- .describe('Repository owner/organization(s). Single string or array for multiple owners. Leave empty for global search.'),
471
+ .describe('Repository owner/organization filter. Examples: "microsoft", "google". Combines with other filters for targeted search. get from user orgamizations in case of private repositories search (e.g. for employees of organizations)'),
466
472
  repo: z
467
473
  .union([z.string(), z.array(z.string())])
468
474
  .optional()
469
- .describe('Specific repositories in "owner/repo" format. Can be a single repository string or array of repository strings. Requires owner parameter to be set.'),
475
+ .describe('Specific repositories in "owner/repo" format. Examples: "facebook/react", "microsoft/vscode". Requires owner parameter.'),
470
476
  language: z
471
477
  .string()
472
478
  .optional()
473
- .describe('Programming language filter (e.g., "javascript", "python", "go").'),
479
+ .describe('Programming language filter. Examples: "javascript", "python", "typescript", "go". Highly effective for targeted searches.'),
474
480
  extension: z
475
481
  .string()
476
482
  .optional()
477
- .describe('File extension filter without dot (e.g., "js", "py", "md").'),
483
+ .describe('File extension filter without dot. Examples: "js", "ts", "py", "md", "json". Precise file type targeting.'),
478
484
  filename: z
479
485
  .string()
480
486
  .optional()
481
- .describe('Exact filename filter (e.g., "package.json", "Dockerfile", "README.md").'),
487
+ .describe('Exact filename filter. Examples: "package.json", "Dockerfile", "README.md", "index.js". Perfect for config files.'),
482
488
  path: z
483
489
  .string()
484
490
  .optional()
485
- .describe('Directory path filter with wildcards (e.g., "src/", "*/tests/*", "docs/**").'),
491
+ .describe('Directory path filter. Examples: "src/", "test/", "docs/", "components/". Focus search on specific directories.'),
486
492
  size: z
487
493
  .string()
488
494
  .optional()
489
495
  .describe('File size filter in KB with operators (e.g., ">100", "<50", "10..100").'),
490
- visibility: z
491
- .enum(['public', 'private', 'internal'])
492
- .optional()
493
- .describe('Repository visibility filter. "public" for public repos, "private" for private repos you have access to.'),
494
496
  limit: z
495
497
  .number()
496
498
  .int()
@@ -502,18 +504,24 @@ function registerGitHubSearchCodeTool(server) {
502
504
  match: z
503
505
  .union([z.enum(['file', 'path']), z.array(z.enum(['file', 'path']))])
504
506
  .optional()
505
- .describe('Search scope: "file" for content search, "path" for filename/path search. If array provided, only first value is used due to GitHub API limitations.'),
507
+ .describe('Search scope: "file" searches code content, "path" searches filenames/paths. Use "path" to find files by name.'),
508
+ visibility: z
509
+ .enum(['public', 'private', 'internal'])
510
+ .optional()
511
+ .describe('Repository visibility filter: "public", "private", or "internal". Defaults to accessible repositories.'),
506
512
  }, {
507
- title: TOOL_NAMES.GITHUB_SEARCH_CODE,
508
- description: TOOL_DESCRIPTIONS[TOOL_NAMES.GITHUB_SEARCH_CODE],
513
+ title: TOOL_NAME$8,
514
+ description: DESCRIPTION$8,
509
515
  readOnlyHint: true,
510
516
  destructiveHint: false,
511
517
  idempotentHint: true,
512
518
  openWorldHint: true,
513
519
  }, async (args) => {
514
520
  try {
515
- if (args.repo && !args.owner) {
516
- return createResult('Repository search requires owner parameter', true);
521
+ // Validate parameter combinations
522
+ const validationError = validateSearchParameters(args);
523
+ if (validationError) {
524
+ return createResult(validationError, true);
517
525
  }
518
526
  const result = await searchGitHubCode(args);
519
527
  if (result.isError) {
@@ -525,113 +533,268 @@ function registerGitHubSearchCodeTool(server) {
525
533
  const items = Array.isArray(codeResults) ? codeResults : [];
526
534
  return createSuccessResult$1({
527
535
  query: args.query,
536
+ processed_query: parseSearchQuery(args.query, args),
528
537
  total_count: items.length,
529
538
  items: items,
539
+ cli_command: execResult.command,
540
+ debug_info: {
541
+ has_complex_boolean_logic: hasComplexBooleanLogic(args.query),
542
+ escaped_args: buildGitHubCliArgs(args),
543
+ original_query: args.query,
544
+ },
530
545
  });
531
546
  }
532
547
  catch (error) {
533
- return createErrorResult$1('Code search failed', error);
548
+ const errorMessage = error.message || '';
549
+ // Handle JSON parsing errors
550
+ if (errorMessage.includes('JSON')) {
551
+ return createErrorResult$1('GitHub CLI returned invalid response - check if GitHub CLI is up to date with "gh version" and try again', error);
552
+ }
553
+ return createErrorResult$1('GitHub code search failed - verify parameters and try with simpler query or specific filters (language, owner, path)', error);
534
554
  }
535
555
  });
536
556
  }
557
+ /**
558
+ * Enhanced query parser that handles exact strings, boolean operators, and filters
559
+ */
560
+ function parseSearchQuery(query, filters) {
561
+ // Step 1: Handle quoted strings more intelligently
562
+ // Convert escaped quotes to simple quotes to avoid shell escaping issues
563
+ let processedQuery = query.replace(/\\"/g, '"');
564
+ // Step 2: Preserve exact phrases (quoted strings)
565
+ const exactPhrases = [];
566
+ // Extract quoted strings and replace with placeholders
567
+ const quotedMatches = processedQuery.match(/"[^"]+"/g) || [];
568
+ quotedMatches.forEach((match, index) => {
569
+ const placeholder = `__EXACT_PHRASE_${index}__`;
570
+ exactPhrases.push(match);
571
+ processedQuery = processedQuery.replace(match, placeholder);
572
+ });
573
+ // Step 3: Check complexity BEFORE adding auto-OR logic
574
+ const originalHasComplexLogic = hasComplexBooleanLogic(processedQuery);
575
+ // Step 4: Smart boolean logic - default to OR between terms if no explicit operators
576
+ let searchQuery = processedQuery;
577
+ // Check if query already has explicit boolean operators
578
+ if (!originalHasComplexLogic) {
579
+ // Split by whitespace and join with OR for better search results
580
+ const terms = processedQuery
581
+ .trim()
582
+ .split(/\s+/)
583
+ .filter(term => term.length > 0);
584
+ if (terms.length > 1) {
585
+ searchQuery = terms.join(' OR ');
586
+ }
587
+ }
588
+ // Step 5: Handle filters differently based on ORIGINAL query complexity
589
+ const githubFilters = [];
590
+ // Always add path and visibility to query string (they don't have CLI equivalents)
591
+ if (filters.path) {
592
+ githubFilters.push(`path:${filters.path}`);
593
+ }
594
+ if (filters.visibility) {
595
+ githubFilters.push(`visibility:${filters.visibility}`);
596
+ }
597
+ // For complex boolean queries, add ALL filters to query string to avoid CLI conflicts
598
+ if (originalHasComplexLogic) {
599
+ if (filters.language) {
600
+ githubFilters.push(`language:${filters.language}`);
601
+ }
602
+ // For complex queries with both language and extension, prioritize language
603
+ if (filters.extension && !filters.language) {
604
+ githubFilters.push(`extension:${filters.extension}`);
605
+ }
606
+ if (filters.filename) {
607
+ githubFilters.push(`filename:${filters.filename}`);
608
+ }
609
+ if (filters.size) {
610
+ githubFilters.push(`size:${filters.size}`);
611
+ }
612
+ }
613
+ // Step 6: Combine query with GitHub filters using proper spacing
614
+ if (githubFilters.length > 0) {
615
+ searchQuery = `${searchQuery} ${githubFilters.join(' ')}`;
616
+ }
617
+ // Step 7: Restore exact phrases
618
+ exactPhrases.forEach((phrase, index) => {
619
+ const placeholder = `__EXACT_PHRASE_${index}__`;
620
+ searchQuery = searchQuery.replace(placeholder, phrase);
621
+ });
622
+ return searchQuery.trim();
623
+ }
624
+ /**
625
+ * Check if query contains complex boolean logic that might conflict with CLI flags
626
+ */
627
+ function hasComplexBooleanLogic(query) {
628
+ const booleanOperators = /\b(AND|OR|NOT)\b/i;
629
+ return booleanOperators.test(query);
630
+ }
631
+ /**
632
+ * Build command line arguments for GitHub CLI with improved parameter handling
633
+ */
634
+ function buildGitHubCliArgs(params) {
635
+ const args = ['code'];
636
+ // Parse and add the main search query
637
+ const searchQuery = parseSearchQuery(params.query, params);
638
+ args.push(searchQuery);
639
+ // Determine strategy based on ORIGINAL query complexity, not processed query
640
+ const hasComplexLogic = hasComplexBooleanLogic(params.query);
641
+ // For simple queries, use CLI flags for better performance and validation
642
+ if (!hasComplexLogic) {
643
+ if (params.language) {
644
+ args.push(`--language=${params.language}`);
645
+ }
646
+ if (params.extension) {
647
+ args.push(`--extension=${params.extension}`);
648
+ }
649
+ if (params.filename) {
650
+ args.push(`--filename=${params.filename}`);
651
+ }
652
+ if (params.size) {
653
+ args.push(`--size=${params.size}`);
654
+ }
655
+ }
656
+ // For complex queries, filters are already in the query string (handled by parseSearchQuery)
657
+ // Always add limit
658
+ if (params.limit) {
659
+ args.push(`--limit=${params.limit}`);
660
+ }
661
+ // Handle match parameter with conflict resolution
662
+ if (params.match) {
663
+ const matchValues = Array.isArray(params.match)
664
+ ? params.match
665
+ : [params.match];
666
+ // Use the first match type when multiple are provided
667
+ const matchValue = matchValues[0];
668
+ args.push(`--match=${matchValue}`);
669
+ }
670
+ // Handle owner parameter - can be string or array
671
+ if (params.owner && !params.repo) {
672
+ const ownerValues = Array.isArray(params.owner)
673
+ ? params.owner
674
+ : [params.owner];
675
+ ownerValues.forEach(owner => args.push(`--owner=${owner}`));
676
+ }
677
+ // Handle repository filters with improved validation
678
+ if (params.owner && params.repo) {
679
+ const owners = Array.isArray(params.owner) ? params.owner : [params.owner];
680
+ const repos = Array.isArray(params.repo) ? params.repo : [params.repo];
681
+ // Create repo filters for each owner/repo combination
682
+ owners.forEach(owner => {
683
+ repos.forEach(repo => {
684
+ // Handle both "owner/repo" format and just "repo" format
685
+ if (repo.includes('/')) {
686
+ args.push(`--repo=${repo}`);
687
+ }
688
+ else {
689
+ args.push(`--repo=${owner}/${repo}`);
690
+ }
691
+ });
692
+ });
693
+ }
694
+ // JSON output with all available fields
695
+ args.push('--json=repository,path,textMatches,sha,url');
696
+ return args;
697
+ }
537
698
  async function searchGitHubCode(params) {
538
699
  const cacheKey = generateCacheKey('gh-code', params);
539
700
  return withCache(cacheKey, async () => {
540
701
  try {
541
- const args = ['code'];
542
- // Build the main query - preserve boolean operators and GitHub syntax
543
- let query = params.query;
544
- // Add path filter to query if provided (GitHub search syntax)
545
- if (params.path) {
546
- query = `${query} path:${params.path}`;
547
- }
548
- // Add visibility filter to query if provided
549
- if (params.visibility) {
550
- query = `${query} visibility:${params.visibility}`;
551
- }
552
- args.push(query);
553
- // Add command-line filters
554
- if (params.language)
555
- args.push(`--language=${params.language}`);
556
- if (params.extension)
557
- args.push(`--extension=${params.extension}`);
558
- if (params.filename)
559
- args.push(`--filename=${params.filename}`);
560
- if (params.size)
561
- args.push(`--size=${params.size}`);
562
- if (params.limit)
563
- args.push(`--limit=${params.limit}`);
564
- // Handle match parameter - can be string or array
565
- // Note: GitHub search API doesn't support multiple match types in a single query
566
- if (params.match) {
567
- const matchValues = Array.isArray(params.match)
568
- ? params.match
569
- : [params.match];
570
- // GitHub API limitation: can't use both in:file and in:path in same query
571
- // Use the first match type when multiple are provided
572
- const matchValue = matchValues[0];
573
- args.push(`--match=${matchValue}`);
574
- }
575
- // Handle owner parameter - can be string or array
576
- if (params.owner && !params.repo) {
577
- const ownerValues = Array.isArray(params.owner)
578
- ? params.owner
579
- : [params.owner];
580
- ownerValues.forEach(owner => args.push(`--owner=${owner}`));
581
- }
582
- // Handle repository filters
583
- if (params.owner && params.repo) {
584
- const owners = Array.isArray(params.owner)
585
- ? params.owner
586
- : [params.owner];
587
- const repos = Array.isArray(params.repo) ? params.repo : [params.repo];
588
- // Create repo filters for each owner/repo combination
589
- owners.forEach(owner => {
590
- repos.forEach(repo => {
591
- // Handle both "owner/repo" format and just "repo" format
592
- if (repo.includes('/')) {
593
- args.push(`--repo=${repo}`);
594
- }
595
- else {
596
- args.push(`--repo=${owner}/${repo}`);
597
- }
598
- });
599
- });
600
- }
601
- // JSON output with all available fields
602
- args.push('--json=repository,path,textMatches,sha,url');
702
+ const args = buildGitHubCliArgs(params);
603
703
  const result = await executeGitHubCommand('search', args, {
604
704
  cache: false,
605
705
  });
606
706
  return result;
607
707
  }
608
708
  catch (error) {
609
- return createErrorResult$1('Failed to execute search command', error);
709
+ const errorMessage = error.message || '';
710
+ // Parse specific GitHub CLI error types
711
+ if (errorMessage.includes('authentication')) {
712
+ return createErrorResult$1('GitHub CLI authentication required - run the api_status_check tool to verify authentication and available organizations', error);
713
+ }
714
+ if (errorMessage.includes('rate limit')) {
715
+ return createErrorResult$1('GitHub API rate limit exceeded - wait a few minutes before searching again or use more specific filters to reduce results', error);
716
+ }
717
+ if (errorMessage.includes('validation failed') ||
718
+ errorMessage.includes('Invalid query')) {
719
+ return createErrorResult$1('Invalid search query syntax - check boolean operators (AND/OR/NOT), quotes for exact phrases, and filter formats. Try simplifying your query', error);
720
+ }
721
+ if (errorMessage.includes('repository not found') ||
722
+ errorMessage.includes('owner not found')) {
723
+ return createErrorResult$1('Repository or owner not found - run api_status_check tool to verify available organizations and access permissions', error);
724
+ }
725
+ if (errorMessage.includes('timeout')) {
726
+ return createErrorResult$1('Search timeout - query too broad or complex. Try adding filters like language, owner, or path to narrow results', error);
727
+ }
728
+ // Generic fallback with helpful guidance
729
+ return createErrorResult$1('GitHub code search failed - run api_status_check tool to verify authentication and permissions, or try simplifying your query', error);
610
730
  }
611
731
  });
612
732
  }
733
+ /**
734
+ * Validate parameter combinations to prevent conflicts
735
+ */
736
+ function validateSearchParameters(params) {
737
+ // Query validation
738
+ if (!params.query.trim()) {
739
+ return 'Empty search query - provide a search term like "useState", "function init", or "api AND endpoint"';
740
+ }
741
+ if (params.query.length > 1000) {
742
+ return 'Search query too long - limit to 1000 characters. Try breaking into smaller, focused searches';
743
+ }
744
+ // Repository validation
745
+ if (params.repo && !params.owner) {
746
+ return 'Missing owner parameter - when searching specific repositories, format as owner/repo (e.g., "microsoft/vscode") or provide both owner and repo parameters';
747
+ }
748
+ // Invalid characters in query
749
+ if (params.query.includes('\\') && !params.query.includes('\\"')) {
750
+ return 'Invalid escape characters in query - use quotes for exact phrases: "exact phrase" instead of escaping';
751
+ }
752
+ // Boolean operator validation
753
+ const invalidBooleans = params.query.match(/\b(and|or|not)\b/g);
754
+ if (invalidBooleans) {
755
+ return `Boolean operators must be uppercase - use ${invalidBooleans.map(op => op.toUpperCase()).join(', ')} instead of ${invalidBooleans.join(', ')}`;
756
+ }
757
+ // Unmatched quotes
758
+ const quoteCount = (params.query.match(/"/g) || []).length;
759
+ if (quoteCount % 2 !== 0) {
760
+ return 'Unmatched quotes in query - ensure all quotes are properly paired for exact phrase matching';
761
+ }
762
+ // Size parameter validation
763
+ if (params.size && !/^[<>]=?\d+$|^\d+\.\.\d+$|^\d+$/.test(params.size)) {
764
+ return 'Invalid size format - use ">100", "<50", "10..100", or "100" for file size filtering';
765
+ }
766
+ return null; // No validation errors
767
+ }
613
768
 
769
+ const TOOL_NAME$7 = 'github_get_file_content';
770
+ const DESCRIPTION$7 = `Read file content. This tool REQUIRES exact path verification from github_get_contents or package view exports. If fetching fails, re-check file existence with github_get_contents or branch name.`;
614
771
  function registerFetchGitHubFileContentTool(server) {
615
- server.tool(TOOL_NAMES.GITHUB_GET_FILE_CONTENT, TOOL_DESCRIPTIONS[TOOL_NAMES.GITHUB_GET_FILE_CONTENT], {
772
+ server.tool(TOOL_NAME$7, DESCRIPTION$7, {
616
773
  owner: z
617
774
  .string()
618
775
  .min(1)
776
+ .max(100)
777
+ .regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/)
619
778
  .describe(`Repository owner/organization (e.g., 'microsoft', 'facebook')`),
620
779
  repo: z
621
780
  .string()
622
781
  .min(1)
782
+ .max(100)
783
+ .regex(/^[a-zA-Z0-9._-]+$/)
623
784
  .describe(`Repository name (e.g., 'vscode', 'react'). Case-sensitive.`),
624
785
  branch: z
625
786
  .string()
626
787
  .min(1)
788
+ .max(255)
789
+ .regex(/^[^\s]+$/)
627
790
  .describe(`Branch name (e.g., 'main', 'master'). Auto-fallback to common branches if not found.`),
628
791
  filePath: z
629
792
  .string()
630
793
  .min(1)
631
794
  .describe(`File path from repository root (e.g., 'README.md', 'src/index.js'). Use github_get_contents to explore structure.`),
632
795
  }, {
633
- title: TOOL_NAMES.GITHUB_GET_FILE_CONTENT,
634
- description: TOOL_DESCRIPTIONS[TOOL_NAMES.GITHUB_GET_FILE_CONTENT],
796
+ title: TOOL_NAME$7,
797
+ description: DESCRIPTION$7,
635
798
  readOnlyHint: true,
636
799
  destructiveHint: false,
637
800
  idempotentHint: true,
@@ -666,26 +829,7 @@ function registerFetchGitHubFileContentTool(server) {
666
829
  return result;
667
830
  }
668
831
  catch (error) {
669
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
670
- let suggestions = [];
671
- if (errorMessage.includes('404') ||
672
- errorMessage.includes('Not Found')) {
673
- suggestions = [
674
- TOOL_NAMES.GITHUB_GET_CONTENTS,
675
- TOOL_NAMES.GITHUB_SEARCH_CODE,
676
- ];
677
- }
678
- else if (errorMessage.includes('403') ||
679
- errorMessage.includes('Forbidden')) {
680
- suggestions = [TOOL_NAMES.API_STATUS_CHECK];
681
- }
682
- else if (errorMessage.includes('branch')) {
683
- suggestions = [TOOL_NAMES.GITHUB_GET_CONTENTS];
684
- }
685
- else {
686
- suggestions = getErrorSuggestions(TOOL_NAMES.GITHUB_GET_FILE_CONTENT);
687
- }
688
- return createResult(`Failed to fetch file content: ${errorMessage}. Context: ${args.owner}/${args.repo}/${args.filePath} on ${args.branch}`, true, suggestions);
832
+ return createResult('File fetch failed - verify file path exists or try github_get_contents first', true);
689
833
  }
690
834
  });
691
835
  }
@@ -718,19 +862,35 @@ async function fetchGitHubFileContent(params) {
718
862
  }
719
863
  // Handle common errors
720
864
  if (errorMsg.includes('404')) {
721
- return createErrorResult$1(`File not found: ${filePath}`, new Error(`File does not exist in ${owner}/${repo} on branch ${branch}`));
865
+ return createErrorResult$1('File not found - verify path with github_get_contents first', new Error(filePath));
722
866
  }
723
867
  else if (errorMsg.includes('403')) {
724
- return createErrorResult$1(`Access denied: ${filePath}`, new Error(`Permission denied for ${owner}/${repo}`));
868
+ return createErrorResult$1('Access denied - repository may be private or require authentication', new Error(`${owner}/${repo}`));
869
+ }
870
+ else if (errorMsg.includes('maxBuffer') ||
871
+ errorMsg.includes('stdout maxBuffer length exceeded')) {
872
+ return createErrorResult$1(` File too large to fetch (buffer limit exceeded)\n\n` +
873
+ `This usually happens with files >300KB. Consider:\n` +
874
+ ` Use github_search_code to find specific patterns\n` +
875
+ ` Browse directory structure with github_get_contents`, new Error(`Buffer overflow: ${filePath}`));
725
876
  }
726
877
  else {
727
- return createErrorResult$1(`Failed to fetch file: ${filePath}`, new Error(errorMsg));
878
+ return createErrorResult$1('Fetch failed - check repository and file path', new Error(errorMsg));
728
879
  }
729
880
  }
730
881
  return await processFileContent(result, owner, repo, branch, filePath);
731
882
  }
732
883
  catch (error) {
733
- return createErrorResult$1(`Unexpected error fetching file: ${filePath}`, error);
884
+ const errorMessage = error.message;
885
+ // Handle maxBuffer errors that escape the main try-catch
886
+ if (errorMessage.includes('maxBuffer') ||
887
+ errorMessage.includes('stdout maxBuffer length exceeded')) {
888
+ return createErrorResult$1(` File too large to fetch (buffer limit exceeded)\n\n` +
889
+ `This usually happens with files >300KB. Consider:\n` +
890
+ ` Use github_search_code to find specific patterns instead of fetching the whole file\n` +
891
+ ` Browse directory structure with github_get_contents`, error);
892
+ }
893
+ return createErrorResult$1('Unexpected error during file fetch - check connection and permissions', error);
734
894
  }
735
895
  });
736
896
  }
@@ -740,30 +900,35 @@ async function processFileContent(result, owner, repo, branch, filePath) {
740
900
  const fileData = JSON.parse(execResult.result);
741
901
  // Check if it's a directory
742
902
  if (Array.isArray(fileData)) {
743
- return createErrorResult$1(`Path is a directory: ${filePath}`, new Error(`"${filePath}" is a directory, not a file`));
903
+ return createErrorResult$1('Path is directory - use github_get_contents to browse directory structure', new Error(filePath));
744
904
  }
745
905
  const fileSize = fileData.size || 0;
746
- const MAX_FILE_SIZE = 500 * 1024; // 500KB limit for simplicity
747
- // Check file size
906
+ const MAX_FILE_SIZE = 300 * 1024; // 300KB limit for better performance and reliability
907
+ // Check file size with helpful message
748
908
  if (fileSize > MAX_FILE_SIZE) {
749
- return createErrorResult$1(`File too large: ${filePath}`, new Error(`File size (${Math.round(fileSize / 1024)}KB) exceeds limit (500KB)`));
909
+ const fileSizeKB = Math.round(fileSize / 1024);
910
+ const maxSizeKB = Math.round(MAX_FILE_SIZE / 1024);
911
+ return createErrorResult$1(` File too large to display (${fileSizeKB}KB > ${maxSizeKB}KB limit)\n\n` +
912
+ `For large files like this, consider:\n` +
913
+ ` Use github_get_contents to browse directory structure\n` +
914
+ ` Search specific code patterns with github_search_code`, new Error(`File: ${filePath} (${fileSizeKB}KB)`));
750
915
  }
751
916
  // Get and decode content
752
917
  const base64Content = fileData.content?.replace(/\s/g, ''); // Remove all whitespace
753
918
  if (!base64Content) {
754
- return createErrorResult$1(`Empty file: ${filePath}`, new Error(`File appears to be empty`));
919
+ return createErrorResult$1('Empty file - file has no content to display', new Error(filePath));
755
920
  }
756
921
  let decodedContent;
757
922
  try {
758
923
  const buffer = Buffer.from(base64Content, 'base64');
759
924
  // Simple binary check - look for null bytes
760
925
  if (buffer.indexOf(0) !== -1) {
761
- return createErrorResult$1(`Binary file detected: ${filePath}`, new Error(`Binary files cannot be displayed as text`));
926
+ return createErrorResult$1('Binary file detected - cannot display binary content as text', new Error(filePath));
762
927
  }
763
928
  decodedContent = buffer.toString('utf-8');
764
929
  }
765
930
  catch (decodeError) {
766
- return createErrorResult$1(`Failed to decode file: ${filePath}`, new Error(`Unable to decode file as UTF-8`));
931
+ return createErrorResult$1('Decode failed - file encoding not supported or corrupted', new Error(filePath));
767
932
  }
768
933
  // Return simplified response
769
934
  const response = {
@@ -781,8 +946,17 @@ async function processFileContent(result, owner, repo, branch, filePath) {
781
946
  return createSuccessResult$1(response);
782
947
  }
783
948
 
949
+ const TOOL_NAME$6 = 'github_search_repositories';
950
+ const DESCRIPTION$6 = `Search repositories by name/description. Start shallow and go broad: use topics for exploratory discovery (e.g., topic:["cli","typescript","api"]) to find ecosystem patterns.
951
+ PRIMARY FILTERS work alone: owner, language, stars, topic, forks. SECONDARY FILTERS require a query or primary filter: license, created, archived, includeForks, updated, visibility, match.
952
+ SMART REPOS SEARCH PATTERNS: Use topic:["cli","typescript"] for semantic discovery; stars:">100" for quality; owner:"microsoft" for organization repos. Query supports GitHub syntax: "language:Go OR language:Rust".
953
+
954
+ EFFICIENCY NOTE: If you have a package name, use npm_view_package FIRST to get repositoryGitUrl - this tool becomes UNNECESSARY
955
+
956
+ SMART INTEGRATION: When finding packages → npm_view_package + npm_package_search provide direct repo access
957
+ AVOID: Searching for "react" repos when npm_view_package("react") gives you the exact repository instantly`;
784
958
  function registerSearchGitHubReposTool(server) {
785
- server.tool(TOOL_NAMES.GITHUB_SEARCH_REPOS, TOOL_DESCRIPTIONS[TOOL_NAMES.GITHUB_SEARCH_REPOS], {
959
+ server.tool(TOOL_NAME$6, DESCRIPTION$6, {
786
960
  query: z
787
961
  .string()
788
962
  .optional()
@@ -791,52 +965,66 @@ function registerSearchGitHubReposTool(server) {
791
965
  owner: z
792
966
  .string()
793
967
  .optional()
794
- .describe('Repository owner/organization. PRIMARY FILTER - works alone.'),
968
+ .describe('Repository owner/organization (e.g., "microsoft", "facebook").'),
795
969
  language: z
796
970
  .string()
797
971
  .optional()
798
- .describe('Programming language. PRIMARY FILTER - works alone.'),
972
+ .describe('Programming language (e.g., "javascript", "python", "go").'),
799
973
  stars: z
800
974
  .string()
801
975
  .optional()
802
- .describe('Stars count with ranges: "100", ">500", "<50", "10..100", ">=1000". PRIMARY FILTER - works alone. Use >100 for quality projects.'),
976
+ .describe('Stars count with ranges: "100", ">500", "<50", "10..100", ">=1000". Use >100 for quality projects.'),
803
977
  topic: z
804
978
  .array(z.string())
805
979
  .optional()
806
- .describe('Filter by topics. PRIMARY FILTER - works alone.'),
807
- forks: z
980
+ .describe('Filter by topics (e.g., ["cli", "typescript", "api"]).'),
981
+ forks: z.number().optional().describe('Exact forks count.'),
982
+ numberOfTopics: z
808
983
  .number()
809
984
  .optional()
810
- .describe('Exact forks count. PRIMARY FILTER - works alone.'),
985
+ .describe('Filter on number of topics.'),
811
986
  // SECONDARY FILTERS (require query or primary filter)
812
987
  license: z
813
988
  .array(z.string())
814
989
  .optional()
815
- .describe('License types. REQUIRES query or primary filter.'),
990
+ .describe('License types (e.g., ["mit", "apache-2.0"]).'),
816
991
  match: z
817
992
  .enum(['name', 'description', 'readme'])
818
993
  .optional()
819
- .describe('Search scope. REQUIRES query.'),
994
+ .describe('Search scope: "name", "description", or "readme".'),
820
995
  visibility: z
821
996
  .enum(['public', 'private', 'internal'])
822
997
  .optional()
823
- .describe('Repository visibility. REQUIRES query or primary filter.'),
998
+ .describe('Repository visibility filter.'),
824
999
  created: z
825
1000
  .string()
826
1001
  .optional()
827
- .describe('Created date filter: ">2020-01-01", "<2023-12-31". REQUIRES query or primary filter.'),
1002
+ .describe('Created date filter: ">2020-01-01", "<2023-12-31", "2022-01-01..2023-12-31".'),
828
1003
  updated: z
829
1004
  .string()
830
1005
  .optional()
831
- .describe('Updated date filter. REQUIRES query or primary filter.'),
832
- archived: z
833
- .boolean()
834
- .optional()
835
- .describe('Archived state. REQUIRES query or primary filter.'),
1006
+ .describe('Updated date filter (same format as created).'),
1007
+ archived: z.boolean().optional().describe('Filter by archived state.'),
836
1008
  includeForks: z
837
1009
  .enum(['false', 'true', 'only'])
838
1010
  .optional()
839
- .describe('Include forks. REQUIRES query or primary filter.'),
1011
+ .describe('Include forks: "false" (default), "true", or "only".'),
1012
+ goodFirstIssues: z
1013
+ .string()
1014
+ .optional()
1015
+ .describe('Filter by good first issues count (e.g., ">=10", ">5").'),
1016
+ helpWantedIssues: z
1017
+ .string()
1018
+ .optional()
1019
+ .describe('Filter by help wanted issues count (e.g., ">=5", ">10").'),
1020
+ followers: z
1021
+ .number()
1022
+ .optional()
1023
+ .describe('Filter by number of followers.'),
1024
+ size: z
1025
+ .string()
1026
+ .optional()
1027
+ .describe('Repository size filter in KB (e.g., ">100", "<50", "10..100").'),
840
1028
  // Sorting and limits
841
1029
  sort: z
842
1030
  .enum(['forks', 'help-wanted-issues', 'stars', 'updated', 'best-match'])
@@ -857,8 +1045,8 @@ function registerSearchGitHubReposTool(server) {
857
1045
  .default(25)
858
1046
  .describe('Maximum results (default: 25, max: 50)'),
859
1047
  }, {
860
- title: TOOL_NAMES.GITHUB_SEARCH_REPOS,
861
- description: TOOL_DESCRIPTIONS[TOOL_NAMES.GITHUB_SEARCH_REPOS],
1048
+ title: TOOL_NAME$6,
1049
+ description: DESCRIPTION$6,
862
1050
  readOnlyHint: true,
863
1051
  destructiveHint: false,
864
1052
  idempotentHint: true,
@@ -880,7 +1068,7 @@ function registerSearchGitHubReposTool(server) {
880
1068
  return result;
881
1069
  }
882
1070
  catch (error) {
883
- return createResult(`Search failed: ${error.message}`, true, getErrorSuggestions(TOOL_NAMES.GITHUB_SEARCH_REPOS));
1071
+ return createResult('Repository search failed - check query syntax, filters, or try broader terms', true);
884
1072
  }
885
1073
  });
886
1074
  }
@@ -947,7 +1135,7 @@ async function searchGitHubRepos(params) {
947
1135
  isPrivate: repo.isPrivate || false,
948
1136
  isArchived: repo.isArchived || false,
949
1137
  isFork: repo.isFork || false,
950
- topics: [], // Topics not available via CLI JSON output
1138
+ topics: [], // GitHub CLI search repos doesn't provide topics in JSON output
951
1139
  license: repo.license?.name || null,
952
1140
  hasIssues: repo.hasIssues || false,
953
1141
  openIssuesCount: repo.openIssuesCount || 0,
@@ -971,55 +1159,31 @@ async function searchGitHubRepos(params) {
971
1159
  }
972
1160
  : {
973
1161
  repositories: [],
974
- suggestions: [
975
- `${TOOL_NAMES.NPM_PACKAGE_SEARCH} "${params.query || 'package'}"`,
976
- `${TOOL_NAMES.GITHUB_SEARCH_CODE} "${params.query || 'code'}"`,
977
- 'Try broader search terms',
978
- 'Check spelling and try synonyms',
979
- ],
980
1162
  }),
981
1163
  });
982
1164
  }
983
1165
  catch (error) {
984
- return createErrorResult$1('Failed to search GitHub repositories', error);
1166
+ return createErrorResult$1('GitHub repository search failed - verify connection or try simpler query', error);
985
1167
  }
986
1168
  });
987
1169
  }
988
1170
  function buildGitHubReposSearchCommand(params) {
989
1171
  // Build query following GitHub CLI patterns
990
1172
  const query = params.query?.trim() || '';
991
- // Handle complex queries (with qualifiers, operators, or --) differently
992
- const hasComplexSyntax = query.includes('--') ||
993
- query.includes(':') ||
994
- query.includes('OR') ||
995
- query.includes('AND') ||
996
- query.includes('(') ||
997
- query.includes(')') ||
998
- query.startsWith('-');
999
1173
  const args = ['repos'];
1000
- // Only add query if it exists
1174
+ // Only add query if it exists and handle it properly
1001
1175
  if (query) {
1002
- if (hasComplexSyntax) {
1003
- // For complex queries with special syntax, we need to be more careful
1004
- // Split by spaces but preserve quoted strings and handle special characters
1005
- const queryParts = query.match(/(?:[^\s"]+|"[^"]*")+/g) || [];
1006
- queryParts.forEach(part => {
1007
- // If part contains shell special characters, quote it
1008
- if (/[><=&|$`(){}[\];\\]/.test(part) && !part.includes('"')) {
1009
- args.push(`"${part}"`);
1010
- }
1011
- else {
1012
- args.push(part);
1013
- }
1014
- });
1176
+ // For repository search, treat multi-word queries as a single quoted string
1177
+ // This matches GitHub CLI expected behavior for repo searches
1178
+ if (query.includes(' ')) {
1179
+ args.push(query); // Let GitHub CLI handle the quoting
1015
1180
  }
1016
1181
  else {
1017
- // For simple queries, use quoting logic
1018
- const queryString = needsQuoting(query) ? `"${query}"` : query;
1019
- args.push(queryString);
1182
+ args.push(query);
1020
1183
  }
1021
1184
  }
1022
1185
  // Add JSON output with specific fields for structured data parsing
1186
+ // Note: 'topics' field is not available in GitHub CLI search repos JSON output
1023
1187
  args.push('--json', 'name,fullName,description,language,stargazersCount,forksCount,updatedAt,createdAt,url,owner,isPrivate,license,hasIssues,openIssuesCount,isArchived,isFork,visibility');
1024
1188
  // PRIMARY FILTERS - Handle owner as single string (BaseSearchParams) or array
1025
1189
  if (params.owner) {
@@ -1034,6 +1198,8 @@ function buildGitHubReposSearchCommand(params) {
1034
1198
  args.push(`--forks=${params.forks}`);
1035
1199
  if (params.topic && params.topic.length > 0)
1036
1200
  args.push(`--topic=${params.topic.join(',')}`);
1201
+ if (params.numberOfTopics !== undefined)
1202
+ args.push(`--number-topics=${params.numberOfTopics}`);
1037
1203
  // Only add stars filter if it's a valid numeric value or range
1038
1204
  if (params.stars !== undefined &&
1039
1205
  params.stars !== '*' &&
@@ -1042,14 +1208,15 @@ function buildGitHubReposSearchCommand(params) {
1042
1208
  const starsValue = params.stars.trim();
1043
1209
  const isValidStars = /^(\d+|>\d+|<\d+|\d+\.\.\d+|>=\d+|<=\d+)$/.test(starsValue);
1044
1210
  if (isValidStars) {
1045
- args.push(`--stars="${params.stars}"`);
1211
+ // Don't add quotes around the stars value - GitHub CLI handles this internally
1212
+ args.push(`--stars=${starsValue}`);
1046
1213
  }
1047
1214
  }
1048
1215
  // SECONDARY FILTERS - only add if we have primary filters
1049
1216
  if (params.archived !== undefined)
1050
1217
  args.push(`--archived=${params.archived}`);
1051
1218
  if (params.created)
1052
- args.push(`--created="${params.created}"`);
1219
+ args.push(`--created=${params.created}`);
1053
1220
  if (params.includeForks)
1054
1221
  args.push(`--include-forks=${params.includeForks}`);
1055
1222
  if (params.license && params.license.length > 0)
@@ -1057,9 +1224,17 @@ function buildGitHubReposSearchCommand(params) {
1057
1224
  if (params.match)
1058
1225
  args.push(`--match=${params.match}`);
1059
1226
  if (params.updated)
1060
- args.push(`--updated="${params.updated}"`);
1227
+ args.push(`--updated=${params.updated}`);
1061
1228
  if (params.visibility)
1062
1229
  args.push(`--visibility=${params.visibility}`);
1230
+ if (params.goodFirstIssues)
1231
+ args.push(`--good-first-issues=${params.goodFirstIssues}`);
1232
+ if (params.helpWantedIssues)
1233
+ args.push(`--help-wanted-issues=${params.helpWantedIssues}`);
1234
+ if (params.followers !== undefined)
1235
+ args.push(`--followers=${params.followers}`);
1236
+ if (params.size)
1237
+ args.push(`--size=${params.size}`);
1063
1238
  // SORTING AND LIMITS
1064
1239
  if (params.limit)
1065
1240
  args.push(`--limit=${params.limit}`);
@@ -1073,12 +1248,14 @@ function buildGitHubReposSearchCommand(params) {
1073
1248
  return { command: 'search', args };
1074
1249
  }
1075
1250
 
1251
+ const TOOL_NAME$5 = 'github_search_commits';
1252
+ const DESCRIPTION$5 = `Search commit history with powerful boolean logic and exact phrase matching. Use advanced GitHub search syntax including AND/OR operators for precise commit discovery. Understand code evolution patterns, track bug fixes, and analyze development workflows over time. Filter by author, date ranges, commit content, and repository metadata with surgical precision.`;
1076
1253
  function registerSearchGitHubCommitsTool(server) {
1077
- server.tool(TOOL_NAMES.GITHUB_SEARCH_COMMITS, TOOL_DESCRIPTIONS[TOOL_NAMES.GITHUB_SEARCH_COMMITS], {
1254
+ server.tool(TOOL_NAME$5, DESCRIPTION$5, {
1078
1255
  query: z
1079
1256
  .string()
1080
1257
  .optional()
1081
- .describe('Search query with full GitHub syntax support: "readme typo" (AND), "bug fix" (phrase), "author:john OR committer:jane" (OR), "-- -author:botuser" (exclude). Optional - can search with just filters.'),
1258
+ .describe('Search query with POWERFUL boolean logic and exact phrase matching. BOOLEAN OPERATORS: "fix AND bug" (both required), "fix OR update" (either term), "readme typo" (implicit AND). EXACT PHRASES: "initial commit" (precise phrase matching). ADVANCED SYNTAX: "author:john OR committer:jane" (user qualifiers), "-- -author:botuser" (exclusions). STRENGTH: Surgical precision for commit discovery across millions of repositories. Optional - can search with just filters.'),
1082
1259
  // Basic filters
1083
1260
  owner: z
1084
1261
  .string()
@@ -1135,10 +1312,10 @@ function registerSearchGitHubCommitsTool(server) {
1135
1312
  .max(50)
1136
1313
  .optional()
1137
1314
  .default(25)
1138
- .describe('Maximum results (default: 25)'),
1315
+ .describe('Maximum results (default: 25, max: 50)'),
1139
1316
  }, {
1140
- title: TOOL_NAMES.GITHUB_SEARCH_COMMITS,
1141
- description: TOOL_DESCRIPTIONS[TOOL_NAMES.GITHUB_SEARCH_COMMITS],
1317
+ title: TOOL_NAME$5,
1318
+ description: DESCRIPTION$5,
1142
1319
  readOnlyHint: true,
1143
1320
  destructiveHint: false,
1144
1321
  idempotentHint: true,
@@ -1157,7 +1334,7 @@ function registerSearchGitHubCommitsTool(server) {
1157
1334
  return result;
1158
1335
  }
1159
1336
  catch (error) {
1160
- return createResult(`Search failed: ${error.message}`, true, getErrorSuggestions(TOOL_NAMES.GITHUB_SEARCH_COMMITS));
1337
+ return createResult('Commit search failed - check query syntax, filters, or repository access', true);
1161
1338
  }
1162
1339
  });
1163
1340
  }
@@ -1252,17 +1429,10 @@ async function searchGitHubCommits(params) {
1252
1429
  query: params.query,
1253
1430
  total: 0,
1254
1431
  commits: [],
1255
- suggestions: [
1256
- `${TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS} "${params.query || 'pr'}"`,
1257
- `${TOOL_NAMES.GITHUB_SEARCH_ISSUES} "${params.query || 'issue'}"`,
1258
- `${TOOL_NAMES.GITHUB_SEARCH_CODE} "${params.query || 'code'}"`,
1259
- 'Try broader search terms',
1260
- 'Check spelling and try synonyms',
1261
- ],
1262
1432
  });
1263
1433
  }
1264
1434
  catch (error) {
1265
- return createErrorResult$1('Failed to search GitHub commits', error);
1435
+ return createErrorResult$1('GitHub commit search failed - verify repository exists or try different filters', error);
1266
1436
  }
1267
1437
  });
1268
1438
  }
@@ -1352,12 +1522,20 @@ function buildGitHubCommitsSearchCommand(params) {
1352
1522
  }
1353
1523
 
1354
1524
  // TODO: add PR commeents. e.g, gh pr view <PR_NUMBER_OR_URL_OR_BRANCH> --comments
1525
+ const TOOL_NAME$4 = 'github_search_pull_requests';
1526
+ const DESCRIPTION$4 = `Find pull requests and implementations with detailed metadata. Discover feature implementations, code review patterns, and development workflows.
1527
+
1528
+ SEARCH PATTERNS:
1529
+ Boolean: "fix AND bug", "refactor OR cleanup", "feature NOT draft"
1530
+ Exact phrases: "initial commit" (quoted)
1531
+ GitHub qualifiers: "is:merged review:approved base:main"
1532
+ Combine with filters for targeted PR discovery`;
1355
1533
  function registerSearchGitHubPullRequestsTool(server) {
1356
- server.tool(TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS, TOOL_DESCRIPTIONS[TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS], {
1534
+ server.tool(TOOL_NAME$4, DESCRIPTION$4, {
1357
1535
  query: z
1358
1536
  .string()
1359
1537
  .min(1, 'Search query is required and cannot be empty')
1360
- .describe('Search query to find pull requests'),
1538
+ .describe('Search query with GITHUB SEARCH SYNTAX support. BOOLEAN OPERATORS: "fix AND bug" (both required), "refactor OR cleanup" (either term), "feature NOT draft" (excludes). EXACT PHRASES: "initial commit" (precise matching). GITHUB QUALIFIERS: "is:merged review:approved base:main" (native GitHub syntax). COMBINED: Mix boolean logic with qualifiers for precise PR discovery.'),
1361
1539
  owner: z.string().optional().describe('Repository owner/organization'),
1362
1540
  repo: z.string().optional().describe('Repository name'),
1363
1541
  author: z.string().optional().describe('Filter by pull request author'),
@@ -1382,6 +1560,15 @@ function registerSearchGitHubPullRequestsTool(server) {
1382
1560
  mergedAt: z.string().optional().describe('Filter by merged date'),
1383
1561
  closed: z.string().optional().describe('Filter by closed date'),
1384
1562
  draft: z.boolean().optional().describe('Filter by draft state'),
1563
+ checks: z
1564
+ .enum(['pending', 'success', 'failure'])
1565
+ .optional()
1566
+ .describe('Filter based on status of the checks'),
1567
+ merged: z.boolean().optional().describe('Filter based on merged state'),
1568
+ review: z
1569
+ .enum(['none', 'required', 'approved', 'changes_requested'])
1570
+ .optional()
1571
+ .describe('Filter based on review status'),
1385
1572
  limit: z
1386
1573
  .number()
1387
1574
  .int()
@@ -1389,7 +1576,7 @@ function registerSearchGitHubPullRequestsTool(server) {
1389
1576
  .max(50)
1390
1577
  .optional()
1391
1578
  .default(25)
1392
- .describe('Maximum results (default: 25)'),
1579
+ .describe('Maximum results (default: 25, max: 50)'),
1393
1580
  sort: z
1394
1581
  .enum([
1395
1582
  'comments',
@@ -1412,24 +1599,24 @@ function registerSearchGitHubPullRequestsTool(server) {
1412
1599
  .default('desc')
1413
1600
  .describe('Order (default: desc)'),
1414
1601
  }, {
1415
- title: TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS,
1416
- description: TOOL_DESCRIPTIONS[TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS],
1602
+ title: TOOL_NAME$4,
1603
+ description: DESCRIPTION$4,
1417
1604
  readOnlyHint: true,
1418
1605
  destructiveHint: false,
1419
1606
  idempotentHint: true,
1420
1607
  openWorldHint: true,
1421
1608
  }, async (args) => {
1422
1609
  if (!args.query?.trim()) {
1423
- return createErrorResult$1('Search query is required and cannot be empty', new Error('Invalid query'));
1610
+ return createErrorResult$1('Search query is required and cannot be empty - provide keywords to search for pull requests', new Error('Invalid query'));
1424
1611
  }
1425
1612
  if (args.query.length > 256) {
1426
- return createErrorResult$1('Search query is too long. Please limit to 256 characters or less.', new Error('Query too long'));
1613
+ return createErrorResult$1('Search query is too long. Please limit to 256 characters or less - simplify your search terms', new Error('Query too long'));
1427
1614
  }
1428
1615
  try {
1429
1616
  return await searchGitHubPullRequests(args);
1430
1617
  }
1431
1618
  catch (error) {
1432
- return createErrorResult$1('Failed to search GitHub pull requests', error);
1619
+ return createErrorResult$1('GitHub pull requests search failed - verify repository access and query syntax', error);
1433
1620
  }
1434
1621
  });
1435
1622
  }
@@ -1521,6 +1708,12 @@ function buildGitHubPullRequestsAPICommand(params) {
1521
1708
  queryParts.push(`merged:${params.mergedAt}`);
1522
1709
  if (params.draft !== undefined)
1523
1710
  queryParts.push(`draft:${params.draft}`);
1711
+ if (params.checks)
1712
+ queryParts.push(`status:${params.checks}`);
1713
+ if (params.merged !== undefined)
1714
+ queryParts.push(`is:${params.merged ? 'merged' : 'unmerged'}`);
1715
+ if (params.review)
1716
+ queryParts.push(`review:${params.review}`);
1524
1717
  // Add type qualifier to search only pull requests
1525
1718
  queryParts.push('type:pr');
1526
1719
  const query = queryParts.filter(Boolean).join(' ');
@@ -1533,13 +1726,22 @@ function buildGitHubPullRequestsAPICommand(params) {
1533
1726
  return { command: 'api', args: [apiPath] };
1534
1727
  }
1535
1728
 
1729
+ const TOOL_NAME$3 = 'npm_package_search';
1730
+ const DESCRIPTION$3 = `Search npm packages by keywords using fuzzy matching.
1731
+
1732
+ IMPORTANT LIMITATIONS:
1733
+ NO BOOLEAN OPERATORS: NPM search does NOT support AND/OR/NOT - use space-separated keywords for broader search
1734
+ FUZZY MATCHING ONLY: No exact phrase matching - searches are approximate keyword matching
1735
+ KEYWORD-BASED: Best results with simple, space-separated terms like "react hooks" or "cli typescript"
1736
+
1737
+ Required when package name is unknown. If you have the exact package name, use npm_view_package directly. This reduces the need to use GitHub search when packages are found.`;
1536
1738
  const MAX_DESCRIPTION_LENGTH = 100;
1537
1739
  const MAX_KEYWORDS = 10;
1538
1740
  function registerNpmSearchTool(server) {
1539
- server.tool(TOOL_NAMES.NPM_PACKAGE_SEARCH, TOOL_DESCRIPTIONS[TOOL_NAMES.NPM_PACKAGE_SEARCH], {
1741
+ server.tool(TOOL_NAME$3, DESCRIPTION$3, {
1540
1742
  queries: z
1541
1743
  .union([z.string(), z.array(z.string())])
1542
- .describe('Package names or keywords to search for'),
1744
+ .describe('Package names or keywords to search for. NOTE: No boolean operators (AND/OR/NOT) supported - use simple space-separated keywords like "react hooks" or "typescript cli" for fuzzy matching.'),
1543
1745
  searchlimit: z
1544
1746
  .number()
1545
1747
  .int()
@@ -1547,10 +1749,10 @@ function registerNpmSearchTool(server) {
1547
1749
  .max(50)
1548
1750
  .optional()
1549
1751
  .default(20)
1550
- .describe('Max results per query (default: 20)'),
1752
+ .describe('Max results per query (default: 20, max: 50)'),
1551
1753
  }, {
1552
- title: TOOL_NAMES.NPM_PACKAGE_SEARCH,
1553
- description: TOOL_DESCRIPTIONS[TOOL_NAMES.NPM_PACKAGE_SEARCH],
1754
+ title: TOOL_NAME$3,
1755
+ description: DESCRIPTION$3,
1554
1756
  readOnlyHint: true,
1555
1757
  destructiveHint: false,
1556
1758
  idempotentHint: true,
@@ -1580,12 +1782,10 @@ function registerNpmSearchTool(server) {
1580
1782
  results: deduplicatedPackages,
1581
1783
  });
1582
1784
  }
1583
- const suggestions = getNoResultsSuggestions(TOOL_NAMES.NPM_PACKAGE_SEARCH);
1584
- return createResult('No packages found', true, suggestions);
1785
+ return createResult('No packages found', true);
1585
1786
  }
1586
1787
  catch (error) {
1587
- const suggestions = getErrorSuggestions(TOOL_NAMES.NPM_PACKAGE_SEARCH);
1588
- return createResult(`Search failed: ${error.message}`, true, suggestions);
1788
+ return createResult('NPM package search failed - check search terms or try different keywords', true);
1589
1789
  }
1590
1790
  });
1591
1791
  }
@@ -1637,8 +1837,10 @@ function parseNpmSearchOutput(output) {
1637
1837
  }
1638
1838
  }
1639
1839
 
1840
+ const TOOL_NAME$2 = 'github_get_contents';
1841
+ const DESCRIPTION$2 = `Browse repository structure and verify file existence. Use before github_get_file_content to confirm files exist and understand organization, especially when the path is uncertain.`;
1640
1842
  function registerViewRepositoryStructureTool(server) {
1641
- server.tool(TOOL_NAMES.GITHUB_GET_CONTENTS, TOOL_DESCRIPTIONS[TOOL_NAMES.GITHUB_GET_CONTENTS], {
1843
+ server.tool(TOOL_NAME$2, DESCRIPTION$2, {
1642
1844
  owner: z
1643
1845
  .string()
1644
1846
  .min(1)
@@ -1668,8 +1870,8 @@ function registerViewRepositoryStructureTool(server) {
1668
1870
  .describe('Directory path within repository (e.g., "src/components", "packages/core"). ' +
1669
1871
  'Leave empty for root. Use previous results to navigate deeper.'),
1670
1872
  }, {
1671
- title: TOOL_NAMES.GITHUB_GET_CONTENTS,
1672
- description: TOOL_DESCRIPTIONS[TOOL_NAMES.GITHUB_GET_CONTENTS],
1873
+ title: TOOL_NAME$2,
1874
+ description: DESCRIPTION$2,
1673
1875
  readOnlyHint: true,
1674
1876
  destructiveHint: false,
1675
1877
  idempotentHint: true,
@@ -1699,48 +1901,7 @@ function registerViewRepositoryStructureTool(server) {
1699
1901
  }
1700
1902
  catch (error) {
1701
1903
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1702
- // Enhanced context-aware error suggestions
1703
- let suggestions = [];
1704
- const context = `${args.owner}/${args.repo}`;
1705
- if (errorMessage.includes('404') ||
1706
- errorMessage.includes('Not Found')) {
1707
- if (args.path) {
1708
- suggestions = [
1709
- `Try exploring root first: github_get_contents(owner: "${args.owner}", repo: "${args.repo}", branch: "${args.branch}", path: "")`,
1710
- `Search for similar paths: github_search_code with path filters`,
1711
- ];
1712
- }
1713
- else {
1714
- suggestions = [
1715
- `Search for repository: github_search_repositories(query: "${args.repo}")`,
1716
- `Verify owner exists: github_search_repositories(owner: "${args.owner}")`,
1717
- ];
1718
- }
1719
- }
1720
- else if (errorMessage.includes('403') ||
1721
- errorMessage.includes('Forbidden')) {
1722
- suggestions = [
1723
- `Check authentication status: api_status_check`,
1724
- `Try public repositories first to verify access`,
1725
- ];
1726
- }
1727
- else if (errorMessage.includes('rate limit')) {
1728
- suggestions = [
1729
- `Check rate limit status: api_status_check`,
1730
- `Wait before retrying or use authenticated requests`,
1731
- ];
1732
- }
1733
- else if (errorMessage.includes('invalid') ||
1734
- errorMessage.includes('branch')) {
1735
- suggestions = [
1736
- `Find valid branches: github_search_repositories(query: "${context}")`,
1737
- `Try common branches: main, master, develop`,
1738
- ];
1739
- }
1740
- else {
1741
- suggestions = getErrorSuggestions(TOOL_NAMES.GITHUB_GET_CONTENTS);
1742
- }
1743
- return createResult(`Repository exploration failed: ${errorMessage}. Context: ${context} on ${args.branch}${args.path ? ` at ${args.path}` : ''}`, true, suggestions);
1904
+ return createResult(`Repository exploration failed: ${errorMessage} - verify repository exists and is accessible`, true);
1744
1905
  }
1745
1906
  });
1746
1907
  }
@@ -1751,7 +1912,7 @@ function registerViewRepositoryStructureTool(server) {
1751
1912
  * - Smart branch detection: fetches repository default branch automatically
1752
1913
  * - Intelligent fallback: tries requested -> default -> common branches
1753
1914
  * - Input validation: prevents path traversal and validates GitHub naming
1754
- * - Enhanced error context: provides actionable suggestions for recovery
1915
+ * - Clear error context: provides descriptive error messages
1755
1916
  * - Efficient caching: avoids redundant API calls
1756
1917
  */
1757
1918
  async function viewRepositoryStructure(params) {
@@ -1794,19 +1955,17 @@ async function viewRepositoryStructure(params) {
1794
1955
  const errorMsg = lastError?.message || 'Unknown error';
1795
1956
  if (errorMsg.includes('404') || errorMsg.includes('Not Found')) {
1796
1957
  if (path) {
1797
- throw new Error(`Path "${path}" not found in repository ${owner}/${repo}. ` +
1798
- `Use github_get_contents with empty path first to discover the repository structure.`);
1958
+ throw new Error(`Path "${path}" not found - verify path exists or use github_search_code to find files`);
1799
1959
  }
1800
1960
  else {
1801
- throw new Error(`Repository ${owner}/${repo} not found or no accessible branches. ` +
1802
- `Tried branches: ${branchesToTry.join(', ')}`);
1961
+ throw new Error(`Repository not found: ${owner}/${repo} - verify owner/repo names or use github_search_repositories`);
1803
1962
  }
1804
1963
  }
1805
1964
  else if (errorMsg.includes('403') || errorMsg.includes('Forbidden')) {
1806
- throw new Error(`Access denied to repository ${owner}/${repo}`);
1965
+ throw new Error(`Access denied to repository ${owner}/${repo} - repository may be private or require authentication`);
1807
1966
  }
1808
1967
  else {
1809
- throw new Error(`Repository ${owner}/${repo} not found or path "${path}" doesn't exist`);
1968
+ throw new Error(`Access failed: ${owner}/${repo} - check connection or repository permissions`);
1810
1969
  }
1811
1970
  }
1812
1971
  // Limit total items to 100 for efficiency
@@ -1864,7 +2023,7 @@ async function viewRepositoryStructure(params) {
1864
2023
  }
1865
2024
  catch (error) {
1866
2025
  const errorMessage = error instanceof Error ? error.message : String(error);
1867
- return createErrorResult$1(`Repository access failed: ${owner}/${repo}${path ? ` at ${path}` : ''}`, new Error(errorMessage));
2026
+ return createErrorResult$1('Repository access failed - verify repository exists and check authentication', new Error(errorMessage));
1868
2027
  }
1869
2028
  });
1870
2029
  }
@@ -1900,12 +2059,20 @@ async function getSmartBranchFallback(owner, repo, requestedBranch) {
1900
2059
  return branches;
1901
2060
  }
1902
2061
 
2062
+ const TOOL_NAME$1 = 'github_search_issues';
2063
+ const DESCRIPTION$1 = `Find GitHub issues with rich metadata (labels, reactions, comments, state). Discover pain points, feature requests, and bug patterns with boolean logic and GitHub qualifiers.
2064
+
2065
+ SEARCH PATTERNS:
2066
+ Boolean: "bug AND crash", "feature OR enhancement", "error NOT test"
2067
+ Exact phrases: "memory leak" (quoted)
2068
+ GitHub qualifiers: "is:open label:bug author:username"
2069
+ Combine with filters for precision`;
1903
2070
  function registerSearchGitHubIssuesTool(server) {
1904
- server.tool(TOOL_NAMES.GITHUB_SEARCH_ISSUES, TOOL_DESCRIPTIONS[TOOL_NAMES.GITHUB_SEARCH_ISSUES], {
2071
+ server.tool(TOOL_NAME$1, DESCRIPTION$1, {
1905
2072
  query: z
1906
2073
  .string()
1907
2074
  .min(1, 'Search query is required and cannot be empty')
1908
- .describe('Search query to find issues'),
2075
+ .describe('Search query with GITHUB SEARCH SYNTAX support. BOOLEAN OPERATORS: "bug AND crash" (both required), "feature OR enhancement" (either term), "error NOT test" (excludes). EXACT PHRASES: "memory leak" (precise matching). GITHUB QUALIFIERS: "is:open label:bug author:username" (native GitHub syntax). COMBINED: Mix boolean logic with qualifiers for precise issue discovery.'),
1909
2076
  owner: z
1910
2077
  .string()
1911
2078
  .min(1)
@@ -1998,26 +2165,26 @@ function registerSearchGitHubIssuesTool(server) {
1998
2165
  .max(50)
1999
2166
  .optional()
2000
2167
  .default(25)
2001
- .describe('Maximum results (default: 25)'),
2168
+ .describe('Maximum results (default: 25, max: 50)'),
2002
2169
  }, {
2003
- title: TOOL_NAMES.GITHUB_SEARCH_ISSUES,
2004
- description: TOOL_DESCRIPTIONS[TOOL_NAMES.GITHUB_SEARCH_ISSUES],
2170
+ title: TOOL_NAME$1,
2171
+ description: DESCRIPTION$1,
2005
2172
  readOnlyHint: true,
2006
2173
  destructiveHint: false,
2007
2174
  idempotentHint: true,
2008
2175
  openWorldHint: true,
2009
2176
  }, async (args) => {
2010
2177
  if (!args.query?.trim()) {
2011
- return createErrorResult$1('Search query is required and cannot be empty', new Error('Invalid query'));
2178
+ return createErrorResult$1('Search query is required and cannot be empty - provide keywords to search for issues', new Error('Invalid query'));
2012
2179
  }
2013
2180
  if (args.query.length > 256) {
2014
- return createErrorResult$1('Search query is too long. Please limit to 256 characters or less.', new Error('Query too long'));
2181
+ return createErrorResult$1('Search query is too long. Please limit to 256 characters or less - simplify your search terms', new Error('Query too long'));
2015
2182
  }
2016
2183
  try {
2017
2184
  return await searchGitHubIssues(args);
2018
2185
  }
2019
2186
  catch (error) {
2020
- return createErrorResult$1('Failed to search GitHub issues', error);
2187
+ return createErrorResult$1('GitHub issues search failed - check repository exists and query is valid', error);
2021
2188
  }
2022
2189
  });
2023
2190
  }
@@ -2110,15 +2277,17 @@ function buildGitHubIssuesAPICommand(params) {
2110
2277
  return { command: 'api', args: [apiPath] };
2111
2278
  }
2112
2279
 
2280
+ const TOOL_NAME = 'npm_view_package';
2281
+ const DESCRIPTION = `Get comprehensive NPM package metadata efficiently. Returns repository URL, exports, dependencies, and version history without needing GitHub searches. Essential for finding package source code and understanding project structure.`;
2113
2282
  function registerNpmViewPackageTool(server) {
2114
- server.tool(TOOL_NAMES.NPM_VIEW_PACKAGE, TOOL_DESCRIPTIONS[TOOL_NAMES.NPM_VIEW_PACKAGE], {
2283
+ server.tool(TOOL_NAME, DESCRIPTION, {
2115
2284
  packageName: z
2116
2285
  .string()
2117
2286
  .min(1, 'Package name is required')
2118
2287
  .describe('NPM package name to analyze. Returns complete package context including exports (critical for GitHub file discovery), repository URL, dependencies, and version history.'),
2119
2288
  }, {
2120
- title: TOOL_NAMES.NPM_VIEW_PACKAGE,
2121
- description: TOOL_DESCRIPTIONS[TOOL_NAMES.NPM_VIEW_PACKAGE],
2289
+ title: TOOL_NAME,
2290
+ description: DESCRIPTION,
2122
2291
  readOnlyHint: true,
2123
2292
  destructiveHint: false,
2124
2293
  idempotentHint: true,
@@ -2126,17 +2295,17 @@ function registerNpmViewPackageTool(server) {
2126
2295
  }, async (args) => {
2127
2296
  try {
2128
2297
  if (!args.packageName || args.packageName.trim() === '') {
2129
- return createResult('Package name is required', true);
2298
+ return createResult('Package name is required - provide a valid NPM package name', true);
2130
2299
  }
2131
2300
  // Basic package name validation
2132
2301
  if (!/^[a-z0-9@._/-]+$/.test(args.packageName)) {
2133
- return createResult('Invalid package name format', true);
2302
+ return createResult('Invalid package name format - use standard NPM naming (e.g., "package-name" or "@scope/package")', true);
2134
2303
  }
2135
2304
  const result = await npmViewPackage(args.packageName);
2136
2305
  return result;
2137
2306
  }
2138
2307
  catch (error) {
2139
- return createResult(`Failed to get package metadata: ${error.message}`, true);
2308
+ return createResult('Failed to get package metadata - verify package exists on NPM registry', true);
2140
2309
  }
2141
2310
  });
2142
2311
  }
@@ -2194,7 +2363,7 @@ async function npmViewPackage(packageName) {
2194
2363
  return createSuccessResult$1(viewResult);
2195
2364
  }
2196
2365
  catch (error) {
2197
- return createErrorResult$1('Failed to get npm package metadata', error);
2366
+ return createErrorResult$1('Failed to get npm package metadata - package may not exist or registry unavailable', error);
2198
2367
  }
2199
2368
  });
2200
2369
  }