octocode-mcp 2.3.2 → 2.3.5

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 +50 -20
  2. package/build/index.js +2169 -1210
  3. package/package.json +1 -1
package/build/index.js CHANGED
@@ -3,79 +3,44 @@ 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 PROMPT_SYSTEM_PROMPT = `You are an expert code research assistant for developers doing smart research in GitHub and NPM ecosystems (public/private).
11
- You leverage powerful semantic search using GitHub (gh) and NPM CLI for code discovery.
11
+ const PROMPT_SYSTEM_PROMPT = `Expert code research assistant for GitHub/NPM ecosystems (public/private). Use powerful semantic search for efficient code discovery.
12
12
 
13
- IMPORTANT: check users github organizations and use them in github search tools if needed
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
14
17
 
15
- TOOLS:
16
- - API status: Check npm/gh connectivity and find user's GitHub organizations (for private repo access)
17
- - GitHub: Search code, repositories, issues, pull requests, commits
18
- - NPM: Search packages, view metadata (git URL, exports, dependencies, versions with dates)
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
19
25
 
20
- APPROACH:
21
- - Once code/project path is found from tools use it and research it for more data
22
- - Understand queries semantically to choose optimal tools
23
- - Optimize tools calls data and be smart about it (e.g. is some tool get information don't use other tools to get the same information)
24
- - Prioritize efficient, targeted searches with smart fallbacks
25
- - Use strategic tool combinations for comprehensive results an
26
- - Balance speed vs throughness based on query type
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
27
30
 
28
- GITHUB SEARCH STRATEGY:
29
- - OR: Explore alternatives ("useState OR setState OR setData")
30
- - AND: Precise requirements ("react AND testing AND hooks")
31
- - NOT: Filter noise ("auth NOT test NOT mock")
32
- - Quotes: Exact phrases ("import React", "useEffect cleanup")
33
- - Mix with filters: language, path, repo for laser focus
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
34
38
 
35
- GUIDELINES:
36
- - Discovery queries ("How X works?"): Be comprehensive, use multiple tools strategically
37
- - Direct queries ("Where is X package repo?"): Use quick, targeted approach
38
- - Always provide referenced code snippets and documentation
39
- - Search docs (README.md) for quality information about code flow and architecture`;
39
+ CLI Help using CLI (use when needed to check failures)
40
+ gh <command> --help
41
+ npm <command> --help
40
42
 
41
- function createResult(data, isError = false, suggestions) {
42
- const text = isError
43
- ? `${data}${''}`
44
- : JSON.stringify(data, null, 2);
45
- return {
46
- content: [{ type: 'text', text }],
47
- isError,
48
- };
49
- }
50
- // LEGACY SUPPORT - Remove these once all tools are updated
51
- function createSuccessResult$1(data) {
52
- return createResult(data, false);
53
- }
54
- function createErrorResult$1(message, error) {
55
- return createResult(`${message}: ${error.message}`, true);
56
- }
57
- // ENHANCED PARSING UTILITY
58
- function parseJsonResponse(responseText, fallback = null) {
59
- try {
60
- const data = JSON.parse(responseText);
61
- return { data, parsed: true };
62
- }
63
- catch {
64
- return { data: (fallback || responseText), parsed: false };
65
- }
66
- }
67
- /**
68
- * Determines if a string needs quoting for GitHub search
69
- */
70
- //TODO: move to util.ts
71
- function needsQuoting(str) {
72
- return (str.includes(' ') ||
73
- str.includes('"') ||
74
- str.includes('\t') ||
75
- str.includes('\n') ||
76
- str.includes('\r') ||
77
- /[<>(){}[\]\\|&;]/.test(str));
78
- }
43
+ Always provide code snippets and documentation references.`;
79
44
 
80
45
  const VERSION = 'v1';
81
46
  const cache = new NodeCache({
@@ -133,6 +98,113 @@ function isValidNpmCommand(command) {
133
98
  function isValidGhCommand(command) {
134
99
  return ALLOWED_GH_COMMANDS.includes(command);
135
100
  }
101
+ /**
102
+ * Get platform-specific shell configuration with improved shell detection
103
+ */
104
+ function getShellConfig(preferredWindowsShell) {
105
+ const isWindows = platform() === 'win32';
106
+ if (!isWindows) {
107
+ // Use user's actual shell instead of hardcoded /bin/sh to avoid alias/function conflicts
108
+ const userShell = process.env.SHELL || '/bin/sh';
109
+ return {
110
+ shell: userShell,
111
+ shellEnv: userShell,
112
+ type: 'unix',
113
+ };
114
+ }
115
+ // Windows shell selection
116
+ const usesPowerShell = preferredWindowsShell === 'powershell';
117
+ if (usesPowerShell) {
118
+ return {
119
+ shell: 'powershell.exe',
120
+ shellEnv: 'powershell.exe',
121
+ type: 'powershell',
122
+ };
123
+ }
124
+ return {
125
+ shell: 'cmd.exe',
126
+ shellEnv: 'cmd.exe',
127
+ type: 'cmd',
128
+ };
129
+ }
130
+ /**
131
+ * Checks if a query contains GitHub search boolean operators
132
+ */
133
+ function hasGitHubBooleanOperators(query) {
134
+ // Check for GitHub boolean operators (must be uppercase)
135
+ return (/\b(AND|OR|NOT)\b/.test(query) ||
136
+ /\s+(AND|OR|NOT)\s+/.test(query) ||
137
+ /"[^"]*\s+(AND|OR|NOT)\s+[^"]*"/.test(query));
138
+ }
139
+ /**
140
+ * Escape shell arguments with improved GitHub CLI boolean query handling
141
+ */
142
+ function escapeShellArg(arg, shellType, isGitHubQuery) {
143
+ // Auto-detect shell type if not provided
144
+ if (!shellType) {
145
+ const isWindows = platform() === 'win32';
146
+ shellType = isWindows ? 'cmd' : 'unix';
147
+ }
148
+ switch (shellType) {
149
+ case 'powershell':
150
+ return escapePowerShellArg(arg);
151
+ case 'cmd':
152
+ return escapeWindowsCmdArg(arg);
153
+ case 'unix':
154
+ default:
155
+ return escapeUnixShellArg(arg, isGitHubQuery);
156
+ }
157
+ }
158
+ /**
159
+ * Escape arguments for PowerShell
160
+ * PowerShell uses single quotes for literal strings and has special escaping rules
161
+ */
162
+ function escapePowerShellArg(arg) {
163
+ // PowerShell special characters that need escaping
164
+ if (/[\s&<>|;`$@"'()[\]{}]/.test(arg)) {
165
+ // Use single quotes for literal strings in PowerShell
166
+ // Escape single quotes by doubling them
167
+ return `'${arg.replace(/'/g, "''")}'`;
168
+ }
169
+ return arg;
170
+ }
171
+ /**
172
+ * Escape arguments for Windows CMD
173
+ */
174
+ function escapeWindowsCmdArg(arg) {
175
+ // Windows CMD escaping
176
+ if (/[\s&<>|^"]/.test(arg)) {
177
+ return `"${arg.replace(/"/g, '""')}"`;
178
+ }
179
+ return arg;
180
+ }
181
+ /**
182
+ * Escape arguments for Unix shells with special handling for GitHub CLI queries
183
+ */
184
+ function escapeUnixShellArg(arg, isGitHubQuery) {
185
+ // Special handling for GitHub CLI search queries to preserve boolean logic
186
+ if (isGitHubQuery && hasGitHubBooleanOperators(arg)) {
187
+ // For boolean queries with OR/AND/NOT, use double quotes to preserve operators
188
+ // GitHub CLI handles boolean logic properly when quoted
189
+ return `"${arg.replace(/"/g, '\\"')}"`;
190
+ }
191
+ // For GitHub queries with spaces but no boolean operators, quote them
192
+ if (isGitHubQuery &&
193
+ /\s/.test(arg) &&
194
+ !arg.startsWith('"') &&
195
+ !arg.endsWith('"')) {
196
+ return `"${arg.replace(/"/g, '\\"')}"`;
197
+ }
198
+ // For already quoted GitHub queries, pass through with escaped internal quotes
199
+ if (isGitHubQuery && arg.startsWith('"') && arg.endsWith('"')) {
200
+ return arg.replace(/\\"/g, '\\\\"'); // Escape already escaped quotes
201
+ }
202
+ // Standard Unix shell escaping for other arguments
203
+ if (/[^\w\-._/:=@]/.test(arg)) {
204
+ return `'${arg.replace(/'/g, "'\"'\"'")}'`;
205
+ }
206
+ return arg;
207
+ }
136
208
  /**
137
209
  * Execute NPM commands safely by validating against allowed commands
138
210
  * Security: Only executes commands that start with "npm {ALLOWED_COMMAND}"
@@ -142,74 +214,94 @@ async function executeNpmCommand(command, args = [], options = {}) {
142
214
  if (!isValidNpmCommand(command)) {
143
215
  return createErrorResult('Command not registered', new Error(`NPM command '${command}' is not in the allowed list`));
144
216
  }
217
+ // Get shell configuration
218
+ const shellConfig = getShellConfig(options.windowsShell);
145
219
  // Build command with validated prefix and properly escaped arguments
146
- const escapedArgs = args.map(escapeShellArg);
220
+ const escapedArgs = args.map(arg => escapeShellArg(arg, shellConfig.type));
147
221
  const fullCommand = `npm ${command} ${escapedArgs.join(' ')}`;
148
- const executeNpmCommand = () => executeCommand(fullCommand, 'npm', options);
222
+ const executeNpmCommand = () => executeCommand(fullCommand, 'npm', options, shellConfig);
149
223
  if (options.cache) {
150
- const cacheKey = generateCacheKey('npm-exec', { command, args });
224
+ const cacheKey = generateCacheKey('npm-exec', {
225
+ command,
226
+ args,
227
+ shell: shellConfig.type,
228
+ });
151
229
  return withCache(cacheKey, executeNpmCommand);
152
230
  }
153
231
  return executeNpmCommand();
154
232
  }
155
233
  /**
156
- * Escape shell arguments to prevent shell injection and handle special characters
157
- */
158
- function escapeShellArg(arg) {
159
- // If the argument contains special characters, wrap it in single quotes
160
- // and escape any single quotes within the argument
161
- if (/[^\w\-._/:=@]/.test(arg)) {
162
- return `'${arg.replace(/'/g, "'\"'\"'")}'`;
163
- }
164
- return arg;
165
- }
166
- /**
167
- * Execute GitHub CLI commands safely by validating against allowed commands
168
- * Security: Only executes commands that start with "gh {ALLOWED_COMMAND}"
234
+ * Execute GitHub CLI commands safely with improved boolean query handling
169
235
  */
170
236
  async function executeGitHubCommand(command, args = [], options = {}) {
171
237
  // Security check: only allow registered commands
172
238
  if (!isValidGhCommand(command)) {
173
239
  return createErrorResult('Command not registered', new Error(`GitHub command '${command}' is not in the allowed list`));
174
240
  }
241
+ // Get shell configuration
242
+ const shellConfig = getShellConfig(options.windowsShell);
175
243
  // Build command with validated prefix and properly escaped arguments
176
- const escapedArgs = args.map(escapeShellArg);
244
+ // First argument is typically the search query for GitHub CLI search commands
245
+ const escapedArgs = args.map((arg, index) => {
246
+ const isFirstArg = index === 0;
247
+ // Detect if this is a search query (first non-subcommand argument)
248
+ const isSearchQuery = command === 'search' && isFirstArg && !arg.startsWith('--');
249
+ return escapeShellArg(arg, shellConfig.type, isSearchQuery);
250
+ });
177
251
  const fullCommand = `gh ${command} ${escapedArgs.join(' ')}`;
178
- const executeGhCommand = () => executeCommand(fullCommand, 'github', options);
252
+ const executeGhCommand = () => executeCommand(fullCommand, 'github', options, shellConfig);
179
253
  if (options.cache) {
180
- const cacheKey = generateCacheKey('gh-exec', { command, args });
254
+ const cacheKey = generateCacheKey('gh-exec', {
255
+ command,
256
+ args,
257
+ shell: shellConfig.type,
258
+ });
181
259
  return withCache(cacheKey, executeGhCommand);
182
260
  }
183
261
  return executeGhCommand();
184
262
  }
185
263
  /**
186
- * Execute shell commands with timeout and error handling
187
- * Security: Should only be called with pre-validated command prefixes
264
+ * Execute shell commands with improved environment handling and error detection
188
265
  */
189
- async function executeCommand(fullCommand, type, options = {}) {
266
+ async function executeCommand(fullCommand, type, options = {}, shellConfig) {
190
267
  try {
191
268
  const defaultTimeout = type === 'npm' ? 30000 : 60000;
269
+ const config = shellConfig || getShellConfig(options.windowsShell);
192
270
  const execOptions = {
193
271
  timeout: options.timeout || defaultTimeout,
272
+ maxBuffer: 5 * 1024 * 1024, // 5MB buffer limit (increased from default 1MB)
194
273
  cwd: options.cwd,
195
274
  env: {
196
275
  ...process.env,
197
276
  ...options.env,
198
- // Ensure clean shell environment
199
- SHELL: '/bin/sh',
277
+ // More conservative shell environment handling
278
+ SHELL: config.shellEnv,
200
279
  PATH: process.env.PATH,
280
+ // Only disable problematic shell features, not all of them
281
+ ...(config.type === 'unix' && {
282
+ // Only disable the most problematic shell configurations
283
+ BASH_ENV: '', // Prevent auto-sourcing of problematic configs
284
+ }),
201
285
  },
202
286
  encoding: 'utf-8',
203
- shell: '/bin/sh', // Use sh instead of default shell
287
+ shell: config.shell,
204
288
  };
205
289
  const { stdout, stderr } = await safeExecAsync(fullCommand, execOptions);
206
- // Handle different warning patterns for npm vs gh
290
+ // Improved error detection that ignores shell configuration conflicts
207
291
  const shouldTreatAsError = type === 'npm'
208
- ? stderr && !stderr.includes('npm WARN')
292
+ ? stderr &&
293
+ !stderr.includes('npm WARN') &&
294
+ !stderr.includes('npm notice')
209
295
  : stderr &&
210
296
  !stderr.includes('Warning:') &&
211
297
  !stderr.includes('notice:') &&
212
- !stderr.includes('No such file or directory') && // Ignore shell-related errors
298
+ // Ignore shell configuration conflicts - common in development environments
299
+ !stderr.includes('No such file or directory') &&
300
+ !stderr.includes('head: illegal option') &&
301
+ !stderr.includes('head: |: No such file or directory') &&
302
+ !stderr.includes('head: cat: No such file or directory') &&
303
+ !/^head:\s+/.test(stderr) && // Ignore all head command errors (shell conflicts)
304
+ !/^\s*head:\s+/.test(stderr) && // Ignore head errors with leading whitespace
213
305
  stderr.trim() !== '';
214
306
  if (shouldTreatAsError) {
215
307
  const errorType = type === 'npm' ? 'NPM command error' : 'GitHub CLI command error';
@@ -220,6 +312,9 @@ async function executeCommand(fullCommand, type, options = {}) {
220
312
  result: stdout,
221
313
  timestamp: new Date().toISOString(),
222
314
  type,
315
+ platform: platform(),
316
+ shell: config.shell,
317
+ shellType: config.type,
223
318
  ...(stderr && { warning: stderr }), // Include warnings but don't treat as error
224
319
  });
225
320
  }
@@ -231,17 +326,110 @@ async function executeCommand(fullCommand, type, options = {}) {
231
326
  }
232
327
  }
233
328
 
329
+ function createResult(options) {
330
+ const { data, error, suggestions, cli_command } = options;
331
+ if (error) {
332
+ const errorMessage = typeof error === 'string'
333
+ ? error
334
+ : error.message || 'Unknown error';
335
+ // Build error response with optional CLI command
336
+ const errorResponse = { error: errorMessage };
337
+ if (suggestions) {
338
+ errorResponse.suggestions = suggestions;
339
+ }
340
+ if (cli_command) {
341
+ errorResponse.cli_command = cli_command;
342
+ }
343
+ return {
344
+ content: [{ type: 'text', text: JSON.stringify(errorResponse, null, 2) }],
345
+ isError: true,
346
+ };
347
+ }
348
+ // For successful responses, include cli_command if provided (for debugging)
349
+ if (cli_command && data && typeof data === 'object') {
350
+ const dataWithCli = { ...data, cli_command };
351
+ return {
352
+ content: [{ type: 'text', text: JSON.stringify(dataWithCli, null, 2) }],
353
+ isError: false,
354
+ };
355
+ }
356
+ return {
357
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
358
+ isError: false,
359
+ };
360
+ }
361
+ /**
362
+ * Convert ISO timestamp to DDMMYYYY format
363
+ */
364
+ function toDDMMYYYY(timestamp) {
365
+ const date = new Date(timestamp);
366
+ const day = date.getDate().toString().padStart(2, '0');
367
+ const month = (date.getMonth() + 1).toString().padStart(2, '0');
368
+ const year = date.getFullYear();
369
+ return `${day}/${month}/${year}`;
370
+ }
371
+ /**
372
+ * Convert repository URL to owner/repo format
373
+ */
374
+ function simplifyRepoUrl(url) {
375
+ const match = url.match(/github\.com\/([^/]+\/[^/]+)/);
376
+ return match ? match[1] : url;
377
+ }
378
+ /**
379
+ * Extract first line of commit message
380
+ */
381
+ function getCommitTitle(message) {
382
+ return message.split('\n')[0].trim();
383
+ }
384
+ /**
385
+ * Convert bytes to human readable format
386
+ */
387
+ function humanizeBytes(bytes) {
388
+ if (bytes === 0)
389
+ return '0 B';
390
+ const k = 1024;
391
+ const sizes = ['B', 'KB', 'MB', 'GB'];
392
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
393
+ return `${Math.round(bytes / Math.pow(k, i))} ${sizes[i]}`;
394
+ }
395
+ /**
396
+ * Simplify GitHub URL to relative path
397
+ */
398
+ function simplifyGitHubUrl(url) {
399
+ const match = url.match(/github\.com\/[^/]+\/[^/]+\/(?:blob|commit)\/[^/]+\/(.+)$/);
400
+ return match ? match[1] : url;
401
+ }
402
+ /**
403
+ * Clean and optimize text match context
404
+ */
405
+ function optimizeTextMatch(fragment, maxLength = 100) {
406
+ // Remove excessive whitespace and normalize
407
+ const cleaned = fragment.replace(/\s+/g, ' ').trim();
408
+ if (cleaned.length <= maxLength) {
409
+ return cleaned;
410
+ }
411
+ // Try to cut at word boundary
412
+ const truncated = cleaned.substring(0, maxLength);
413
+ const lastSpace = truncated.lastIndexOf(' ');
414
+ if (lastSpace > maxLength * 0.7) {
415
+ return truncated.substring(0, lastSpace) + '…';
416
+ }
417
+ return truncated + '…';
418
+ }
419
+
234
420
  const TOOL_NAME$9 = 'api_status_check';
235
- 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.
236
- 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.`;
421
+ const DESCRIPTION$9 = `Get GitHub organizations list and check CLI authentication status. Use when searching private repos or when CLI tools fail.`;
237
422
  function registerApiStatusCheckTool(server) {
238
- server.tool(TOOL_NAME$9, DESCRIPTION$9, {}, {
239
- title: 'Check API Connections and Github Organizations',
423
+ server.registerTool(TOOL_NAME$9, {
240
424
  description: DESCRIPTION$9,
241
- readOnlyHint: true,
242
- destructiveHint: false,
243
- idempotentHint: true,
244
- openWorldHint: false,
425
+ inputSchema: {},
426
+ annotations: {
427
+ title: 'Check API Connections and Github Organizations',
428
+ readOnlyHint: true,
429
+ destructiveHint: false,
430
+ idempotentHint: true,
431
+ openWorldHint: false,
432
+ },
245
433
  }, async () => {
246
434
  try {
247
435
  let githubConnected = false;
@@ -341,111 +529,131 @@ function registerApiStatusCheckTool(server) {
341
529
  }
342
530
  npmConnected = false;
343
531
  }
344
- return createResult({
345
- github: {
346
- connected: githubConnected,
347
- organizations,
348
- },
349
- npm: {
350
- connected: npmConnected,
351
- registry,
532
+ // Build structured response object
533
+ const loginStatus = {
534
+ login: {
535
+ github: {
536
+ connected: githubConnected,
537
+ user_organizations: organizations,
538
+ },
539
+ npm: {
540
+ connected: npmConnected,
541
+ registry: registry || 'https://registry.npmjs.org/',
542
+ },
543
+ hints: [
544
+ 'use user organizations: to search on private repositories in case the user asked about private repo - check by query nd structure',
545
+ ],
352
546
  },
353
- });
547
+ };
548
+ return createResult({ data: loginStatus });
354
549
  }
355
550
  catch (error) {
356
- return createResult('API status check failed - verify GitHub CLI and NPM are installed and accessible', true);
551
+ return createResult({
552
+ error: `API Status Check Failed\nError: ${error instanceof Error ? error.message : 'Unknown error'}\n\nThis usually indicates a system configuration issue. Please verify GitHub CLI and NPM are properly installed.`,
553
+ });
357
554
  }
358
555
  });
359
556
  }
360
557
 
361
558
  const TOOL_NAME$8 = 'github_search_code';
362
- const DESCRIPTION$8 = `Search code across GitHub repositories using strategic boolean operators and filters usign "gh code search" command.
559
+ const DESCRIPTION$8 = `Search code across GitHub repositories with powerful GitHub search syntax and advanced filtering.
363
560
 
364
- STRATEGIC SEARCH PATTERNS:
561
+ BOOLEAN LOGIC (MOST POWERFUL):
562
+ - "useState OR useEffect" - Find either hook
563
+ - "useState AND useEffect" - Find both hooks together
564
+ - "authentication AND (jwt OR oauth)" - Complex logic combinations
565
+ - "NOT deprecated" - Exclude deprecated code
365
566
 
366
- OR LOGIC (Exploratory Discovery):
367
- • Auto-applied to multi-word queries: "useState hook" "useState OR hook"
368
- Best for: Learning, finding alternatives, casting wide nets
369
- Scope: BROADEST - finds files with ANY of the terms
567
+ EMBEDDED QUALIFIERS:
568
+ - "useState language:javascript filename:*.jsx" - Hook in React files
569
+ - "authentication language:python path:*/security/*" - Security code in Python
570
+ - "docker OR kubernetes language:yaml extension:yml" - Container configs
370
571
 
371
- AND LOGIC (Precise Intersection):
372
- Explicit requirement: "react AND hooks" requires BOTH terms present
373
- • Best for: Finding specific combinations, technology intersections
374
- • Scope: RESTRICTIVE - only files containing ALL terms
572
+ TRADITIONAL FILTERS (ALSO SUPPORTED):
573
+ - language: "javascript", owner: "microsoft", filename: "package.json"
375
574
 
376
- EXACT PHRASE (Laser Targeting):
377
- Escaped quotes: "useState hook" finds literal "useState hook" sequence
378
- Best for: Documentation titles, specific API calls, exact implementations
379
- Scope: MOST PRECISE - only exact sequence matches
575
+ PROVEN PATTERNS: "authentication" → +language → +owner → +filename
576
+ KEY TIPS: language filter = 90% speed boost, boolean operators work perfectly with filters`;
577
+ function registerGitHubSearchCodeTool(server) {
578
+ server.registerTool(TOOL_NAME$8, {
579
+ description: DESCRIPTION$8,
580
+ inputSchema: {
581
+ query: z
582
+ .string()
583
+ .min(1)
584
+ .describe(`Search query with GitHub syntax. BOOLEAN LOGIC: "useState OR useEffect", "authentication AND jwt", "NOT deprecated". EMBEDDED QUALIFIERS: "useState language:javascript", "docker path:*/config/*". EXACT PHRASES: "error handling".
380
585
 
381
- NOT LOGIC (Noise Filtering):
382
- Exclude unwanted results: "authentication NOT test NOT mock"
383
- • Best for: Removing examples, tests, deprecated code
586
+ POWERFUL EXAMPLES: "useState OR useEffect language:javascript", "authentication AND (jwt OR oauth)", "docker OR kubernetes language:yaml", "NOT deprecated language:python"
587
+ RULES: Boolean operators MUST be uppercase (AND, OR, NOT). Combines perfectly with traditional filters.`),
588
+ language: z
589
+ .string()
590
+ .optional()
591
+ .describe(`MOST EFFECTIVE FILTER - 90% speed boost! Essential for popular languages.
384
592
 
385
- RESTRICTIVENESS SCALE: OR < AND < Exact Phrase (Broadest Most Precise)
593
+ POPULAR: javascript, typescript, python, java, go, rust, php, ruby, swift, kotlin, dart
594
+ SYSTEMS: c, cpp, assembly, shell, dockerfile, yaml`),
595
+ owner: z
596
+ .union([z.string(), z.array(z.string())])
597
+ .optional()
598
+ .describe(`HIGH IMPACT - Reduces search space by 95%+
386
599
 
387
- COMBINE FILTERS: Mix query with language, owner, path filters for laser-focused results.`;
388
- function registerGitHubSearchCodeTool(server) {
389
- server.tool(TOOL_NAME$8, DESCRIPTION$8, {
390
- query: z
391
- .string()
392
- .min(1)
393
- .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.'),
394
- owner: z
395
- .union([z.string(), z.array(z.string())])
396
- .optional()
397
- .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)'),
398
- repo: z
399
- .union([z.string(), z.array(z.string())])
400
- .optional()
401
- .describe('Specific repositories in "owner/repo" format. Examples: "facebook/react", "microsoft/vscode". Requires owner parameter.'),
402
- language: z
403
- .string()
404
- .optional()
405
- .describe('Programming language filter. Examples: "javascript", "python", "typescript", "go". Highly effective for targeted searches.'),
406
- extension: z
407
- .string()
408
- .optional()
409
- .describe('File extension filter without dot. Examples: "js", "ts", "py", "md", "json". Precise file type targeting.'),
410
- filename: z
411
- .string()
412
- .optional()
413
- .describe('Exact filename filter. Examples: "package.json", "Dockerfile", "README.md", "index.js". Perfect for config files.'),
414
- path: z
415
- .string()
416
- .optional()
417
- .describe('Directory path filter. Examples: "src/", "test/", "docs/", "components/". Focus search on specific directories.'),
418
- size: z
419
- .string()
420
- .optional()
421
- .describe('File size filter in KB with operators (e.g., ">100", "<50", "10..100").'),
422
- limit: z
423
- .number()
424
- .int()
425
- .min(1)
426
- .max(50)
427
- .optional()
428
- .default(30)
429
- .describe('Maximum results to return (1-50, default: 30).'),
430
- match: z
431
- .union([z.enum(['file', 'path']), z.array(z.enum(['file', 'path']))])
432
- .optional()
433
- .describe('Search scope: "file" searches code content, "path" searches filenames/paths. Use "path" to find files by name.'),
434
- visibility: z
435
- .enum(['public', 'private', 'internal'])
436
- .optional()
437
- .describe('Repository visibility filter: "public", "private", or "internal". Defaults to accessible repositories.'),
438
- }, {
439
- title: TOOL_NAME$8,
440
- description: DESCRIPTION$8,
441
- readOnlyHint: true,
442
- destructiveHint: false,
443
- idempotentHint: true,
444
- openWorldHint: true,
600
+ EXAMPLES: "microsoft", "google", "facebook" or ["microsoft", "google"]
601
+ POPULAR: microsoft, google, facebook, amazon, apache, hashicorp, kubernetes`),
602
+ filename: z
603
+ .string()
604
+ .optional()
605
+ .describe(`SURGICAL PRECISION for configs and special files
606
+
607
+ TARGETS: "package.json", "Dockerfile", "webpack.config.js", ".eslintrc", "README.md"
608
+ STRATEGY: filename:package.json + "react typescript"`),
609
+ repo: z
610
+ .union([z.string(), z.array(z.string())])
611
+ .optional()
612
+ .describe(`PRECISE TARGETING for specific repositories
613
+
614
+ FORMAT: "facebook/react", "microsoft/vscode" or ["facebook/react", "vuejs/vue"]
615
+ USE: Deep dive analysis of specific projects`),
616
+ extension: z
617
+ .string()
618
+ .optional()
619
+ .describe(`FILE TYPE PRECISION - More specific than language filter
620
+
621
+ POPULAR: js, ts, jsx, tsx, py, java, go, rs, rb, php, cs, sh, yml, json, md
622
+ USE: extension:tsx (React TypeScript only), extension:dockerfile`),
623
+ match: z
624
+ .union([z.enum(['file', 'path']), z.array(z.enum(['file', 'path']))])
625
+ .optional()
626
+ .describe(`SEARCH SCOPE: "file" (content), "path" (filenames), ["file", "path"] (both)
627
+
628
+ EXAMPLES: match:"path" + "test" (find test files), match:"file" + "useState"`),
629
+ size: z
630
+ .string()
631
+ .optional()
632
+ .describe(`FILE SIZE FILTER: ">100" (>100KB), "<50" (<50KB), "10..100" (range)
633
+
634
+ STRATEGY: "<200" (avoid huge files), ">20" (substantial code), "<10" (configs)`),
635
+ limit: z
636
+ .number()
637
+ .int()
638
+ .min(1)
639
+ .max(100)
640
+ .optional()
641
+ .default(30)
642
+ .describe(`RESULTS: 10-20 (quick), 30 (default), 50-100 (comprehensive). Use filters over high limits.`),
643
+ },
644
+ annotations: {
645
+ title: 'GitHub Code Search - Smart & Efficient',
646
+ readOnlyHint: true,
647
+ destructiveHint: false,
648
+ idempotentHint: true,
649
+ openWorldHint: true,
650
+ },
445
651
  }, async (args) => {
446
652
  try {
447
- if (args.repo && !args.owner) {
448
- return createResult('Repository search requires owner parameter - specify owner when searching specific repositories', true);
653
+ // Validate parameter combinations
654
+ const validationError = validateSearchParameters(args);
655
+ if (validationError) {
656
+ return createResult({ error: validationError });
449
657
  }
450
658
  const result = await searchGitHubCode(args);
451
659
  if (result.isError) {
@@ -455,131 +663,436 @@ function registerGitHubSearchCodeTool(server) {
455
663
  const codeResults = JSON.parse(execResult.result);
456
664
  // GitHub CLI returns a direct array, not an object with total_count and items
457
665
  const items = Array.isArray(codeResults) ? codeResults : [];
458
- return createSuccessResult$1({
459
- query: args.query,
460
- processed_query: parseSearchQuery(args.query, args),
461
- total_count: items.length,
462
- items: items,
463
- cli_command: execResult.command,
464
- debug_info: {
465
- has_complex_boolean_logic: hasComplexBooleanLogic(args.query),
466
- escaped_args: buildGitHubCliArgs(args),
467
- original_query: args.query,
468
- },
469
- });
666
+ // Smart handling for no results - provide actionable suggestions
667
+ if (items.length === 0) {
668
+ return handleNoResults(args, execResult.command);
669
+ }
670
+ // Transform to optimized format
671
+ const optimizedResult = transformToOptimizedFormat$1(items, args);
672
+ return createResult({ data: optimizedResult });
470
673
  }
471
674
  catch (error) {
472
- return createErrorResult$1('GitHub code search failed - check repository access or simplify query', error);
675
+ const errorMessage = error.message || '';
676
+ // Handle JSON parsing errors
677
+ if (errorMessage.includes('JSON')) {
678
+ return createResult({
679
+ error: 'GitHub CLI returned invalid response - check if GitHub CLI is up to date with "gh version" and try again',
680
+ });
681
+ }
682
+ return createResult({
683
+ error: 'GitHub code search failed',
684
+ suggestions: [
685
+ 'Try simpler queries without NOT operators',
686
+ 'Use filters (language, owner, filename) instead of complex boolean',
687
+ 'Check authentication with api_status_check',
688
+ ],
689
+ });
473
690
  }
474
691
  });
475
692
  }
476
693
  /**
477
- * Enhanced query parser that handles exact strings, boolean operators, and filters
694
+ * Smart handler for no results - provides actionable suggestions
478
695
  */
479
- function parseSearchQuery(query, filters) {
480
- // Step 1: Handle quoted strings more intelligently
481
- // Convert escaped quotes to simple quotes to avoid shell escaping issues
482
- let processedQuery = query.replace(/\\"/g, '"');
483
- // Step 2: Preserve exact phrases (quoted strings)
484
- const exactPhrases = [];
485
- // Extract quoted strings and replace with placeholders
486
- const quotedMatches = processedQuery.match(/"[^"]+"/g) || [];
487
- quotedMatches.forEach((match, index) => {
488
- const placeholder = `__EXACT_PHRASE_${index}__`;
489
- exactPhrases.push(match);
490
- processedQuery = processedQuery.replace(match, placeholder);
696
+ function handleNoResults(params, cliCommand) {
697
+ const suggestions = generateSmartSuggestions(params);
698
+ const fallbackQueries = generateFallbackQueries(params);
699
+ return createResult({
700
+ data: {
701
+ items: [],
702
+ total_count: 0,
703
+ smart_suggestions: {
704
+ message: "No results found. Here are smart strategies to find what you're looking for:",
705
+ suggestions,
706
+ fallback_queries: fallbackQueries,
707
+ next_steps: [
708
+ 'Try one of the fallback queries above',
709
+ 'Remove some filters to broaden the search',
710
+ 'Use different keywords or synonyms',
711
+ 'Check spelling and try variations',
712
+ ],
713
+ },
714
+ metadata: {
715
+ has_filters: !!(params.language ||
716
+ params.owner ||
717
+ params.filename ||
718
+ params.extension),
719
+ search_scope: params.match
720
+ ? Array.isArray(params.match)
721
+ ? params.match.join(',')
722
+ : params.match
723
+ : 'file',
724
+ },
725
+ cli_command: cliCommand,
726
+ },
491
727
  });
492
- // Step 3: Smart boolean logic - default to OR between terms if no explicit operators
493
- let searchQuery = processedQuery;
494
- // Check if query already has explicit boolean operators
495
- if (!hasComplexBooleanLogic(processedQuery)) {
496
- // Split by whitespace and join with OR for better search results
497
- const terms = processedQuery
498
- .trim()
499
- .split(/\s+/)
500
- .filter(term => term.length > 0);
501
- if (terms.length > 1) {
502
- searchQuery = terms.join(' OR ');
503
- }
728
+ }
729
+ /**
730
+ * Generate smart suggestions based on current search parameters
731
+ */
732
+ function generateSmartSuggestions(params) {
733
+ const suggestions = [];
734
+ // Query-specific suggestions
735
+ if (params.query.includes('"')) {
736
+ suggestions.push('Remove quotes to search for individual terms instead of exact phrase');
737
+ }
738
+ if (params.query.includes(' AND ') || params.query.includes(' OR ')) {
739
+ suggestions.push('Try simpler queries without boolean operators');
740
+ }
741
+ if (params.query.length > 50) {
742
+ suggestions.push('Simplify query - shorter terms often work better');
504
743
  }
505
- // Step 4: Add GitHub-specific filters that go in the query string
506
- const githubFilters = [];
507
- if (filters.path) {
508
- githubFilters.push(`path:${filters.path}`);
744
+ // Filter-specific suggestions
745
+ if (!params.language) {
746
+ suggestions.push('Add language filter - this improves results by 90%');
747
+ }
748
+ if (params.language && !params.owner && !params.filename) {
749
+ suggestions.push('Add owner filter to target specific organizations');
750
+ }
751
+ if (params.owner && params.repo) {
752
+ suggestions.push('Remove repo filter and search across all repos in the organization');
509
753
  }
510
- if (filters.visibility) {
511
- githubFilters.push(`visibility:${filters.visibility}`);
754
+ if (params.filename) {
755
+ suggestions.push('Remove filename filter to search all file types');
512
756
  }
513
- // For complex boolean queries, add language/extension/filename/size to query string
514
- const hasComplexLogic = hasComplexBooleanLogic(searchQuery);
515
- if (hasComplexLogic) {
516
- if (filters.language) {
517
- githubFilters.push(`language:${filters.language}`);
757
+ if (params.extension && params.language) {
758
+ suggestions.push('Try removing extension filter (language filter might be sufficient)');
759
+ }
760
+ // Always provide generic fallbacks
761
+ if (suggestions.length === 0) {
762
+ suggestions.push('Try a broader search with fewer filters');
763
+ suggestions.push('Use more common/general terms');
764
+ suggestions.push('Check for typos in query or filter values');
765
+ }
766
+ return suggestions.slice(0, 5); // Limit to top 5 suggestions
767
+ }
768
+ /**
769
+ * Generate fallback queries that are likely to succeed
770
+ */
771
+ function generateFallbackQueries(params) {
772
+ const fallbacks = [];
773
+ // Extract key terms from original query
774
+ const keyTerms = extractKeyTerms(params.query);
775
+ // Fallback 1: Simplify query, keep language if present
776
+ if (keyTerms.length > 1) {
777
+ const simplifiedQuery = keyTerms[0];
778
+ fallbacks.push({
779
+ query: simplifiedQuery,
780
+ description: `Search for just "${simplifiedQuery}"${params.language ? ` in ${params.language}` : ''}`,
781
+ rationale: 'Single term searches often yield better results',
782
+ });
783
+ }
784
+ // Fallback 2: Remove filters but keep core query
785
+ if (params.language || params.owner || params.filename || params.extension) {
786
+ fallbacks.push({
787
+ query: keyTerms.join(' '),
788
+ description: `Search "${keyTerms.join(' ')}" without filters`,
789
+ rationale: 'Removes restrictive filters for broader results',
790
+ });
791
+ }
792
+ // Fallback 3: Language-specific popular search
793
+ if (params.language) {
794
+ const popularTerms = getPopularTermsForLanguage(params.language);
795
+ if (popularTerms.length > 0) {
796
+ fallbacks.push({
797
+ query: popularTerms[0],
798
+ description: `Popular ${params.language} pattern: "${popularTerms[0]}"`,
799
+ rationale: `Common patterns in ${params.language} development`,
800
+ });
518
801
  }
519
- if (filters.extension) {
520
- githubFilters.push(`extension:${filters.extension}`);
802
+ }
803
+ // Fallback 4: Broader category search
804
+ const category = inferCategory(params.query);
805
+ if (category) {
806
+ fallbacks.push({
807
+ query: category,
808
+ description: `Broader search: "${category}"`,
809
+ rationale: 'Searches the general category for related patterns',
810
+ });
811
+ }
812
+ return fallbacks.slice(0, 4);
813
+ }
814
+ /**
815
+ * Extract key terms from search query
816
+ */
817
+ function extractKeyTerms(query) {
818
+ // Remove quotes, boolean operators, and special characters
819
+ const cleaned = query
820
+ .replace(/["']/g, '')
821
+ .replace(/\b(AND|OR|NOT)\b/gi, '')
822
+ .replace(/[^\w\s]/g, ' ')
823
+ .replace(/\s+/g, ' ')
824
+ .trim();
825
+ return cleaned.split(' ').filter(term => term.length > 2);
826
+ }
827
+ /**
828
+ * Get popular search terms for specific programming languages
829
+ */
830
+ function getPopularTermsForLanguage(language) {
831
+ const popularTerms = {
832
+ javascript: [
833
+ 'useState',
834
+ 'async await',
835
+ 'fetch api',
836
+ 'event listener',
837
+ 'promise',
838
+ ],
839
+ typescript: [
840
+ 'interface',
841
+ 'generic type',
842
+ 'type guard',
843
+ 'enum',
844
+ 'namespace',
845
+ ],
846
+ python: ['def function', 'import', 'class method', 'lambda', 'decorator'],
847
+ java: [
848
+ 'public class',
849
+ 'static method',
850
+ 'interface',
851
+ 'annotation',
852
+ 'spring',
853
+ ],
854
+ go: ['func main', 'interface', 'goroutine', 'channel', 'struct'],
855
+ rust: ['fn main', 'impl', 'trait', 'enum', 'match'],
856
+ cpp: ['class', 'template', 'namespace', 'virtual', 'std::'],
857
+ c: ['int main', 'struct', 'malloc', 'pointer', 'header'],
858
+ php: ['function', 'class', 'namespace', 'interface', 'trait'],
859
+ ruby: ['def', 'class', 'module', 'block', 'gem'],
860
+ swift: ['func', 'class', 'protocol', 'extension', 'guard'],
861
+ kotlin: ['fun', 'class', 'interface', 'data class', 'coroutine'],
862
+ dart: ['class', 'function', 'widget', 'async', 'future'],
863
+ shell: ['function', 'if then', 'for loop', 'variable', 'script'],
864
+ yaml: ['version', 'name', 'build', 'deploy', 'config'],
865
+ dockerfile: ['FROM', 'RUN', 'COPY', 'EXPOSE', 'CMD'],
866
+ };
867
+ return popularTerms[language.toLowerCase()] || [];
868
+ }
869
+ /**
870
+ * Infer search category from query terms
871
+ */
872
+ function inferCategory(query) {
873
+ const categories = {
874
+ authentication: [
875
+ 'auth',
876
+ 'login',
877
+ 'oauth',
878
+ 'jwt',
879
+ 'token',
880
+ 'passport',
881
+ 'session',
882
+ ],
883
+ database: ['db', 'sql', 'query', 'model', 'schema', 'migration', 'orm'],
884
+ api: ['rest', 'graphql', 'endpoint', 'route', 'request', 'response'],
885
+ testing: ['test', 'spec', 'mock', 'assert', 'unit', 'integration'],
886
+ config: ['config', 'setting', 'env', 'environment', 'setup'],
887
+ ui: ['component', 'button', 'form', 'modal', 'layout', 'style'],
888
+ error: ['error', 'exception', 'try', 'catch', 'throw', 'handle'],
889
+ performance: ['cache', 'optimization', 'memory', 'speed', 'performance'],
890
+ security: ['security', 'encrypt', 'hash', 'validate', 'sanitize'],
891
+ };
892
+ const queryLower = query.toLowerCase();
893
+ for (const [category, keywords] of Object.entries(categories)) {
894
+ if (keywords.some(keyword => queryLower.includes(keyword))) {
895
+ return category;
521
896
  }
522
- if (filters.filename) {
523
- githubFilters.push(`filename:${filters.filename}`);
897
+ }
898
+ return null;
899
+ }
900
+ /**
901
+ * Transform GitHub CLI response to optimized format with enhanced metadata
902
+ */
903
+ function transformToOptimizedFormat$1(items, params) {
904
+ const searchEfficiency = calculateSearchEfficiency(params);
905
+ // Extract repository info if single repo search
906
+ const singleRepo = extractSingleRepository$1(items);
907
+ const optimizedItems = items.map(item => ({
908
+ path: item.path,
909
+ matches: item.textMatches.map(match => ({
910
+ context: optimizeTextMatch(match.fragment, 120), // Increased context for better understanding
911
+ positions: match.matches.map(m => m.indices),
912
+ })),
913
+ url: singleRepo ? item.path : simplifyGitHubUrl(item.url),
914
+ }));
915
+ const result = {
916
+ items: optimizedItems,
917
+ total_count: items.length,
918
+ };
919
+ // Add repository info if single repo
920
+ if (singleRepo) {
921
+ result.repository = {
922
+ name: singleRepo.nameWithOwner,
923
+ url: simplifyRepoUrl(singleRepo.url),
924
+ };
925
+ }
926
+ // Enhanced metadata with search efficiency and tips
927
+ result.metadata = {
928
+ has_filters: !!(params.language ||
929
+ params.owner ||
930
+ params.filename ||
931
+ params.extension),
932
+ search_scope: params.match
933
+ ? Array.isArray(params.match)
934
+ ? params.match.join(',')
935
+ : params.match
936
+ : 'file',
937
+ search_efficiency: searchEfficiency,
938
+ };
939
+ // Add performance tips for low-efficiency searches
940
+ if (searchEfficiency.score < 7) {
941
+ result.metadata.performance_tips = generatePerformanceTips(params);
942
+ }
943
+ return result;
944
+ }
945
+ /**
946
+ * Calculate search efficiency score and provide insights
947
+ */
948
+ function calculateSearchEfficiency(params) {
949
+ let score = 5; // Base score
950
+ const factors = [];
951
+ const recommendations = [];
952
+ // Language filter is huge efficiency boost
953
+ if (params.language) {
954
+ score += 3;
955
+ factors.push('Language filter (+3)');
956
+ }
957
+ else {
958
+ recommendations.push('Add language filter for 90% performance boost');
959
+ }
960
+ // Owner filter provides good targeting
961
+ if (params.owner) {
962
+ score += 2;
963
+ factors.push('Owner filter (+2)');
964
+ }
965
+ // Filename filter is very efficient
966
+ if (params.filename) {
967
+ score += 2;
968
+ factors.push('Filename filter (+2)');
969
+ }
970
+ // Extension filter adds precision
971
+ if (params.extension) {
972
+ score += 1;
973
+ factors.push('Extension filter (+1)');
974
+ }
975
+ // Repository filter is highly targeted
976
+ if (params.repo) {
977
+ score += 2;
978
+ factors.push('Repository filter (+2)');
979
+ }
980
+ // Size filter helps avoid huge files
981
+ if (params.size) {
982
+ score += 1;
983
+ factors.push('Size filter (+1)');
984
+ }
985
+ // Boolean operators are now properly supported
986
+ if (params.query.includes(' OR ')) {
987
+ // OR is powerful when used correctly
988
+ if (params.language || params.owner) {
989
+ score += 1;
990
+ factors.push('OR with filters (+1)');
524
991
  }
525
- if (filters.size) {
526
- githubFilters.push(`size:${filters.size}`);
992
+ else {
993
+ factors.push('OR operator (neutral)');
994
+ recommendations.push('Add language or owner filter with OR for better targeting');
527
995
  }
528
996
  }
529
- // Step 5: Combine query with GitHub filters
530
- if (githubFilters.length > 0) {
531
- searchQuery = `${searchQuery} ${githubFilters.join(' ')}`;
997
+ if (params.query.includes(' AND ')) {
998
+ score += 1;
999
+ factors.push('AND operator (+1)');
532
1000
  }
533
- // Step 6: Restore exact phrases
534
- exactPhrases.forEach((phrase, index) => {
535
- const placeholder = `__EXACT_PHRASE_${index}__`;
536
- searchQuery = searchQuery.replace(placeholder, phrase);
537
- });
538
- return searchQuery.trim();
1001
+ if (params.query.includes(' NOT ')) {
1002
+ // NOT can be useful but less efficient
1003
+ factors.push('NOT operator (neutral)');
1004
+ recommendations.push('Consider positive filters alongside NOT for better results');
1005
+ }
1006
+ if (params.query.length > 100) {
1007
+ score -= 1;
1008
+ factors.push('Long query (-1)');
1009
+ recommendations.push('Simplify query for better results');
1010
+ }
1011
+ return {
1012
+ score: Math.max(1, Math.min(10, score)),
1013
+ factors,
1014
+ recommendations,
1015
+ };
539
1016
  }
540
1017
  /**
541
- * Check if query contains complex boolean logic that might conflict with CLI flags
1018
+ * Generate performance tips for inefficient searches
542
1019
  */
543
- function hasComplexBooleanLogic(query) {
544
- const booleanOperators = /\b(AND|OR|NOT)\b/i;
545
- return booleanOperators.test(query);
1020
+ function generatePerformanceTips(params) {
1021
+ const tips = [];
1022
+ if (!params.language) {
1023
+ tips.push('Add language filter - single biggest performance boost');
1024
+ }
1025
+ if (!params.owner && !params.repo) {
1026
+ tips.push('Add owner filter to target specific organizations');
1027
+ }
1028
+ if (params.query.includes(' OR ') && !params.language && !params.owner) {
1029
+ tips.push('Add language or owner filter with OR for better targeting');
1030
+ }
1031
+ if (params.query.includes(' NOT ')) {
1032
+ tips.push('Combine NOT with positive filters (language, filename, etc.) for best results');
1033
+ }
1034
+ if (!params.filename && !params.extension) {
1035
+ tips.push('Add filename or extension filter for file-type precision');
1036
+ }
1037
+ return tips.slice(0, 3);
1038
+ }
1039
+ /**
1040
+ * Extract single repository if all results are from same repo
1041
+ */
1042
+ function extractSingleRepository$1(items) {
1043
+ if (items.length === 0)
1044
+ return null;
1045
+ const firstRepo = items[0].repository;
1046
+ const allSameRepo = items.every(item => item.repository.nameWithOwner === firstRepo.nameWithOwner);
1047
+ return allSameRepo ? firstRepo : null;
546
1048
  }
547
1049
  /**
548
- * Build command line arguments for GitHub CLI
1050
+ * Build command line arguments for GitHub CLI with improved parameter handling
1051
+ * Ensures exact string search capability with proper quote and escape handling
549
1052
  */
550
1053
  function buildGitHubCliArgs(params) {
551
1054
  const args = ['code'];
552
- // Parse and add the main search query
553
- const searchQuery = parseSearchQuery(params.query, params);
554
- args.push(searchQuery);
555
- // Add CLI flags - Always add basic flags, but be careful with complex boolean queries
556
- const hasComplexLogic = hasComplexBooleanLogic(searchQuery);
557
- // For complex boolean queries, add filters to the query string instead of CLI flags
558
- if (hasComplexLogic) ;
1055
+ // Handle exact string search - preserve quotes and special characters
1056
+ const searchQuery = params.query;
1057
+ // For exact string searches (quoted strings), preserve the quotes
1058
+ // For special characters and escape sequences, pass them through
1059
+ // GitHub CLI will handle the proper escaping to the GitHub API
1060
+ if (searchQuery.includes('"') || searchQuery.includes("'")) {
1061
+ // Already has quotes - exact string search
1062
+ args.push(searchQuery);
1063
+ }
1064
+ else if (searchQuery.includes('\\') ||
1065
+ /[<>{}[\]()&|;$`!*?~]/.test(searchQuery)) {
1066
+ // Contains special characters that might need exact matching
1067
+ // Let GitHub CLI handle the escaping - don't double-escape
1068
+ args.push(searchQuery);
1069
+ }
559
1070
  else {
560
- // Simple queries: use CLI flags for better performance
561
- if (params.language) {
562
- args.push(`--language=${params.language}`);
563
- }
564
- if (params.extension) {
565
- args.push(`--extension=${params.extension}`);
566
- }
567
- if (params.filename) {
568
- args.push(`--filename=${params.filename}`);
569
- }
570
- if (params.size) {
571
- args.push(`--size=${params.size}`);
572
- }
1071
+ // Regular search query
1072
+ args.push(searchQuery);
1073
+ }
1074
+ // Add filters in order of effectiveness for better CLI performance
1075
+ if (params.language) {
1076
+ args.push(`--language=${params.language}`);
1077
+ }
1078
+ if (params.filename) {
1079
+ args.push(`--filename=${params.filename}`);
1080
+ }
1081
+ if (params.extension) {
1082
+ args.push(`--extension=${params.extension}`);
573
1083
  }
1084
+ if (params.size) {
1085
+ args.push(`--size=${params.size}`);
1086
+ }
1087
+ // Always add limit
574
1088
  if (params.limit) {
575
1089
  args.push(`--limit=${params.limit}`);
576
1090
  }
577
- // Handle match parameter - can be string or array
1091
+ // Handle match parameter
578
1092
  if (params.match) {
579
1093
  const matchValues = Array.isArray(params.match)
580
1094
  ? params.match
581
1095
  : [params.match];
582
- // GitHub API limitation: can't use both in:file and in:path in same query
583
1096
  // Use the first match type when multiple are provided
584
1097
  const matchValue = matchValues[0];
585
1098
  args.push(`--match=${matchValue}`);
@@ -591,22 +1104,32 @@ function buildGitHubCliArgs(params) {
591
1104
  : [params.owner];
592
1105
  ownerValues.forEach(owner => args.push(`--owner=${owner}`));
593
1106
  }
594
- // Handle repository filters
595
- if (params.owner && params.repo) {
596
- const owners = Array.isArray(params.owner) ? params.owner : [params.owner];
1107
+ // Handle repository filters with improved validation
1108
+ if (params.repo) {
597
1109
  const repos = Array.isArray(params.repo) ? params.repo : [params.repo];
598
- // Create repo filters for each owner/repo combination
599
- owners.forEach(owner => {
1110
+ if (params.owner) {
1111
+ const owners = Array.isArray(params.owner)
1112
+ ? params.owner
1113
+ : [params.owner];
1114
+ // Create repo filters for each owner/repo combination
1115
+ owners.forEach(owner => {
1116
+ repos.forEach(repo => {
1117
+ // Handle both "owner/repo" format and just "repo" format
1118
+ if (repo.includes('/')) {
1119
+ args.push(`--repo=${repo}`);
1120
+ }
1121
+ else {
1122
+ args.push(`--repo=${owner}/${repo}`);
1123
+ }
1124
+ });
1125
+ });
1126
+ }
1127
+ else {
1128
+ // Handle repo without owner (must be in owner/repo format)
600
1129
  repos.forEach(repo => {
601
- // Handle both "owner/repo" format and just "repo" format
602
- if (repo.includes('/')) {
603
- args.push(`--repo=${repo}`);
604
- }
605
- else {
606
- args.push(`--repo=${owner}/${repo}`);
607
- }
1130
+ args.push(`--repo=${repo}`);
608
1131
  });
609
- });
1132
+ }
610
1133
  }
611
1134
  // JSON output with all available fields
612
1135
  args.push('--json=repository,path,textMatches,sha,url');
@@ -623,75 +1146,149 @@ async function searchGitHubCode(params) {
623
1146
  return result;
624
1147
  }
625
1148
  catch (error) {
626
- return createErrorResult$1('Code search command failed - verify GitHub CLI is authenticated', error);
1149
+ const errorMessage = error.message || '';
1150
+ // Parse specific GitHub CLI error types
1151
+ if (errorMessage.includes('authentication')) {
1152
+ return createResult({
1153
+ error: 'GitHub authentication required - run api_status_check tool',
1154
+ });
1155
+ }
1156
+ if (errorMessage.includes('rate limit')) {
1157
+ return createResult({
1158
+ error: 'GitHub rate limit exceeded - use more specific filters or wait',
1159
+ suggestions: [
1160
+ 'Add language filter to reduce search scope',
1161
+ 'Use owner filter to target specific organizations',
1162
+ 'Add filename filter for precision targeting',
1163
+ 'Wait a few minutes and try again',
1164
+ ],
1165
+ });
1166
+ }
1167
+ if (errorMessage.includes('validation failed') ||
1168
+ errorMessage.includes('Invalid query')) {
1169
+ return createResult({
1170
+ error: 'Invalid query syntax. GitHub legacy search has limitations.',
1171
+ suggestions: [
1172
+ 'Remove NOT operators (use positive filters instead)',
1173
+ 'Simplify OR logic to separate searches',
1174
+ 'Use quotes for exact phrases: "error handling"',
1175
+ 'Try: language:python + "function definition" instead of complex boolean',
1176
+ ],
1177
+ });
1178
+ }
1179
+ if (errorMessage.includes('repository not found') ||
1180
+ errorMessage.includes('owner not found')) {
1181
+ return createResult({
1182
+ error: 'Repository or owner not found',
1183
+ suggestions: [
1184
+ 'Verify exact owner/repository names (case-sensitive)',
1185
+ 'Check if repository is private and you have access',
1186
+ 'Remove repo filter to search across all repositories',
1187
+ 'Use owner filter instead of specific repo for broader search',
1188
+ ],
1189
+ });
1190
+ }
1191
+ if (errorMessage.includes('timeout')) {
1192
+ return createResult({
1193
+ error: 'Search timeout - query too broad',
1194
+ suggestions: [
1195
+ 'Add language filter to narrow scope',
1196
+ 'Use owner or repo filters for targeting',
1197
+ 'Simplify query terms',
1198
+ 'Add filename filter for specific file types',
1199
+ ],
1200
+ });
1201
+ }
1202
+ // Generic fallback with helpful guidance
1203
+ return createResult({
1204
+ error: 'Code search failed',
1205
+ suggestions: [
1206
+ 'Check GitHub CLI authentication with: gh auth status',
1207
+ 'Simplify query and add language filter',
1208
+ 'Try owner filter for targeted search',
1209
+ 'Use api_status_check tool to verify setup',
1210
+ ],
1211
+ });
627
1212
  }
628
1213
  });
629
1214
  }
1215
+ /**
1216
+ * Enhanced validation with helpful suggestions
1217
+ * Supports exact string search with quotes and special characters
1218
+ */
1219
+ function validateSearchParameters(params) {
1220
+ // Query validation
1221
+ if (!params.query.trim()) {
1222
+ return 'Empty query. Try: "useState", "authentication", "docker setup", or use filters like language:python';
1223
+ }
1224
+ if (params.query.length > 1000) {
1225
+ return 'Query too long (max 1000 chars). Simplify to key terms like "error handling" instead of full sentences.';
1226
+ }
1227
+ // Repository validation - allow owner/repo format in repo field
1228
+ if (params.repo && !params.owner) {
1229
+ const repoValues = Array.isArray(params.repo) ? params.repo : [params.repo];
1230
+ const hasOwnerFormat = repoValues.every(repo => repo.includes('/'));
1231
+ if (!hasOwnerFormat) {
1232
+ return 'Repository format error. Use "owner/repo" format like "facebook/react" or provide both owner and repo parameters.';
1233
+ }
1234
+ }
1235
+ // Support exact string searches with quotes and special characters
1236
+ // Remove overly restrictive validation for escape characters
1237
+ // GitHub CLI and API can handle special characters properly
1238
+ // Boolean operator validation with suggestions
1239
+ const invalidBooleans = params.query.match(/\b(and|or|not)\b/g);
1240
+ if (invalidBooleans) {
1241
+ const corrected = invalidBooleans.map(op => op.toUpperCase()).join(', ');
1242
+ return `Boolean operators must be uppercase: ${corrected}. Example: "react OR vue" not "react or vue"`;
1243
+ }
1244
+ return null; // No validation errors
1245
+ }
630
1246
 
631
1247
  const TOOL_NAME$7 = 'github_get_file_content';
632
- 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.`;
1248
+ const DESCRIPTION$7 = `Read file content with exact path verification. Smart branch fallbacks and size limits. Use github_get_contents first to verify file existence.`;
633
1249
  function registerFetchGitHubFileContentTool(server) {
634
- server.tool(TOOL_NAME$7, DESCRIPTION$7, {
635
- owner: z
636
- .string()
637
- .min(1)
638
- .max(100)
639
- .regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/)
640
- .describe(`Repository owner/organization (e.g., 'microsoft', 'facebook')`),
641
- repo: z
642
- .string()
643
- .min(1)
644
- .max(100)
645
- .regex(/^[a-zA-Z0-9._-]+$/)
646
- .describe(`Repository name (e.g., 'vscode', 'react'). Case-sensitive.`),
647
- branch: z
648
- .string()
649
- .min(1)
650
- .max(255)
651
- .regex(/^[^\s]+$/)
652
- .describe(`Branch name (e.g., 'main', 'master'). Auto-fallback to common branches if not found.`),
653
- filePath: z
654
- .string()
655
- .min(1)
656
- .describe(`File path from repository root (e.g., 'README.md', 'src/index.js'). Use github_get_contents to explore structure.`),
657
- }, {
658
- title: TOOL_NAME$7,
1250
+ server.registerTool(TOOL_NAME$7, {
659
1251
  description: DESCRIPTION$7,
660
- readOnlyHint: true,
661
- destructiveHint: false,
662
- idempotentHint: true,
663
- openWorldHint: true,
1252
+ inputSchema: {
1253
+ owner: z
1254
+ .string()
1255
+ .min(1)
1256
+ .max(100)
1257
+ .regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/)
1258
+ .describe(`Repository owner/organization`),
1259
+ repo: z
1260
+ .string()
1261
+ .min(1)
1262
+ .max(100)
1263
+ .regex(/^[a-zA-Z0-9._-]+$/)
1264
+ .describe(`Repository name. Case-sensitive.`),
1265
+ branch: z
1266
+ .string()
1267
+ .min(1)
1268
+ .max(255)
1269
+ .regex(/^[^\s]+$/)
1270
+ .describe(`Branch name. Auto-fallback to common branches if not found.`),
1271
+ filePath: z
1272
+ .string()
1273
+ .min(1)
1274
+ .describe(`File path from repository root. Use github_get_contents to explore structure.`),
1275
+ },
1276
+ annotations: {
1277
+ title: 'GitHub File Content Reader',
1278
+ readOnlyHint: true,
1279
+ destructiveHint: false,
1280
+ idempotentHint: true,
1281
+ openWorldHint: true,
1282
+ },
664
1283
  }, async (args) => {
665
1284
  try {
666
1285
  const result = await fetchGitHubFileContent(args);
667
- if (result.content && result.content[0] && !result.isError) {
668
- const { data, parsed } = parseJsonResponse(result.content[0].text);
669
- if (parsed) {
670
- return createResult({
671
- file: `${args.owner}/${args.repo}/${args.filePath}`,
672
- content: data.content || data,
673
- metadata: {
674
- branch: args.branch,
675
- size: data.size,
676
- encoding: data.encoding,
677
- },
678
- });
679
- }
680
- else {
681
- // Return raw file content
682
- return createResult({
683
- file: `${args.owner}/${args.repo}/${args.filePath}`,
684
- content: data,
685
- metadata: {
686
- branch: args.branch,
687
- },
688
- });
689
- }
690
- }
691
1286
  return result;
692
1287
  }
693
1288
  catch (error) {
694
- return createResult('File fetch failed - verify file path exists or try github_get_contents first', true);
1289
+ return createResult({
1290
+ error: 'File fetch failed - verify path or try github_get_contents first',
1291
+ });
695
1292
  }
696
1293
  });
697
1294
  }
@@ -724,19 +1321,45 @@ async function fetchGitHubFileContent(params) {
724
1321
  }
725
1322
  // Handle common errors
726
1323
  if (errorMsg.includes('404')) {
727
- return createErrorResult$1('File not found - verify path with github_get_contents first', new Error(filePath));
1324
+ return createResult({
1325
+ error: 'File not found - verify path with github_get_contents',
1326
+ cli_command: `gh api "/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}"`,
1327
+ });
728
1328
  }
729
1329
  else if (errorMsg.includes('403')) {
730
- return createErrorResult$1('Access denied - repository may be private or require authentication', new Error(`${owner}/${repo}`));
1330
+ return createResult({
1331
+ error: 'Access denied - repository may be private',
1332
+ cli_command: `gh api "/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}"`,
1333
+ });
1334
+ }
1335
+ else if (errorMsg.includes('maxBuffer') ||
1336
+ errorMsg.includes('stdout maxBuffer length exceeded')) {
1337
+ return createResult({
1338
+ error: 'File too large (>300KB) - use github_search_code for patterns instead',
1339
+ cli_command: `gh api "/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}"`,
1340
+ });
731
1341
  }
732
1342
  else {
733
- return createErrorResult$1('Fetch failed - check repository and file path', new Error(errorMsg));
1343
+ return createResult({
1344
+ error: 'Fetch failed - check repository and file path',
1345
+ cli_command: `gh api "/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}"`,
1346
+ });
734
1347
  }
735
1348
  }
736
1349
  return await processFileContent(result, owner, repo, branch, filePath);
737
1350
  }
738
1351
  catch (error) {
739
- return createErrorResult$1('Unexpected error during file fetch - check connection and permissions', error);
1352
+ const errorMessage = error.message;
1353
+ // Handle maxBuffer errors that escape the main try-catch
1354
+ if (errorMessage.includes('maxBuffer') ||
1355
+ errorMessage.includes('stdout maxBuffer length exceeded')) {
1356
+ return createResult({
1357
+ error: 'File too large (>300KB) - use github_search_code for patterns instead',
1358
+ });
1359
+ }
1360
+ return createResult({
1361
+ error: 'Unexpected error during file fetch - check connection',
1362
+ });
740
1363
  }
741
1364
  });
742
1365
  }
@@ -746,30 +1369,42 @@ async function processFileContent(result, owner, repo, branch, filePath) {
746
1369
  const fileData = JSON.parse(execResult.result);
747
1370
  // Check if it's a directory
748
1371
  if (Array.isArray(fileData)) {
749
- return createErrorResult$1('Path is directory - use github_get_contents to browse directory structure', new Error(filePath));
1372
+ return createResult({
1373
+ error: 'Path is directory - use github_get_contents instead',
1374
+ });
750
1375
  }
751
1376
  const fileSize = fileData.size || 0;
752
- const MAX_FILE_SIZE = 500 * 1024; // 500KB limit for simplicity
753
- // Check file size
1377
+ const MAX_FILE_SIZE = 300 * 1024; // 300KB limit for better performance and reliability
1378
+ // Check file size with helpful message
754
1379
  if (fileSize > MAX_FILE_SIZE) {
755
- return createErrorResult$1('File too large - files over 500KB cannot be fetched', new Error(`${Math.round(fileSize / 1024)}KB > 500KB`));
1380
+ const fileSizeKB = Math.round(fileSize / 1024);
1381
+ const maxSizeKB = Math.round(MAX_FILE_SIZE / 1024);
1382
+ return createResult({
1383
+ error: `File too large (${fileSizeKB}KB > ${maxSizeKB}KB) - use github_search_code for patterns`,
1384
+ });
756
1385
  }
757
1386
  // Get and decode content
758
1387
  const base64Content = fileData.content?.replace(/\s/g, ''); // Remove all whitespace
759
1388
  if (!base64Content) {
760
- return createErrorResult$1('Empty file - file has no content to display', new Error(filePath));
1389
+ return createResult({
1390
+ error: 'Empty file - no content to display',
1391
+ });
761
1392
  }
762
1393
  let decodedContent;
763
1394
  try {
764
1395
  const buffer = Buffer.from(base64Content, 'base64');
765
1396
  // Simple binary check - look for null bytes
766
1397
  if (buffer.indexOf(0) !== -1) {
767
- return createErrorResult$1('Binary file detected - cannot display binary content as text', new Error(filePath));
1398
+ return createResult({
1399
+ error: 'Binary file detected - cannot display as text',
1400
+ });
768
1401
  }
769
1402
  decodedContent = buffer.toString('utf-8');
770
1403
  }
771
1404
  catch (decodeError) {
772
- return createErrorResult$1('Decode failed - file encoding not supported or corrupted', new Error(filePath));
1405
+ return createResult({
1406
+ error: 'Decode failed - file encoding not supported',
1407
+ });
773
1408
  }
774
1409
  // Return simplified response
775
1410
  const response = {
@@ -784,132 +1419,275 @@ async function processFileContent(result, owner, repo, branch, filePath) {
784
1419
  encoding: 'utf-8',
785
1420
  },
786
1421
  };
787
- return createSuccessResult$1(response);
1422
+ return createResult({ data: response });
788
1423
  }
789
1424
 
1425
+ /**
1426
+ * GitHub Repository Search Tool
1427
+ *
1428
+ * MOST EFFECTIVE PATTERNS (based on testing):
1429
+ *
1430
+ * 1. Quality Discovery:
1431
+ * { topic: ["react", "typescript"], stars: "1000..5000", limit: 10 }
1432
+ *
1433
+ * 2. Organization Research:
1434
+ * { owner: ["microsoft", "google"], language: "python", limit: 10 }
1435
+ *
1436
+ * 3. Beginner Projects:
1437
+ * { goodFirstIssues: ">=5", stars: "100..5000", limit: 10 }
1438
+ *
1439
+ * 4. Recent Quality:
1440
+ * { stars: ">1000", created: ">2023-01-01", limit: 10 }
1441
+ *
1442
+ * AVOID: OR queries + language filter, 5+ filters, multi-word OR
1443
+ * TIP: Use limit parameter instead of adding more filters
1444
+ */
790
1445
  const TOOL_NAME$6 = 'github_search_repositories';
791
- 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.
792
- PRIMARY FILTERS work alone: owner, language, stars, topic, forks. SECONDARY FILTERS require a query or primary filter: license, created, archived, includeForks, updated, visibility, match.
793
- 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".
794
-
795
- EFFICIENCY NOTE: If you have a package name, use npm_view_package FIRST to get repositoryGitUrl - this tool becomes UNNECESSARY
796
-
797
- SMART INTEGRATION: When finding packages npm_view_package + npm_package_search provide direct repo access
798
- AVOID: Searching for "react" repos when npm_view_package("react") gives you the exact repository instantly`;
1446
+ const DESCRIPTION$6 = `Search GitHub repositories with powerful GitHub search syntax and advanced filtering.
1447
+
1448
+ EMBEDDED QUALIFIERS (MOST POWERFUL):
1449
+ - "vue OR react stars:>1000 language:javascript" - OR logic with constraints
1450
+ - "typescript AND framework stars:100..5000" - AND logic with star range
1451
+ - "repo:facebook/react OR repo:vuejs/vue" - Multiple specific repositories
1452
+ - "org:microsoft OR org:google language:typescript" - Multiple organizations
1453
+ - "topic:react topic:typescript stars:>500" - Multiple topics with quality filter
1454
+
1455
+ TRADITIONAL FILTERS (ALSO SUPPORTED):
1456
+ - owner: ["microsoft", "google"] - Multiple owners as array
1457
+ - topic: ["react", "typescript"] - Multiple topics as array
1458
+ - stars: "1000..5000" - Range or threshold filtering
1459
+
1460
+ BEST PRACTICES:
1461
+ - Use embedded qualifiers for complex queries with OR/AND logic
1462
+ - Use traditional filters for simple, clean parameter-based searches
1463
+ - Combine both approaches: "vue OR react" + language:"javascript" + stars:">1000"`;
1464
+ /**
1465
+ * Extract owner/repo information from various query formats
1466
+ */
1467
+ function extractOwnerRepoFromQuery(query) {
1468
+ let cleanedQuery = query;
1469
+ let extractedOwner;
1470
+ let extractedRepo;
1471
+ // Pattern 1: GitHub URLs (https://github.com/owner/repo)
1472
+ const githubUrlMatch = query.match(/github\.com\/([^\\s]+)\/([^\\s]+)/i);
1473
+ if (githubUrlMatch) {
1474
+ extractedOwner = githubUrlMatch[1];
1475
+ extractedRepo = githubUrlMatch[2];
1476
+ cleanedQuery = query
1477
+ .replace(/https?:\/\/github\.com\/[^\\s]+\/[^\\s]+/gi, '')
1478
+ .trim();
1479
+ }
1480
+ // Pattern 2: owner/repo format in query
1481
+ const ownerRepoMatch = query.match(/\b([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\/([a-zA-Z0-9][a-zA-Z0-9\-.]*[a-zA-Z0-9])\b/);
1482
+ if (ownerRepoMatch && !extractedOwner) {
1483
+ extractedOwner = ownerRepoMatch[1];
1484
+ extractedRepo = ownerRepoMatch[2];
1485
+ cleanedQuery = query.replace(ownerRepoMatch[0], '').trim();
1486
+ }
1487
+ // Pattern 3: NPM package-like references (@scope/package)
1488
+ const scopedPackageMatch = query.match(/@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\/([a-zA-Z0-9][a-zA-Z0-9\-.]*[a-zA-Z0-9])/);
1489
+ if (scopedPackageMatch && !extractedOwner) {
1490
+ extractedOwner = scopedPackageMatch[1];
1491
+ extractedRepo = scopedPackageMatch[2];
1492
+ cleanedQuery = query.replace(scopedPackageMatch[0], '').trim();
1493
+ }
1494
+ return {
1495
+ extractedOwner,
1496
+ extractedRepo,
1497
+ cleanedQuery: cleanedQuery || query,
1498
+ };
1499
+ }
799
1500
  function registerSearchGitHubReposTool(server) {
800
- server.tool(TOOL_NAME$6, DESCRIPTION$6, {
801
- query: z
802
- .string()
803
- .optional()
804
- .describe('Search query with GitHub syntax: "cli shell" (AND), "vim plugin" (phrase), "language:Go OR language:Rust" (OR). Optional - can search with just primary filters.'),
805
- // PRIMARY FILTERS (can work alone)
806
- owner: z
807
- .string()
808
- .optional()
809
- .describe('Repository owner/organization (e.g., "microsoft", "facebook").'),
810
- language: z
811
- .string()
812
- .optional()
813
- .describe('Programming language (e.g., "javascript", "python", "go").'),
814
- stars: z
815
- .string()
816
- .optional()
817
- .describe('Stars count with ranges: "100", ">500", "<50", "10..100", ">=1000". Use >100 for quality projects.'),
818
- topic: z
819
- .array(z.string())
820
- .optional()
821
- .describe('Filter by topics (e.g., ["cli", "typescript", "api"]).'),
822
- forks: z.number().optional().describe('Exact forks count.'),
823
- numberOfTopics: z
824
- .number()
825
- .optional()
826
- .describe('Filter on number of topics.'),
827
- // SECONDARY FILTERS (require query or primary filter)
828
- license: z
829
- .array(z.string())
830
- .optional()
831
- .describe('License types (e.g., ["mit", "apache-2.0"]).'),
832
- match: z
833
- .enum(['name', 'description', 'readme'])
834
- .optional()
835
- .describe('Search scope: "name", "description", or "readme".'),
836
- visibility: z
837
- .enum(['public', 'private', 'internal'])
838
- .optional()
839
- .describe('Repository visibility filter.'),
840
- created: z
841
- .string()
842
- .optional()
843
- .describe('Created date filter: ">2020-01-01", "<2023-12-31", "2022-01-01..2023-12-31".'),
844
- updated: z
845
- .string()
846
- .optional()
847
- .describe('Updated date filter (same format as created).'),
848
- archived: z.boolean().optional().describe('Filter by archived state.'),
849
- includeForks: z
850
- .enum(['false', 'true', 'only'])
851
- .optional()
852
- .describe('Include forks: "false" (default), "true", or "only".'),
853
- goodFirstIssues: z
854
- .string()
855
- .optional()
856
- .describe('Filter by good first issues count (e.g., ">=10", ">5").'),
857
- helpWantedIssues: z
858
- .string()
859
- .optional()
860
- .describe('Filter by help wanted issues count (e.g., ">=5", ">10").'),
861
- followers: z
862
- .number()
863
- .optional()
864
- .describe('Filter by number of followers.'),
865
- size: z
866
- .string()
867
- .optional()
868
- .describe('Repository size filter in KB (e.g., ">100", "<50", "10..100").'),
869
- // Sorting and limits
870
- sort: z
871
- .enum(['forks', 'help-wanted-issues', 'stars', 'updated', 'best-match'])
872
- .optional()
873
- .default('best-match')
874
- .describe('Sort criteria (default: best-match)'),
875
- order: z
876
- .enum(['asc', 'desc'])
877
- .optional()
878
- .default('desc')
879
- .describe('Result order (default: desc)'),
880
- limit: z
881
- .number()
882
- .int()
883
- .min(1)
884
- .max(50)
885
- .optional()
886
- .default(25)
887
- .describe('Maximum results (default: 25, max: 50)'),
888
- }, {
889
- title: TOOL_NAME$6,
1501
+ server.registerTool(TOOL_NAME$6, {
890
1502
  description: DESCRIPTION$6,
891
- readOnlyHint: true,
892
- destructiveHint: false,
893
- idempotentHint: true,
894
- openWorldHint: true,
1503
+ inputSchema: {
1504
+ query: z
1505
+ .string()
1506
+ .optional()
1507
+ .describe('Search query with GitHub search syntax. POWERFUL EXAMPLES: "vue OR react stars:>1000", "typescript AND framework stars:100..5000", "repo:facebook/react OR repo:vuejs/vue", "org:microsoft language:typescript", "topic:react topic:typescript stars:>500". SUPPORTS: OR/AND logic, embedded qualifiers (stars:, language:, org:, repo:, topic:, etc.), exact repository targeting. COMBINES with traditional filters for maximum flexibility.'),
1508
+ // CORE FILTERS (GitHub CLI flags)
1509
+ owner: z
1510
+ .union([z.string(), z.array(z.string())])
1511
+ .optional()
1512
+ .describe('Repository owner/organization. HIGHLY EFFECTIVE as array ["microsoft", "google"]. FIXED: Now supports multiple owners with comma separation. Best for targeted research.'),
1513
+ language: z
1514
+ .string()
1515
+ .optional()
1516
+ .describe('Programming language filter. CAUTION: Restrictive with other filters. Use alone or with stars/owner only.'),
1517
+ stars: z
1518
+ .union([
1519
+ z.number().int().min(0),
1520
+ z
1521
+ .string()
1522
+ .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid stars format. Use: number, ">100", ">=50", "<200", "<=100", or "10..100"'),
1523
+ ])
1524
+ .optional()
1525
+ .describe('Stars filter. MOST EFFECTIVE: ranges "1000..5000", thresholds ">1000". Excellent for quality filtering.'),
1526
+ topic: z
1527
+ .union([z.string(), z.array(z.string())])
1528
+ .optional()
1529
+ .describe('Topics filter. BEST PATTERN: arrays ["react", "typescript"]. FIXED: Now supports comma-separated topics. Preferred over OR queries. Combines well with stars.'),
1530
+ forks: z.number().optional().describe('Number of forks filter.'),
1531
+ // UPDATED: Match CLI parameter name exactly
1532
+ numberOfTopics: z
1533
+ .number()
1534
+ .optional()
1535
+ .describe('Filter by number of topics (indicates documentation quality).'),
1536
+ // QUALITY & STATE FILTERS
1537
+ license: z
1538
+ .union([z.string(), z.array(z.string())])
1539
+ .optional()
1540
+ .describe('License filter. Works well as array ["mit", "apache-2.0"].'),
1541
+ archived: z
1542
+ .boolean()
1543
+ .optional()
1544
+ .describe('Filter archived repositories (true/false).'),
1545
+ includeForks: z
1546
+ .enum(['false', 'true', 'only'])
1547
+ .optional()
1548
+ .describe('Include forks: false (exclude), true (include), only (forks only).'),
1549
+ visibility: z
1550
+ .enum(['public', 'private', 'internal'])
1551
+ .optional()
1552
+ .describe('Repository visibility filter.'),
1553
+ // DATE & SIZE FILTERS
1554
+ created: z
1555
+ .string()
1556
+ .optional()
1557
+ .describe('Created date filter. Format: ">2020-01-01", "<2023-12-31".'),
1558
+ updated: z
1559
+ .string()
1560
+ .optional()
1561
+ .describe('Updated date filter. Good for finding active projects.'),
1562
+ size: z
1563
+ .string()
1564
+ .optional()
1565
+ .describe('Repository size filter in KB. Format: ">1000", "<500".'),
1566
+ // COMMUNITY FILTERS - Match CLI parameter names exactly
1567
+ goodFirstIssues: z
1568
+ .union([
1569
+ z.number().int().min(0),
1570
+ z
1571
+ .string()
1572
+ .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: number, ">5", ">=10", "<20", "<=15", or "5..20"'),
1573
+ ])
1574
+ .optional()
1575
+ .describe('Good first issues count. WORKING: Filter for beginner-friendly projects. EXCELLENT when combined with stars "100..5000" for quality beginner projects.'),
1576
+ helpWantedIssues: z
1577
+ .union([
1578
+ z.number().int().min(0),
1579
+ z
1580
+ .string()
1581
+ .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: number, ">5", ">=10", "<20", "<=15", or "5..20"'),
1582
+ ])
1583
+ .optional()
1584
+ .describe('Help wanted issues count. Good for finding projects needing contributors.'),
1585
+ followers: z.number().optional().describe('Followers count filter.'),
1586
+ // SEARCH SCOPE
1587
+ match: z
1588
+ .enum(['name', 'description', 'readme'])
1589
+ .optional()
1590
+ .describe('Search scope: name, description, or readme content.'),
1591
+ // SORTING & LIMITS - Match CLI defaults exactly
1592
+ sort: z
1593
+ .enum([
1594
+ 'forks',
1595
+ 'help-wanted-issues',
1596
+ 'stars',
1597
+ 'updated',
1598
+ 'best-match',
1599
+ ])
1600
+ .optional()
1601
+ .default('best-match')
1602
+ .describe('Sort by: stars, updated, forks, help-wanted-issues, best-match.'),
1603
+ order: z
1604
+ .enum(['asc', 'desc'])
1605
+ .optional()
1606
+ .default('desc')
1607
+ .describe('Sort order: asc or desc.'),
1608
+ limit: z
1609
+ .number()
1610
+ .int()
1611
+ .min(1)
1612
+ .max(100)
1613
+ .optional()
1614
+ .default(30)
1615
+ .describe('Results limit (1-100). PREFER increasing limit over adding filters.'),
1616
+ },
1617
+ annotations: {
1618
+ title: 'GitHub Repository Search',
1619
+ readOnlyHint: true,
1620
+ destructiveHint: false,
1621
+ idempotentHint: true,
1622
+ openWorldHint: true,
1623
+ },
895
1624
  }, async (args) => {
896
1625
  try {
897
- // Updated validation logic for primary filters
898
- const hasPrimaryFilter = args.query?.trim() ||
899
- args.owner ||
900
- args.language ||
901
- args.topic ||
902
- args.stars ||
903
- args.forks;
1626
+ // Extract owner/repo from query if present
1627
+ const queryInfo = args.query
1628
+ ? extractOwnerRepoFromQuery(args.query)
1629
+ : {
1630
+ cleanedQuery: '',
1631
+ extractedOwner: undefined,
1632
+ extractedRepo: undefined,
1633
+ };
1634
+ // Merge extracted owner with explicit owner parameter
1635
+ let finalOwner = args.owner;
1636
+ if (queryInfo.extractedOwner && !finalOwner) {
1637
+ finalOwner = queryInfo.extractedOwner;
1638
+ }
1639
+ // Update parameters with extracted information
1640
+ const enhancedArgs = {
1641
+ ...args,
1642
+ query: queryInfo.cleanedQuery || args.query,
1643
+ owner: finalOwner,
1644
+ };
1645
+ // Enhanced validation logic for primary filters
1646
+ const hasPrimaryFilter = enhancedArgs.query?.trim() ||
1647
+ enhancedArgs.owner ||
1648
+ enhancedArgs.language ||
1649
+ enhancedArgs.topic ||
1650
+ enhancedArgs.stars ||
1651
+ enhancedArgs.forks;
904
1652
  if (!hasPrimaryFilter) {
905
- return createResult('Requires query or primary filter (owner, language, stars, topic, forks)', true);
1653
+ return createResult({
1654
+ error: 'Requires query or primary filter (owner, language, stars, topic, forks). You can also use owner/repo format like "microsoft/vscode" in the query.',
1655
+ });
1656
+ }
1657
+ // First attempt: Search with current parameters
1658
+ const result = await searchGitHubRepos(enhancedArgs);
1659
+ // Fallback for private repositories: If no results and owner is specified, try with private visibility
1660
+ if (!result.isError) {
1661
+ const resultData = JSON.parse(result.content[0].text);
1662
+ if (resultData.total === 0 &&
1663
+ enhancedArgs.owner &&
1664
+ !enhancedArgs.visibility) {
1665
+ // Try searching with private visibility for organization repos
1666
+ const privateSearchArgs = {
1667
+ ...enhancedArgs,
1668
+ visibility: 'private',
1669
+ };
1670
+ const privateResult = await searchGitHubRepos(privateSearchArgs);
1671
+ if (!privateResult.isError) {
1672
+ const privateData = JSON.parse(privateResult.content[0].text);
1673
+ if (privateData.total > 0) {
1674
+ // Return private results with note
1675
+ return createResult({
1676
+ data: {
1677
+ ...privateData,
1678
+ note: 'Found results in private repositories within the specified organization.',
1679
+ },
1680
+ });
1681
+ }
1682
+ }
1683
+ }
906
1684
  }
907
- // Search repositories using GitHub CLI
908
- const result = await searchGitHubRepos(args);
909
1685
  return result;
910
1686
  }
911
1687
  catch (error) {
912
- return createResult('Repository search failed - check query syntax, filters, or try broader terms', true);
1688
+ return createResult({
1689
+ error: 'Repository search failed - verify connection or simplify query',
1690
+ });
913
1691
  }
914
1692
  });
915
1693
  }
@@ -980,129 +1758,124 @@ async function searchGitHubRepos(params) {
980
1758
  license: repo.license?.name || null,
981
1759
  hasIssues: repo.hasIssues || false,
982
1760
  openIssuesCount: repo.openIssuesCount || 0,
983
- createdAt: repo.createdAt,
984
- updatedAt: repo.updatedAt,
1761
+ createdAt: toDDMMYYYY(repo.createdAt),
1762
+ updatedAt: toDDMMYYYY(repo.updatedAt),
985
1763
  visibility: repo.visibility || 'public',
986
1764
  owner: repo.owner?.login || repo.owner,
987
1765
  }));
988
1766
  }
989
- return createSuccessResult$1({
990
- query: params.query,
991
- total: analysis.totalFound,
992
- ...(analysis.totalFound > 0
993
- ? {
994
- repositories: analysis.topStarred,
995
- summary: {
996
- languages: Array.from(analysis.languages).slice(0, 10),
997
- avgStars: analysis.avgStars,
998
- recentlyUpdated: analysis.recentlyUpdated,
999
- },
1000
- }
1001
- : {
1002
- repositories: [],
1003
- }),
1767
+ return createResult({
1768
+ data: {
1769
+ total_count: analysis.totalFound,
1770
+ ...(analysis.totalFound > 0
1771
+ ? {
1772
+ repositories: analysis.topStarred,
1773
+ summary: {
1774
+ languages: Array.from(analysis.languages).slice(0, 10),
1775
+ avgStars: analysis.avgStars,
1776
+ recentlyUpdated: analysis.recentlyUpdated,
1777
+ },
1778
+ }
1779
+ : {
1780
+ repositories: [],
1781
+ cli_command: execResult.command, // Only on no results
1782
+ }),
1783
+ },
1004
1784
  });
1005
1785
  }
1006
1786
  catch (error) {
1007
- return createErrorResult$1('GitHub repository search failed - verify connection or try simpler query', error);
1787
+ return createResult({
1788
+ error: 'Repository search failed - verify connection or simplify query',
1789
+ });
1008
1790
  }
1009
1791
  });
1010
1792
  }
1011
1793
  function buildGitHubReposSearchCommand(params) {
1012
- // Build query following GitHub CLI patterns
1013
- const query = params.query?.trim() || '';
1014
- // Handle complex queries (with qualifiers, operators, or --) differently
1015
- const hasComplexSyntax = query.includes('--') ||
1016
- query.includes(':') ||
1017
- query.includes('OR') ||
1018
- query.includes('AND') ||
1019
- query.includes('(') ||
1020
- query.includes(')') ||
1021
- query.startsWith('-');
1022
- const args = ['repos'];
1023
- // Only add query if it exists
1024
- if (query) {
1025
- if (hasComplexSyntax) {
1026
- // For complex queries with special syntax, we need to be more careful
1027
- // Split by spaces but preserve quoted strings and handle special characters
1028
- const queryParts = query.match(/(?:[^\s"]+|"[^"]*")+/g) || [];
1029
- queryParts.forEach(part => {
1030
- // If part contains shell special characters, quote it
1031
- if (/[><=&|$`(){}[\];\\]/.test(part) && !part.includes('"')) {
1032
- args.push(`"${part}"`);
1033
- }
1034
- else {
1035
- args.push(part);
1036
- }
1037
- });
1038
- }
1039
- else {
1040
- // For simple queries, split by spaces to match GitHub CLI examples
1041
- // "cli shell" becomes separate args: cli shell
1042
- const queryParts = query.split(/\s+/).filter(part => part.length > 0);
1043
- queryParts.forEach(part => {
1044
- // Only quote if the part contains special characters
1045
- if (needsQuoting(part)) {
1046
- args.push(`"${part}"`);
1047
- }
1048
- else {
1049
- args.push(part);
1050
- }
1051
- });
1052
- }
1794
+ // Build query following GitHub CLI patterns
1795
+ const query = params.query?.trim() || '';
1796
+ const args = ['repos'];
1797
+ // Detect embedded qualifiers in query to avoid conflicts and optimize
1798
+ const hasEmbeddedQualifiers = query &&
1799
+ /\b(stars|language|org|repo|topic|user|created|updated|size|license|archived|fork|good-first-issues|help-wanted-issues):/i.test(query);
1800
+ // Handle exact string search - preserve quotes and special characters
1801
+ if (query) {
1802
+ // For exact repository name searches (quoted strings), preserve the quotes
1803
+ // For special characters, pass them through - GitHub CLI handles escaping
1804
+ // Support searches like "microsoft/vscode", "@types/node", etc.
1805
+ args.push(query);
1053
1806
  }
1054
1807
  // Add JSON output with specific fields for structured data parsing
1055
1808
  // Note: 'topics' field is not available in GitHub CLI search repos JSON output
1056
- args.push('--json', 'name,fullName,description,language,stargazersCount,forksCount,updatedAt,createdAt,url,owner,isPrivate,license,hasIssues,openIssuesCount,isArchived,isFork,visibility');
1057
- // PRIMARY FILTERS - Handle owner as single string (BaseSearchParams) or array
1058
- if (params.owner) {
1059
- const ownerValue = Array.isArray(params.owner)
1060
- ? params.owner.join(',')
1061
- : params.owner;
1062
- args.push(`--owner=${ownerValue}`);
1809
+ args.push('--json=name,fullName,description,language,stargazersCount,forksCount,updatedAt,createdAt,url,owner,isPrivate,license,hasIssues,openIssuesCount,isArchived,isFork,visibility');
1810
+ // SMART FILTER HANDLING - Avoid conflicts with embedded qualifiers
1811
+ // Only add CLI flags if not already specified in query to prevent conflicts
1812
+ // CORE FILTERS - Handle arrays properly
1813
+ if (params.owner && !hasEmbeddedQualifiers) {
1814
+ // GitHub CLI supports multiple owners with comma separation: --owner=owner1,owner2
1815
+ const owners = Array.isArray(params.owner) ? params.owner : [params.owner];
1816
+ args.push(`--owner=${owners.join(',')}`);
1063
1817
  }
1064
- if (params.language)
1818
+ if (params.language && !hasEmbeddedQualifiers)
1065
1819
  args.push(`--language=${params.language}`);
1066
- if (params.forks !== undefined)
1820
+ if (params.forks !== undefined && !hasEmbeddedQualifiers)
1067
1821
  args.push(`--forks=${params.forks}`);
1068
- if (params.topic && params.topic.length > 0)
1069
- args.push(`--topic=${params.topic.join(',')}`);
1822
+ // Handle topic as string or array - GitHub CLI expects comma-separated topics
1823
+ if (params.topic && !hasEmbeddedQualifiers) {
1824
+ const topics = Array.isArray(params.topic) ? params.topic : [params.topic];
1825
+ args.push(`--topic=${topics.join(',')}`);
1826
+ }
1070
1827
  if (params.numberOfTopics !== undefined)
1071
1828
  args.push(`--number-topics=${params.numberOfTopics}`);
1072
- // Only add stars filter if it's a valid numeric value or range
1073
- if (params.stars !== undefined &&
1074
- params.stars !== '*' &&
1075
- params.stars.trim() !== '') {
1076
- // Validate that stars parameter contains valid numeric patterns
1077
- const starsValue = params.stars.trim();
1078
- const isValidStars = /^(\d+|>\d+|<\d+|\d+\.\.\d+|>=\d+|<=\d+)$/.test(starsValue);
1079
- if (isValidStars) {
1080
- args.push(`--stars=${params.stars}`);
1829
+ // Handle stars as number or string - avoid conflicts with embedded qualifiers
1830
+ if (params.stars !== undefined && !hasEmbeddedQualifiers) {
1831
+ const starsValue = typeof params.stars === 'number'
1832
+ ? params.stars.toString()
1833
+ : params.stars.trim();
1834
+ // Validate numeric patterns for string values
1835
+ if (typeof params.stars === 'number' ||
1836
+ /^(\d+|>\d+|<\d+|\d+\.\.\d+|>=\d+|<=\d+)$/.test(starsValue)) {
1837
+ args.push(`--stars=${starsValue}`);
1081
1838
  }
1082
1839
  }
1083
- // SECONDARY FILTERS - only add if we have primary filters
1840
+ // QUALITY & STATE FILTERS
1084
1841
  if (params.archived !== undefined)
1085
1842
  args.push(`--archived=${params.archived}`);
1086
- if (params.created)
1087
- args.push(`--created="${params.created}"`);
1088
1843
  if (params.includeForks)
1089
1844
  args.push(`--include-forks=${params.includeForks}`);
1090
- if (params.license && params.license.length > 0)
1091
- args.push(`--license=${params.license.join(',')}`);
1092
- if (params.match)
1093
- args.push(`--match=${params.match}`);
1094
- if (params.updated)
1095
- args.push(`--updated="${params.updated}"`);
1096
1845
  if (params.visibility)
1097
1846
  args.push(`--visibility=${params.visibility}`);
1098
- if (params.goodFirstIssues)
1099
- args.push(`--good-first-issues=${params.goodFirstIssues}`);
1100
- if (params.helpWantedIssues)
1101
- args.push(`--help-wanted-issues=${params.helpWantedIssues}`);
1102
- if (params.followers !== undefined)
1103
- args.push(`--followers=${params.followers}`);
1847
+ // Handle license as string or array
1848
+ if (params.license) {
1849
+ const licenses = Array.isArray(params.license)
1850
+ ? params.license
1851
+ : [params.license];
1852
+ args.push(`--license=${licenses.join(',')}`);
1853
+ }
1854
+ // DATE & SIZE FILTERS
1855
+ if (params.created)
1856
+ args.push(`--created=${params.created}`);
1857
+ if (params.updated)
1858
+ args.push(`--updated=${params.updated}`);
1104
1859
  if (params.size)
1105
1860
  args.push(`--size=${params.size}`);
1861
+ // COMMUNITY FILTERS - handle both number and string
1862
+ if (params.goodFirstIssues) {
1863
+ const value = typeof params.goodFirstIssues === 'number'
1864
+ ? params.goodFirstIssues.toString()
1865
+ : params.goodFirstIssues;
1866
+ args.push(`--good-first-issues=${value}`);
1867
+ }
1868
+ if (params.helpWantedIssues) {
1869
+ const value = typeof params.helpWantedIssues === 'number'
1870
+ ? params.helpWantedIssues.toString()
1871
+ : params.helpWantedIssues;
1872
+ args.push(`--help-wanted-issues=${value}`);
1873
+ }
1874
+ if (params.followers !== undefined)
1875
+ args.push(`--followers=${params.followers}`);
1876
+ // SEARCH SCOPE
1877
+ if (params.match)
1878
+ args.push(`--match=${params.match}`);
1106
1879
  // SORTING AND LIMITS
1107
1880
  if (params.limit)
1108
1881
  args.push(`--limit=${params.limit}`);
@@ -1117,376 +1890,443 @@ function buildGitHubReposSearchCommand(params) {
1117
1890
  }
1118
1891
 
1119
1892
  const TOOL_NAME$5 = 'github_search_commits';
1120
- 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.`;
1121
- function registerSearchGitHubCommitsTool(server) {
1122
- server.tool(TOOL_NAME$5, DESCRIPTION$5, {
1123
- query: z
1124
- .string()
1125
- .optional()
1126
- .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.'),
1127
- // Basic filters
1128
- owner: z
1129
- .string()
1130
- .optional()
1131
- .describe('Repository owner/organization. Leave empty for global search.'),
1132
- repo: z
1133
- .string()
1134
- .optional()
1135
- .describe('Repository name. Do exploratory search without repo filter first'),
1136
- // Author filters
1137
- author: z.string().optional().describe('Filter by commit author'),
1138
- authorDate: z
1139
- .string()
1140
- .optional()
1141
- .describe('Filter by authored date (format: >2020-01-01, <2023-12-31)'),
1142
- authorEmail: z.string().optional().describe('Filter by author email'),
1143
- authorName: z.string().optional().describe('Filter by author name'),
1144
- // Committer filters
1145
- committer: z.string().optional().describe('Filter by committer'),
1146
- committerDate: z
1147
- .string()
1148
- .optional()
1149
- .describe('Filter by committed date (format: >2020-01-01, <2023-12-31)'),
1150
- committerEmail: z
1151
- .string()
1152
- .optional()
1153
- .describe('Filter by committer email'),
1154
- committerName: z.string().optional().describe('Filter by committer name'),
1155
- // Hash filters
1156
- hash: z.string().optional().describe('Filter by commit hash'),
1157
- parent: z.string().optional().describe('Filter by parent hash'),
1158
- tree: z.string().optional().describe('Filter by tree hash'),
1159
- // Boolean filters
1160
- merge: z.boolean().optional().describe('Filter merge commits'),
1161
- visibility: z
1162
- .enum(['public', 'private', 'internal'])
1163
- .optional()
1164
- .describe('Filter by repository visibility'),
1165
- // Sorting and limits
1166
- sort: z
1167
- .enum(['author-date', 'committer-date', 'best-match'])
1168
- .optional()
1169
- .default('best-match')
1170
- .describe('Sort criteria (default: best-match)'),
1171
- order: z
1172
- .enum(['asc', 'desc'])
1173
- .optional()
1174
- .default('desc')
1175
- .describe('Order (default: desc)'),
1176
- limit: z
1177
- .number()
1178
- .int()
1179
- .min(1)
1180
- .max(50)
1181
- .optional()
1182
- .default(25)
1183
- .describe('Maximum results (default: 25, max: 50)'),
1184
- }, {
1185
- title: TOOL_NAME$5,
1893
+ const DESCRIPTION$5 = `Search commit history effectively with GitHub's commit search. Use simple, specific terms for best results. Complex boolean queries may return limited results - try individual keywords instead.`;
1894
+ function registerGitHubSearchCommitsTool(server) {
1895
+ server.registerTool(TOOL_NAME$5, {
1186
1896
  description: DESCRIPTION$5,
1187
- readOnlyHint: true,
1188
- destructiveHint: false,
1189
- idempotentHint: true,
1190
- openWorldHint: true,
1897
+ inputSchema: {
1898
+ query: z
1899
+ .string()
1900
+ .optional()
1901
+ .describe('Search query with boolean logic. Boolean: "fix AND bug", exact phrases: "initial commit", advanced syntax: "author:john OR committer:jane".'),
1902
+ // Repository filters
1903
+ owner: z
1904
+ .string()
1905
+ .optional()
1906
+ .describe('Repository owner/organization. Leave empty for global search.'),
1907
+ repo: z
1908
+ .string()
1909
+ .optional()
1910
+ .describe('Repository name. Do exploratory search without repo filter first'),
1911
+ // Author filters
1912
+ author: z.string().optional().describe('Filter by commit author'),
1913
+ authorName: z.string().optional().describe('Filter by author name'),
1914
+ authorEmail: z.string().optional().describe('Filter by author email'),
1915
+ // Committer filters
1916
+ committer: z.string().optional().describe('Filter by committer'),
1917
+ committerName: z
1918
+ .string()
1919
+ .optional()
1920
+ .describe('Filter by committer name'),
1921
+ committerEmail: z
1922
+ .string()
1923
+ .optional()
1924
+ .describe('Filter by committer email'),
1925
+ // Date filters
1926
+ authorDate: z
1927
+ .string()
1928
+ .optional()
1929
+ .describe('Filter by authored date (format: >2020-01-01, <2023-12-31)'),
1930
+ committerDate: z
1931
+ .string()
1932
+ .optional()
1933
+ .describe('Filter by committed date (format: >2020-01-01, <2023-12-31)'),
1934
+ // Hash filters
1935
+ hash: z.string().optional().describe('Filter by commit hash'),
1936
+ parent: z.string().optional().describe('Filter by parent hash'),
1937
+ tree: z.string().optional().describe('Filter by tree hash'),
1938
+ // State filters
1939
+ merge: z.boolean().optional().describe('Filter merge commits'),
1940
+ // Visibility
1941
+ visibility: z
1942
+ .enum(['public', 'private', 'internal'])
1943
+ .optional()
1944
+ .describe('Filter by repository visibility'),
1945
+ // Pagination and sorting
1946
+ limit: z
1947
+ .number()
1948
+ .int()
1949
+ .min(1)
1950
+ .max(50)
1951
+ .optional()
1952
+ .default(25)
1953
+ .describe('Maximum results (default: 25, max: 50)'),
1954
+ sort: z
1955
+ .enum(['author-date', 'committer-date'])
1956
+ .optional()
1957
+ .describe('Sort criteria (default: relevance)'),
1958
+ order: z
1959
+ .enum(['asc', 'desc'])
1960
+ .optional()
1961
+ .default('desc')
1962
+ .describe('Order (default: desc)'),
1963
+ },
1964
+ annotations: {
1965
+ title: 'GitHub Commit Search',
1966
+ readOnlyHint: true,
1967
+ destructiveHint: false,
1968
+ idempotentHint: true,
1969
+ openWorldHint: true,
1970
+ },
1191
1971
  }, async (args) => {
1192
1972
  try {
1193
- // Query is optional - can search with just filters
1194
- if (!args.query?.trim() &&
1195
- !args.owner &&
1196
- !args.author &&
1197
- !args.committer &&
1198
- !args.repo) {
1199
- return createResult('Either query or at least one filter is required', true);
1200
- }
1201
1973
  const result = await searchGitHubCommits(args);
1202
- return result;
1974
+ if (result.isError) {
1975
+ return result;
1976
+ }
1977
+ const execResult = JSON.parse(result.content[0].text);
1978
+ const commits = JSON.parse(execResult.result);
1979
+ // GitHub CLI returns a direct array
1980
+ const items = Array.isArray(commits) ? commits : [];
1981
+ // Enhanced handling for no results - provide fallback suggestions
1982
+ if (items.length === 0) {
1983
+ return createResult({
1984
+ data: {
1985
+ commits: [],
1986
+ total_count: 0,
1987
+ cli_command: execResult.command,
1988
+ suggestions: {
1989
+ message: 'No commits found. GitHub commit search is limited compared to code/issue search.',
1990
+ fallback_strategies: [
1991
+ 'Try simpler, shorter queries (single keywords work better)',
1992
+ "Use broader terms like 'fix' instead of 'fix useState bug'",
1993
+ 'Search by author: add author filter for specific contributors',
1994
+ 'Use date ranges: add authorDate or committerDate filters',
1995
+ 'Try github_search_code tool for finding code patterns instead',
1996
+ ],
1997
+ alternative_queries: generateCommitSearchAlternatives(args.query),
1998
+ },
1999
+ },
2000
+ });
2001
+ }
2002
+ // Transform to optimized format
2003
+ const optimizedResult = transformCommitsToOptimizedFormat(items, args);
2004
+ return createResult({ data: optimizedResult });
1203
2005
  }
1204
2006
  catch (error) {
1205
- return createResult('Commit search failed - check query syntax, filters, or repository access', true);
2007
+ const errorMessage = error.message || '';
2008
+ if (errorMessage.includes('authentication')) {
2009
+ return createResult({
2010
+ error: 'GitHub authentication required - run api_status_check tool',
2011
+ });
2012
+ }
2013
+ if (errorMessage.includes('rate limit')) {
2014
+ return createResult({
2015
+ error: 'GitHub rate limit exceeded - try more specific filters',
2016
+ });
2017
+ }
2018
+ return createResult({
2019
+ error: 'Commit search failed',
2020
+ suggestions: [
2021
+ 'Check authentication with api_status_check',
2022
+ 'Use more specific date ranges or author filters',
2023
+ 'Try simpler boolean queries',
2024
+ ],
2025
+ });
1206
2026
  }
1207
2027
  });
1208
2028
  }
2029
+ /**
2030
+ * Transform GitHub CLI response to optimized format
2031
+ */
2032
+ function transformCommitsToOptimizedFormat(items, _params) {
2033
+ // Extract repository info if single repo search
2034
+ const singleRepo = extractSingleRepository(items);
2035
+ // Get unique authors for metadata
2036
+ const uniqueAuthors = new Set(items.map(item => item.commit?.author?.name || item.author?.login || 'Unknown')).size;
2037
+ const optimizedCommits = items
2038
+ .map(item => ({
2039
+ sha: item.sha,
2040
+ message: getCommitTitle(item.commit?.message || ''),
2041
+ author: item.commit?.author?.name || item.author?.login || 'Unknown',
2042
+ date: toDDMMYYYY(item.commit?.author?.date || ''),
2043
+ repository: singleRepo
2044
+ ? undefined
2045
+ : simplifyRepoUrl(item.repository?.url || ''),
2046
+ url: singleRepo
2047
+ ? item.sha
2048
+ : `${simplifyRepoUrl(item.repository?.url || '')}@${item.sha}`,
2049
+ }))
2050
+ .map(commit => {
2051
+ // Remove undefined fields
2052
+ const cleanCommit = {};
2053
+ Object.entries(commit).forEach(([key, value]) => {
2054
+ if (value !== undefined) {
2055
+ cleanCommit[key] = value;
2056
+ }
2057
+ });
2058
+ return cleanCommit;
2059
+ });
2060
+ const result = {
2061
+ commits: optimizedCommits,
2062
+ total_count: items.length,
2063
+ };
2064
+ // Add repository info if single repo
2065
+ if (singleRepo) {
2066
+ result.repository = {
2067
+ name: singleRepo.fullName,
2068
+ description: singleRepo.description,
2069
+ };
2070
+ }
2071
+ // Add metadata for insights
2072
+ if (items.length > 1) {
2073
+ result.metadata = {
2074
+ timeframe: getTimeframe(items),
2075
+ unique_authors: uniqueAuthors,
2076
+ };
2077
+ }
2078
+ return result;
2079
+ }
2080
+ /**
2081
+ * Extract single repository if all results are from same repo
2082
+ */
2083
+ function extractSingleRepository(items) {
2084
+ if (items.length === 0)
2085
+ return null;
2086
+ const firstRepo = items[0].repository;
2087
+ const allSameRepo = items.every(item => item.repository.fullName === firstRepo.fullName);
2088
+ return allSameRepo ? firstRepo : null;
2089
+ }
2090
+ /**
2091
+ * Calculate timeframe of commits
2092
+ */
2093
+ function getTimeframe(items) {
2094
+ if (items.length === 0)
2095
+ return '';
2096
+ const dates = items.map(item => new Date(item.commit?.author?.date || ''));
2097
+ const oldest = new Date(Math.min(...dates.map(d => d.getTime())));
2098
+ const newest = new Date(Math.max(...dates.map(d => d.getTime())));
2099
+ const diffMs = newest.getTime() - oldest.getTime();
2100
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
2101
+ if (diffDays === 0)
2102
+ return 'same day';
2103
+ if (diffDays < 7)
2104
+ return `${diffDays} days`;
2105
+ if (diffDays < 30)
2106
+ return `${Math.floor(diffDays / 7)} weeks`;
2107
+ if (diffDays < 365)
2108
+ return `${Math.floor(diffDays / 30)} months`;
2109
+ return `${Math.floor(diffDays / 365)} years`;
2110
+ }
2111
+ /**
2112
+ * Generate alternative commit search queries when original query fails
2113
+ */
2114
+ function generateCommitSearchAlternatives(originalQuery) {
2115
+ if (!originalQuery) {
2116
+ return [
2117
+ { query: 'fix', reason: 'Search for general fixes' },
2118
+ { query: 'bug', reason: 'Search for bug-related commits' },
2119
+ { query: 'refactor', reason: 'Search for refactoring commits' },
2120
+ ];
2121
+ }
2122
+ const alternatives = [];
2123
+ const query = originalQuery.toLowerCase();
2124
+ // Extract key terms and create simpler alternatives
2125
+ if (query.includes('fix') && query.includes('bug')) {
2126
+ alternatives.push({ query: 'fix', reason: 'Broader search for all fixes' }, { query: 'bug', reason: 'Search for bug-related commits' });
2127
+ }
2128
+ else if (query.includes(' ')) {
2129
+ // Multi-word query - suggest individual terms
2130
+ const words = query.split(' ').filter(w => w.length > 2);
2131
+ words.slice(0, 2).forEach(word => {
2132
+ alternatives.push({
2133
+ query: word,
2134
+ reason: `Single keyword search for '${word}'`,
2135
+ });
2136
+ });
2137
+ }
2138
+ // Always suggest some common commit patterns
2139
+ alternatives.push({ query: 'feat', reason: 'Search for feature commits' }, { query: 'docs', reason: 'Search for documentation updates' });
2140
+ return alternatives.slice(0, 4); // Limit to 4 suggestions
2141
+ }
1209
2142
  async function searchGitHubCommits(params) {
1210
2143
  const cacheKey = generateCacheKey('gh-commits', params);
1211
2144
  return withCache(cacheKey, async () => {
1212
2145
  try {
1213
- const { command, args } = buildGitHubCommitsSearchCommand(params);
1214
- const result = await executeGitHubCommand(command, args, {
2146
+ const args = buildGitHubCommitCliArgs(params);
2147
+ const result = await executeGitHubCommand('search', args, {
1215
2148
  cache: false,
1216
2149
  });
1217
- if (result.isError) {
1218
- return result;
1219
- }
1220
- // Extract the actual content from the exec result
1221
- const execResult = JSON.parse(result.content[0].text);
1222
- const rawContent = execResult.result;
1223
- // Parse JSON results and provide structured analysis
1224
- let commits = [];
1225
- const analysis = {
1226
- totalFound: 0,
1227
- recentCommits: 0,
1228
- topAuthors: [],
1229
- repositories: new Set(),
1230
- };
1231
- // Parse JSON response from GitHub CLI
1232
- commits = JSON.parse(rawContent);
1233
- if (Array.isArray(commits) && commits.length > 0) {
1234
- analysis.totalFound = commits.length;
1235
- // Simple analysis
1236
- const now = new Date();
1237
- const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
1238
- const authorCounts = {};
1239
- commits.forEach(commit => {
1240
- // Count recent commits
1241
- const commitDate = commit.commit?.author?.date || commit.commit?.committer?.date;
1242
- if (commitDate && new Date(commitDate) > thirtyDaysAgo) {
1243
- analysis.recentCommits++;
1244
- }
1245
- // Count authors
1246
- const authorName = commit.commit?.author?.name || commit.author?.login || 'Unknown';
1247
- authorCounts[authorName] = (authorCounts[authorName] || 0) + 1;
1248
- // Track repositories
1249
- if (commit.repository?.fullName) {
1250
- analysis.repositories.add(commit.repository.fullName);
1251
- }
2150
+ return result;
2151
+ }
2152
+ catch (error) {
2153
+ const errorMessage = error.message || '';
2154
+ if (errorMessage.includes('authentication')) {
2155
+ return createResult({
2156
+ error: 'GitHub authentication required',
1252
2157
  });
1253
- // Get top authors
1254
- analysis.topAuthors = Object.entries(authorCounts)
1255
- .sort(([, a], [, b]) => b - a)
1256
- .slice(0, 5)
1257
- .map(([name, count]) => ({ name, commits: count }));
1258
- // Format commits for output
1259
- const formattedCommits = commits.map(commit => ({
1260
- sha: commit.sha,
1261
- message: commit.commit?.message || '',
1262
- author: {
1263
- name: commit.commit?.author?.name,
1264
- email: commit.commit?.author?.email,
1265
- date: commit.commit?.author?.date,
1266
- login: commit.author?.login,
1267
- },
1268
- committer: {
1269
- name: commit.commit?.committer?.name,
1270
- email: commit.commit?.committer?.email,
1271
- date: commit.commit?.committer?.date,
1272
- login: commit.committer?.login,
1273
- },
1274
- repository: commit.repository
1275
- ? {
1276
- name: commit.repository.name,
1277
- fullName: commit.repository.fullName,
1278
- url: commit.repository.url,
1279
- description: commit.repository.description,
1280
- }
1281
- : null,
1282
- url: commit.url,
1283
- parents: commit.parents?.map((p) => p.sha) || [],
1284
- }));
1285
- return createSuccessResult$1({
1286
- query: params.query,
1287
- total: analysis.totalFound,
1288
- commits: formattedCommits,
1289
- summary: {
1290
- recentCommits: analysis.recentCommits,
1291
- topAuthors: analysis.topAuthors,
1292
- repositories: Array.from(analysis.repositories),
1293
- },
2158
+ }
2159
+ if (errorMessage.includes('rate limit')) {
2160
+ return createResult({
2161
+ error: 'GitHub rate limit exceeded',
1294
2162
  });
1295
2163
  }
1296
- return createSuccessResult$1({
1297
- query: params.query,
1298
- total: 0,
1299
- commits: [],
2164
+ return createResult({
2165
+ error: 'Commit search execution failed',
1300
2166
  });
1301
2167
  }
1302
- catch (error) {
1303
- return createErrorResult$1('GitHub commit search failed - verify repository exists or try different filters', error);
1304
- }
1305
2168
  });
1306
2169
  }
1307
- function buildGitHubCommitsSearchCommand(params) {
1308
- // Build query following GitHub CLI patterns
1309
- const query = params.query?.trim() || '';
1310
- // Handle complex queries (with qualifiers, operators, or --) differently
1311
- const hasComplexSyntax = query.includes('--') ||
1312
- query.includes(':') ||
1313
- query.includes('OR') ||
1314
- query.includes('AND') ||
1315
- query.includes('(') ||
1316
- query.includes(')') ||
1317
- query.startsWith('-');
2170
+ function buildGitHubCommitCliArgs(params) {
1318
2171
  const args = ['commits'];
1319
- // Only add query if it exists
1320
- if (query) {
1321
- if (hasComplexSyntax) {
1322
- // For complex queries with special syntax, handle carefully
1323
- const queryParts = query.match(/(?:[^\s"]+|"[^"]*")+/g) || [];
1324
- queryParts.forEach(part => {
1325
- // If part contains shell special characters, quote it
1326
- if (/[><=&|$`(){}[\];\\]/.test(part) && !part.includes('"')) {
1327
- args.push(`"${part}"`);
1328
- }
1329
- else {
1330
- args.push(part);
1331
- }
1332
- });
1333
- }
1334
- else {
1335
- // For simple queries, use quoting logic
1336
- const queryString = needsQuoting(query) ? `"${query}"` : query;
1337
- args.push(queryString);
1338
- }
2172
+ // Add query if provided - simplified approach for better results
2173
+ if (params.query) {
2174
+ // Simple, direct query handling - GitHub commit search works better with straightforward queries
2175
+ args.push(params.query.trim());
1339
2176
  }
1340
- // Add JSON output with commit fields
1341
- args.push('--json', 'author,commit,committer,id,parents,repository,sha,url');
1342
- // Add all filters
2177
+ // Repository filters
2178
+ if (params.owner && params.repo) {
2179
+ args.push(`--repo=${params.owner}/${params.repo}`);
2180
+ }
2181
+ else if (params.owner) {
2182
+ args.push(`--owner=${params.owner}`);
2183
+ }
2184
+ // Author filters
1343
2185
  if (params.author)
1344
2186
  args.push(`--author=${params.author}`);
1345
- if (params.authorDate)
1346
- args.push(`--author-date="${params.authorDate}"`);
2187
+ if (params.authorName)
2188
+ args.push(`--author-name=${params.authorName}`);
1347
2189
  if (params.authorEmail)
1348
2190
  args.push(`--author-email=${params.authorEmail}`);
1349
- if (params.authorName)
1350
- args.push(`--author-name="${params.authorName}"`);
2191
+ // Committer filters
1351
2192
  if (params.committer)
1352
2193
  args.push(`--committer=${params.committer}`);
1353
- if (params.committerDate)
1354
- args.push(`--committer-date="${params.committerDate}"`);
2194
+ if (params.committerName)
2195
+ args.push(`--committer-name=${params.committerName}`);
1355
2196
  if (params.committerEmail)
1356
2197
  args.push(`--committer-email=${params.committerEmail}`);
1357
- if (params.committerName)
1358
- args.push(`--committer-name="${params.committerName}"`);
2198
+ // Date filters
2199
+ if (params.authorDate)
2200
+ args.push(`--author-date=${params.authorDate}`);
2201
+ if (params.committerDate)
2202
+ args.push(`--committer-date=${params.committerDate}`);
2203
+ // Hash filters
1359
2204
  if (params.hash)
1360
2205
  args.push(`--hash=${params.hash}`);
1361
2206
  if (params.parent)
1362
2207
  args.push(`--parent=${params.parent}`);
1363
2208
  if (params.tree)
1364
2209
  args.push(`--tree=${params.tree}`);
1365
- if (params.merge)
1366
- args.push('--merge');
2210
+ // State filters
2211
+ if (params.merge !== undefined)
2212
+ args.push(`--merge=${params.merge}`);
2213
+ // Visibility
1367
2214
  if (params.visibility)
1368
2215
  args.push(`--visibility=${params.visibility}`);
1369
- // Handle repo and owner
1370
- if (params.repo && params.owner) {
1371
- args.push(`--repo=${params.owner}/${params.repo}`);
1372
- }
1373
- else if (params.repo) {
1374
- args.push(`--repo=${params.repo}`);
1375
- }
1376
- else if (params.owner) {
1377
- args.push(`--owner=${params.owner}`);
1378
- }
1379
- // Sorting
1380
- const sortBy = params.sort || 'best-match';
1381
- if (sortBy !== 'best-match') {
1382
- args.push(`--sort=${sortBy}`);
1383
- }
2216
+ // Sorting and pagination
2217
+ if (params.sort)
2218
+ args.push(`--sort=${params.sort}`);
1384
2219
  if (params.order)
1385
2220
  args.push(`--order=${params.order}`);
1386
- // Limit
1387
2221
  if (params.limit)
1388
2222
  args.push(`--limit=${params.limit}`);
1389
- return { command: 'search', args };
2223
+ // JSON output
2224
+ args.push('--json=sha,commit,author,committer,repository,url,parents');
2225
+ return args;
1390
2226
  }
1391
2227
 
1392
2228
  // TODO: add PR commeents. e.g, gh pr view <PR_NUMBER_OR_URL_OR_BRANCH> --comments
1393
2229
  const TOOL_NAME$4 = 'github_search_pull_requests';
1394
- const DESCRIPTION$4 = `Find pull requests and implementations with detailed metadata. Discover how features were implemented, code review patterns, and development workflows.
1395
-
1396
- SEARCH PATTERNS SUPPORTED:
1397
- • BOOLEAN OPERATORS: "fix AND bug" (both required), "refactor OR cleanup" (either term), "feature NOT draft" (excludes draft)
1398
- • EXACT PHRASES: "initial commit" (precise phrase matching)
1399
- • GITHUB QUALIFIERS: Built-in support for "is:merged", "review:approved", "base:main", etc.
1400
- • COMBINABLE: Mix search terms with filters for targeted PR discovery
1401
-
1402
- Filter by state, author, reviewer, or merge status for comprehensive development workflow analysis.`;
2230
+ const DESCRIPTION$4 = `Find pull requests and implementations with detailed metadata. Discover feature implementations, code review patterns, and development workflows.`;
1403
2231
  function registerSearchGitHubPullRequestsTool(server) {
1404
- server.tool(TOOL_NAME$4, DESCRIPTION$4, {
1405
- query: z
1406
- .string()
1407
- .min(1, 'Search query is required and cannot be empty')
1408
- .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.'),
1409
- owner: z.string().optional().describe('Repository owner/organization'),
1410
- repo: z.string().optional().describe('Repository name'),
1411
- author: z.string().optional().describe('Filter by pull request author'),
1412
- assignee: z.string().optional().describe('Filter by assignee'),
1413
- mentions: z.string().optional().describe('Filter by user mentions'),
1414
- commenter: z.string().optional().describe('Filter by comments by user'),
1415
- involves: z.string().optional().describe('Filter by user involvement'),
1416
- reviewedBy: z.string().optional().describe('Filter by user who reviewed'),
1417
- reviewRequested: z
1418
- .string()
1419
- .optional()
1420
- .describe('Filter by user or team requested to review'),
1421
- state: z.enum(['open', 'closed']).optional().describe('Filter by state'),
1422
- head: z.string().optional().describe('Filter by head branch name'),
1423
- base: z.string().optional().describe('Filter by base branch name'),
1424
- language: z.string().optional().describe('Filter by coding language'),
1425
- created: z
1426
- .string()
1427
- .optional()
1428
- .describe("Filter by created date (e.g., '>2022-01-01')"),
1429
- updated: z.string().optional().describe('Filter by last updated date'),
1430
- mergedAt: z.string().optional().describe('Filter by merged date'),
1431
- closed: z.string().optional().describe('Filter by closed date'),
1432
- draft: z.boolean().optional().describe('Filter by draft state'),
1433
- checks: z
1434
- .enum(['pending', 'success', 'failure'])
1435
- .optional()
1436
- .describe('Filter based on status of the checks'),
1437
- merged: z.boolean().optional().describe('Filter based on merged state'),
1438
- review: z
1439
- .enum(['none', 'required', 'approved', 'changes_requested'])
1440
- .optional()
1441
- .describe('Filter based on review status'),
1442
- limit: z
1443
- .number()
1444
- .int()
1445
- .min(1)
1446
- .max(50)
1447
- .optional()
1448
- .default(25)
1449
- .describe('Maximum results (default: 25, max: 50)'),
1450
- sort: z
1451
- .enum([
1452
- 'comments',
1453
- 'reactions',
1454
- 'reactions-+1',
1455
- 'reactions--1',
1456
- 'reactions-smile',
1457
- 'reactions-thinking_face',
1458
- 'reactions-heart',
1459
- 'reactions-tada',
1460
- 'interactions',
1461
- 'created',
1462
- 'updated',
1463
- ])
1464
- .optional()
1465
- .describe('Sort criteria'),
1466
- order: z
1467
- .enum(['asc', 'desc'])
1468
- .optional()
1469
- .default('desc')
1470
- .describe('Order (default: desc)'),
1471
- }, {
1472
- title: TOOL_NAME$4,
2232
+ server.registerTool(TOOL_NAME$4, {
1473
2233
  description: DESCRIPTION$4,
1474
- readOnlyHint: true,
1475
- destructiveHint: false,
1476
- idempotentHint: true,
1477
- openWorldHint: true,
2234
+ inputSchema: {
2235
+ query: z
2236
+ .string()
2237
+ .min(1, 'Search query is required and cannot be empty')
2238
+ .describe('Search query with GitHub syntax. Boolean: "fix AND bug", exact phrases: "initial commit", qualifiers: "is:merged review:approved".'),
2239
+ owner: z.string().optional().describe('Repository owner/organization'),
2240
+ repo: z.string().optional().describe('Repository name'),
2241
+ author: z.string().optional().describe('Filter by pull request author'),
2242
+ assignee: z.string().optional().describe('Filter by assignee'),
2243
+ mentions: z.string().optional().describe('Filter by user mentions'),
2244
+ commenter: z.string().optional().describe('Filter by comments by user'),
2245
+ involves: z.string().optional().describe('Filter by user involvement'),
2246
+ reviewedBy: z
2247
+ .string()
2248
+ .optional()
2249
+ .describe('Filter by user who reviewed'),
2250
+ reviewRequested: z
2251
+ .string()
2252
+ .optional()
2253
+ .describe('Filter by user or team requested to review'),
2254
+ state: z
2255
+ .enum(['open', 'closed'])
2256
+ .optional()
2257
+ .describe('Filter by state'),
2258
+ head: z.string().optional().describe('Filter by head branch name'),
2259
+ base: z.string().optional().describe('Filter by base branch name'),
2260
+ language: z.string().optional().describe('Filter by coding language'),
2261
+ created: z.string().optional().describe('Filter by created date'),
2262
+ updated: z.string().optional().describe('Filter by last updated date'),
2263
+ mergedAt: z.string().optional().describe('Filter by merged date'),
2264
+ closed: z.string().optional().describe('Filter by closed date'),
2265
+ draft: z.boolean().optional().describe('Filter by draft state'),
2266
+ checks: z
2267
+ .enum(['pending', 'success', 'failure'])
2268
+ .optional()
2269
+ .describe('Filter based on status of the checks'),
2270
+ merged: z.boolean().optional().describe('Filter based on merged state'),
2271
+ review: z
2272
+ .enum(['none', 'required', 'approved', 'changes_requested'])
2273
+ .optional()
2274
+ .describe('Filter based on review status'),
2275
+ limit: z
2276
+ .number()
2277
+ .int()
2278
+ .min(1)
2279
+ .max(50)
2280
+ .optional()
2281
+ .default(25)
2282
+ .describe('Maximum results (default: 25, max: 50)'),
2283
+ sort: z
2284
+ .enum([
2285
+ 'comments',
2286
+ 'reactions',
2287
+ 'reactions-+1',
2288
+ 'reactions--1',
2289
+ 'reactions-smile',
2290
+ 'reactions-thinking_face',
2291
+ 'reactions-heart',
2292
+ 'reactions-tada',
2293
+ 'interactions',
2294
+ 'created',
2295
+ 'updated',
2296
+ ])
2297
+ .optional()
2298
+ .describe('Sort criteria'),
2299
+ order: z
2300
+ .enum(['asc', 'desc'])
2301
+ .optional()
2302
+ .default('desc')
2303
+ .describe('Order (default: desc)'),
2304
+ },
2305
+ annotations: {
2306
+ title: 'GitHub Pull Requests Search',
2307
+ readOnlyHint: true,
2308
+ destructiveHint: false,
2309
+ idempotentHint: true,
2310
+ openWorldHint: true,
2311
+ },
1478
2312
  }, async (args) => {
1479
2313
  if (!args.query?.trim()) {
1480
- return createErrorResult$1('Search query is required and cannot be empty - provide keywords to search for pull requests', new Error('Invalid query'));
2314
+ return createResult({
2315
+ error: 'Search query is required and cannot be empty - provide keywords to search for pull requests',
2316
+ });
1481
2317
  }
1482
2318
  if (args.query.length > 256) {
1483
- return createErrorResult$1('Search query is too long. Please limit to 256 characters or less - simplify your search terms', new Error('Query too long'));
2319
+ return createResult({
2320
+ error: 'Search query is too long. Please limit to 256 characters or less - simplify your search terms',
2321
+ });
1484
2322
  }
1485
2323
  try {
1486
2324
  return await searchGitHubPullRequests(args);
1487
2325
  }
1488
2326
  catch (error) {
1489
- return createErrorResult$1('GitHub pull requests search failed - verify repository access and query syntax', error);
2327
+ return createResult({
2328
+ error: 'GitHub pull requests search failed - verify repository access and query syntax',
2329
+ });
1490
2330
  }
1491
2331
  });
1492
2332
  }
@@ -1509,8 +2349,8 @@ async function searchGitHubPullRequests(params) {
1509
2349
  author: pr.user?.login || '',
1510
2350
  repository: pr.repository_url?.split('/').slice(-2).join('/') || 'unknown',
1511
2351
  labels: pr.labels?.map(l => l.name) || [],
1512
- created_at: pr.created_at,
1513
- updated_at: pr.updated_at,
2352
+ created_at: toDDMMYYYY(pr.created_at),
2353
+ updated_at: toDDMMYYYY(pr.updated_at),
1514
2354
  url: pr.html_url,
1515
2355
  comments: pr.comments,
1516
2356
  reactions: pr.reactions?.total_count || 0,
@@ -1520,7 +2360,7 @@ async function searchGitHubPullRequests(params) {
1520
2360
  if (pr.merged_at)
1521
2361
  result.merged_at = pr.merged_at;
1522
2362
  if (pr.closed_at)
1523
- result.closed_at = pr.closed_at;
2363
+ result.closed_at = toDDMMYYYY(pr.closed_at);
1524
2364
  if (pr.head?.ref)
1525
2365
  result.head = pr.head.ref;
1526
2366
  if (pr.base?.ref)
@@ -1528,15 +2368,13 @@ async function searchGitHubPullRequests(params) {
1528
2368
  return result;
1529
2369
  });
1530
2370
  const searchResult = {
1531
- searchType: 'prs',
1532
- query: params.query || '',
1533
2371
  results: cleanPRs,
2372
+ total_count: apiResponse.total_count || cleanPRs.length,
1534
2373
  metadata: {
1535
- total_count: apiResponse.total_count || 0,
1536
2374
  incomplete_results: apiResponse.incomplete_results || false,
1537
2375
  },
1538
2376
  };
1539
- return createSuccessResult$1(searchResult);
2377
+ return createResult({ data: searchResult });
1540
2378
  });
1541
2379
  }
1542
2380
  function buildGitHubPullRequestsAPICommand(params) {
@@ -1597,36 +2435,32 @@ function buildGitHubPullRequestsAPICommand(params) {
1597
2435
  }
1598
2436
 
1599
2437
  const TOOL_NAME$3 = 'npm_package_search';
1600
- const DESCRIPTION$3 = `Search npm packages by keywords using fuzzy matching.
1601
-
1602
- IMPORTANT LIMITATIONS:
1603
- • NO BOOLEAN OPERATORS: NPM search does NOT support AND/OR/NOT - use space-separated keywords for broader search
1604
- • FUZZY MATCHING ONLY: No exact phrase matching - searches are approximate keyword matching
1605
- • KEYWORD-BASED: Best results with simple, space-separated terms like "react hooks" or "cli typescript"
1606
-
1607
- 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.`;
2438
+ const DESCRIPTION$3 = `Search npm packages by keywords using fuzzy matching. Use space-separated keywords like "react hooks" or "cli typescript". No boolean operators supported.`;
1608
2439
  const MAX_DESCRIPTION_LENGTH = 100;
1609
2440
  const MAX_KEYWORDS = 10;
1610
2441
  function registerNpmSearchTool(server) {
1611
- server.tool(TOOL_NAME$3, DESCRIPTION$3, {
1612
- queries: z
1613
- .union([z.string(), z.array(z.string())])
1614
- .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.'),
1615
- searchlimit: z
1616
- .number()
1617
- .int()
1618
- .min(1)
1619
- .max(50)
1620
- .optional()
1621
- .default(20)
1622
- .describe('Max results per query (default: 20, max: 50)'),
1623
- }, {
1624
- title: TOOL_NAME$3,
2442
+ server.registerTool(TOOL_NAME$3, {
1625
2443
  description: DESCRIPTION$3,
1626
- readOnlyHint: true,
1627
- destructiveHint: false,
1628
- idempotentHint: true,
1629
- openWorldHint: true,
2444
+ inputSchema: {
2445
+ queries: z
2446
+ .union([z.string(), z.array(z.string())])
2447
+ .describe('Package names or keywords to search for. Use simple space-separated keywords like "react hooks" or "typescript cli" for fuzzy matching.'),
2448
+ searchlimit: z
2449
+ .number()
2450
+ .int()
2451
+ .min(1)
2452
+ .max(50)
2453
+ .optional()
2454
+ .default(20)
2455
+ .describe('Max results per query (default: 20, max: 50)'),
2456
+ },
2457
+ annotations: {
2458
+ title: 'NPM Package Search',
2459
+ readOnlyHint: true,
2460
+ destructiveHint: false,
2461
+ idempotentHint: true,
2462
+ openWorldHint: true,
2463
+ },
1630
2464
  }, async (args) => {
1631
2465
  try {
1632
2466
  const queries = Array.isArray(args.queries)
@@ -1645,17 +2479,22 @@ function registerNpmSearchTool(server) {
1645
2479
  const deduplicatedPackages = deduplicatePackages(allPackages);
1646
2480
  if (deduplicatedPackages.length > 0) {
1647
2481
  return createResult({
1648
- query: Array.isArray(args.queries)
1649
- ? args.queries.join(', ')
1650
- : args.queries,
1651
- total: deduplicatedPackages.length,
1652
- results: deduplicatedPackages,
2482
+ data: {
2483
+ total_count: deduplicatedPackages.length,
2484
+ results: deduplicatedPackages,
2485
+ },
1653
2486
  });
1654
2487
  }
1655
- return createResult('No packages found', true);
2488
+ return createResult({
2489
+ error: 'No packages found',
2490
+ cli_command: `npm search ${Array.isArray(args.queries) ? args.queries.join(' ') : args.queries}`,
2491
+ });
1656
2492
  }
1657
2493
  catch (error) {
1658
- return createResult('NPM package search failed - check search terms or try different keywords', true);
2494
+ return createResult({
2495
+ error: 'Package search failed - check terms or try different keywords',
2496
+ cli_command: `npm search ${Array.isArray(args.queries) ? args.queries.join(' ') : args.queries}`,
2497
+ });
1659
2498
  }
1660
2499
  });
1661
2500
  }
@@ -1708,70 +2547,54 @@ function parseNpmSearchOutput(output) {
1708
2547
  }
1709
2548
 
1710
2549
  const TOOL_NAME$2 = 'github_get_contents';
1711
- 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.`;
2550
+ const DESCRIPTION$2 = `Browse repository structure and verify file existence. Smart branch detection with fallbacks. Use before fetching files to understand organization.`;
1712
2551
  function registerViewRepositoryStructureTool(server) {
1713
- server.tool(TOOL_NAME$2, DESCRIPTION$2, {
1714
- owner: z
1715
- .string()
1716
- .min(1)
1717
- .max(100)
1718
- .regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/, 'Invalid GitHub username/org format')
1719
- .describe(`Repository owner/organization (e.g., 'microsoft', 'facebook'). Use github_search_repositories to discover valid owners.`),
1720
- repo: z
1721
- .string()
1722
- .min(1)
1723
- .max(100)
1724
- .regex(/^[a-zA-Z0-9._-]+$/, 'Invalid repository name format')
1725
- .describe('Repository name (e.g., "vscode", "react"). Case-sensitive. Use github_search_repositories to find exact names.'),
1726
- branch: z
1727
- .string()
1728
- .min(1)
1729
- .max(255)
1730
- .regex(/^[^\s]+$/, 'Branch name cannot contain spaces')
1731
- .describe("Target branch name (e.g., 'main', 'canary', 'develop'). " +
1732
- 'Auto-detects repository default if not found. ' +
1733
- 'Use github_search_repositories or api calls to discover valid branches first.'),
1734
- path: z
1735
- .string()
1736
- .optional()
1737
- .default('')
1738
- .refine(path => !path.includes('..'), 'Path traversal not allowed')
1739
- .refine(path => path.length <= 500, 'Path too long')
1740
- .describe('Directory path within repository (e.g., "src/components", "packages/core"). ' +
1741
- 'Leave empty for root. Use previous results to navigate deeper.'),
1742
- }, {
1743
- title: TOOL_NAME$2,
2552
+ server.registerTool(TOOL_NAME$2, {
1744
2553
  description: DESCRIPTION$2,
1745
- readOnlyHint: true,
1746
- destructiveHint: false,
1747
- idempotentHint: true,
1748
- openWorldHint: true,
2554
+ inputSchema: {
2555
+ owner: z
2556
+ .string()
2557
+ .min(1)
2558
+ .max(100)
2559
+ .regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/, 'Invalid GitHub username/org format')
2560
+ .describe(`Repository owner/organization.`),
2561
+ repo: z
2562
+ .string()
2563
+ .min(1)
2564
+ .max(100)
2565
+ .regex(/^[a-zA-Z0-9._-]+$/, 'Invalid repository name format')
2566
+ .describe('Repository name. Case-sensitive.'),
2567
+ branch: z
2568
+ .string()
2569
+ .min(1)
2570
+ .max(255)
2571
+ .regex(/^[^\s]+$/, 'Branch name cannot contain spaces')
2572
+ .describe('Target branch name. Auto-detects default if not found.'),
2573
+ path: z
2574
+ .string()
2575
+ .optional()
2576
+ .default('')
2577
+ .refine(path => !path.includes('..'), 'Path traversal not allowed')
2578
+ .refine(path => path.length <= 500, 'Path too long')
2579
+ .describe('Directory path within repository. Leave empty for root.'),
2580
+ },
2581
+ annotations: {
2582
+ title: 'GitHub Repository Contents',
2583
+ readOnlyHint: true,
2584
+ destructiveHint: false,
2585
+ idempotentHint: true,
2586
+ openWorldHint: true,
2587
+ },
1749
2588
  }, async (args) => {
1750
2589
  try {
1751
2590
  const result = await viewRepositoryStructure(args);
1752
- if (result.isError) {
1753
- return createResult(result.content[0].text, true);
1754
- }
1755
- if (result.content && result.content[0] && !result.isError) {
1756
- const { data, parsed } = parseJsonResponse(result.content[0].text);
1757
- if (parsed) {
1758
- const typedResult = {
1759
- path: data.path,
1760
- baseUrl: data.baseUrl,
1761
- files: data.files || [],
1762
- folders: data.folders || [],
1763
- ...(data.branchFallback && {
1764
- branchFallback: data.branchFallback,
1765
- }),
1766
- };
1767
- return createResult(typedResult);
1768
- }
1769
- }
1770
2591
  return result;
1771
2592
  }
1772
2593
  catch (error) {
1773
2594
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1774
- return createResult(`Repository exploration failed: ${errorMessage} - verify repository exists and is accessible`, true);
2595
+ return createResult({
2596
+ error: `Repository exploration failed: ${errorMessage} - verify access and permissions`,
2597
+ });
1775
2598
  }
1776
2599
  });
1777
2600
  }
@@ -1784,6 +2607,7 @@ function registerViewRepositoryStructureTool(server) {
1784
2607
  * - Input validation: prevents path traversal and validates GitHub naming
1785
2608
  * - Clear error context: provides descriptive error messages
1786
2609
  * - Efficient caching: avoids redundant API calls
2610
+ * - Rich metadata: includes all GitHub API fields (sha, urls, links, etc.)
1787
2611
  */
1788
2612
  async function viewRepositoryStructure(params) {
1789
2613
  const cacheKey = generateCacheKey('gh-repo-structure', params);
@@ -1825,17 +2649,25 @@ async function viewRepositoryStructure(params) {
1825
2649
  const errorMsg = lastError?.message || 'Unknown error';
1826
2650
  if (errorMsg.includes('404') || errorMsg.includes('Not Found')) {
1827
2651
  if (path) {
1828
- throw new Error(`Path "${path}" not found - verify path exists or use github_search_code to find files`);
2652
+ return createResult({
2653
+ error: `Path "${path}" not found - verify path or use code search`,
2654
+ });
1829
2655
  }
1830
2656
  else {
1831
- throw new Error(`Repository not found: ${owner}/${repo} - verify owner/repo names or use github_search_repositories`);
2657
+ return createResult({
2658
+ error: `Repository not found: ${owner}/${repo} - verify names`,
2659
+ });
1832
2660
  }
1833
2661
  }
1834
2662
  else if (errorMsg.includes('403') || errorMsg.includes('Forbidden')) {
1835
- throw new Error(`Access denied to repository ${owner}/${repo} - repository may be private or require authentication`);
2663
+ return createResult({
2664
+ error: `Access denied to ${owner}/${repo} - check permissions`,
2665
+ });
1836
2666
  }
1837
2667
  else {
1838
- throw new Error(`Access failed: ${owner}/${repo} - check connection or repository permissions`);
2668
+ return createResult({
2669
+ error: `Access failed: ${owner}/${repo} - check connection`,
2670
+ });
1839
2671
  }
1840
2672
  }
1841
2673
  // Limit total items to 100 for efficiency
@@ -1847,53 +2679,54 @@ async function viewRepositoryStructure(params) {
1847
2679
  }
1848
2680
  return a.name.localeCompare(b.name);
1849
2681
  });
1850
- // Create base URL for GitHub API - construct it reliably from the parameters
1851
- const baseUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${cleanPath ? cleanPath + '/' : ''}`;
1852
- // Transform to lean structure with simplified paths and URLs
2682
+ // Create simplified, token-efficient structure
1853
2683
  const files = limitedItems
1854
2684
  .filter(item => item.type === 'file')
1855
- .map(item => {
1856
- // Simplify path by removing redundant prefix
1857
- let simplifiedPath = item.name; // Use just the filename for files in current directory
1858
- // If we're in a subdirectory and the item path is longer than just the name,
1859
- // show relative path from current directory
1860
- if (cleanPath && item.path.startsWith(cleanPath + '/')) {
1861
- simplifiedPath = item.path.substring(cleanPath.length + 1);
1862
- }
1863
- else if (!cleanPath) {
1864
- // At root level, use the full path but without leading slash
1865
- simplifiedPath = item.path;
1866
- }
1867
- // Extract just the filename and query params from the URL
1868
- const urlParts = item.url.split('/');
1869
- const filename = urlParts[urlParts.length - 1]; // Gets "filename?ref=branch"
1870
- return {
1871
- name: simplifiedPath,
1872
- size: item.size,
1873
- url: filename, // Just the filename and query params
1874
- };
1875
- });
2685
+ .map(item => ({
2686
+ name: item.name,
2687
+ size: item.size,
2688
+ url: item.path, // Use path for fetching
2689
+ }));
1876
2690
  const folders = limitedItems
1877
2691
  .filter(item => item.type === 'dir')
1878
- .map(item => `${item.name}/`);
2692
+ .map(item => ({
2693
+ name: item.name,
2694
+ url: item.path, // Use path for browsing
2695
+ }));
2696
+ // Simplified result structure - token efficient
1879
2697
  const result = {
1880
- path: `${owner}/${repo}${path ? `/${path}` : ''}`,
1881
- baseUrl: `${baseUrl}?ref=${usedBranch}`, // Include complete base URL with branch
1882
- files,
1883
- folders,
1884
- ...(usedBranch !== branch && {
1885
- branchFallback: {
1886
- requested: branch,
1887
- used: usedBranch,
1888
- message: `Used '${usedBranch}' instead of '${branch}'`,
2698
+ repository: `${owner}/${repo}`,
2699
+ branch: usedBranch,
2700
+ path: cleanPath || '/',
2701
+ githubBasePath: `https://api.github.com/repos/${owner}/${repo}/contents/`,
2702
+ files: {
2703
+ count: files.length,
2704
+ files: files,
2705
+ },
2706
+ folders: {
2707
+ count: folders.length,
2708
+ folders: folders,
2709
+ },
2710
+ ...((usedBranch !== branch || limitedItems.length === 100) && {
2711
+ metadata: {
2712
+ ...(usedBranch !== branch && {
2713
+ branchFallback: {
2714
+ requested: branch,
2715
+ used: usedBranch,
2716
+ },
2717
+ }),
2718
+ ...(limitedItems.length === 100 && {
2719
+ truncated: true,
2720
+ }),
1889
2721
  },
1890
2722
  }),
1891
2723
  };
1892
- return createSuccessResult$1(result);
2724
+ return createResult({ data: result });
1893
2725
  }
1894
2726
  catch (error) {
1895
- const errorMessage = error instanceof Error ? error.message : String(error);
1896
- return createErrorResult$1('Repository access failed - verify repository exists and check authentication', new Error(errorMessage));
2727
+ return createResult({
2728
+ error: `Repository access failed - verify repository and authentication: ${error}`,
2729
+ });
1897
2730
  }
1898
2731
  });
1899
2732
  }
@@ -1930,133 +2763,152 @@ async function getSmartBranchFallback(owner, repo, requestedBranch) {
1930
2763
  }
1931
2764
 
1932
2765
  const TOOL_NAME$1 = 'github_search_issues';
1933
- const DESCRIPTION$1 = `Find GitHub issues and problems with rich metadata (labels, reactions, comments, state). Discover pain points, feature requests, bug patterns, and community discussions.
1934
-
1935
- SEARCH PATTERNS SUPPORTED:
1936
- • BOOLEAN OPERATORS: "bug AND crash" (both required), "feature OR enhancement" (either term), "error NOT test" (excludes test)
1937
- • EXACT PHRASES: "memory leak" (precise phrase matching)
1938
- • GITHUB QUALIFIERS: Built-in support for "is:open", "label:bug", "author:username", etc.
1939
- • COMBINABLE: Mix search terms with filters for surgical precision
1940
-
1941
- Filter by state, labels, assignee, or date ranges for comprehensive issue discovery.`;
2766
+ const DESCRIPTION$1 = `Find GitHub issues with rich metadata. Discover pain points, feature requests, and bug patterns with boolean logic and GitHub qualifiers.`;
1942
2767
  function registerSearchGitHubIssuesTool(server) {
1943
- server.tool(TOOL_NAME$1, DESCRIPTION$1, {
1944
- query: z
1945
- .string()
1946
- .min(1, 'Search query is required and cannot be empty')
1947
- .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.'),
1948
- owner: z
1949
- .string()
1950
- .min(1)
1951
- .optional()
1952
- .describe('Repository owner/organization. Leave empty for global search.'),
1953
- repo: z
1954
- .string()
1955
- .optional()
1956
- .describe('Repository name. Do exploratory search without repo filter first'),
1957
- app: z.string().optional().describe('Filter by GitHub App author'),
1958
- archived: z
1959
- .boolean()
1960
- .optional()
1961
- .describe('Filter by repository archived state'),
1962
- assignee: z.string().optional().describe('Filter by assignee'),
1963
- author: z.string().optional().describe('Filter by issue author'),
1964
- closed: z.string().optional().describe('Filter by closed date'),
1965
- commenter: z.string().optional().describe('Filter by user who commented'),
1966
- comments: z.number().optional().describe('Filter by number of comments'),
1967
- created: z
1968
- .string()
1969
- .optional()
1970
- .describe("Filter by created date (e.g., '>2022-01-01')"),
1971
- includePrs: z
1972
- .boolean()
1973
- .optional()
1974
- .describe('Include pull requests in results'),
1975
- interactions: z
1976
- .number()
1977
- .optional()
1978
- .describe('Filter by reactions and comments count'),
1979
- involves: z.string().optional().describe('Filter by user involvement'),
1980
- labels: z.string().optional().describe('Filter by labels'),
1981
- language: z.string().optional().describe('Filter by coding language'),
1982
- locked: z
1983
- .boolean()
1984
- .optional()
1985
- .describe('Filter by locked conversation status'),
1986
- match: z
1987
- .enum(['title', 'body', 'comments'])
1988
- .optional()
1989
- .describe('Restrict search to specific field'),
1990
- mentions: z.string().optional().describe('Filter by user mentions'),
1991
- milestone: z.string().optional().describe('Filter by milestone title'),
1992
- noAssignee: z.boolean().optional().describe('Filter by missing assignee'),
1993
- noLabel: z.boolean().optional().describe('Filter by missing label'),
1994
- noMilestone: z
1995
- .boolean()
1996
- .optional()
1997
- .describe('Filter by missing milestone'),
1998
- noProject: z.boolean().optional().describe('Filter by missing project'),
1999
- project: z.string().optional().describe('Filter by project board'),
2000
- reactions: z.number().optional().describe('Filter by reactions count'),
2001
- state: z
2002
- .enum(['open', 'closed'])
2003
- .optional()
2004
- .describe('Filter by issue state'),
2005
- teamMentions: z.string().optional().describe('Filter by team mentions'),
2006
- updated: z.string().optional().describe('Filter by last updated date'),
2007
- visibility: z
2008
- .enum(['public', 'private', 'internal'])
2009
- .optional()
2010
- .describe('Filter by repository visibility'),
2011
- sort: z
2012
- .enum([
2013
- 'comments',
2014
- 'created',
2015
- 'interactions',
2016
- 'reactions',
2017
- 'reactions-+1',
2018
- 'reactions--1',
2019
- 'reactions-heart',
2020
- 'reactions-smile',
2021
- 'reactions-tada',
2022
- 'reactions-thinking_face',
2023
- 'updated',
2024
- 'best-match',
2025
- ])
2026
- .optional()
2027
- .describe('Sort criteria'),
2028
- order: z
2029
- .enum(['asc', 'desc'])
2030
- .optional()
2031
- .default('desc')
2032
- .describe('Order (default: desc)'),
2033
- limit: z
2034
- .number()
2035
- .int()
2036
- .min(1)
2037
- .max(50)
2038
- .optional()
2039
- .default(25)
2040
- .describe('Maximum results (default: 25, max: 50)'),
2041
- }, {
2042
- title: TOOL_NAME$1,
2768
+ server.registerTool(TOOL_NAME$1, {
2043
2769
  description: DESCRIPTION$1,
2044
- readOnlyHint: true,
2045
- destructiveHint: false,
2046
- idempotentHint: true,
2047
- openWorldHint: true,
2770
+ inputSchema: {
2771
+ query: z
2772
+ .string()
2773
+ .min(1, 'Search query is required and cannot be empty')
2774
+ .describe('Search query with GitHub syntax. Boolean: "bug AND crash", exact phrases: "memory leak", qualifiers: "is:open label:bug".'),
2775
+ owner: z
2776
+ .string()
2777
+ .min(1)
2778
+ .optional()
2779
+ .describe('Repository owner/organization. Leave empty for global search.'),
2780
+ repo: z
2781
+ .string()
2782
+ .optional()
2783
+ .describe('Repository name. Do exploratory search without repo filter first'),
2784
+ app: z.string().optional().describe('Filter by GitHub App author'),
2785
+ archived: z
2786
+ .boolean()
2787
+ .optional()
2788
+ .describe('Filter by repository archived state'),
2789
+ assignee: z.string().optional().describe('Filter by assignee'),
2790
+ author: z.string().optional().describe('Filter by issue author'),
2791
+ closed: z.string().optional().describe('Filter by closed date'),
2792
+ commenter: z
2793
+ .string()
2794
+ .optional()
2795
+ .describe('Filter by user who commented'),
2796
+ comments: z
2797
+ .number()
2798
+ .optional()
2799
+ .describe('Filter by number of comments'),
2800
+ created: z.string().optional().describe('Filter by created date'),
2801
+ includePrs: z
2802
+ .boolean()
2803
+ .optional()
2804
+ .describe('Include pull requests in results'),
2805
+ interactions: z
2806
+ .number()
2807
+ .optional()
2808
+ .describe('Filter by reactions and comments count'),
2809
+ involves: z.string().optional().describe('Filter by user involvement'),
2810
+ labels: z.string().optional().describe('Filter by labels'),
2811
+ language: z.string().optional().describe('Filter by coding language'),
2812
+ locked: z
2813
+ .boolean()
2814
+ .optional()
2815
+ .describe('Filter by locked conversation status'),
2816
+ match: z
2817
+ .enum(['title', 'body', 'comments'])
2818
+ .optional()
2819
+ .describe('Restrict search to specific field'),
2820
+ mentions: z.string().optional().describe('Filter by user mentions'),
2821
+ milestone: z.string().optional().describe('Filter by milestone title'),
2822
+ noAssignee: z
2823
+ .boolean()
2824
+ .optional()
2825
+ .describe('Filter by missing assignee'),
2826
+ noLabel: z.boolean().optional().describe('Filter by missing label'),
2827
+ noMilestone: z
2828
+ .boolean()
2829
+ .optional()
2830
+ .describe('Filter by missing milestone'),
2831
+ noProject: z.boolean().optional().describe('Filter by missing project'),
2832
+ project: z.string().optional().describe('Filter by project board'),
2833
+ reactions: z.number().optional().describe('Filter by reactions count'),
2834
+ state: z
2835
+ .enum(['open', 'closed'])
2836
+ .optional()
2837
+ .describe('Filter by issue state'),
2838
+ teamMentions: z.string().optional().describe('Filter by team mentions'),
2839
+ updated: z.string().optional().describe('Filter by last updated date'),
2840
+ visibility: z
2841
+ .enum(['public', 'private', 'internal'])
2842
+ .optional()
2843
+ .describe('Filter by repository visibility'),
2844
+ sort: z
2845
+ .enum([
2846
+ 'comments',
2847
+ 'created',
2848
+ 'interactions',
2849
+ 'reactions',
2850
+ 'reactions-+1',
2851
+ 'reactions--1',
2852
+ 'reactions-heart',
2853
+ 'reactions-smile',
2854
+ 'reactions-tada',
2855
+ 'reactions-thinking_face',
2856
+ 'updated',
2857
+ 'best-match',
2858
+ ])
2859
+ .optional()
2860
+ .describe('Sort criteria'),
2861
+ order: z
2862
+ .enum(['asc', 'desc'])
2863
+ .optional()
2864
+ .default('desc')
2865
+ .describe('Order (default: desc)'),
2866
+ limit: z
2867
+ .number()
2868
+ .int()
2869
+ .min(1)
2870
+ .max(50)
2871
+ .optional()
2872
+ .default(25)
2873
+ .describe('Maximum results (default: 25, max: 50)'),
2874
+ },
2875
+ annotations: {
2876
+ title: 'GitHub Issues Search',
2877
+ readOnlyHint: true,
2878
+ destructiveHint: false,
2879
+ idempotentHint: true,
2880
+ openWorldHint: true,
2881
+ },
2048
2882
  }, async (args) => {
2049
2883
  if (!args.query?.trim()) {
2050
- return createErrorResult$1('Search query is required and cannot be empty - provide keywords to search for issues', new Error('Invalid query'));
2884
+ return createResult({
2885
+ error: 'Search query is required and cannot be empty - provide keywords to search for issues',
2886
+ });
2051
2887
  }
2052
2888
  if (args.query.length > 256) {
2053
- return createErrorResult$1('Search query is too long. Please limit to 256 characters or less - simplify your search terms', new Error('Query too long'));
2889
+ return createResult({
2890
+ error: 'Search query is too long. Please limit to 256 characters or less - simplify your search terms',
2891
+ });
2054
2892
  }
2055
2893
  try {
2056
2894
  return await searchGitHubIssues(args);
2057
2895
  }
2058
2896
  catch (error) {
2059
- return createErrorResult$1('GitHub issues search failed - check repository exists and query is valid', error);
2897
+ const errorMessage = error instanceof Error ? error.message : '';
2898
+ if (errorMessage.includes('authentication')) {
2899
+ return createResult({
2900
+ error: 'GitHub authentication required - run api_status_check tool',
2901
+ });
2902
+ }
2903
+ if (errorMessage.includes('rate limit')) {
2904
+ return createResult({
2905
+ error: 'GitHub rate limit exceeded - wait or use specific filters',
2906
+ });
2907
+ }
2908
+ // Generic fallback
2909
+ return createResult({
2910
+ error: 'GitHub issue search failed - check authentication or simplify query',
2911
+ });
2060
2912
  }
2061
2913
  });
2062
2914
  }
@@ -2078,34 +2930,55 @@ async function searchGitHubIssues(params) {
2078
2930
  author: issue.user?.login || '',
2079
2931
  repository: issue.repository_url?.split('/').slice(-2).join('/') || 'unknown',
2080
2932
  labels: issue.labels?.map(l => l.name) || [],
2081
- created_at: issue.created_at,
2082
- updated_at: issue.updated_at,
2933
+ created_at: toDDMMYYYY(issue.created_at),
2934
+ updated_at: toDDMMYYYY(issue.updated_at),
2083
2935
  url: issue.html_url,
2084
2936
  comments: issue.comments,
2085
2937
  reactions: issue.reactions?.total_count || 0,
2086
2938
  }));
2087
2939
  const searchResult = {
2088
- searchType: 'issues',
2089
- query: params.query || '',
2090
2940
  results: cleanIssues,
2941
+ total_count: apiResponse.total_count || cleanIssues.length,
2091
2942
  metadata: {
2092
- total_count: apiResponse.total_count || 0,
2093
2943
  incomplete_results: apiResponse.incomplete_results || false,
2094
2944
  },
2095
2945
  };
2096
- return createSuccessResult$1(searchResult);
2946
+ return createResult({ data: searchResult });
2097
2947
  });
2098
2948
  }
2099
2949
  function buildGitHubIssuesAPICommand(params) {
2100
- const queryParts = [params.query?.trim() || ''];
2101
- // Repository/organization qualifiers
2950
+ const queryParts = [];
2951
+ // Start with the base query, but filter out qualifiers that will be added separately
2952
+ const baseQuery = params.query?.trim() || '';
2953
+ // Extract and remove qualifiers from the main query to avoid conflicts
2954
+ const qualifierPatterns = [
2955
+ /\bis:(open|closed)\b/gi,
2956
+ /\blabel:("[^"]*"|[^\s]+)/gi,
2957
+ /\bcreated:([^\s]+)/gi,
2958
+ /\bupdated:([^\s]+)/gi,
2959
+ /\bauthor:([^\s]+)/gi,
2960
+ /\bassignee:([^\s]+)/gi,
2961
+ /\bstate:(open|closed)/gi,
2962
+ /\brepo:([^\s]+)/gi,
2963
+ /\borg:([^\s]+)/gi,
2964
+ ];
2965
+ // Remove extracted qualifiers from base query
2966
+ let cleanQuery = baseQuery;
2967
+ qualifierPatterns.forEach(pattern => {
2968
+ cleanQuery = cleanQuery.replace(pattern, '').trim();
2969
+ });
2970
+ // Add the cleaned query if it has content
2971
+ if (cleanQuery) {
2972
+ queryParts.push(cleanQuery);
2973
+ }
2974
+ // Repository/organization qualifiers - prioritize function params over query
2102
2975
  if (params.owner && params.repo) {
2103
2976
  queryParts.push(`repo:${params.owner}/${params.repo}`);
2104
2977
  }
2105
2978
  else if (params.owner) {
2106
2979
  queryParts.push(`org:${params.owner}`);
2107
2980
  }
2108
- // Build search qualifiers from params
2981
+ // Build search qualifiers from function parameters (these take precedence)
2109
2982
  const qualifiers = {
2110
2983
  author: params.author,
2111
2984
  assignee: params.assignee,
@@ -2122,9 +2995,10 @@ function buildGitHubIssuesAPICommand(params) {
2122
2995
  if (value)
2123
2996
  queryParts.push(`${key}:${value}`);
2124
2997
  });
2125
- // Special qualifiers
2126
- if (params.labels)
2998
+ // Special qualifiers - handle labels carefully
2999
+ if (params.labels) {
2127
3000
  queryParts.push(`label:"${params.labels}"`);
3001
+ }
2128
3002
  if (params.milestone)
2129
3003
  queryParts.push(`milestone:"${params.milestone}"`);
2130
3004
  if (params.noAssignee)
@@ -2139,6 +3013,26 @@ function buildGitHubIssuesAPICommand(params) {
2139
3013
  queryParts.push('is:locked');
2140
3014
  if (params.visibility)
2141
3015
  queryParts.push(`is:${params.visibility}`);
3016
+ // Extract qualifiers from original query and add them if not already set by params
3017
+ if (baseQuery.includes('is:') && !params.state) {
3018
+ const isMatch = baseQuery.match(/\bis:(open|closed)\b/i);
3019
+ if (isMatch && !queryParts.some(part => part.startsWith('state:'))) {
3020
+ queryParts.push(`state:${isMatch[1].toLowerCase()}`);
3021
+ }
3022
+ }
3023
+ if (baseQuery.includes('label:') && !params.labels) {
3024
+ const labelMatch = baseQuery.match(/\blabel:("[^"]*"|[^\s]+)/i);
3025
+ if (labelMatch) {
3026
+ const labelValue = labelMatch[1].replace(/"/g, '');
3027
+ queryParts.push(`label:"${labelValue}"`);
3028
+ }
3029
+ }
3030
+ if (baseQuery.includes('created:') && !params.created) {
3031
+ const createdMatch = baseQuery.match(/\bcreated:([^\s]+)/i);
3032
+ if (createdMatch) {
3033
+ queryParts.push(`created:${createdMatch[1]}`);
3034
+ }
3035
+ }
2142
3036
  const query = queryParts.filter(Boolean).join(' ');
2143
3037
  const limit = Math.min(params.limit || 25, 100);
2144
3038
  let apiPath = `search/issues?q=${encodeURIComponent(query)}&per_page=${limit}`;
@@ -2150,95 +3044,160 @@ function buildGitHubIssuesAPICommand(params) {
2150
3044
  }
2151
3045
 
2152
3046
  const TOOL_NAME = 'npm_view_package';
2153
- const DESCRIPTION = `use npm view to get package metadata. You can get the package
2154
- name from search results or user input. This tool is effieicnt since it gets important package metadata
2155
- on a package fast to optimize tools calls. get package git repository path easily without need to search it (using repo/code search tools),
2156
- exported files of a package are there, along with dependencies and version history.`;
3047
+ const DESCRIPTION = `Get comprehensive NPM package metadata efficiently. Returns repository URL, exports, dependencies, and version history. Essential for finding package source code and understanding project structure.`;
2157
3048
  function registerNpmViewPackageTool(server) {
2158
- server.tool(TOOL_NAME, DESCRIPTION, {
2159
- packageName: z
2160
- .string()
2161
- .min(1, 'Package name is required')
2162
- .describe('NPM package name to analyze. Returns complete package context including exports (critical for GitHub file discovery), repository URL, dependencies, and version history.'),
2163
- }, {
2164
- title: TOOL_NAME,
3049
+ server.registerTool(TOOL_NAME, {
2165
3050
  description: DESCRIPTION,
2166
- readOnlyHint: true,
2167
- destructiveHint: false,
2168
- idempotentHint: true,
2169
- openWorldHint: true,
3051
+ inputSchema: {
3052
+ packageName: z
3053
+ .string()
3054
+ .min(1)
3055
+ .describe('NPM package name to analyze. Returns complete package context including exports (critical for GitHub file discovery), repository URL, dependencies, and version history.'),
3056
+ },
3057
+ annotations: {
3058
+ title: 'NPM Package Metadata',
3059
+ readOnlyHint: true,
3060
+ destructiveHint: false,
3061
+ idempotentHint: true,
3062
+ openWorldHint: true,
3063
+ },
2170
3064
  }, async (args) => {
2171
3065
  try {
2172
- if (!args.packageName || args.packageName.trim() === '') {
2173
- return createResult('Package name is required - provide a valid NPM package name', true);
2174
- }
2175
- // Basic package name validation
2176
- if (!/^[a-z0-9@._/-]+$/.test(args.packageName)) {
2177
- return createResult('Invalid package name format - use standard NPM naming (e.g., "package-name" or "@scope/package")', true);
3066
+ const result = await viewNpmPackage(args.packageName);
3067
+ if (result.isError) {
3068
+ return result;
2178
3069
  }
2179
- const result = await npmViewPackage(args.packageName);
2180
- return result;
3070
+ const execResult = JSON.parse(result.content[0].text);
3071
+ const packageData = JSON.parse(execResult.result);
3072
+ // Transform to optimized format
3073
+ const optimizedResult = transformToOptimizedFormat(packageData);
3074
+ return createResult({ data: optimizedResult });
2181
3075
  }
2182
3076
  catch (error) {
2183
- return createResult('Failed to get package metadata - verify package exists on NPM registry', true);
3077
+ const errorMessage = error.message || '';
3078
+ if (errorMessage.includes('not found')) {
3079
+ return createResult({
3080
+ error: 'Package not found - verify package name spelling',
3081
+ cli_command: `npm view ${args.packageName} --json`,
3082
+ });
3083
+ }
3084
+ if (errorMessage.includes('network')) {
3085
+ return createResult({
3086
+ error: 'Network error - check internet connection',
3087
+ cli_command: `npm view ${args.packageName} --json`,
3088
+ });
3089
+ }
3090
+ return createResult({
3091
+ error: 'NPM package lookup failed',
3092
+ cli_command: `npm view ${args.packageName} --json`,
3093
+ suggestions: [
3094
+ 'Verify package name is correct',
3095
+ 'Check if package exists on npmjs.com',
3096
+ 'Try again in a moment',
3097
+ ],
3098
+ });
2184
3099
  }
2185
3100
  });
2186
3101
  }
2187
- // Helper function to process versions
2188
- function processVersions(time) {
2189
- const semanticVersionRegex = /^\d+\.\d+\.\d+$/;
2190
- const versions = Object.entries(time || {})
2191
- .filter(([key]) => key !== 'created' && key !== 'modified')
2192
- .filter(([version]) => semanticVersionRegex.test(version))
2193
- .sort(([, a], [, b]) => new Date(b).getTime() - new Date(a).getTime());
2194
- return {
2195
- recent: versions
2196
- .slice(0, 10)
2197
- .map(([version, releaseDate]) => ({ version, releaseDate })),
3102
+ /**
3103
+ * Transform NPM CLI response to optimized format
3104
+ */
3105
+ function transformToOptimizedFormat(packageData) {
3106
+ // Extract repository URL and simplify
3107
+ const repoUrl = packageData.repository?.url || packageData.repositoryGitUrl || '';
3108
+ const repository = repoUrl ? simplifyRepoUrl(repoUrl) : '';
3109
+ // Simplify exports to essential entry points only
3110
+ const exports = packageData.exports
3111
+ ? simplifyExports(packageData.exports)
3112
+ : undefined;
3113
+ // Get version timestamps from time object and limit to last 5
3114
+ const timeData = packageData.time || {};
3115
+ const versionList = packageData.versions || [];
3116
+ const recentVersions = versionList.slice(-5).map((version) => ({
3117
+ version,
3118
+ date: timeData[version] ? toDDMMYYYY(timeData[version]) : 'Unknown',
3119
+ }));
3120
+ const result = {
3121
+ name: packageData.name,
3122
+ version: packageData.version,
3123
+ description: packageData.description || '',
3124
+ license: packageData.license || 'Unknown',
3125
+ repository,
3126
+ size: humanizeBytes(packageData.dist?.unpackedSize || 0),
3127
+ created: timeData.created ? toDDMMYYYY(timeData.created) : 'Unknown',
3128
+ updated: timeData.modified ? toDDMMYYYY(timeData.modified) : 'Unknown',
3129
+ versions: recentVersions,
2198
3130
  stats: {
2199
- total: Object.keys(time || {}).length - 2, // exclude 'created' and 'modified'
2200
- official: versions.length,
3131
+ total_versions: versionList.length,
3132
+ weekly_downloads: packageData.weeklyDownloads,
2201
3133
  },
2202
3134
  };
3135
+ // Add exports only if they exist and are useful
3136
+ if (exports && Object.keys(exports).length > 0) {
3137
+ result.exports = exports;
3138
+ }
3139
+ return result;
3140
+ }
3141
+ /**
3142
+ * Simplify exports object to essential entry points
3143
+ */
3144
+ function simplifyExports(exports) {
3145
+ if (typeof exports === 'string') {
3146
+ return { main: exports };
3147
+ }
3148
+ if (typeof exports === 'object') {
3149
+ const simplified = {};
3150
+ // Extract main entry point
3151
+ if (exports['.']) {
3152
+ const mainExport = exports['.'];
3153
+ if (typeof mainExport === 'string') {
3154
+ simplified.main = mainExport;
3155
+ }
3156
+ else if (mainExport.default) {
3157
+ simplified.main = mainExport.default;
3158
+ }
3159
+ else if (mainExport.import) {
3160
+ simplified.main = mainExport.import;
3161
+ }
3162
+ }
3163
+ // Extract types if available
3164
+ if (exports['./types'] || exports['.']?.types) {
3165
+ simplified.types = exports['./types'] || exports['.'].types;
3166
+ }
3167
+ // Add a few other important exports (max 3 total)
3168
+ let count = 0;
3169
+ for (const [key, value] of Object.entries(exports)) {
3170
+ if (count >= 3 || key === '.' || key === './types')
3171
+ continue;
3172
+ if (key.includes('package.json') || key.includes('node_modules'))
3173
+ continue;
3174
+ simplified[key] =
3175
+ typeof value === 'object' ? value.default || value : value;
3176
+ count++;
3177
+ }
3178
+ return simplified;
3179
+ }
3180
+ return { main: 'index.js' };
2203
3181
  }
2204
- async function npmViewPackage(packageName) {
2205
- const cacheKey = generateCacheKey('npm-view-package', { packageName });
3182
+ async function viewNpmPackage(packageName) {
3183
+ const cacheKey = generateCacheKey('npm-view', { packageName });
2206
3184
  return withCache(cacheKey, async () => {
2207
3185
  try {
2208
3186
  const result = await executeNpmCommand('view', [packageName, '--json'], {
2209
- cache: true,
3187
+ cache: false,
2210
3188
  });
2211
- if (result.isError) {
2212
- return result;
2213
- }
2214
- // Parse the result from the executed command
2215
- const commandOutput = JSON.parse(result.content[0].text);
2216
- const npmData = JSON.parse(commandOutput.result);
2217
- // Process versions
2218
- const versionData = processVersions(npmData.time);
2219
- // Extract registry URL from tarball
2220
- const registryUrl = npmData.dist?.tarball?.match(/^(https?:\/\/[^/]+)/)?.[1] || '';
2221
- // Build result
2222
- const viewResult = {
2223
- name: npmData.name,
2224
- latest: npmData['dist-tags']?.latest || '',
2225
- license: npmData.license || '',
2226
- timeCreated: npmData.time?.created || '',
2227
- timeModified: npmData.time?.modified || '',
2228
- repositoryGitUrl: npmData.repository?.url || '',
2229
- registryUrl,
2230
- description: npmData.description || '',
2231
- size: npmData.dist?.unpackedSize || 0,
2232
- dependencies: npmData.dependencies || {},
2233
- devDependencies: npmData.devDependencies || {},
2234
- exports: npmData.exports || {},
2235
- versions: versionData.recent,
2236
- versionStats: versionData.stats,
2237
- };
2238
- return createSuccessResult$1(viewResult);
3189
+ return result;
2239
3190
  }
2240
3191
  catch (error) {
2241
- return createErrorResult$1('Failed to get npm package metadata - package may not exist or registry unavailable', error);
3192
+ const errorMessage = error.message || '';
3193
+ if (errorMessage.includes('404')) {
3194
+ return createResult({
3195
+ error: 'Package not found on NPM registry',
3196
+ });
3197
+ }
3198
+ return createResult({
3199
+ error: 'NPM command execution failed',
3200
+ });
2242
3201
  }
2243
3202
  });
2244
3203
  }
@@ -2258,7 +3217,7 @@ function registerAllTools(server) {
2258
3217
  fn: registerFetchGitHubFileContentTool,
2259
3218
  },
2260
3219
  { name: 'SearchGitHubRepos', fn: registerSearchGitHubReposTool },
2261
- { name: 'SearchGitHubCommits', fn: registerSearchGitHubCommitsTool },
3220
+ { name: 'SearchGitHubCommits', fn: registerGitHubSearchCommitsTool },
2262
3221
  {
2263
3222
  name: 'SearchGitHubPullRequests',
2264
3223
  fn: registerSearchGitHubPullRequestsTool,