@orygn/opa-mcp 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -1
- package/README.md +14 -10
- package/dist/lib/opa-cli.d.ts +25 -0
- package/dist/lib/opa-cli.d.ts.map +1 -1
- package/dist/lib/opa-cli.js +40 -0
- package/dist/lib/opa-cli.js.map +1 -1
- package/dist/lib/regal-cli.d.ts +33 -0
- package/dist/lib/regal-cli.d.ts.map +1 -1
- package/dist/lib/regal-cli.js +31 -0
- package/dist/lib/regal-cli.js.map +1 -1
- package/dist/tools/helpers/fix.d.ts +41 -0
- package/dist/tools/helpers/fix.d.ts.map +1 -0
- package/dist/tools/helpers/fix.js +149 -0
- package/dist/tools/helpers/fix.js.map +1 -0
- package/dist/tools/helpers/format-write.d.ts +17 -0
- package/dist/tools/helpers/format-write.d.ts.map +1 -0
- package/dist/tools/helpers/format-write.js +111 -0
- package/dist/tools/helpers/format-write.js.map +1 -0
- package/dist/tools/helpers/index.d.ts.map +1 -1
- package/dist/tools/helpers/index.js +8 -0
- package/dist/tools/helpers/index.js.map +1 -1
- package/dist/tools/helpers/infer-input-schema.d.ts +12 -0
- package/dist/tools/helpers/infer-input-schema.d.ts.map +1 -0
- package/dist/tools/helpers/infer-input-schema.js +206 -0
- package/dist/tools/helpers/infer-input-schema.js.map +1 -0
- package/dist/tools/helpers/policy-diff.d.ts +41 -0
- package/dist/tools/helpers/policy-diff.d.ts.map +1 -0
- package/dist/tools/helpers/policy-diff.js +226 -0
- package/dist/tools/helpers/policy-diff.js.map +1 -0
- package/dist/tools/index.d.ts +5 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rego_format_write` -- run `opa fmt --write` to format Rego files in place.
|
|
3
|
+
*
|
|
4
|
+
* Two-phase execution:
|
|
5
|
+
* 1. `opa fmt --list` identifies which files are not already canonical.
|
|
6
|
+
* If any file cannot be parsed this phase exits non-zero and we bail
|
|
7
|
+
* before touching the filesystem.
|
|
8
|
+
* 2. `opa fmt --write` rewrites only those files (skipping the write when
|
|
9
|
+
* dryRun: true).
|
|
10
|
+
*
|
|
11
|
+
* NOTE: `--list` and `--write` are mutually exclusive OPA flags -- combining
|
|
12
|
+
* them suppresses the write. The two calls are intentional.
|
|
13
|
+
*
|
|
14
|
+
* Supports `--rego-v1`, `--v0-compatible`, and `--v1-compatible` pass-through.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { OpaCli } from '../../lib/opa-cli.js';
|
|
18
|
+
import { err, ok } from '../../lib/errors.js';
|
|
19
|
+
import { mapSubprocessFailure, validatePaths, withToolEnvelope } from '../../lib/tool-helpers.js';
|
|
20
|
+
const RegoFormatWriteInput = {
|
|
21
|
+
paths: z
|
|
22
|
+
.array(z.string())
|
|
23
|
+
.min(1)
|
|
24
|
+
.describe('Policy files or directories to format in place. Each must be inside an allowed root (OPA_MCP_ALLOWED_PATHS).'),
|
|
25
|
+
dryRun: z
|
|
26
|
+
.boolean()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Preview which files would be reformatted without modifying them. Recommended before the first real run.'),
|
|
29
|
+
regoV1: z
|
|
30
|
+
.boolean()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('Format module(s) to be compatible with both Rego v1 and the current OPA version. Adds `import rego.v1` where missing.'),
|
|
33
|
+
v0Compatible: z
|
|
34
|
+
.boolean()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe('Use OPA behaviors and syntax prior to the v1.0 release.'),
|
|
37
|
+
v1Compatible: z.boolean().optional().describe('Use OPA v1.0-compatible behaviors.'),
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Parse the newline-delimited stdout produced by `opa fmt --list` into an
|
|
41
|
+
* array of absolute file paths.
|
|
42
|
+
*/
|
|
43
|
+
export function parseFmtListOutput(stdout) {
|
|
44
|
+
return stdout
|
|
45
|
+
.split('\n')
|
|
46
|
+
.map((line) => line.trim())
|
|
47
|
+
.filter((line) => line.length > 0);
|
|
48
|
+
}
|
|
49
|
+
export function registerRegoFormatWrite(server, config) {
|
|
50
|
+
const opa = new OpaCli(config);
|
|
51
|
+
server.registerTool('rego_format_write', {
|
|
52
|
+
title: 'Format Rego files in place',
|
|
53
|
+
description: 'Run `opa fmt --write` to canonically format one or more Rego files or directories in place. Use `dryRun: true` to preview which files would change without modifying them. Returns a list of files that were (or would be) reformatted. Unlike `rego_format` which returns formatted source as a string, this tool writes directly to disk. Supports `regoV1`, `v0Compatible`, and `v1Compatible` flags for version-specific formatting. If any file cannot be parsed, the operation is aborted and no files are written.',
|
|
54
|
+
inputSchema: RegoFormatWriteInput,
|
|
55
|
+
}, async ({ paths, dryRun, regoV1, v0Compatible, v1Compatible }) => {
|
|
56
|
+
return withToolEnvelope(config, async () => {
|
|
57
|
+
const validation = validatePaths(paths, config, { mustExist: true });
|
|
58
|
+
if (!validation.ok)
|
|
59
|
+
return validation.error;
|
|
60
|
+
const fmtInput = {
|
|
61
|
+
paths: validation.resolved,
|
|
62
|
+
regoV1,
|
|
63
|
+
v0Compatible,
|
|
64
|
+
v1Compatible,
|
|
65
|
+
};
|
|
66
|
+
// Phase 1: identify which files would change. Also validates that all
|
|
67
|
+
// files parse successfully before we touch the filesystem.
|
|
68
|
+
const listResult = await opa.fmtList(fmtInput);
|
|
69
|
+
const listFailure = mapSubprocessFailure(listResult, 'opa');
|
|
70
|
+
if (listFailure)
|
|
71
|
+
return listFailure;
|
|
72
|
+
if (listResult.exitCode !== 0) {
|
|
73
|
+
return err('INVALID_REGO', 'opa fmt could not parse one or more files.', {
|
|
74
|
+
details: { stderr: listResult.stderr.trim(), exitCode: listResult.exitCode },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const formattedFiles = parseFmtListOutput(listResult.stdout);
|
|
78
|
+
if (dryRun) {
|
|
79
|
+
return ok({
|
|
80
|
+
formattedFiles,
|
|
81
|
+
formattedCount: formattedFiles.length,
|
|
82
|
+
dryRun: true,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// Phase 2: nothing to write -- skip the subprocess call.
|
|
86
|
+
if (formattedFiles.length === 0) {
|
|
87
|
+
return ok({
|
|
88
|
+
formattedFiles: [],
|
|
89
|
+
formattedCount: 0,
|
|
90
|
+
dryRun: false,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// Phase 2: write.
|
|
94
|
+
const writeResult = await opa.fmtWrite(fmtInput);
|
|
95
|
+
const writeFailure = mapSubprocessFailure(writeResult, 'opa');
|
|
96
|
+
if (writeFailure)
|
|
97
|
+
return writeFailure;
|
|
98
|
+
if (writeResult.exitCode !== 0) {
|
|
99
|
+
return err('INVALID_REGO', 'opa fmt --write failed.', {
|
|
100
|
+
details: { stderr: writeResult.stderr.trim(), exitCode: writeResult.exitCode },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return ok({
|
|
104
|
+
formattedFiles,
|
|
105
|
+
formattedCount: formattedFiles.length,
|
|
106
|
+
dryRun: false,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=format-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-write.js","sourceRoot":"","sources":["../../../src/tools/helpers/format-write.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/helpers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/helpers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAY9C,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAW3E"}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { registerRegoCoverageGaps } from './coverage-gaps.js';
|
|
2
2
|
import { registerRegoDescribePolicy } from './describe-policy.js';
|
|
3
3
|
import { registerRegoExplainDecision } from './explain-decision.js';
|
|
4
|
+
import { registerRegoFix } from './fix.js';
|
|
5
|
+
import { registerRegoFormatWrite } from './format-write.js';
|
|
4
6
|
import { registerRegoGenerateTestSkeleton } from './generate-test-skeleton.js';
|
|
7
|
+
import { registerRegoInferInputSchema } from './infer-input-schema.js';
|
|
8
|
+
import { registerRegoPolicyDiff } from './policy-diff.js';
|
|
5
9
|
import { registerRegoSecurityAudit } from './security-audit.js';
|
|
6
10
|
import { registerRegoSuggestFix } from './suggest-fix.js';
|
|
7
11
|
export function registerHelperTools(server, config) {
|
|
@@ -11,5 +15,9 @@ export function registerHelperTools(server, config) {
|
|
|
11
15
|
registerRegoSuggestFix(server, config);
|
|
12
16
|
registerRegoCoverageGaps(server, config);
|
|
13
17
|
registerRegoSecurityAudit(server, config);
|
|
18
|
+
registerRegoInferInputSchema(server, config);
|
|
19
|
+
registerRegoFix(server, config);
|
|
20
|
+
registerRegoFormatWrite(server, config);
|
|
21
|
+
registerRegoPolicyDiff(server, config);
|
|
14
22
|
}
|
|
15
23
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/helpers/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,gCAAgC,EAAE,MAAM,6BAA6B,CAAC;AAC/E,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,MAAc;IACnE,2BAA2B,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,gCAAgC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,0BAA0B,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,wBAAwB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/helpers/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,gCAAgC,EAAE,MAAM,6BAA6B,CAAC;AAC/E,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,MAAc;IACnE,2BAA2B,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,gCAAgC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,0BAA0B,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,wBAAwB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,4BAA4B,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { Config } from '../../config.js';
|
|
3
|
+
export interface RegoInferInputSchemaOutput {
|
|
4
|
+
/** JSON Schema draft-07 object describing the inferred input shape. */
|
|
5
|
+
schema: object;
|
|
6
|
+
/** Human-readable list of every input.* path found, e.g. ["input.action", "input.user.role"]. */
|
|
7
|
+
inputPaths: string[];
|
|
8
|
+
/** Number of .rego files analysed. */
|
|
9
|
+
filesAnalyzed: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function registerRegoInferInputSchema(server: McpServer, config: Config): void;
|
|
12
|
+
//# sourceMappingURL=infer-input-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"infer-input-schema.d.ts","sourceRoot":"","sources":["../../../src/tools/helpers/infer-input-schema.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAkC9C,MAAM,WAAW,0BAA0B;IACzC,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;IACf,iGAAiG;IACjG,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,sCAAsC;IACtC,aAAa,EAAE,MAAM,CAAC;CACvB;AAkGD,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAyFpF"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rego_infer_input_schema` -- statically analyse Rego source and return
|
|
3
|
+
* a JSON Schema describing every input.* field the policy reads.
|
|
4
|
+
*
|
|
5
|
+
* Uses `opa parse --format=json` to get the module AST, then walks the
|
|
6
|
+
* tree for Ref nodes whose first term is the `input` var. String-keyed
|
|
7
|
+
* path components become nested object properties; variable-keyed
|
|
8
|
+
* components (array wildcards like `_`) mark the parent field as an
|
|
9
|
+
* array type.
|
|
10
|
+
*
|
|
11
|
+
* The result is not a validation schema -- it cannot infer scalar types
|
|
12
|
+
* without semantic analysis -- but it gives the complete set of fields a
|
|
13
|
+
* policy touches, which is the correct starting point for writing
|
|
14
|
+
* integration tests or setting up `opa check --schema`.
|
|
15
|
+
*/
|
|
16
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
17
|
+
import { extname, join } from 'node:path';
|
|
18
|
+
import { z } from 'zod';
|
|
19
|
+
import { OpaCli } from '../../lib/opa-cli.js';
|
|
20
|
+
import { err, ok } from '../../lib/errors.js';
|
|
21
|
+
import { mapSubprocessFailure, tryParseJson, validatePaths, withToolEnvelope, } from '../../lib/tool-helpers.js';
|
|
22
|
+
const RegoInferInputSchemaInput = {
|
|
23
|
+
source: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('Inline Rego source to analyse. Mutually exclusive with paths.'),
|
|
27
|
+
paths: z
|
|
28
|
+
.array(z.string())
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('Policy files or directories to analyse. Each must be inside an allowed root (OPA_MCP_ALLOWED_PATHS). Directories are walked recursively for *.rego files.'),
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Recursively walk an OPA parse AST (arbitrary JSON) and collect every
|
|
34
|
+
* Ref whose first term is the `input` variable. Each collected ref is an
|
|
35
|
+
* array of path parts: string for a named key, null for a variable/wildcard.
|
|
36
|
+
*/
|
|
37
|
+
function collectInputRefs(node, refs) {
|
|
38
|
+
if (node === null || node === undefined || typeof node !== 'object')
|
|
39
|
+
return;
|
|
40
|
+
if (Array.isArray(node)) {
|
|
41
|
+
for (const item of node)
|
|
42
|
+
collectInputRefs(item, refs);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const obj = node;
|
|
46
|
+
if (obj['type'] === 'ref' && Array.isArray(obj['value'])) {
|
|
47
|
+
const terms = obj['value'];
|
|
48
|
+
if (terms.length >= 2 && terms[0]?.type === 'var' && terms[0]?.value === 'input') {
|
|
49
|
+
const path = [];
|
|
50
|
+
for (let i = 1; i < terms.length; i++) {
|
|
51
|
+
const t = terms[i];
|
|
52
|
+
if (!t)
|
|
53
|
+
break;
|
|
54
|
+
if (t.type === 'string' && typeof t.value === 'string') {
|
|
55
|
+
path.push(t.value);
|
|
56
|
+
}
|
|
57
|
+
else if (t.type === 'var') {
|
|
58
|
+
path.push(null); // array wildcard (e.g. `_` or a named loop variable)
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
break; // computed/dynamic key -- cannot statically resolve
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (path.length > 0)
|
|
65
|
+
refs.push(path);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const val of Object.values(obj)) {
|
|
69
|
+
collectInputRefs(val, refs);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Insert a single input path into the growing schema tree.
|
|
74
|
+
* A null part signals array access; everything else is an object key.
|
|
75
|
+
*/
|
|
76
|
+
function mergePath(node, path, idx) {
|
|
77
|
+
if (idx >= path.length)
|
|
78
|
+
return;
|
|
79
|
+
const part = path[idx];
|
|
80
|
+
if (part === null || part === undefined) {
|
|
81
|
+
if (node.type !== 'array') {
|
|
82
|
+
node.type = 'array';
|
|
83
|
+
node.items = { type: 'object', properties: {} };
|
|
84
|
+
delete node.properties;
|
|
85
|
+
}
|
|
86
|
+
if (idx + 1 < path.length && node.items) {
|
|
87
|
+
mergePath(node.items, path, idx + 1);
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!node.properties)
|
|
92
|
+
node.properties = {};
|
|
93
|
+
if (node.type !== 'array')
|
|
94
|
+
node.type = 'object';
|
|
95
|
+
if (!node.properties[part])
|
|
96
|
+
node.properties[part] = {};
|
|
97
|
+
if (idx + 1 < path.length)
|
|
98
|
+
mergePath(node.properties[part], path, idx + 1);
|
|
99
|
+
}
|
|
100
|
+
function buildSchema(allPaths) {
|
|
101
|
+
const seen = new Set();
|
|
102
|
+
const root = { type: 'object', properties: {} };
|
|
103
|
+
for (const path of allPaths) {
|
|
104
|
+
const key = JSON.stringify(path);
|
|
105
|
+
if (!seen.has(key)) {
|
|
106
|
+
seen.add(key);
|
|
107
|
+
mergePath(root, path, 0);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { $schema: 'http://json-schema.org/draft-07/schema#', ...root };
|
|
111
|
+
}
|
|
112
|
+
function pathToString(path) {
|
|
113
|
+
return 'input.' + path.map((p) => (p === null ? '[]' : p)).join('.');
|
|
114
|
+
}
|
|
115
|
+
async function findRegoFiles(root) {
|
|
116
|
+
const files = [];
|
|
117
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
118
|
+
for (const entry of entries) {
|
|
119
|
+
const full = join(root, entry.name);
|
|
120
|
+
if (entry.isDirectory()) {
|
|
121
|
+
files.push(...(await findRegoFiles(full)));
|
|
122
|
+
}
|
|
123
|
+
else if (entry.isFile() && extname(entry.name) === '.rego') {
|
|
124
|
+
files.push(full);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return files;
|
|
128
|
+
}
|
|
129
|
+
export function registerRegoInferInputSchema(server, config) {
|
|
130
|
+
const opa = new OpaCli(config);
|
|
131
|
+
server.registerTool('rego_infer_input_schema', {
|
|
132
|
+
title: 'Infer input schema',
|
|
133
|
+
description: 'Statically analyse one or more Rego policies and return a JSON Schema (draft-07) object describing every input.* field the policies read. Uses opa parse for AST-level analysis -- no running OPA server required. Correct starting point for writing integration tests, configuring opa check --schema validation, or documenting a policy API. Accepts inline source, individual files, or directories (walked recursively for *.rego files).',
|
|
134
|
+
inputSchema: RegoInferInputSchemaInput,
|
|
135
|
+
}, async ({ source, paths }) => {
|
|
136
|
+
return withToolEnvelope(config, async () => {
|
|
137
|
+
if (source === undefined && (!paths || paths.length === 0)) {
|
|
138
|
+
return err('UNKNOWN_ERROR', 'rego_infer_input_schema requires either source or at least one path.');
|
|
139
|
+
}
|
|
140
|
+
const allRefs = [];
|
|
141
|
+
let filesAnalyzed = 0;
|
|
142
|
+
if (source !== undefined) {
|
|
143
|
+
const result = await opa.parse({ source });
|
|
144
|
+
const failure = mapSubprocessFailure(result, 'opa');
|
|
145
|
+
if (failure)
|
|
146
|
+
return failure;
|
|
147
|
+
if (result.exitCode !== 0) {
|
|
148
|
+
return err('INVALID_REGO', 'opa parse failed -- check the policy for syntax errors.', {
|
|
149
|
+
details: { stderr: result.stderr.trim() },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const ast = tryParseJson(result.stdout);
|
|
153
|
+
if (ast)
|
|
154
|
+
collectInputRefs(ast, allRefs);
|
|
155
|
+
filesAnalyzed = 1;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const validation = validatePaths(paths, config, { mustExist: true });
|
|
159
|
+
if (!validation.ok)
|
|
160
|
+
return validation.error;
|
|
161
|
+
const filePaths = [];
|
|
162
|
+
for (const p of validation.resolved) {
|
|
163
|
+
const s = await stat(p);
|
|
164
|
+
if (s.isDirectory()) {
|
|
165
|
+
filePaths.push(...(await findRegoFiles(p)));
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
filePaths.push(p);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
for (const filePath of filePaths) {
|
|
172
|
+
const result = await opa.run(['parse', '--format=json', filePath]);
|
|
173
|
+
const failure = mapSubprocessFailure(result, 'opa');
|
|
174
|
+
if (failure)
|
|
175
|
+
return failure;
|
|
176
|
+
// Skip files that fail to parse (e.g. test files with syntax issues)
|
|
177
|
+
// rather than aborting the entire analysis.
|
|
178
|
+
if (result.exitCode === 0) {
|
|
179
|
+
const ast = tryParseJson(result.stdout);
|
|
180
|
+
if (ast) {
|
|
181
|
+
collectInputRefs(ast, allRefs);
|
|
182
|
+
filesAnalyzed++;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const schema = buildSchema(allRefs);
|
|
188
|
+
const seen = new Set();
|
|
189
|
+
const inputPaths = [];
|
|
190
|
+
for (const p of allRefs) {
|
|
191
|
+
const s = pathToString(p);
|
|
192
|
+
if (!seen.has(s)) {
|
|
193
|
+
seen.add(s);
|
|
194
|
+
inputPaths.push(s);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
inputPaths.sort();
|
|
198
|
+
const warnings = [];
|
|
199
|
+
if (inputPaths.length === 0) {
|
|
200
|
+
warnings.push('No input.* references found. The policy may not read from input at all, or may use dynamic keys (e.g. input[key]) that cannot be statically resolved.');
|
|
201
|
+
}
|
|
202
|
+
return ok({ schema, inputPaths, filesAnalyzed }, warnings);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=infer-input-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"infer-input-schema.js","sourceRoot":"","sources":["../../../src/tools/helpers/infer-input-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { Config } from '../../config.js';
|
|
3
|
+
/** Raw value extracted from one side of an OPA eval result. */
|
|
4
|
+
type ResultValue = unknown;
|
|
5
|
+
export interface RegoPolicyDiffOutput {
|
|
6
|
+
/** The query that was evaluated. */
|
|
7
|
+
query: string;
|
|
8
|
+
/** True when resultA and resultB are structurally identical. */
|
|
9
|
+
equal: boolean;
|
|
10
|
+
/** Value extracted from policy A's evaluation. Undefined when the query is undefined for that policy. */
|
|
11
|
+
resultA: ResultValue;
|
|
12
|
+
/** Value extracted from policy B's evaluation. */
|
|
13
|
+
resultB: ResultValue;
|
|
14
|
+
/**
|
|
15
|
+
* Dot/bracket paths that differ between resultA and resultB.
|
|
16
|
+
* Empty when equal is true. "." denotes a root-level scalar difference.
|
|
17
|
+
*/
|
|
18
|
+
changedPaths: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Extract the meaningful value from `opa eval --format=json` stdout.
|
|
22
|
+
*
|
|
23
|
+
* - Single result, single expression: returns expressions[0].value
|
|
24
|
+
* - Multiple result rows (iteration binding): returns the full result array
|
|
25
|
+
* - Empty result (`{}` or `{"result":[]}`) or parse failure: returns undefined
|
|
26
|
+
*/
|
|
27
|
+
export declare function extractResultValue(stdout: string): ResultValue;
|
|
28
|
+
/**
|
|
29
|
+
* Return the set of dot/bracket paths where `a` and `b` differ.
|
|
30
|
+
*
|
|
31
|
+
* Rules:
|
|
32
|
+
* - Identical values (JSON.stringify equal): []
|
|
33
|
+
* - Different types, nulls, or undefined on either side: [path || '.']
|
|
34
|
+
* - Both arrays with different lengths: [path || '.']
|
|
35
|
+
* - Both arrays, same length: recurse per index using `[i]` notation
|
|
36
|
+
* - Both plain objects: recurse per key using dot notation
|
|
37
|
+
*/
|
|
38
|
+
export declare function diffValues(a: unknown, b: unknown, path?: string): string[];
|
|
39
|
+
export declare function registerRegoPolicyDiff(server: McpServer, config: Config): void;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=policy-diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-diff.d.ts","sourceRoot":"","sources":["../../../src/tools/helpers/policy-diff.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAgD9C,+DAA+D;AAC/D,KAAK,WAAW,GAAG,OAAO,CAAC;AAE3B,MAAM,WAAW,oBAAoB;IACnC,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,KAAK,EAAE,OAAO,CAAC;IACf,yGAAyG;IACzG,OAAO,EAAE,WAAW,CAAC;IACrB,kDAAkD;IAClD,OAAO,EAAE,WAAW,CAAC;IACrB;;;OAGG;IACH,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAwB9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,SAAK,GAAG,MAAM,EAAE,CAqCtE;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAiH9E"}
|