Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ mureo/
│ ├── _handlers_search_console.py # Search Console handlers
│ ├── tools_rollback.py # rollback_plan_get / rollback_apply
│ ├── _handlers_rollback.py # Rollback handlers (lazy-resolve dispatcher)
│ ├── tools_batch.py # mureo_batch_begin / _end / _status (#549)
│ ├── _handlers_batch.py # Batch lifecycle handlers
│ ├── tools_analysis.py # analysis_anomalies_check / analysis_exclusion_impact_preview
│ ├── _handlers_analysis.py # Anomaly detector composition handler
│ ├── _handlers_exclusion_impact.py # analysis_exclusion_impact_preview handler (#547)
Expand Down Expand Up @@ -124,7 +126,8 @@ mureo/
├── context/ # File-based strategy context (no DB)
│ ├── strategy.py # STRATEGY.md parser/writer
│ ├── state.py # STATE.json parser/writer
│ ├── models.py # StrategyEntry, StateDocument, CampaignSnapshot, ActionLogEntry (rollback_of)
│ ├── models.py # StrategyEntry, StateDocument, CampaignSnapshot, ActionLogEntry (rollback_of, batch_id), BatchRecord
│ ├── batch.py # Batch id minting + the action_log stamping rule (#549)
│ └── errors.py # Context-specific errors
├── analysis/ # Analysis utilities
│ ├── lp_analyzer.py # Landing page analyzer
Expand All @@ -137,8 +140,9 @@ mureo/
│ │ └── surfaces.py # Which tools are exclusion surfaces (mureo's + plugin-registered)
│ └── tracking/ # Tracking-parameter consistency: platform-neutral detector + per-platform URL accessors (#550)
├── rollback/ # Rollback feature (allow-list gated, append-only audit trail)
│ ├── models.py # RollbackStatus enum + RollbackPlan dataclass
│ ├── models.py # RollbackStatus / RollbackPlan + batch verdicts (BatchCoverage, BatchRollbackPlan)
│ ├── planner.py # plan_rollback(ActionLogEntry) -> RollbackPlan | None
│ ├── batch.py # plan_batch_rollback(doc, batch_id) -> every member, gaps included (#549)
│ └── executor.py # execute_rollback(...) -> appends ActionLogEntry(rollback_of=index)
├── adapters/ # Provider adapters wrapping each ad-platform client as a registry Protocol
├── analytics/ # Analytics-module registry for external MCP / plugin platforms (#120)
Expand Down Expand Up @@ -239,6 +243,7 @@ These families are not tied to a single ad platform. Tool names are the exact MC
|--------|-------|
| Analytics Registry (#440) | `mureo_analytics_modules_list`, `mureo_analytics_run` |
| Rollback | `rollback_plan_get`, `rollback_apply` |
| Batch (#549) | `mureo_batch_begin`, `mureo_batch_end`, `mureo_batch_status` |
| Analysis | `analysis_anomalies_check`, `analysis_exclusion_impact_preview` |
| Creative Studio | `creative_studio_providers_list`, `creative_studio_generate_visual`, `creative_studio_edit_visual`, `creative_studio_compose`, `creative_studio_brand_kit_get` |
| Learning | `mureo_learning_insights_get`, `mureo_consult_advisor` |
Expand Down
64 changes: 62 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]


### Added

- **A bulk change is one revertible unit** (#549). mureo had rollback, but it
reasoned about one allow-listed operation at a time, so "undo what I did on
Monday" was not expressible: after a bulk pass the operator had to work out
by hand which entries a change set contained. An unverifiable revert is
nearly as bad as no revert — it leaves the operator unable to rule their own
fix out as a variable.

- `mureo_batch_begin` / `mureo_batch_end` / `mureo_batch_status` declare the
boundary of a change set. A bulk pass is many tool calls and nothing in a
single call says which others belong with it, so the boundary is declared
rather than guessed at from timing or target.
- Membership is stamped at the one place every recording path already
converges (`append_action_log`), not through tool arguments. That is what
makes it platform-agnostic with no per-platform code and no ABI change: a
native Google/Meta status toggle, a mutation an agent records for a hosted
connector, and a bridged/plugin tool call mureo promotes all join the same
batch — including tools whose input schemas mureo does not own.
- `rollback_plan_get` accepts `batch_id` and returns a plan covering **every**
member, with `coverage` (`full` / `partial` / `none` / `empty`), the same
verdict **per platform**, per-member reversibility, the reason each
irreversible member cannot be reversed, and an `apply_order`. Reversibility
is not uniform across platforms, and a plan that listed only the reversible
members would read as a complete revert; a batch where 60 of 80 members can
be restored says so before anything is applied.
- Each member is classified by the existing `plan_rollback` allow-list, so
grouping loosens no guarantee. Reversals appended by `rollback_apply` never
join an open batch — otherwise reverting a batch would grow it.

Honest limits, stated in the docs rather than smoothed over: native
mutations other than status toggles (budget, keywords, exclusions) join a
batch only when the agent records them with `mureo_state_action_log_append`;
a bridged/plugin reversal is executed only when it names a registered plugin
tool, and is otherwise reported `irreversible`; hosted-connector members are
never reversed by mureo, so their plan is an accurate manual checklist; and
Search Console mutations are not recorded in `action_log` at all, so they
cannot join a batch today.

Membership is validated, not trusted. An explicit `batch_id` on
`mureo_state_action_log_append` must name a batch that was actually declared
and is still open; an unknown id is refused, and so is a closed one. The
check lives at the `append_action_log` choke point, so no caller — handler,
library user or future recorder — can conjure a change set or grow one whose
membership `mureo_batch_end` already reported.

A forgotten `mureo_batch_end` announces itself. After 24 hours open,
`mureo_batch_status` returns a warning and one is appended to every mutating
tool result, so the agent that forgot is told without having to ask. Nothing
is ever closed automatically: a timeout would trade a visible wrong answer
for an invisible one.

STATE.json gains an optional `batches` array and an optional `batch_id` on
each `action_log` entry, both emitted only when present — an existing
STATE.json parses unchanged and gains no new key on the next write.


- **A bulk exclusion now says how much of your delivery it removes, before it
is applied — and a threshold in `STRATEGY.md` can refuse it** (#547). mureo
would happily apply an exclusion / block / negative-keyword batch without
Expand Down Expand Up @@ -145,6 +199,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- `_resolve_path`, the workspace sandbox boundary shared by the STATE.json /
STRATEGY.md tools, moved from `mureo/mcp/_handlers_mureo_context.py` to
`mureo/mcp/_helpers.py` as `resolve_workspace_path`. It is a security check;
a sibling module reaching into another handler's privates to borrow it —
or copying it — is a place for the two to drift.


- **The rollback planner's destructive-verb net now has one explicit,
bounded exemption** (#544). That net refuses to plan any reversal whose
operation name contains `_remove` / `_delete` / …, which is the right
Expand Down Expand Up @@ -2702,7 +2763,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- READMEs now link the commercial editions (mureo.jp): the cloud-hosted
service and the local Agency edition. (#380)


## [0.10.19] - 2026-07-09

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ STATE.jsonから接続媒体を検出:

### MCP サーバーとツール一覧

mureo は **213 の MCP ツール** を stdio で公開します。Google広告(89)、Meta広告(90)、Search Console(10)に加え、rollback、異常検知、除外の配信インパクトプレビュー、トラッキングパラメータ整合性、戦略と状態のコンテキスト、分析モジュールレジストリ、学習、学習期間リセットのプリフライト、Creative Studio を含みます。Amazon 広告を設定している場合は、ローカルのマニフェストからブリッジされた Amazon のツールがこれに加わります(ツール名も本数も Amazon 側のもので、mureo が定義するものではありません。詳細は [docs/amazon-ads.ja.md](docs/amazon-ads.ja.md))。MCP 対応クライアントなら何からでも接続できます。
mureo は **216 の MCP ツール** を stdio で公開します。Google広告(89)、Meta広告(90)、Search Console(10)に加え、rollback、バッチ(一括変更を1つの取り消し単位にまとめる)、異常検知、除外の配信インパクトプレビュー、トラッキングパラメータ整合性、戦略と状態のコンテキスト、分析モジュールレジストリ、学習、学習期間リセットのプリフライト、Creative Studio を含みます。Amazon 広告を設定している場合は、ローカルのマニフェストからブリッジされた Amazon のツールがこれに加わります(ツール名も本数も Amazon 側のもので、mureo が定義するものではありません。詳細は [docs/amazon-ads.ja.md](docs/amazon-ads.ja.md))。MCP 対応クライアントなら何からでも接続できます。

```json
{
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ Why this matters: `link_click` vs `pixel_lead` optimization is a tracking distin

### MCP server & tool list

mureo exposes **213 MCP tools** over stdio: Google Ads (89), Meta Ads (90), Search Console (10), plus rollback, anomaly detection, exclusion delivery-impact preview, tracking-parameter consistency, strategy/state context, analytics registry, learning, learning-period reset pre-flight, and Creative Studio. When Amazon Ads is configured, the bridged Amazon tools are added on top from the local manifest (their names and count are Amazon's, not mureo's — see [docs/amazon-ads.md](docs/amazon-ads.md)). Any MCP-compatible client can connect:
mureo exposes **216 MCP tools** over stdio: Google Ads (89), Meta Ads (90), Search Console (10), plus rollback, batch (group a bulk change into one revertible unit), anomaly detection, exclusion delivery-impact preview, tracking-parameter consistency, strategy/state context, analytics registry, learning, learning-period reset pre-flight, and Creative Studio. When Amazon Ads is configured, the bridged Amazon tools are added on top from the local manifest (their names and count are Amazon's, not mureo's — see [docs/amazon-ads.md](docs/amazon-ads.md)). Any MCP-compatible client can connect:

```json
{
Expand Down
12 changes: 9 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,14 @@ mureo/
│ ├── convention.py # Opt-in '## Tracking Convention' section of STRATEGY.md
│ └── sources.py # One thin accessor per platform ('this ad's destination URLs')
├── rollback/ # Rollback feature (allow-list gated, append-only)
│ ├── models.py # RollbackStatus enum + RollbackPlan dataclass
│ ├── models.py # RollbackStatus / RollbackPlan + batch verdicts (BatchCoverage, BatchRollbackPlan)
│ ├── planner.py # plan_rollback(ActionLogEntry) -> RollbackPlan | None
│ ├── batch.py # plan_batch_rollback(doc, batch_id) -> every member, gaps included (#549)
│ └── executor.py # execute_rollback(...) -> appends ActionLogEntry(rollback_of=index)
├── context/ # File-based context (STRATEGY.md, STATE.json)
│ ├── models.py # Immutable dataclasses (ActionLogEntry.rollback_of for audit trail)
│ ├── models.py # Immutable dataclasses (ActionLogEntry.rollback_of / .batch_id, BatchRecord)
│ ├── strategy.py # STRATEGY.md parser / renderer
│ ├── batch.py # Batch id minting, the stamping rule, membership queries (#549)
│ ├── state.py # STATE.json read / mutate / atomic write + state lock (re-exports the two below)
│ ├── state_codec.py # STATE.json <-> StateDocument codec (parse_state / render_state)
│ ├── conversion_overrides.py # Per-account conversion action_type override lookup (#342)
Expand Down Expand Up @@ -182,6 +184,8 @@ mureo/
│ ├── _handlers_search_console.py # Search Console handlers
│ ├── tools_rollback.py # rollback_plan_get / rollback_apply
│ ├── _handlers_rollback.py # Rollback handlers (lazy-resolve dispatcher)
│ ├── tools_batch.py # mureo_batch_begin / _end / _status
│ ├── _handlers_batch.py # Batch lifecycle handlers
│ ├── tools_analysis.py # analysis_anomalies_check / analysis_tracking_consistency_check
│ ├── _handlers_analysis.py # Anomaly detector composition handler
│ ├── _handlers_tracking.py # Tracking-parameter consistency handler (#550)
Expand Down Expand Up @@ -275,6 +279,8 @@ mureo assumes the caller is an AI agent susceptible to prompt injection, not a t
4. **Tracking-parameter consistency** — `mureo/analysis/tracking/` detects ads whose final-URL tracking parameters disagree with the campaign they live in: a silent defect, because delivery and spend look healthy while the analytics everyone downstream trusts is quietly wrong. The detector is platform-neutral (it sees only `AdTrackingRecord`) with one thin accessor per platform, and it derives its verdict from evidence already in the account — never from a guessed naming convention. Operator intent is declared in STRATEGY.md's `## Tracking Convention` and parsed by mureo, not interpreted by the agent. Exposed as `analysis_tracking_consistency_check`, used by `/tracking-health` for the account audit and as a pre-flight before ads are created. See [tracking-consistency.md](tracking-consistency.md) for the exhaustive list of what it cannot detect.
5. **Rollback with allow-list gating** — `mureo/rollback/` turns agent-authored `reversible_params` hints into concrete `RollbackPlan` records. `reversible_params` is untrusted input for the rollback executor, so the planner enforces an explicit allow-list of operations (budget update + status toggles across Google/Meta Ads), refuses destructive verbs (`.delete` / `.remove` / `.destroy` / `.purge` / `.transfer`), and rejects unexpected parameter keys — a compromised agent cannot smuggle a privileged call through the rollback path. The `mureo rollback list` / `show` CLI commands are inspection-only; execution stays with the MCP dispatcher so it re-enters the same policy gate as forward actions, and control characters from STATE.json are stripped before terminal output to prevent ANSI-escape spoofing.

A bulk change is planned as **one unit** (#549): `mureo_batch_begin` / `mureo_batch_end` declare the boundary, every `action_log` entry written in between is stamped with the batch id at the single `append_action_log` choke point (so native, hosted-connector and bridged/plugin recordings all join without any per-platform code), and `rollback_plan_get` takes that id and classifies **every** member. Coverage is reported overall and per platform as `full` / `partial` / `none` — because reversibility is not uniform across platforms, and a plan that quietly omitted the members mureo cannot reverse would read as a complete revert. The same allow-list decides each member, so nothing about the guarantee is loosened by grouping.

See [SECURITY.md](../SECURITY.md) for the full threat model.

## Mixin Architecture
Expand Down Expand Up @@ -456,7 +462,7 @@ The Amazon bridge is the reason mureo sits in the request path rather than letti

## Command-Based Workflow System

In addition to the 213 individual MCP tools, mureo provides **workflow commands** as Claude Code native slash skills (deployed to `~/.claude/skills/`). These commands are **platform-agnostic orchestration instructions** that guide the AI agent to discover platforms, select tools, and synthesize cross-platform insights — all driven by the strategy context in `STRATEGY.md`.
In addition to the 216 individual MCP tools, mureo provides **workflow commands** as Claude Code native slash skills (deployed to `~/.claude/skills/`). These commands are **platform-agnostic orchestration instructions** that guide the AI agent to discover platforms, select tools, and synthesize cross-platform insights — all driven by the strategy context in `STRATEGY.md`.

### How It Works

Expand Down
4 changes: 4 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ Agent: rollback_apply({index: 0, confirm: true}) → dispatches.

`confirm` must be the literal boolean `true` (truthy non-booleans are refused). On success the executor appends a new log entry tagged `rollback_of=<index>`; a second apply of the same index is refused. `state_file` resolves strictly inside the MCP server's current working directory — `..`-traversal and symlink escape are refused so an attacker-crafted `STATE.json` elsewhere on disk cannot be used as the reversal source.

### Reverting a whole bulk change

A bulk pass wrapped in a batch (`mureo_batch_begin` / `mureo_batch_end`) is planned as one unit by `rollback_plan_get` with `batch_id` instead of `index` — it reports every member, overall and per-platform coverage (`full` / `partial` / `none`), and the reason each member it cannot reverse. That surface is **MCP-only**: `mureo rollback list` / `show` still work entry by entry, and neither the batch tools nor batch planning has a CLI command today. Ask the agent for the batch plan before applying anything; a batch where only some members can be restored will say so there.

## BYOD Commands (Bring Your Own Data)

Analyse your ad-account data locally without OAuth or a developer token. The importer accepts a single XLSX produced by either the mureo Google Ads Script (`scripts/sheet-template/google-ads-script.js`) or a Meta Ads Manager Saved Report. Activated automatically when `~/.mureo/byod/manifest.json` registers a platform — no `--byod` flag exists. Adapter dispatch is by workbook header signature, so no `--google-ads / --meta-ads` flags on `import` are needed. See [`docs/byod.md`](byod.md) for the full walkthrough.
Expand Down
Loading