octocode-mcp 2.3.0 → 2.3.2
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/README.md +133 -515
- package/build/index.js +434 -385
- package/package.json +27 -27
package/build/index.js
CHANGED
|
@@ -7,79 +7,40 @@ import NodeCache from 'node-cache';
|
|
|
7
7
|
import crypto from 'crypto';
|
|
8
8
|
import z from 'zod';
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
GITHUB_GET_CONTENTS: 'github_get_contents',
|
|
20
|
-
GITHUB_GET_FILE_CONTENT: 'github_get_file_content',
|
|
21
|
-
// NPM
|
|
22
|
-
NPM_PACKAGE_SEARCH: 'npm_package_search',
|
|
23
|
-
NPM_VIEW_PACKAGE: 'npm_view_package',
|
|
24
|
-
};
|
|
25
|
-
const PROMPT_SYSTEM_PROMPT = `Smart code research assistant with semantic search capabilities.
|
|
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.
|
|
12
|
+
|
|
13
|
+
IMPORTANT: check users github organizations and use them in github search tools if needed
|
|
14
|
+
|
|
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)
|
|
26
19
|
|
|
27
20
|
APPROACH:
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
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
|
|
32
27
|
|
|
33
|
-
SEARCH STRATEGY:
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
- ${TOOL_NAMES.GITHUB_GET_CONTENTS} → ${TOOL_NAMES.GITHUB_GET_FILE_CONTENT} - ALWAYS verify file existence before fetching
|
|
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
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
- Use ${TOOL_NAMES.NPM_VIEW_PACKAGE} exports field to discover available files in packages
|
|
47
|
-
- Always check documentation and examples when available`;
|
|
48
|
-
const TOOL_DESCRIPTIONS = {
|
|
49
|
-
[TOOL_NAMES.API_STATUS_CHECK]: `Check GitHub & NPM authentication status and discover user organizations.
|
|
50
|
-
Essential first step that enables access to private/organizational repositories by identifying available organizations for the 'owner' parameter in search tools.
|
|
51
|
-
Critical for enterprise code exploration and accessing company-specific repositories that require organizational membership.`,
|
|
52
|
-
[TOOL_NAMES.NPM_PACKAGE_SEARCH]: `Search NPM packages by keyword. Use for package ecosystem discovery.`,
|
|
53
|
-
[TOOL_NAMES.NPM_VIEW_PACKAGE]: `Get comprehensive package metadata essential for GitHub searches and code analysis. Returns complete package context:
|
|
54
|
-
• repositoryGitUrl - Direct GitHub repo link for accurate searches
|
|
55
|
-
• exports - Critical for discovering available files and import paths
|
|
56
|
-
• dependencies/devDependencies - Full ecosystem understanding
|
|
57
|
-
• versions with dates - Historical evolution context
|
|
58
|
-
The exports field is invaluable for GitHub file discovery - shows exact paths before fetching. Always use when finding packages in code.`,
|
|
59
|
-
[TOOL_NAMES.GITHUB_SEARCH_CODE]: `SEMANTIC CODE DISCOVERY: Search code with boolean logic (AND, OR, NOT).
|
|
60
|
-
Format: "term AND term" language:js path:src. Filters: owner/org/user, repo, extension, filename, language, path, size, limit, match scope.
|
|
61
|
-
Use for finding actual implementation patterns and code examples.
|
|
62
|
-
CRITICAL: When packages found in results or from user input, use ${TOOL_NAMES.NPM_VIEW_PACKAGE} for metadata/paths.`,
|
|
63
|
-
[TOOL_NAMES.GITHUB_SEARCH_REPOS]: `Search repositories by name/description. PRIMARY FILTERS work alone: owner, language, stars, topic, forks. SECONDARY FILTERS require query/primary filter: license, created, archived, includeForks, updated, visibility, match.
|
|
64
|
-
|
|
65
|
-
PATTERNS: Use topic:["cli","typescript"] for semantic discovery. Use stars:">100" for quality. Use owner:"microsoft" for organization repos. Query supports GitHub syntax: "language:Go OR language:Rust".
|
|
66
|
-
|
|
67
|
-
CRITICAL: When finding packages, use ${TOOL_NAMES.NPM_VIEW_PACKAGE} for metadata and repository paths.`,
|
|
68
|
-
[TOOL_NAMES.GITHUB_GET_CONTENTS]: `Browse repository structure and verify file existence. ALWAYS use before github_get_file_content to confirm files exist and understand organization.`,
|
|
69
|
-
[TOOL_NAMES.GITHUB_GET_FILE_CONTENT]: `Read file content. REQUIRES exact path verification from github_get_contents first. If fetching fails, check file existence with github_get_contents.`,
|
|
70
|
-
[TOOL_NAMES.GITHUB_SEARCH_ISSUES]: `Find GitHub issues and problems with rich metadata (labels, reactions, comments, state).
|
|
71
|
-
Discover pain points, feature requests, bug patterns, and community discussions.
|
|
72
|
-
Filter by state, labels, assignee, or date ranges. Use for understanding project health and common user issues.`,
|
|
73
|
-
[TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS]: `Find pull requests and implementations with detailed metadata.
|
|
74
|
-
Discover how features were implemented, code review patterns, and development workflows.
|
|
75
|
-
Filter by state, author, reviewer, or merge status. Essential for understanding project development practices.`,
|
|
76
|
-
[TOOL_NAMES.GITHUB_SEARCH_COMMITS]: `Search commit history. Use for understanding code evolution and development patterns.`,
|
|
77
|
-
};
|
|
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`;
|
|
78
40
|
|
|
79
|
-
// CONSOLIDATED ERROR & SUCCESS HANDLING
|
|
80
41
|
function createResult(data, isError = false, suggestions) {
|
|
81
42
|
const text = isError
|
|
82
|
-
? `${data}${
|
|
43
|
+
? `${data}${''}`
|
|
83
44
|
: JSON.stringify(data, null, 2);
|
|
84
45
|
return {
|
|
85
46
|
content: [{ type: 'text', text }],
|
|
@@ -103,56 +64,6 @@ function parseJsonResponse(responseText, fallback = null) {
|
|
|
103
64
|
return { data: (fallback || responseText), parsed: false };
|
|
104
65
|
}
|
|
105
66
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Generate fallback suggestions for no results - ensures no tool suggests itself
|
|
108
|
-
*/
|
|
109
|
-
function getNoResultsSuggestions(currentTool) {
|
|
110
|
-
const suggestions = [];
|
|
111
|
-
// Tool-specific fallbacks
|
|
112
|
-
switch (currentTool) {
|
|
113
|
-
case TOOL_NAMES.GITHUB_SEARCH_REPOS:
|
|
114
|
-
suggestions.push(TOOL_NAMES.GITHUB_SEARCH_CODE, TOOL_NAMES.NPM_PACKAGE_SEARCH);
|
|
115
|
-
break;
|
|
116
|
-
case TOOL_NAMES.GITHUB_SEARCH_CODE:
|
|
117
|
-
suggestions.push(TOOL_NAMES.GITHUB_SEARCH_REPOS, TOOL_NAMES.GITHUB_SEARCH_ISSUES);
|
|
118
|
-
break;
|
|
119
|
-
case TOOL_NAMES.NPM_PACKAGE_SEARCH:
|
|
120
|
-
suggestions.push(TOOL_NAMES.GITHUB_SEARCH_REPOS, TOOL_NAMES.GITHUB_SEARCH_CODE);
|
|
121
|
-
break;
|
|
122
|
-
case TOOL_NAMES.GITHUB_SEARCH_ISSUES:
|
|
123
|
-
suggestions.push(TOOL_NAMES.GITHUB_SEARCH_CODE, TOOL_NAMES.GITHUB_SEARCH_REPOS);
|
|
124
|
-
break;
|
|
125
|
-
case TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS:
|
|
126
|
-
suggestions.push(TOOL_NAMES.GITHUB_SEARCH_ISSUES, TOOL_NAMES.GITHUB_SEARCH_CODE);
|
|
127
|
-
break;
|
|
128
|
-
case TOOL_NAMES.GITHUB_SEARCH_COMMITS:
|
|
129
|
-
suggestions.push(TOOL_NAMES.GITHUB_SEARCH_CODE, TOOL_NAMES.GITHUB_SEARCH_REPOS);
|
|
130
|
-
break;
|
|
131
|
-
case TOOL_NAMES.GITHUB_GET_CONTENTS:
|
|
132
|
-
case TOOL_NAMES.GITHUB_GET_FILE_CONTENT:
|
|
133
|
-
suggestions.push(TOOL_NAMES.GITHUB_SEARCH_REPOS, TOOL_NAMES.GITHUB_SEARCH_CODE);
|
|
134
|
-
break;
|
|
135
|
-
default:
|
|
136
|
-
// Fallback for any other tools
|
|
137
|
-
suggestions.push(TOOL_NAMES.GITHUB_SEARCH_REPOS, TOOL_NAMES.GITHUB_SEARCH_CODE);
|
|
138
|
-
}
|
|
139
|
-
return suggestions.slice(0, 3);
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Generate fallback suggestions for errors - ensures no tool suggests itself
|
|
143
|
-
*/
|
|
144
|
-
function getErrorSuggestions(currentTool) {
|
|
145
|
-
const suggestions = [];
|
|
146
|
-
// Always suggest API status check first (unless it's the current tool)
|
|
147
|
-
if (currentTool !== TOOL_NAMES.API_STATUS_CHECK) {
|
|
148
|
-
suggestions.push(TOOL_NAMES.API_STATUS_CHECK);
|
|
149
|
-
}
|
|
150
|
-
// Add discovery alternatives
|
|
151
|
-
if (currentTool !== TOOL_NAMES.GITHUB_SEARCH_REPOS) {
|
|
152
|
-
suggestions.push(TOOL_NAMES.GITHUB_SEARCH_REPOS);
|
|
153
|
-
}
|
|
154
|
-
return suggestions.slice(0, 3);
|
|
155
|
-
}
|
|
156
67
|
/**
|
|
157
68
|
* Determines if a string needs quoting for GitHub search
|
|
158
69
|
*/
|
|
@@ -231,8 +142,9 @@ async function executeNpmCommand(command, args = [], options = {}) {
|
|
|
231
142
|
if (!isValidNpmCommand(command)) {
|
|
232
143
|
return createErrorResult('Command not registered', new Error(`NPM command '${command}' is not in the allowed list`));
|
|
233
144
|
}
|
|
234
|
-
// Build command with validated prefix
|
|
235
|
-
const
|
|
145
|
+
// Build command with validated prefix and properly escaped arguments
|
|
146
|
+
const escapedArgs = args.map(escapeShellArg);
|
|
147
|
+
const fullCommand = `npm ${command} ${escapedArgs.join(' ')}`;
|
|
236
148
|
const executeNpmCommand = () => executeCommand(fullCommand, 'npm', options);
|
|
237
149
|
if (options.cache) {
|
|
238
150
|
const cacheKey = generateCacheKey('npm-exec', { command, args });
|
|
@@ -240,6 +152,17 @@ async function executeNpmCommand(command, args = [], options = {}) {
|
|
|
240
152
|
}
|
|
241
153
|
return executeNpmCommand();
|
|
242
154
|
}
|
|
155
|
+
/**
|
|
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
|
+
}
|
|
243
166
|
/**
|
|
244
167
|
* Execute GitHub CLI commands safely by validating against allowed commands
|
|
245
168
|
* Security: Only executes commands that start with "gh {ALLOWED_COMMAND}"
|
|
@@ -249,8 +172,9 @@ async function executeGitHubCommand(command, args = [], options = {}) {
|
|
|
249
172
|
if (!isValidGhCommand(command)) {
|
|
250
173
|
return createErrorResult('Command not registered', new Error(`GitHub command '${command}' is not in the allowed list`));
|
|
251
174
|
}
|
|
252
|
-
// Build command with validated prefix
|
|
253
|
-
const
|
|
175
|
+
// Build command with validated prefix and properly escaped arguments
|
|
176
|
+
const escapedArgs = args.map(escapeShellArg);
|
|
177
|
+
const fullCommand = `gh ${command} ${escapedArgs.join(' ')}`;
|
|
254
178
|
const executeGhCommand = () => executeCommand(fullCommand, 'github', options);
|
|
255
179
|
if (options.cache) {
|
|
256
180
|
const cacheKey = generateCacheKey('gh-exec', { command, args });
|
|
@@ -307,10 +231,13 @@ async function executeCommand(fullCommand, type, options = {}) {
|
|
|
307
231
|
}
|
|
308
232
|
}
|
|
309
233
|
|
|
234
|
+
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.`;
|
|
310
237
|
function registerApiStatusCheckTool(server) {
|
|
311
|
-
server.tool(
|
|
312
|
-
title: '
|
|
313
|
-
description:
|
|
238
|
+
server.tool(TOOL_NAME$9, DESCRIPTION$9, {}, {
|
|
239
|
+
title: 'Check API Connections and Github Organizations',
|
|
240
|
+
description: DESCRIPTION$9,
|
|
314
241
|
readOnlyHint: true,
|
|
315
242
|
destructiveHint: false,
|
|
316
243
|
idempotentHint: true,
|
|
@@ -426,64 +353,72 @@ function registerApiStatusCheckTool(server) {
|
|
|
426
353
|
});
|
|
427
354
|
}
|
|
428
355
|
catch (error) {
|
|
429
|
-
return createResult(
|
|
356
|
+
return createResult('API status check failed - verify GitHub CLI and NPM are installed and accessible', true);
|
|
430
357
|
}
|
|
431
358
|
});
|
|
432
359
|
}
|
|
433
360
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
361
|
+
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.
|
|
363
|
+
|
|
364
|
+
STRATEGIC SEARCH PATTERNS:
|
|
365
|
+
|
|
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
|
|
370
|
+
|
|
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
|
|
375
|
+
|
|
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
|
|
380
|
+
|
|
381
|
+
NOT LOGIC (Noise Filtering):
|
|
382
|
+
• Exclude unwanted results: "authentication NOT test NOT mock"
|
|
383
|
+
• Best for: Removing examples, tests, deprecated code
|
|
384
|
+
|
|
385
|
+
RESTRICTIVENESS SCALE: OR < AND < Exact Phrase (Broadest → Most Precise)
|
|
386
|
+
|
|
387
|
+
COMBINE FILTERS: Mix query with language, owner, path filters for laser-focused results.`;
|
|
447
388
|
function registerGitHubSearchCodeTool(server) {
|
|
448
|
-
server.tool(
|
|
389
|
+
server.tool(TOOL_NAME$8, DESCRIPTION$8, {
|
|
449
390
|
query: z
|
|
450
391
|
.string()
|
|
451
392
|
.min(1)
|
|
452
|
-
.describe('Search query with boolean operators (AND
|
|
453
|
-
'Examples: "react lifecycle", "error handling", "logger AND debug", "config OR settings", "main NOT test". ' +
|
|
454
|
-
'Use quotes for exact phrases. Supports GitHub search syntax.'),
|
|
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.'),
|
|
455
394
|
owner: z
|
|
456
395
|
.union([z.string(), z.array(z.string())])
|
|
457
396
|
.optional()
|
|
458
|
-
.describe('Repository owner/organization
|
|
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)'),
|
|
459
398
|
repo: z
|
|
460
|
-
.array(z.string())
|
|
399
|
+
.union([z.string(), z.array(z.string())])
|
|
461
400
|
.optional()
|
|
462
|
-
.describe('Specific repositories in "owner/repo" format. Requires owner parameter
|
|
401
|
+
.describe('Specific repositories in "owner/repo" format. Examples: "facebook/react", "microsoft/vscode". Requires owner parameter.'),
|
|
463
402
|
language: z
|
|
464
403
|
.string()
|
|
465
404
|
.optional()
|
|
466
|
-
.describe('Programming language filter
|
|
405
|
+
.describe('Programming language filter. Examples: "javascript", "python", "typescript", "go". Highly effective for targeted searches.'),
|
|
467
406
|
extension: z
|
|
468
407
|
.string()
|
|
469
408
|
.optional()
|
|
470
|
-
.describe('File extension filter without dot
|
|
409
|
+
.describe('File extension filter without dot. Examples: "js", "ts", "py", "md", "json". Precise file type targeting.'),
|
|
471
410
|
filename: z
|
|
472
411
|
.string()
|
|
473
412
|
.optional()
|
|
474
|
-
.describe('Exact filename filter
|
|
413
|
+
.describe('Exact filename filter. Examples: "package.json", "Dockerfile", "README.md", "index.js". Perfect for config files.'),
|
|
475
414
|
path: z
|
|
476
415
|
.string()
|
|
477
416
|
.optional()
|
|
478
|
-
.describe('Directory path filter
|
|
417
|
+
.describe('Directory path filter. Examples: "src/", "test/", "docs/", "components/". Focus search on specific directories.'),
|
|
479
418
|
size: z
|
|
480
419
|
.string()
|
|
481
420
|
.optional()
|
|
482
421
|
.describe('File size filter in KB with operators (e.g., ">100", "<50", "10..100").'),
|
|
483
|
-
visibility: z
|
|
484
|
-
.enum(['public', 'private', 'internal'])
|
|
485
|
-
.optional()
|
|
486
|
-
.describe('Repository visibility filter. "public" for public repos, "private" for private repos you have access to.'),
|
|
487
422
|
limit: z
|
|
488
423
|
.number()
|
|
489
424
|
.int()
|
|
@@ -495,11 +430,14 @@ function registerGitHubSearchCodeTool(server) {
|
|
|
495
430
|
match: z
|
|
496
431
|
.union([z.enum(['file', 'path']), z.array(z.enum(['file', 'path']))])
|
|
497
432
|
.optional()
|
|
498
|
-
.describe('Search scope: "file"
|
|
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.'),
|
|
499
438
|
}, {
|
|
500
|
-
title:
|
|
501
|
-
description:
|
|
502
|
-
'Supports language, file type, owner, repository, path, and visibility filtering.',
|
|
439
|
+
title: TOOL_NAME$8,
|
|
440
|
+
description: DESCRIPTION$8,
|
|
503
441
|
readOnlyHint: true,
|
|
504
442
|
destructiveHint: false,
|
|
505
443
|
idempotentHint: true,
|
|
@@ -507,7 +445,7 @@ function registerGitHubSearchCodeTool(server) {
|
|
|
507
445
|
}, async (args) => {
|
|
508
446
|
try {
|
|
509
447
|
if (args.repo && !args.owner) {
|
|
510
|
-
return createResult('Repository search requires owner parameter', true);
|
|
448
|
+
return createResult('Repository search requires owner parameter - specify owner when searching specific repositories', true);
|
|
511
449
|
}
|
|
512
450
|
const result = await searchGitHubCode(args);
|
|
513
451
|
if (result.isError) {
|
|
@@ -519,114 +457,206 @@ function registerGitHubSearchCodeTool(server) {
|
|
|
519
457
|
const items = Array.isArray(codeResults) ? codeResults : [];
|
|
520
458
|
return createSuccessResult$1({
|
|
521
459
|
query: args.query,
|
|
460
|
+
processed_query: parseSearchQuery(args.query, args),
|
|
522
461
|
total_count: items.length,
|
|
523
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
|
+
},
|
|
524
469
|
});
|
|
525
470
|
}
|
|
526
471
|
catch (error) {
|
|
527
|
-
return createErrorResult$1('
|
|
472
|
+
return createErrorResult$1('GitHub code search failed - check repository access or simplify query', error);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Enhanced query parser that handles exact strings, boolean operators, and filters
|
|
478
|
+
*/
|
|
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);
|
|
491
|
+
});
|
|
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
|
+
}
|
|
504
|
+
}
|
|
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}`);
|
|
509
|
+
}
|
|
510
|
+
if (filters.visibility) {
|
|
511
|
+
githubFilters.push(`visibility:${filters.visibility}`);
|
|
512
|
+
}
|
|
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}`);
|
|
518
|
+
}
|
|
519
|
+
if (filters.extension) {
|
|
520
|
+
githubFilters.push(`extension:${filters.extension}`);
|
|
521
|
+
}
|
|
522
|
+
if (filters.filename) {
|
|
523
|
+
githubFilters.push(`filename:${filters.filename}`);
|
|
528
524
|
}
|
|
525
|
+
if (filters.size) {
|
|
526
|
+
githubFilters.push(`size:${filters.size}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// Step 5: Combine query with GitHub filters
|
|
530
|
+
if (githubFilters.length > 0) {
|
|
531
|
+
searchQuery = `${searchQuery} ${githubFilters.join(' ')}`;
|
|
532
|
+
}
|
|
533
|
+
// Step 6: Restore exact phrases
|
|
534
|
+
exactPhrases.forEach((phrase, index) => {
|
|
535
|
+
const placeholder = `__EXACT_PHRASE_${index}__`;
|
|
536
|
+
searchQuery = searchQuery.replace(placeholder, phrase);
|
|
529
537
|
});
|
|
538
|
+
return searchQuery.trim();
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Check if query contains complex boolean logic that might conflict with CLI flags
|
|
542
|
+
*/
|
|
543
|
+
function hasComplexBooleanLogic(query) {
|
|
544
|
+
const booleanOperators = /\b(AND|OR|NOT)\b/i;
|
|
545
|
+
return booleanOperators.test(query);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Build command line arguments for GitHub CLI
|
|
549
|
+
*/
|
|
550
|
+
function buildGitHubCliArgs(params) {
|
|
551
|
+
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) ;
|
|
559
|
+
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
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (params.limit) {
|
|
575
|
+
args.push(`--limit=${params.limit}`);
|
|
576
|
+
}
|
|
577
|
+
// Handle match parameter - can be string or array
|
|
578
|
+
if (params.match) {
|
|
579
|
+
const matchValues = Array.isArray(params.match)
|
|
580
|
+
? params.match
|
|
581
|
+
: [params.match];
|
|
582
|
+
// GitHub API limitation: can't use both in:file and in:path in same query
|
|
583
|
+
// Use the first match type when multiple are provided
|
|
584
|
+
const matchValue = matchValues[0];
|
|
585
|
+
args.push(`--match=${matchValue}`);
|
|
586
|
+
}
|
|
587
|
+
// Handle owner parameter - can be string or array
|
|
588
|
+
if (params.owner && !params.repo) {
|
|
589
|
+
const ownerValues = Array.isArray(params.owner)
|
|
590
|
+
? params.owner
|
|
591
|
+
: [params.owner];
|
|
592
|
+
ownerValues.forEach(owner => args.push(`--owner=${owner}`));
|
|
593
|
+
}
|
|
594
|
+
// Handle repository filters
|
|
595
|
+
if (params.owner && params.repo) {
|
|
596
|
+
const owners = Array.isArray(params.owner) ? params.owner : [params.owner];
|
|
597
|
+
const repos = Array.isArray(params.repo) ? params.repo : [params.repo];
|
|
598
|
+
// Create repo filters for each owner/repo combination
|
|
599
|
+
owners.forEach(owner => {
|
|
600
|
+
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
|
+
}
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
// JSON output with all available fields
|
|
612
|
+
args.push('--json=repository,path,textMatches,sha,url');
|
|
613
|
+
return args;
|
|
530
614
|
}
|
|
531
615
|
async function searchGitHubCode(params) {
|
|
532
616
|
const cacheKey = generateCacheKey('gh-code', params);
|
|
533
617
|
return withCache(cacheKey, async () => {
|
|
534
618
|
try {
|
|
535
|
-
const args =
|
|
536
|
-
// Build the main query - preserve boolean operators and GitHub syntax
|
|
537
|
-
let query = params.query;
|
|
538
|
-
// Add path filter to query if provided (GitHub search syntax)
|
|
539
|
-
if (params.path) {
|
|
540
|
-
query = `${query} path:${params.path}`;
|
|
541
|
-
}
|
|
542
|
-
// Add visibility filter to query if provided
|
|
543
|
-
if (params.visibility) {
|
|
544
|
-
query = `${query} visibility:${params.visibility}`;
|
|
545
|
-
}
|
|
546
|
-
args.push(query);
|
|
547
|
-
// Add command-line filters
|
|
548
|
-
if (params.language)
|
|
549
|
-
args.push(`--language=${params.language}`);
|
|
550
|
-
if (params.extension)
|
|
551
|
-
args.push(`--extension=${params.extension}`);
|
|
552
|
-
if (params.filename)
|
|
553
|
-
args.push(`--filename=${params.filename}`);
|
|
554
|
-
if (params.size)
|
|
555
|
-
args.push(`--size=${params.size}`);
|
|
556
|
-
if (params.limit)
|
|
557
|
-
args.push(`--limit=${params.limit}`);
|
|
558
|
-
// Handle match parameter - can be string or array
|
|
559
|
-
// Note: GitHub search API doesn't support multiple match types in a single query
|
|
560
|
-
if (params.match) {
|
|
561
|
-
const matchValues = Array.isArray(params.match)
|
|
562
|
-
? params.match
|
|
563
|
-
: [params.match];
|
|
564
|
-
// GitHub API limitation: can't use both in:file and in:path in same query
|
|
565
|
-
// Use the first match type when multiple are provided
|
|
566
|
-
const matchValue = matchValues[0];
|
|
567
|
-
args.push(`--match=${matchValue}`);
|
|
568
|
-
}
|
|
569
|
-
// Handle owner parameter - can be string or array
|
|
570
|
-
if (params.owner && !params.repo) {
|
|
571
|
-
const ownerValues = Array.isArray(params.owner)
|
|
572
|
-
? params.owner
|
|
573
|
-
: [params.owner];
|
|
574
|
-
ownerValues.forEach(owner => args.push(`--owner=${owner}`));
|
|
575
|
-
}
|
|
576
|
-
// Handle repository filters
|
|
577
|
-
if (params.owner && params.repo) {
|
|
578
|
-
const owners = Array.isArray(params.owner)
|
|
579
|
-
? params.owner
|
|
580
|
-
: [params.owner];
|
|
581
|
-
const repos = params.repo;
|
|
582
|
-
// Create repo filters for each owner/repo combination
|
|
583
|
-
owners.forEach(owner => {
|
|
584
|
-
repos.forEach(repo => {
|
|
585
|
-
// Handle both "owner/repo" format and just "repo" format
|
|
586
|
-
if (repo.includes('/')) {
|
|
587
|
-
args.push(`--repo=${repo}`);
|
|
588
|
-
}
|
|
589
|
-
else {
|
|
590
|
-
args.push(`--repo=${owner}/${repo}`);
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
});
|
|
594
|
-
}
|
|
595
|
-
// JSON output with all available fields
|
|
596
|
-
args.push('--json=repository,path,textMatches,sha,url');
|
|
619
|
+
const args = buildGitHubCliArgs(params);
|
|
597
620
|
const result = await executeGitHubCommand('search', args, {
|
|
598
621
|
cache: false,
|
|
599
622
|
});
|
|
600
623
|
return result;
|
|
601
624
|
}
|
|
602
625
|
catch (error) {
|
|
603
|
-
return createErrorResult$1('
|
|
626
|
+
return createErrorResult$1('Code search command failed - verify GitHub CLI is authenticated', error);
|
|
604
627
|
}
|
|
605
628
|
});
|
|
606
629
|
}
|
|
607
630
|
|
|
631
|
+
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.`;
|
|
608
633
|
function registerFetchGitHubFileContentTool(server) {
|
|
609
|
-
server.tool(
|
|
634
|
+
server.tool(TOOL_NAME$7, DESCRIPTION$7, {
|
|
610
635
|
owner: z
|
|
611
636
|
.string()
|
|
612
637
|
.min(1)
|
|
638
|
+
.max(100)
|
|
639
|
+
.regex(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/)
|
|
613
640
|
.describe(`Repository owner/organization (e.g., 'microsoft', 'facebook')`),
|
|
614
641
|
repo: z
|
|
615
642
|
.string()
|
|
616
643
|
.min(1)
|
|
644
|
+
.max(100)
|
|
645
|
+
.regex(/^[a-zA-Z0-9._-]+$/)
|
|
617
646
|
.describe(`Repository name (e.g., 'vscode', 'react'). Case-sensitive.`),
|
|
618
647
|
branch: z
|
|
619
648
|
.string()
|
|
620
649
|
.min(1)
|
|
650
|
+
.max(255)
|
|
651
|
+
.regex(/^[^\s]+$/)
|
|
621
652
|
.describe(`Branch name (e.g., 'main', 'master'). Auto-fallback to common branches if not found.`),
|
|
622
653
|
filePath: z
|
|
623
654
|
.string()
|
|
624
655
|
.min(1)
|
|
625
656
|
.describe(`File path from repository root (e.g., 'README.md', 'src/index.js'). Use github_get_contents to explore structure.`),
|
|
626
657
|
}, {
|
|
627
|
-
title:
|
|
628
|
-
description:
|
|
629
|
-
`Handles text files up to 500KB, detects binary files, supports branch fallback.`,
|
|
658
|
+
title: TOOL_NAME$7,
|
|
659
|
+
description: DESCRIPTION$7,
|
|
630
660
|
readOnlyHint: true,
|
|
631
661
|
destructiveHint: false,
|
|
632
662
|
idempotentHint: true,
|
|
@@ -661,26 +691,7 @@ function registerFetchGitHubFileContentTool(server) {
|
|
|
661
691
|
return result;
|
|
662
692
|
}
|
|
663
693
|
catch (error) {
|
|
664
|
-
|
|
665
|
-
let suggestions = [];
|
|
666
|
-
if (errorMessage.includes('404') ||
|
|
667
|
-
errorMessage.includes('Not Found')) {
|
|
668
|
-
suggestions = [
|
|
669
|
-
TOOL_NAMES.GITHUB_GET_CONTENTS,
|
|
670
|
-
TOOL_NAMES.GITHUB_SEARCH_CODE,
|
|
671
|
-
];
|
|
672
|
-
}
|
|
673
|
-
else if (errorMessage.includes('403') ||
|
|
674
|
-
errorMessage.includes('Forbidden')) {
|
|
675
|
-
suggestions = [TOOL_NAMES.API_STATUS_CHECK];
|
|
676
|
-
}
|
|
677
|
-
else if (errorMessage.includes('branch')) {
|
|
678
|
-
suggestions = [TOOL_NAMES.GITHUB_GET_CONTENTS];
|
|
679
|
-
}
|
|
680
|
-
else {
|
|
681
|
-
suggestions = getErrorSuggestions(TOOL_NAMES.GITHUB_GET_FILE_CONTENT);
|
|
682
|
-
}
|
|
683
|
-
return createResult(`Failed to fetch file content: ${errorMessage}. Context: ${args.owner}/${args.repo}/${args.filePath} on ${args.branch}`, true, suggestions);
|
|
694
|
+
return createResult('File fetch failed - verify file path exists or try github_get_contents first', true);
|
|
684
695
|
}
|
|
685
696
|
});
|
|
686
697
|
}
|
|
@@ -713,19 +724,19 @@ async function fetchGitHubFileContent(params) {
|
|
|
713
724
|
}
|
|
714
725
|
// Handle common errors
|
|
715
726
|
if (errorMsg.includes('404')) {
|
|
716
|
-
return createErrorResult$1(
|
|
727
|
+
return createErrorResult$1('File not found - verify path with github_get_contents first', new Error(filePath));
|
|
717
728
|
}
|
|
718
729
|
else if (errorMsg.includes('403')) {
|
|
719
|
-
return createErrorResult$1(
|
|
730
|
+
return createErrorResult$1('Access denied - repository may be private or require authentication', new Error(`${owner}/${repo}`));
|
|
720
731
|
}
|
|
721
732
|
else {
|
|
722
|
-
return createErrorResult$1(
|
|
733
|
+
return createErrorResult$1('Fetch failed - check repository and file path', new Error(errorMsg));
|
|
723
734
|
}
|
|
724
735
|
}
|
|
725
736
|
return await processFileContent(result, owner, repo, branch, filePath);
|
|
726
737
|
}
|
|
727
738
|
catch (error) {
|
|
728
|
-
return createErrorResult$1(
|
|
739
|
+
return createErrorResult$1('Unexpected error during file fetch - check connection and permissions', error);
|
|
729
740
|
}
|
|
730
741
|
});
|
|
731
742
|
}
|
|
@@ -735,30 +746,30 @@ async function processFileContent(result, owner, repo, branch, filePath) {
|
|
|
735
746
|
const fileData = JSON.parse(execResult.result);
|
|
736
747
|
// Check if it's a directory
|
|
737
748
|
if (Array.isArray(fileData)) {
|
|
738
|
-
return createErrorResult$1(
|
|
749
|
+
return createErrorResult$1('Path is directory - use github_get_contents to browse directory structure', new Error(filePath));
|
|
739
750
|
}
|
|
740
751
|
const fileSize = fileData.size || 0;
|
|
741
752
|
const MAX_FILE_SIZE = 500 * 1024; // 500KB limit for simplicity
|
|
742
753
|
// Check file size
|
|
743
754
|
if (fileSize > MAX_FILE_SIZE) {
|
|
744
|
-
return createErrorResult$1(
|
|
755
|
+
return createErrorResult$1('File too large - files over 500KB cannot be fetched', new Error(`${Math.round(fileSize / 1024)}KB > 500KB`));
|
|
745
756
|
}
|
|
746
757
|
// Get and decode content
|
|
747
758
|
const base64Content = fileData.content?.replace(/\s/g, ''); // Remove all whitespace
|
|
748
759
|
if (!base64Content) {
|
|
749
|
-
return createErrorResult$1(
|
|
760
|
+
return createErrorResult$1('Empty file - file has no content to display', new Error(filePath));
|
|
750
761
|
}
|
|
751
762
|
let decodedContent;
|
|
752
763
|
try {
|
|
753
764
|
const buffer = Buffer.from(base64Content, 'base64');
|
|
754
765
|
// Simple binary check - look for null bytes
|
|
755
766
|
if (buffer.indexOf(0) !== -1) {
|
|
756
|
-
return createErrorResult$1(
|
|
767
|
+
return createErrorResult$1('Binary file detected - cannot display binary content as text', new Error(filePath));
|
|
757
768
|
}
|
|
758
769
|
decodedContent = buffer.toString('utf-8');
|
|
759
770
|
}
|
|
760
771
|
catch (decodeError) {
|
|
761
|
-
return createErrorResult$1(
|
|
772
|
+
return createErrorResult$1('Decode failed - file encoding not supported or corrupted', new Error(filePath));
|
|
762
773
|
}
|
|
763
774
|
// Return simplified response
|
|
764
775
|
const response = {
|
|
@@ -776,8 +787,17 @@ async function processFileContent(result, owner, repo, branch, filePath) {
|
|
|
776
787
|
return createSuccessResult$1(response);
|
|
777
788
|
}
|
|
778
789
|
|
|
790
|
+
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`;
|
|
779
799
|
function registerSearchGitHubReposTool(server) {
|
|
780
|
-
server.tool(
|
|
800
|
+
server.tool(TOOL_NAME$6, DESCRIPTION$6, {
|
|
781
801
|
query: z
|
|
782
802
|
.string()
|
|
783
803
|
.optional()
|
|
@@ -786,52 +806,66 @@ function registerSearchGitHubReposTool(server) {
|
|
|
786
806
|
owner: z
|
|
787
807
|
.string()
|
|
788
808
|
.optional()
|
|
789
|
-
.describe('Repository owner/organization.
|
|
809
|
+
.describe('Repository owner/organization (e.g., "microsoft", "facebook").'),
|
|
790
810
|
language: z
|
|
791
811
|
.string()
|
|
792
812
|
.optional()
|
|
793
|
-
.describe('Programming language.
|
|
813
|
+
.describe('Programming language (e.g., "javascript", "python", "go").'),
|
|
794
814
|
stars: z
|
|
795
815
|
.string()
|
|
796
816
|
.optional()
|
|
797
|
-
.describe('Stars count with ranges: "100", ">500", "<50", "10..100", ">=1000".
|
|
817
|
+
.describe('Stars count with ranges: "100", ">500", "<50", "10..100", ">=1000". Use >100 for quality projects.'),
|
|
798
818
|
topic: z
|
|
799
819
|
.array(z.string())
|
|
800
820
|
.optional()
|
|
801
|
-
.describe('Filter by topics.
|
|
802
|
-
forks: z
|
|
821
|
+
.describe('Filter by topics (e.g., ["cli", "typescript", "api"]).'),
|
|
822
|
+
forks: z.number().optional().describe('Exact forks count.'),
|
|
823
|
+
numberOfTopics: z
|
|
803
824
|
.number()
|
|
804
825
|
.optional()
|
|
805
|
-
.describe('
|
|
826
|
+
.describe('Filter on number of topics.'),
|
|
806
827
|
// SECONDARY FILTERS (require query or primary filter)
|
|
807
828
|
license: z
|
|
808
829
|
.array(z.string())
|
|
809
830
|
.optional()
|
|
810
|
-
.describe('License types.
|
|
831
|
+
.describe('License types (e.g., ["mit", "apache-2.0"]).'),
|
|
811
832
|
match: z
|
|
812
833
|
.enum(['name', 'description', 'readme'])
|
|
813
834
|
.optional()
|
|
814
|
-
.describe('Search scope
|
|
835
|
+
.describe('Search scope: "name", "description", or "readme".'),
|
|
815
836
|
visibility: z
|
|
816
837
|
.enum(['public', 'private', 'internal'])
|
|
817
838
|
.optional()
|
|
818
|
-
.describe('Repository visibility
|
|
839
|
+
.describe('Repository visibility filter.'),
|
|
819
840
|
created: z
|
|
820
841
|
.string()
|
|
821
842
|
.optional()
|
|
822
|
-
.describe('Created date filter: ">2020-01-01", "<2023-12-31"
|
|
843
|
+
.describe('Created date filter: ">2020-01-01", "<2023-12-31", "2022-01-01..2023-12-31".'),
|
|
823
844
|
updated: z
|
|
824
845
|
.string()
|
|
825
846
|
.optional()
|
|
826
|
-
.describe('Updated date filter
|
|
827
|
-
archived: z
|
|
828
|
-
.boolean()
|
|
829
|
-
.optional()
|
|
830
|
-
.describe('Archived state. REQUIRES query or primary filter.'),
|
|
847
|
+
.describe('Updated date filter (same format as created).'),
|
|
848
|
+
archived: z.boolean().optional().describe('Filter by archived state.'),
|
|
831
849
|
includeForks: z
|
|
832
850
|
.enum(['false', 'true', 'only'])
|
|
833
851
|
.optional()
|
|
834
|
-
.describe('Include forks
|
|
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").'),
|
|
835
869
|
// Sorting and limits
|
|
836
870
|
sort: z
|
|
837
871
|
.enum(['forks', 'help-wanted-issues', 'stars', 'updated', 'best-match'])
|
|
@@ -852,8 +886,8 @@ function registerSearchGitHubReposTool(server) {
|
|
|
852
886
|
.default(25)
|
|
853
887
|
.describe('Maximum results (default: 25, max: 50)'),
|
|
854
888
|
}, {
|
|
855
|
-
title:
|
|
856
|
-
description:
|
|
889
|
+
title: TOOL_NAME$6,
|
|
890
|
+
description: DESCRIPTION$6,
|
|
857
891
|
readOnlyHint: true,
|
|
858
892
|
destructiveHint: false,
|
|
859
893
|
idempotentHint: true,
|
|
@@ -875,7 +909,7 @@ function registerSearchGitHubReposTool(server) {
|
|
|
875
909
|
return result;
|
|
876
910
|
}
|
|
877
911
|
catch (error) {
|
|
878
|
-
return createResult(
|
|
912
|
+
return createResult('Repository search failed - check query syntax, filters, or try broader terms', true);
|
|
879
913
|
}
|
|
880
914
|
});
|
|
881
915
|
}
|
|
@@ -942,7 +976,7 @@ async function searchGitHubRepos(params) {
|
|
|
942
976
|
isPrivate: repo.isPrivate || false,
|
|
943
977
|
isArchived: repo.isArchived || false,
|
|
944
978
|
isFork: repo.isFork || false,
|
|
945
|
-
topics: [], //
|
|
979
|
+
topics: [], // GitHub CLI search repos doesn't provide topics in JSON output
|
|
946
980
|
license: repo.license?.name || null,
|
|
947
981
|
hasIssues: repo.hasIssues || false,
|
|
948
982
|
openIssuesCount: repo.openIssuesCount || 0,
|
|
@@ -966,17 +1000,11 @@ async function searchGitHubRepos(params) {
|
|
|
966
1000
|
}
|
|
967
1001
|
: {
|
|
968
1002
|
repositories: [],
|
|
969
|
-
suggestions: [
|
|
970
|
-
`${TOOL_NAMES.NPM_PACKAGE_SEARCH} "${params.query || 'package'}"`,
|
|
971
|
-
`${TOOL_NAMES.GITHUB_SEARCH_CODE} "${params.query || 'code'}"`,
|
|
972
|
-
'Try broader search terms',
|
|
973
|
-
'Check spelling and try synonyms',
|
|
974
|
-
],
|
|
975
1003
|
}),
|
|
976
1004
|
});
|
|
977
1005
|
}
|
|
978
1006
|
catch (error) {
|
|
979
|
-
return createErrorResult$1('
|
|
1007
|
+
return createErrorResult$1('GitHub repository search failed - verify connection or try simpler query', error);
|
|
980
1008
|
}
|
|
981
1009
|
});
|
|
982
1010
|
}
|
|
@@ -1009,12 +1037,22 @@ function buildGitHubReposSearchCommand(params) {
|
|
|
1009
1037
|
});
|
|
1010
1038
|
}
|
|
1011
1039
|
else {
|
|
1012
|
-
// For simple queries,
|
|
1013
|
-
|
|
1014
|
-
|
|
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
|
+
});
|
|
1015
1052
|
}
|
|
1016
1053
|
}
|
|
1017
1054
|
// Add JSON output with specific fields for structured data parsing
|
|
1055
|
+
// Note: 'topics' field is not available in GitHub CLI search repos JSON output
|
|
1018
1056
|
args.push('--json', 'name,fullName,description,language,stargazersCount,forksCount,updatedAt,createdAt,url,owner,isPrivate,license,hasIssues,openIssuesCount,isArchived,isFork,visibility');
|
|
1019
1057
|
// PRIMARY FILTERS - Handle owner as single string (BaseSearchParams) or array
|
|
1020
1058
|
if (params.owner) {
|
|
@@ -1029,6 +1067,8 @@ function buildGitHubReposSearchCommand(params) {
|
|
|
1029
1067
|
args.push(`--forks=${params.forks}`);
|
|
1030
1068
|
if (params.topic && params.topic.length > 0)
|
|
1031
1069
|
args.push(`--topic=${params.topic.join(',')}`);
|
|
1070
|
+
if (params.numberOfTopics !== undefined)
|
|
1071
|
+
args.push(`--number-topics=${params.numberOfTopics}`);
|
|
1032
1072
|
// Only add stars filter if it's a valid numeric value or range
|
|
1033
1073
|
if (params.stars !== undefined &&
|
|
1034
1074
|
params.stars !== '*' &&
|
|
@@ -1037,7 +1077,7 @@ function buildGitHubReposSearchCommand(params) {
|
|
|
1037
1077
|
const starsValue = params.stars.trim();
|
|
1038
1078
|
const isValidStars = /^(\d+|>\d+|<\d+|\d+\.\.\d+|>=\d+|<=\d+)$/.test(starsValue);
|
|
1039
1079
|
if (isValidStars) {
|
|
1040
|
-
args.push(`--stars
|
|
1080
|
+
args.push(`--stars=${params.stars}`);
|
|
1041
1081
|
}
|
|
1042
1082
|
}
|
|
1043
1083
|
// SECONDARY FILTERS - only add if we have primary filters
|
|
@@ -1055,6 +1095,14 @@ function buildGitHubReposSearchCommand(params) {
|
|
|
1055
1095
|
args.push(`--updated="${params.updated}"`);
|
|
1056
1096
|
if (params.visibility)
|
|
1057
1097
|
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}`);
|
|
1104
|
+
if (params.size)
|
|
1105
|
+
args.push(`--size=${params.size}`);
|
|
1058
1106
|
// SORTING AND LIMITS
|
|
1059
1107
|
if (params.limit)
|
|
1060
1108
|
args.push(`--limit=${params.limit}`);
|
|
@@ -1068,12 +1116,14 @@ function buildGitHubReposSearchCommand(params) {
|
|
|
1068
1116
|
return { command: 'search', args };
|
|
1069
1117
|
}
|
|
1070
1118
|
|
|
1119
|
+
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.`;
|
|
1071
1121
|
function registerSearchGitHubCommitsTool(server) {
|
|
1072
|
-
server.tool(
|
|
1122
|
+
server.tool(TOOL_NAME$5, DESCRIPTION$5, {
|
|
1073
1123
|
query: z
|
|
1074
1124
|
.string()
|
|
1075
1125
|
.optional()
|
|
1076
|
-
.describe('Search query with
|
|
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.'),
|
|
1077
1127
|
// Basic filters
|
|
1078
1128
|
owner: z
|
|
1079
1129
|
.string()
|
|
@@ -1130,10 +1180,10 @@ function registerSearchGitHubCommitsTool(server) {
|
|
|
1130
1180
|
.max(50)
|
|
1131
1181
|
.optional()
|
|
1132
1182
|
.default(25)
|
|
1133
|
-
.describe('Maximum results (default: 25)'),
|
|
1183
|
+
.describe('Maximum results (default: 25, max: 50)'),
|
|
1134
1184
|
}, {
|
|
1135
|
-
title:
|
|
1136
|
-
description:
|
|
1185
|
+
title: TOOL_NAME$5,
|
|
1186
|
+
description: DESCRIPTION$5,
|
|
1137
1187
|
readOnlyHint: true,
|
|
1138
1188
|
destructiveHint: false,
|
|
1139
1189
|
idempotentHint: true,
|
|
@@ -1152,7 +1202,7 @@ function registerSearchGitHubCommitsTool(server) {
|
|
|
1152
1202
|
return result;
|
|
1153
1203
|
}
|
|
1154
1204
|
catch (error) {
|
|
1155
|
-
return createResult(
|
|
1205
|
+
return createResult('Commit search failed - check query syntax, filters, or repository access', true);
|
|
1156
1206
|
}
|
|
1157
1207
|
});
|
|
1158
1208
|
}
|
|
@@ -1247,17 +1297,10 @@ async function searchGitHubCommits(params) {
|
|
|
1247
1297
|
query: params.query,
|
|
1248
1298
|
total: 0,
|
|
1249
1299
|
commits: [],
|
|
1250
|
-
suggestions: [
|
|
1251
|
-
`${TOOL_NAMES.GITHUB_SEARCH_PULL_REQUESTS} "${params.query || 'pr'}"`,
|
|
1252
|
-
`${TOOL_NAMES.GITHUB_SEARCH_ISSUES} "${params.query || 'issue'}"`,
|
|
1253
|
-
`${TOOL_NAMES.GITHUB_SEARCH_CODE} "${params.query || 'code'}"`,
|
|
1254
|
-
'Try broader search terms',
|
|
1255
|
-
'Check spelling and try synonyms',
|
|
1256
|
-
],
|
|
1257
1300
|
});
|
|
1258
1301
|
}
|
|
1259
1302
|
catch (error) {
|
|
1260
|
-
return createErrorResult$1('
|
|
1303
|
+
return createErrorResult$1('GitHub commit search failed - verify repository exists or try different filters', error);
|
|
1261
1304
|
}
|
|
1262
1305
|
});
|
|
1263
1306
|
}
|
|
@@ -1347,12 +1390,22 @@ function buildGitHubCommitsSearchCommand(params) {
|
|
|
1347
1390
|
}
|
|
1348
1391
|
|
|
1349
1392
|
// TODO: add PR commeents. e.g, gh pr view <PR_NUMBER_OR_URL_OR_BRANCH> --comments
|
|
1393
|
+
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.`;
|
|
1350
1403
|
function registerSearchGitHubPullRequestsTool(server) {
|
|
1351
|
-
server.tool(
|
|
1404
|
+
server.tool(TOOL_NAME$4, DESCRIPTION$4, {
|
|
1352
1405
|
query: z
|
|
1353
1406
|
.string()
|
|
1354
1407
|
.min(1, 'Search query is required and cannot be empty')
|
|
1355
|
-
.describe('Search query
|
|
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.'),
|
|
1356
1409
|
owner: z.string().optional().describe('Repository owner/organization'),
|
|
1357
1410
|
repo: z.string().optional().describe('Repository name'),
|
|
1358
1411
|
author: z.string().optional().describe('Filter by pull request author'),
|
|
@@ -1377,6 +1430,15 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
1377
1430
|
mergedAt: z.string().optional().describe('Filter by merged date'),
|
|
1378
1431
|
closed: z.string().optional().describe('Filter by closed date'),
|
|
1379
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'),
|
|
1380
1442
|
limit: z
|
|
1381
1443
|
.number()
|
|
1382
1444
|
.int()
|
|
@@ -1384,7 +1446,7 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
1384
1446
|
.max(50)
|
|
1385
1447
|
.optional()
|
|
1386
1448
|
.default(25)
|
|
1387
|
-
.describe('Maximum results (default: 25)'),
|
|
1449
|
+
.describe('Maximum results (default: 25, max: 50)'),
|
|
1388
1450
|
sort: z
|
|
1389
1451
|
.enum([
|
|
1390
1452
|
'comments',
|
|
@@ -1407,24 +1469,24 @@ function registerSearchGitHubPullRequestsTool(server) {
|
|
|
1407
1469
|
.default('desc')
|
|
1408
1470
|
.describe('Order (default: desc)'),
|
|
1409
1471
|
}, {
|
|
1410
|
-
title:
|
|
1411
|
-
description:
|
|
1472
|
+
title: TOOL_NAME$4,
|
|
1473
|
+
description: DESCRIPTION$4,
|
|
1412
1474
|
readOnlyHint: true,
|
|
1413
1475
|
destructiveHint: false,
|
|
1414
1476
|
idempotentHint: true,
|
|
1415
1477
|
openWorldHint: true,
|
|
1416
1478
|
}, async (args) => {
|
|
1417
1479
|
if (!args.query?.trim()) {
|
|
1418
|
-
return createErrorResult$1('Search query is required and cannot be empty', new Error('Invalid query'));
|
|
1480
|
+
return createErrorResult$1('Search query is required and cannot be empty - provide keywords to search for pull requests', new Error('Invalid query'));
|
|
1419
1481
|
}
|
|
1420
1482
|
if (args.query.length > 256) {
|
|
1421
|
-
return createErrorResult$1('Search query is too long. Please limit to 256 characters or less
|
|
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'));
|
|
1422
1484
|
}
|
|
1423
1485
|
try {
|
|
1424
1486
|
return await searchGitHubPullRequests(args);
|
|
1425
1487
|
}
|
|
1426
1488
|
catch (error) {
|
|
1427
|
-
return createErrorResult$1('
|
|
1489
|
+
return createErrorResult$1('GitHub pull requests search failed - verify repository access and query syntax', error);
|
|
1428
1490
|
}
|
|
1429
1491
|
});
|
|
1430
1492
|
}
|
|
@@ -1516,6 +1578,12 @@ function buildGitHubPullRequestsAPICommand(params) {
|
|
|
1516
1578
|
queryParts.push(`merged:${params.mergedAt}`);
|
|
1517
1579
|
if (params.draft !== undefined)
|
|
1518
1580
|
queryParts.push(`draft:${params.draft}`);
|
|
1581
|
+
if (params.checks)
|
|
1582
|
+
queryParts.push(`status:${params.checks}`);
|
|
1583
|
+
if (params.merged !== undefined)
|
|
1584
|
+
queryParts.push(`is:${params.merged ? 'merged' : 'unmerged'}`);
|
|
1585
|
+
if (params.review)
|
|
1586
|
+
queryParts.push(`review:${params.review}`);
|
|
1519
1587
|
// Add type qualifier to search only pull requests
|
|
1520
1588
|
queryParts.push('type:pr');
|
|
1521
1589
|
const query = queryParts.filter(Boolean).join(' ');
|
|
@@ -1528,13 +1596,22 @@ function buildGitHubPullRequestsAPICommand(params) {
|
|
|
1528
1596
|
return { command: 'api', args: [apiPath] };
|
|
1529
1597
|
}
|
|
1530
1598
|
|
|
1599
|
+
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.`;
|
|
1531
1608
|
const MAX_DESCRIPTION_LENGTH = 100;
|
|
1532
1609
|
const MAX_KEYWORDS = 10;
|
|
1533
1610
|
function registerNpmSearchTool(server) {
|
|
1534
|
-
server.tool(
|
|
1611
|
+
server.tool(TOOL_NAME$3, DESCRIPTION$3, {
|
|
1535
1612
|
queries: z
|
|
1536
1613
|
.union([z.string(), z.array(z.string())])
|
|
1537
|
-
.describe('Package names or keywords to search for'),
|
|
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.'),
|
|
1538
1615
|
searchlimit: z
|
|
1539
1616
|
.number()
|
|
1540
1617
|
.int()
|
|
@@ -1542,10 +1619,10 @@ function registerNpmSearchTool(server) {
|
|
|
1542
1619
|
.max(50)
|
|
1543
1620
|
.optional()
|
|
1544
1621
|
.default(20)
|
|
1545
|
-
.describe('Max results per query (default: 20)'),
|
|
1622
|
+
.describe('Max results per query (default: 20, max: 50)'),
|
|
1546
1623
|
}, {
|
|
1547
|
-
title:
|
|
1548
|
-
description:
|
|
1624
|
+
title: TOOL_NAME$3,
|
|
1625
|
+
description: DESCRIPTION$3,
|
|
1549
1626
|
readOnlyHint: true,
|
|
1550
1627
|
destructiveHint: false,
|
|
1551
1628
|
idempotentHint: true,
|
|
@@ -1575,12 +1652,10 @@ function registerNpmSearchTool(server) {
|
|
|
1575
1652
|
results: deduplicatedPackages,
|
|
1576
1653
|
});
|
|
1577
1654
|
}
|
|
1578
|
-
|
|
1579
|
-
return createResult('No packages found', true, suggestions);
|
|
1655
|
+
return createResult('No packages found', true);
|
|
1580
1656
|
}
|
|
1581
1657
|
catch (error) {
|
|
1582
|
-
|
|
1583
|
-
return createResult(`Search failed: ${error.message}`, true, suggestions);
|
|
1658
|
+
return createResult('NPM package search failed - check search terms or try different keywords', true);
|
|
1584
1659
|
}
|
|
1585
1660
|
});
|
|
1586
1661
|
}
|
|
@@ -1632,8 +1707,10 @@ function parseNpmSearchOutput(output) {
|
|
|
1632
1707
|
}
|
|
1633
1708
|
}
|
|
1634
1709
|
|
|
1710
|
+
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.`;
|
|
1635
1712
|
function registerViewRepositoryStructureTool(server) {
|
|
1636
|
-
server.tool(
|
|
1713
|
+
server.tool(TOOL_NAME$2, DESCRIPTION$2, {
|
|
1637
1714
|
owner: z
|
|
1638
1715
|
.string()
|
|
1639
1716
|
.min(1)
|
|
@@ -1663,8 +1740,8 @@ function registerViewRepositoryStructureTool(server) {
|
|
|
1663
1740
|
.describe('Directory path within repository (e.g., "src/components", "packages/core"). ' +
|
|
1664
1741
|
'Leave empty for root. Use previous results to navigate deeper.'),
|
|
1665
1742
|
}, {
|
|
1666
|
-
title:
|
|
1667
|
-
description:
|
|
1743
|
+
title: TOOL_NAME$2,
|
|
1744
|
+
description: DESCRIPTION$2,
|
|
1668
1745
|
readOnlyHint: true,
|
|
1669
1746
|
destructiveHint: false,
|
|
1670
1747
|
idempotentHint: true,
|
|
@@ -1694,48 +1771,7 @@ function registerViewRepositoryStructureTool(server) {
|
|
|
1694
1771
|
}
|
|
1695
1772
|
catch (error) {
|
|
1696
1773
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1697
|
-
|
|
1698
|
-
let suggestions = [];
|
|
1699
|
-
const context = `${args.owner}/${args.repo}`;
|
|
1700
|
-
if (errorMessage.includes('404') ||
|
|
1701
|
-
errorMessage.includes('Not Found')) {
|
|
1702
|
-
if (args.path) {
|
|
1703
|
-
suggestions = [
|
|
1704
|
-
`Try exploring root first: github_get_contents(owner: "${args.owner}", repo: "${args.repo}", branch: "${args.branch}", path: "")`,
|
|
1705
|
-
`Search for similar paths: github_search_code with path filters`,
|
|
1706
|
-
];
|
|
1707
|
-
}
|
|
1708
|
-
else {
|
|
1709
|
-
suggestions = [
|
|
1710
|
-
`Search for repository: github_search_repositories(query: "${args.repo}")`,
|
|
1711
|
-
`Verify owner exists: github_search_repositories(owner: "${args.owner}")`,
|
|
1712
|
-
];
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
|
-
else if (errorMessage.includes('403') ||
|
|
1716
|
-
errorMessage.includes('Forbidden')) {
|
|
1717
|
-
suggestions = [
|
|
1718
|
-
`Check authentication status: api_status_check`,
|
|
1719
|
-
`Try public repositories first to verify access`,
|
|
1720
|
-
];
|
|
1721
|
-
}
|
|
1722
|
-
else if (errorMessage.includes('rate limit')) {
|
|
1723
|
-
suggestions = [
|
|
1724
|
-
`Check rate limit status: api_status_check`,
|
|
1725
|
-
`Wait before retrying or use authenticated requests`,
|
|
1726
|
-
];
|
|
1727
|
-
}
|
|
1728
|
-
else if (errorMessage.includes('invalid') ||
|
|
1729
|
-
errorMessage.includes('branch')) {
|
|
1730
|
-
suggestions = [
|
|
1731
|
-
`Find valid branches: github_search_repositories(query: "${context}")`,
|
|
1732
|
-
`Try common branches: main, master, develop`,
|
|
1733
|
-
];
|
|
1734
|
-
}
|
|
1735
|
-
else {
|
|
1736
|
-
suggestions = getErrorSuggestions(TOOL_NAMES.GITHUB_GET_CONTENTS);
|
|
1737
|
-
}
|
|
1738
|
-
return createResult(`Repository exploration failed: ${errorMessage}. Context: ${context} on ${args.branch}${args.path ? ` at ${args.path}` : ''}`, true, suggestions);
|
|
1774
|
+
return createResult(`Repository exploration failed: ${errorMessage} - verify repository exists and is accessible`, true);
|
|
1739
1775
|
}
|
|
1740
1776
|
});
|
|
1741
1777
|
}
|
|
@@ -1746,7 +1782,7 @@ function registerViewRepositoryStructureTool(server) {
|
|
|
1746
1782
|
* - Smart branch detection: fetches repository default branch automatically
|
|
1747
1783
|
* - Intelligent fallback: tries requested -> default -> common branches
|
|
1748
1784
|
* - Input validation: prevents path traversal and validates GitHub naming
|
|
1749
|
-
* -
|
|
1785
|
+
* - Clear error context: provides descriptive error messages
|
|
1750
1786
|
* - Efficient caching: avoids redundant API calls
|
|
1751
1787
|
*/
|
|
1752
1788
|
async function viewRepositoryStructure(params) {
|
|
@@ -1789,19 +1825,17 @@ async function viewRepositoryStructure(params) {
|
|
|
1789
1825
|
const errorMsg = lastError?.message || 'Unknown error';
|
|
1790
1826
|
if (errorMsg.includes('404') || errorMsg.includes('Not Found')) {
|
|
1791
1827
|
if (path) {
|
|
1792
|
-
throw new Error(`Path "${path}" not found
|
|
1793
|
-
`Use github_get_contents with empty path first to discover the repository structure.`);
|
|
1828
|
+
throw new Error(`Path "${path}" not found - verify path exists or use github_search_code to find files`);
|
|
1794
1829
|
}
|
|
1795
1830
|
else {
|
|
1796
|
-
throw new Error(`Repository ${owner}/${repo}
|
|
1797
|
-
`Tried branches: ${branchesToTry.join(', ')}`);
|
|
1831
|
+
throw new Error(`Repository not found: ${owner}/${repo} - verify owner/repo names or use github_search_repositories`);
|
|
1798
1832
|
}
|
|
1799
1833
|
}
|
|
1800
1834
|
else if (errorMsg.includes('403') || errorMsg.includes('Forbidden')) {
|
|
1801
|
-
throw new Error(`Access denied to repository ${owner}/${repo}`);
|
|
1835
|
+
throw new Error(`Access denied to repository ${owner}/${repo} - repository may be private or require authentication`);
|
|
1802
1836
|
}
|
|
1803
1837
|
else {
|
|
1804
|
-
throw new Error(`
|
|
1838
|
+
throw new Error(`Access failed: ${owner}/${repo} - check connection or repository permissions`);
|
|
1805
1839
|
}
|
|
1806
1840
|
}
|
|
1807
1841
|
// Limit total items to 100 for efficiency
|
|
@@ -1859,7 +1893,7 @@ async function viewRepositoryStructure(params) {
|
|
|
1859
1893
|
}
|
|
1860
1894
|
catch (error) {
|
|
1861
1895
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1862
|
-
return createErrorResult$1(
|
|
1896
|
+
return createErrorResult$1('Repository access failed - verify repository exists and check authentication', new Error(errorMessage));
|
|
1863
1897
|
}
|
|
1864
1898
|
});
|
|
1865
1899
|
}
|
|
@@ -1895,12 +1929,22 @@ async function getSmartBranchFallback(owner, repo, requestedBranch) {
|
|
|
1895
1929
|
return branches;
|
|
1896
1930
|
}
|
|
1897
1931
|
|
|
1932
|
+
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.`;
|
|
1898
1942
|
function registerSearchGitHubIssuesTool(server) {
|
|
1899
|
-
server.tool(
|
|
1943
|
+
server.tool(TOOL_NAME$1, DESCRIPTION$1, {
|
|
1900
1944
|
query: z
|
|
1901
1945
|
.string()
|
|
1902
1946
|
.min(1, 'Search query is required and cannot be empty')
|
|
1903
|
-
.describe('Search query
|
|
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.'),
|
|
1904
1948
|
owner: z
|
|
1905
1949
|
.string()
|
|
1906
1950
|
.min(1)
|
|
@@ -1993,26 +2037,26 @@ function registerSearchGitHubIssuesTool(server) {
|
|
|
1993
2037
|
.max(50)
|
|
1994
2038
|
.optional()
|
|
1995
2039
|
.default(25)
|
|
1996
|
-
.describe('Maximum results (default: 25)'),
|
|
2040
|
+
.describe('Maximum results (default: 25, max: 50)'),
|
|
1997
2041
|
}, {
|
|
1998
|
-
title:
|
|
1999
|
-
description:
|
|
2042
|
+
title: TOOL_NAME$1,
|
|
2043
|
+
description: DESCRIPTION$1,
|
|
2000
2044
|
readOnlyHint: true,
|
|
2001
2045
|
destructiveHint: false,
|
|
2002
2046
|
idempotentHint: true,
|
|
2003
2047
|
openWorldHint: true,
|
|
2004
2048
|
}, async (args) => {
|
|
2005
2049
|
if (!args.query?.trim()) {
|
|
2006
|
-
return createErrorResult$1('Search query is required and cannot be empty', new Error('Invalid query'));
|
|
2050
|
+
return createErrorResult$1('Search query is required and cannot be empty - provide keywords to search for issues', new Error('Invalid query'));
|
|
2007
2051
|
}
|
|
2008
2052
|
if (args.query.length > 256) {
|
|
2009
|
-
return createErrorResult$1('Search query is too long. Please limit to 256 characters or less
|
|
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'));
|
|
2010
2054
|
}
|
|
2011
2055
|
try {
|
|
2012
2056
|
return await searchGitHubIssues(args);
|
|
2013
2057
|
}
|
|
2014
2058
|
catch (error) {
|
|
2015
|
-
return createErrorResult$1('
|
|
2059
|
+
return createErrorResult$1('GitHub issues search failed - check repository exists and query is valid', error);
|
|
2016
2060
|
}
|
|
2017
2061
|
});
|
|
2018
2062
|
}
|
|
@@ -2105,15 +2149,20 @@ function buildGitHubIssuesAPICommand(params) {
|
|
|
2105
2149
|
return { command: 'api', args: [apiPath] };
|
|
2106
2150
|
}
|
|
2107
2151
|
|
|
2152
|
+
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.`;
|
|
2108
2157
|
function registerNpmViewPackageTool(server) {
|
|
2109
|
-
server.tool(
|
|
2158
|
+
server.tool(TOOL_NAME, DESCRIPTION, {
|
|
2110
2159
|
packageName: z
|
|
2111
2160
|
.string()
|
|
2112
2161
|
.min(1, 'Package name is required')
|
|
2113
2162
|
.describe('NPM package name to analyze. Returns complete package context including exports (critical for GitHub file discovery), repository URL, dependencies, and version history.'),
|
|
2114
2163
|
}, {
|
|
2115
|
-
title:
|
|
2116
|
-
description:
|
|
2164
|
+
title: TOOL_NAME,
|
|
2165
|
+
description: DESCRIPTION,
|
|
2117
2166
|
readOnlyHint: true,
|
|
2118
2167
|
destructiveHint: false,
|
|
2119
2168
|
idempotentHint: true,
|
|
@@ -2121,17 +2170,17 @@ function registerNpmViewPackageTool(server) {
|
|
|
2121
2170
|
}, async (args) => {
|
|
2122
2171
|
try {
|
|
2123
2172
|
if (!args.packageName || args.packageName.trim() === '') {
|
|
2124
|
-
return createResult('Package name is required', true);
|
|
2173
|
+
return createResult('Package name is required - provide a valid NPM package name', true);
|
|
2125
2174
|
}
|
|
2126
2175
|
// Basic package name validation
|
|
2127
2176
|
if (!/^[a-z0-9@._/-]+$/.test(args.packageName)) {
|
|
2128
|
-
return createResult('Invalid package name format', true);
|
|
2177
|
+
return createResult('Invalid package name format - use standard NPM naming (e.g., "package-name" or "@scope/package")', true);
|
|
2129
2178
|
}
|
|
2130
2179
|
const result = await npmViewPackage(args.packageName);
|
|
2131
2180
|
return result;
|
|
2132
2181
|
}
|
|
2133
2182
|
catch (error) {
|
|
2134
|
-
return createResult(
|
|
2183
|
+
return createResult('Failed to get package metadata - verify package exists on NPM registry', true);
|
|
2135
2184
|
}
|
|
2136
2185
|
});
|
|
2137
2186
|
}
|
|
@@ -2189,7 +2238,7 @@ async function npmViewPackage(packageName) {
|
|
|
2189
2238
|
return createSuccessResult$1(viewResult);
|
|
2190
2239
|
}
|
|
2191
2240
|
catch (error) {
|
|
2192
|
-
return createErrorResult$1('Failed to get npm package metadata', error);
|
|
2241
|
+
return createErrorResult$1('Failed to get npm package metadata - package may not exist or registry unavailable', error);
|
|
2193
2242
|
}
|
|
2194
2243
|
});
|
|
2195
2244
|
}
|