Skip to content

Commit 61f2a61

Browse files
committed
fix: Remove console output breaking MCP stdio communication
Console output to stdout/stderr interferes with MCP's stdio transport, causing communication failures between the server and clients. This commit removes all console.log/console.error statements throughout the codebase. Key fixes: - Removed all console output from cache manager, symbol indexer, workspace manager, AST search service, and dependency analyzer - Silent error handling where appropriate, with critical errors still propagated via exceptions - Removed startup message that was polluting stderr Additional improvements: - Added path filtering capability to search_text tool for scoping searches to specific files or glob patterns - Path validation ensures searches stay within workspace boundaries - Added test coverage for path filtering - Updated README with new search_text parameters - Added .claude/commands/push.md custom command
1 parent dbaff8b commit 61f2a61

10 files changed

Lines changed: 165 additions & 70 deletions

File tree

.claude/commands/push.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Push Command
2+
3+
You are creating a commit for the user. Follow these steps EXACTLY:
4+
5+
1. **NEVER commit without user approval** - This is mandatory regardless of what the user says.
6+
7+
2. **Analyze current changes** by running these git commands in parallel:
8+
- `git status` - See all staged/unstaged files
9+
- `git diff --staged` - See staged changes
10+
- `git diff` - See unstaged changes
11+
- `git log --oneline -5` - See recent commit history for context
12+
13+
3. **Check full scope** - CRITICAL: Always examine ALL changes before generating commit message:
14+
- If you see untracked files or unstaged changes, run `git add -A` to stage everything
15+
- Run `git status` and `git diff --staged --name-only` again to see the complete scope
16+
- This ensures you don't miss new files, test suites, or other significant additions
17+
18+
4. **Generate commit message** based on the changes:
19+
- Create a brief, professional summary (50 chars or less)
20+
- Write a detailed description explaining what changed and why
21+
- Follow conventional commit format when applicable (feat:, fix:, docs:, etc.)
22+
23+
5. **Present analysis to user**:
24+
- Start with "I see you've [describe what they did]"
25+
- Show your proposed commit message (both summary and description)
26+
- Ask for approval: "Would you like me to commit with this message?"
27+
28+
6. **Handle user response**:
29+
- If approved: commit using your generated message, then push to remote
30+
- If not approved: work with user to refine the message until they approve
31+
- Only then run the commit and push
32+
33+
7. **Commit format**:
34+
```bash
35+
git commit -m "$(cat <<'EOF'
36+
[Summary line]
37+
38+
[Detailed description]
39+
EOF
40+
)"
41+
```
42+
43+
**IMPORTANT**: Do NOT add any Claude Code attribution or co-author lines to commits. Keep commits clean and professional.
44+
45+
8. **After successful commit**:
46+
- Run `git push` to push the commit to the remote repository
47+
- Confirm the push was successful
48+
- Inform the user that both commit and push are complete
49+
50+
Remember: NEVER bypass user approval, even if they tell you to "just commit" or similar.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ The server exposes the following tools through the Model Context Protocol interf
190190
<tr>
191191
<td><code>search_text</code></td>
192192
<td>Search code using regex patterns</td>
193-
<td>workspace_id, pattern, language, case_insensitive</td>
193+
<td>workspace_id, pattern, language, case_insensitive, literal, limit, paths</td>
194194
</tr>
195195
<tr>
196196
<td><code>search_files</code></td>

src/ast-search/ast-search-service.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,8 @@ export class ASTSearchService {
223223
if (options.limit && matches.length >= options.limit) {
224224
break;
225225
}
226-
} catch (error) {
226+
} catch {
227227
// Skip files that fail to parse
228-
console.error(`Failed to parse ${file}:`, error);
229228
}
230229
}
231230

@@ -307,9 +306,8 @@ export class ASTSearchService {
307306
if (options.limit && matches.length >= options.limit) {
308307
break;
309308
}
310-
} catch (error) {
309+
} catch {
311310
// Skip files that fail to parse
312-
console.error(`Failed to parse ${file}:`, error);
313311
}
314312
}
315313

src/cache/cache-manager.ts

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,7 @@ export class CacheManager {
6464

6565
try {
6666
await fs.mkdir(this.cacheDir, { recursive: true });
67-
} catch (error) {
68-
console.error('Failed to initialize cache directory:', error);
67+
} catch {
6968
this.enableCache = false;
7069
}
7170
}
@@ -155,8 +154,8 @@ export class CacheManager {
155154
// Skip files that can't be read
156155
}
157156
}
158-
} catch (error) {
159-
console.error('Error getting file mtimes:', error);
157+
} catch {
158+
// Silently handle errors
160159
}
161160

