json_search_kv
Search key-value pairs in JSON files within a directory, filtering by specified key and optional value. Supports recursive search, match types, and customizable parameters for file size, depth, and results.
Instructions
Search for key-value pairs in JSON files within a directory. Requires maxBytes (default 10KB), maxDepth (default 2), and maxResults (default 10) parameters. Returns all key-value pairs that match the search pattern. The path must be within allowed directories.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| directoryPath | Yes | Directory to search in | |
| key | Yes | Key to search for | |
| matchType | No | How to match values - only applies if value is provided | exact |
| maxBytes | Yes | Maximum bytes to read from each file. Must be a positive integer. Handler default: 10KB. | |
| maxDepth | Yes | Maximum directory depth to search. Must be a positive integer. Handler default: 2. | |
| maxResults | Yes | Maximum number of results to return. Must be a positive integer. Handler default: 10. | |
| recursive | No | Whether to search recursively in subdirectories | |
| value | No | Optional value to match against the key |
Implementation Reference
- src/handlers/json-handlers.ts:598-735 (handler)The core handler function implementing the logic for the 'json_search_kv' tool. It searches for specified key-value pairs in JSON files within a directory (recursively up to maxDepth), validates paths, reads JSON files, and returns matching files with paths to the keys.export async function handleJsonSearchKv( args: unknown, allowedDirectories: string[], symlinksMap: Map<string, string>, noFollowSymlinks: boolean ) { const parsed = parseArgs(JsonSearchKvArgsSchema, args, 'json_search_kv'); const validDirPath = await validatePath(parsed.directoryPath, allowedDirectories, symlinksMap, noFollowSymlinks); const { key, value, recursive = true, matchType = 'exact', maxBytes, maxResults = 10, maxDepth } = parsed; const effectiveMaxDepth = maxDepth ?? 2; // Default depth 2 /** * Check if a value matches the search criteria */ function isValueMatch(foundValue: any): boolean { if (value === undefined) return true; if (typeof foundValue === 'string' && typeof value === 'string') { switch (matchType) { case 'contains': return foundValue.includes(value); case 'startsWith': return foundValue.startsWith(value); case 'endsWith': return foundValue.endsWith(value); default: return foundValue === value; } } return isEqual(foundValue, value); } /** * Search for key/value pairs in a JSON object */ function searchInObject(obj: any, currentPath: string[] = []): string[] { const matches: string[] = []; if (isPlainObject(obj)) { for (const [k, v] of Object.entries(obj)) { const newPath = [...currentPath, k]; // Check if this key matches if (k === key && isValueMatch(v)) { matches.push(newPath.join('.')); } // Recursively search in nested objects and arrays if (isPlainObject(v) || Array.isArray(v)) { matches.push(...searchInObject(v, newPath)); } } } else if (Array.isArray(obj)) { obj.forEach((item, index) => { const newPath = [...currentPath, index.toString()]; matches.push(...searchInObject(item, newPath)); }); } return matches; } /** * Process a single JSON file */ async function processFile(filePath: string): Promise<{ file: string; matches: string[] } | null> { try { // Pass maxBytes from parsed args to readJsonFile // Use the maxBytes variable destructured earlier const jsonData = await readJsonFile(filePath, maxBytes); const matches = searchInObject(jsonData); return matches.length > 0 ? { file: filePath, matches } : null; } catch (error) { // Skip files that can't be read or aren't valid JSON return null; } } /** * Recursively get all JSON files in directory */ async function getJsonFiles(dir: string, currentDepth: number): Promise<string[]> { // Check depth limit if (currentDepth >= effectiveMaxDepth) { return []; } const entries = await fs.readdir(dir, { withFileTypes: true }); const files: string[] = []; for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory() && recursive) { const validSubPath = await validatePath(fullPath, allowedDirectories, symlinksMap, noFollowSymlinks); files.push(...await getJsonFiles(validSubPath, currentDepth + 1)); } else if (entry.isFile() && entry.name.endsWith('.json')) { const validFilePath = await validatePath(fullPath, allowedDirectories, symlinksMap, noFollowSymlinks); files.push(validFilePath); } } return files; } try { // Get all JSON files in the directory const jsonFiles = await getJsonFiles(validDirPath, 0); // Start at depth 0 // Process files and collect results const results = []; for (const file of jsonFiles) { if (results.length >= maxResults) break; const result = await processFile(file); if (result) { results.push(result); } } return { content: [{ type: "text", text: JSON.stringify({ totalFiles: jsonFiles.length, matchingFiles: results.length, results }, null, 2) }], }; } catch (error) { if (error instanceof Error) { throw new Error(`JSON key/value search failed: ${error.message}`); } throw error; } }
- TypeBox schema defining the input parameters for the json_search_kv tool, including directory, key, optional value, matching options, and limits.export const JsonSearchKvArgsSchema = Type.Object({ directoryPath: Type.String({ description: 'Directory to search in' }), key: Type.String({ description: 'Key to search for' }), value: Type.Optional(Type.Any({ description: 'Optional value to match against the key' })), recursive: Type.Optional(Type.Boolean({ default: true, description: 'Whether to search recursively in subdirectories' })), matchType: Type.Optional( Type.Union([ Type.Literal('exact'), Type.Literal('contains'), Type.Literal('startsWith'), Type.Literal('endsWith') ], { default: 'exact', description: 'How to match values - only applies if value is provided' }) ), maxBytes: Type.Integer({ minimum: 1, description: 'Maximum bytes to read from each file. Must be a positive integer. Handler default: 10KB.' }), maxResults: Type.Integer({ minimum: 1, description: 'Maximum number of results to return. Must be a positive integer. Handler default: 10.' }), maxDepth: Type.Integer({ minimum: 1, description: 'Maximum directory depth to search. Must be a positive integer. Handler default: 2.' }) }); export type JsonSearchKvArgs = Static<typeof JsonSearchKvArgsSchema>;
- index.ts:293-294 (registration)Registers the handleJsonSearchKv function as the executor for the 'json_search_kv' tool in the toolHandlers object.json_search_kv: (a: unknown) => handleJsonSearchKv(a, allowedDirectories, symlinksMap, noFollowSymlinks),
- index.ts:51-51 (registration)Imports the handleJsonSearchKv handler from json-handlers.handleJsonSearchKv,
- index.ts:333-333 (registration)Defines the tool metadata (name and description) in the allTools list used for server.addTool.{ name: "json_search_kv", description: "Search key/value in JSON" },
- src/schemas/index.ts:98-98 (schema)Maps the json_search_kv tool name to its schema in the central toolSchemas export.json_search_kv: JsonSearchKvArgsSchema,