MCP server plug for Phoenix LiveView — exposes socket assigns and live state to AI tools like Claude Code
LiveAgent MCP server demonstrates solid definition quality with well-structured tool definitions, comprehensive descriptions, and proper JSON Schema input schemas. All 11 tools are explicitly registered with descriptions and typed schemas. However, output schemas are not documented, several parameters lack descriptions (particularly in list_async_tasks and get_async_event), and tool annotations are absent. The descriptions are domain-specific and detailed (averaging ~300 chars), which aids LLM selection, but they occasionally include implementation details (e.g., '250ms poll loop') that don't help the agent. The tool naming follows verb_noun conventions consistently (list_, get_, etc.), which is excellent. This server would benefit from documenting return schemas, adding missing parameter descriptions, and adopting tool annotations (readOnlyHint, idempotentHint) to guide agent behavior.
Returns the value of a single assign key from a LiveView's socket. Useful when you only need one specific piece of data rather than all assigns.
Returns the assigns map for a specific LiveView process. Assigns are the data your LiveView template renders — this is the live state of what is currently displayed on screen. Use list_live_views first to obtain the pid_string. Optionally pass `keys` as a list of dot-path strings to retrieve only specific nested values instead of the full (potentially huge) assigns map. Examples: "current_user.email", "current_user.total_bounties", "count"
Returns a single async history entry by id. Use after get_async_history to drill into a specific completion.
Returns completed async tasks for a LiveView, newest first. Each entry records the name, kind, duration, result (:ok or :exit), and (for :assign kind) the post-completion %AsyncResult{} from assigns. Best-effort note: capture is driven by a 250ms poll loop, so tasks that complete in <~250ms may be missed entirely (the inspector couldn't observe them between launch and exit). Tasks that took longer than a single tick are reliably captured. Each entry also carries `state_timeline_id` linking to the get_state_event that recorded the assigns diff produced by the async callback (if one was recorded within ~150ms).
Output schemas are not documented for any tool. LLMs cannot plan downstream calls or extract needed fields without knowing return structure. Every tool should declare what fields and types it returns.
Tool annotations (readOnlyHint, idempotentHint, destructiveHint) are absent. These guide agent behavior and prevent incorrect retries or chaining assumptions. All 11 tools are read-only and idempotent but this is not declared.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | C | 64 | 2026-07-28+ | v2 |
Returns the security scope bound to a LiveView — the actor (current user), tenant/organization, and Ash scope context the user on screen is authorized against. This is the sanitized, compact answer to "who is this, and what are they scoped to?" without grepping the full assigns. RUBRIC: call this before any `project_eval` / SQL that touches tenant-scoped data, then bind the returned actor/tenant into your eval so the query reproduces the user's exact authorization boundary — instead of passing `authorize?: false` or a hand-built scope that does not match what the user actually sees. Resolution is heuristic across Ash apps (current_scope, current_user + __tenant__, current_organization, …) and can be overridden with `plug LiveAgent, scope_assign_keys: [:my_scope]`. A result with `raw_present: false` means the LiveView has no scope-like assign (an unscoped LV) — distinct from a lookup failure, which is an error.
Returns the DOM element most recently selected via the LiveAgent panel's element picker. Includes tag, id, classes, text content, outerHTML (truncated), Phoenix attributes (phx-click, phx-hook, data-phx-component, etc.), and parent chain. Use this when the user says "I selected X" or "look at this element".
Returns full socket metadata for a LiveView process: view module, socket ID, parent/root PIDs, transport info, URL, and all assigns. More detailed than get_assigns.
Returns a single timeline entry by id, with the full diff (changed / added / removed assigns), trigger details, duration, and exception if the callback crashed. Call get_state_history first to find the entry_id you care about. Diffs larger than ~16KB are stored as an `oversize: true` summary listing the changed keys; use get_assigns to read the current values.
Returns the recent assigns transitions (timeline) for a LiveView process. Each entry records what triggered the change, the diff between previous and current assigns, duration, and any exception. Trigger kinds: - "mount", "handle_params", "handle_event" — captured from LiveView telemetry - "live_component_event" — from a LiveComponent handle_event - "unknown" (note: "likely_handle_info") — a render happened but no callback telemetry preceded it; almost always means a handle_info (PubSub message, send_after timer, etc.) changed assigns Use this BEFORE re-running the user's flow when they ask "why did X change?" or "what happened after I clicked Y" — the answer is already recorded. Entries are returned newest-first. Default last_n is 20, max 50. Pair with get_state_event(pid, entry_id) to drill into a specific transition.
Returns the "what's loading right now" view for a LiveView: - pending: tasks currently in flight (name, kind, task_pid, elapsed_ms) - async_results: %AsyncResult{} values in assigns, with loading/ok?/failed flags `kind` is one of "start", "assign", "stream" — straight from LiveView's internal `:live_async` registry. Empty `pending` + an `async_result` with `loading: nil, ok?: true` means the task already finished — check get_async_history for the completion entry. Use this before re-running the user's flow when they say "the spinner is still up" or "is anything still loading?".
Lists all active Phoenix LiveView processes currently running in the application. Returns the PID, view module, socket ID, URL, connection status, and available assign keys. Always call this first to discover which LiveView you want to inspect. With multiple browser tabs open, the entry whose URL matches the tab with the "Drive" toggle ON is marked "← Drive target" — that's the tab the browser-driving tools (click/fill/navigate/screenshot/...) will act on. The header also states the Drive target URL (or that no tab has Drive on).
Parameter 'last_n' in list_async_tasks and get_async_history lacks a description explaining the default (10 or 20) and max (25 or 50). Without this, LLMs must guess or might pass invalid values.
get_async_event description is minimal (31 chars) and does not explain when or why to call it relative to get_async_history. It violates the 10 - 1024 character guideline by being too terse for proper LLM selection.
Descriptions include implementation details (e.g., '250ms poll loop', 'best-effort note: capture is driven by a 250ms poll loop') that are noise for LLM planning. Strip internals; keep only user-facing behavior.
No error handling guidance in any tool description. LLMs need to know: what if a PID is invalid? What if a key doesn't exist? How should the agent recover?
get_selected_element has an empty input schema (no parameters) but description says 'when user says I selected X'. This is ambiguous, is the element selected via a UI picker, or must the agent pass a selector?