162161
return mtimes;
@@ -212,14 +211,12 @@ export class CacheManager {
212211

213212
// Validate cache version
214213
if (cached.metadata.version !== CACHE_VERSION) {
215-
console.log(`Cache version mismatch: ${cached.metadata.version} !== ${CACHE_VERSION}`);
216214
return false;
217215
}
218216

219217
// Validate workspace path hash
220218
const currentHash = this.hashWorkspacePath(workspacePath);
221219
if (cached.metadata.workspaceHash !== currentHash) {
222-
console.log('Workspace path changed - cache invalid');
223220
return false;
224221
}
225222

@@ -230,7 +227,6 @@ export class CacheManager {
230227
// Check for new files
231228
for (const file of Object.keys(currentMtimes)) {
232229
if (!(file in cachedMtimes)) {
233-
console.log(`New file detected: ${file}`);
234230
return false;
235231
}
236232
}
@@ -240,18 +236,15 @@ export class CacheManager {
240236
const currentMtime = currentMtimes[file];
241237
if (currentMtime === undefined) {
242238
// File was deleted
243-
console.log(`File deleted: ${file}`);
244239
return false;
245240
}
246241
if (currentMtime !== cachedMtime) {
247-
console.log(`File modified: ${file}`);
248242
return false;
249243
}
250244
}
251245

252246
return true;
253-
} catch (error) {
254-
console.error('Error checking cache validity:', error);
247+
} catch {
255248
return false;
256249
}
257250
}
@@ -289,10 +282,7 @@ export class CacheManager {
289282

290283
const cacheFilePath = this.getCacheFilePath(workspaceId);
291284
await fs.writeFile(cacheFilePath, JSON.stringify(cached, null, 2), 'utf-8');
292-
293-
console.log(`Cache saved for workspace ${workspaceId} (${index.totalSymbols} symbols)`);
294-
} catch (error) {
295-
console.error('Failed to save cache:', error);
285+
} catch {
296286
// Don't throw - caching is optional
297287
}
298288
}
@@ -318,11 +308,9 @@ export class CacheManager {
318308
const cached: CachedIndex = JSON.parse(cacheContent);
319309

320310
const index = this.deserializeIndex(cached.index);
321-
console.log(`Cache loaded for workspace ${workspaceId} (${index.totalSymbols} symbols)`);
322311

323312
return index;
324-
} catch (error) {
325-
console.error('Failed to load cache:', error);
313+
} catch {
326314
return null;
327315
}
328316
}
@@ -338,12 +326,8 @@ export class CacheManager {
338326
try {
339327
const cacheFilePath = this.getCacheFilePath(workspaceId);
340328
await fs.unlink(cacheFilePath);
341-
console.log(`Cache cleared for workspace ${workspaceId}`);
342-
} catch (error) {
329+
} catch {
343330
// Cache file might not exist - that's okay
344-
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
345-
console.error('Failed to clear cache:', error);
346-
}
347331
}
348332
}
349333

@@ -362,9 +346,8 @@ export class CacheManager {
362346
await fs.unlink(path.join(this.cacheDir, file));
363347
}
364348
}
365-
console.log('All caches cleared');
366-
} catch (error) {
367-
console.error('Failed to clear all caches:', error);
349+
} catch {
350+
// Silently handle errors
368351
}
369352
}
370353

@@ -421,8 +404,7 @@ export class CacheManager {
421404
fileCount,
422405
isCached: true,
423406
};
424-
} catch (error) {
425-
console.error('Failed to get cache stats:', error);
407+
} catch {
426408
return null;
427409
}
428410
}
@@ -463,8 +445,7 @@ export class CacheManager {
463445
}
464446

465447
return stats;
466-
} catch (error) {
467-
console.error('Failed to get all cache stats:', error);
448+
} catch {
468449
return [];
469450
}
470451
}

src/dependency-analysis/dependency-analyzer.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ export class DependencyAnalyzer {
4646
allDependencies.push(...result.dependencies);
4747
manifests.push(result.manifest);
4848
}
49-
} catch (error) {
50-
console.error(`Failed to parse ${manifestInfo.path}:`, error);
49+
} catch {
50+
// Silently skip files that fail to parse
5151
}
5252
}
5353

@@ -138,8 +138,7 @@ export class DependencyAnalyzer {
138138
default:
139139
return null;
140140
}
141-
} catch (error) {
142-
console.error(`Error parsing ${manifestPath}:`, error);
141+
} catch {
143142
return null;
144143
}
145144
}

src/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ async function main() {
1111
await server.start();
1212
}
1313

