Evaluate Rego with coverage
rego_eval_with_coverageEvaluate Rego queries with coverage to verify which policy lines are exercised by tests.
Instructions
Evaluate with --coverage and return per-line coverage data. Useful for verifying that tests actually exercise the rules they're meant to.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Rego query to evaluate, e.g. "data.example.allow". | |
| source | No | Inline Rego policy source. Mutually exclusive with `paths`. | |
| paths | No | Policy / data file or directory paths. Each must be inside an allowed root. | |
| input | No | Inline input document. | |
| inputPath | No | Path to a JSON input file. Mutually exclusive with `input`. | |
| unknowns | No | Refs to treat as unknown during partial evaluation. | |
| partial | No | Run partial evaluation rather than full evaluation. | |
| strictBuiltinErrors | No | Treat builtin errors as fatal instead of returning undefined. |
Implementation Reference
- src/tools/evaluation/eval.ts:60-72 (handler)The handler function for 'rego_eval_with_coverage'. It calls runEval with { coverage: true }, which adds --coverage flag to opa eval CLI call.
server.registerTool( 'rego_eval_with_coverage', { title: 'Evaluate Rego with coverage', description: "Evaluate with `--coverage` and return per-line coverage data. Useful for verifying that tests actually exercise the rules they're meant to.", inputSchema: SharedEvalInput, }, async (args) => { return withToolEnvelope<RegoEvalOutput>(config, () => runEval(opa, config, args, { coverage: true }), ); }, - SharedEvalInput is the Zod schema used for all rego_eval variants including rego_eval_with_coverage. The RegoEvalOutput interface defines the shape of the output including the optional 'coverage' field.
export const SharedEvalInput = { query: z.string().min(1).describe('Rego query to evaluate, e.g. "data.example.allow".'), source: z .string() .optional() .describe('Inline Rego policy source. Mutually exclusive with `paths`.'), paths: z .array(z.string()) .optional() .describe('Policy / data file or directory paths. Each must be inside an allowed root.'), input: z.unknown().optional().describe('Inline input document.'), inputPath: z .string() .optional() .describe('Path to a JSON input file. Mutually exclusive with `input`.'), unknowns: z .array(z.string()) .optional() .describe('Refs to treat as unknown during partial evaluation.'), partial: z.boolean().optional().describe('Run partial evaluation rather than full evaluation.'), strictBuiltinErrors: z .boolean() .optional() .describe('Treat builtin errors as fatal instead of returning undefined.'), }; export interface RegoEvalOutput { result?: unknown[]; errors?: unknown[]; metrics?: Record<string, unknown>; explanation?: unknown[]; profile?: unknown[]; coverage?: unknown; } - runEval is the shared helper used by all rego_eval variants. It passes the coverage flag through to the OPA CLI and runs opa eval.
export async function runEval( opa: OpaCli, config: Config, args: EvalArgs, flags: EvalFlags, ): Promise<ToolEnvelope<RegoEvalOutput>> { if (!args.source && !args.paths?.length) { return err( 'INVALID_INPUT', 'rego_eval requires either `source` or at least one entry in `paths`.', ); } if (args.input !== undefined && args.inputPath) { return err('INVALID_INPUT', 'rego_eval accepts either `input` or `inputPath`, not both.'); } const evalInput: EvalInput = { query: args.query }; if (args.source !== undefined) evalInput.source = args.source; if (args.paths?.length) { const validation = validatePaths(args.paths, config, { mustExist: true }); if (!validation.ok) return validation.error; evalInput.paths = validation.resolved; } if (args.input !== undefined) { evalInput.input = args.input; } else if (args.inputPath) { const inputPathValidation = validatePaths([args.inputPath], config, { mustExist: true }); if (!inputPathValidation.ok) return inputPathValidation.error; evalInput.inputPath = inputPathValidation.resolved[0]; } if (args.partial) evalInput.partial = true; if (args.unknowns?.length) evalInput.unknowns = args.unknowns; if (args.strictBuiltinErrors) evalInput.strictBuiltinErrors = true; if (flags.explain) evalInput.explain = flags.explain; if (flags.profile) evalInput.profile = true; if (flags.coverage) evalInput.coverage = true; if (flags.metrics) evalInput.metrics = true; const result = await opa.eval(evalInput); const subprocessFailure = mapSubprocessFailure(result, 'opa'); if (subprocessFailure) return subprocessFailure; // `opa eval` returns exit code 0 even when the query produces no // results or partial results. A non-zero exit means a hard error // (parse, type, runtime). Output JSON is on stdout. const parsed = tryParseJson<RegoEvalOutput>(result.stdout); if (result.exitCode !== 0) { return err('EVAL_ERROR', 'opa eval exited with an error.', { details: parsed ?? { stderr: result.stderr.trim(), stdout: result.stdout.trim() }, }); } if (parsed === undefined) { return err('UNKNOWN_ERROR', 'opa eval produced no parseable JSON output.', { details: { stdout: result.stdout.trim() }, }); } return ok<RegoEvalOutput>(parsed); } - src/lib/opa-cli.ts:280-315 (helper)OpaCli.eval() method that actually runs the 'opa eval' subprocess. When input.coverage is set, adds '--coverage' flag to the CLI command.
async eval(input: EvalInput): Promise<SpawnResult> { // Inline source becomes a temp file added to --data. if (input.source !== undefined) { const { source, ...rest } = input; void source; return this.withTempSource(input.source, (sourcePath) => this.eval({ ...rest, paths: [...(input.paths ?? []), sourcePath], }), ); } const args = ['eval', '--format=json']; for (const path of input.paths ?? []) args.push('--data', path); if (input.inputPath) args.push('--input', input.inputPath); if (input.explain) args.push('--explain', input.explain); if (input.profile) args.push('--profile'); if (input.coverage) args.push('--coverage'); if (input.metrics) args.push('--metrics'); if (input.instrument) args.push('--instrument'); if (input.partial) args.push('--partial'); for (const ref of input.unknowns ?? []) args.push('--unknowns', ref); if (input.strictBuiltinErrors) args.push('--strict-builtin-errors'); if (input.capabilities) args.push('--capabilities', input.capabilities); if (input.schemaDir) args.push('--schema', input.schemaDir); let stdin: string | undefined; if (input.input !== undefined) { args.push('--stdin-input'); stdin = JSON.stringify(input.input); } args.push(input.query); return this.run(args, stdin); } - src/tools/evaluation/index.ts:16-21 (registration)registerEvaluationTools() is called by the top-level registration entry point (src/tools/index.ts). It calls registerRegoEval() which registers all four rego_eval variants including rego_eval_with_coverage.
export function registerEvaluationTools(server: McpServer, config: Config): void { registerRegoEval(server, config); // registers rego_eval + 3 variants registerRegoTest(server, config); registerRegoBench(server, config); registerRegoCompileQuery(server, config); }