Operations

What Phoenix MCP exposes once it is running: per-token rate limits and sensors, global settings, the audit log, the events it fires, and the token-facing proxy routes. The admin API has its own reference.

Rate limiting

Every token has a sliding-window rate limit covering its network requests: the REST proxy and the MCP endpoint. The in-process AI surfaces (Agent Chat, the Assist tool provider, the voice agent, and AI Task) are operator-initiated and are deliberately not rate limited; their loops are bounded by the steps-before-check-in limit instead, and their tool calls are still audited and counted. The defaults are 60 requests per minute with a burst of 10 per second, both configurable per token. Setting rate_limit_requests to 0 disables rate limiting for that token. Pass-through tokens get the same default limits and are rate limited like any other token; the create dialog hides the inputs while pass-through is selected, so adjust them afterward on the token's detail page.

A rate-limited request returns HTTP 429 with a Retry-After header. Successful responses carry the current limit state.

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1712345678

If Notify on rate limit is enabled in global settings, Home Assistant raises a persistent notification when a token hits its limit, throttled to once per token per minute.

Telemetry & sensors

Phoenix MCP creates six Home Assistant sensors for each active token. For a token named my_token:

EntityReports
sensor.phoenix_mcp_my_token_denied_countRequests blocked by permission rules
sensor.phoenix_mcp_my_token_expires_inDays until expiry; unknown when the token has no expiry
sensor.phoenix_mcp_my_token_last_accessTimestamp of the most recent request
sensor.phoenix_mcp_my_token_rate_limit_hitsTimes this token has been rate limited
sensor.phoenix_mcp_my_token_request_countTotal requests made with this token
sensor.phoenix_mcp_my_token_statusactive in practice: sensors are removed the moment a token is revoked or archived, and expiry archives the token, so the expired / revoked values are only ever visible transiently. Automate on the phoenix_mcp_token_expired / phoenix_mcp_token_revoked events below instead

Sensors are removed automatically when a token is revoked. Phoenix MCP's own sensors are blocked from all token access, so an external tool cannot read its telemetry through Phoenix MCP.

Global settings

SettingDefaultEffect
Audit log flush interval15 minHow often the in-memory log is snapshotted to disk; set to "Never" to disable persistence
Disable all loggingOffSuppresses all auditing
Kill switchOffWhen enabled at startup, Phoenix MCP registers no client routes; enabling it at runtime makes the already-registered proxy, MCP, and skill routes refuse with 503 (HA cannot unregister them)
Log allowed requestsOnRecord successful requests
Log client IPOnInclude caller IP in audit entries
Log denied requestsOnRecord blocked requests and unsupported MCP methods
Log entity namesOnInclude entity IDs in audit entries
Log rate-limited requestsOnRecord rate-limited requests
Maximum log entries10,000Capacity of the buffer and on-disk snapshot; reducing it trims the oldest entries immediately
MESA modeAdvisoryPer-entity safety enforcement: off, advisory, or enforced. See MESA
Notify on approvalOnRaise an HA notification, with a deep link, when an action needs admin approval
Notify on rate limitOffRaise an HA notification when a token is rate limited
Token settings presetsOffLets each token save and switch named snapshots of its full configuration; enabling seeds every token with a preset of its current settings. See the panel guide
ThemeAutoThis changes how the panel looks for you and does not sync to other admins or devices

Advanced token options

Two per-token options sit outside the capability matrix, in the panel's Tool Announcement card on the token detail page (and settable through PATCH).

Always announce all tools
Off by default: a token's tools/list advertises only the tools its capabilities unlock and hides the rest. Turn it on to advertise the full tool list regardless of gating. Calls are still gated, so this changes what a client sees, not what it can do.
Limit to Assist-exposed entities
Pass-through tokens only, off by default. A pass-through token normally sees every non-Phoenix entity; enable this to additionally narrow it to the entities exposed to Home Assistant Assist. It is ignored for scoped tokens, which scope by the permission tree, so the toggle is disabled unless pass-through mode is on.

Audit log

Phoenix MCP keeps a circular buffer of requests, viewable in the panel's audit tab or through the admin API. Each entry records a request ID (matching X-Phoenix-Request-ID), timestamp, token ID and name, method, resource path, outcome, and client IP.

OutcomeMeaning
allowedThe request succeeded.
deniedBlocked by permission rules, the blocklist, or a RED / NO_ACCESS result. Includes permission-based 404s.
invalid_requestThe request was structurally malformed and rejected before permission checks, for example a template with a syntax error. Also used when an authorised service call carried an argument the target service itself rejected, such as a value outside the range that service accepts: the validator's message is returned so the caller can correct it, which is safe because the call had already passed every permission check and the message describes the caller's own argument. Also used when a permitted action failed for an internal reason, such as a write that could not be saved: those are always accompanied by a full traceback in the Home Assistant log. They are deliberately not recorded as denied, so that outcome stays a reliable list of what your permission rules actually stopped.
not_foundThe entity is genuinely absent from both HA state and the registry. Identical to denied for the caller, but distinguished here so you can tell a missing entity from a permission wall.
not_implementedThe client called an MCP method Phoenix MCP does not support. A protocol gap, not a permission block; does not increment the denied counter.
rate_limitedThe token exceeded its rate limit.
pending_approvalA capability set to Confirm queued the action for admin review instead of running it. Always recorded, even with logging toggles that would otherwise suppress it.

