Releases: logly/mureo
v0.9.9 — analytics module for external-integration platforms
Per-platform analytics module surface for external-integration platforms (#120)
External-integration platforms — official MCPs and third-party plugins alike — now have an opt-in path to mureo's deep analytics on the same terms as the built-in google_ads / meta_ads adapters. Workflow skills (daily-check, rescue, …) consult a new registry and either run the platform's analytics module or honestly report analytics_not_available_for_<platform>. Auto-deriving heuristics from tool schemas is explicitly rejected — it would fabricate plausible-but-wrong analysis and violate mureo's trustworthiness principle.
New public surface in mureo.analytics
- Protocol —
AnalyticsModule(runtime_checkable, opt-in) with four methods:detect_anomalies,diagnose_performance,audit_creative,analyze_budget_efficiency. Modules advertise their actually-supported subset viacapabilities(); un-advertised methods raiseNotImplementedError. Discovery validates structurally and detects un-overridden Protocol stubs by qualified name. - Capability enum —
AnalyticsCapability(DETECT_ANOMALIES,DIAGNOSE_PERFORMANCE,AUDIT_CREATIVE,ANALYZE_BUDGET_EFFICIENCY). - Frozen-dataclass models —
Anomaly,AnomalySeverity,PerformanceDiagnosis(with optionalper_campaign_metricsfor DEEP scope),PerformanceScope,CreativeAudit(withfindings+per_campaign_summary),CreativeFinding(withcampaign_id),BudgetEfficiency. - Registry — entry-point group
mureo.analytics, independent ofmureo.providers/mureo.skills. Plugin packages register one class via[project.entry-points."mureo.analytics"]. Built-in adapters auto-register; broken plugins are skipped withAnalyticsModuleWarningand never crash the MCP server. - MCP tool —
mureo_analytics_modules_listreturns the registered platforms + advertised capabilities + source distribution so skills can branch dynamically. - TypedDicts —
GoogleLivePerformanceRow,GoogleByodPerformanceRow,GoogleMetricsDict,GooglePerformanceRow,MetaLivePerformanceRow,MetaByodPerformanceRow,MetaActionEntry,MetaPerformanceRow,GoogleAdRow,MetaAdRow— re-exported for plugin-side typing. Alltotal=False; field-set contract documented indocs/ABI-stability.md§4a.
Built-in adapters wired against live + BYOD clients
google_adsandmeta_adsadvertise all four capabilities.detect_anomaliesruns per-campaign fan-out — single-campaign anomalies are no longer masked by offsetting movements at the aggregate.diagnose_performancereturns aggregate metrics by default and per-campaign drilldown (sorted by spend descending, one finding per campaign with spend / CV / CPA) atPerformanceScope.DEEP.audit_creativechecks RSA / RDA / Meta ad shape against Google's Ad-Strength thresholds and Meta's creative requirements, stampscampaign_idon every finding, and exposes a sortedCreativeAudit.per_campaign_summary.analyze_budget_efficiencynormalisesconversions/costacross campaigns and emits a concrete reallocation suggestion when the spread is wide.- Lazy auth + BYOD routing; missing credentials in live mode produce sentinel responses (empty anomaly tuple, sentinel
PerformanceDiagnosisheadline). Tolerates both live row shape and BYOD flat shape — regression tests pin both.
Skill integration
daily-check, rescue, _mureo-shared updated to consult mureo_analytics_modules_list before running deep diagnostics on any external-integration platform.
Docs
docs/plugin-authoring.md§14 "Shipping analytics with your plugin"docs/ABI-stability.md§4a (Protocol contract) + §6 (entry-point groupmureo.analytics) + TypedDict field-set rules
PRs in this release
- #137 — Protocol + registry + skill integration + docs
- #138 — Live client wiring + BYOD shape fix
- #139 — Per-campaign fan-out +
audit_creative+analyze_budget_efficiency - #140 — Per-campaign drilldown for
audit_creative - #141 — DEEP scope drilldown for
diagnose_performance+ TypedDicts - #142 — Release commit
Closes #120.
v0.9.8 — Meta Ads period silent-fallback fix (#134)
Fixed — Meta Ads period argument no longer silently falls back to last_7d (#134, #135)
The Meta Ads MCP tools' period argument advertised last_14d, last_90d, and explicit YYYY-MM-DD..YYYY-MM-DD ranges, but the implementation accepted only six hard-coded preset names and silently returned last_7d data for anything else. Period-over-period analyses (meta_ads_analysis_performance, meta_ads_analysis_cost) doubled the bug: the "previous" window was likewise mapped via a tiny dict that, for last_7d, returned last_30d — a superset that overlaps the current window, making every delta meaningless.
This release wires the full advertised surface through to the Meta Graph API:
- New
mureo/meta_ads/_period.py:resolve_period(period)returns either("date_preset", str)or("time_range", (since, until)). Unknown values raiseValueError— there is no silent fallback. ISO date validation, ordering, and..separator counting all surface clear errors at the boundary. get_performance_reportandget_breakdown_reportnow build their request params via the resolver, so aYYYY-MM-DD..YYYY-MM-DDperiodis forwarded as Meta'stime_range, andlast_14d/last_90dare forwarded asdate_preset(instead of being silently downgraded).previous_period(period, *, today=…)returns a same-length window that sits immediately before the current window. Forlast_7dthe previous block is the 7 days before that — never thelast_30dsuperset.this_monthround-trips tolast_month;last_monthreturns an explicit calendar-month range so callers don't need to do calendar arithmetic themselves.AnalysisMixin.investigate_costandInsightsMixin.analyze_performanceboth use the new helper, so their current/previous comparison is correct for every acceptedperiodshape (including custom date ranges)._PERIOD_PARAMtool description lists every preset the resolver accepts (this_month/last_monthadded) and clarifies that custom ranges are inclusive at both endpoints.
Backwards compatibility: callers relying on the silent last_7d fallback now see a ValueError. This is the bug being fixed — they were always getting the wrong window. Every previously-working preset continues to work, plus the previously-advertised-but-broken last_14d, last_90d, and YYYY-MM-DD..YYYY-MM-DD shapes.
Install
pip install --upgrade mureo
v0.9.7 — self-describing providers via account_credential_fields
Added — Optional account_credential_fields for self-describing providers (#131)
Provider plugins (built-in google_ads / meta_ads, and third-party plugins discovered via the mureo.providers entry-point group) can now declare an optional account_credential_fields: tuple[AccountCredentialField, ...] class attribute so introspection tooling — the mureo providers … CLI, configuration wizards, plugin authoring guides — can render setup prompts, validate config, and document plugins without hardcoding per-provider knowledge.
from mureo.core.providers import AccountCredentialField
class MyAdsProvider:
name = "my_ads"
display_name = "My Ads"
capabilities = frozenset({...})
account_credential_fields = (
AccountCredentialField(
key="advertiser_id",
display_name="Advertiser ID",
placeholder="adv-12345",
required=True,
description="From the MyAds dashboard.",
),
)- New public surface in
mureo.core.providers:AccountCredentialField(frozen dataclass —key,display_name,placeholder="",required=False,description="") andget_account_credential_fields(provider) -> tuple[AccountCredentialField, ...]accessor. The accessor reads the optional attribute defensively (returns()when absent) and validates the shape (tupleofAccountCredentialFieldonly) — malformed declarations raiseTypeErrorat introspection time, not deep inside the consuming UI. BaseProviderProtocol is unchanged. The new attribute is documented as optional in theBaseProviderdocstring; the Protocol body itself stays stable so every pre-feature plugin keeps loading without modification.- Built-in adapters updated:
mureo.adapters.google_ads.GoogleAdsAdapterdeclarescustomer_id;mureo.adapters.meta_ads.MetaAdsAdapterdeclaresad_account_id. Operator-shared credentials (developer token, app secret, refresh token, MCClogin_customer_id) intentionally do NOT appear — those belong to a separate operator-level layer. - Plugin author guide:
docs/plugin-authoring.md§3 (BaseProvider) gains a Declaring per-account credential fields (optional) subsection covering the dataclass shape, defaults, and the accessor's defensive-read / strict-validation semantics.
Backward compatibility: providers that do not declare account_credential_fields continue to load unchanged; get_account_credential_fields() returns (), which downstream tooling treats as "no per-account configuration needed."
Install
pip install --upgrade mureo
v0.9.6 — optional per-locale labels for web-extension nav tabs
Added — Optional per-locale labels for web-extension nav tabs (#129)
Web extensions can now ship an optional display_name_i18n: Mapping[str, str] class attribute alongside display_name so the configure-UI nav tab follows the active locale. Built-in nav tabs (Setup / Demo / BYOD / Danger Zone) are already translated via data-i18n keys in i18n.json; extension tabs now follow the same convention without extension authors having to touch the OSS i18n.json catalog.
class MyExtension:
name = "acme-vault"
display_name = "Acme Vault setup" # fallback for any locale
display_name_i18n = {
"en": "Acme Vault",
"ja": "Acme Vault 設定",
}Lookup priority on the renderer side: display_name_i18n[active_locale] → display_name_i18n["en"] → display_name. Operators who toggle the configure-UI locale see your tab name update without a page reload — the renderer listens for the existing mureo:locale_changed event.
mureo.web.extensions—WebExtensionEntrygains adisplay_name_i18n: Mapping[str, str]field that defaults to{}so existing constructors continue to work unchanged. TheWebExtensionProtocol is unchanged — the new attribute is read defensively viagetattrso every pre-feature extension keeps loading without modification. Discovery validates the value asMapping[str, str](strkeys and values both required) and skips the extension with aWebExtensionWarningif the shape is wrong (explicitNone, list of pairs,intkeys,intvalues).- HTTP —
GET /api/extensionsincludes a newdisplay_name_i18nfield per entry (empty{}when the extension did not declare any). JSON-only addition; existing consumers ignore unknown keys. - Front-end (
mureo/_data/web/extensions.js) — initial render readsdocument.documentElement.langand looks updisplay_name_i18n[locale]with the fallback chain above. Amureo:locale_changedlistener (fired byapp.js#setLocale) re-runs the lookup so every nav label updates the moment the operator toggles 日本語 / English. - Plugin author docs —
docs/plugin-authoring.md§13 gains a Localising the nav-tab label subsection with the example class attribute, the documented lookup priority, and the empty-string fallthrough note.
Security: labels are injected via textContent (never innerHTML), so a label containing <script> or similar is harmless DOM text. The configure-UI Content-Security-Policy (script-src 'self'; style-src 'self') is unchanged.
Backward compatibility: extensions that do not declare display_name_i18n get an empty dict in their WebExtensionEntry; the renderer's fallback chain resolves to display_name, so the nav tab looks byte-identical to v0.9.5.
Install
pip install --upgrade mureo
v0.9.5 — web extensions for the configure UI
Added — Web extensions: third-party tabs and API routes for mureo configure (#127)
A new entry-point group mureo.web_extensions lets a plugin register additional tabs and API routes inside the mureo configure wizard without each surface having to know about the plugin. The mechanism mirrors the existing mureo.providers / mureo.runtime_context_factory entry-point patterns: discovery iterates the group exactly once at startup, isolates per-plugin faults (WebExtensionWarning), and exposes survivors as frozen WebExtensionEntry records consumed by mureo.web.handlers.
mureo.web.extensions— public surface:WebExtensionProtocol (name,display_name,routes(),view()), frozen dataclassesRouteContribution(method, subpath, handler),ViewContribution(html_fragment, scripts, styles),StaticAsset(filename, content_type, body), plusdiscover_web_extensions()/reset_web_extensions()and the regex constants (NAME_PATTERN,SUBPATH_PATTERN,FILENAME_PATTERN) shared with the dispatch layer.- HTTP surface in
mureo.web.handlers:GET /api/extensions— index for the front-end renderer (one entry per extension;viewisnullfor headless / route-only plugins).GET /api/ext/<name>/<subpath>— extension GET route; payload is the flattened query string (first-value-wins).POST /api/ext/<name>/<subpath>— extension POST route, gated by the existing Host + body-cap + CSRF pipeline (the plugin author inherits CSRF protection for free).GET /static/ext/<name>/<filename>— extension-shipped static asset served from in-memory bytes with the same Content-Security-Policy + X-Frame-Options + Cache-Control header stack as the bundled static files.
- Front-end (
mureo/_data/web/extensions.js): the configure UI fetches/api/extensionsonce when the dashboard opens, renders one nav tab per extension, and lazy-loads each extension'shtml_fragment/ scripts / styles on first tab activation. Operators who never visit a given tab pay zero added page weight. - Plugin author guide:
docs/plugin-authoring.md§13 documents the contract end-to-end (entry-point setup, sampleWebExtension, URL surface, CSP / CSRF / fault-isolation model, lazy-load behaviour, debugging recipe). - Security: subpaths and filenames are regex-validated at both registration and dispatch so
.., double-slash, trailing slash,?,#, and directory separators cannot smuggle the dispatcher outside/api/ext/<name>/or/static/ext/<name>/. Static asset bodies stay in memory; the dispatcher never reads from disk so filesystem traversal is impossible by construction.html_fragmentis rejected at registration if it contains<script>,<style>,on*=event handlers, orjavascript:URLs — the CSP (script-src 'self'; style-src 'self') is the runtime enforcement, the regex is the explicit author-feedback signal. Handler exceptions are caught by the dispatcher and surfaced as a generic{"error": "extension_handler_error"}500 envelope; exception details are logged server-side only (they may carry secrets the handler touched).
Backward compatibility: when no third-party mureo.web_extensions entry points are installed, discover_web_extensions() returns an empty tuple, /api/extensions returns [], the renderer creates zero DOM nodes, and the configure UI is byte-identical to v0.9.4.
Install
pip install --upgrade mureo
v0.9.4 — extension protocols + mureo learn CLI
Added — Extension Protocols and mureo learn CLI (#125)
A new public surface under mureo.core lets alternate backends and tests inject pluggable persistence without forking call sites. The shape mirrors the existing mureo.core.providers and mureo.core.skills extension patterns. Every default reproduces today's file-backed behaviour, so existing users see no change.
mureo.core.SecretStore—Protocolfor credential round-trip (load / save / delete). DefaultFilesystemSecretStorereads and writes~/.mureo/credentials.jsonbyte-for-byte equivalent to the previous flow (atomic write,0o600viamureo.fsutil.secure_fchmod,ensure_ascii=False).mureo.core.StateStore—ProtocolforSTATE.json/STRATEGY.md/ action_log persistence. DefaultFilesystemStateStorecomposes the existing helpers inmureo.context.state/mureo.context.strategy.mureo.core.KnowledgeStore— two-tierProtocolfor/learninsights (operator + workspace). DefaultFilesystemKnowledgeStorewrites to today's~/.claude/skills/_mureo-pro-diagnosis/SKILL.mdlocation with the same frontmatter scaffold.mureo.core.ThrottleStore—Protocolfor per-key API rate limiting. DefaultProcessLocalThrottleStorewrapsmureo.throttle.Throttler;register(key, config)pre-installs custom buckets matching the MCP server's_PLUGIN_TOOL_THROTTLERSpattern.mureo.core.RuntimeContext— frozen dataclass aggregating the four stores plus aworkspace_id.DEFAULT_WORKSPACE_ID = "default"is the canonical single-workspace sentinel.mureo.core.default_runtime_context()— factory wiring the four file-backed defaults.mureo.core.get_runtime_context()— process-cached resolver that discovers a single zero-arg factory under themureo.runtime_context_factoryentry-point group; raisesRuntimeContextFactoryErroron multiple registrations or a returning-non-RuntimeContextfactory.
Added — mureo learn add CLI
mureo learn add <text> [--scope {operator,workspace}] persists /learn insights through RuntimeContext.knowledge_store rather than writing files directly. Default scope operator writes the cross-workspace tier (today's pro-diagnosis location); --scope workspace writes a workspace-scoped tier if one is configured. The /learn skill (skills/learn/SKILL.md) now invokes the CLI instead of carrying its own copy of the file scaffold.
Changed — Consumers routed through the new Protocols
These refactors are call-site changes only; all on-disk artefacts and CLI behaviour are byte-equivalent in the default file-backed runtime.
mureo.auth.load_google_ads_credentials/load_meta_ads_credentialsread throughSecretStore(get_runtime_context().secret_storewhenpathis not passed; one-shotFilesystemSecretStore(path=…)when it is).- MCP handlers
mureo_strategy_*,mureo_state_*,rollback_*,analysis_anomalies_checkresolve theirpath/state_fileargument againststate_store.workspacerather than raw CWD. Error messages and traversal-refusal semantics are preserved; symlink refusal in the analysis handler is unchanged. - MCP plugin dispatch acquires its throttle slot via
RuntimeContext.throttle_store. The defaultProcessLocalThrottleStoreis seeded with the existing per-toolThrottlerinstances (_PLUGIN_TOOL_THROTTLERS) on first call; alternate backends receiveacquire(name)and own per-key fallback semantics. mureo.cli.rollback_cmd--state-filedefault is now resolved throughRuntimeContext(rather than the literalPath("STATE.json")).mureo.byod.runtime.byod_data_dir()adds a middle-priority resolution path: when a non-defaultRuntimeContextexposes a filesystemworkspace, BYOD data lives at<workspace>/byod/.MUREO_BYOD_DIRenv var and the legacy~/.mureo/byod/fallback are unchanged.
Install
pip install --upgrade mureo
v0.9.3 — Windows compatibility
Fixed — Windows compatibility (#122)
mureo crashed on Windows. This release makes the core (CLI + mureo configure web UI) run on Windows, verified by a new real windows-latest CI job.
os.fchmodcrash (Unix-only) in every credential/config write path → newmureo/fsutil.py(secure_fchmod/secure_chmod): owner-only0o600on POSIX (byte-identical, no Linux/macOS change), best-effort never-raising no-op on Windows. NTFS confidentiality relies on the%USERPROFILE%profile ACL (documented best-effort).- Interactive setup menu (
simple_term_menu) imports Unix-onlytermiosand raisedNotImplementedErroron Windows, making the plain number-input fallback unreachable → both fallbacks nowexcept (ImportError, NotImplementedError)(also degrades gracefully in non-terminal envs: CI, pipes, PyCharm). mureo configureresolved the wrong Claude Desktop config path on Windows →host_pathsnow returns%APPDATA%\Claude\claude_desktop_config.json(macOS unchanged; Linux keeps the Code-style fallback — Claude Desktop has no Linux build).
CI
- Added a
windows-latestCI job — the real-Windows verification + an automatic regression guard. POSIX-only test assertions were made platform-aware. Also deflaked a stop-lifecycle test (event-set race) surfaced by the new matrix.
Known limitations (out of scope; not crashes)
mureo install-desktop(CLI) is still macOS-only by explicit design — a Windows launcher is a separate feature; it errors gracefully off macOS.- Real-desktop UX not exercisable by headless CI (browser auto-open, native file/folder picker dialog) is not yet end-to-end verified on Windows.
Full changelog: CHANGELOG.md
v0.9.2 — configure Desktop host-confirm fix
Fixed
mureo configure no longer misroutes Claude Desktop users on the Meta connector finalize (#118)
A Claude Desktop user who had connected the Meta hosted MCP saw a misleading "not connected yet — finish the Meta login" on finalize. The in-memory session.host could reset to the claude-code default, so the Claude Code claude mcp list verification path ran for a Desktop user (no CLI ⇒ accusatory dead-end). The official Meta MCP itself was always usable — only the switch-native-off step was wrongly blocked (tool ambiguity, never a strand).
- Client-authoritative host — confirm / native-toggle resolve host from the request payload (validated, self-heals a stale session).
- Host-sync hardening — explicit choice persisted to localStorage and preferred over the server-echoed value;
/api/hostretries once + toast instead of silent swallow; host re-asserted on load. - Tri-state connectivity —
connected/not_connected/unknown;unknown(no Claude Code CLI / timeout) is not "not connected". Desktop / unknown now offer an explicit "I've verified it" affirm path that applies the native↔official switch — no-strand preserved.
Full changelog: CHANGELOG.md
v0.9.1 — mureo safety layer for plugin tools
Highlights
mureo safety layer for third-party plugin tools (#114, #116) — opt-in & purely additive via standard MCP Tool metadata, no plugin-side changes required:
- Phase 1 — audit (
~/.mureo/plugin_audit.jsonl, secret-masked, 0600) / throttle / fault-isolation (record-then-reraise; never crashes or silently swallows). - Phase 2 — classify via
readOnlyHint(undeclared ⇒ mutating) + optional_meta["mureo"](reversal,throttle); successful mutating calls promoted intoSTATE.jsonaction_log(platform="plugin:<dist>", only when a STATE.json exists). - Phase 3 — provider-aware skill guidance (plugin platforms enumerated best-effort, treated advisory).
- Phase 4 — structural strategy parity: mutating calls get an
observation_duewindow (14-day default,_meta["mureo"]["observation_days"]overridable) so daily-check reviews outcomes like a built-in.
Honest scope: confirm + STRATEGY gating are skill-mediated; audit/action_log/observation/rollback-intent are mechanical — the same channel built-ins use. mureo's platform-specific analytics and executable auto-rollback for arbitrary ops are not generically possible and not claimed. See docs/plugin-authoring.md, docs/ABI-stability.md.
Also since v0.9.0
- Fixed:
mureo configurefrees the terminal on finish / Ctrl+C (#111). - Docs: getting-started leads with
mureo configure+ 'Before you start' (#109, #110); BYOD/Demo are mureo-native only (#112).
Full changelog: CHANGELOG.md
v0.9.0
Fixed — /learn slash command restored (regression from #77)
- Phase 3 plugin packaging (#77) migrated every
.claude/commands/*.mdslash command into an operational skill underskills/+ the bundledmureo/_data/skills/, but droppedlearnentirely (deleted.claude/commands/learn.md, never created alearnskill)./learnbecame uninvocable while every other workflow command kept working, even though README/docs still document it. Restored as an operational skill (skills/learn/+ byte-identical bundled copy):name: learn(no_prefix → appears in the picker), saves insights to../_mureo-pro-diagnosis/SKILL.md(scaffolding that canonical-only knowledge base on first use), approval-required and append-only, never Claude memory or secrets/PII.
Changed — mureo configure visual refresh + official mureo logo
- The configure Web UI got a cohesive design-system pass (refined spacing/type/color tokens, light and dark via
prefers-color-scheme, crafted cards/buttons/focus states, system fonts only — strict CSP, no web fonts/CDN/build). The header now shows the official mureo wordmark (bundledlogo.png/logo-dark.png, scheme-swapped). CSS-only; everydata-*/data-i18nhook and EN/JA parity preserved.
Added — Google Ads OAuth-scope guidance in the auth step
- The Web UI Google Ads auth step and
docs/authentication.mdnow explain that a reused refresh token must carry the Google Ads scopehttps://www.googleapis.com/auth/adwordsor API calls fail withACCESS_TOKEN_SCOPE_INSUFFICIENT, with a link to the official Google scope reference. mureo's own OAuth already requests it; the note prevents the failure when users supply a hand-minted token.
Fixed — Meta hosted MCP on Claude Code goes through the Claude.ai connector (supersedes the earlier "/mcp register" Unreleased note)
- A prior Unreleased change had
mureo configure/mureo providers add/ the wizard registermeta-ads-officialinto~/.claude.jsonon Claude Code and tell the user to finish OAuth via/mcp→ Authenticate. Real-environment verification proved this cannot work: Meta's hosted MCP (https://mcp.facebook.com/ads) does not support OAuth Dynamic Client Registration, so Claude Code's/mcpOAuth fails withSDK auth failed: The provided redirect_uris are not registered for this client. Registering it locally only creates an unauthenticatable user-scope server. Corrected behavior: on Claude Code, mureo now does not register Meta locally at all —install_provider/mureo providers addreturnmanual_required(no~/.claude.jsonwrite, no subprocess) and the UI/CLI point the user to add Meta as a Claude.ai account connector (claude.ai → Settings → Connectors → Add custom connector →https://mcp.facebook.com/ads; Anthropic brokers the Meta Business sign-in there, requires a paid plan, then works account-wide in Claude Code and Claude Desktop and surfaces asmcp__claude_ai_MetaAds__*). mureo-native Meta is still not auto-disabled (nothing registered/verified — native steps aside only viamureo providers confirmonce the connector is verified Connected; no-strand preserved). Claude Desktop is unchanged (manual_required, Settings → Connectors). This re-aligns with the "no dead config entry / Connectors" behavior described in the bullets below; the intervening "/mcp register on Code" wording is withdrawn.
Docs — mureo configure is now the documented front door; auth setup --web removed
mureo auth setup --webwas removed (its browser credential flow is now part of the unifiedmureo configureUI). README and docs (cli.md,authentication.md,getting-started.md/.ja.md,byod.md/.ja.md,architecture.md) updated: everyauth setup --webreference now points tomureo configure(or terminalmureo auth setup). README gained a top-of-"Choose your setup" quickstart —pip install mureo+mureo configure— enumerating what the browser UI does (host pick, basic setup, OAuth/credentials, official MCP providers, native↔official toggle, Demo/BYOD).
Fixed — official/native precedence when mureo MCP is configured after an official provider
MUREO_DISABLE_<PLATFORM>(which makes the mureo-native MCP step aside so an official provider is the single source for a platform) was only auto-set bymureo providers addwhen amcpServers.mureoblock already existed. A user who registered the official provider first and configured the mureo MCP later ended up with native + official both active and no deterministic precedence (tool ambiguity).install_mureo_mcp(the path both basic-setup and the dashboard use) now backfills the disable env, after the mureo block is written, for already-registered pipx/npm official providers (google-ads-official, ga4-official) detected by a pure host-config registry read. Meta (hosted) is intentionally out of backfill scope — detecting it needs a networkclaude mcp listprobe that must not run on the basic-setup path; Meta native↔official is the explicitmureo providers confirm/ dashboard native-toggle (both gate on the verified connector — no-strand preserved). Best-effort and idempotent (never raises, never invents a mureo block); Search Console is never disabled. Works for both Claude Code (~/.claude.json) and Claude Desktop (claude_desktop_config.json).- Web-UI per-platform native↔official toggle — the dashboard now shows, per official provider (when the mureo MCP is configured), the current tool source for that platform and a button to switch.
POST /api/providers/native-togglesets/unsetsMUREO_DISABLE_<PLATFORM>;statusexposes the per-platform state. Switching to official is allowed only when the official path is actually usable (pipx/npm provider registered, or Meta connector verified Connected) — refused with an actionable message otherwise; switching back to native is always allowed (the un-strand path). Restart Claude to apply (the flag is read once at MCP start). Host-aware (Code + Desktop), EN + JA.
Fixed — GA4 wizard inputs were collected but never saved
- The configure-UI auth wizard's GA4 step rendered the service-account-path / project-id inputs and a "Done" button whose handler only advanced the wizard — the entered values were discarded, so
GOOGLE_APPLICATION_CREDENTIALS/GOOGLE_PROJECT_IDwere never written tocredentials.jsonand the officialga4-officialMCP launched unauthenticated (same class as the earlier Google Ads bug). The Done handler now POSTs each value through the allow-listed/api/credentials/env-varwriter (into thega4section) before advancing, only proceeding if every write succeeds (otherwise it surfaces a save-failed message and stays on the step). Host-accurate labels + saving/failure status added (EN + JA).
Changed — host selector clarity + Desktop-unavailable credential-guard hook note
- The configure-UI host selector labels were ambiguous (
Claude Code (terminal)implied terminal-only). Relabelled toClaude Code (CLI, Desktop app)vsClaude Desktop app (Chat, Cowork)so users running Claude Code inside the Desktop app correctly pick the Claude Code option (which targets~/.claude.json). Japanese punctuation made consistent (fullwidth、). - The credential-guard hook has no surface on Claude Desktop (
install_auth_hookis anoop:unsupported_on_desktopthere). The basic-setup list (wizard and dashboard) now appends "(not available on the Desktop app)" / "(デスクトップアプリでは利用できません)" to that row when the chosen host is Claude Desktop, instead of implying it can be installed.
Fixed — dashboard "mureo integrations" listed GA4 (not native) and omitted Search Console
- The configure-UI dashboard's mureo integrations section listed
Google Ads / Meta Ads / GA4. mureo ships no native GA4 tools (GA4 is official-provider-only), so GA4 did not belong there; meanwhile the genuinely mureo-native Search Console was missing (only a sub-note under Google Ads). GA4's presence came from thega4credentials.json section, which actually stores the official GA4 MCP's service-account env — not a mureo-native integration. - Removed the GA4 row; added a Search Console row. Search Console has no own credentials section (it reuses the Google Ads Google OAuth — adwords + webmasters scopes), so the row is status-only: it shows configured the moment the wizard's Search Console / Google sign-in is done (driven by the existing
credentials_oauth.googlesignal) and has no standalone Remove (a note directs removal to the Google Ads row, since the sign-in is shared).
Fixed — official Meta Ads provider registered a dead entry on Claude Code
- Adding the official Meta Ads MCP (
meta-ads-official, hosted athttps://mcp.facebook.com/ads) on Claude Code wrote a raw{"type":"http","url":…}entry into~/.claude.jsonand setMUREO_DISABLE_META_ADS=1. But Meta's hosted MCP has no OAuth Dynamic Client Registration, so Claude Code can never connect that raw entry (✗ Failed to connect) — while mureo-native Meta was disabled, leaving the user with zero Meta capability (the model fell back to a mureo-native "credentials not found" error). The Desktop path already short-circuited this; the Code path and themureo providers addCLI did not. - Claude Code, the
mureo providers addCLI, and Claude Desktop now treathosted_httpproviders consistently: no dead config entry is written and mureo-native tools are NOT auto-disabled (auto-disabling before the official path is verified strands the user). The result ismanual_required, and the UI/CLI now point the user to Claude's account-level Connectors (the working path mureo cannot create programmatically). Connectors setup guidance is host-accurate — terminal (Claude Code) vs Claude Desktop have genuinely different steps — EN + JA.mureo providers removestill self-heals a stale `MUREO_D...