octocode-mcp 2.3.11 → 2.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/build/index.js +270 -244
  2. package/package.json +1 -1
package/build/index.js CHANGED
@@ -144,29 +144,6 @@ function escapeShellArg(arg, shellType, isGitHubQuery // Flag to indicate if thi
144
144
  const isWindows = platform() === 'win32';
145
145
  shellType = isWindows ? 'cmd' : 'unix';
146
146
  }
147
- // Special handling for GitHub search queries to preserve AND logic
148
- if (isGitHubQuery) {
149
- // If the argument already contains quotes, preserve them for exact phrases
150
- if (arg.includes('"')) {
151
- // For Unix-like shells, wrap the entire argument in single quotes
152
- if (shellType === 'unix') {
153
- return `'${arg.replace(/'/g, "'\"'\"'")}'`;
154
- }
155
- // For Windows CMD
156
- if (shellType === 'cmd') {
157
- return `"${arg.replace(/"/g, '""')}"`;
158
- }
159
- // For PowerShell
160
- return `'${arg.replace(/'/g, "''")}'`;
161
- }
162
- // For space-separated terms (AND search), minimize escaping
163
- if (arg.includes(' ') && shellType === 'unix') {
164
- // Only escape if contains dangerous shell characters
165
- if (!/[;&|<>$`\\]/.test(arg)) {
166
- return `"${arg}"`;
167
- }
168
- }
169
- }
170
147
  switch (shellType) {
171
148
  case 'powershell':
172
149
  return escapePowerShellArg(arg);
@@ -174,7 +151,7 @@ function escapeShellArg(arg, shellType, isGitHubQuery // Flag to indicate if thi
174
151
  return escapeWindowsCmdArg(arg);
175
152
  case 'unix':
176
153
  default:
177
- return escapeUnixShellArg(arg, isGitHubQuery);
154
+ return escapeUnixShellArg(arg);
178
155
  }
179
156
  }
180
157
  /**
@@ -205,30 +182,10 @@ function escapeWindowsCmdArg(arg) {
205
182
  * Preserves AND search logic by not over-escaping space-separated terms
206
183
  */
207
184
  function escapeUnixShellArg(arg, isGitHubQuery) {
208
- // For GitHub search queries, we need to preserve AND logic and quoted phrases
209
- if (isGitHubQuery) {
210
- // If the query contains quotes, we need to preserve them for GitHub CLI
211
- // but escape the entire argument for the shell
212
- if (arg.includes('"')) {
213
- // Use single quotes to wrap the entire query while preserving internal quotes
214
- // This allows GitHub CLI to see: "quoted phrase" other terms
215
- return `'${arg.replace(/'/g, "'\"'\"'")}'`;
216
- }
217
- // For space-separated terms (AND search), only escape if absolutely necessary
218
- // GitHub CLI expects space-separated terms for AND logic
219
- if (arg.includes(' ') && !/[;&|<>$`\\]/.test(arg)) {
220
- // Only wrap in quotes if it contains shell metacharacters beyond spaces
221
- return `"${arg}"`;
222
- }
223
- // For single terms or terms with special chars, escape normally
224
- if (/[;&|<>$`\\]/.test(arg)) {
225
- return `'${arg.replace(/'/g, "'\"'\"'")}'`;
226
- }
227
- // Simple terms don't need escaping
228
- return arg;
229
- }
230
185
  // Standard Unix shell escaping for other arguments
231
- if (/[^a-zA-Z0-9\-_./=@:]/.test(arg)) {
186
+ // Only escape if contains dangerous shell metacharacters
187
+ // Allow common safe characters: alphanumeric, dash, underscore, dot, slash, equals, at, colon, comma
188
+ if (/[;&|<>$`\\*?()[\]{}^~]/.test(arg)) {
232
189
  return `'${arg.replace(/'/g, "'\"'\"'")}'`;
233
190
  }
234
191
  return arg;