MESA denials are recorded here too, alongside the request that caused them. A companion entry with a method of mesa:enforcement_decision (or mesa:privacy_access, or mesa:lease) names the entity and the rule that stopped it, so you can tell which of a call's targets was the problem. Note the request itself is only logged as denied when the safety layer stopped everything it targeted: a call over several entities where one is blocked and the rest run is an allowed request with a companion denial entry beside it, because that is what happened. Only denials are recorded: the safety layer also reports the accesses it allows, and logging those would push real request history out of the buffer. An entity marked confirm is not a denial either way, so it files no entry; under advisory it runs with a warning, and under enforced it becomes an approval with a record of its own. A preview through dry_run_service never files one, since a predicted block is not a block.

These entries depend on your log level

The safety layer reports its decisions at INFO on the mesa_core.audit logger, and Phoenix MCP deliberately does not raise that level: doing so would override your logging config and put a line in home-assistant.log for every decision. The consequence is that a logger: default above INFO, a common way to quiet the log file, also silences these audit entries. The blocks still happen; only the record of them is lost, and an empty MESA history then looks identical to a safety layer that stopped nothing.

If you have lowered your log level, add this to configuration.yaml to keep the audit trail without turning the rest of the logging back up:

logger:
  default: warning
  logs:
    mesa_core.audit: info

The log is stored in .storage/phoenix_mcp_audit and survives restarts. It is included in HA full backups and in partial backups of .storage. Phoenix MCP flushes on the configured interval and also on HA stop, reload, and unload. Set the interval to "Never" to keep the log in memory only.

Configuration history

Every agent-made create, edit, or delete of an automation, script, scene, helper, blueprint, dashboard layout, entity-registry metadata, ESPHome device YAML, the raw configuration.yaml, or a scoped file (under www/, themes/, custom_templates/) is recorded as a before/after snapshot, viewable in the panel's Changes tab or through the admin API (which also documents the few snapshots that deliberately refuse to restore). The most recent twenty versions per resource are kept, oldest evicted first, so history stays bounded without a sweep. A file or YAML snapshot larger than 100 KB is kept as a metadata-only marker and cannot be restored inline, so a large file cannot bloat storage. Snapshots are stored in .storage/phoenix_mcp_versions and survive restarts; the file is included in HA backups like the rest of .storage. The store is admin-only; restoring a version re-applies it and records a new rollback entry, so the history stays append-only.

HA events

Token-lifecycle event data includes token_id, token_name, and timestamp. Revocation and rotation events also include the HA user ID of the admin who acted. phoenix_mcp_config_changed instead carries resource_type, resource_id, and action.

EventFired when
phoenix_mcp_approval_claimedAn approval's saved action starts executing (claimed: true), or is released back to pending after a failed execution (claimed: false)
phoenix_mcp_approval_resolvedA pending approval is approved, rejected, expired, or cancelled
phoenix_mcp_config_changedA configuration version is recorded (agent create/edit/delete, or an admin rollback); drives the panel's live Changes feed
phoenix_mcp_rate_limitedA token exceeds its rate limit. Fires on every rejected request; only the optional persistent notification is throttled to once per token per minute
phoenix_mcp_token_expiredA token's expiry passes. A timer scheduled at creation fires it on time (with a periodic sweep and an on-access check as backstops), so it does not wait for the token to be used
phoenix_mcp_token_revokedA token is revoked
phoenix_mcp_token_rotatedA token's raw value is rotated

Route reference

Admin API

The admin routes (tokens, permissions, approvals, MESA profiles, entity hints, settings, and the rest) now have their own Admin API reference, complete with payload schemas. They require a Home Assistant session and the admin role.

Proxy API

Requires a Phoenix MCP token in the Authorization: Bearer header.

GET        /api/phoenix-mcp/states                             All accessible entity states
GET        /api/phoenix-mcp/states/{entity_id}                 One entity state
POST       /api/phoenix-mcp/services/{domain}/{service}        Call a service
GET        /api/phoenix-mcp/history/period/{timestamp}         State history (max 7-day range)
GET        /api/phoenix-mcp/statistics                         Long-term statistics
POST       /api/phoenix-mcp/template                           Render a Jinja2 template
GET        /api/phoenix-mcp/config                             HA configuration
GET        /api/phoenix-mcp/events                             HA event-bus listeners
GET        /api/phoenix-mcp/services                           Accessible service list
GET        /api/phoenix-mcp/logs                               Recent HA system log entries
GET        /api/phoenix-mcp/health                             Health check (token-authenticated, rate limited)
POST       /api/phoenix-mcp                                MCP Streamable HTTP endpoint
GET        /api/phoenix-mcp/context                        Token context summary

A service call gated by a capability set to Confirm (restart/stop, physical control, the config-reload family) answers with HTTP 202 and {"status": "pending_approval", "approval_id": ..., "review_url": ...} instead of executing; the action runs when an admin approves it in the panel, exactly like the same call made through MCP.

The MCP endpoint answers a client that advertises text/event-stream (which every MCP client does) with the same single JSON-RPC response delivered as one SSE event, plus a keepalive frame every 15 seconds while the request is still running. That matters behind a reverse proxy: a call held by an inline confirm wait can otherwise sit silent for up to three minutes and get dropped. Nothing is kept server-side, and a client asking only for JSON gets a plain JSON body as before.

There is no session and no handshake to establish: every request carries its own bearer token and is answered on its own, so a client may call server/discover or go straight to tools/list. server/discover is where the endpoint reports which protocol revisions this build implements, its identity, and the same token-aware primer initialize returns, which is how a client that never sends a handshake still learns which of its capabilities are approval-gated. A method the endpoint does not implement answers HTTP 404 with a JSON-RPC -32601, which is what tells a client it reached a real MCP endpoint that lacks that method rather than a server hosting none.

There is also an unauthenticated skill route

GET /api/phoenix-mcp/skill serves the agent skill guide with no token. It is generic and contains no token or entity data. See the agent skill.