@orygn/opa-mcp 0.0.0 → 0.1.1

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.
Files changed (184) hide show
  1. package/CHANGELOG.md +150 -0
  2. package/LICENSE +21 -0
  3. package/README.md +622 -2
  4. package/dist/config.d.ts +58 -0
  5. package/dist/config.d.ts.map +1 -0
  6. package/dist/config.js +72 -0
  7. package/dist/config.js.map +1 -0
  8. package/dist/lib/errors.d.ts +18 -0
  9. package/dist/lib/errors.d.ts.map +1 -0
  10. package/dist/lib/errors.js +25 -0
  11. package/dist/lib/errors.js.map +1 -0
  12. package/dist/lib/logger.d.ts +10 -0
  13. package/dist/lib/logger.d.ts.map +1 -0
  14. package/dist/lib/logger.js +57 -0
  15. package/dist/lib/logger.js.map +1 -0
  16. package/dist/lib/opa-cli.d.ts +226 -0
  17. package/dist/lib/opa-cli.d.ts.map +1 -0
  18. package/dist/lib/opa-cli.js +283 -0
  19. package/dist/lib/opa-cli.js.map +1 -0
  20. package/dist/lib/opa-client.d.ts +47 -0
  21. package/dist/lib/opa-client.d.ts.map +1 -0
  22. package/dist/lib/opa-client.js +101 -0
  23. package/dist/lib/opa-client.js.map +1 -0
  24. package/dist/lib/output.d.ts +17 -0
  25. package/dist/lib/output.d.ts.map +1 -0
  26. package/dist/lib/output.js +21 -0
  27. package/dist/lib/output.js.map +1 -0
  28. package/dist/lib/regal-cli.d.ts +79 -0
  29. package/dist/lib/regal-cli.d.ts.map +1 -0
  30. package/dist/lib/regal-cli.js +122 -0
  31. package/dist/lib/regal-cli.js.map +1 -0
  32. package/dist/lib/security.d.ts +16 -0
  33. package/dist/lib/security.d.ts.map +1 -0
  34. package/dist/lib/security.js +64 -0
  35. package/dist/lib/security.js.map +1 -0
  36. package/dist/lib/subprocess.d.ts +26 -0
  37. package/dist/lib/subprocess.d.ts.map +1 -0
  38. package/dist/lib/subprocess.js +72 -0
  39. package/dist/lib/subprocess.js.map +1 -0
  40. package/dist/lib/tool-helpers.d.ts +46 -0
  41. package/dist/lib/tool-helpers.d.ts.map +1 -0
  42. package/dist/lib/tool-helpers.js +73 -0
  43. package/dist/lib/tool-helpers.js.map +1 -0
  44. package/dist/prompts/index.d.ts +4 -0
  45. package/dist/prompts/index.d.ts.map +1 -0
  46. package/dist/prompts/index.js +147 -0
  47. package/dist/prompts/index.js.map +1 -0
  48. package/dist/resources/index.d.ts +12 -0
  49. package/dist/resources/index.d.ts.map +1 -0
  50. package/dist/resources/index.js +107 -0
  51. package/dist/resources/index.js.map +1 -0
  52. package/dist/resources/patterns.d.ts +9 -0
  53. package/dist/resources/patterns.d.ts.map +1 -0
  54. package/dist/resources/patterns.js +375 -0
  55. package/dist/resources/patterns.js.map +1 -0
  56. package/dist/resources/style-guide.d.ts +6 -0
  57. package/dist/resources/style-guide.d.ts.map +1 -0
  58. package/dist/resources/style-guide.js +187 -0
  59. package/dist/resources/style-guide.js.map +1 -0
  60. package/dist/server.d.ts +34 -0
  61. package/dist/server.d.ts.map +1 -0
  62. package/dist/server.js +125 -0
  63. package/dist/server.js.map +1 -0
  64. package/dist/tools/authoring/capabilities.d.ts +11 -0
  65. package/dist/tools/authoring/capabilities.d.ts.map +1 -0
  66. package/dist/tools/authoring/capabilities.js +57 -0
  67. package/dist/tools/authoring/capabilities.js.map +1 -0
  68. package/dist/tools/authoring/check.d.ts +18 -0
  69. package/dist/tools/authoring/check.d.ts.map +1 -0
  70. package/dist/tools/authoring/check.js +71 -0
  71. package/dist/tools/authoring/check.js.map +1 -0
  72. package/dist/tools/authoring/deps.d.ts +8 -0
  73. package/dist/tools/authoring/deps.d.ts.map +1 -0
  74. package/dist/tools/authoring/deps.js +51 -0
  75. package/dist/tools/authoring/deps.js.map +1 -0
  76. package/dist/tools/authoring/format.d.ts +8 -0
  77. package/dist/tools/authoring/format.d.ts.map +1 -0
  78. package/dist/tools/authoring/format.js +44 -0
  79. package/dist/tools/authoring/format.js.map +1 -0
  80. package/dist/tools/authoring/index.d.ts +11 -0
  81. package/dist/tools/authoring/index.d.ts.map +1 -0
  82. package/dist/tools/authoring/index.js +17 -0
  83. package/dist/tools/authoring/index.js.map +1 -0
  84. package/dist/tools/authoring/inspect.d.ts +10 -0
  85. package/dist/tools/authoring/inspect.d.ts.map +1 -0
  86. package/dist/tools/authoring/inspect.js +47 -0
  87. package/dist/tools/authoring/inspect.js.map +1 -0
  88. package/dist/tools/authoring/lint.d.ts +18 -0
  89. package/dist/tools/authoring/lint.d.ts.map +1 -0
  90. package/dist/tools/authoring/lint.js +111 -0
  91. package/dist/tools/authoring/lint.js.map +1 -0
  92. package/dist/tools/authoring/parse.d.ts +7 -0
  93. package/dist/tools/authoring/parse.d.ts.map +1 -0
  94. package/dist/tools/authoring/parse.js +43 -0
  95. package/dist/tools/authoring/parse.js.map +1 -0
  96. package/dist/tools/bundles/build.d.ts +8 -0
  97. package/dist/tools/bundles/build.d.ts.map +1 -0
  98. package/dist/tools/bundles/build.js +87 -0
  99. package/dist/tools/bundles/build.js.map +1 -0
  100. package/dist/tools/bundles/index.d.ts +10 -0
  101. package/dist/tools/bundles/index.d.ts.map +1 -0
  102. package/dist/tools/bundles/index.js +7 -0
  103. package/dist/tools/bundles/index.js.map +1 -0
  104. package/dist/tools/bundles/sign.d.ts +8 -0
  105. package/dist/tools/bundles/sign.d.ts.map +1 -0
  106. package/dist/tools/bundles/sign.js +47 -0
  107. package/dist/tools/bundles/sign.js.map +1 -0
  108. package/dist/tools/evaluation/_shared.d.ts +55 -0
  109. package/dist/tools/evaluation/_shared.d.ts.map +1 -0
  110. package/dist/tools/evaluation/_shared.js +102 -0
  111. package/dist/tools/evaluation/_shared.js.map +1 -0
  112. package/dist/tools/evaluation/bench.d.ts +9 -0
  113. package/dist/tools/evaluation/bench.d.ts.map +1 -0
  114. package/dist/tools/evaluation/bench.js +76 -0
  115. package/dist/tools/evaluation/bench.js.map +1 -0
  116. package/dist/tools/evaluation/compile.d.ts +11 -0
  117. package/dist/tools/evaluation/compile.d.ts.map +1 -0
  118. package/dist/tools/evaluation/compile.js +19 -0
  119. package/dist/tools/evaluation/compile.js.map +1 -0
  120. package/dist/tools/evaluation/eval.d.ts +10 -0
  121. package/dist/tools/evaluation/eval.d.ts.map +1 -0
  122. package/dist/tools/evaluation/eval.js +35 -0
  123. package/dist/tools/evaluation/eval.js.map +1 -0
  124. package/dist/tools/evaluation/index.d.ts +11 -0
  125. package/dist/tools/evaluation/index.d.ts.map +1 -0
  126. package/dist/tools/evaluation/index.js +11 -0
  127. package/dist/tools/evaluation/index.js.map +1 -0
  128. package/dist/tools/evaluation/test.d.ts +28 -0
  129. package/dist/tools/evaluation/test.d.ts.map +1 -0
  130. package/dist/tools/evaluation/test.js +74 -0
  131. package/dist/tools/evaluation/test.js.map +1 -0
  132. package/dist/tools/helpers/describe-policy.d.ts +26 -0
  133. package/dist/tools/helpers/describe-policy.d.ts.map +1 -0
  134. package/dist/tools/helpers/describe-policy.js +76 -0
  135. package/dist/tools/helpers/describe-policy.js.map +1 -0
  136. package/dist/tools/helpers/explain-decision.d.ts +41 -0
  137. package/dist/tools/helpers/explain-decision.d.ts.map +1 -0
  138. package/dist/tools/helpers/explain-decision.js +75 -0
  139. package/dist/tools/helpers/explain-decision.js.map +1 -0
  140. package/dist/tools/helpers/generate-test-skeleton.d.ts +8 -0
  141. package/dist/tools/helpers/generate-test-skeleton.d.ts.map +1 -0
  142. package/dist/tools/helpers/generate-test-skeleton.js +93 -0
  143. package/dist/tools/helpers/generate-test-skeleton.js.map +1 -0
  144. package/dist/tools/helpers/index.d.ts +12 -0
  145. package/dist/tools/helpers/index.d.ts.map +1 -0
  146. package/dist/tools/helpers/index.js +11 -0
  147. package/dist/tools/helpers/index.js.map +1 -0
  148. package/dist/tools/helpers/suggest-fix.d.ts +15 -0
  149. package/dist/tools/helpers/suggest-fix.d.ts.map +1 -0
  150. package/dist/tools/helpers/suggest-fix.js +119 -0
  151. package/dist/tools/helpers/suggest-fix.js.map +1 -0
  152. package/dist/tools/index.d.ts +31 -0
  153. package/dist/tools/index.d.ts.map +1 -0
  154. package/dist/tools/index.js +13 -0
  155. package/dist/tools/index.js.map +1 -0
  156. package/dist/tools/server-management/_shared.d.ts +9 -0
  157. package/dist/tools/server-management/_shared.d.ts.map +1 -0
  158. package/dist/tools/server-management/_shared.js +41 -0
  159. package/dist/tools/server-management/_shared.js.map +1 -0
  160. package/dist/tools/server-management/data.d.ts +4 -0
  161. package/dist/tools/server-management/data.d.ts.map +1 -0
  162. package/dist/tools/server-management/data.js +90 -0
  163. package/dist/tools/server-management/data.js.map +1 -0
  164. package/dist/tools/server-management/decisions.d.ts +4 -0
  165. package/dist/tools/server-management/decisions.d.ts.map +1 -0
  166. package/dist/tools/server-management/decisions.js +87 -0
  167. package/dist/tools/server-management/decisions.js.map +1 -0
  168. package/dist/tools/server-management/index.d.ts +11 -0
  169. package/dist/tools/server-management/index.d.ts.map +1 -0
  170. package/dist/tools/server-management/index.js +11 -0
  171. package/dist/tools/server-management/index.js.map +1 -0
  172. package/dist/tools/server-management/policies.d.ts +4 -0
  173. package/dist/tools/server-management/policies.d.ts.map +1 -0
  174. package/dist/tools/server-management/policies.js +94 -0
  175. package/dist/tools/server-management/policies.js.map +1 -0
  176. package/dist/tools/server-management/status.d.ts +4 -0
  177. package/dist/tools/server-management/status.d.ts.map +1 -0
  178. package/dist/tools/server-management/status.js +82 -0
  179. package/dist/tools/server-management/status.js.map +1 -0
  180. package/dist/types.d.ts +24 -0
  181. package/dist/types.d.ts.map +1 -0
  182. package/dist/types.js +5 -0
  183. package/dist/types.js.map +1 -0
  184. package/package.json +86 -4