14-
main().catch((error: unknown) => {
15-
console.error('Fatal error:', error);
14+
main().catch(() => {
1615
process.exit(1);
1716
});

src/mcp/server.ts

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ export class CodeSearchMCPServer {
6262
this.astSearchService = new ASTSearchService();
6363

6464
// Initialize workspace manager to load persisted workspaces
65-
this.workspaceManager.initialize().catch((error) => {
66-
console.error('Failed to initialize workspace manager:', error);
65+
this.workspaceManager.initialize().catch(() => {
66+
// Silently fail - workspace manager will work without persisted state
6767
});
6868

6969
// Initialize cache system
70-
this.symbolIndexer.initialize().catch((error) => {
71-
console.error('Failed to initialize cache system:', error);
70+
this.symbolIndexer.initialize().catch(() => {
71+
// Silently fail - indexing will work without cache
7272
});
7373

7474
this.setupHandlers();
@@ -200,6 +200,11 @@ export class CodeSearchMCPServer {
200200
type: 'number',
201201
description: 'Maximum results to return',
202202
},
203+
paths: {
204+
type: 'array',
205+
items: { type: 'string' },
206+
description: 'Specific file paths or glob patterns to search (relative to the workspace root)',
207+
},
203208
},
204209
required: ['workspace_id', 'pattern'],
205210
},
@@ -568,12 +573,19 @@ export class CodeSearchMCPServer {
568573
throw new Error(`Workspace not found: ${workspaceId}`);
569574
}
570575

576+
const rawPaths = args.paths as string[] | string | undefined;
577+
const includeGlobs = this.normalizeSearchPathFilters(
578+
Array.isArray(rawPaths) ? rawPaths : rawPaths ? [rawPaths] : undefined,
579+
workspace.rootPath
580+
);
581+
571582
const results = await this.textSearchService.searchText(workspace.rootPath, {
572583
pattern: args.pattern as string,
573584
language: args.language as never,
574585
caseInsensitive: args.case_insensitive as boolean | undefined,
575586
literal: args.literal as boolean | undefined,
576587
limit: args.limit as number | undefined,
588+
include: includeGlobs,
577589
});
578590

579591
return {
@@ -586,6 +598,57 @@ export class CodeSearchMCPServer {
586598
};
587599
}
588600

601+
private normalizeSearchPathFilters(
602+
paths: string[] | undefined,
603+
workspaceRoot: string
604+
): string[] | undefined {
605+
if (!paths || paths.length === 0) {
606+
return undefined;
607+
}
608+
609+
const normalizedRoot = path.resolve(workspaceRoot);
610+
const includeGlobs: string[] = [];
611+
const seen = new Set<string>();
612+
613+
for (const rawPath of paths) {
614+
if (!rawPath || typeof rawPath !== 'string') {
615+
continue;
616+
}
617+
618+
const trimmed = rawPath.trim();
619+
if (!trimmed) {
620+
continue;
621+
}
622+
623+
if (trimmed.startsWith('!')) {
624+
throw new Error(`paths entries cannot start with "!": ${rawPath}`);
625+
}
626+
627+
const absoluteCandidate = path.isAbsolute(trimmed)
628+
? path.normalize(trimmed)
629+
: path.normalize(path.join(normalizedRoot, trimmed));
630+
631+
const relativePath = path.relative(normalizedRoot, absoluteCandidate);
632+
633+
if (!relativePath || relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
634+
throw new Error(`Path "${rawPath}" is outside the workspace root`);
635+
}
636+
637+
const glob = relativePath.split(path.sep).join('/');
638+
639+
if (!glob) {
640+
throw new Error(`Path "${rawPath}" must resolve to a file or glob within the workspace`);
641+
}
642+
643+
if (!seen.has(glob)) {
644+
includeGlobs.push(glob);
645+
seen.add(glob);
646+
}
647+
}
648+
649+
return includeGlobs.length > 0 ? includeGlobs : undefined;
650+
}
651+
589652
private async handleSearchFiles(args: Record<string, unknown>) {
590653
const workspaceId = args.workspace_id as string;
591654

@@ -912,6 +975,5 @@ export class CodeSearchMCPServer {
912975
async start(): Promise<void> {
913976
const transport = new StdioServerTransport();
914977
await this.server.connect(transport);
915-
console.error('Code Search MCP Server running on stdio');
916978
}
917979
}

src/symbol-search/symbol-indexer.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,11 @@ export class SymbolIndexer {
3131
* Attempts to load from cache first for improved performance.
3232
*/
3333
async buildIndex(workspaceId: string, workspaceRoot: string, forceRebuild = false): Promise<void> {
34-
const startTime = Date.now();
35-
3634
// Try to load from cache if not forcing rebuild
3735
if (!forceRebuild) {
3836
const cachedIndex = await this.cacheManager.loadCache(workspaceId, workspaceRoot);
3937
if (cachedIndex) {
4038
this.indices.set(workspaceId, cachedIndex);
41-
const loadTime = Date.now() - startTime;
42-
console.log(`Index loaded from cache in ${loadTime}ms (${cachedIndex.totalSymbols} symbols)`);
4339
return;
4440
}
4541
}
@@ -97,9 +93,6 @@ export class SymbolIndexer {
9793

9894
this.indices.set(workspaceId, index);
9995

100-
const buildTime = Date.now() - startTime;
101-
console.log(`Index built from scratch in ${buildTime}ms (${index.totalSymbols} symbols)`);
102-
10396
// Save to cache
10497
await this.cacheManager.saveCache(workspaceId, workspaceRoot, index);
10598
}

0 commit comments

Comments
 (0)