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.
- package/build/index.js +270 -244
- 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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
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 = `
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
-
|
|
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
|
-
|
|
1123
|
-
-
|
|
1124
|
-
-
|
|
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
|
-
|
|
1129
|
-
-
|
|
1130
|
-
-
|
|
1131
|
-
-
|
|
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
|
-
|
|
1134
|
-
-
|
|
1135
|
-
-
|
|
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
|
-
-
|
|
1146
|
-
- Combine filters
|
|
1147
|
-
- Never use filters on exploratory searches - use to refine
|
|
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
|
-
|
|
1115
|
+
exactQuery: z
|
|
1153
1116
|
.string()
|
|
1154
|
-
.
|
|
1155
|
-
.describe('
|
|
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.
|
|
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)
|
|
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('
|
|
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.
|
|
1138
|
+
.describe('Target specific filename or pattern.'),
|
|
1172
1139
|
extension: z
|
|
1173
1140
|
.string()
|
|
1174
1141
|
.optional()
|
|
1175
|
-
.describe('File extension filter.
|
|
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:
|
|
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: ">
|
|
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.
|
|
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).
|
|
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)
|
|
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
|
-
//
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
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
|
|
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
|
|
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
|
|
1382
|
+
if (params.filename) {
|
|
1400
1383
|
args.push(`--filename=${params.filename}`);
|
|
1401
1384
|
}
|
|
1402
|
-
if (params.extension
|
|
1385
|
+
if (params.extension) {
|
|
1403
1386
|
args.push(`--extension=${params.extension}`);
|
|
1404
1387
|
}
|
|
1405
|
-
if (params.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
|
-
|
|
1414
|
-
|
|
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
|
|
112369
|
-
- Use
|
|
112370
|
-
-
|
|
112371
|
-
- Use language to
|
|
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
|
-
|
|
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
|
|
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)
|
|
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.
|
|
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: ">
|
|
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: ">
|
|
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('
|
|
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: ">
|
|
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: ">
|
|
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.
|
|
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.
|
|
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
|
|
113420
|
+
const DESCRIPTION$3 = `Search NPM packages using 'npm search' command. Discover packages by functionality keywords and explore alternatives.
|
|
113489
113421
|
|
|
113490
|
-
|
|
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
|
-
|
|
113496
|
-
-
|
|
113497
|
-
-
|
|
113498
|
-
-
|
|
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
|
-
|
|
113501
|
-
-
|
|
113502
|
-
-
|
|
113503
|
-
-
|
|
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 = `
|
|
114369
|
-
|
|
114370
|
-
|
|
114371
|
-
|
|
114372
|
-
|
|
114373
|
-
-
|
|
114374
|
-
-
|
|
114375
|
-
|
|
114376
|
-
|
|
114377
|
-
-
|
|
114378
|
-
|
|
114379
|
-
|
|
114380
|
-
-
|
|
114381
|
-
|
|
114382
|
-
|
|
114383
|
-
- Repository
|
|
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
|
-
|
|
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.
|
|
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",
|