grep
Search file contents for specific text patterns using regex and glob filtering to find relevant lines across your filesystem.
Instructions
Search file contents for text (grep-like). Returns matching lines. Scope with filePattern (e.g. **/*.ts) to reduce noise. includeHidden=true for dotfiles.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | Base directory (default: root). Absolute path required if multiple roots. | |
| pattern | Yes | Search text. RE2 regex when `isRegex=true`. | |
| isRegex | No | Treat pattern as RE2 regex (no lookahead/lookbehind/backrefs). | |
| caseSensitive | No | Case-sensitive matching. Default: case-insensitive. | |
| wholeWord | No | Match whole words only | |
| contextLines | No | Include N lines of context before/after matches | |
| maxResults | No | Maximum match rows to return. Default: 500 | |
| filePattern | No | Glob for candidate files (e.g. "**/*.ts") | **/* |
| includeHidden | No | Include hidden items (starting with .) | |
| includeIgnored | No | Include ignored items (node_modules, etc). |
Implementation Reference
- src/tools/search-content.ts:364-418 (handler)The primary logic for the "grep" tool, which handles path resolution, file searching, result normalization, preview creation, and externalizing results if necessary.
async function handleSearchContent( args: SearchInput, signal?: AbortSignal, resourceStore?: ToolRegistrationOptions['resourceStore'], onProgress?: (progress: { total?: number; current: number }) => void ): Promise<ToolResponse<SearchOutput>> { const basePath = resolvePathOrRoot(args.path); const regexMatcher = createSearchMatcher(args); const result = await executeSearch(args, basePath, signal, onProgress); const normalizedMatches = normalizeMatches(result); const searchContext = createSearchContext(args, regexMatcher); const matchPayloads = buildMatchPayloads(normalizedMatches, searchContext); const fullStructured = buildSearchStructured(result.summary, matchPayloads); const preview = buildSearchPreviewState(normalizedMatches, matchPayloads); if (resourceStore && preview.needsExternalize) { const previewStructured: SearchOutput = { ...fullStructured, matches: preview.visiblePayloads, truncated: true, }; const entry = resourceStore.putText({ name: 'grep:matches', mimeType: 'application/json', text: JSON.stringify(fullStructured), }); previewStructured.resourceUri = entry.uri; const text = buildSearchText(preview.heading, preview.visibleMatches); return buildToolResponse(text, previewStructured, [ buildResourceLink({ uri: entry.uri, name: entry.name, mimeType: entry.mimeType, description: 'Full grep results as JSON (structuredContent)', expiresAt: entry.expiresAt, }), ]); } const text = buildSearchText( preview.heading, preview.visibleMatches, result.summary ); return buildToolResponse(text, fullStructured); } - src/tools/search-content.ts:158-175 (schema)The tool contract definition for "grep", including its input/output schema and metadata.
export const SEARCH_CONTENT_TOOL: ToolContract = { name: 'grep', title: 'Search Content', description: 'Search file contents for text (grep-like). Returns matching lines. ' + 'Scope with `filePattern` (e.g. `**/*.ts`) to reduce noise. ' + '`includeHidden=true` for dotfiles.', inputSchema: SearchContentInputSchema, outputSchema: SearchContentOutputSchema, annotations: READ_ONLY_TOOL_ANNOTATIONS, nuances: [ 'Inline results capped at 50 matches; full results via `resourceUri`.', ], gotchas: [ 'Skips binary/oversized files silently — verify with `stat` if no matches.', ], taskSupport: 'optional', } as const; - src/tools/search-content.ts:420-511 (registration)Registration function for the "grep" tool on the MCP server.
export function registerSearchContentTool( server: McpServer, options: ToolRegistrationOptions = {} ): void { const handler = ( args: SearchInput, extra: ToolExtra ): Promise<ToolResult<SearchOutput>> => executeToolWithDiagnostics({ toolName: 'grep', extra, outputSchema: SearchContentOutputSchema, context: { path: args.path ?? '.' }, run: async (signal) => { const { pattern, filePattern: scope } = args; const progressLabel = `🔎︎ grep: ${truncateProgressPattern(pattern)}`; const progress = createToolProgressSession(extra, progressLabel); const progressWithMessage = ({ current, total, }: { total?: number; current: number; }): void => { progress.update({ current, ...(total !== undefined ? { total } : {}), message: `${progressLabel} [${current} files]`, }); }; try { const result = await handleSearchContent( args, signal, options.resourceStore, progressWithMessage ); const sc = result.structuredContent; const { totalMatches = 0, filesMatched = 0, stoppedReason } = sc; const suffix = buildCompletionSuffix( totalMatches, filesMatched, scope, stoppedReason ); const finalCurrent = resolveFinalProgressCurrent( progress, (sc.filesScanned ?? 0) + 1 ); progress.complete(`${progressLabel} • ${suffix}`, finalCurrent); return result; } catch (error) { progress.fail(`${progressLabel} • failed`); throw error; } }, onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path ?? '.'), }); const { isInitialized } = options; const wrappedHandler = wrapToolHandler(handler, { guard: isInitialized, }); const validatedHandler = withValidatedArgs( SearchContentInputSchema, wrappedHandler ); if ( registerToolTaskIfAvailable( server, 'grep', SEARCH_CONTENT_TOOL, validatedHandler, options.iconInfo, isInitialized ) ) return; server.registerTool( 'grep', withDefaultIcons({ ...SEARCH_CONTENT_TOOL }, options.iconInfo), validatedHandler ); }