Skip to content

logly/mureo

v0.9.23 Feature

This release adds 2 notable features for engineering teams evaluating rollout.

✓ No known CVEs patched
Read the diff → Tool health → What is this tool? →

✓ No known CVEs patched in this version

Topics

advertising agentic-ai ai-agents claude-code cli codex
+11 more
cursor facebook-ads gemini-cli google-ads marketing marketing-automation mcp meta-ads model-context-protocol python search-console

Affected surfaces

rbac

Summary

AI summary

Updates 0.9.23, Protocol, and https://github.com/logly/mureo/issues/174 across a mixed release.

Changes in this release

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:

  1. Entry-point loadimportlib.metadata.entry_points failure logs WARNING and returns empty tuple (not silent).
  2. Gate instantiationep.load() or cls() failure on a partial third-party install logs WARNING, skips that gate, others still load.
  3. Per-call evaluationgate.evaluate(...) raising any Exception or returning anything other than a PolicyDecision (e.g. None, True, a tuple from a buggy gate) is treated as abstain + WARNING; dispatch continues to the next gate. The non-PolicyDecision isinstance guard was added in round-2 code review to close a HIGH where a buggy gate returning False would have crashed _refuse_text_content downstream 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-refresh and /budget-rebalance); a new paragraph in the Learnable operational know-how section describes external advisor MCP federation via ~/.mureo/insight_sources.json and mureo_consult_advisor, with a link to docs/insight-federation.md.
  • README.ja.md — same additions mirrored in Japanese.
  • docs/mcp-server.md — opening tool count corrected from 173 to 185, with an explicit per-family breakdown and a maintenance note pointing at test_list_tools_returns_all_tools.
  • docs/architecture.md.claude/commands/ enumeration gains lead-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-create added between creative-refresh and rescue in 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

Track logly/mureo

Get notified when new releases ship.

Sign up free

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.

All releases →

Related context

Beta — feedback welcome: [email protected]