@@ -245,7 +202,28 @@ async function executeNpmCommand(command, args = [], options = {}) {
245
202
  // Get shell configuration
246
203
  const shellConfig = getShellConfig(options.windowsShell);
247
204
  // Build command with validated prefix and properly escaped arguments
248
- const escapedArgs = args.map(arg => escapeShellArg(arg, shellConfig.type));
205
+ // NPM commands need minimal escaping - most arguments are package names or CLI flags
206
+ const escapedArgs = args.map(arg => {
207
+ const isCliFlag = arg.startsWith('--');
208
+ // CLI flags like --searchlimit=20, --json need minimal escaping
209
+ if (isCliFlag) {
210
+ // Only escape CLI flags if they contain dangerous shell characters
211
+ if (/[;&|<>$`\\]/.test(arg)) {
212
+ return escapeShellArg(arg, shellConfig.type);
213
+ }
214
+ return arg;
215
+ }
216
+ // Package names and search terms need minimal escaping
217
+ // Only escape if contains shell metacharacters that could be dangerous
218
+ if (/[;&|<>$`\\*?[\]{}]/.test(arg)) {
219
+ return escapeShellArg(arg, shellConfig.type);
220
+ }
221
+ // For arguments with spaces, use minimal quoting
222
+ if (/\s/.test(arg)) {
223
+ return `"${arg}"`;
224
+ }
225
+ return arg;
226
+ });
249
227
  const fullCommand = `npm ${command} ${escapedArgs.join(' ')}`;
250
228
  const executeNpmCommand = () => executeCommand(fullCommand, 'npm', options, shellConfig);
251
229
  if (options.cache) {
@@ -269,33 +247,40 @@ async function executeGitHubCommand(command, args = [], options = {}) {
269
247
  // Get shell configuration
270
248
  const shellConfig = getShellConfig(options.windowsShell);
271
249
  // Build command with validated prefix and properly escaped arguments
272
- // For GitHub search commands, we need to distinguish between:
273
- // 1. Main query (index 1) - needs special escaping for AND logic
274
- // 2. CLI flags (--flag=value) - standard escaping
275
- // 3. Search qualifiers (key:value) - minimal escaping
250
+ // For GitHub search commands, we need minimal escaping to avoid interfering with GitHub CLI
276
251
  const escapedArgs = args.map((arg, index) => {
277
252
  const isMainQueryArgument = command === 'search' && index === 1;
278
253
  const isCliFlag = arg.startsWith('--');
279
- const isGitHubQualifier = command === 'search' &&
280
- index > 1 &&
281
- !isCliFlag &&
282
- (arg.includes(':') || arg.startsWith('('));
283
- // CLI flags like --language=javascript, --repo=owner/repo need standard escaping
254
+ // CLI flags like --language=javascript, --repo=owner/repo need minimal escaping
284
255
  if (isCliFlag) {
285
- return escapeShellArg(arg, shellConfig.type, false);
256
+ // Only escape CLI flags if they contain dangerous shell characters
257
+ if (/[;&|<>$`\\*?[\]{}]/.test(arg)) {
258
+ return escapeShellArg(arg, shellConfig.type);
259
+ }
260
+ return arg;
286
261
  }
287
- // GitHub search qualifiers need special handling
288
- // Most qualifiers can be passed as-is, but those with shell metacharacters need escaping
289
- if (isGitHubQualifier) {
290
- // Check if the qualifier contains shell metacharacters that need escaping
291
- if (/[<>&|;`$\\]/.test(arg)) {
292
- // Escape qualifiers that contain shell metacharacters like size:<1000, size:>500
293
- return escapeShellArg(arg, shellConfig.type, false);
262
+ // For search queries, only escape if absolutely necessary for shell safety
263
+ if (isMainQueryArgument) {
264
+ // Only escape if the argument contains shell metacharacters that could be dangerous
265
+ if (/[;&|<>$`\\*?[\]{}]/.test(arg)) {
266
+ return escapeShellArg(arg, shellConfig.type);
267
+ }
268
+ // For simple queries with spaces or special chars, use minimal quoting
269
+ if (/\s/.test(arg)) {
270
+ return `"${arg}"`;
294
271
  }
295
- // Safe qualifiers like "language:typescript", "user:microsoft" can be passed as-is
296
272
  return arg;
297
273
  }
298
- return escapeShellArg(arg, shellConfig.type, isMainQueryArgument);
274
+ // For other arguments, use minimal escaping
275
+ // Only escape if contains shell metacharacters that could be dangerous
276
+ if (/[;&|<>$`\\*?[\]{}]/.test(arg)) {
277
+ return escapeShellArg(arg, shellConfig.type);
278
+ }
279
+ // For arguments with spaces, use minimal quoting
280
+ if (/\s/.test(arg)) {
281
+ return `"${arg}"`;
282
+ }
283
+ return arg;
299
284
  });
300
285
  const fullCommand = `gh ${command} ${escapedArgs.join(' ')}`;
301
286
  const executeGhCommand = () => executeCommand(fullCommand, 'github', options, shellConfig);
@@ -944,23 +929,10 @@ function createToolSuggestion(currentTool, suggestedTools) {
944
929
  }
945
930
 
946
931
  const API_STATUS_CHECK_TOOL_NAME = 'apiStatusCheck';
947
- const DESCRIPTION$9 = `Verify API connections and discover available organizations. FIRST STEP for private repository research.
948
-
949
- AUTHENTICATION VERIFICATION:
950
- - Check GitHub and NPM CLI authentication status
951
- - Troubleshoot access issues before extensive searches
952
- - Verify API connectivity and permissions
953
-
954
- ORGANIZATION DISCOVERY:
955
- - List available GitHub organizations for scoped searches
956
- - Identify accessible private repositories
957
- - Guide repository search strategy based on permissions
958
-
959
- WORKFLOW OPTIMIZATION:
960
- - Run first when dealing with private/organizational repositories
961
- - Prevents access errors in subsequent tool usage
962
- - Informs search scope and strategy decisions
963
- - Essential for comprehensive organizational research`;
932
+ const DESCRIPTION$9 = `initial tool to verify user connections
933
+
934
+ - Github: check gh login status and list available organizations.
935
+ - Npm: check npm login status and list npm registry.`;
964
936
  // Helper function to parse execution results with proper typing
965
937
  function parseExecResult(result) {
966
938
  if (!result.isError && result.content?.[0]?.text) {
@@ -1119,69 +1091,64 @@ const DESCRIPTION$8 = `Search code across GitHub repositories using GitHub's cod
1119
1091
 
1120
1092
  SEARCH STRATEGY FOR BEST RESULTS:
1121
1093
 
1122
- TERM OPTIMIZATION (IMPORTANT):
1123
- - BEST: Single terms for maximum coverage and relevance
1124
- - GOOD: 2 terms when you need both to be present
1125
- - RESTRICTIVE: 3+ terms - very specific but may miss relevant results
1126
- - Example: "useState" (best coverage) vs "react hook useState" (specific but restrictive)
1094
+ EXACT vs TERMS (Choose ONE):
1095
+ - exactQuery: Use for exact phrase matching
1096
+ - queryTerms: Use minimal words for broader coverage
1127
1097
 
1128
- MULTI-SEARCH STRATEGY:
1129
- - Use SEPARATE searches for different aspects instead of complex single queries
1130
- - Example: Search "authentication" separately, then "login" separately
1131
- - Separate searches provide broader coverage than restrictive AND logic
1098
+ TERM OPTIMIZATION:
1099
+ - BEST: Single terms for maximum coverage
1100
+ - GOOD: 2-3 minimal terms
1101
+ - AVOID: Long phrases in queryTerms
1132
1102
 
1133
- Search Logic:
1134
- - Multiple terms = ALL must be present (AND logic) - very restrictive
1135
- - Single terms = maximum results and coverage
1136
- - Quoted phrases = exact phrase matching
1137
- - Mixed queries: "exact phrase" additional_term (phrase + term)
1138
-
1139
- Quote Usage:
1140
- - Single terms: NO quotes (useState, authentication)
1141
- - Multi-word phrases: WITH quotes ("error handling", "user authentication")
1142
- - Mixed queries: "exact phrase" single_term another_term
1103
+ MULTI-SEARCH STRATEGY:
1104
+ - Use separate searches for different aspects
1105
+ - Separate searches provide broader coverage than complex queries
1143
1106
 
1144
1107
  Filter Usage:
1145
- - All filters use GitHub CLI flags (--language, --owner, --repo, etc.)
1146
- - Combine filters to narrow scope: language + owner, repo + filename
1147
- - Never use filters on exploratory searches - use to refine when too many results`;
1108
+ - Use filters to narrow scope: language, owner, repo, filename
1109
+ - Combine filters strategically: language + owner for organization-wide searches
1110
+ - Never use filters on exploratory searches - use to refine results`;
1148
1111
  function registerGitHubSearchCodeTool(server) {
1149
1112
  server.registerTool(GITHUB_SEARCH_CODE_TOOL_NAME, {
1150
1113
  description: DESCRIPTION$8,
1151
1114
  inputSchema: {
1152
- query: z
1115
+ exactQuery: z
1153
1116
  .string()
1154
- .min(1)
1155
- .describe('Search query with AND logic between terms. OPTIMIZATION: Single terms give best coverage, 2 terms when both needed, 3+ terms very restrictive. Multiple words require ALL to be present. Use quotes for exact phrases. Examples: "useState" (best coverage), "error handling" (exact phrase), "react hook useState" (specific but restrictive).'),
1117
+ .optional()
1118
+ .describe('Exact phrase/word to search for'),
1119
+ queryTerms: z
1120
+ .array(z.string())
1121
+ .optional()
1122
+ .describe('Array of search terms (AND logic in files). Use minimal words for broader coverage'),
1156
1123
  language: z
1157
1124
  .string()
1158
1125
  .optional()
1159
- .describe('Programming language filter. Uses --language CLI flag. Narrows search to specific language files. Use for language-specific searches.'),
1126
+ .describe('Programming language filter. Narrows search to specific language files.'),
1160
1127
  owner: z
1161
1128
  .union([z.string(), z.array(z.string())])
1162
1129
  .optional()
1163
- .describe('Repository owner/organization name(s) to search within (e.g., "facebook", ["google", "microsoft"]). Uses --owner CLI flag for organization-wide search. Can be combined with repo parameter for specific repository search. Do NOT use owner/repo format - just the organization/username.'),
1130
+ .describe('Repository owner/organization name(s). Organization-wide search.'),
1164
1131
  repo: z
1165
1132
  .union([z.string(), z.array(z.string())])
1166
1133
  .optional()
1167
- .describe('Filter on specific repository(ies). Uses --repo CLI flag. Two usage patterns: (1) Use with owner parameter - provide just repo name (e.g., owner="facebook", repo="react" → --repo=facebook/react), or (2) Use alone - provide full "owner/repo" format (e.g., "facebook/react" → --repo=facebook/react).'),
1134
+ .describe('Repository filter. Use with owner or full format.'),
1168
1135
  filename: z
1169
1136
  .string()
1170
1137
  .optional()
1171
- .describe('Target specific filename or pattern. Uses --filename CLI flag. Use for file-specific searches.'),
1138
+ .describe('Target specific filename or pattern.'),
1172
1139
  extension: z
1173
1140
  .string()
1174
1141
  .optional()
1175
- .describe('File extension filter. Uses --extension CLI flag. Alternative to language parameter.'),
1142
+ .describe('File extension filter. Alternative to language parameter.'),
1176
1143
  match: z
1177
1144
  .enum(['file', 'path'])
1178
1145
  .optional()
1179
- .describe('Search scope: "file" for file content (default), "path" for filenames/paths. Uses --match CLI flag. Single value only - multiple scopes not supported by GitHub CLI.'),
1146
+ .describe('Search scope: file for file content (default), path for filenames/paths.'),
1180
1147
  size: z
1181
1148
  .string()
1182
- .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid size format. Use: ">10", ">=5", "<100", "<=50", "10..100", or exact number "50"')
1149
+ .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid size format. Use: ">N", ">=N", "<N", "<=N", "N..M", or exact number')
1183
1150
  .optional()
1184
- .describe('File size filter in KB. Uses --size CLI flag. Format: ">N" (larger than), "<N" (smaller than), "N..M" (range), "N" (exact).'),
1151
+ .describe('File size filter in KB. Format: ">N" (larger), "<N" (smaller), "N..M" (range).'),
1185
1152
  limit: z
1186
1153
  .number()
1187
1154
  .int()
@@ -1189,7 +1156,7 @@ function registerGitHubSearchCodeTool(server) {
1189
1156
  .max(100)
1190
1157
  .optional()
1191
1158
  .default(30)
1192
- .describe('Maximum number of results to return (1-100). Default: 30. Higher values may increase response time.'),
1159
+ .describe('Maximum number of results to return (1-100).'),
1193
1160
  },
1194
1161
  annotations: {
1195
1162
  title: 'GitHub Code Search - Smart & Efficient',
@@ -1199,6 +1166,19 @@ function registerGitHubSearchCodeTool(server) {
1199
1166
  openWorldHint: true,
1200
1167
  },
1201
1168
  }, async (args) => {
1169
+ // Validate that exactly one search parameter is provided (not both)
1170
+ const hasExactQuery = !!args.exactQuery;
1171
+ const hasQueryTerms = args.queryTerms && args.queryTerms.length > 0;
1172
+ if (!hasExactQuery && !hasQueryTerms) {
1173
+ return createResult({
1174
+ error: 'One search parameter required: exactQuery OR queryTerms',
1175
+ });
1176
+ }
1177
+ if (hasExactQuery && hasQueryTerms) {
1178
+ return createResult({
1179
+ error: 'Use either exactQuery OR queryTerms, not both. Choose one search approach.',
1180
+ });
1181
+ }
1202
1182
  try {
1203
1183
  const result = await searchGitHubCode(args);
1204
1184
  if (result.isError) {
@@ -1354,26 +1334,31 @@ function extractSingleRepository$1(items) {
1354
1334
  }
1355
1335
  /**
1356
1336
  * Build command line arguments for GitHub CLI following the exact CLI format.
1357
- * Uses proper flags (--flag=value) instead of qualifiers where appropriate.
1337
+ * Uses proper flags (--flag=value) for filters and direct query terms.
1358
1338
  */
1359
1339
  function buildGitHubCliArgs(params) {
1360
1340
  const args = ['code'];
1361
- // Parse query to preserve quoted phrases and extract qualifiers
1362
- const { searchQuery, extractedQualifiers } = parseSearchQuery(params.query);
1363
- // Add the main search query if present
1364
- if (searchQuery) {
1365
- args.push(searchQuery);
1366
- }
1367
- // Add extracted qualifiers from the query (these should remain as qualifiers)
1368
- extractedQualifiers.forEach(qualifier => {
1369
- args.push(qualifier);
1370
- });
1341
+ // Build search query (either exactQuery OR queryTerms, never both)
1342
+ if (params.exactQuery) {
1343
+ // Add exact query - let GitHub CLI handle the quoting
1344
+ args.push(params.exactQuery);
1345
+ }
1346
+ else if (params.queryTerms && params.queryTerms.length > 0) {
1347
+ // Add query terms as separate arguments (for AND logic)
1348
+ // Auto-quote terms with special characters for literal matching
1349
+ const processedTerms = params.queryTerms.map(term => {
1350
+ // Check if term contains special search characters that need quoting
1351
+ const hasSpecialChars = /[()[\]{}*?^$|.\\+]/.test(term);
1352
+ return hasSpecialChars ? `"${term}"` : term;
1353
+ });
1354
+ args.push(...processedTerms);
1355
+ }
1371
1356
  // Add explicit parameters as CLI flags (following GitHub CLI format)
1372
- if (params.language && !params.query.includes('language:')) {
1357
+ if (params.language) {
1373
1358
  args.push(`--language=${params.language}`);
1374
1359
  }
1375
1360
  // Handle owner and repo parameters properly
1376
- if (params.repo && !params.query.includes('repo:')) {
1361
+ if (params.repo) {
1377
1362
  const repos = Array.isArray(params.repo) ? params.repo : [params.repo];
1378
1363
  repos.forEach(repo => {
1379
1364
  // If both owner and repo are provided, combine them for --repo flag
@@ -1389,30 +1374,27 @@ function buildGitHubCliArgs(params) {
1389
1374
  }
1390
1375
  });
1391
1376
  }
1392
- else if (params.owner &&
1393
- !params.query.includes('org:') &&
1394
- !params.query.includes('user:')) {
1377
+ else if (params.owner) {
1395
1378
  // Only owner provided, no repo - use --owner flag for organization-wide search
1396
1379
  const owners = Array.isArray(params.owner) ? params.owner : [params.owner];
1397
1380
  owners.forEach(owner => args.push(`--owner=${owner}`));
1398
1381
  }
1399
- if (params.filename && !params.query.includes('filename:')) {
1382
+ if (params.filename) {
1400
1383
  args.push(`--filename=${params.filename}`);
1401
1384
  }
1402
- if (params.extension && !params.query.includes('extension:')) {
1385
+ if (params.extension) {
1403
1386
  args.push(`--extension=${params.extension}`);
1404
1387
  }
1405
- if (params.size && !params.query.includes('size:')) {
1388
+ if (params.size) {
1406
1389
  args.push(`--size=${params.size}`);
1407
1390
  }
1408
1391
  // Handle match parameter - use --match flag
1409
1392
  if (params.match) {
1410
1393
  args.push(`--match=${params.match}`);
1411
1394
  }
1412
- // Add limit flag
1413
- if (params.limit) {
1414
- args.push(`--limit=${params.limit}`);
1415
- }
1395
+ // Add limit flag (use default 30 if not specified)
1396
+ const limit = params.limit || 30;
1397
+ args.push(`--limit=${limit}`);
1416
1398
  // Add JSON output format
1417
1399
  args.push('--json=repository,path,textMatches,sha,url');
1418
1400
  return args;
@@ -1433,55 +1415,6 @@ async function searchGitHubCode(params) {
1433
1415
  }
1434
1416
  });
1435
1417
  }
1436
- /**
1437
- * Parse search query to preserve quoted phrases and extract qualifiers.
1438
- * Handles:
1439
- * - Quoted phrases: "error handling" -> kept as single unit
1440
- * - Multiple terms: react lifecycle -> both terms for AND search
1441
- * - Mixed: "error handling" debug -> phrase + term
1442
- * - Qualifiers: language:javascript -> extracted separately
1443
- * - Quote escaping issues: automatically fixes common mistakes
1444
- */
1445
- function parseSearchQuery(query) {
1446
- const qualifiers = [];
1447
- const searchTerms = [];
1448
- // Clean up common quote escaping issues
1449
- let cleanedQuery = query;
1450
- // Fix escaped quotes that shouldn't be escaped
1451
- if (cleanedQuery.includes('\\"') && !cleanedQuery.includes(' ')) {
1452
- cleanedQuery = cleanedQuery.replace(/\\"/g, '');
1453
- }
1454
- // Fix single-word queries wrapped in quotes unnecessarily
1455
- if (cleanedQuery.startsWith('"') &&
1456
- cleanedQuery.endsWith('"') &&
1457
- !cleanedQuery.slice(1, -1).includes(' ')) {
1458
- cleanedQuery = cleanedQuery.slice(1, -1);
1459
- }
1460
- // Regular expression to match quoted strings or individual words/qualifiers
1461
- const tokenRegex = /"([^"]+)"|([^\s]+)/g;
1462
- let match;
1463
- while ((match = tokenRegex.exec(cleanedQuery)) !== null) {
1464
- const token = match[1] || match[2]; // match[1] is quoted content, match[2] is unquoted
1465
- // Check if it's a qualifier (contains : but not inside quotes)
1466
- if (!match[1] && token.includes(':') && /^[a-zA-Z]+:/.test(token)) {
1467
- qualifiers.push(token);
1468
- }
1469
- else {
1470
- // It's a search term (either quoted or unquoted)
1471
- if (match[1]) {
1472
- // Preserve quotes for exact phrase search
1473
- searchTerms.push(`"${token}"`);
1474
- }
1475
- else {
1476
- searchTerms.push(token);
1477
- }
1478
- }
1479
- }
1480
- return {
1481
- searchQuery: searchTerms.join(' '),
1482
- extractedQualifiers: qualifiers,
1483
- };
1484
- }
1485
1418
 
1486
1419
  /***********************************************************************
1487
1420
 
@@ -112365,14 +112298,13 @@ const GITHUB_SEARCH_REPOSITORIES_TOOL_NAME = 'githubSearchRepositories';
112365
112298
  const DESCRIPTION$6 = `Search GitHub repositories using gh search repos CLI.
112366
112299
 
112367
112300
  BEST PRACTICES:
112368
- - Use topics for unknown domains to explore repositories by TOPIC
112369
- - Use owner to explore repositories by ORGANIZATION
112370
- - Search by name and sort by best match / starts to search specific repositories
112371
- - Use language to explore repositories by LANGUAGE
112301
+ - Use topic for discovering repositories by technology/purpose
112302
+ - Use query for searching by repository name
112303
+ - Use owner to explore specific organizations
112304
+ - Use language to filter by programming language
112372
112305
  - Use quality filters (stars, forks) for refinement
112373
- - Use limit to control the number of repositories to return
112374
112306
 
112375
- Seperate queries for different topics and repositories search and use minimal filters to get the most relevant results`;
112307
+ Separate searches for different topics and use minimal filters to get the most relevant results`;
112376
112308
  /**
112377
112309
  * Extract owner/repo information from various query formats
112378
112310
  */
@@ -112410,38 +112342,38 @@ function registerSearchGitHubReposTool(server) {
112410
112342
  query: z
112411
112343
  .string()
112412
112344
  .optional()
112413
- .describe('Search query with AND logic between terms. Multiple words require ALL to be present. Use quotes for exact phrases. Examples: "cli shell", "vim plugin". For negation, use embedded qualifiers like "topic:react -topic:vue". Advanced: supports GitHub search qualifiers (stars:>100, language:python, etc.)'),
112345
+ .describe('Search by repository name. Use minimal words for repository names or specific projects. For topic discovery, use topic parameter instead.'),
112414
112346
  // CORE FILTERS (GitHub CLI flags)
112415
112347
  owner: z
112416
112348
  .union([z.string(), z.array(z.string())])
112417
112349
  .optional()
112418
- .describe('Repository owner/organization name(s) (e.g., "facebook", ["google", "microsoft"]). Search within specific organizations. Do NOT use owner/repo format - just the organization/username.'),
112350
+ .describe('Repository owner/organization name(s). Search within specific organizations or users.'),
112419
112351
  language: z
112420
112352
  .string()
112421
112353
  .optional()
112422
- .describe('Programming language filter. Filters repositories by primary language. Essential for language-specific searches.'),
112354
+ .describe('Programming language filter. Filters repositories by primary language.'),
112423
112355
  stars: z
112424
112356
  .union([
112425
112357
  z.number().int().min(0),
112426
112358
  z
112427
112359
  .string()
112428
- .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">1000", ">=500", "<100", "<=50", "10..100", or exact number "50"'),
112360
+ .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">N", ">=N", "<N", "<=N", "N..M", or exact number'),
112429
112361
  ])
112430
112362
  .optional()
112431
- .describe('Star count filter. Format: ">1000" (more than), ">=500" (more than or equal), "<100" (less than), "<=50" (less than or equal), "100..1000" (range), "500" (exact).'),
112363
+ .describe('Star count filter. Format: ">N" (more than), "<N" (less than), "N..M" (range).'),
112432
112364
  topic: z
112433
112365
  .union([z.string(), z.array(z.string())])
112434
112366
  .optional()
112435
- .describe('🎯 BEST FOR EXPLORATION: Repository topics filter. Use for discovering projects in unknown domains. Format: single topic "react" or array ["unix", "terminal"]. Topics use kebab-case format.'),
112367
+ .describe('Discover repositories by topic. Use for exploring unknown domains and finding projects by technology or purpose.'),
112436
112368
  forks: z
112437
112369
  .union([
112438
112370
  z.number().int().min(0),
112439
112371
  z
112440
112372
  .string()
112441
- .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">100", ">=50", "<10", "<=5", "10..100", or exact number "5"'),
112373
+ .regex(/^(>=?\d+|<=?\d+|\d+\.\.\d+|\d+)$/, 'Invalid format. Use: ">N", ">=N", "<N", "<=N", "N..M", or exact number'),
112442
112374
  ])
112443
112375
  .optional()
112444
- .describe('Fork count filter. Format: ">100" (more than), ">=50" (more than or equal), "<10" (less than), "<=5" (less than or equal), "10..100" (range), "5" (exact).'),
112376
+ .describe('Fork count filter. Format: ">N" (more than), "<N" (less than), "N..M" (range).'),
112445
112377
  // Match CLI parameter name exactly
112446
112378
  'number-topics': z
112447
112379
  .union([
@@ -112456,7 +112388,7 @@ function registerSearchGitHubReposTool(server) {
112456
112388
  license: z
112457
112389
  .union([z.string(), z.array(z.string())])
112458
112390
  .optional()
112459
- .describe('License filter. Examples: "mit", "apache-2.0", ["mit", "bsd-3-clause"]'),
112391
+ .describe('License filter. Filter repositories by license type.'),
112460
112392
  archived: z
112461
112393
  .boolean()
112462
112394
  .optional()
@@ -112520,7 +112452,7 @@ function registerSearchGitHubReposTool(server) {
112520
112452
  z.array(z.enum(['name', 'description', 'readme'])),
112521
112453
  ])
112522
112454
  .optional()
112523
- .describe('Search scope. "name" (repository names only), "description" (descriptions only), "readme" (README content). Can be single value or array.'),
112455
+ .describe('Search scope. Where to search: name, description, or readme content.'),
112524
112456
  // SORTING & LIMITS - Match CLI defaults exactly
112525
112457
  sort: z
112526
112458
  .enum([
@@ -113485,22 +113417,21 @@ function buildGitHubPullRequestsListCommand(params) {
113485
113417
  }
113486
113418
 
113487
113419
  const NPM_PACKAGE_SEARCH_TOOL_NAME = 'npmPackageSearch';
113488
- const DESCRIPTION$3 = `Search NPM packages by functionality keywords. PRIMARY ENTRY POINT for package-related queries.
113420
+ const DESCRIPTION$3 = `Search NPM packages using 'npm search' command. Discover packages by functionality keywords and explore alternatives.
113489
113421
 
113490
- PACKAGE-FIRST STRATEGY:
113491
- - Start here when users mention: libraries, dependencies, installations, alternatives
113492
- - Use broad functional terms for discovery (not exact package names)
113493
- - Bridge to GitHub tools via repository URLs from results
113422
+ **WHEN TO USE**: Use when users ask questions about npm packages or need to discover packages - provides package discovery and ecosystem insights.
113494
113423
 
113495
- SEARCH APPROACH:
113496
- - Single functional terms work best for discovery
113497
- - Multiple searches for different aspects/use-cases
113498
- - Reveals ecosystem alternatives and quality indicators
113424
+ **KEY INSIGHTS**:
113425
+ - Another code search mechanism for npm packages (along github repository search)
113426
+ - Repo discovery by npm packages search
113427
+ - Package descriptions, keywords, and version information
113428
+ - Can be used undesrsant npm depndencies better
113499
113429
 
113500
- INTEGRATION WORKFLOW:
113501
- - Package Discovery Repository Analysis Implementation Patterns
113502
- - npmPackageSearch npmViewPackage GitHub repository tools
113503
- - Compare alternatives by searching different functional terms`;
113430
+ **SEARCH STRATEGY**:
113431
+ - Use broad functional terms for best discovery
113432
+ - Single keywords work better than complex phrases
113433
+ - Multiple searches reveal ecosystem alternatives
113434
+ - Combine with npm_view_package for detailed analysis of discovered packages`;
113504
113435
  const MAX_DESCRIPTION_LENGTH = 100;
113505
113436
  const MAX_KEYWORDS = 10;
113506
113437
  function registerNpmSearchTool(server) {
@@ -114365,25 +114296,22 @@ function buildGitHubIssuesAPICommand(params) {
114365
114296
  }
114366
114297
 
114367
114298
  const NPM_VIEW_PACKAGE_TOOL_NAME = 'npmViewPackage';
114368
- const DESCRIPTION = `Analyze NPM packages for repository discovery and dependency insights. BRIDGE to GitHub ecosystem.
114369
-
114370
- PACKAGE ANALYSIS CAPABILITIES:
114371
- - Repository URL discovery for GitHub exploration
114372
- - Version history and release patterns
114373
- - Export analysis for implementation understanding
114374
- - Dependency metadata for ecosystem mapping
114375
-
114376
- GITHUB INTEGRATION:
114377
- - Provides repository links for GitHub repository tools
114378
- - Connects package metadata to source code analysis
114379
- - Enables package-to-implementation research workflows
114380
- - Essential for dependency and alternative evaluation
114381
-
114382
- USE CASES:
114383
- - Repository discovery from package names
114384
- - Version analysis and security assessment
114385
- - Export structure for integration planning
114386
- - Dependency research and ecosystem exploration`;
114299
+ const DESCRIPTION = `View NPM package information using 'npm view' command. Supports field-specific queries and GitHub repository discovery.
114300
+
114301
+ **WHEN TO USE**: Use when users ask questions about npm packages - provides comprehensive package data and insights.
114302
+
114303
+ **KEY INSIGHTS**:
114304
+ - Git repository URL for source code exploration
114305
+ - Package exports structure (understand API surface and dependencies)
114306
+ - Dependencies/devDependencies for ecosystem analysis
114307
+ - Version history, size, performance metrics
114308
+ - License and author information
114309
+
114310
+ **CAPABILITIES**:
114311
+ - Full package info: npm view <package> --json (optimized format)
114312
+ - Single field: npm view <package> <field> (version, description, license)
114313
+ - Multiple fields: filtered JSON response for specific fields
114314
+ - Repository URLs for GitHub integration and source code analysis`;
114387
114315
  function registerNpmViewPackageTool(server) {
114388
114316
  server.registerTool(NPM_VIEW_PACKAGE_TOOL_NAME, {
114389
114317
  description: DESCRIPTION,
@@ -114392,6 +114320,51 @@ function registerNpmViewPackageTool(server) {
114392
114320
  .string()
114393
114321
  .min(1)
114394
114322
  .describe('NPM package name (e.g., "react", "express", "@types/node"). Include @ prefix for scoped packages.'),
114323
+ field: z
114324
+ .string()
114325
+ .optional()
114326
+ .describe('Optional field to get specific information (e.g., "version", "description", "license"). If not provided, returns full package info.'),
114327
+ match: z
114328
+ .union([
114329
+ z.enum([
114330
+ 'version',
114331
+ 'description',
114332
+ 'license',
114333
+ 'author',
114334
+ 'homepage',
114335
+ 'repository',
114336
+ 'dependencies',
114337
+ 'devDependencies',
114338
+ 'keywords',
114339
+ 'main',
114340
+ 'scripts',
114341
+ 'engines',
114342
+ 'files',
114343
+ 'publishConfig',
114344
+ 'dist-tags',
114345
+ 'time',
114346
+ ]),
114347
+ z.array(z.enum([
114348
+ 'version',
114349
+ 'description',
114350
+ 'license',
114351
+ 'author',
114352
+ 'homepage',
114353
+ 'repository',
114354
+ 'dependencies',
114355
+ 'devDependencies',
114356
+ 'keywords',
114357
+ 'main',
114358
+ 'scripts',
114359
+ 'engines',
114360
+ 'files',
114361
+ 'publishConfig',
114362
+ 'dist-tags',
114363
+ 'time',
114364
+ ])),
114365
+ ])
114366
+ .optional()
114367
+ .describe('Specific field(s) to retrieve. Can be single field or array of fields. Examples: "version", ["version", "description"], ["dependencies", "devDependencies"]. When used, returns only the specified fields. use repository to get the repository url in github'),
114395
114368
  },
114396
114369
  annotations: {
114397
114370
  title: 'NPM Package Analyzer',
@@ -114402,13 +114375,48 @@ function registerNpmViewPackageTool(server) {
114402
114375
  },
114403
114376
  }, async (args) => {
114404
114377
  try {
114405
- const result = await viewNpmPackage(args.packageName);
114378
+ const result = await viewNpmPackage(args.packageName, args.field, args.match);
114406
114379
  if (result.isError) {
114407
114380
  return result;
114408
114381
  }
114382
+ // If field is specified, npm returns plain text, not JSON
114383
+ if (args.field) {
114384
+ const plainTextResult = result.content[0].text;
114385
+ // Parse the plain text result from npm command
114386
+ const execResult = JSON.parse(plainTextResult);
114387
+ const fieldValue = execResult.result;
114388
+ return createResult({
114389
+ data: {
114390
+ field: args.field,
114391
+ value: fieldValue,
114392
+ package: args.packageName,
114393
+ },
114394
+ });
114395
+ }
114396
+ // If match is specified, filter the JSON response to only include matched fields
114397
+ if (args.match) {
114398
+ const execResult = JSON.parse(result.content[0].text);
114399
+ const packageData = execResult.result;
114400
+ const matchFields = Array.isArray(args.match)
114401
+ ? args.match
114402
+ : [args.match];
114403
+ const filteredData = {};
114404
+ matchFields.forEach(field => {
114405
+ if (packageData[field] !== undefined) {
114406
+ filteredData[field] = packageData[field];
114407
+ }
114408
+ });
114409
+ return createResult({
114410
+ data: {
114411
+ package: args.packageName,
114412
+ fields: matchFields,
114413
+ values: filteredData,
114414
+ },
114415
+ });
114416
+ }
114417
+ // Otherwise return full optimized format (JSON response)
114409
114418
  const execResult = JSON.parse(result.content[0].text);
114410
114419
  const packageData = execResult.result;
114411
- // Transform to optimized format
114412
114420
  const optimizedResult = transformToOptimizedFormat(packageData);
114413
114421
  return createResult({ data: optimizedResult });
114414
114422
  }
@@ -114418,6 +114426,11 @@ function registerNpmViewPackageTool(server) {
114418
114426
  if (errorMessage.includes('not found') ||
114419
114427
  errorMessage.includes('404')) {
114420
114428
  const packageName = args.packageName;
114429
+ const fieldInfo = args.field ? ` (field: ${args.field})` : '';
114430
+ const matchInfo = args.match
114431
+ ? ` (match: ${Array.isArray(args.match) ? args.match.join(', ') : args.match})`
114432
+ : '';
114433
+ const paramInfo = fieldInfo || matchInfo;
114421
114434
  const suggestions = [];
114422
114435
  // Check for common naming patterns
114423
114436
  if (packageName.includes('_')) {
@@ -114432,12 +114445,22 @@ function registerNpmViewPackageTool(server) {
114432
114445
  if (packageName.startsWith('@')) {
114433
114446
  suggestions.push(`• Try without scope: "${packageName.split('/')[1]}"`);
114434
114447
  }
114448
+ // Add field/match-specific suggestions
114449
+ if (args.field) {
114450
+ suggestions.push(`• Try without field parameter to get full package info`);
114451
+ suggestions.push(`• Common fields: version, description, license, dependencies`);
114452
+ }
114453
+ if (args.match) {
114454
+ suggestions.push(`• Try without match parameter to get full package info`);
114455
+ suggestions.push(`• Try single field instead of multiple: field: "version"`);
114456
+ suggestions.push(`• Common fields: version, description, license, dependencies`);
114457
+ }
114435
114458
  // Add discovery alternatives
114436
114459
  suggestions.push('• Use npm_package_search for discovery');
114437
114460
  suggestions.push('• Use github_search_repos to find source repository');
114438
114461
  suggestions.push('• Check exact spelling on npmjs.com');
114439
114462
  return createResult({
114440
- error: `Package "${packageName}" not found on NPM registry.
114463
+ error: `Package "${packageName}"${paramInfo} not found on NPM registry.
114441
114464
 
114442
114465
  Try these alternatives:
114443
114466
  ${suggestions.join('\n')}
@@ -114582,11 +114605,14 @@ function simplifyExports(exports) {
114582
114605
  }
114583
114606
  return { main: 'index.js' };
114584
114607
  }
114585
- async function viewNpmPackage(packageName) {
114586
- const cacheKey = generateCacheKey('npm-view', { packageName });
114608
+ async function viewNpmPackage(packageName, field, match) {
114609
+ const cacheKey = generateCacheKey('npm-view', { packageName, field, match });
114587
114610
  return withCache(cacheKey, async () => {
114588
114611
  try {
114589
- const result = await executeNpmCommand('view', [packageName, '--json'], {
114612
+ // Build arguments based on parameters
114613
+ // Priority: field > match > full JSON
114614
+ const args = field ? [packageName, field] : [packageName, '--json']; // For match or full info, we need JSON
114615
+ const result = await executeNpmCommand('view', args, {
114590
114616
  cache: false,
114591
114617
  });
114592
114618
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octocode-mcp",
3
- "version": "2.3.11",
3
+ "version": "2.3.13",
4
4
  "description": "Model Context Protocol (MCP) server for advanced GitHub repository analysis, code discovery, and npm package exploration. Provides AI assistants with powerful tools to search, analyze, and understand codebases across GitHub and npm ecosystems.",
5
5
  "author": "Guy Bary <guybary@gmail.com>",
6
6
  "homepage": "https://octocode.ai",