This release adds 2 notable features for engineering teams evaluating rollout.
✓ No known CVEs patched in this version
Topics
+11 more
Affected surfaces
Summary
AI summaryUpdates 0.9.23, Protocol, and https://github.com/logly/mureo/issues/174 across a mixed release.
Changes in this release
| Type | Severity | Summary | CVE |
|---|---|---|---|
| Security | Medium |
Hardens policy‑gate evaluation to treat non‑`PolicyDecision` returns or exceptions as abstain and log a warning, preventing crashes from buggy gates. Hardens policy‑gate evaluation to treat non‑`PolicyDecision` returns or exceptions as abstain and log a warning, preventing crashes from buggy gates. Source: granite4.1:30b@2026-05-31-audit Confidence: low |
— |
| Feature | Medium |
Adds `mureo.core.policy.PolicyGate` extension point and `mureo.policy_gates` entry-point group. Adds `mureo.core.policy.PolicyGate` extension point and `mureo.policy_gates` entry-point group. Source: llm_adapter@2026-05-31 Confidence: high |
— |
| Performance | Low |
Loads policy gates per‑dispatch rather than at module import, enabling runtime gate addition without restart. Loads policy gates per‑dispatch rather than at module import, enabling runtime gate addition without restart. Source: granite4.1:30b@2026-05-31-audit Confidence: low |
— |
| Bugfix | Medium |
Corrects documentation drift across README, README.ja.md, docs/mcp-server.md, docs/architecture.md, and getting‑started files after versions 0.9.18–0.9.21. Corrects documentation drift across README, README.ja.md, docs/mcp-server.md, docs/architecture.md, and getting‑started files after versions 0.9.18–0.9.21. Source: granite4.1:30b@2026-05-31-audit Confidence: low |
— |
| Refactor | Low |
Refactors `handle_call_tool` in `mureo/mcp/server.py` to consult all registered policy gates before dispatching tool calls. Refactors `handle_call_tool` in `mureo/mcp/server.py` to consult all registered policy gates before dispatching tool calls. Source: granite4.1:30b@2026-05-31-audit Confidence: low |
— |
Full changelog
v0.9.23 ships the OSS extension point that enables mureo-agency to build a paid read-only mode for ad-platform mutations. Because v0.9.22 (a docs-drift cleanup) was merged but never independently released, this release bundles both. PyPI users upgrading from v0.9.21 will see one release on PyPI that delivers both changes.
Added — mureo.core.policy.PolicyGate extension point + mureo.policy_gates entry-point group (0.9.23)
mureo OSS gains a small generic policy-gate extension point. Third-party packages (for example mureo-agency, which is building a paid read-only mode that blocks ad-platform mutations) can register a PolicyGate implementation against the new mureo.policy_gates entry-point group, and mureo's MCP server consults every registered gate before dispatching each tool call. mureo OSS itself ships zero gates, so the default behaviour is byte-identical to v0.9.22 — every call dispatches normally with zero policy overhead.
The OSS surface is intentionally tiny: a PolicyGate Protocol, a PolicyDecision frozen dataclass, the POLICY_GATES_ENTRY_POINT_GROUP constant, and the dispatcher integration. The policy logic — what to allow, what to block, how to detect read-only-safe tools on external MCPs, the bundled catalog of safe tools per provider, the CLI surface, the ~/.mureo/config.json schema — all lives outside OSS, in the third-party package. This keeps mureo focused on being the orchestration layer and lets commercial / agency extensions differentiate without forking.
# mureo/core/policy.py
@dataclass(frozen=True)
class PolicyDecision:
allowed: bool
reason: str = "" # surfaced verbatim to the agent when allowed=False
@runtime_checkable
class PolicyGate(Protocol):
def evaluate(
self, tool_name: str, arguments: dict[str, Any]
) -> PolicyDecision: ...
POLICY_GATES_ENTRY_POINT_GROUP = "mureo.policy_gates"
Registration via the entry-point group:
[project.entry-points."mureo.policy_gates"]
read_only = "mureo_agency.policy:ReadOnlyGate"
handle_call_tool in mureo/mcp/server.py calls _evaluate_policy_gates(name, arguments) before any per-family dispatch. If any gate returns allowed=False, a TextContent refusal is returned that surfaces the gate's reason verbatim, and the per-family handler is never invoked.
Three layers of fault isolation, each tested:
- Entry-point load —
importlib.metadata.entry_pointsfailure logs WARNING and returns empty tuple (not silent). - Gate instantiation —
ep.load()orcls()failure on a partial third-party install logs WARNING, skips that gate, others still load. - Per-call evaluation —
gate.evaluate(...)raising anyExceptionor returning anything other than aPolicyDecision(e.g.None,True, a tuple from a buggy gate) is treated as abstain + WARNING; dispatch continues to the next gate. The non-PolicyDecisionisinstanceguard was added in round-2 code review to close a HIGH where a buggy gate returningFalsewould have crashed_refuse_text_contentdownstream with no surrounding try/except.
_load_policy_gates is called per dispatch rather than cached at module-import time so a (rare) at-runtime install/uninstall of a third-party gate is picked up without a server restart. importlib.metadata.entry_points is itself cached internally, so the per-call cost is microseconds. Each gate is instantiated fresh per call; implementors needing TTL caches put state on class or module level (documented explicitly in the PolicyGate docstring).
The refusal payload deliberately echoes the tool name and the gate's reason but not the arguments dict — arguments routinely contain account IDs, budget figures, or credentials, and the agent already has them. Pinned by test_refusal_does_not_echo_arguments with sentinel key/value.
docs/ABI-stability.md §6 expanded from 3 to 4 entry-point groups; the full contract for mureo.policy_gates is documented: stability promise (MAY add fields to PolicyDecision, MUST NOT remove or rename existing ones; SHOULD use kwargs); evaluation order unspecified; buggy-return discipline; refusal-payload boundary; zero-gates byte-identical default.
The matching mureo-agency issue (logly/mureo-agency#6) builds the actual read-only mode on top of this hook: a ReadOnlyGate for mureo's own MCP tool surface (using existing mureo.mcp.plugin_semantics.derive_semantics to identify mutating calls, with rollback_apply / rollback_plan_get exempted), a bundled catalog of read-only tools for four initial providers (Google Ads / Meta Ads / GA4 / Amazon Ads official MCPs), a PreToolUse Claude Code hook that screens calls to external MCPs (since mureo cannot intercept those from its own dispatcher), and the mureo-agency readonly enable/disable/status CLI. Detection on external MCPs prefers the MCP annotations.readOnlyHint standard field over name heuristics, falling back to the bundled catalog and then to a user override file; unknown tools are refused fail-closed.
OSS users who do not install mureo-agency see no behaviour change. The OSS extension point alone does not enable read-only mode for anyone.
Closes #174.
Docs — drift fixes after v0.9.18–v0.9.21 (0.9.22)
A parallel English + Japanese documentation audit after v0.9.21 surfaced six drift sites accumulated across v0.9.18 (mureo_learning_insights_get), v0.9.19 (mureo_consult_advisor + insight federation), v0.9.20 (consult-advisor reframe + skill embedding), and v0.9.21 (lead-form-create skill). This release applies the corrections in one go so the README and key reference docs reflect the shipped surface again.
- README.md — workflow-commands table gains
/lead-form-create(between/creative-refreshand/budget-rebalance); a new paragraph in the Learnable operational know-how section describes external advisor MCP federation via~/.mureo/insight_sources.jsonandmureo_consult_advisor, with a link todocs/insight-federation.md. - README.ja.md — same additions mirrored in Japanese.
- docs/mcp-server.md — opening tool count corrected from
173to185, with an explicit per-family breakdown and a maintenance note pointing attest_list_tools_returns_all_tools. - docs/architecture.md —
.claude/commands/enumeration gainslead-form-create.md. - docs/getting-started.md + docs/getting-started.ja.md — operational-skill count bumped 10 → 11 in both the host-comparison table and the manual claude.ai upload list;
lead-form-createadded betweencreative-refreshandrescuein the upload sequence.
No tool / handler / schema changes — purely documentation.
Closes #172.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About logly/mureo
Framework for AI agents (Claude Code, Cursor, Codex, Gemini) to operate Google Ads, Meta Ads, and Search Console. Grounded in a local STRATEGY.md — not metric-chasing. Defense-in-depth security, local-first. Apache 2.0.
Related context
Related tools
Beta — feedback welcome: [email protected]