assistant tool. Send a natural-language task, poll its progress, and handle the human-in-the-loop (HITL) cards it raises.
For HTTP/curl or SDK access, use Assistant · chat. For connection setup, see MCP server. For direct phone control, use OpenClaw · Android control.
Install
Download the packaged skill and load it into a compatible agent runtime:mobilerun-assistant-mcp.skill
Download the latest release from GitHub
MOBILERUN_API_KEY, curl, or jq is required.
Assistant write operations require the server’s full policy profile. Under readonly or no-commerce (the self-hosted default), only list_sessions and get_messages are available. Tell the user about that restriction rather than bypassing it with another tool or transport.
How a conversation works
A session is a persistent chat thread. Each message starts a turn, and only one turn can run in a session. Every call toassistant includes an operation.
1
Create or select a session
Call
create_session with a title, or list_sessions to find an existing conversation. Keep its id as sessionId. Before sending into an existing session, check get_messages for an active turn or pending cards.2
Send a message once
Call
send_message with sessionId and message. waitSeconds is an integer from 1 to 50, defaulting to 45. The response reports completed or running; read any errorText before treating a completed response as success.3
Poll long-running turns
On
running, a send error, or a timeout, call get_messages — never resend the task. Inspect turnActive, turn, lastTurnOutcome, and pending on each poll.4
Resolve pending cards
Surface
pending.questions and pending.permissions, collect the user’s decision, and call the matching answer or rejection operation. Use the card IDs from pending directly, then continue polling.5
Read the final outcome
Once the turn settles, read its text and outcome before reporting the result. Surface errors or aborted outcomes; do not silently retry a task cut off by a limit.
Human-in-the-loop (HITL)
Question and approval cards appear inget_messages under pending. Sending follow-up chat text does not resolve them.
Answer or dismiss a question
UsequestionId from pending.questions. For answer_question, answers is an outer array aligned with the card’s questions. Each inner array must be nonempty and contain {label}, {custom}, or {label, custom} selections with nonempty strings.
reject_question and its questionId.
Approve or reject a permission
Show the user theaction, title, and params from pending.permissions. Call answer_permission with its permissionId and response: "once" for one-time approval, or response: "reject" when the user declines. There is no always option over MCP.
After resolving either kind of card, poll get_messages to follow the turn.
Turn lifecycle and pitfalls
- Blocked, not broken:
turnActive: truewith a pending card means the turn is waiting for a human. Cards have no self-timeout; resolve them using the user’s decision. If your integration cannot wait, useabortwithsessionIdto stop the turn. - A turn can die at birth:
send_messagecan returncompletedwitherrorTextand empty or missingassistantText. Checkget_messages; if no turn is active, send a short nudge such as “Are you still on it?” rather than resending the original text verbatim. - Answer retries are not deduplicated: if an answer or rejection errors or times out, check
get_messagesfirst. Retry only if the card is still inpending. - No stream re-attach: recover from disconnects with
get_messagesand its pending cards. A wait timeout does not stop the server-side turn. - Conflicting turn:
409means a turn is already running; poll it or abort it instead of retrying the original message. Session-scopedabortis an idempotent no-op when no turn is running; optionally passexpectedTurnIdfromturn.id. - Credits and stale sessions:
402means out of credits — stop and ask the user to top up.404means an unknown or archived session.410withsession_machine_replacedmeans the runtime was recycled — start a fresh session.