package/README.md CHANGED
@@ -1,3 +1,623 @@
1
- # @orygn/opa-mcp
1
+ # OPA MCP Server
2
2
 
3
- Reserved namespace. Stable release coming soon at https://github.com/OrygnsCode/opa-mcp-server
3
+ [![CI](https://github.com/OrygnsCode/opa-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/OrygnsCode/opa-mcp-server/actions/workflows/ci.yml)
4
+ [![CodeQL](https://github.com/OrygnsCode/opa-mcp-server/actions/workflows/codeql.yml/badge.svg)](https://github.com/OrygnsCode/opa-mcp-server/actions/workflows/codeql.yml)
5
+ [![npm version](https://img.shields.io/npm/v/@orygn/opa-mcp.svg)](https://www.npmjs.com/package/@orygn/opa-mcp)
6
+ [![Docker pulls](https://img.shields.io/docker/pulls/orygn/opa-mcp.svg)](https://hub.docker.com/r/orygn/opa-mcp)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
8
+ [![Node.js](https://img.shields.io/node/v/@orygn/opa-mcp.svg)](./package.json)
9
+
10
+ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server
11
+ that turns any MCP-compatible client (Claude Desktop, Claude Code, Cursor,
12
+ VS Code, Windsurf, Zed, and others) into a first-class
13
+ [Open Policy Agent](https://www.openpolicyagent.org/) and Rego authoring
14
+ environment.
15
+
16
+ ```
17
+ +--------------------+ MCP / stdio +-----------------+ spawn / HTTP +------------------+
18
+ | Claude · Cursor · | ------------> | @orygn/opa-mcp | -------------> | opa · regal · |
19
+ | VS Code · ... | <------------ | | <------------- | OPA REST API |
20
+ +--------------------+ 32 tools +-----------------+ +------------------+
21
+ ```
22
+
23
+ > **Status:** v0.1.1. Tool surface, error codes, and
24
+ > environment variables follow [SemVer](https://semver.org/) from
25
+ > v0.1.0 forward.
26
+
27
+ ---
28
+
29
+ ## Table of contents
30
+
31
+ - [What you can do with it](#what-you-can-do-with-it)
32
+ - [Why this MCP](#why-this-mcp)
33
+ - [Install](#install)
34
+ - [Configuration](#configuration)
35
+ - [Tool reference](#tool-reference)
36
+ - [Prompts](#prompts)
37
+ - [Resources](#resources)
38
+ - [Cookbook](#cookbook)
39
+ - [Architecture](#architecture)
40
+ - [Security](#security)
41
+ - [Troubleshooting](#troubleshooting)
42
+ - [Development](#development)
43
+ - [Versioning & support](#versioning--support)
44
+ - [License](#license)
45
+
46
+ ## What you can do with it
47
+
48
+ Once an MCP client is connected, an agent can:
49
+
50
+ - **Author Rego.** Generate, format, and refactor policies. The server
51
+ runs the real `opa fmt` and `opa parse` so output is byte-identical to
52
+ what you'd get on the command line, and `regal` (optional) surfaces
53
+ idiomatic suggestions.
54
+ - **Evaluate against data.** Run a query against a policy and an input
55
+ document. Optional `--explain`, `--profile`, and `--coverage` flags
56
+ surface execution traces, hot rules, and per-line coverage.
57
+ - **Debug a deny.** `rego_explain_decision` walks the agent through every
58
+ rule that fired (and every one that didn't), so it can answer "why was
59
+ this rejected" without you reading the trace by hand.
60
+ - **Manage policies on a running OPA.** List, get, put, delete policies on
61
+ an OPA server through its REST API. Works against a local
62
+ `opa run --server` or a production deployment with bearer-token auth.
63
+ - **Build & sign bundles.** Package a directory of policies into a
64
+ deployable bundle, optionally signing it. Output is a regular `.tar.gz`
65
+ the agent can hand to your delivery system.
66
+ - **Lint.** `rego_lint` runs Regal across a directory or a single file
67
+ and returns categorized findings (style, bugs, performance, idioms).
68
+
69
+ A walk-through of a typical session lives in [Cookbook](#cookbook).
70
+
71
+ ## Why this MCP
72
+
73
+ OPA already has a perfectly good CLI and REST API. So why an MCP wrapper?
74
+
75
+ - **Schema-shaped tool surface.** An agent calling `rego_eval` gets a
76
+ validated input schema, a structured output envelope, and stable error
77
+ codes, instead of parsing free-form CLI text and inventing its own
78
+ failure taxonomy. That alone makes Rego usable to an agent the way a
79
+ language server makes a language usable to an IDE.
80
+ - **Higher-level helpers.** `rego_explain_decision`,
81
+ `rego_generate_test_skeleton`, `rego_describe_policy`, and
82
+ `rego_suggest_fix` compose the lower-level primitives into the tasks
83
+ agents are actually asked to do. They don't exist in the OPA CLI.
84
+ - **Curated knowledge.** The bundled MCP **resources** expose the OPA
85
+ built-in function catalog, the official Rego style guide (formatted for
86
+ LLMs), and a curated pattern library covering RBAC, ABAC, Kubernetes
87
+ admission, IaC gates, API authz, and rate limiting, so the agent has
88
+ authoritative context without needing to scrape it.
89
+ - **Safety boundaries the agent can rely on.** Path allow-list,
90
+ subprocess timeouts, response-size caps, and an explicit
91
+ `HTTP_SEND_BLOCKED` error for the dangerous OPA built-ins. Defaults are
92
+ conservative; running the server doesn't quietly grant the agent more
93
+ reach than the operator intended.
94
+
95
+ If you've ever watched an agent fight `opa eval`'s argument order, you'll
96
+ recognize the gap this fills.
97
+
98
+ ## Install
99
+
100
+ The server runs locally over stdio. Pick the install path that matches
101
+ your client.
102
+
103
+ ### Claude Desktop / Claude Code
104
+
105
+ The fastest path is the Smithery one-liner:
106
+
107
+ ```bash
108
+ npx -y @smithery/cli install @orygn/opa-mcp --client claude
109
+ ```
110
+
111
+ Or download `opa-mcp.mcpb` from the
112
+ [latest release](https://github.com/OrygnsCode/opa-mcp-server/releases/latest)
113
+ and double-click it.
114
+
115
+ If you prefer to edit `claude_desktop_config.json` by hand, the snippet
116
+ lives in [`examples/claude-desktop.json`](./examples/claude-desktop.json):
117
+
118
+ ```json
119
+ {
120
+ "mcpServers": {
121
+ "opa": {
122
+ "command": "npx",
123
+ "args": ["-y", "@orygn/opa-mcp"],
124
+ "env": {
125
+ "OPA_BINARY": "/usr/local/bin/opa",
126
+ "REGAL_BINARY": "/usr/local/bin/regal",
127
+ "OPA_URL": "http://localhost:8181",
128
+ "OPA_MCP_ALLOWED_PATHS": "/path/to/your/policies"
129
+ }
130
+ }
131
+ }
132
+ }
133
+ ```
134
+
135
+ > Replace the `/usr/local/bin/...` paths with your real ones. See the
136
+ > [first-time install gotcha](#-first-time-install-gotcha-read-this-if-you-used-npx-or-the-global-install)
137
+ > below. Windows users substitute `C:\\path\\to\\opa.exe`.
138
+
139
+ ### Cursor
140
+
141
+ Drop [`examples/cursor.json`](./examples/cursor.json) into either
142
+ `.cursor/mcp.json` (project-scoped) or `~/.cursor/mcp.json` (user-scoped).
143
+
144
+ ### VS Code (GitHub Copilot Chat)
145
+
146
+ Drop [`examples/vscode.json`](./examples/vscode.json) into
147
+ `.vscode/mcp.json`, or paste the `servers` block into your user
148
+ `settings.json` under `mcp.servers`.
149
+
150
+ ### Windsurf, Zed, and others
151
+
152
+ See [`examples/`](./examples) for a full set of drop-in configs.
153
+
154
+ ### Manual install (any MCP client)
155
+
156
+ ```bash
157
+ npm install -g @orygn/opa-mcp
158
+ opa-mcp --version
159
+ ```
160
+
161
+ then point your client at the `opa-mcp` binary.
162
+
163
+ ### Docker
164
+
165
+ ```bash
166
+ docker pull orygn/opa-mcp:latest
167
+ docker run --rm -i \
168
+ -v /path/to/your/policies:/policies:ro \
169
+ -e OPA_MCP_ALLOWED_PATHS=/policies \
170
+ orygn/opa-mcp
171
+ ```
172
+
173
+ The image is multi-arch (`linux/amd64`, `linux/arm64`), bundles pinned
174
+ versions of `opa` and `regal`, and runs as a non-root user. No host
175
+ install of OPA or Regal is required.
176
+
177
+ ### ⚠ First-time install gotcha (read this if you used `npx` or the global install)
178
+
179
+ If your client's `PATH` doesn't include the directory where `opa` lives
180
+ (this happens with Claude Desktop on Windows and macOS by default), the
181
+ server boots fine but every tool call returns `OPA_BINARY_NOT_FOUND`.
182
+
183
+ **Fix:** add `OPA_BINARY` and `REGAL_BINARY` env entries to your client
184
+ config with the absolute path to each binary. The example configs under
185
+ [`examples/`](./examples) ship with placeholder paths you replace.
186
+ Find the real paths with:
187
+
188
+ ```bash
189
+ which opa && which regal # macOS / Linux
190
+ ```
191
+
192
+ ```powershell
193
+ Get-Command opa, regal | Select-Object Source # Windows
194
+ ```
195
+
196
+ This does not affect the **Docker** or **MCPB** install paths; those
197
+ ship `opa` and `regal` inside the bundle and bypass `PATH` entirely.
198
+ See [Troubleshooting](#troubleshooting) for full detail.
199
+
200
+ ## Configuration
201
+
202
+ The server reads its configuration from environment variables. Every
203
+ variable is optional; defaults are sensible for a local OPA on
204
+ `http://localhost:8181`.
205
+
206
+ | Variable | Default | Purpose |
207
+ | ---------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
208
+ | `OPA_URL` | `http://localhost:8181` | Base URL of an OPA REST endpoint, used by `opa_*` tools. |
209
+ | `OPA_TOKEN` | _(unset)_ | Bearer token for OPA, if your instance requires auth. Treated as a secret. Never echoed in logs or tool responses. |
210
+ | `OPA_BINARY` | `opa` (on `PATH`) | Path to the `opa` CLI, used by `rego_*` tools. |
211
+ | `REGAL_BINARY` | `regal` (on `PATH`) | Path to the `regal` linter. Only required by `rego_lint`. |
212
+ | `OPA_MCP_ALLOWED_PATHS` | _(unset)_ | Comma- or semicolon-separated list of directories the server is allowed to read policies from. **When unset, file-based tools refuse to read from disk.** |
213
+ | `OPA_MCP_LOG_FILE` | `<tmpdir>/orygn-opa-mcp.log` | Path the server appends logs to. The server never writes to stdout; that channel is reserved for the MCP protocol. |
214
+ | `OPA_MCP_LOG_LEVEL` | `info` | One of `debug`, `info`, `warn`, `error`. |
215
+ | `OPA_MCP_MAX_RESPONSE_BYTES` | `100000` | Hard cap on a single tool response. Larger payloads are truncated with a `__truncated: true` marker. |
216
+ | `OPA_MCP_TIMEOUT_MS` | `30000` | Hard timeout for any spawned subprocess (`opa`, `regal`). After this, the child gets `SIGTERM` and then `SIGKILL`. |
217
+ | `OPA_MCP_HTTP_TIMEOUT_MS` | `15000` | Timeout for HTTP requests to the OPA REST API. |
218
+
219
+ Paths in `OPA_MCP_ALLOWED_PATHS` and the `*_BINARY` variables must be
220
+ absolute. Relative paths and missing binaries are rejected with structured
221
+ errors.
222
+
223
+ ## Tool reference
224
+
225
+ Every tool returns a JSON envelope:
226
+
227
+ ```json
228
+ { "ok": true, "data": { ... }, "warnings": [ ... ] }
229
+ { "ok": false, "error": { "code": "INVALID_REGO", "message": "...", "hint": "...", "details": { ... } } }
230
+ ```
231
+
232
+ Stable error codes: `INVALID_INPUT`, `INVALID_REGO`, `INVALID_BUNDLE`,
233
+ `EVAL_ERROR`, `OPA_BINARY_NOT_FOUND`, `REGAL_NOT_FOUND`,
234
+ `REGAL_VERSION_TOO_OLD`, `OPA_UNREACHABLE`, `OPA_AUTH_FAILED`,
235
+ `POLICY_NOT_FOUND`, `PATH_NOT_ALLOWED`, `PATH_NOT_FOUND`,
236
+ `DEPENDENCY_CONFLICT`, `NO_TESTS_FOUND`, `HTTP_SEND_BLOCKED`, `TIMEOUT`,
237
+ `UNKNOWN_ERROR`.
238
+
239
+ ### Category A: Authoring & static analysis
240
+
241
+ Operate on Rego source code without needing a running OPA server. Wrap
242
+ `opa fmt`, `opa parse`, `opa check`, `opa inspect`, `opa capabilities`,
243
+ `opa deps`, and `regal`.
244
+
245
+ | Tool | What it does |
246
+ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
247
+ | `rego_format` | Format Rego source. Wraps `opa fmt`. Idempotent. |
248
+ | `rego_check` | Type-check and validate Rego. Wraps `opa check`. |
249
+ | `rego_lint` | Run Regal across a file or directory. Returns findings grouped by category. **Requires `regal` on `PATH` or `REGAL_BINARY` set.** |
250
+ | `rego_parse_ast` | Parse Rego to AST JSON. Wraps `opa parse`. |
251
+ | `rego_inspect` | Inspect a bundle or directory: packages, rules, annotations. Wraps `opa inspect`. |
252
+ | `rego_capabilities` | Return the capabilities (built-ins, future keywords) understood by the bundled OPA. |
253
+ | `rego_deps` | Static dependency analysis: rule-level data references and cross-package calls. |
254
+
255
+ #### Featured: `rego_format`
256
+
257
+ ```jsonc
258
+ // Input
259
+ {
260
+ "source": "package x\nallow{input.user==\"admin\"}"
261
+ }
262
+
263
+ // Output (ok)
264
+ {
265
+ "ok": true,
266
+ "data": {
267
+ "formatted": "package x\n\nallow if input.user == \"admin\"\n",
268
+ "changed": true
269
+ }
270
+ }
271
+ ```
272
+
273
+ #### Featured: `rego_check`
274
+
275
+ ```jsonc
276
+ // Input
277
+ {
278
+ "source": "package x\nallow if y",
279
+ "strict": true
280
+ }
281
+
282
+ // Output (error path; the JSON diagnostics arrive on stderr from opa)
283
+ {
284
+ "ok": true,
285
+ "data": {
286
+ "valid": false,
287
+ "errors": [
288
+ {
289
+ "code": "rego_unsafe_var_error",
290
+ "message": "var y is unsafe",
291
+ "location": { "row": 2, "col": 11 }
292
+ }
293
+ ]
294
+ }
295
+ }
296
+ ```
297
+
298
+ ### Category B: Evaluation & testing
299
+
300
+ Run a query against a policy and input. Wrap `opa eval`, `opa test`, and
301
+ `opa bench`.
302
+
303
+ | Tool | What it does |
304
+ | ------------------------- | ------------------------------------------------------------------------------------ |
305
+ | `rego_eval` | Evaluate a query against a policy and input. The bread-and-butter tool. |
306
+ | `rego_eval_with_explain` | Evaluate with `--explain=full` and return a structured trace. |
307
+ | `rego_eval_with_profile` | Evaluate with `--profile` and return per-rule timing and evaluation counts. |
308
+ | `rego_eval_with_coverage` | Evaluate with `--coverage` and return per-line coverage. |
309
+ | `rego_test` | Run `opa test` over a directory. Returns pass/fail per test, with optional coverage. |
310
+ | `rego_bench` | Run `opa bench` and return statistical timing data. |
311
+ | `rego_compile_query` | Partially evaluate a query against a policy. |
312
+
313
+ #### Featured: `rego_eval`
314
+
315
+ ```jsonc
316
+ // Input
317
+ {
318
+ "query": "data.rbac.allow",
319
+ "source": "package rbac\nimport rego.v1\nallow if input.role == \"admin\"",
320
+ "input": { "role": "admin" }
321
+ }
322
+
323
+ // Output
324
+ {
325
+ "ok": true,
326
+ "data": {
327
+ "result": [{ "expressions": [{ "value": true, "text": "data.rbac.allow", "location": { "row": 1, "col": 1 } }] }]
328
+ }
329
+ }
330
+ ```
331
+
332
+ ### Category C: Bundle operations
333
+
334
+ Package and sign deployable bundles. Wrap `opa build` and `opa sign`.
335
+
336
+ | Tool | What it does |
337
+ | ------------------ | ------------------------------------------------------------------------------------- |
338
+ | `opa_bundle_build` | Build a `.tar.gz` bundle from a policy directory. Supports `optimize` and `revision`. |
339
+ | `opa_bundle_sign` | Sign a bundle with a private key. Returns `.signatures.json` content. |
340
+
341
+ ### Category D: OPA server management
342
+
343
+ Talk to a running OPA server over its REST API. Require `OPA_URL` to
344
+ point at a reachable server.
345
+
346
+ | Tool | What it does |
347
+ | -------------------- | ------------------------------------------------------ |
348
+ | `opa_list_policies` | List policies registered on the server. |
349
+ | `opa_get_policy` | Get a single policy by ID. |
350
+ | `opa_put_policy` | Upload or replace a policy. |
351
+ | `opa_delete_policy` | Delete a policy by ID. |
352
+ | `opa_get_data` | Read a path from the data hierarchy. |
353
+ | `opa_put_data` | Write to a path in the data hierarchy. |
354
+ | `opa_patch_data` | Apply a JSON Patch to the data hierarchy. |
355
+ | `opa_query_decision` | POST to a `/v1/data/...` decision endpoint with input. |
356
+ | `opa_compile_query` | Partially evaluate a query against the running server. |
357
+ | `opa_health` | Liveness / readiness check. |
358
+ | `opa_status` | Bundle / decision-log status. |
359
+ | `opa_config` | Server configuration (without secrets). |
360
+
361
+ ### Category E: Higher-level helpers
362
+
363
+ The differentiation surface. These compose lower-level primitives into
364
+ the tasks agents are actually asked to do.
365
+
366
+ | Tool | What it does |
367
+ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
368
+ | `rego_explain_decision` | Walk through every rule that fired (and didn't) for a given query. Wraps `rego_eval_with_explain` and produces a step-by-step natural-language trace. |
369
+ | `rego_generate_test_skeleton` | Given a policy, generate a `_test.rego` skeleton covering each rule. |
370
+ | `rego_describe_policy` | Summarize what a policy does, its inputs, decisions, and assumptions. |
371
+ | `rego_suggest_fix` | For a failed `rego_check` or `rego_lint`, propose minimal patches. |
372
+
373
+ ## Prompts
374
+
375
+ Three [MCP prompts](https://modelcontextprotocol.io/specification/server/prompts)
376
+ ship with the server. Clients surface them as slash commands or workflow
377
+ templates.
378
+
379
+ | Prompt | Purpose |
380
+ | ----------------------------- | ---------------------------------------------------------------------------------------------------------------- |
381
+ | `policy_authoring_assistant` | Walks the agent through writing a new policy: ask about the decision surface, draft, review, format, lint, test. |
382
+ | `policy_review_checklist` | Review checklist for an existing policy: completeness, edge cases, performance, security pitfalls. |
383
+ | `decision_debugging_workflow` | Diagnostic flow when a decision is unexpected: gather input, run with explain, isolate the rule, propose a fix. |
384
+
385
+ ## Resources
386
+
387
+ Three [MCP resources](https://modelcontextprotocol.io/specification/server/resources)
388
+ expose curated reference data the agent can read at any time.
389
+
390
+ | Resource URI | What's there |
391
+ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
392
+ | `opa://builtins` | Categorized OPA built-in function reference, derived at read time from `opa capabilities --current`. Security-sensitive functions (`http.send`, `crypto.x509.*`, `opa.runtime`) are flagged. |
393
+ | `opa://style-guide` | Condensed Rego style guide, formatted for LLM consumption. |
394
+ | `opa://patterns` | Curated common-pattern library: RBAC, ABAC, Kubernetes admission, IaC gates, API authz, rate limiting. Each pattern includes when-to-use, full Rego, a test, and common pitfalls. |
395
+
396
+ ## Cookbook
397
+
398
+ A few session shapes that the tool set was designed for.
399
+
400
+ ### "Help me write a policy"
401
+
402
+ ```
403
+ You: I need an authz policy: editors can read/write, viewers can only read,
404
+ admins can do anything.
405
+
406
+ Agent: I'll draft it. (calls rego_format on a draft, then rego_check, then
407
+ rego_lint)
408
+
409
+ Agent: Here's the policy. I've also generated a test file with cases for
410
+ each role. (calls rego_generate_test_skeleton, then rego_test)
411
+
412
+ Agent: All 9 tests pass. Want me to save it to <path>?
413
+ ```
414
+
415
+ ### "Why was this denied?"
416
+
417
+ ```
418
+ You: This API call is being denied and I don't know why.
419
+ [pastes input.json]
420
+
421
+ Agent: (calls rego_explain_decision against your local policy with that input)
422
+
423
+ Agent: The deny comes from rule `forbid_anonymous_writes` at line 17.
424
+ Specifically, `input.user` is null and the request method is "POST".
425
+ The rule fires, which causes the default deny. To allow this, you'd
426
+ need either an authenticated user or a policy exception for this
427
+ endpoint.
428
+ ```
429
+
430
+ ### "Push this policy to staging OPA"
431
+
432
+ ```
433
+ You: Push policies/rbac.rego to the staging OPA server, but first lint and
434
+ test it.
435
+
436
+ Agent: (rego_lint → 2 style warnings, no errors)
437
+ (rego_test on policies/ → all pass)
438
+ (opa_put_policy with id="rbac" against $OPA_URL)
439
+ (opa_get_policy to verify)
440
+
441
+ Agent: Done. Policy `rbac` is live on staging at $OPA_URL.
442
+ ```
443
+
444
+ ## Architecture
445
+
446
+ ```
447
+ ┌──────────────────────────────────── @orygn/opa-mcp ───────────────────────────────────┐
448
+ │ │
449
+ │ src/server.ts ──── McpServer (stdio) ─── tool / prompt / resource registries │
450
+ │ │ │
451
+ │ ├── tools/authoring/ ─┐ │
452
+ │ ├── tools/evaluation/ ─┤ │
453
+ │ ├── tools/bundles/ ─┼─── lib/opa-cli.ts ──┐ │
454
+ │ ├── tools/server-management/ ─┤ │ │
455
+ │ ├── tools/helpers/ ─┘ │ │
456
+ │ │ ▼ │
457
+ │ │ lib/subprocess.ts ──┴── opa │
458
+ │ │ lib/regal-cli.ts ───── regal│
459
+ │ │ lib/opa-client.ts ───── HTTP │
460
+ │ │ │
461
+ │ └── lib/output.ts (envelope + truncation) │
462
+ │ lib/security.ts (path allow-list) │
463
+ │ lib/errors.ts (structured failures) │
464
+ │ lib/logger.ts (file-only, never stdout) │
465
+ └───────────────────────────────────────────────────────────────────────────────────────┘
466
+ ```
467
+
468
+ Three things worth knowing if you're going to operate this:
469
+
470
+ 1. **stdout is the protocol channel.** The server logs to a file via
471
+ `lib/logger.ts` and never writes to stdout. If you see stray stdout
472
+ bytes, the client disconnects; the MCP transport layer is strict.
473
+ 2. **No tool throws.** Every tool catches its own exceptions and returns
474
+ a structured `{ ok: false, error: ... }` envelope. The agent sees a
475
+ stable error vocabulary, not a stack trace.
476
+ 3. **Subprocesses are tightly bounded.** `lib/subprocess.ts` runs `opa`
477
+ and `regal` with `shell: false`, a hard timeout, and `SIGTERM`-then-
478
+ `SIGKILL` escalation. There is no path through the server where an
479
+ agent can construct a shell command.
480
+
481
+ ## Security
482
+
483
+ This server is designed to run **locally**, started by an MCP client on
484
+ the user's own machine, communicating over stdio. It is not designed to
485
+ be exposed on the network.
486
+
487
+ - File-based tools refuse to read anything outside `OPA_MCP_ALLOWED_PATHS`.
488
+ When that variable is unset, file tools return `PATH_NOT_ALLOWED`.
489
+ - Subprocesses run with `shell: false` and a hard timeout.
490
+ - `OPA_TOKEN` is never echoed in tool responses or log entries.
491
+ - Releases are published with
492
+ [npm provenance](https://docs.npmjs.com/generating-provenance-statements);
493
+ the Docker image is built reproducibly from the committed `Dockerfile`.
494
+
495
+ To report a vulnerability, follow [SECURITY.md](./SECURITY.md). **Please
496
+ do not open a public issue for security problems.**
497
+
498
+ ## Troubleshooting
499
+
500
+ Common issues, fast fixes.
501
+
502
+ **`OPA_BINARY_NOT_FOUND` even though `opa` is installed.** _(most common
503
+ first-day issue, read this first)_
504
+
505
+ MCP clients (notably **Claude Desktop on Windows and macOS**) launch the
506
+ server with a deliberately reduced `PATH` that omits user-local bin
507
+ directories, even ones that work fine in your interactive shell. The
508
+ binary is on your machine; the spawned MCP server just can't see it.
509
+
510
+ Find the absolute path to `opa`:
511
+
512
+ ```bash
513
+ # macOS / Linux
514
+ which opa
515
+ # → /usr/local/bin/opa (or /opt/homebrew/bin/opa, or ~/.local/bin/opa)
516
+ ```
517
+
518
+ ```powershell
519
+ # Windows
520
+ Get-Command opa | Select-Object -ExpandProperty Source
521
+ # → C:\Users\you\bin\opa.exe (or wherever)
522
+ ```
523
+
524
+ Then set `OPA_BINARY` to that absolute path in your client's MCP `env`
525
+ block. Same for `REGAL_BINARY` if you use the `rego_lint` tool. The
526
+ [`examples/`](./examples) configs already include both env vars; just
527
+ edit the placeholder paths.
528
+
529
+ This issue does **not** affect the Docker or MCPB install paths. Those
530
+ bundle `opa` and `regal` and bypass `PATH` entirely.
531
+
532
+ **The server starts, then the client says "disconnected."**
533
+
534
+ The most likely cause is something in the process writing to stdout
535
+ besides MCP frames. If you've added a custom tool, check that no library
536
+ it calls prints to stdout. The fixed-position safety net is
537
+ `lib/logger.ts`. Use it, not `console.log`.
538
+
539
+ **`PATH_NOT_ALLOWED` on a file under my project.**
540
+
541
+ `OPA_MCP_ALLOWED_PATHS` is empty by default. Set it to the absolute
542
+ path(s) you want the server to read from, comma-separated.
543
+
544
+ **`OPA_UNREACHABLE` when calling `opa_*` tools.**
545
+
546
+ `OPA_URL` (default `http://localhost:8181`) must point at a running OPA
547
+ server (`opa run --server ...`). Check with `curl $OPA_URL/health`.
548
+
549
+ **Regal "version too old."**
550
+
551
+ We track the current Regal release. If `REGAL_VERSION_TOO_OLD` fires,
552
+ upgrade Regal: `brew upgrade regal` or download from the
553
+ [Regal releases](https://github.com/StyraInc/regal/releases) page.
554
+
555
+ **`directory-package-mismatch` violation when linting inline source.**
556
+
557
+ When you pass `source` rather than `paths` to `rego_lint`, Regal sees a
558
+ randomized temp-file path that can't possibly match your declared package
559
+ path. The diagnostic is an artifact of inline linting, not a real issue.
560
+ Disable the rule for inline workflows or lint via `paths` against the
561
+ real on-disk file when you want canonical signal.
562
+
563
+ **Where are the logs?**
564
+
565
+ Default location is `<OS-tmpdir>/orygn-opa-mcp.log`. That's typically
566
+ `/tmp/orygn-opa-mcp.log` on Linux/macOS or `%TEMP%\orygn-opa-mcp.log`
567
+ on Windows. Set `OPA_MCP_LOG_FILE` to override, and
568
+ `OPA_MCP_LOG_LEVEL=debug` to widen the firehose.
569
+
570
+ ## Development
571
+
572
+ ```bash
573
+ git clone https://github.com/OrygnsCode/opa-mcp-server.git
574
+ cd opa-mcp-server
575
+ npm install
576
+ npm run dev
577
+ ```
578
+
579
+ Common commands:
580
+
581
+ ```bash
582
+ npm run lint # ESLint
583
+ npm run typecheck # tsc --noEmit
584
+ npm test # unit tests (Vitest)
585
+ npm run test:coverage # unit + coverage report
586
+ npm run test:integration # against real opa + regal binaries
587
+ npm run build # compile to dist/
588
+ ```
589
+
590
+ CI runs lint, typecheck, build, and unit tests on every push and PR
591
+ across Ubuntu, macOS, and Windows on Node 20 and 22. Integration tests
592
+ run on Linux against pinned `opa` and `regal` releases.
593
+
594
+ For the full contributor workflow (adding tools, naming conventions,
595
+ logging discipline, release process), see [CONTRIBUTING.md](./CONTRIBUTING.md).
596
+
597
+ ## Versioning & support
598
+
599
+ This project follows [Semantic Versioning](https://semver.org/). The public
600
+ surface for SemVer purposes is the set of registered tools, prompts, and
601
+ resources, their input/output schemas, the recognized environment
602
+ variables, and the CLI entry point.
603
+
604
+ Breaking changes will be:
605
+
606
+ - announced in [CHANGELOG.md](./CHANGELOG.md) under a new major version,
607
+ - preceded by at least one minor release with a deprecation warning,
608
+ - accompanied by a migration note in the release announcement.
609
+
610
+ Pinned versions of the upstream toolchain (`opa` and `regal`) are treated
611
+ as part of the build, not as a dependency the operator manages. The
612
+ Dockerfile, MCPB bundle, and CI all use the same pin; bumps go through
613
+ Dependabot or a manual PR.
614
+
615
+ ## License
616
+
617
+ [MIT](./LICENSE) © Orygn LLC
618
+
619
+ `@orygn/opa-mcp` is an independent project. It is not affiliated with,
620
+ endorsed by, or sponsored by the Open Policy Agent project, the Cloud
621
+ Native Computing Foundation, Styra, or Anthropic. "Open Policy Agent"
622
+ and "Rego" are trademarks of their respective owners. "Model Context
623
+ Protocol" is a trademark of Anthropic, PBC.
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod';
2
+ declare const ConfigSchema: z.ZodObject<{
3
+ /** Base URL of a running OPA server (used by `opa_*` runtime tools). */
4
+ opaUrl: z.ZodDefault<z.ZodString>;
5
+ /** Optional bearer token for OPA running with `--authentication=token`. */
6
+ opaToken: z.ZodOptional<z.ZodString>;
7
+ /** Path to the `opa` binary. Defaults to `opa` on PATH. */
8
+ opaBinary: z.ZodDefault<z.ZodString>;
9
+ /** Path to the `regal` binary. Defaults to `regal` on PATH. */
10
+ regalBinary: z.ZodDefault<z.ZodString>;
11
+ /** Hard timeout in ms for any spawned subprocess (opa, regal). */
12
+ subprocessTimeoutMs: z.ZodDefault<z.ZodNumber>;
13
+ /** HTTP request timeout for OPA REST API calls. */
14
+ httpTimeoutMs: z.ZodDefault<z.ZodNumber>;
15
+ /**
16
+ * Allow-listed root directories for file path inputs. Tools that accept
17
+ * filesystem paths reject anything outside these roots. Empty by
18
+ * default — file-based tools refuse to read from disk until the
19
+ * operator explicitly opts in via `OPA_MCP_ALLOWED_PATHS`.
20
+ */
21
+ allowedPaths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
22
+ /** Path to the log file. Defaults to OS tmpdir + orygn-opa-mcp.log. */
23
+ logFile: z.ZodDefault<z.ZodString>;
24
+ /** Log level for the file logger. */
25
+ logLevel: z.ZodDefault<z.ZodEnum<["debug", "info", "warn", "error"]>>;
26
+ /**
27
+ * Maximum size in bytes for tool response payloads before truncation.
28
+ * Larger payloads are truncated with `truncated: true` and a hint to
29
+ * write to a file path the agent specifies.
30
+ */
31
+ maxResponseBytes: z.ZodDefault<z.ZodNumber>;
32
+ }, "strip", z.ZodTypeAny, {
33
+ opaUrl: string;
34
+ opaBinary: string;
35
+ regalBinary: string;
36
+ subprocessTimeoutMs: number;
37
+ httpTimeoutMs: number;
38
+ allowedPaths: string[];
39
+ logFile: string;
40
+ logLevel: "debug" | "info" | "warn" | "error";
41
+ maxResponseBytes: number;
42
+ opaToken?: string | undefined;
43
+ }, {
44
+ opaUrl?: string | undefined;
45
+ opaToken?: string | undefined;
46
+ opaBinary?: string | undefined;
47
+ regalBinary?: string | undefined;
48
+ subprocessTimeoutMs?: number | undefined;
49
+ httpTimeoutMs?: number | undefined;
50
+ allowedPaths?: string[] | undefined;
51
+ logFile?: string | undefined;
52
+ logLevel?: "debug" | "info" | "warn" | "error" | undefined;
53
+ maxResponseBytes?: number | undefined;
54
+ }>;
55
+ export type Config = z.infer<typeof ConfigSchema>;
56
+ export declare function loadConfig(): Config;
57
+ export {};
58
+ //# sourceMappingURL=config.d.ts.map