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