check_files
Verify file paths and context size before sending to Grok. This dry-run tool checks file syntax, patterns, and limits to confirm resolution succeeds, preventing errors in ask_grok.
Instructions
Dry-run file resolution. Use this before ask_grok to verify files will resolve correctly and check context size. Uses the same validation as ask_grok — if check_files passes, ask_grok will too. File paths resolve relative to: /app
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| files | Yes | Files to check. Same syntax as ask_grok: "path/to/file", "path/to/file:10-30", "src/**/*.ts", "large-file.ts:force" | |
| max_files | No | Override max file count (default 50) | |
| max_file_size | No | Override max per-file size in KB (default 32) |
Implementation Reference
- src/index.ts:334-368 (registration)Registration of the 'check_files' tool on the MCP server via server.tool(), defining name, description, input schema (files, max_files, max_file_size), and the handler callback.
server.tool( "check_files", `Dry-run file resolution. Use this before ask_grok to verify files will resolve correctly and check context size. Uses the same validation as ask_grok — if check_files passes, ask_grok will too. File paths resolve relative to: ${process.cwd()}`, { files: z .array(z.string()) .describe('Files to check. Same syntax as ask_grok: "path/to/file", "path/to/file:10-30", "src/**/*.ts", "large-file.ts:force"'), max_files: z .number() .optional() .describe("Override max file count (default 50)"), max_file_size: z .number() .optional() .describe("Override max per-file size in KB (default 32)"), }, async ({ files, max_files, max_file_size }) => { const result = resolveFiles(files, { maxFiles: max_files, maxFileSize: max_file_size }); if (!result.ok) { return { isError: true, content: [{ type: "text" as const, text: result.error }] }; } const sorted = [...result.files].sort((a, b) => b.bytes - a.bytes); const lines = [ `${result.files.length} file(s), ${Math.round(result.totalBytes / 1024)} KB total (limit ${MAX_TOTAL_BYTES / 1024} KB)`, "", ...sorted.map((f) => { const kb = (f.bytes / 1024).toFixed(1); return ` ${kb} KB ${f.path}${f.range}`; }), ]; return { content: [{ type: "text" as const, text: lines.join("\n") }] }; } ); - src/index.ts:337-349 (schema)Zod input schema for check_files: 'files' (array of strings, required), 'max_files' (optional number), 'max_file_size' (optional number in KB).
{ files: z .array(z.string()) .describe('Files to check. Same syntax as ask_grok: "path/to/file", "path/to/file:10-30", "src/**/*.ts", "large-file.ts:force"'), max_files: z .number() .optional() .describe("Override max file count (default 50)"), max_file_size: z .number() .optional() .describe("Override max per-file size in KB (default 32)"), }, - src/index.ts:350-367 (handler)Handler function that calls resolveFiles() with the provided arguments, formats the result as a summary line (file count / total KB / limit) followed by a sorted list (largest first) showing each file's size and path+range, and returns it as text content.
async ({ files, max_files, max_file_size }) => { const result = resolveFiles(files, { maxFiles: max_files, maxFileSize: max_file_size }); if (!result.ok) { return { isError: true, content: [{ type: "text" as const, text: result.error }] }; } const sorted = [...result.files].sort((a, b) => b.bytes - a.bytes); const lines = [ `${result.files.length} file(s), ${Math.round(result.totalBytes / 1024)} KB total (limit ${MAX_TOTAL_BYTES / 1024} KB)`, "", ...sorted.map((f) => { const kb = (f.bytes / 1024).toFixed(1); return ` ${kb} KB ${f.path}${f.range}`; }), ]; return { content: [{ type: "text" as const, text: lines.join("\n") }] }; } - src/index.ts:77-144 (helper)The resolveFiles() helper that performs the actual file resolution logic: parses file args via parseFileArg(), reads files with readFileSync(), enforces max file count, per-file size limits, line range validation, and total byte cap (256 KB). Returns either ok with resolved files or ok:false with error messages.
function resolveFiles(fileArgs: string[], opts?: ResolveOptions): ResolveResult { const maxFiles = opts?.maxFiles ?? DEFAULT_MAX_FILES; const maxSingleBytes = opts?.maxFileSize ? opts.maxFileSize * 1024 : DEFAULT_MAX_SINGLE_BYTES; const errors: string[] = []; const resolved: ResolvedFile[] = []; const specs = fileArgs.flatMap(parseFileArg); if (specs.length > maxFiles) { return { ok: false, error: `Too many files: ${specs.length} resolved (limit ${maxFiles}). This usually means a glob matched more than intended (e.g. node_modules). Use a more specific pattern.` }; } let totalBytes = 0; for (const spec of specs) { try { const raw = readFileSync(spec.path, "utf-8"); if (!spec.force && raw.length > maxSingleBytes && !spec.startLine && !spec.endLine) { const kb = Math.round(raw.length / 1024); errors.push(`${spec.path}: file is ${kb} KB (limit ${maxSingleBytes / 1024} KB per file). Use ":force" to override, or line ranges to include only the relevant part, e.g. "${spec.path}:1-100"`); continue; } const allLines = raw.split("\n"); const totalLines = allLines.length; if (spec.startLine && spec.startLine > totalLines) { errors.push(`${spec.path}: line ${spec.startLine} is past end of file (${totalLines} lines)`); continue; } if (spec.endLine && spec.endLine > totalLines) { errors.push(`${spec.path}: line ${spec.endLine} is past end of file (${totalLines} lines)`); continue; } if (spec.startLine && spec.endLine && spec.startLine > spec.endLine) { errors.push(`${spec.path}: start line ${spec.startLine} is after end line ${spec.endLine}`); continue; } const start = spec.startLine ? spec.startLine - 1 : 0; const end = spec.endLine ? spec.endLine : totalLines; const sliced = allLines.slice(start, end); const numbered = sliced .map((line, i) => `${start + i + 1}\t${line}`) .join("\n"); totalBytes += numbered.length; if (totalBytes > MAX_TOTAL_BYTES) { const kb = Math.round(totalBytes / 1024); errors.push(`Total file context is ${kb} KB (hard limit ${MAX_TOTAL_BYTES / 1024} KB). Include fewer files or use line ranges to narrow down.`); break; } const range = spec.startLine || spec.endLine ? `:${spec.startLine ?? 1}-${spec.endLine ?? totalLines}` : ""; resolved.push({ path: spec.path, range, content: numbered, bytes: numbered.length }); } catch (err) { errors.push(`${spec.path}: ${err instanceof Error ? err.message : String(err)}`); } } if (errors.length > 0) { return { ok: false, error: `File context error:\n${errors.join("\n")}\n\nFix and try again.` }; } return { ok: true, files: resolved, totalBytes }; } - src/index.ts:16-50 (helper)The parseFileArg() helper that parses individual file argument strings, supporting patterns like 'path:10-30', 'path:10', glob patterns with **/*, and the :force suffix to bypass size limits.
function parseFileArg(arg: string): FileSpec[] { // Strip :force suffix first let force = false; let input = arg; if (input.endsWith(":force")) { force = true; input = input.slice(0, -6); } // "path/to/file:10-30" or "path/to/file:10" or "path/to/file" or "src/**/*.ts" const match = input.match(/^(.+?):(\d+)(?:-(\d+))?$/); let pattern: string; let startLine: number | undefined; let endLine: number | undefined; if (match) { pattern = match[1]; startLine = parseInt(match[2], 10); endLine = match[3] ? parseInt(match[3], 10) : startLine; } else { pattern = input; } const resolved = resolve(pattern); if (/[*?[\]]/.test(pattern)) { const paths = globSync(pattern).sort(); if (paths.length === 0) { return [{ path: pattern, force }]; // will fail at read time with a clear error } return paths.map((p) => ({ path: resolve(p as string), startLine, endLine, force })); } return [{ path: resolved, startLine, endLine, force }]; }