search_shelf
Search for text across files in a shelf using substring or regex patterns to locate specific content within stored documents.
Instructions
Search for text across files in a shelf
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| shelf_id | Yes | ||
| query | Yes | ||
| mode | No | ||
| case_sensitive | No | ||
| max_matches | No |
Implementation Reference
- src/tools/search-shelf.ts:75-156 (handler)Main handler function that executes the search_shelf tool. It validates input, resolves the shelf source, builds a matcher (substring or regex), iterates through files to find matches, and returns structured results with matches, scanned files/bytes count, and truncation status.
async (input, extra) => { try { const mode = input.mode ?? "substring"; const caseSensitive = input.case_sensitive ?? false; const apiKey = context.getApiKey(extra); const source = await resolveShelfSource({ client: context.createShelvClient(apiKey), shelfPublicId: input.shelf_id, mode: "archive-first", }); const matcher = buildMatcher(input.query, mode, caseSensitive); const maxMatches = Math.min( input.max_matches ?? context.config.searchMaxMatches, context.config.searchMaxMatches, ); const matches: Array<{ path: string; line: string; line_number: number; snippet: string; }> = []; let scannedFiles = 0; let scannedBytes = 0; let truncated = false; for (const [filePath, content] of Object.entries(source.files)) { if (scannedFiles >= context.config.searchMaxFiles) { truncated = true; break; } const bytes = Buffer.byteLength(content, "utf8"); if (scannedBytes + bytes > context.config.searchMaxBytes) { truncated = true; break; } scannedFiles += 1; scannedBytes += bytes; const lines = content.split(/\r?\n/); for (let index = 0; index < lines.length; index += 1) { const line = lines[index] || ""; if (!matcher(line)) continue; matches.push({ path: filePath, line, line_number: index + 1, snippet: line.slice(0, 300), }); if (matches.length >= maxMatches) { truncated = true; break; } } if (truncated) break; } return successResult( `Found ${matches.length} matches across ${scannedFiles} files`, { shelf_id: input.shelf_id, query: input.query, mode, case_sensitive: caseSensitive, matches, scanned_files: scannedFiles, scanned_bytes: scannedBytes, truncated, }, ); } catch (error) { return errorResult(error); } }, - src/tools/search-shelf.ts:8-32 (schema)Zod schemas defining the tool's input (shelf_id, query, mode, case_sensitive, max_matches) and output (shelf_id, query, mode, case_sensitive, matches array with path/line/line_number/snippet, scanned_files, scanned_bytes, truncated) structure.
const inputSchema = { shelf_id: z.string().min(1), query: z.string().min(1), mode: z.enum(["substring", "regex"]).optional(), case_sensitive: z.boolean().optional(), max_matches: z.number().int().min(1).optional(), }; const outputSchema = { shelf_id: z.string(), query: z.string(), mode: z.enum(["substring", "regex"]), case_sensitive: z.boolean(), matches: z.array( z.object({ path: z.string(), line: z.string(), line_number: z.number(), snippet: z.string(), }), ), scanned_files: z.number(), scanned_bytes: z.number(), truncated: z.boolean(), }; - src/tools/search-shelf.ts:62-158 (registration)Registers the search_shelf tool with the MCP server, providing its name, metadata (title, description, annotations), schemas, and the async handler function.
export function registerSearchShelfTool( server: McpServer, context: ToolContext, ): void { server.registerTool( "search_shelf", { title: "Search Shelf", description: "Search for text across files in a shelf", inputSchema, outputSchema, annotations: { readOnlyHint: true }, }, async (input, extra) => { try { const mode = input.mode ?? "substring"; const caseSensitive = input.case_sensitive ?? false; const apiKey = context.getApiKey(extra); const source = await resolveShelfSource({ client: context.createShelvClient(apiKey), shelfPublicId: input.shelf_id, mode: "archive-first", }); const matcher = buildMatcher(input.query, mode, caseSensitive); const maxMatches = Math.min( input.max_matches ?? context.config.searchMaxMatches, context.config.searchMaxMatches, ); const matches: Array<{ path: string; line: string; line_number: number; snippet: string; }> = []; let scannedFiles = 0; let scannedBytes = 0; let truncated = false; for (const [filePath, content] of Object.entries(source.files)) { if (scannedFiles >= context.config.searchMaxFiles) { truncated = true; break; } const bytes = Buffer.byteLength(content, "utf8"); if (scannedBytes + bytes > context.config.searchMaxBytes) { truncated = true; break; } scannedFiles += 1; scannedBytes += bytes; const lines = content.split(/\r?\n/); for (let index = 0; index < lines.length; index += 1) { const line = lines[index] || ""; if (!matcher(line)) continue; matches.push({ path: filePath, line, line_number: index + 1, snippet: line.slice(0, 300), }); if (matches.length >= maxMatches) { truncated = true; break; } } if (truncated) break; } return successResult( `Found ${matches.length} matches across ${scannedFiles} files`, { shelf_id: input.shelf_id, query: input.query, mode, case_sensitive: caseSensitive, matches, scanned_files: scannedFiles, scanned_bytes: scannedBytes, truncated, }, ); } catch (error) { return errorResult(error); } }, ); } - src/tools/search-shelf.ts:34-60 (helper)Helper function buildMatcher that creates a line matching function based on mode (substring or regex) and case sensitivity. Validates regex patterns and throws McpToolError for invalid expressions.
function buildMatcher( query: string, mode: "substring" | "regex", caseSensitive: boolean, ): (line: string) => boolean { if (mode === "regex") { let regex: RegExp; try { regex = new RegExp(query, caseSensitive ? "" : "i"); } catch { throw new McpToolError({ code: "INPUT_ERROR", message: "Invalid regular expression", status: 400, retryable: false, }); } return (line: string) => regex.test(line); } const needle = caseSensitive ? query : query.toLowerCase(); return (line: string) => { const haystack = caseSensitive ? line : line.toLowerCase(); return haystack.includes(needle); }; } - src/tools/index.ts:17-17 (registration)Calls registerSearchShelfTool as part of the main registerShelvTools function, which registers all shelf-related tools with the MCP server.
registerSearchShelfTool(server, context);