cedar-mcp-server 1.0.0
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/.editorconfig +12 -0
- package/.github/workflows/ci.yml +31 -0
- package/.github/workflows/release.yml +42 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +241 -0
- package/CONTRIBUTING.md +83 -0
- package/LICENSE +182 -0
- package/README.md +1635 -0
- package/SECURITY.md +37 -0
- package/dist/http-server.d.ts +61 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +194 -0
- package/dist/http-server.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +270 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/policy-ast.d.ts +49 -0
- package/dist/parser/policy-ast.d.ts.map +1 -0
- package/dist/parser/policy-ast.js +311 -0
- package/dist/parser/policy-ast.js.map +1 -0
- package/dist/prompts/index.d.ts +38 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +172 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/resources/ref-resolver.d.ts +23 -0
- package/dist/resources/ref-resolver.d.ts.map +1 -0
- package/dist/resources/ref-resolver.js +128 -0
- package/dist/resources/ref-resolver.js.map +1 -0
- package/dist/resources/store-manager.d.ts +64 -0
- package/dist/resources/store-manager.d.ts.map +1 -0
- package/dist/resources/store-manager.js +221 -0
- package/dist/resources/store-manager.js.map +1 -0
- package/dist/server.d.ts +18 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +539 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/advise/avp-rules.d.ts +49 -0
- package/dist/tools/advise/avp-rules.d.ts.map +1 -0
- package/dist/tools/advise/avp-rules.js +59 -0
- package/dist/tools/advise/avp-rules.js.map +1 -0
- package/dist/tools/advise/cedar-patterns.d.ts +24 -0
- package/dist/tools/advise/cedar-patterns.d.ts.map +1 -0
- package/dist/tools/advise/cedar-patterns.js +57 -0
- package/dist/tools/advise/cedar-patterns.js.map +1 -0
- package/dist/tools/advise/context-builder.d.ts +28 -0
- package/dist/tools/advise/context-builder.d.ts.map +1 -0
- package/dist/tools/advise/context-builder.js +89 -0
- package/dist/tools/advise/context-builder.js.map +1 -0
- package/dist/tools/advise/gotchas.d.ts +15 -0
- package/dist/tools/advise/gotchas.d.ts.map +1 -0
- package/dist/tools/advise/gotchas.js +83 -0
- package/dist/tools/advise/gotchas.js.map +1 -0
- package/dist/tools/advise.d.ts +96 -0
- package/dist/tools/advise.d.ts.map +1 -0
- package/dist/tools/advise.js +258 -0
- package/dist/tools/advise.js.map +1 -0
- package/dist/tools/authorize-batch.d.ts +35 -0
- package/dist/tools/authorize-batch.d.ts.map +1 -0
- package/dist/tools/authorize-batch.js +262 -0
- package/dist/tools/authorize-batch.js.map +1 -0
- package/dist/tools/authorize.d.ts +115 -0
- package/dist/tools/authorize.d.ts.map +1 -0
- package/dist/tools/authorize.js +373 -0
- package/dist/tools/authorize.js.map +1 -0
- package/dist/tools/check-change.d.ts +19 -0
- package/dist/tools/check-change.d.ts.map +1 -0
- package/dist/tools/check-change.js +91 -0
- package/dist/tools/check-change.js.map +1 -0
- package/dist/tools/diff-schema.d.ts +103 -0
- package/dist/tools/diff-schema.d.ts.map +1 -0
- package/dist/tools/diff-schema.js +379 -0
- package/dist/tools/diff-schema.js.map +1 -0
- package/dist/tools/diff-stores.d.ts +45 -0
- package/dist/tools/diff-stores.d.ts.map +1 -0
- package/dist/tools/diff-stores.js +222 -0
- package/dist/tools/diff-stores.js.map +1 -0
- package/dist/tools/explain.d.ts +80 -0
- package/dist/tools/explain.d.ts.map +1 -0
- package/dist/tools/explain.js +187 -0
- package/dist/tools/explain.js.map +1 -0
- package/dist/tools/format.d.ts +11 -0
- package/dist/tools/format.d.ts.map +1 -0
- package/dist/tools/format.js +20 -0
- package/dist/tools/format.js.map +1 -0
- package/dist/tools/generate-sample.d.ts +28 -0
- package/dist/tools/generate-sample.d.ts.map +1 -0
- package/dist/tools/generate-sample.js +568 -0
- package/dist/tools/generate-sample.js.map +1 -0
- package/dist/tools/link-template.d.ts +17 -0
- package/dist/tools/link-template.d.ts.map +1 -0
- package/dist/tools/link-template.js +78 -0
- package/dist/tools/link-template.js.map +1 -0
- package/dist/tools/list-template-links.d.ts +16 -0
- package/dist/tools/list-template-links.d.ts.map +1 -0
- package/dist/tools/list-template-links.js +22 -0
- package/dist/tools/list-template-links.js.map +1 -0
- package/dist/tools/list-templates.d.ts +16 -0
- package/dist/tools/list-templates.d.ts.map +1 -0
- package/dist/tools/list-templates.js +36 -0
- package/dist/tools/list-templates.js.map +1 -0
- package/dist/tools/translate.d.ts +11 -0
- package/dist/tools/translate.d.ts.map +1 -0
- package/dist/tools/translate.js +53 -0
- package/dist/tools/translate.js.map +1 -0
- package/dist/tools/validate-entities.d.ts +19 -0
- package/dist/tools/validate-entities.d.ts.map +1 -0
- package/dist/tools/validate-entities.js +88 -0
- package/dist/tools/validate-entities.js.map +1 -0
- package/dist/tools/validate-schema.d.ts +22 -0
- package/dist/tools/validate-schema.d.ts.map +1 -0
- package/dist/tools/validate-schema.js +89 -0
- package/dist/tools/validate-schema.js.map +1 -0
- package/dist/tools/validate-template.d.ts +18 -0
- package/dist/tools/validate-template.d.ts.map +1 -0
- package/dist/tools/validate-template.js +59 -0
- package/dist/tools/validate-template.js.map +1 -0
- package/dist/tools/validate.d.ts +90 -0
- package/dist/tools/validate.d.ts.map +1 -0
- package/dist/tools/validate.js +351 -0
- package/dist/tools/validate.js.map +1 -0
- package/dist/utils/format-detector.d.ts +49 -0
- package/dist/utils/format-detector.d.ts.map +1 -0
- package/dist/utils/format-detector.js +298 -0
- package/dist/utils/format-detector.js.map +1 -0
- package/examples/README.md +36 -0
- package/examples/abac-multi-tenant/README.md +150 -0
- package/examples/abac-multi-tenant/entities/users-and-docs.json +33 -0
- package/examples/abac-multi-tenant/policies/member-read-internal.cedar +9 -0
- package/examples/abac-multi-tenant/policies/owner-full-access.cedar +9 -0
- package/examples/abac-multi-tenant/policies/premium-share-guard.cedar +9 -0
- package/examples/abac-multi-tenant/policies/private-doc-guard.cedar +13 -0
- package/examples/abac-multi-tenant/run.ts +92 -0
- package/examples/abac-multi-tenant/schema.json +60 -0
- package/examples/api-gateway-path-routing/README.md +154 -0
- package/examples/api-gateway-path-routing/entities/users-and-roles.json +20 -0
- package/examples/api-gateway-path-routing/policies/admin-full-access.cedar +6 -0
- package/examples/api-gateway-path-routing/policies/developer-projects.cedar +14 -0
- package/examples/api-gateway-path-routing/policies/viewer-readonly.cedar +10 -0
- package/examples/api-gateway-path-routing/run.ts +108 -0
- package/examples/api-gateway-path-routing/schema.json +54 -0
- package/examples/rbac-document-management/README.md +167 -0
- package/examples/rbac-document-management/entities/users-and-docs.json +43 -0
- package/examples/rbac-document-management/policies/admin.cedar +6 -0
- package/examples/rbac-document-management/policies/editor.cedar +6 -0
- package/examples/rbac-document-management/policies/top-secret-forbid.cedar +13 -0
- package/examples/rbac-document-management/policies/viewer.cedar +6 -0
- package/examples/rbac-document-management/run.ts +87 -0
- package/examples/rbac-document-management/schema.json +57 -0
- package/package.json +50 -0
- package/src/http-server.ts +239 -0
- package/src/index.ts +294 -0
- package/src/parser/policy-ast.ts +345 -0
- package/src/prompts/README.md +3 -0
- package/src/prompts/index.ts +217 -0
- package/src/resources/ref-resolver.ts +134 -0
- package/src/resources/store-manager.ts +248 -0
- package/src/server.ts +711 -0
- package/src/tools/advise/avp-rules.ts +70 -0
- package/src/tools/advise/cedar-patterns.ts +73 -0
- package/src/tools/advise/context-builder.ts +109 -0
- package/src/tools/advise/gotchas.ts +92 -0
- package/src/tools/advise.ts +366 -0
- package/src/tools/authorize-batch.ts +345 -0
- package/src/tools/authorize.ts +464 -0
- package/src/tools/check-change.ts +119 -0
- package/src/tools/diff-schema.ts +510 -0
- package/src/tools/diff-stores.ts +298 -0
- package/src/tools/explain.ts +278 -0
- package/src/tools/format.ts +33 -0
- package/src/tools/generate-sample.ts +665 -0
- package/src/tools/link-template.ts +109 -0
- package/src/tools/list-template-links.ts +41 -0
- package/src/tools/list-templates.ts +55 -0
- package/src/tools/translate.ts +66 -0
- package/src/tools/validate-entities.ts +125 -0
- package/src/tools/validate-schema.ts +128 -0
- package/src/tools/validate-template.ts +72 -0
- package/src/tools/validate.ts +459 -0
- package/src/utils/format-detector.ts +356 -0
- package/test/fixtures/docmgmt.ts +121 -0
- package/test/fixtures/multitenant.ts +163 -0
- package/test/index.test.ts +96 -0
- package/test/integration/e2e/behavior.test.ts +359 -0
- package/test/integration/e2e/edge-cases.test.ts +365 -0
- package/test/integration/e2e/failure-modes.test.ts +266 -0
- package/test/integration/e2e/protocol.test.ts +252 -0
- package/test/integration/http-smoke.test.ts +588 -0
- package/test/integration/smoke.test.ts +475 -0
- package/test/prompts/prompts.test.ts +173 -0
- package/test/property/properties.test.ts +234 -0
- package/test/resources/ref-resolver.test.ts +186 -0
- package/test/resources/store-manager.test.ts +344 -0
- package/test/setup.test.ts +7 -0
- package/test/tools/advise/avp-rules.test.ts +76 -0
- package/test/tools/advise.test.ts +339 -0
- package/test/tools/authorize-batch.test.ts +459 -0
- package/test/tools/authorize.test.ts +682 -0
- package/test/tools/check-change.test.ts +104 -0
- package/test/tools/cross-fixture.test.ts +170 -0
- package/test/tools/diff-schema.test.ts +355 -0
- package/test/tools/diff-stores.test.ts +291 -0
- package/test/tools/explain.test.ts +221 -0
- package/test/tools/format.test.ts +33 -0
- package/test/tools/generate-sample.test.ts +480 -0
- package/test/tools/link-template.test.ts +90 -0
- package/test/tools/list-templates.test.ts +151 -0
- package/test/tools/translate.test.ts +89 -0
- package/test/tools/validate-entities.test.ts +178 -0
- package/test/tools/validate-schema.test.ts +86 -0
- package/test/tools/validate-template.test.ts +89 -0
- package/test/tools/validate.test.ts +331 -0
- package/test/utils/format-detector.test.ts +518 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,1635 @@
|
|
|
1
|
+
# cedar-mcp-server
|
|
2
|
+
|
|
3
|
+
`cedar-mcp-server` is an MCP server that puts Cedar policy tooling directly inside your AI assistant conversation. It covers the full Cedar policy lifecycle: validate policies, simulate authorization decisions, plan changes against AVP constraints, and diff two policy stores for blue/green deployment. Cedar 4.11.0 runs in-process via WASM, so there's nothing to install beyond `npx`.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/Pigius/cedar-mcp-server/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/cedar-mcp-server)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](https://nodejs.org/)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## What it does
|
|
14
|
+
|
|
15
|
+
Seventeen tools across six categories, plus three MCP prompts.
|
|
16
|
+
|
|
17
|
+
#### Authorization
|
|
18
|
+
|
|
19
|
+
| Tool | What it does |
|
|
20
|
+
|------|-------------|
|
|
21
|
+
| [`cedar_authorize`](#cedar_authorize) | Evaluates one authorization request locally; returns the decision and which policies fired |
|
|
22
|
+
| [`cedar_authorize_batch`](#cedar_authorize_batch) | Runs N authorization requests through one policy set and returns the decision matrix; for regression testing after a policy edit |
|
|
23
|
+
|
|
24
|
+
#### Validation
|
|
25
|
+
|
|
26
|
+
| Tool | What it does |
|
|
27
|
+
|------|-------------|
|
|
28
|
+
| [`cedar_validate`](#cedar_validate) | Validates Cedar policies against a schema; returns errors with hints and source locations |
|
|
29
|
+
| [`cedar_validate_schema`](#cedar_validate_schema) | Validates a Cedar schema in isolation (no policies required); returns parse errors and namespace/type counts |
|
|
30
|
+
| [`cedar_validate_template`](#cedar_validate_template) | Validates a Cedar template against a schema; detects slot placeholders |
|
|
31
|
+
| [`cedar_validate_entities`](#cedar_validate_entities) | Validates a Cedar entities JSON array against a schema; classifies errors by kind (unknown_type, missing_required_attribute, type_mismatch, unknown_attribute, disallowed_parent_type) |
|
|
32
|
+
|
|
33
|
+
#### Formatting and translation
|
|
34
|
+
|
|
35
|
+
| Tool | What it does |
|
|
36
|
+
|------|-------------|
|
|
37
|
+
| [`cedar_format`](#cedar_format) | Formats Cedar policy text to canonical style |
|
|
38
|
+
| [`cedar_translate`](#cedar_translate) | Translates between Cedar text and Cedar JSON formats for policies and schemas |
|
|
39
|
+
|
|
40
|
+
#### Planning and analysis
|
|
41
|
+
|
|
42
|
+
| Tool | What it does |
|
|
43
|
+
|------|-------------|
|
|
44
|
+
| [`cedar_explain`](#cedar_explain) | Explains a Cedar policy in plain English with pattern detection |
|
|
45
|
+
| [`cedar_check_policy_change`](#cedar_check_policy_change) | Determines whether a policy modification can be applied in-place in AVP or requires delete-and-recreate |
|
|
46
|
+
| [`cedar_generate_sample_request`](#cedar_generate_sample_request) | Generates a complete authorization request payload that produces a target decision |
|
|
47
|
+
| [`cedar_advise`](#cedar_advise) | Returns a structured context bundle (schema summary, policy inventory with pattern classification, gotchas, AVP rules, sequencing guidance) for any policy-change intent so the calling assistant can plan correctly |
|
|
48
|
+
|
|
49
|
+
#### Templates
|
|
50
|
+
|
|
51
|
+
Instantiate and inspect template-linked policies. Template validation lives in the Validation category above.
|
|
52
|
+
|
|
53
|
+
| Tool | What it does |
|
|
54
|
+
|------|-------------|
|
|
55
|
+
| [`cedar_link_template`](#cedar_link_template) | Instantiates a template by binding `?principal` and `?resource` slots to specific entity references |
|
|
56
|
+
| [`cedar_list_templates`](#cedar_list_templates) | Lists all templates in a policy store (reads from `templates/` subdirectory) |
|
|
57
|
+
| [`cedar_list_template_links`](#cedar_list_template_links) | Lists all template-linked policy instances in a store (reads from `template-links/` subdirectory) |
|
|
58
|
+
|
|
59
|
+
#### Diffing
|
|
60
|
+
|
|
61
|
+
| Tool | What it does |
|
|
62
|
+
|------|-------------|
|
|
63
|
+
| [`cedar_diff_schema`](#cedar_diff_schema) | Structural diff of two schemas with AVP-aware risk classification per change (safe/review/breaking) |
|
|
64
|
+
| [`cedar_diff_policy_stores`](#cedar_diff_policy_stores) | Structural and optional behavioral diff between two policy stores with AVP immutability classification (embeds structured `schema_diff` from `cedar_diff_schema`) |
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Why use `cedar-mcp-server` instead of reading my Cedar files directly?
|
|
69
|
+
|
|
70
|
+
A fair question, especially in an MCP client (Claude Code, Cursor) where the assistant can already Read the policy files in your workspace. If an LLM can read `policies/admin.cedar` and `schema.cedarschema`, why route through tool calls at all?
|
|
71
|
+
|
|
72
|
+
Because the tools encode things that do not live in the files.
|
|
73
|
+
|
|
74
|
+
#### Validation
|
|
75
|
+
|
|
76
|
+
`cedar_validate` and `cedar_validate_schema` run the official Cedar 4.11.0 parser. Reading a policy tells you what the author wrote, not whether the parser accepts it. Syntax errors, schema-type mismatches, missing-attribute references, optional-attribute access without a guard, and `appliesTo` violations all surface from the parser, not from text inspection. The parser is the only authority on validity.
|
|
77
|
+
|
|
78
|
+
#### Evaluation
|
|
79
|
+
|
|
80
|
+
`cedar_authorize` and `cedar_authorize_batch` run the Cedar engine. Reading a policy set tells you the rules; running them tells you the decisions. The default-deny behavior, forbid-overrides-permit precedence, optional-attribute silent-skip, action-group membership resolution, schema-validated entity typing, and condition short-circuiting are engine semantics. You cannot simulate the engine accurately by mental execution over the policy text, especially when the entity graph has parents or shared attributes.
|
|
81
|
+
|
|
82
|
+
#### Planning
|
|
83
|
+
|
|
84
|
+
`cedar_advise` returns a structured context bundle for any "I want to change my policies to do X" intent: schema summary, policy inventory with AST-classified Cedar pattern per file (Membership / Relationship / Discretionary / hybrid), intent-selected gotcha catalog (10 entries drawn from Cedar/AVP failure modes), AVP `UpdatePolicy` mutability rules, Cedar patterns reference, sequencing guidance, and explicit follow-up instructions. None of this lives in the policy files. AST-based pattern classification requires parsing each policy and walking the JSON; the AVP API contract requires knowing the `UpdatePolicy` spec; the gotcha catalog requires Cedar/AVP experience. The bundle is deterministic (no LLM round-trip on the server side); the calling assistant produces the actual plan from it and then verifies snippets via `cedar_validate` and `cedar_check_policy_change`.
|
|
85
|
+
|
|
86
|
+
#### Change safety
|
|
87
|
+
|
|
88
|
+
`cedar_check_policy_change`, `cedar_diff_schema`, and `cedar_diff_policy_stores` encode the AVP `UpdatePolicy` contract and the rules of which schema changes break which policies. Visually diffing two policies in a code review tells you what text changed; only these tools tell you whether AVP will accept the update in place, whether the change will silently drop existing policy matches, or whether decisions will flip for canonical requests. A text diff over schemas does not tell you which attribute removals break which policy reads.
|
|
89
|
+
|
|
90
|
+
#### Analysis
|
|
91
|
+
|
|
92
|
+
`cedar_explain` returns structure derived from the parsed AST: effect, scope breakdown, conditions, and detected patterns. Reading the policy gives the assistant a paraphrase; the AST gives ground truth (correct slot detection in templates, optional-attribute guard recognition, path-matching pattern detection, name-based identity recognition). For inherited policies that mix RBAC scope with ABAC conditions, the AST-derived breakdown is materially more reliable than text inspection.
|
|
93
|
+
|
|
94
|
+
#### Discoverability and sequencing
|
|
95
|
+
|
|
96
|
+
`cedar_list_templates`, `cedar_list_template_links`, the `cedar://policies/{store}` and `cedar://entities/{store}` resource URIs, and the StoreManager-backed `policy_ref` / `schema_ref` / `entities_ref` parameters give the assistant a stable, schema-aware view of the project. Doing this through `ls` and `cat` works for one-off inspection, but the tools encode the store layout convention (`policies/`, `entities/`, `templates/`, `template-links/`, `schema.cedarschema` or `schema.json`) and surface only what the other tools will actually resolve.
|
|
97
|
+
|
|
98
|
+
Rule of thumb for assistants: if a question is "what does this policy look like?", Read is fine. If it is "is this valid?", "would it allow X?", "can I update it in place?", "what changes if I deploy this?", "what's the safest way to add Y?", or "what patterns does this store use?", the right answer is a tool call. The tools are load-bearing for correctness, not a convenience layer.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Quick start
|
|
103
|
+
|
|
104
|
+
### How MCP stdio servers work
|
|
105
|
+
|
|
106
|
+
Your MCP client (Claude Code, Claude Desktop, Cursor) spawns `cedar-mcp-server` as a child process when it needs Cedar tooling. You do not run the server directly. You configure your client to point at it once, and the client manages the process lifecycle over stdio for each session. If you try `node dist/index.js` in a terminal it will appear to hang; that is the server waiting for JSON-RPC messages on stdin. Stop it with `Ctrl+C`.
|
|
107
|
+
|
|
108
|
+
### Run from source (current path)
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
git clone https://github.com/Pigius/cedar-mcp-server.git
|
|
112
|
+
cd cedar-mcp-server
|
|
113
|
+
npm install
|
|
114
|
+
npm run build # compiles TypeScript to dist/
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Then point your MCP client at the built entry. Replace `command: "npx"` and `args: ["-y", "cedar-mcp-server"]` in the configs below with:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{ "command": "node", "args": ["/absolute/path/to/cedar-mcp-server/dist/index.js"] }
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Or run directly via `tsx` without a build step:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{ "command": "npx", "args": ["tsx", "/absolute/path/to/cedar-mcp-server/src/index.ts"] }
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Claude Code (after publish)
|
|
130
|
+
|
|
131
|
+
Add to `.claude/settings.json` in your project, or to `~/.claude/settings.json` globally:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"mcpServers": {
|
|
136
|
+
"cedar": {
|
|
137
|
+
"command": "npx",
|
|
138
|
+
"args": ["-y", "cedar-mcp-server"]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
If you register via `claude mcp add` instead of editing settings.json by hand, run the command from the directory you will actually use the server in. Claude Code stores MCP configurations per-project by default, so a registration done from one project does not surface in another. For a single global registration that works across every project, add `--scope user`:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
claude mcp add --scope user cedar -- npx -y cedar-mcp-server
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Claude Desktop (after publish)
|
|
151
|
+
|
|
152
|
+
Add to `claude_desktop_config.json`:
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"mcpServers": {
|
|
157
|
+
"cedar": {
|
|
158
|
+
"command": "npx",
|
|
159
|
+
"args": ["-y", "cedar-mcp-server"]
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Cursor (after publish)
|
|
166
|
+
|
|
167
|
+
Add to `.cursor/mcp.json` in your project:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"mcpServers": {
|
|
172
|
+
"cedar": {
|
|
173
|
+
"command": "npx",
|
|
174
|
+
"args": ["-y", "cedar-mcp-server"]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Once published, first `npx` run pulls the package; subsequent runs use the npm cache.
|
|
181
|
+
|
|
182
|
+
Then in your client conversation:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
Validate this Cedar policy against this schema: [paste policy and schema]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## The workflow it enables
|
|
191
|
+
|
|
192
|
+
This server is designed around three steps in any Cedar policy lifecycle.
|
|
193
|
+
|
|
194
|
+
### 1. PLAN: from intent to a step-by-step Cedar policy change plan
|
|
195
|
+
|
|
196
|
+
Use `cedar_advise` to translate a natural-language description of what you want into a concrete, ordered list of Cedar and schema changes, with AVP deployment classification for each step.
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
You: I need to add a "contractor" role that can read documents
|
|
200
|
+
but only if the document is marked as "external_share".
|
|
201
|
+
Here's my current policy store: [attach via cedar:// ref or paste]
|
|
202
|
+
|
|
203
|
+
AI: [calls cedar_advise]
|
|
204
|
+
|
|
205
|
+
Step 1: Schema (no changes needed; "classification" attribute already exists).
|
|
206
|
+
Step 2: Policy (new_policy_via_create_policy):
|
|
207
|
+
permit (
|
|
208
|
+
principal in MyApp::Role::"contractor",
|
|
209
|
+
action == MyApp::Action::"read",
|
|
210
|
+
resource
|
|
211
|
+
)
|
|
212
|
+
when { resource.classification == "external_share" };
|
|
213
|
+
Gotcha (high): accessing resource.classification without a guard works here
|
|
214
|
+
because the attribute is required in the schema. If you make it optional
|
|
215
|
+
later, add `resource has classification &&` before the check.
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 2. DIFF: from "what changed" to "is it safe to deploy"
|
|
219
|
+
|
|
220
|
+
Configure two policy stores as MCP roots (production and staging), then use `cedar_diff_policy_stores` to get a structural diff plus AVP immutability classification for each change.
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
You: Compare staging and production policies before I promote.
|
|
224
|
+
|
|
225
|
+
AI: [calls cedar_diff_policy_stores with blue: "production", green: "staging"]
|
|
226
|
+
|
|
227
|
+
Added: contractor-read-external.cedar (new_policy_via_create_policy, safe to add)
|
|
228
|
+
Modified: editor-policy.cedar (principal clause changed, requires_delete_recreate)
|
|
229
|
+
Schema: unchanged
|
|
230
|
+
|
|
231
|
+
Behavioral diff (optional): pass a list of authorization requests to see
|
|
232
|
+
which decisions would change between the two stores.
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 3. APPLY: deploy with confidence
|
|
236
|
+
|
|
237
|
+
The server doesn't call AVP APIs directly. Apply your changes through your own deployment pipeline. The `avp_update_mode` classification from steps 1 and 2 tells you exactly which AVP operations each change requires:
|
|
238
|
+
|
|
239
|
+
- `new_policy_via_create_policy`: safe to add with `CreatePolicy`.
|
|
240
|
+
- `in_place_via_update_policy`: change action or conditions with `UpdatePolicy`.
|
|
241
|
+
- `requires_delete_recreate`: principal, resource, or effect changed; use `DeletePolicy` then `CreatePolicy`.
|
|
242
|
+
|
|
243
|
+
See [`avp-cli`](https://github.com/Pigius/avp-cli) for a companion CLI that handles the pull/push side.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Tool details
|
|
248
|
+
|
|
249
|
+
Every example below assumes a Cedar workspace store called `cedar-sandbox`: a directory with `schema.cedarschema` declaring a `MyApp` namespace (User, Role, Document, Folder entities; read / write / delete actions), per-file policies in `policies/` (`admin.cedar`, `editor.cedar`, `viewer.cedar`), and an `entities/sample.json` containing alice (admin), bob (editor), charlie (viewer) plus a `doc-public` document. Substitute your own store name in any example prompt that mentions `cedar-sandbox`. For complete worked fixtures, see [`examples/`](./examples/).
|
|
250
|
+
|
|
251
|
+
### `cedar_validate`
|
|
252
|
+
|
|
253
|
+
Validates Cedar policies with or without a schema. Two modes:
|
|
254
|
+
|
|
255
|
+
- **Syntax-only** (no schema): runs the parser alone. Catches typos, malformed scopes, bad operators. Cheapest sanity check, useful when you have a 5-line snippet and no schema yet.
|
|
256
|
+
- **Syntax-and-schema** (schema provided): parses then type-checks against the schema. Catches attribute typos, entity type mismatches, action applicability errors, and `UnsafeOptionalAttributeAccess` warnings.
|
|
257
|
+
|
|
258
|
+
The response's `validation_mode` field tells you which mode ran.
|
|
259
|
+
|
|
260
|
+
**Inputs:**
|
|
261
|
+
|
|
262
|
+
| Parameter | Required | Description |
|
|
263
|
+
|-----------|----------|-------------|
|
|
264
|
+
| `policies` | yes (or `policy_ref`) | Cedar policy text (one or more policies) |
|
|
265
|
+
| `schema` | no | Cedar schema (JSON object or `.cedarschema` text). Omit for syntax-only mode or to auto-discover from the workspace store. |
|
|
266
|
+
| `policy_ref` | no | `cedar://` URI pointing to a policy in a configured store |
|
|
267
|
+
| `schema_ref` | no | `cedar://` URI pointing to a schema in a configured store |
|
|
268
|
+
| `store` | no | Store name (a configured MCP root). Use to disambiguate auto-discovery when multiple stores are loaded. |
|
|
269
|
+
| `validation_mode` | no | One of `"auto"` (default), `"syntax_only"`, or `"syntax_and_schema"`. See "Forcing a mode" below. |
|
|
270
|
+
|
|
271
|
+
**Workspace auto-discovery.** When `schema` and `schema_ref` are both omitted and exactly one MCP root is loaded, the tool reads the schema from that store and upgrades the run to `syntax_and_schema` mode. The response's `auto_discovered.schema_from` field names the source store. With multiple stores loaded, the response is an actionable error listing the candidate names. Pass `store: "<name>"` to choose one.
|
|
272
|
+
|
|
273
|
+
**Forcing a mode.** Set `validation_mode` when the default schema-presence heuristic isn't what you want.
|
|
274
|
+
|
|
275
|
+
- `"auto"` (default): schema presence picks the mode, as described above.
|
|
276
|
+
- `"syntax_only"`: parser-only. Skips workspace auto-discovery entirely and ignores any inline schema, `schema_ref`, or `store` you pass alongside it (the user said parser-only; the tool honors that literally). Use when the user explicitly says they have no schema, or for a fast parse-only sanity check inside a Cedar-workspace cwd.
|
|
277
|
+
- `"syntax_and_schema"`: require a schema. If neither an inline schema nor a workspace schema is resolvable, the response is an error rather than a silent drop to syntax-only. Use when you want to be sure the type-check ran.
|
|
278
|
+
|
|
279
|
+
**Example prompt** (paste into Claude Code from a Cedar workspace):
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
Use cedar_validate on: permit (prinicpal in MyApp::Role::"admin", action, resource);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Response:**
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{
|
|
289
|
+
"valid": false,
|
|
290
|
+
"errors": [
|
|
291
|
+
{
|
|
292
|
+
"policy_id": "",
|
|
293
|
+
"message": "failed to parse policies from string: found an invalid variable in the policy scope: prinicpal",
|
|
294
|
+
"hint": "Did you mean 'principal'?",
|
|
295
|
+
"line": 1,
|
|
296
|
+
"column": 9
|
|
297
|
+
}
|
|
298
|
+
],
|
|
299
|
+
"warnings": [],
|
|
300
|
+
"policy_count": 0,
|
|
301
|
+
"validation_mode": "syntax_and_schema",
|
|
302
|
+
"auto_discovered": { "schema_from": "cedar-sandbox" }
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The typo `prinicpal` lands on the schema-aware path because the cwd-fallback auto-discovered the sandbox schema, so `validation_mode` is `syntax_and_schema`. With no workspace store loaded (or `validation_mode: "syntax_only"` set explicitly), the same prompt produces the same parse error but `validation_mode: "syntax_only"` and no `auto_discovered` field.
|
|
307
|
+
|
|
308
|
+
Additional shapes you'll see:
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
// Schema-validation error: attribute not declared on the entity type
|
|
312
|
+
{
|
|
313
|
+
"valid": false,
|
|
314
|
+
"errors": [
|
|
315
|
+
{
|
|
316
|
+
"policy_id": "policy0",
|
|
317
|
+
"message": "attribute `nonexistent` on entity type `MyApp::Document` not found",
|
|
318
|
+
"hint": "did you mean `classification`?",
|
|
319
|
+
"line": 1,
|
|
320
|
+
"column": 47
|
|
321
|
+
}
|
|
322
|
+
],
|
|
323
|
+
"policy_count": 1,
|
|
324
|
+
"validation_mode": "syntax_and_schema"
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
```json
|
|
329
|
+
// Parse error on a multi-line policy with `int` instead of `in` on line 3
|
|
330
|
+
{
|
|
331
|
+
"valid": false,
|
|
332
|
+
"errors": [
|
|
333
|
+
{
|
|
334
|
+
"policy_id": "",
|
|
335
|
+
"message": "failed to parse policies from string: unexpected token `int`",
|
|
336
|
+
"hint": "Did you mean 'in'?",
|
|
337
|
+
"line": 3,
|
|
338
|
+
"column": 10
|
|
339
|
+
}
|
|
340
|
+
],
|
|
341
|
+
"policy_count": 0,
|
|
342
|
+
"validation_mode": "syntax_and_schema"
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Each error includes `line` and `column` (1-indexed) derived from the WASM parser's source location when available. The `hint` field is populated either from Cedar's own diagnostic help text or from a small built-in typo table for common misspellings (`int` for `in`, `permint` for `permit`, `prinicpal` / `prinipal` for `principal`, `wen` for `when`, `unles` for `unless`, etc.). When neither applies, `hint` is `null`.
|
|
347
|
+
|
|
348
|
+
**When to use:** every time you write or modify a policy. Syntax-only mode is the fast first pass when iterating on a snippet. Full schema validation catches attribute typos, entity type mismatches, and action applicability errors before they become silent runtime surprises.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
### `cedar_authorize`
|
|
353
|
+
|
|
354
|
+
Evaluates an authorization request locally against your policies and entities. Returns the decision and which policies fired.
|
|
355
|
+
|
|
356
|
+
**Inputs:**
|
|
357
|
+
|
|
358
|
+
| Parameter | Required | Description |
|
|
359
|
+
|-----------|----------|-------------|
|
|
360
|
+
| `policies` | yes (or `policy_ref`, or auto-discovered) | Cedar policy text |
|
|
361
|
+
| `principal` | yes | Entity reference, e.g. `MyApp::User::"alice"` |
|
|
362
|
+
| `action` | yes | Entity reference, e.g. `MyApp::Action::"read"` |
|
|
363
|
+
| `resource` | yes | Entity reference, e.g. `MyApp::Document::"doc-public"` |
|
|
364
|
+
| `entities` | yes (or `entities_ref`, or auto-discovered) | JSON array of entity objects (uid, attrs, parents) |
|
|
365
|
+
| `schema` | no (or `schema_ref`, or auto-discovered) | Cedar schema; enables request validation |
|
|
366
|
+
| `context` | no | JSON object with context attributes |
|
|
367
|
+
| `policy_ref` | no | `cedar://` URI for policy store reference |
|
|
368
|
+
| `schema_ref` | no | `cedar://` URI for schema reference |
|
|
369
|
+
| `entities_ref` | no | `cedar://` URI for an entities file reference |
|
|
370
|
+
| `store` | no | Store name (a configured MCP root). Use to disambiguate auto-discovery when multiple stores are loaded. |
|
|
371
|
+
|
|
372
|
+
**Workspace auto-discovery.** When any of `policies` / `schema` / `entities` (and their `_ref` siblings) are omitted and exactly one MCP root is loaded, the tool reads each missing input from that store: `policies/*.cedar` files (per-policy basenames preserved as determining-policy IDs), `schema.cedarschema` or `schema.json`, and the entities under `entities/*.json` merged into one array. The response's `auto_discovered` field reports which store satisfied each missing input. With multiple stores loaded, the response is an actionable error listing the candidate names. Pass `store: "<name>"` to choose one.
|
|
373
|
+
|
|
374
|
+
**Example prompt** (paste into Claude Code from a Cedar workspace):
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
Can alice read doc-public?
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
The assistant translates this to a cedar_authorize call with `principal: MyApp::User::"alice"`, `action: MyApp::Action::"read"`, `resource: MyApp::Document::"doc-public"`. Policies, schema, and entities all auto-discover from the sandbox.
|
|
381
|
+
|
|
382
|
+
**Response:**
|
|
383
|
+
|
|
384
|
+
```json
|
|
385
|
+
{
|
|
386
|
+
"decision": "Allow",
|
|
387
|
+
"determining_policies": ["admin"],
|
|
388
|
+
"errors": [],
|
|
389
|
+
"decision_reason": "permit_policy_fired",
|
|
390
|
+
"format_detected": "cedar",
|
|
391
|
+
"format_note": "Input is in Cedar/WASM format.",
|
|
392
|
+
"auto_discovered": {
|
|
393
|
+
"policies_from": "cedar-sandbox",
|
|
394
|
+
"schema_from": "cedar-sandbox",
|
|
395
|
+
"entities_from": "cedar-sandbox"
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
`determining_policies: ["admin"]` is the file basename (`policies/admin.cedar` → `admin`), not a positional placeholder, because cedar_authorize uses the H1 stable-ID resolution ladder described below.
|
|
401
|
+
|
|
402
|
+
Additional shapes:
|
|
403
|
+
|
|
404
|
+
```json
|
|
405
|
+
// Deny via default-deny (no policy matched)
|
|
406
|
+
{
|
|
407
|
+
"decision": "Deny",
|
|
408
|
+
"determining_policies": [],
|
|
409
|
+
"errors": [],
|
|
410
|
+
"decision_reason": "default_deny_no_permit_matched"
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
```json
|
|
415
|
+
// Deny via a forbid policy
|
|
416
|
+
{
|
|
417
|
+
"decision": "Deny",
|
|
418
|
+
"determining_policies": ["editor_readonly"],
|
|
419
|
+
"errors": [],
|
|
420
|
+
"decision_reason": "forbid_policy_fired"
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
`determining_policies` lists the stable policy IDs that contributed to the decision. The ID resolution order is: the policy's `@id("name")` annotation if present, then the source file basename when policies are loaded via `policy_ref` (e.g. `admin.cedar` becomes `admin`), then a positional fallback `policy0`, `policy1`, etc. for unannotated inline text. On a deny caused by a `forbid` policy, that policy's ID appears here. An empty list means default deny: no `permit` matched.
|
|
425
|
+
|
|
426
|
+
`decision_reason` is an explicit machine-readable classification of the outcome. It takes one of four values:
|
|
427
|
+
|
|
428
|
+
- `permit_policy_fired` when `decision: "Allow"` and at least one permit policy is determining.
|
|
429
|
+
- `forbid_policy_fired` when `decision: "Deny"` and at least one forbid policy is determining.
|
|
430
|
+
- `default_deny_no_permit_matched` when `decision: "Deny"`, no policy fired, and no evaluation errors occurred. This is the Cedar default-deny path.
|
|
431
|
+
- `evaluation_error` when at least one policy errored during evaluation (for example, a policy reads an attribute the entity lacks). Pair with the `errors` array for details.
|
|
432
|
+
|
|
433
|
+
**When to use:** verifying authorization logic after writing a policy, and checking that your entity payloads produce the expected decisions before deploying.
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
### `cedar_authorize_batch`
|
|
438
|
+
|
|
439
|
+
Runs N authorization requests through ONE policy set and returns the decision matrix. Use case: regression testing after a policy edit (run a canonical request suite against the new policy set and confirm decisions haven't drifted).
|
|
440
|
+
|
|
441
|
+
**Inputs:**
|
|
442
|
+
|
|
443
|
+
| Parameter | Required | Description |
|
|
444
|
+
|-----------|----------|-------------|
|
|
445
|
+
| `policies` | one of | Inline Cedar policy text |
|
|
446
|
+
| `policy_ref` | one of | `cedar://` URI to load policies from a configured store |
|
|
447
|
+
| `schema` | no | Cedar schema (JSON or `.cedarschema`); when supplied, schema-violating requests resolve to `decision: "Error"` rather than silent Allow/Deny |
|
|
448
|
+
| `schema_ref` | no | `cedar://` URI to load schema |
|
|
449
|
+
| `requests` | yes | JSON array of authorization request objects: `{principal, action, resource, entities, context?}` |
|
|
450
|
+
| `entities` | no | Shared entities JSON applied when individual requests omit their own `entities` field |
|
|
451
|
+
| `entities_ref` | no | `cedar://` URI to load shared entities |
|
|
452
|
+
|
|
453
|
+
**Example prompt** (paste into Claude Code from a Cedar workspace):
|
|
454
|
+
|
|
455
|
+
```
|
|
456
|
+
Run a batch of three authorizations against the sandbox: alice/read/doc-public, bob/write/doc-public, charlie/delete/doc-public.
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
The assistant translates this to a cedar_authorize_batch call referencing `cedar://policies/cedar-sandbox`, `cedar://schema/cedar-sandbox`, and `cedar://entities/cedar-sandbox`, with the three requests inline.
|
|
460
|
+
|
|
461
|
+
**Response:**
|
|
462
|
+
|
|
463
|
+
```json
|
|
464
|
+
{
|
|
465
|
+
"total": 3,
|
|
466
|
+
"allowed": 2,
|
|
467
|
+
"denied": 1,
|
|
468
|
+
"errored": 0,
|
|
469
|
+
"decisions": [
|
|
470
|
+
{
|
|
471
|
+
"index": 0,
|
|
472
|
+
"principal": "MyApp::User::\"alice\"",
|
|
473
|
+
"action": "MyApp::Action::\"read\"",
|
|
474
|
+
"resource": "MyApp::Document::\"doc-public\"",
|
|
475
|
+
"decision": "Allow",
|
|
476
|
+
"determining_policies": ["admin"]
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
"index": 1,
|
|
480
|
+
"principal": "MyApp::User::\"bob\"",
|
|
481
|
+
"action": "MyApp::Action::\"write\"",
|
|
482
|
+
"resource": "MyApp::Document::\"doc-public\"",
|
|
483
|
+
"decision": "Allow",
|
|
484
|
+
"determining_policies": ["editor"]
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
"index": 2,
|
|
488
|
+
"principal": "MyApp::User::\"charlie\"",
|
|
489
|
+
"action": "MyApp::Action::\"delete\"",
|
|
490
|
+
"resource": "MyApp::Document::\"doc-public\"",
|
|
491
|
+
"decision": "Deny",
|
|
492
|
+
"determining_policies": []
|
|
493
|
+
}
|
|
494
|
+
],
|
|
495
|
+
"summary": "3 requests: 2 Allow, 1 Deny, 0 Error"
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Per-request errors carry an `error` field describing what went wrong (malformed entities, schema violation, etc.) without aborting the rest of the batch.
|
|
500
|
+
|
|
501
|
+
`determining_policies` returns file basenames (`admin`, `editor`) when policies are loaded from a `cedar://policies/{store}` ref, matching the H1 stable-ID resolution that single-request `cedar_authorize` uses. Inline policies passed as a flat string still surface as `policy0` / `policy1` (positional fallback) because the caller did not supply basenames.
|
|
502
|
+
|
|
503
|
+
**When to use:** regression testing a canonical request suite after any policy edit. Pair with `cedar_diff_policy_stores`'s `behavioral_test_requests` when you also want a side-by-side comparison against the previous policy set.
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
### `cedar_format`
|
|
508
|
+
|
|
509
|
+
Formats Cedar policy text to canonical style. Useful before committing policy files or pasting into pull requests.
|
|
510
|
+
|
|
511
|
+
**Inputs:**
|
|
512
|
+
|
|
513
|
+
| Parameter | Required | Description |
|
|
514
|
+
|-----------|----------|-------------|
|
|
515
|
+
| `policies` | yes | Cedar policy text to format |
|
|
516
|
+
| `line_width` | no | Maximum line width (default: 80) |
|
|
517
|
+
| `indent_width` | no | Indent width in spaces (default: 2) |
|
|
518
|
+
|
|
519
|
+
**Example prompt** (paste into Claude Code):
|
|
520
|
+
|
|
521
|
+
```
|
|
522
|
+
Format this Cedar: permit(principal in MyApp::Role::"admin",action,resource);
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
**Response:**
|
|
526
|
+
|
|
527
|
+
```json
|
|
528
|
+
{
|
|
529
|
+
"formatted": "permit (\n principal in MyApp::Role::\"admin\",\n action,\n resource\n);\n",
|
|
530
|
+
"error": null
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
The `formatted` field is the canonical-style Cedar text. The same input applied to the formatter is idempotent (already-canonical text returns unchanged).
|
|
535
|
+
|
|
536
|
+
**When to use:** before committing policy files. Canonical formatting makes diffs readable and policy reviews easier.
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
### `cedar_translate`
|
|
541
|
+
|
|
542
|
+
Translates between Cedar text and JSON formats for policies and schemas.
|
|
543
|
+
|
|
544
|
+
**Inputs:**
|
|
545
|
+
|
|
546
|
+
| Parameter | Required | Description |
|
|
547
|
+
|-----------|----------|-------------|
|
|
548
|
+
| `input` | yes | Cedar text or JSON string to translate |
|
|
549
|
+
| `type` | yes | `"policy"` or `"schema"` |
|
|
550
|
+
| `direction` | yes | `"to_json"` or `"to_cedar"` |
|
|
551
|
+
|
|
552
|
+
**Example prompt** (paste into Claude Code):
|
|
553
|
+
|
|
554
|
+
```
|
|
555
|
+
Translate to JSON: permit (principal, action, resource);
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
**Response:**
|
|
559
|
+
|
|
560
|
+
```json
|
|
561
|
+
{
|
|
562
|
+
"output": "{\n \"effect\": \"permit\",\n \"principal\": {\n \"op\": \"All\"\n },\n \"action\": {\n \"op\": \"All\"\n },\n \"resource\": {\n \"op\": \"All\"\n },\n \"conditions\": []\n}",
|
|
563
|
+
"error": null
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
The `output` field is the Cedar JSON-AST representation of the policy. Round-trip back via `direction: "to_cedar"` to recover the source.
|
|
568
|
+
|
|
569
|
+
**When to use:** programmatic policy inspection, generating policies from code, or feeding policy structure into tools that work with JSON.
|
|
570
|
+
|
|
571
|
+
---
|
|
572
|
+
|
|
573
|
+
### `cedar_explain`
|
|
574
|
+
|
|
575
|
+
Explains a Cedar policy in plain English with pattern detection.
|
|
576
|
+
|
|
577
|
+
**Inputs:**
|
|
578
|
+
|
|
579
|
+
| Parameter | Required | Description |
|
|
580
|
+
|-----------|----------|-------------|
|
|
581
|
+
| `policy` | yes | A single Cedar policy (not a policy set) |
|
|
582
|
+
| `schema` | no | Cedar schema; improves entity type descriptions |
|
|
583
|
+
| `schema_ref` | no | `cedar://` URI for schema reference |
|
|
584
|
+
| `store` | no | Store name (a configured MCP root). Use to disambiguate auto-discovery when multiple stores are loaded. |
|
|
585
|
+
|
|
586
|
+
**Workspace auto-discovery.** When `schema` and `schema_ref` are both omitted and exactly one MCP root is loaded, the tool reads the schema from that store. The response's `auto_discovered.schema_from` field names the source store. The schema is optional for explain, so a single store with no schema file still produces a result. With multiple stores loaded and no `store` parameter, the response is an actionable error listing the candidate names.
|
|
587
|
+
|
|
588
|
+
**Example prompt** (paste into Claude Code from a Cedar workspace):
|
|
589
|
+
|
|
590
|
+
```
|
|
591
|
+
Explain this policy: permit (principal in MyApp::Role::"admin", action, resource);
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
**Response:**
|
|
595
|
+
|
|
596
|
+
```json
|
|
597
|
+
{
|
|
598
|
+
"effect": "permit",
|
|
599
|
+
"principal": {
|
|
600
|
+
"scope": "in",
|
|
601
|
+
"description": "principal in MyApp::Role::\"admin\""
|
|
602
|
+
},
|
|
603
|
+
"action": {
|
|
604
|
+
"scope": "All",
|
|
605
|
+
"description": "any action"
|
|
606
|
+
},
|
|
607
|
+
"resource": {
|
|
608
|
+
"scope": "All",
|
|
609
|
+
"description": "any resource"
|
|
610
|
+
},
|
|
611
|
+
"conditions": [],
|
|
612
|
+
"summary": "PERMITS principal in MyApp::Role::\"admin\" to perform any action on any resource.",
|
|
613
|
+
"patterns_detected": [
|
|
614
|
+
"role_based_access",
|
|
615
|
+
"unrestricted_action",
|
|
616
|
+
"unrestricted_resource"
|
|
617
|
+
],
|
|
618
|
+
"auto_discovered": {
|
|
619
|
+
"schema_from": "cedar-sandbox"
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
The `patterns_detected` array names recognizable shapes the tool spotted in the AST: `role_based_access` for membership in a Role, `unrestricted_action` / `unrestricted_resource` for `All`-scope clauses, `attribute_condition` when `when`/`unless` reads attributes, `forbid_policy` for forbid effects, etc.
|
|
625
|
+
|
|
626
|
+
**When to use:** onboarding teammates onto an existing policy set, auditing policies you didn't write, or explaining the intent behind a complex `when`/`unless` combination.
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
### `cedar_check_policy_change`
|
|
631
|
+
|
|
632
|
+
Determines whether a policy modification can be applied in-place in Amazon Verified Permissions or requires delete-and-recreate.
|
|
633
|
+
|
|
634
|
+
**Inputs:**
|
|
635
|
+
|
|
636
|
+
| Parameter | Required | Description |
|
|
637
|
+
|-----------|----------|-------------|
|
|
638
|
+
| `old_policy` | yes | Existing Cedar policy text |
|
|
639
|
+
| `new_policy` | yes | Updated Cedar policy text |
|
|
640
|
+
|
|
641
|
+
**Example prompt** (paste into Claude Code):
|
|
642
|
+
|
|
643
|
+
```
|
|
644
|
+
Check this change: editor.cedar narrows from `action in [read, write]` to `action == read`.
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
The assistant translates this to a cedar_check_policy_change call with `old_policy` = the read+write permit, `new_policy` = the read-only permit (both in the MyApp namespace).
|
|
648
|
+
|
|
649
|
+
**Response:**
|
|
650
|
+
|
|
651
|
+
```json
|
|
652
|
+
{
|
|
653
|
+
"can_update_in_place": true,
|
|
654
|
+
"changes": [
|
|
655
|
+
{
|
|
656
|
+
"field": "action",
|
|
657
|
+
"old_value": "{\"op\":\"in\",\"entities\":[{\"type\":\"MyApp::Action\",\"id\":\"read\"},{\"type\":\"MyApp::Action\",\"id\":\"write\"}]}",
|
|
658
|
+
"new_value": "{\"op\":\"==\",\"entity\":{\"type\":\"MyApp::Action\",\"id\":\"read\"}}",
|
|
659
|
+
"in_place_allowed": true,
|
|
660
|
+
"reason": "Action clause changes can be applied in-place."
|
|
661
|
+
}
|
|
662
|
+
],
|
|
663
|
+
"recommendation": "All changes can be applied as an in-place policy update."
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
An example where the diff requires delete-and-recreate instead:
|
|
668
|
+
|
|
669
|
+
```json
|
|
670
|
+
// old_policy changed the principal head clause (e.g. viewer → senior_viewer)
|
|
671
|
+
{
|
|
672
|
+
"can_update_in_place": false,
|
|
673
|
+
"changes": [
|
|
674
|
+
{
|
|
675
|
+
"field": "principal",
|
|
676
|
+
"in_place_allowed": false,
|
|
677
|
+
"reason": "Changing the principal clause requires deleting and recreating the policy."
|
|
678
|
+
}
|
|
679
|
+
],
|
|
680
|
+
"recommendation": "This policy requires delete-and-recreate. Use DeletePolicy then CreatePolicy."
|
|
681
|
+
}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
**AVP immutability rules:**
|
|
685
|
+
|
|
686
|
+
| Field | In-place via UpdatePolicy? |
|
|
687
|
+
|-------|--------------------------|
|
|
688
|
+
| `effect` | No (delete and recreate) |
|
|
689
|
+
| `principal` | No (delete and recreate) |
|
|
690
|
+
| `resource` | No (delete and recreate) |
|
|
691
|
+
| `action` | Yes |
|
|
692
|
+
| `conditions` (when/unless) | Yes |
|
|
693
|
+
|
|
694
|
+
**When to use:** before deploying any policy change to AVP. Knowing upfront whether a change requires delete-and-recreate prevents surprises in production.
|
|
695
|
+
|
|
696
|
+
---
|
|
697
|
+
|
|
698
|
+
### `cedar_generate_sample_request`
|
|
699
|
+
|
|
700
|
+
Generates a complete authorization request payload that produces a target decision against a given policy.
|
|
701
|
+
|
|
702
|
+
**Inputs:**
|
|
703
|
+
|
|
704
|
+
| Parameter | Required | Description |
|
|
705
|
+
|-----------|----------|-------------|
|
|
706
|
+
| `policy` | yes | A single Cedar policy |
|
|
707
|
+
| `schema` | yes | Cedar schema for the namespace |
|
|
708
|
+
| `target_decision` | yes | `"allow"` or `"deny"` |
|
|
709
|
+
|
|
710
|
+
**Example prompt** (paste into Claude Code):
|
|
711
|
+
|
|
712
|
+
```
|
|
713
|
+
Generate a sample request that would be allowed by: permit (principal in MyApp::Role::"admin", action, resource);
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
The assistant translates this to a cedar_generate_sample_request call with the policy text and the cedar-sandbox schema.
|
|
717
|
+
|
|
718
|
+
**Response:**
|
|
719
|
+
|
|
720
|
+
```json
|
|
721
|
+
{
|
|
722
|
+
"principal": "MyApp::User::\"sample-principal\"",
|
|
723
|
+
"action": "MyApp::Action::\"delete\"",
|
|
724
|
+
"resource": "MyApp::Document::\"sample-resource\"",
|
|
725
|
+
"entities": [
|
|
726
|
+
{
|
|
727
|
+
"uid": { "type": "MyApp::User", "id": "sample-principal" },
|
|
728
|
+
"attrs": { "email": "", "name": "" },
|
|
729
|
+
"parents": [{ "type": "MyApp::Role", "id": "admin" }]
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
"uid": { "type": "MyApp::Document", "id": "sample-resource" },
|
|
733
|
+
"attrs": { "classification": "", "owner": "" },
|
|
734
|
+
"parents": []
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
"uid": { "type": "MyApp::Role", "id": "admin" },
|
|
738
|
+
"attrs": {},
|
|
739
|
+
"parents": []
|
|
740
|
+
}
|
|
741
|
+
],
|
|
742
|
+
"explanation": "This request will be ALLOW as expected.",
|
|
743
|
+
"decision": "Allow",
|
|
744
|
+
"ready_to_test": true
|
|
745
|
+
}
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
The generator pre-fills required attributes from the schema (`name`, `email` on User; `classification`, `owner` on Document) with neutral defaults so `validateRequest: true` accepts the payload. The verification step runs the generated request through `cedar_authorize` with the same schema; `ready_to_test: true` means a follow-up call with these exact inputs reproduces the documented `decision`. If the verification fails (most commonly when the policy pins an entity type the schema does not declare), the response carries `ready_to_test: false` and a non-null `error` instead.
|
|
749
|
+
|
|
750
|
+
**When to use:** generating test payloads without hand-crafting entities, or verifying that a policy produces the decisions you expect before deploying it. Pass the output directly to `cedar_authorize` to verify.
|
|
751
|
+
|
|
752
|
+
---
|
|
753
|
+
|
|
754
|
+
### `cedar_advise`
|
|
755
|
+
|
|
756
|
+
Returns a deterministic, structured context bundle for any "I want to change my Cedar policies to do X" intent. The bundle encodes the Cedar / AVP knowledge that does not live in the policy files: AVP `UpdatePolicy` mutability rules, AVP validation error categories, the 10-entry gotcha catalog (with the subset selected by the user's intent keywords), the Cedar patterns reference, AST-based pattern classification of every policy in the store, and explicit sequencing + follow-up guidance.
|
|
757
|
+
|
|
758
|
+
The tool itself does not produce the plan. The calling assistant produces the plan from the bundle, then verifies each Cedar snippet with `cedar_validate` and each modification with `cedar_check_policy_change`. No MCP sampling, no client LLM round-trip on the server side.
|
|
759
|
+
|
|
760
|
+
This pivot replaced the original sampling-based `cedar_advise` after dogfooding revealed two problems: (1) Claude Code does not advertise the MCP `sampling` capability, so the prior tool returned `-32601 Method not found`; (2) when the assistant fell back to drafting from file Read alone, it bypassed the server entirely and lost the AVP/gotcha grounding the server is supposed to provide. The bundle design defeats both.
|
|
761
|
+
|
|
762
|
+
**Inputs:**
|
|
763
|
+
|
|
764
|
+
| Parameter | Required | Description |
|
|
765
|
+
|-----------|----------|-------------|
|
|
766
|
+
| `intent` | yes | Natural-language description of the desired authorization behavior, kept verbatim from the user |
|
|
767
|
+
| `store_ref` | no | Store name or `cedar://` URI (e.g. `cedar://policies/production` or `production`); when supplied, the bundle includes `schema_summary`, `policy_inventory` with full policy text, and `patterns_detected_in_store` counts grounded in the actual store. Omit when exactly one store is loaded and the bundle will auto-resolve to it (the response's `auto_discovered.store_from` field reports `"single_loaded_store"`). With multiple stores loaded and no `store_ref`, the response sets `store_status: "ambiguous"` and lists the candidate names under `available_stores`. |
|
|
768
|
+
|
|
769
|
+
**Example prompt** (paste into Claude Code from a Cedar workspace):
|
|
770
|
+
|
|
771
|
+
```
|
|
772
|
+
I want to make editors read-only, admins exempt. Plan it.
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
The assistant translates this to a cedar_advise call with `intent: "Make editors read-only, admins exempt."` and no `store_ref` (cedar-sandbox is the only loaded store, so it auto-resolves).
|
|
776
|
+
|
|
777
|
+
**Response (abridged):**
|
|
778
|
+
|
|
779
|
+
```json
|
|
780
|
+
{
|
|
781
|
+
"tool": "cedar_advise",
|
|
782
|
+
"bundle_version": "v2",
|
|
783
|
+
"intent": "Make editors read-only, admins exempt.",
|
|
784
|
+
"store_name": "cedar-sandbox",
|
|
785
|
+
"store_status": "loaded",
|
|
786
|
+
"auto_discovered": { "store_from": "single_loaded_store" },
|
|
787
|
+
"schema_summary": {
|
|
788
|
+
"valid": true,
|
|
789
|
+
"format": "cedarschema",
|
|
790
|
+
"namespaces": ["MyApp"],
|
|
791
|
+
"entity_type_count": 4,
|
|
792
|
+
"action_count": 3,
|
|
793
|
+
"raw_text": "namespace MyApp { ... }"
|
|
794
|
+
},
|
|
795
|
+
"policy_inventory": [
|
|
796
|
+
{
|
|
797
|
+
"policy_id": "admin",
|
|
798
|
+
"pattern": "membership",
|
|
799
|
+
"pattern_confidence": "high",
|
|
800
|
+
"summary": "admin (permit, principal scope uses 'in' — group/role membership (RBAC))",
|
|
801
|
+
"policy_text": "permit (principal in MyApp::Role::\"admin\", action, resource);"
|
|
802
|
+
},
|
|
803
|
+
{ "policy_id": "editor", "pattern": "membership", "...": "..." },
|
|
804
|
+
{ "policy_id": "viewer", "pattern": "membership", "...": "..." }
|
|
805
|
+
],
|
|
806
|
+
"patterns_detected_in_store": [{ "pattern": "membership", "count": 3 }],
|
|
807
|
+
"applicable_gotchas": [
|
|
808
|
+
{
|
|
809
|
+
"id": "array_containment_syntax",
|
|
810
|
+
"severity": "medium",
|
|
811
|
+
"description": "Cedar array containment is left-first: `[\"a\", \"b\"].contains(attr)` — the ARRAY is on the left. Writing `attr in [\"a\", \"b\"]` is an entity-hierarchy check, not a value containment check."
|
|
812
|
+
}
|
|
813
|
+
],
|
|
814
|
+
"avp_update_policy_rules": {
|
|
815
|
+
"summary": "...",
|
|
816
|
+
"in_place_via_update_policy": ["Action scope", "When/unless conditions", "Policy name"],
|
|
817
|
+
"requires_delete_recreate": ["Effect", "Principal scope", "Resource scope", "Static ↔ template-linked conversion"],
|
|
818
|
+
"new_via_create_policy": ["Wholly new policy"],
|
|
819
|
+
"notes": ["UpdatePolicy only updates STATIC policies..."]
|
|
820
|
+
},
|
|
821
|
+
"avp_validation_error_catalog": [
|
|
822
|
+
{ "id": "UnsafeOptionalAttributeAccess", "description": "..." }
|
|
823
|
+
],
|
|
824
|
+
"cedar_patterns_reference": {
|
|
825
|
+
"summary": "...",
|
|
826
|
+
"patterns": [
|
|
827
|
+
{ "name": "Membership (RBAC)", "description": "...", "example": "..." }
|
|
828
|
+
]
|
|
829
|
+
},
|
|
830
|
+
"sequencing_guidance": [
|
|
831
|
+
"Schema changes that add new entity types, attributes, or actions MUST be deployed BEFORE policies that reference them..."
|
|
832
|
+
],
|
|
833
|
+
"next_steps_for_llm": "Use this context to produce a Cedar policy change plan. Do not skip these steps: 1. Identify the entity types... 6. After drafting Cedar snippets, call cedar_validate on each... 7. For each modification to an existing policy, call cedar_check_policy_change..."
|
|
834
|
+
}
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
The main example above shows the **auto-resolve** path (one store loaded, no `store_ref` passed). When multiple stores are loaded and no `store_ref` is passed, the response shape is **ambiguous** instead:
|
|
838
|
+
|
|
839
|
+
```json
|
|
840
|
+
{
|
|
841
|
+
"tool": "cedar_advise",
|
|
842
|
+
"bundle_version": "v2",
|
|
843
|
+
"intent": "Make editors read-only, admins exempt.",
|
|
844
|
+
"store_status": "ambiguous",
|
|
845
|
+
"available_stores": ["blue", "green"],
|
|
846
|
+
"policy_inventory": [],
|
|
847
|
+
"patterns_detected_in_store": [],
|
|
848
|
+
"...": "universal Cedar / AVP context still present"
|
|
849
|
+
}
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
The calling LLM should ask the user which store and re-invoke with an explicit `store_ref`. The `next_steps_for_llm` field in the response includes this guidance.
|
|
853
|
+
|
|
854
|
+
**When to use:** at the start of any policy-change conversation, before recommending any Cedar snippet. Call this once per intent; iterate the plan in conversation rather than re-calling for small refinements (the bundle is the same for a given intent + store).
|
|
855
|
+
|
|
856
|
+
When you have more than one store loaded, name your policy store in the prompt (for example, "plan this change against my cedar-sandbox store" or "modify policies in production"). With a store name, the bundle grounds in your actual schema, full policy inventory, and detected patterns. If exactly one store is loaded the bundle auto-resolves to it (you'll see `auto_discovered.store_from: "single_loaded_store"`). If multiple stores are loaded and you don't pass `store_ref`, `store_status` is `"ambiguous"` and `available_stores` lists the candidates so you can retry. If no stores are loaded at all, `store_status` is `"not_provided"` and the bundle returns the generic Cedar / AVP context only.
|
|
857
|
+
|
|
858
|
+
---
|
|
859
|
+
|
|
860
|
+
### `cedar_validate_template`
|
|
861
|
+
|
|
862
|
+
Validates a Cedar template policy against a schema. Templates use slot placeholders (`?principal`, `?resource`) that are bound when the template is instantiated.
|
|
863
|
+
|
|
864
|
+
**Inputs:**
|
|
865
|
+
|
|
866
|
+
| Parameter | Required | Description |
|
|
867
|
+
|-----------|----------|-------------|
|
|
868
|
+
| `template` | yes | Cedar template text, e.g. `permit(principal == ?principal, action == ..., resource == ?resource);` |
|
|
869
|
+
| `schema` | yes | Cedar schema (JSON or `.cedarschema` format) |
|
|
870
|
+
|
|
871
|
+
**Example prompt** (paste into Claude Code from a Cedar workspace):
|
|
872
|
+
|
|
873
|
+
```
|
|
874
|
+
Validate this Cedar template against the sandbox schema:
|
|
875
|
+
permit (principal == ?principal, action == MyApp::Action::"read", resource == ?resource);
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
The assistant translates this to a cedar_validate_template call with the template text and the inline schema from `cedar-sandbox/schema.cedarschema`.
|
|
879
|
+
|
|
880
|
+
**Response:**
|
|
881
|
+
|
|
882
|
+
```json
|
|
883
|
+
{
|
|
884
|
+
"valid": true,
|
|
885
|
+
"errors": [],
|
|
886
|
+
"warnings": [],
|
|
887
|
+
"slots_detected": ["?principal", "?resource"]
|
|
888
|
+
}
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
**When to use:** after writing a new template, before adding it to your policy store. Also use to discover which slots a template exposes before calling `cedar_link_template`.
|
|
892
|
+
|
|
893
|
+
---
|
|
894
|
+
|
|
895
|
+
### `cedar_link_template`
|
|
896
|
+
|
|
897
|
+
Instantiates a Cedar template by binding its `?principal` and/or `?resource` slots to specific entity references. Returns the resulting Cedar policy text.
|
|
898
|
+
|
|
899
|
+
**Inputs:**
|
|
900
|
+
|
|
901
|
+
| Parameter | Required | Description |
|
|
902
|
+
|-----------|----------|-------------|
|
|
903
|
+
| `template` | yes | Cedar template text |
|
|
904
|
+
| `principal` | no | Entity reference for the `?principal` slot, e.g. `App::User::"alice"` |
|
|
905
|
+
| `resource` | no | Entity reference for the `?resource` slot, e.g. `App::Document::"doc-42"` |
|
|
906
|
+
| `schema` | no | Cedar schema; if provided, the linked policy is validated against it |
|
|
907
|
+
|
|
908
|
+
**Example prompt** (paste into Claude Code from a Cedar workspace):
|
|
909
|
+
|
|
910
|
+
```
|
|
911
|
+
Link this template with alice + doc-public, validated against the sandbox schema:
|
|
912
|
+
permit (principal == ?principal, action == MyApp::Action::"read", resource == ?resource);
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
**Response:**
|
|
916
|
+
|
|
917
|
+
```json
|
|
918
|
+
{
|
|
919
|
+
"linked_policy": "permit(principal == MyApp::User::\"alice\", action == MyApp::Action::\"read\", resource == MyApp::Document::\"doc-public\");",
|
|
920
|
+
"slots_bound": {
|
|
921
|
+
"?principal": "MyApp::User::\"alice\"",
|
|
922
|
+
"?resource": "MyApp::Document::\"doc-public\""
|
|
923
|
+
},
|
|
924
|
+
"valid": true,
|
|
925
|
+
"errors": []
|
|
926
|
+
}
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
Entity reference format is `Namespace::Type::"id"`, same as `cedar_authorize`'s principal / resource parameters.
|
|
930
|
+
|
|
931
|
+
**When to use:** instantiating a template to inspect the resulting policy or to validate it before deployment. For AVP, you upload the template once via `CreatePolicyTemplate` and create instances via `CreatePolicy` (template-linked variant); `cedar_link_template` helps you reason about what those instances will look like before you make the API call.
|
|
932
|
+
|
|
933
|
+
---
|
|
934
|
+
|
|
935
|
+
### `cedar_list_templates`
|
|
936
|
+
|
|
937
|
+
Lists all Cedar template policies in a policy store. Templates live in a `templates/` subdirectory of the store root, following the same layout convention as `policies/`.
|
|
938
|
+
|
|
939
|
+
**Store layout with templates:**
|
|
940
|
+
|
|
941
|
+
```
|
|
942
|
+
my-store/
|
|
943
|
+
policies/
|
|
944
|
+
admin.cedar
|
|
945
|
+
templates/
|
|
946
|
+
viewer-access.cedar <- permit(principal == ?principal, action == ..., resource == ?resource);
|
|
947
|
+
editor-access.cedar
|
|
948
|
+
template-links/
|
|
949
|
+
alice-docs.json <- { "template_id": "viewer-access", "slot_values": { ... } }
|
|
950
|
+
schema.cedarschema
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
**Inputs:**
|
|
954
|
+
|
|
955
|
+
| Parameter | Required | Description |
|
|
956
|
+
|-----------|----------|-------------|
|
|
957
|
+
| `store` | yes | Store name (must be a configured MCP root) |
|
|
958
|
+
|
|
959
|
+
**Example prompt** (paste into Claude Code from a Cedar workspace):
|
|
960
|
+
|
|
961
|
+
```
|
|
962
|
+
List templates in the cedar-sandbox store.
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
**Response:**
|
|
966
|
+
|
|
967
|
+
```json
|
|
968
|
+
{
|
|
969
|
+
"store": "cedar-sandbox",
|
|
970
|
+
"templates": []
|
|
971
|
+
}
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
The sandbox has no `templates/` subdirectory, so the array is empty. With templates present in a store, each entry includes `id` (filename basename without `.cedar`), `content` (the template text), and `slots` (the `?principal` / `?resource` placeholders detected by parsing the template).
|
|
975
|
+
|
|
976
|
+
**When to use:** discovering what templates exist in a store before instantiating or diffing them.
|
|
977
|
+
|
|
978
|
+
---
|
|
979
|
+
|
|
980
|
+
### `cedar_list_template_links`
|
|
981
|
+
|
|
982
|
+
Lists all template-linked policy instances in a store. Links live in a `template-links/` subdirectory. Each link is a JSON file recording which template it uses and the slot values bound to it.
|
|
983
|
+
|
|
984
|
+
**Template link file format** (`template-links/alice-docs.json`):
|
|
985
|
+
|
|
986
|
+
```json
|
|
987
|
+
{
|
|
988
|
+
"template_id": "viewer-access",
|
|
989
|
+
"slot_values": {
|
|
990
|
+
"?principal": "App::User::\"alice\"",
|
|
991
|
+
"?resource": "App::Document::\"doc-42\""
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
**Inputs:**
|
|
997
|
+
|
|
998
|
+
| Parameter | Required | Description |
|
|
999
|
+
|-----------|----------|-------------|
|
|
1000
|
+
| `store` | yes | Store name (must be a configured MCP root) |
|
|
1001
|
+
|
|
1002
|
+
**Example prompt** (paste into Claude Code from a Cedar workspace):
|
|
1003
|
+
|
|
1004
|
+
```
|
|
1005
|
+
List template links in the cedar-sandbox store.
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
**Response:**
|
|
1009
|
+
|
|
1010
|
+
```json
|
|
1011
|
+
{
|
|
1012
|
+
"store": "cedar-sandbox",
|
|
1013
|
+
"links": []
|
|
1014
|
+
}
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
The sandbox has no `template-links/` subdirectory, so the array is empty. With links present, each entry includes `id` (filename basename without `.json`), `template_id`, and `slot_values`.
|
|
1018
|
+
|
|
1019
|
+
**When to use:** auditing which principal-resource pairs are covered by template-linked policies in a store, or diffing link coverage before a deployment.
|
|
1020
|
+
|
|
1021
|
+
---
|
|
1022
|
+
|
|
1023
|
+
### `cedar_diff_policy_stores`
|
|
1024
|
+
|
|
1025
|
+
Structural and optional behavioral diff between two policy stores. Requires MCP Roots configured (see [Setup with policy stores](#setup-with-policy-stores-mcp-roots)).
|
|
1026
|
+
|
|
1027
|
+
**Inputs:**
|
|
1028
|
+
|
|
1029
|
+
| Parameter | Required | Description |
|
|
1030
|
+
|-----------|----------|-------------|
|
|
1031
|
+
| `blue` | yes | Store name for the base (e.g. `"production"`) |
|
|
1032
|
+
| `green` | yes | Store name for the proposed changes (e.g. `"staging"`) |
|
|
1033
|
+
| `behavioral_test_requests` | no | JSON string of authorization requests to run through both stores; surfaces decision drift |
|
|
1034
|
+
|
|
1035
|
+
**Output shape:**
|
|
1036
|
+
|
|
1037
|
+
**Example prompt** (paste into Claude Code from a Cedar workspace):
|
|
1038
|
+
|
|
1039
|
+
```
|
|
1040
|
+
Diff the cedar-sandbox store against itself (smoke test the tool shape).
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
The assistant translates this to a cedar_diff_policy_stores call with `blue: "cedar-sandbox"`, `green: "cedar-sandbox"`. A real promotion run would compare distinct stores (e.g. `blue: "production"`, `green: "staging"`); see [Setup with policy stores](#setup-with-policy-stores-mcp-roots) for multi-store configuration.
|
|
1044
|
+
|
|
1045
|
+
**Response:**
|
|
1046
|
+
|
|
1047
|
+
```json
|
|
1048
|
+
{
|
|
1049
|
+
"blue": "cedar-sandbox",
|
|
1050
|
+
"green": "cedar-sandbox",
|
|
1051
|
+
"policies_added": [],
|
|
1052
|
+
"policies_removed": [],
|
|
1053
|
+
"policies_modified": [],
|
|
1054
|
+
"schema_diff": {
|
|
1055
|
+
"namespaces_added": [],
|
|
1056
|
+
"namespaces_removed": [],
|
|
1057
|
+
"entity_types": { "added": [], "removed": [], "modified": [] },
|
|
1058
|
+
"actions": { "added": [], "removed": [], "modified": [] },
|
|
1059
|
+
"common_types": { "added": [], "removed": [], "modified": [] },
|
|
1060
|
+
"summary": "No schema changes detected.",
|
|
1061
|
+
"risk_level": "safe"
|
|
1062
|
+
},
|
|
1063
|
+
"summary": "No changes detected between blue and green stores."
|
|
1064
|
+
}
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
With real differences between the two stores, `policies_added` / `policies_removed` / `policies_modified` carry per-policy details (the same `can_update_in_place` + `changes` shape as `cedar_check_policy_change`), and `schema_diff` carries the full structured output of [`cedar_diff_schema`](#cedar_diff_schema). When `behavioral_test_requests` is supplied, the response also includes a `behavioral_diff` array of per-request `blue_decision` vs `green_decision` entries with a `drifted` boolean flagging differences.
|
|
1068
|
+
|
|
1069
|
+
**When to use:** before promoting any policy changes from staging to production. The structural diff tells you what changed and how to deploy it. The behavioral diff tells you which authorization decisions would actually change.
|
|
1070
|
+
|
|
1071
|
+
---
|
|
1072
|
+
|
|
1073
|
+
### `cedar_validate_schema`
|
|
1074
|
+
|
|
1075
|
+
Validates a Cedar schema in isolation, without requiring any policies. Useful for the schema-first workflow: shape the entity model before writing the first policy.
|
|
1076
|
+
|
|
1077
|
+
**Inputs:**
|
|
1078
|
+
|
|
1079
|
+
| Parameter | Required | Description |
|
|
1080
|
+
|-----------|----------|-------------|
|
|
1081
|
+
| `schema` | one of | Cedar schema text (JSON object or `.cedarschema` text). Format auto-detected. |
|
|
1082
|
+
| `schema_ref` | one of | `cedar://schema/{store}` URI for a configured policy store. |
|
|
1083
|
+
|
|
1084
|
+
Exactly one of `schema` or `schema_ref` is required.
|
|
1085
|
+
|
|
1086
|
+
**Example prompt** (paste into Claude Code from a Cedar workspace):
|
|
1087
|
+
|
|
1088
|
+
```
|
|
1089
|
+
Validate the cedar-sandbox schema.
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
The assistant translates this to a cedar_validate_schema call with `schema_ref: "cedar://schema/cedar-sandbox"`.
|
|
1093
|
+
|
|
1094
|
+
**Response:**
|
|
1095
|
+
|
|
1096
|
+
```json
|
|
1097
|
+
{
|
|
1098
|
+
"valid": true,
|
|
1099
|
+
"format": "cedarschema",
|
|
1100
|
+
"namespaces": ["MyApp"],
|
|
1101
|
+
"entity_type_count": 4,
|
|
1102
|
+
"action_count": 3,
|
|
1103
|
+
"common_type_count": 0,
|
|
1104
|
+
"errors": []
|
|
1105
|
+
}
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
When invalid, the same shape carries `valid: false`, zeroed counts, and an `errors` array with `message` plus a `source_location` block:
|
|
1109
|
+
|
|
1110
|
+
```json
|
|
1111
|
+
{
|
|
1112
|
+
"valid": false,
|
|
1113
|
+
"format": "cedarschema",
|
|
1114
|
+
"namespaces": [],
|
|
1115
|
+
"entity_type_count": 0,
|
|
1116
|
+
"action_count": 0,
|
|
1117
|
+
"common_type_count": 0,
|
|
1118
|
+
"errors": [
|
|
1119
|
+
{
|
|
1120
|
+
"message": "failed to parse schema from string: unexpected token `not`",
|
|
1121
|
+
"source_location": { "start": 0, "end": 3, "label": "expected `@`, `action`, `entity`, `namespace`, or `type`" }
|
|
1122
|
+
}
|
|
1123
|
+
]
|
|
1124
|
+
}
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
**When to use:**
|
|
1128
|
+
- before writing the first policy, to confirm the schema you sketched parses correctly
|
|
1129
|
+
- before pushing schema changes to AVP via `PutSchema`, as a syntactic sanity check
|
|
1130
|
+
- as a fast check inside an agentic loop that's building a schema iteratively
|
|
1131
|
+
|
|
1132
|
+
---
|
|
1133
|
+
|
|
1134
|
+
### `cedar_diff_schema`
|
|
1135
|
+
|
|
1136
|
+
Structural diff of two Cedar schemas with AVP-aware risk classification per change. Replaces the hand-wavy "schemas differ, review carefully" pattern with a structured payload that says exactly what changed and how risky each change is for existing policies.
|
|
1137
|
+
|
|
1138
|
+
**Inputs:**
|
|
1139
|
+
|
|
1140
|
+
| Parameter | Required | Description |
|
|
1141
|
+
|-----------|----------|-------------|
|
|
1142
|
+
| `blue` | yes | Baseline schema. Inline schema text (JSON or `.cedarschema`) OR a `cedar://schema/{store}` URI. |
|
|
1143
|
+
| `green` | yes | Proposed schema. Same input forms as `blue`. |
|
|
1144
|
+
|
|
1145
|
+
The tool auto-detects `cedar://` URIs and resolves them via configured policy stores.
|
|
1146
|
+
|
|
1147
|
+
**Example prompt** (paste into Claude Code from a Cedar workspace):
|
|
1148
|
+
|
|
1149
|
+
```
|
|
1150
|
+
Diff the cedar-sandbox schema against a proposed schema that adds a verified_email: Bool attribute to User.
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
The assistant translates this to a cedar_diff_schema call with `blue: "cedar://schema/cedar-sandbox"` and `green` set to the sandbox schema text with `verified_email: Bool,` inserted into the User entity.
|
|
1154
|
+
|
|
1155
|
+
**Response:**
|
|
1156
|
+
|
|
1157
|
+
```json
|
|
1158
|
+
{
|
|
1159
|
+
"namespaces_added": [],
|
|
1160
|
+
"namespaces_removed": [],
|
|
1161
|
+
"entity_types": {
|
|
1162
|
+
"added": [],
|
|
1163
|
+
"removed": [],
|
|
1164
|
+
"modified": [
|
|
1165
|
+
{
|
|
1166
|
+
"namespace": "MyApp",
|
|
1167
|
+
"name": "User",
|
|
1168
|
+
"attribute_changes": [
|
|
1169
|
+
{
|
|
1170
|
+
"attr": "verified_email",
|
|
1171
|
+
"change": "added",
|
|
1172
|
+
"new_type": "Bool",
|
|
1173
|
+
"risk": "breaking",
|
|
1174
|
+
"reason": "Required attribute added: existing entities/requests without this field will fail validation."
|
|
1175
|
+
}
|
|
1176
|
+
]
|
|
1177
|
+
}
|
|
1178
|
+
]
|
|
1179
|
+
},
|
|
1180
|
+
"actions": { "added": [], "removed": [], "modified": [] },
|
|
1181
|
+
"common_types": { "added": [], "removed": [], "modified": [] },
|
|
1182
|
+
"summary": "Schema diff: 1 entity type(s) modified (1 BREAKING).",
|
|
1183
|
+
"risk_level": "breaking"
|
|
1184
|
+
}
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
The `risk: "breaking"` classification fires because adding a *required* attribute to an existing entity type invalidates every entity payload that doesn't already carry the new field. To deploy this safely, make the attribute optional first, backfill values, then tighten to required.
|
|
1188
|
+
|
|
1189
|
+
**Risk classification rules.** Each change carries `risk: safe | review | breaking` plus a `reason` string. The rules:
|
|
1190
|
+
|
|
1191
|
+
| Change | Risk | Why |
|
|
1192
|
+
|---|---|---|
|
|
1193
|
+
| Entity type added | safe | No existing policy references it |
|
|
1194
|
+
| Entity type removed | breaking | Policies referencing the type fail validation |
|
|
1195
|
+
| Optional attribute added | safe | Existing policies don't reference it |
|
|
1196
|
+
| Required attribute added | breaking | Existing entities lack the field |
|
|
1197
|
+
| Attribute removed | breaking | Policies referencing it fail validation |
|
|
1198
|
+
| Attribute type changed | breaking | Policies expecting the old type fail evaluation |
|
|
1199
|
+
| Optional → required | breaking | Existing entities without the field fail |
|
|
1200
|
+
| Required → optional | safe | Existing entities still satisfy the constraint |
|
|
1201
|
+
| `memberOfTypes` added | review | Hierarchy widens; `in` checks may match more entities |
|
|
1202
|
+
| `memberOfTypes` removed | breaking | Policies using `in` against removed parents fail |
|
|
1203
|
+
| Action added | safe | No existing policy targets it |
|
|
1204
|
+
| Action removed | breaking | Policies referencing it become invalid |
|
|
1205
|
+
| Action `principalTypes` widened | review | Policy effect may change |
|
|
1206
|
+
| Action `principalTypes` narrowed | breaking | Existing policies for the removed type fail |
|
|
1207
|
+
| Action `resourceTypes` widened / narrowed | review / breaking | Same as principalTypes |
|
|
1208
|
+
| Action context attribute follows entity-attribute rules | (see above) | (see above) |
|
|
1209
|
+
| Common type added | safe | Nothing references it yet |
|
|
1210
|
+
| Common type removed | review | If unreferenced, safe; if referenced, breaking. Audit. |
|
|
1211
|
+
| Common type modified | review | Default to review; precise impact depends on references |
|
|
1212
|
+
|
|
1213
|
+
`risk_level` on the top-level result is the worst risk across all changes.
|
|
1214
|
+
|
|
1215
|
+
**When to use:**
|
|
1216
|
+
- before promoting a schema change to production, to see exactly what's at risk
|
|
1217
|
+
- as the structured backbone of `cedar_diff_policy_stores` (embedded automatically there)
|
|
1218
|
+
- inside an agentic policy review, so the agent can reason about whether the schema change is deployable as-is
|
|
1219
|
+
|
|
1220
|
+
---
|
|
1221
|
+
|
|
1222
|
+
### `cedar_validate_entities`
|
|
1223
|
+
|
|
1224
|
+
Validates a Cedar entities JSON array against a schema, returning per-entity errors classified by kind. Useful for catching entity-store drift before it hits authorization at runtime.
|
|
1225
|
+
|
|
1226
|
+
**Inputs:**
|
|
1227
|
+
|
|
1228
|
+
| Parameter | Required | Description |
|
|
1229
|
+
|-----------|----------|-------------|
|
|
1230
|
+
| `entities` | yes | JSON array of entity objects with `uid`, `attrs`, `parents` |
|
|
1231
|
+
| `schema` | no | Cedar schema (JSON or `.cedarschema`); when supplied, enables type validation. Without it, only JSON shape is checked. |
|
|
1232
|
+
| `schema_ref` | no | `cedar://schema/{store}` URI alternative to inline `schema` |
|
|
1233
|
+
|
|
1234
|
+
**Example prompt** (paste into Claude Code from a Cedar workspace):
|
|
1235
|
+
|
|
1236
|
+
```
|
|
1237
|
+
Validate the entities in cedar-sandbox against its schema.
|
|
1238
|
+
```
|
|
1239
|
+
|
|
1240
|
+
The assistant translates this to a cedar_validate_entities call with the entities payload inline (read from `cedar-sandbox/entities/sample.json`) and `schema_ref: "cedar://schema/cedar-sandbox"`.
|
|
1241
|
+
|
|
1242
|
+
**Response:**
|
|
1243
|
+
|
|
1244
|
+
```json
|
|
1245
|
+
{
|
|
1246
|
+
"valid": true,
|
|
1247
|
+
"entity_count": 11,
|
|
1248
|
+
"errors": []
|
|
1249
|
+
}
|
|
1250
|
+
```
|
|
1251
|
+
|
|
1252
|
+
When some entities fail validation, `valid` flips to `false` and `errors` lists per-entity findings:
|
|
1253
|
+
|
|
1254
|
+
```json
|
|
1255
|
+
{
|
|
1256
|
+
"valid": false,
|
|
1257
|
+
"entity_count": 1,
|
|
1258
|
+
"errors": [
|
|
1259
|
+
{
|
|
1260
|
+
"entity_uid": "MyApp::User::\"alice\"",
|
|
1261
|
+
"error_kind": "type_mismatch",
|
|
1262
|
+
"attribute": "name",
|
|
1263
|
+
"message": "entity does not conform to the schema: in attribute `name` on `MyApp::User::\"alice\"`, type mismatch: value was expected to have type string, but it actually has type long: `42`"
|
|
1264
|
+
}
|
|
1265
|
+
]
|
|
1266
|
+
}
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
`error_kind` is one of: `unknown_type`, `missing_required_attribute`, `type_mismatch`, `unknown_attribute`, `disallowed_parent_type`, `parse_error`, `other`. The `attribute` field is present for attribute-related errors. `disallowed_parent_type` fires when an entity's `parents` array contains a type the schema doesn't allow as an ancestor for that entity type.
|
|
1270
|
+
|
|
1271
|
+
**When to use:**
|
|
1272
|
+
- when working with entity dumps from AVP `BatchGet` or custom entity stores
|
|
1273
|
+
- before running `cedar_authorize` on a request that includes user-supplied entities, to catch shape issues early
|
|
1274
|
+
- inside a CI pipeline that publishes an entity snapshot, to fail fast on drift
|
|
1275
|
+
|
|
1276
|
+
---
|
|
1277
|
+
|
|
1278
|
+
## MCP Prompts
|
|
1279
|
+
|
|
1280
|
+
In addition to tools, the server registers three MCP prompts that clients surface as slash commands or pre-canned message templates. Each prompt takes arguments, then returns an assembled message that drives the assistant through a structured Cedar workflow.
|
|
1281
|
+
|
|
1282
|
+
| Prompt | Arguments | What it does |
|
|
1283
|
+
|--------|-----------|--------------|
|
|
1284
|
+
| `cedar-review-policy-diff` | `blue_store` (required), `green_store` (required), `focus` (optional) | Drives `cedar_diff_policy_stores` + `cedar_diff_schema`, summarizes structural changes plus risk-classified schema diff, and recommends whether to promote. |
|
|
1285
|
+
| `cedar-explain-denial` | `principal`, `action`, `resource`, `store` (all required) | Runs `cedar_authorize` against the store via `cedar://` refs, calls `cedar_explain` on the deciding policies, and produces a plain-English explanation of why the request was denied (or allowed) plus what would need to change. |
|
|
1286
|
+
| `cedar-avp-migration-checklist` | `namespace` (optional) | Returns a guided checklist for migrating an AVP policy store: schema validation, entity format detection, single-namespace constraint, template-linked policies, schema diff before `PutSchema`, behavioral diff before traffic shift. Informational only; no tool calls assumed. |
|
|
1287
|
+
|
|
1288
|
+
In Claude Code these appear under the `/` slash menu when the server is configured. Other clients surface them differently per their UI conventions.
|
|
1289
|
+
|
|
1290
|
+
---
|
|
1291
|
+
|
|
1292
|
+
## Running as a shared HTTP server
|
|
1293
|
+
|
|
1294
|
+
The default `npx cedar-mcp-server` mode is stdio, designed for Claude Code / Claude Desktop / Cursor on a single developer machine. For a shared team deployment (one server, many clients), use Streamable HTTP mode.
|
|
1295
|
+
|
|
1296
|
+
### Start the server
|
|
1297
|
+
|
|
1298
|
+
```bash
|
|
1299
|
+
# Local-only (recommended default; binds to 127.0.0.1 with DNS rebinding protection)
|
|
1300
|
+
cedar-mcp-server --http 3000 --root production=/etc/cedar/production --root staging=/etc/cedar/staging
|
|
1301
|
+
|
|
1302
|
+
# Non-localhost binding (you handle auth via reverse proxy)
|
|
1303
|
+
cedar-mcp-server --http 3000 --host 0.0.0.0 --root prod=/etc/cedar/prod
|
|
1304
|
+
```
|
|
1305
|
+
|
|
1306
|
+
CLI flags:
|
|
1307
|
+
|
|
1308
|
+
| Flag | Required | Description |
|
|
1309
|
+
|------|----------|-------------|
|
|
1310
|
+
| `--http <port>` | yes (HTTP mode) | Listen port (1-65535) |
|
|
1311
|
+
| `--host <host>` | no | Bind host (default `127.0.0.1`) |
|
|
1312
|
+
| `--root <name>=<path>` | repeatable | Deployer-configured policy store; clients see these as MCP Roots |
|
|
1313
|
+
| `--help` | no | Print usage |
|
|
1314
|
+
|
|
1315
|
+
### Roots in stdio vs HTTP mode
|
|
1316
|
+
|
|
1317
|
+
In stdio mode, your MCP client advertises its workspace folders to the server automatically via the `listRoots()` protocol. You do not need the `--root` flag at all; the server queries the client on initialize and loads each advertised root as a named policy store. If you pass `--root` to the stdio binary, the server exits at startup with an error message saying `--root` is HTTP-only. This is intentional: in stdio, the client is the authority on what is in scope.
|
|
1318
|
+
|
|
1319
|
+
Not every stdio client advertises the workspace as a root. Claude Code currently does not. If the server's working directory itself looks like a Cedar policy store (one of `schema.cedarschema`, `schema.json`, or a `policies/` directory exists), the server loads the cwd as a store named after the cwd's basename **synchronously at startup, before the transport accepts any client requests**. By the time the client can send anything (including `resources/list`), the store is already populated. This is the "user opens an MCP-enabled CLI inside their Cedar repo and expects the tools to work" path.
|
|
1320
|
+
|
|
1321
|
+
When the MCP client later advertises roots via `listRoots()` (during the initialize handshake) or via a `notifications/roots/list_changed` message, those roots **replace** the sync-loaded cwd-fallback. A client that advertises roots is stating authoritative intent. When the client advertises zero roots, the sync-loaded cwd-fallback is preserved. The server also emits `notifications/resources/list_changed` after any reconciliation pass so cache-aware clients can refresh on the rare swap.
|
|
1322
|
+
|
|
1323
|
+
In HTTP mode, there is no client workspace to negotiate with; the operator running the long-lived process is the authority. The `--root name=path` flag is how the deployer tells the server "expose this directory as a named policy store". Pass `--root` once per store. Every connected HTTP client sees the same set of roots.
|
|
1324
|
+
|
|
1325
|
+
### Endpoints
|
|
1326
|
+
|
|
1327
|
+
- `POST /mcp`: the MCP Streamable HTTP endpoint. Each session gets a unique `Mcp-Session-Id` returned on the initialize response and required on subsequent requests.
|
|
1328
|
+
- `GET /health`: returns `{ status, transport, mode, active_sessions }` JSON. Useful for liveness probes.
|
|
1329
|
+
|
|
1330
|
+
### Client configuration
|
|
1331
|
+
|
|
1332
|
+
Point any Streamable-HTTP-capable MCP client at `http://<host>:<port>/mcp`. Example (Claude Code or similar) using the SDK's `StreamableHTTPClientTransport`:
|
|
1333
|
+
|
|
1334
|
+
```ts
|
|
1335
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1336
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
1337
|
+
|
|
1338
|
+
const client = new Client({ name: "team", version: "1.0.0" }, { capabilities: {} });
|
|
1339
|
+
const transport = new StreamableHTTPClientTransport(new URL("http://cedar-mcp.internal:3000/mcp"));
|
|
1340
|
+
await client.connect(transport);
|
|
1341
|
+
```
|
|
1342
|
+
|
|
1343
|
+
### Sharing model and limitations
|
|
1344
|
+
|
|
1345
|
+
The HTTP server runs **one shared `storeManager`** across all concurrent sessions. The deployment model is "one server per policy-store set; many team clients all see the same roots." Every client connected to the same HTTP server reads the same `--root` mappings. For per-tenant isolation (different teams seeing different policy stores), deploy multiple processes behind a routing layer.
|
|
1346
|
+
|
|
1347
|
+
Each MCP session DOES get its own `McpServer` instance; protocol state (initialized, message history, sampling) is per-session as the MCP spec requires.
|
|
1348
|
+
|
|
1349
|
+
### Security
|
|
1350
|
+
|
|
1351
|
+
- Default localhost binding plus the SDK's built-in DNS rebinding protection covers the local team-dev case.
|
|
1352
|
+
- Non-localhost binding (`--host 0.0.0.0` or a public IP) is on you to secure. Recommended pattern: terminate TLS at a reverse proxy (nginx, Caddy, Cloudflare), add bearer-token or mTLS auth at that layer, forward the `POST /mcp` and `GET /health` paths to the server.
|
|
1353
|
+
- v1 ships without built-in auth or CORS. Both are deferred until real demand surfaces.
|
|
1354
|
+
- See `SECURITY.md` for the trust boundary and input validation guarantees.
|
|
1355
|
+
|
|
1356
|
+
---
|
|
1357
|
+
|
|
1358
|
+
## Setup with policy stores (MCP Roots)
|
|
1359
|
+
|
|
1360
|
+
If your Cedar policies live on disk, configure MCP roots once and the server reads them directly. No more pasting policy text into every tool call.
|
|
1361
|
+
|
|
1362
|
+
### Policy store layout
|
|
1363
|
+
|
|
1364
|
+
Each root directory must follow this structure:
|
|
1365
|
+
|
|
1366
|
+
```
|
|
1367
|
+
my-store/
|
|
1368
|
+
policies/
|
|
1369
|
+
admin.cedar
|
|
1370
|
+
editor.cedar
|
|
1371
|
+
viewer.cedar
|
|
1372
|
+
templates/
|
|
1373
|
+
viewer-access.cedar <- Cedar template with ?principal / ?resource slots (optional)
|
|
1374
|
+
template-links/
|
|
1375
|
+
alice-docs.json <- { "template_id": "...", "slot_values": { ... } } (optional)
|
|
1376
|
+
schema.cedarschema <- Cedar schema text (preferred)
|
|
1377
|
+
schema.json <- Cedar JSON schema (alternative)
|
|
1378
|
+
```
|
|
1379
|
+
|
|
1380
|
+
### Configure roots in Claude Code
|
|
1381
|
+
|
|
1382
|
+
Add roots to `.claude/settings.json`:
|
|
1383
|
+
|
|
1384
|
+
```json
|
|
1385
|
+
{
|
|
1386
|
+
"mcpServers": {
|
|
1387
|
+
"cedar": {
|
|
1388
|
+
"command": "npx",
|
|
1389
|
+
"args": ["-y", "cedar-mcp-server"],
|
|
1390
|
+
"roots": [
|
|
1391
|
+
{ "uri": "file:///path/to/production-store", "name": "production" },
|
|
1392
|
+
{ "uri": "file:///path/to/staging-store", "name": "staging" }
|
|
1393
|
+
]
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
```
|
|
1398
|
+
|
|
1399
|
+
The root name (`"production"`, `"staging"`) becomes the store identifier used in tool calls.
|
|
1400
|
+
|
|
1401
|
+
### Use `cedar://` references instead of inline text
|
|
1402
|
+
|
|
1403
|
+
Once roots are configured, use `cedar://` URIs instead of pasting policy text:
|
|
1404
|
+
|
|
1405
|
+
```
|
|
1406
|
+
cedar://policies/production <- all policies in the production store
|
|
1407
|
+
cedar://policies/production/admin <- the admin.cedar policy
|
|
1408
|
+
cedar://schema/production <- the production schema
|
|
1409
|
+
cedar://templates/production <- all templates in the production store
|
|
1410
|
+
cedar://templates/production/viewer-access <- the viewer-access template
|
|
1411
|
+
cedar://template-links/production <- all template-link IDs in the store
|
|
1412
|
+
cedar://template-links/production/alice-docs <- a specific link's metadata
|
|
1413
|
+
cedar://entities/production <- merged entity JSON across all entities/*.json files
|
|
1414
|
+
cedar://entities/production/users-and-docs <- a single entity file
|
|
1415
|
+
```
|
|
1416
|
+
|
|
1417
|
+
Both `policy_ref` and `schema_ref` accept these URIs in `cedar_validate` and `cedar_authorize`. Inline text still works; pass either form.
|
|
1418
|
+
|
|
1419
|
+
### Error when no stores are configured
|
|
1420
|
+
|
|
1421
|
+
If you call `cedar_diff_policy_stores` or use a `cedar://` reference but haven't configured roots, the error message explains the expected directory layout and how to configure roots in your MCP client settings.
|
|
1422
|
+
|
|
1423
|
+
---
|
|
1424
|
+
|
|
1425
|
+
## Coming from Amazon Verified Permissions?
|
|
1426
|
+
|
|
1427
|
+
If you're already using AVP, your entity JSON looks different from Cedar's open-source format. `cedar_authorize` detects and converts all three AVP SDK formats automatically.
|
|
1428
|
+
|
|
1429
|
+
| What you have | What's auto-detected |
|
|
1430
|
+
|---|---|
|
|
1431
|
+
| Ruby SDK (`entity_type`, `entity_id`) | snake_case AVP format |
|
|
1432
|
+
| Python / JS SDK v3 (`entityType`, `entityId`) | camelCase AVP format |
|
|
1433
|
+
| AWS console / official API (`EntityType`, `EntityId`) | PascalCase AVP format |
|
|
1434
|
+
|
|
1435
|
+
Typed attribute wrappers (`{ "string": "val" }`, `{ "long": 42 }`, `{ "boolean": true }`) are unwrapped to raw Cedar values. Entity references in attributes (`entityIdentifier`) are converted to Cedar's `__entity` format. `Set` and `Record` wrappers are unwrapped recursively.
|
|
1436
|
+
|
|
1437
|
+
The response includes `format_detected` and `format_note` telling you what was detected:
|
|
1438
|
+
|
|
1439
|
+
```json
|
|
1440
|
+
{
|
|
1441
|
+
"decision": "Allow",
|
|
1442
|
+
"format_detected": "avp",
|
|
1443
|
+
"format_note": "Entities are in AVP format. Automatically converted to Cedar format."
|
|
1444
|
+
}
|
|
1445
|
+
```
|
|
1446
|
+
|
|
1447
|
+
**Three things to know when migrating from AVP:**
|
|
1448
|
+
|
|
1449
|
+
1. Action groups are not automatic. AVP appends action group entities to the entity list for you. With this tool, include them manually in the `entities` array.
|
|
1450
|
+
|
|
1451
|
+
2. The `entities` SDK envelope is handled. If you pass the full SDK value (`{ entity_list: [...] }` or `{ entityList: [...] }`), it is automatically unwrapped.
|
|
1452
|
+
|
|
1453
|
+
3. One namespace only. AVP policy stores support a single namespace. This tool works with multi-namespace Cedar schemas, but your AVP-derived policies will only reference one.
|
|
1454
|
+
|
|
1455
|
+
### Schema and entity workflows
|
|
1456
|
+
|
|
1457
|
+
These tools are useful when you are working directly with AVP schema and entity data.
|
|
1458
|
+
|
|
1459
|
+
**`cedar_validate_schema`**: Validates a Cedar schema JSON for structural correctness before you call AVP `PutSchema`. Run this as a pre-flight check: if the schema is malformed, `cedar_validate_schema` tells you the exact error before AVP rejects it.
|
|
1460
|
+
|
|
1461
|
+
**`cedar_diff_schema`**: Computes a structural diff between two schemas and classifies each change as `safe`, `review`, or `breaking`. A `risk: breaking` change means existing policies will fail validation against the new schema. Use this when comparing schemas across two AVP policy stores, or when planning a `PutSchema` deployment and you want to know what you are changing and at what risk level before committing.
|
|
1462
|
+
|
|
1463
|
+
**`cedar_validate_entities`**: Validates an entity-store JSON against a schema. When you retrieve entities from AVP via `BatchGetPolicyStoreEntities` (or build them manually), run this before passing them to `cedar_authorize`. It catches type mismatches and missing required attributes early, so authorization failures have a clear cause.
|
|
1464
|
+
|
|
1465
|
+
### Template-linked policies
|
|
1466
|
+
|
|
1467
|
+
AVP uses template-linked policies heavily. A template has `?principal` and `?resource` slots; each link binds those slots to specific entity references to produce a concrete policy. The template body is immutable once created; only the slot bindings change per link.
|
|
1468
|
+
|
|
1469
|
+
**`cedar_validate_template`**: Validates a Cedar policy template against a schema. Detects `?principal` and `?resource` slot presence and reports schema errors. Run this before uploading a new template to AVP via `CreatePolicyTemplate`.
|
|
1470
|
+
|
|
1471
|
+
**`cedar_link_template`**: Instantiates a template by binding `?principal` and `?resource` to specific entity references. The output is a Cedar-format policy string. You can then pass that to `cedar_check_policy_change` to diff it against an existing policy, or use it as the body for an AVP `CreatePolicy` (static) call.
|
|
1472
|
+
|
|
1473
|
+
**`cedar_list_templates`**: Lists templates from the configured policy store (`templates/*.cedar` subdirectory). Useful when auditing which templates exist in a store before deploying changes.
|
|
1474
|
+
|
|
1475
|
+
**`cedar_list_template_links`**: Lists template links from the configured policy store (`template-links/*.json` subdirectory). Each link record shows which entity refs are bound to a given template. Use this to audit coverage: which principals and resources have active template-linked policies.
|
|
1476
|
+
|
|
1477
|
+
When updating a template-linked policy in AVP, the binding (slot values) can change via `UpdatePolicy`, but the template body can only change via `UpdatePolicyTemplate`, which invalidates all links. Use `cedar_check_policy_change` to verify the impact of a template body change before calling `UpdatePolicyTemplate`.
|
|
1478
|
+
|
|
1479
|
+
---
|
|
1480
|
+
|
|
1481
|
+
## Coming from cedar-cli or cedar-policy-cli?
|
|
1482
|
+
|
|
1483
|
+
The Cedar project ships a CLI (`cedar`) for one-shot policy operations. This MCP server is complementary, not a replacement.
|
|
1484
|
+
|
|
1485
|
+
| Operation | cedar CLI | cedar-mcp-server |
|
|
1486
|
+
|-----------|-----------|------------------|
|
|
1487
|
+
| Validate a policy file | `cedar validate` | `cedar_validate` |
|
|
1488
|
+
| Evaluate a request | `cedar authorize` | `cedar_authorize` |
|
|
1489
|
+
| Format a policy file | `cedar format` | `cedar_format` |
|
|
1490
|
+
| Translate policy to JSON | `cedar translate` | `cedar_translate` |
|
|
1491
|
+
| Explain a policy | (none) | `cedar_explain` |
|
|
1492
|
+
| Check if a change needs delete-recreate | (none) | `cedar_check_policy_change` |
|
|
1493
|
+
| Generate a test request payload | (none) | `cedar_generate_sample_request` |
|
|
1494
|
+
| Plan a policy change from intent | (none) | `cedar_advise` |
|
|
1495
|
+
| Diff two policy stores with AVP classification | (none) | `cedar_diff_policy_stores` |
|
|
1496
|
+
| Validate a template policy | (none) | `cedar_validate_template` |
|
|
1497
|
+
| Instantiate a template (bind slots) | (none) | `cedar_link_template` |
|
|
1498
|
+
| List templates / links in a store | (none) | `cedar_list_templates`, `cedar_list_template_links` |
|
|
1499
|
+
|
|
1500
|
+
**When to use the CLI:** one-shot scripts, CI pipelines, or shell automation.
|
|
1501
|
+
|
|
1502
|
+
**When to use this server:** conversational workflows where you want Cedar reasoning inside the same context as your question, workspace-aware operations (reading live policy files from disk via roots), or the planning and diffing tools that have no CLI equivalent.
|
|
1503
|
+
|
|
1504
|
+
A practical pattern: use `avp-cli` to pull policy stores from AVP to disk, then use this server's roots support to read them for diffing and planning.
|
|
1505
|
+
|
|
1506
|
+
---
|
|
1507
|
+
|
|
1508
|
+
## Coming fresh to Cedar?
|
|
1509
|
+
|
|
1510
|
+
Cedar is a policy language built around three entities: a **principal** (who's asking), an **action** (what they want to do), and a **resource** (what they want to do it to). Authorization is the question: does a `permit` policy match this triple, without any `forbid` policy blocking it?
|
|
1511
|
+
|
|
1512
|
+
A few things that shape how you write policies.
|
|
1513
|
+
|
|
1514
|
+
Cedar uses **default deny**: every request is denied unless a `permit` policy explicitly matches. There is no "allow by default" option.
|
|
1515
|
+
|
|
1516
|
+
A matching **`forbid`** policy overrides every `permit`. A single matching `forbid` blocks the request regardless of how many `permit` policies also match. If you need an exception to a `forbid`, use `unless`:
|
|
1517
|
+
|
|
1518
|
+
```cedar
|
|
1519
|
+
forbid (principal, action, resource)
|
|
1520
|
+
when { resource.classification == "top_secret" }
|
|
1521
|
+
unless { principal in MyApp::Role::"admin" };
|
|
1522
|
+
```
|
|
1523
|
+
|
|
1524
|
+
Namespaces are part of entity type names. `MyApp::User::"alice"` is the canonical form: the double colon separates namespace from type, and the quoted string is the entity ID.
|
|
1525
|
+
|
|
1526
|
+
Start with `cedar_advise`. Describe what you want in plain language and let the server produce a starting policy. Then validate it with `cedar_validate` and verify the decision with `cedar_authorize`.
|
|
1527
|
+
|
|
1528
|
+
The upstream [Cedar documentation](https://docs.cedarpolicy.com) and [Cedar policy patterns](https://docs.cedarpolicy.com/overview/patterns.html) are the authoritative reference.
|
|
1529
|
+
|
|
1530
|
+
---
|
|
1531
|
+
|
|
1532
|
+
## Troubleshooting
|
|
1533
|
+
|
|
1534
|
+
**"schema not found in store"**
|
|
1535
|
+
|
|
1536
|
+
The server looked for `schema.cedarschema` or `schema.json` in the root directory but found neither. Check that your policy store directory has one of these files at the root level, not inside the `policies/` subdirectory.
|
|
1537
|
+
|
|
1538
|
+
**"store X not found, no roots configured"**
|
|
1539
|
+
|
|
1540
|
+
You passed a `cedar://` reference or a store name to a tool, but no MCP roots are configured. Add a `roots` entry to your MCP client config (see [Setup with policy stores](#setup-with-policy-stores-mcp-roots)).
|
|
1541
|
+
|
|
1542
|
+
**"policy_text and policy_ref both provided"**
|
|
1543
|
+
|
|
1544
|
+
Inline text takes precedence, but passing both is probably a mistake. Remove one.
|
|
1545
|
+
|
|
1546
|
+
**"Failed to initialize Cedar WASM"**
|
|
1547
|
+
|
|
1548
|
+
The `@cedar-policy/cedar-wasm` module failed to load. This typically means the npm package is corrupted or the Node.js version is too old. Run `node --version` and verify it's 20 or higher. Delete `node_modules` and reinstall if the version is correct.
|
|
1549
|
+
|
|
1550
|
+
**"MCP client does not support sampling" (cedar_advise)**
|
|
1551
|
+
|
|
1552
|
+
`cedar_advise` requires the MCP client to support the `sampling/createMessage` capability. Claude Code and Claude Desktop support this. Some other MCP clients don't. Check your client's documentation.
|
|
1553
|
+
|
|
1554
|
+
**"Name collision: two roots share the last path segment"**
|
|
1555
|
+
|
|
1556
|
+
The server names stores after the last segment of their file path. If two roots resolve to the same name (e.g. `/path/a/policies` and `/path/b/policies`), the second one gets a numeric suffix (`policies-2`). Check your roots config and give each store a unique path.
|
|
1557
|
+
|
|
1558
|
+
---
|
|
1559
|
+
|
|
1560
|
+
## Compatibility
|
|
1561
|
+
|
|
1562
|
+
| Component | Tested version |
|
|
1563
|
+
|-----------|---------------|
|
|
1564
|
+
| Cedar | 4.11.0 |
|
|
1565
|
+
| `@cedar-policy/cedar-wasm` | 4.11.0 |
|
|
1566
|
+
| Node.js | 20.x and above |
|
|
1567
|
+
| `@modelcontextprotocol/sdk` | 1.29.0 |
|
|
1568
|
+
| MCP clients tested | Claude Code, Claude Desktop |
|
|
1569
|
+
|
|
1570
|
+
Other MCP clients that support the MCP 1.0 protocol should work with every tool. All 17 tools are deterministic; none of them require the MCP `sampling/createMessage` capability. `cedar_advise` is also deterministic in the current design (kickoff-08 pivoted it from a sampling-based planner to a structured context bundle, so the calling assistant produces the actual plan from the bundle).
|
|
1571
|
+
|
|
1572
|
+
---
|
|
1573
|
+
|
|
1574
|
+
## Known limitations
|
|
1575
|
+
|
|
1576
|
+
This server runs Cedar 4.11.0 through `@cedar-policy/cedar-wasm`. That WASM package does not expose Cedar's symbolic-analysis backend (`cedar-policy-symcc`), so the following capabilities are NOT available in this server:
|
|
1577
|
+
|
|
1578
|
+
- **Semantic equivalence between two policy sets.** "Do these two policy sets produce the same decision for every well-formed request?" `cedar_diff_policy_stores` performs a structural diff plus an optional behavioral diff over a request matrix you supply, but neither proves logical equivalence.
|
|
1579
|
+
- **Full shadowing detection.** `cedar_diff_policy_stores` and Cedar's own `validate` surface some shadowing cases, but a complete pairwise shadowing analysis (does policy A's match condition imply policy B's?) requires SMT.
|
|
1580
|
+
- **Full reachability / dead-policy analysis.** Cedar's WASM `validate` surfaces dead policies that fail trivially (schema-mismatched scopes, literal-folding unsat such as `when { 1 == 2 }`, type-incompatible expressions). It does not catch attribute-value contradictions like `when { age > 18 && age < 10 }`.
|
|
1581
|
+
- **`never-errors` verification.** Proving that a policy can never produce a runtime error.
|
|
1582
|
+
|
|
1583
|
+
Cedar's official CLI ships these as 11 verification subcommands under `cedar symcc`, but only when built with `cargo install cedar-policy-cli --features analyze` and used together with the CVC5 SMT solver. That install chain is incompatible with the `npx`-only positioning of this server, so the SMT tools are not bundled today.
|
|
1584
|
+
|
|
1585
|
+
**Future direction:** if upstream Cedar exposes `cedar-policy-symcc` through `@cedar-policy/cedar-wasm`, the equivalent tools land here directly. Otherwise a companion package (e.g. `cedar-mcp-server-analyze`) that shells out to a locally-installed `cedar symcc` is the most likely path. No timeline.
|
|
1586
|
+
|
|
1587
|
+
### Multi-store under stdio
|
|
1588
|
+
|
|
1589
|
+
Stdio mode loads at most one synchronous cwd-fallback store (named after the cwd's basename) plus any roots the MCP client advertises via `listRoots()`. Claude Code does not currently advertise the workspace as a root, so in practice stdio is single-store: the cwd. To work with multiple Cedar policy stores from one client session, two options:
|
|
1590
|
+
|
|
1591
|
+
1. Run the server in HTTP mode with one `--root name=path` flag per store, and point your client at the HTTP endpoint. All connected clients see the same multi-store set.
|
|
1592
|
+
2. Register the same `cedar-mcp-server` binary multiple times in your client's MCP configuration, each with its own working directory (or each pointing at a different cwd via the client's per-server config). Each instance becomes a separate MCP server with its own single store.
|
|
1593
|
+
|
|
1594
|
+
The pure-stdio multi-store-from-cwd model (one process, multiple stores discovered without `--root` or `listRoots()`) is not supported. Tracked for v1.1.
|
|
1595
|
+
|
|
1596
|
+
---
|
|
1597
|
+
|
|
1598
|
+
## Versioning policy
|
|
1599
|
+
|
|
1600
|
+
SemVer for v1.0+. Major versions may introduce breaking changes to tool input/output schemas. Minor versions add capabilities without breaking existing inputs. Patches are bug fixes.
|
|
1601
|
+
|
|
1602
|
+
The current version is `0.0.1` (pre-release). Pin to an exact version during the `0.x` line; breaking changes can happen between `0.x` releases.
|
|
1603
|
+
|
|
1604
|
+
---
|
|
1605
|
+
|
|
1606
|
+
## Examples
|
|
1607
|
+
|
|
1608
|
+
See [`examples/`](./examples/) for three full working scenarios with schemas, policies, entities, and copy-paste prompts:
|
|
1609
|
+
|
|
1610
|
+
- [`rbac-document-management`](./examples/rbac-document-management/): role membership, `forbid` + `unless`, default deny
|
|
1611
|
+
- [`abac-multi-tenant`](./examples/abac-multi-tenant/): attribute conditions, `contains()`, optional attribute guards, plan-tier gating
|
|
1612
|
+
- [`api-gateway-path-routing`](./examples/api-gateway-path-routing/): path matching with `like`, depth limiting via negation, method restriction
|
|
1613
|
+
|
|
1614
|
+
Each example includes a `run.ts` that exercises all tools offline without an MCP client.
|
|
1615
|
+
|
|
1616
|
+
---
|
|
1617
|
+
|
|
1618
|
+
## Contributing
|
|
1619
|
+
|
|
1620
|
+
See [`CONTRIBUTING.md`](./CONTRIBUTING.md).
|
|
1621
|
+
|
|
1622
|
+
---
|
|
1623
|
+
|
|
1624
|
+
## License
|
|
1625
|
+
|
|
1626
|
+
Apache 2.0, same as Cedar itself. See [`LICENSE`](./LICENSE).
|
|
1627
|
+
|
|
1628
|
+
---
|
|
1629
|
+
|
|
1630
|
+
## Acknowledgments
|
|
1631
|
+
|
|
1632
|
+
- [Cedar team at AWS](https://github.com/cedar-policy/cedar) for the open-source Cedar engine and the `@cedar-policy/cedar-wasm` bindings
|
|
1633
|
+
- [Amazon Verified Permissions team](https://aws.amazon.com/verified-permissions/) for the production service this server complements
|
|
1634
|
+
- [Anthropic MCP team](https://modelcontextprotocol.io) for the protocol and SDK
|
|
1635
|
+
- Built by [Daniel Aniszkiewicz](https://builder.aws.com/community/heroes/DanielAniszkiewicz), AWS Hero
|