Operations
Monitor and maintain Phoenix MCP with rate limits, sensors, audit logs, configuration history, and Home Assistant events. See the separate Admin API reference for HTTP automation.
Rate limiting
Each token limits MCP network traffic with a sliding one-minute window.
- Default
- 60 requests per minute, with a burst of 10 per second.
- Disable
- Set
rate_limit_requeststo0for that token. - Pass-through
- Uses the same limits. Edit them on the token detail page after creation.
- JSON-RPC batch
- Each call spends one request. Phoenix rejects the full batch with HTTP 429 before anything runs when it would exceed the limit.
- In-process AI
- Agent Chat, Assist, Voice Agent, and AI Task use the steps-before-check-in limit instead. Their tool calls remain audited and counted.
A blocked request returns HTTP 429 and Retry-After. Successful responses include 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 they are usually named as below. Home Assistant assigns the entity ID when a sensor is first created and resolves any collision by adding a suffix, so copy the real IDs from the device page rather than typing these; tokens created before version 1.0.0 keep whatever IDs they were given then.
| Entity | Reports |
|---|---|
sensor.phoenix_mcp_token_my_token_denied_count | Requests blocked by permission rules |
sensor.phoenix_mcp_token_my_token_expires_in | Days until expiry; unknown when the token has no expiry |
sensor.phoenix_mcp_token_my_token_last_access | Timestamp of the most recent request |
sensor.phoenix_mcp_token_my_token_rate_limit_hits | Times this token has been rate limited |
sensor.phoenix_mcp_token_my_token_request_count | Total requests made with this token |
sensor.phoenix_mcp_token_my_token_status | active 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. Renaming a token changes the display name only; the entity IDs are fixed when the sensors are first created and a rename does not move them, so a dashboard card or automation referring to one keeps working. Phoenix MCP's own sensors are blocked from all token access, so an external tool cannot read its telemetry through Phoenix MCP.
Global settings
| Setting | Default | Effect |
|---|---|---|
| Audit log flush interval | 15 min | How often the in-memory log is snapshotted to disk; set to "Never" to disable persistence |
| Disable all logging | Off | Suppresses all auditing |
| Kill switch | Off | When enabled at startup, Phoenix MCP registers no client routes; enabling it at runtime makes the already-registered MCP, context, skill, and Agent Chat routes refuse with 503 (HA cannot unregister them) |
| Log allowed requests | On | Record successful requests |
| Log client IP | On | Include caller IP in audit entries |
| Log denied requests | On | Record blocked requests and unsupported MCP methods |
| Log entity names | On | Include entity IDs in audit entries |
| Log rate-limited requests | On | Record rate-limited requests |
| Maximum log entries | 10,000 | Capacity of the buffer and on-disk snapshot; reducing it trims the oldest entries immediately |
| MESA mode | Advisory | Per-entity safety enforcement: off, advisory, or enforced. See MESA |
| MESA in-context profile buttons | Off | Show experimental admin-only MESA profile controls on Home Assistant's entity, device, area, and integration pages |
| Notify on approval | On | Raise an HA notification, with a deep link, when an action needs admin approval |
| Notify on rate limit | Off | Raise an HA notification when a token is rate limited |
| Token settings presets | Off | Lets 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 |
| Agent Chat visibility | All Home Assistant pages | When off, the floating Agent Chat button appears only in the Phoenix MCP panel |
| Chat memory | 100 lines | Number of prior chat transcript lines kept for the next model call; range 0-5,000 |
| Steps before check-in | 20 | Agent Chat tool rounds allowed before the turn pauses for Continue; range 3-100 |
| Assist Tool Provider token | None | Active token whose scoped tools the Assist integration exposes |
| Voice Agent | Off; token, provider, and model unset | Registers Phoenix MCP as a Home Assistant conversation agent when fully configured |
| AI Task | Off; token, provider, and model unset | Registers Phoenix MCP as a Home Assistant AI Task entity when fully configured |
| Theme | Auto | This changes how the panel looks for you and does not sync to other admins or devices |
System-log diagnostics
get_logs and get_phoenix_diagnostics read Home Assistant's in-memory system_log ring through MCP. This is a bounded diagnostic sample, not the Phoenix audit log above and not the raw home-assistant.log file. Home Assistant deduplicates repeated records into buckets and retains only warning-and-above records in this source.
Use the response's source envelope before interpreting an empty result. It distinguishes an available empty ring from an unavailable integration or a degraded upstream shape, and reports capacity, retained buckets, malformed buckets skipped, earliest and latest retained times, and the read timestamp. Continue a clipped query with next_cursor and exactly the same filters. Cursors are snapshot- and filter-bound, but continuation is best-effort because a live bucket can be updated and move while pages are being read.
get_logs requires Log read and excludes Phoenix records. get_phoenix_diagnostics requires both Log read and Diagnostics, includes only Phoenix records, and applies stronger topology, path, identifier, and entity-scope redaction. After a deployment, verify these tools only through the connected Phoenix MCP client after Home Assistant has restarted.
list_integration_log_levels reports effective and stored integration-aware logger settings only for integration domains visible inside the token's resource scope. set_integration_log_level additionally requires the separate, pass-through-exempt Integration log levels capability. Runtime-only timed changes survive Phoenix reloads, restore only while the Phoenix-applied setting remains current, and are discarded rather than reapplied after a full Home Assistant restart. A Phoenix core-data wipe first restores every still-matching timed change.
Logbook diagnostics
get_logbook reads Home Assistant's narrative event history through MCP and returns events chronologically. A home-wide query may cover at most 7 days. Supplying accessible entity IDs, device IDs, or one causal context ID raises that ceiling to 31 days; text search is post-retrieval and does not raise it. Entity and device filters may be combined, while a context filter must stand alone.
Read total and truncated before treating the reply as complete. total is the number left after permission filtering, response redaction, and optional search but before limit; when truncated, reduce the time window rather than looking for a cursor. Home Assistant's underlying logbook command returns one unpaginated database result, so Phoenix deliberately does not offer response-only continuation that would repeat the expensive query and imply a stable snapshot.
Configure advanced token behavior
Advanced token options
Three 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/listadvertises 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. - Inline approval wait
- Off by default. A confirm-gated call returns
pending_approvalas soon as the approval is queued, so a run of writes reaches the Approvals queue in seconds and you clear them individually or as a batch; the agent asks for the outcome when it needs one. Choosing a duration instead makes the call hold the response open until you decide, which suits a client that never asks, at the cost of the next approval not being created until the previous call stops waiting. This affects external MCP clients only. Agent Chat, the Assist bridge, the voice agent and AI Task ignore it: they queue immediately and wait their own way, so testing the setting in Agent Chat will show no difference. - 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 source. After token authentication, pre-dispatch protocol failures are recorded as invalid_request when denied-request logging is enabled; authentication failures have no token audit entry. Every entry produced by one legacy JSON-RPC batch shares the batch's parent HTTP request ID. In the panel, Source shows the caller's IP address for network requests. Requests from Phoenix MCP's own surfaces appear as Agent Chat, Assist Tool Provider, Voice Agent, or AI Task. The admin API continues to expose the underlying value in client_ip.
| Outcome | Meaning |
|---|---|
| allowed | The request succeeded. |
| denied | Blocked by permission rules, the blocklist, or a RED / NO_ACCESS result. Includes permission-based 404s. |
| invalid_request | The 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_found | The 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_implemented | The client called an MCP method Phoenix MCP does not support. A protocol gap, not a permission block; does not increment the denied counter. |
| rate_limited | The token exceeded its rate limit. |
| pending_approval | A 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.
Automate with Home Assistant events
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.
| Event | Fired when |
|---|---|
phoenix_mcp_approval_claimed | An approval's saved action starts executing (claimed: true), or is released back to pending after a failed execution (claimed: false) |
phoenix_mcp_approval_resolved | A pending approval is approved, rejected, expired, or cancelled |
phoenix_mcp_config_changed | A configuration version is recorded (agent create/edit/delete, or an admin rollback); drives the panel's live Changes feed |
phoenix_mcp_rate_limited | A 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_expired | A 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_revoked | A token is revoked |
phoenix_mcp_token_rotated | A token's raw value is rotated |
Inspect HTTP routes and protocol versions
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.
MCP and skill routes
POST /api/phoenix-mcp MCP Streamable HTTP endpoint
GET /api/phoenix-mcp/context Token context summary
GET /api/phoenix-mcp/skill Agent skill guide
- Authentication
Authorization: Bearer phx_...is required for the MCP and context routes. Tokens in a URL or query parameter are rejected. The skill route is unauthenticated.- Modern MCP headers
MCP-Protocol-Version: 2026-07-28andMcp-Methodmust match the request metadata and JSON-RPC method. Named methods also requireMcp-Namematching the requested tool name, resource URI, or prompt name.- Legacy session header
- A 2025-03-26 client sends the
Mcp-Session-Idreturned byinitializeon later requests. - Response framing
- Include
Accept: text/event-streamto receive the single JSON-RPC response as an SSE event with keepalives. A client asking only for JSON receivesapplication/json.
The MCP endpoint answers a client that advertises text/event-stream 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. Conforming clients for the supported protocol revisions advertise both SSE and JSON; custom JSON-only callers keep the plain response path. 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. For modern 2026-07-28 requests, closing the response stream cancels work that has not already committed. Legacy 2025-03-26 requests follow that revision's opposite rule: a disconnect alone is not cancellation.
Phoenix MCP supports two protocol eras on the same endpoint. A modern 2026-07-28 request carries its protocol version and client capabilities in per-request metadata, uses no session, and keeps no resumable SSE event queue. Modern clients may POST only JSON-RPC requests and notifications; a client-sent response object is rejected as an invalid request. A legacy 2025-03-26 client starts with initialize, receives an Mcp-Session-Id, sends notifications/initialized, and includes that session ID on later requests. The server retains at most 256 legacy lifecycle records for up to one hour of inactivity; they are cleared on integration reload. An unknown or expired legacy session returns HTTP 404 so the client can initialize again. Every request in both eras still carries and revalidates its Phoenix bearer token. server/discover reports both implemented revisions, the server identity, and the token-aware primer. A method unavailable in the selected era answers HTTP 404 with JSON-RPC -32601.
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.