Repository navigation
chore(plugins): move cedarpolicyplugin to plugins/external/cedar - #50
reginaldalfret wants to merge 2 commits into
Conversation
Move the cedarpolicyplugin external plugin from the mcp-context-forge monorepo to plugins/external/cedar/ as part of the plugin ecosystem separation. Fixes IBM#21. Signed-off-by: reginaldalfret <reginaldalfret@gmail.com>
jonpspri
left a comment
There was a problem hiding this comment.
Review: request changes
2 of 5 acceptance criteria from #21 fail (one proven by execution). The copy is faithful to upstream (IBM/mcp-context-forge@main), sign-off is present, and the code passes its own tests and lint in a working environment — but it does not install as merged.
Acceptance criteria scorecard
| #21 criterion | Status |
|---|---|
Plugin lands at plugins/external/cedar/ |
✅ all 20 files, matches issue layout |
pip install -e plugins/external/cedar |
❌ ResolutionImpossible (proven below) |
| Tests pass | ✅ 12/12 passed, 90% coverage — but only after hand-fixing deps |
| CI covers plugin (lint + test) | ❌ no workflow added; .github/workflows/ untouched |
| README reflects new home | ❌ all URLs/instructions still point to the monorepo |
Blocking
1. Declared dependencies are mutually unsatisfiable — pyproject.toml
cedarpolicyplugin 0.1.0 depends on mcp<2 and >=1.28.1
mcp-contextforge-gateway 1.0.11 depends on mcp>=2.0.0
Gateway 1.0.8–1.0.10 require mcp<2,>=1.28.1; 1.0.11 flipped to mcp>=2.0.0 (verified via PyPI metadata). Issue #21 specified >=1.0.8; this PR took upstream's newer >=1.0.11 pin, which is exactly the version that breaks resolution. (Upstream has the same broken pin — the defect was copied, not introduced — but the acceptance criterion is explicit and the PR fails it today.)
Verified fix: with mcp-contextforge-gateway>=1.0.8,<1.0.11 the resolver picks 1.0.10, which pulls cpex 0.1.3 transitively, and pytest tests/ → 12 passed, ruff check → clean. Alternative: drop the mcp<2 pin and migrate to gateway 1.0.11 / cpex 0.1.4 (mcp 2.x) — larger, untested; since #21 says dependency review is tracked separately, the <1.0.11 pin is the minimal honest fix.
Note: cpex is imported everywhere in plugin.py/tests but never declared in dependencies — it only arrives transitively via the gateway. Works today; fragile. Worth declaring explicitly.
2. No CI — criterion 4. No workflow exists for lint+test of this plugin. Existing per-component workflows (e.g. build-fast-time-server.yml) are the established pattern to copy.
Non-blocking but should fix
3. Shipped example configs contain policies that can never match or parse:
examples/config-cedar.yamlandresources/plugins/config.yamlusePrompts::"judge_prompts"— the plugin generatesPrompt::"..."(CedarResourceTemplates.PROMPTinplugin.py). Prompt policies silently never match. The README uses the correctPrompt::.examples/config-cedar.yaml(and the README's full-config blob) useResource::""https://example.com/data""— doubled quotes; invalid Cedar entity syntax, will raise at evaluation.resources/plugins/config.yamlregisters only 4 hooks (prompt_*,tool_*) yet defines Resource/Prompt policies; resource hooks are never registered.
4. tool_post_invoke fails open where sibling hooks fail closed — plugin.py: when the tool action is Allowed but both view_full and view_redacted are Deny, it returns continue_processing=True with the unredacted result. prompt_post_fetch/resource_post_fetch return a DENY violation in the identical state. Inconsistent and security-relevant; untested path.
5. plugin-manifest.yaml is stale: lists prompt_pre_hook/tool_pre_hook-style names (actual hooks: prompt_pre_fetch, tool_pre_invoke, …), omits resource hooks, and default_configs: is null. Repo convention (plugins/python/pii_filter/plugin-manifest.yaml) uses correct names and populated defaults.
6. README defects: references .env.example (file is .env.template); references resources/config.yaml (actual: resources/plugins/config.yaml); "opa plugin" copy-paste remnants; versionaaaaa typo; "server fast-time that exposes git tools" (registers mcp-server-git); broken step numbering. Plus criterion 5: nothing points to this repo — Homepage/Repository/Bug-Tracker in pyproject.toml also still target IBM/mcp-context-forge.
7. Packaging nits: license-files = ["LICENSE"] and MANIFEST.in include LICENSE, but no LICENSE file ships (editable install survived; sdist/wheel will lack it). [tool.setuptools.package-data] references resources/plugins/config.yaml relative to the package dir — matches nothing.
8. Dead/buggy code carried over: _extract_payload_key never called; _preprocess_request's elif "is_admin" in user branch does a substring test on a str user — effectively dead, and unknown users get Role::"None" (silent deny); unused AUDIT_CODE, REQUIRES_HUMAN_APPROVAL_CODE, INVALID_CEDAR_CONFIG, cedar_context_key. Acceptable for a verbatim move; fine to defer.
Suggested path
1-line pin fix (>=1.0.8,<1.0.11) + CI workflow gets this mergeable with criteria met. Items 3–6 are small, contained, and worth fixing here rather than re-copying upstream's bugs — but if strict-verbatim is the policy, at minimum fix 1 and 2, and file follow-ups for 3–5.
Address maintainer review on PR IBM#50: - pyproject.toml: pin mcp-contextforge-gateway to '>=1.0.8,<1.0.11' to resolve dependency conflict with mcp<2, declare cpex>=0.1.3 explicitly, remove tool.uv.sources, remove conflicting pytest-env, update project URLs to contextforge-examples, and fix package-data. - LICENSE: add missing Apache 2.0 license file to plugins/external/cedar/. - plugin.py: fix fail-open behavior in tool_post_invoke when tool is Allowed but neither view_full nor view_redacted is permitted, returning a DENY violation. - tests: add regression test test_cedarpolicyplugin_post_tool_invoke_both_views_denied. - configs: correct Prompts:: to Prompt::, fix doubled quotes in Resource:: URIs, and include resource hooks. - plugin-manifest.yaml: update available_hooks with canonical names and resource hooks, populate default_configs. - schema.py: clean up leftover OPA docstring references. - README.md: fix .env.template, config.yaml paths, OPA remnants, server description, typo, and step numbering. - .github/workflows/cedar-plugin-tests.yml: add CI workflow for ruff lint and pytest. Signed-off-by: reginaldalfret <reginaldalfret@gmail.com>
jonpspri
left a comment
There was a problem hiding this comment.
Review: request changes — two small items, same defect class this commit already fixed elsewhere. Everything blocking from my previous review is resolved and proven by execution, not just by reading.
Re-verification (executed at 1acf749)
pip install -e plugins/external/cedarin a fresh Python 3.12 venv → exit 0. Resolver picks gateway 1.0.10 / cpex 0.1.3 / mcp 1.30.0 — the ResolutionImpossible conflict is gone.pytest tests/→ 13 passed, 91% coverage, including the newtest_cedarpolicyplugin_post_tool_invoke_both_views_denied, which exercises exactly the fixed branch.ruff check .→ clean.tool_post_invokefail-open fix confirmed atplugin.py:551-558(DENY violation,continue_processing=False) — now consistent with the sibling hooks.- CI:
.github/workflows/cedar-plugin-tests.ymlruns ruff + pytest on py3.12 with path filters and pinned action SHAs;pip install -e ".[dev]"covers pytest/pytest-cov/ruff. Matches the repo's per-component pattern.
Acceptance criteria (#21)
| Criterion | Status |
|---|---|
Plugin lands at plugins/external/cedar/ |
✅ |
Installable via pip install -e |
✅ proven by execution |
| Tests pass | ✅ 13/13, 91% cov |
| CI covers lint + test | ✅ |
| README reflects new home | ✅ |
Blocking — redaction silently no-ops in shipped configs (security-relevant)
1. Redaction regex ships quote-delimited. examples/config-cedar.yaml:21, examples/config-dsl.yaml:21, and README.md:105 all carry pattern: '"\$\d{1,}(,\d{1,})*"'. The YAML single-quoting preserves the literal " characters, so the compiled regex requires a quote character before the $ — it can never match currency text. A user granted only view_redacted receives full, unredacted output with no error. This commit fixed the identical doubled-quote defect in the Resource:: URIs but left it in the regex patterns in the same files (the test suite uses the correct unquoted form, which is why tests pass). Fix: pattern: "\$\d{1,}(,\d{1,})*" in all three places.
2. resources/plugins/config.yaml grants redaction it can't perform. The policy grants Role::"non-admin" only Action::"view_redacted_output" (lines 44–78), but the plugin config block omits policy_redaction_spec entirely; plugin.py:318-319 then returns the payload unchanged. The README's end-to-end walkthrough is built on this file, so following it demonstrates redaction that isn't happening. Fix: add policy_redaction_spec mirroring examples/config-cedar.yaml (with the quote fix from item 1).
Both are ~3-line edits to files this commit already touches — after those, this is an approve from me.
Non-blocking (fine as immediate follow-ups)
cedarpolicyplugin/__init__.py:22-23andContainerfile:43: 🔵IBM/mcp-context-forgeURLs survived the URL pass. Repoint at this repo.pyproject.toml:58: 🔵Changeloglinks to aCHANGELOG.mdthat doesn't exist here. Drop the entry.README.md:93: 🔵 config snippet documentsmode: "enforce" | permissive | disabled; every shipped config usesmode: "sequential" | transform | disabled. Align the snippet..ruff.toml:38,42: 🔵target-version = "py311"contradictsrequires-python = ">=3.12", andselect = ["E4","E7","E9","F"]is weaker than the sibling convention (url_to_markdown_server: py312,E W F B I N UP). Align when convenient.
Question
plugin-manifest.yaml:11-15: shipsdefault_configs(plural) with noname/kind/tags; the pinned cpex 0.1.3PluginManifestmodel requiresname,kind,tags,default_config(singular) — verified against the installed package, so this manifest fails cpex validation as shipped. It does match our ownpii_filterconvention, so I don't want it fixed in isolation: is the plural key the intended plugin-ecosystem format (and cpex's model is stale), or should both manifests move to the cpex schema in a follow-up?
Note / self-correction
- My earlier comment about
tests/pytest.ini:10(env_files) was wrong: that's a pytest-dotenv option and pytest-dotenv is in dev extras, so CI (which installs.[dev]) is fine. No change needed; only bare non-dev runs emit an unknown-option warning.
Inherited upstream smells (duplicated hook scaffolding, dead _extract_payload_key/cedar_context_key, monorepo leftovers in MANIFEST.in/.dockerignore/dev extras) remain as expected for a move — follow-up material, not PR demands, consistent with how we treated item 8 originally.
Description
Moves the
cedarpolicypluginexternal plugin from theIBM/mcp-context-forgemonorepo into this repository atplugins/external/cedar/, as part of the plugin ecosystem separation tracked in IBM/mcp-context-forge#2100.Includes:
cedarpolicyplugin/(__init__.py,plugin.py,schema.py,plugin-manifest.yaml)examples/resources/tests/pyproject.toml,Containerfile,Makefile,run-server.sh, andREADME.mdFixes #21.