Imported from MetaMask/client-mcp-core (
SKILL.md). Install upstream withnpx skills add MetaMask/client-mcp-core. Copyright stays with the author.
mm CLI — Agent Reference
You control a browser extension through the mm CLI. Every command talks to a local HTTP daemon that manages Playwright and the extension lifecycle. The daemon auto-starts when you run mm launch.
If you are running outside the target project directory, use --project <path> or set the MM_PROJECT environment variable to point at the project root. All commands accept --project before the command name (e.g., mm --project ../metamask-extension launch).
Core Loop
mm launch # 1. Start browser + extension
mm describe-screen # 2. See what's on screen (ALWAYS do this before interacting)
mm click <ref> # 3. Interact using refs from describe-screen
mm describe-screen # 4. Re-describe after every action to get fresh refs
mm cleanup --shutdown # 5. Clean up when done
Critical rules:
- Always
describe-screenbefore interacting. Refs likee1,e2are ephemeral — they change after every action. - Always
describe-screenafter interacting — OR use inlineobservationsfrom mutating tool responses. Mutating tools (click, type, navigate, etc.) return anobservationsobject with freshstate,testIds, anda11yrefs. You can use these refs directly for the next interaction without callingdescribe-screen. Calldescribe-screenwhen you needpriorKnowledgeor screenshots. - One target per command. Specify exactly ONE of: a11y ref (
e5), testId, or CSS selector. - Errors are structured. Check the
error.codefield to decide recovery strategy (see Error Codes below).
Observation Behavior
Tool responses include different data based on the tool's category:
| Category | Examples | Observations in response? |
|---|---|---|
| Mutating | click, type, navigate, launch, cleanup, build, clipboard, cdp, mock_network, scroll_to_element, device_swipe, long_press, tap_coordinates, dismiss_keyboard, dismiss_alert, open_app, close_app, press_button, device_context, device_clipboard | Yes — state + a11y (compacted) + testIds |
| Read-only | get_state, get_text, knowledge_*, get_context, set_context, get_alert_text, screen_recording, device_logs, generate_locators, hermes_targets | No — faster response |
| Discovery | describe_screen, list_testids, accessibility_snapshot, screenshot | Data is already in result |
| Batch | run_steps | Controlled by includeObservations param |
Observation Compaction: Mutating tool observations are compacted before returning: option runs of 3 or more under a combobox or listbox are replaced with a single summary node (e.g., "55 options (refs e2–e56)"). The describe-screen tool always returns the full, unfiltered a11y tree — use it when you need the complete option list or priorKnowledge.
Diff-Based Observations: After the first mutating tool call sets a baseline, subsequent mutations return diff-based observations. The observations.a11y.diff field (when present) shows what changed:
{
"added": ["e4", "e5"], // new node refs
"removed": ["e2"], // disappeared node refs
"unchanged": 3 // count of unchanged nodes
}
The observations.a11y.nodes field contains only the changed and new nodes (not all nodes). The baseline resets after describe-screen, launch, or cleanup — the next mutation returns a full compact observation (no diff field). When the diff would be larger than the full observation, the full option-filtered observation is returned instead (no diff field).
Using inline observations (mutating tools)
After a mutating action, the response includes fresh screen state:
{
"ok": true,
"result": { ... },
"observations": {
"state": { "screen": "send", "url": "...", "balance": "1.5 ETH" },
"testIds": ["send-amount-input", "send-button"],
"a11y": {
"nodes": [
{ "ref": "e1", "role": "textbox", "name": "Amount" },
{ "ref": "e2", "role": "button", "name": "Send" }
]
}
}
}
You can use the ref values from observations.a11y.nodes for the next interaction — no describe-screen needed. Note that refs in compacted observations may be summary nodes (e.g., "55 options (refs e2–e56)") when there are 3+ options under a combobox or listbox.
Quick reference:
- Use
observations.statefor quick checks (screen name, loading status, balance, etc.) - Use
observations.a11y.nodeswith the compact refs for the next interaction - Call
describe-screenonly when you need the full tree orpriorKnowledge
mm click e3 # mutating: response includes fresh observations
# observations.a11y.nodes has updated refs — use them directly:
mm type e1 "0.01" # use ref from previous response
Call describe-screen explicitly when you need:
priorKnowledge(historical actions for this screen)- A screenshot via
includeScreenshot - Full context after unexpected navigation
- The complete, unfiltered a11y tree (e.g., all options in a dropdown)
run_steps and includeObservations
The run_steps tool collects observations once after all steps complete. Control inclusion with the includeObservations parameter:
| Value | Behavior |
|---|---|
'all' (default) |
Always include final state observations |
'none' |
Never include observations (fastest response) |
'failures' |
Include observations only if any step failed |
{
"steps": [
{ "tool": "click", "args": { "a11yRef": "e3" } },
{ "tool": "type", "args": { "a11yRef": "e5", "text": "0.01" } }
],
"includeObservations": "failures"
}
Commands
Session Lifecycle
mm launch
Starts the daemon (if not running) and launches a headed Chrome session with the extension.
mm launch [--context e2e|prod] [--state default|onboarding|custom] [--extension-path <path>] [--goal <text>] [--force] [--flow-tags <tags>]
| Flag | Description |
|---|---|
--context e2e|prod |
Set the environment context before launching |
--state default |
Pre-onboarded wallet with 25 ETH on local Anvil chain (default) |
--state onboarding |
Fresh wallet requiring manual onboarding setup |
--state custom |
Use a custom fixture for wallet state |
--extension-path <path> |
Override the extension build directory |
--goal <text> |
Tag the session with a goal for knowledge store |
--force |
Replace an existing active session |
--flow-tags <tags> |
Comma-separated flow tags for cross-session knowledge |
Returns: sessionId, extensionId, state (current extension state).
mm cleanup
Stops the browser, tears down test services, and releases session resources.
mm cleanup [--shutdown]
| Flag | Description |
|---|---|
--shutdown |
Also terminate the daemon process |
Without --shutdown, the daemon stays running for the next mm launch.
mm status
Shows daemon status: PID, port, uptime, allocated sub-ports.
mm status
mm stop
Stops the daemon process (symmetric to mm serve). Sends a best-effort cleanup before shutdown.
mm stop [--force]
| Flag | Description |
|---|---|
--force |
Remove stale .mm-server state from crashed daemons |
mm build
Triggers an extension build using the configured BuildCapability. The daemon must be running.
mm build [--force]
| Flag | Description |
|---|---|
--force |
Force a rebuild even if the extension is already built |
mm serve
Manually starts the daemon without launching a browser. Useful for debugging.
mm serve [--background]
Screen Discovery
mm describe-screen
Your primary observation tool. Returns the complete screen state:
- Extension state: current URL, screen name, network, account, balance
- Active tab: the currently focused tab's role and URL (if tracked)
- Test IDs: visible
data-testidattributes with text previews - A11y tree: interactive elements with deterministic refs (
e1,e2, ...) - Prior knowledge: suggested actions from past sessions on this screen
mm describe-screen
The a11y tree includes actionable roles: button, link, checkbox, radio, switch, textbox, combobox, menuitem; structural roles: menu, listbox, option, tab, tabpanel, list, listitem; and important roles: dialog, alert, status, heading.
Each node looks like:
{
"ref": "e3",
"role": "button",
"name": "Confirm",
"path": ["dialog:Transaction"],
"testId": "confirm-footer-button",
"textContent": "Confirm"
}
The testId and textContent fields appear only on nodes with short or generic names — they provide extra context from the DOM to help identify ambiguous elements. Nodes with clear names omit these fields.
When 3+ consecutive identical nodes appear (same role, name, and path), they are collapsed into a summary like … 3 more "maskicon" (refs e2–e4) to reduce token waste. Individual refs still work for targeting.
Use the ref value (e3) for click/type/get-text/wait-for commands.
mm list-testids
Lists all visible data-testid attributes on the current page with text previews.
mm list-testids [--limit <n>]
| Flag | Description |
|---|---|
--limit <n> |
Maximum number of test IDs to return |
Useful when you know a testId value and want to verify it exists. Prefer describe-screen for general observation.
mm accessibility-snapshot
Captures just the trimmed accessibility tree with deterministic refs. Lighter than describe-screen (no state, no prior knowledge, no test IDs).
mm accessibility-snapshot [--root <selector>]
| Flag | Description |
|---|---|
--root <selector> |
CSS selector to scope the snapshot to a subtree |
mm screenshot
Captures a screenshot of the current page.
mm screenshot [--name <name>]
Returns: file path, dimensions.
Element Interaction
All interaction commands accept an element reference from describe-screen.
mm click <ref>
Clicks an element. Waits for it to become visible, then clicks. The --timeout flag covers the entire operation (visibility wait + click action combined). Default: 15s.
mm click e3
mm click --testid end-accessory --within "testid:account-list-item/0"
mm click --testid onboarding-complete-done --timeout 60000
Use --within to scope the target inside a parent element. Values use the format testid:<id>, selector:<css>, or a bare a11y ref (e5).
If the page closes after clicking (e.g., confirmation popup), the response includes pageClosedAfterClick: true — this is normal, not an error.
Timeout behavior: If the click hangs (e.g., element found but click never resolves due to a side effect), MM_CLICK_TIMEOUT is returned. The click may still complete in the background — run mm describe-screen to verify current state before retrying.
mm type <ref> <text>
Types text into an input field. Clears the field first, then sets the new value (uses Playwright's fill()). No clearFirst flag needed — clearing is always implicit. Accepts --timeout <ms> to set the total time budget for the visibility wait + fill operation. Default: 15s.
mm type e5 "0x1234abcd..."
mm type e5 "0x1234abcd..." --timeout 10000
mm get-text <ref>
Reads the text content of an element. Returns the inner text, target descriptor, and character length. Useful for asserting visible values without screenshots. Categorized as read-only (no observations in response). Accepts --timeout <ms> to set the total time budget. Default: 15s.
mm get-text e5
mm get-text --testid balance-amount
mm get-text --testid amount --within "testid:tx-row"
mm get-text --testid balance-amount --timeout 5000
Returns: text (string content), target (descriptor like testId:balance-amount), length (character count).
mm wait-for <ref>
Blocks until an element becomes visible. Default timeout: 15s.
mm wait-for e7 [--timeout <ms>]
mm wait-for --testid confirm-btn --within "testid:dialog-container"
mm wait-for-notification
Waits for the extension notification popup to appear within a timeout. Returns the notification page URL.
mm wait-for-notification [--timeout <ms>]
mm clipboard
Reads from or writes to the system clipboard via Chrome DevTools Protocol. Useful for pasting seed phrases or copying addresses.
mm clipboard read
mm clipboard write "0x1234abcd..."
Navigation
mm navigate <url>
Opens a new tab and navigates to the given URL.
mm navigate https://app.uniswap.org
mm navigate-home
Navigates the extension tab to the wallet home screen.
mm navigate-home
mm navigate-settings
Navigates the extension tab to the settings page.
mm navigate-settings
mm switch-to-tab
Switches the active page to a tab matching a given role or URL prefix. Supports a positional role as the first argument.
mm switch-to-tab dapp
mm switch-to-tab --role extension
mm switch-to-tab --url https://app.uniswap.org
mm close-tab
Closes a browser tab matching a given role or URL. Falls back to the extension tab if the active tab is closed.
mm close-tab --role dapp
mm close-tab --url https://app.uniswap.org
State & Context
mm get-state
Returns extension state and tracked tabs without the full a11y tree.
mm get-state
Returns: state (extension state) and tabs (active + tracked tabs with roles and URLs).
mm get-context
Returns the current environment context (e2e or prod), session status, available capabilities, and whether context switching is allowed.
mm get-context
mm set-context
Switches the session environment between e2e and prod modes. Blocked while a session is active — run mm cleanup first.
mm set-context <e2e|prod>
Knowledge Store
The knowledge store records every tool invocation and uses past sessions to suggest actions.
mm knowledge-search <query>
Searches past sessions for steps matching the query. Matches against tool names, screen names, test IDs, and a11y node names.
mm knowledge-search "confirm transaction"
mm knowledge-last
Gets the most recent step records from the current session.
mm knowledge-last
mm knowledge-sessions
Lists recent sessions with metadata (goal, flow tags, timestamps).
mm knowledge-sessions
mm knowledge-summarize
Generates a recipe-style summary of a session's tool invocations, showing the step sequence with targets and outcomes.
mm knowledge-summarize [--session <id>]
Contracts (E2E only)
mm seed-contract <name>
Deploys a single smart contract to the local Anvil chain by name. Requires ContractSeedingCapability.
mm seed-contract hst
mm seed-contract piggybank --hardfork london
| Flag | Description |
|---|---|
--hardfork <fork> |
EVM hardfork to use for deployment |
mm seed-contracts <names...>
Deploys multiple smart contracts in sequence.
mm seed-contracts hst nfts piggybank
mm get-contract-address <name>
Looks up the deployed address of a contract by name.
mm get-contract-address hst
mm list-contracts
Lists all contracts deployed in the current session with addresses and timestamps.
mm list-contracts
Batch Execution
mm run-steps <json>
Executes multiple tool invocations in sequence from a JSON array. Each step specifies a tool name and arguments.
mm run-steps '{"steps":[{"tool":"click","args":{"a11yRef":"e3"}},{"tool":"wait_for","args":{"a11yRef":"e5"}}]}'
Supports stopOnError (halt on first failure) and returns per-step results with timing. The includeObservations param controls whether final-state observations appear in the response: 'all' (default), 'none', or 'failures' (only on partial failure). Use batchTimeoutMs to set an overall deadline — if exceeded, remaining steps are marked as skipped and partial results are returned immediately. The summary includes a skipped count alongside succeeded and failed.
Tool aliases are supported in steps: navigate_home / navigate-home, navigate_settings / navigate-settings, and navigate_notification / navigate-notification resolve to navigate with the appropriate screen argument. You can also use ref as shorthand for a11yRef in step args and within targets.
Advanced
mm mock-network add '<json-rule-or-config>'
Adds targeted route mocks during an active session. Each rule specifies an HTTP method, URL pattern (exact or glob), and a response body.
mm mock-network add '{"id":"mock-balance","method":"GET","url":"https://api.example.com/v1/balance","response":{"json":{"balance":"100"}}}'
A rule object requires:
| Field | Description |
|---|---|
id |
Stable identifier — adding a rule with an existing id replaces it |
method |
HTTP method to match (e.g., GET, POST) |
url |
Absolute URL or URL glob (e.g., https://api.example.com/**) |
response.status |
HTTP status code (default: 200) |
response.json |
JSON response body (mutually exclusive with body) |
response.body |
Text response body (mutually exclusive with json) |
response.headers |
Additional response headers (optional; keys are normalized to lowercase and can override defaults like content-type) |
You can also pass an array of rules or an object with a routes array:
mm mock-network add '[{"id":"r1","method":"GET","url":"https://a.com/x","response":{"json":{}}},{"id":"r2","method":"POST","url":"https://a.com/y","response":{"json":{}}}]'
mm mock-network add '{"routes":[...]}'
Unmatched same-origin requests are continued unchanged.
mm mock-network clear
Clears all route mocks and recorded requests.
mm mock-network clear
mm mock-network list
Lists currently active route mocks.
mm mock-network list
mm mock-network requests
Shows recorded matched and missed requests.
mm mock-network requests [--limit <n>]
| Flag | Description |
|---|---|
--limit <n> |
Maximum number of recent records to return |
mm cdp <method> [params-json] [--timeout <ms>] [--target hermes|android-webview] [--url-filter <substr>] [--metro-port <p>] [--app-id <id>]
Sends a raw Chrome DevTools Protocol command against the active session. This is an escape hatch for cases where structured tools are insufficient — e.g., evaluating JavaScript, enabling network tracking, or inspecting the DOM tree. It dispatches through the active platform driver, so it works on both browser and mobile sessions — but the target runtime and available methods differ (see the table below). On mobile, --target selects between the React Native JS runtime (hermes, default) and a debuggable in-app Android WebView (android-webview).
# Browser
mm cdp Runtime.evaluate '{"expression":"document.title"}'
mm cdp Network.enable
mm cdp DOM.getDocument '{"depth":2}' --timeout 60000
# Mobile (Hermes, default) — evaluate JS in the running React Native app
mm cdp Runtime.evaluate '{"expression":"1+1","returnByValue":true}' --app-id io.metamask --metro-port 8081
# Mobile (Android WebView) — drive the DOM of the in-app browser
mm cdp Runtime.evaluate '{"expression":"document.querySelector(\'#personalSign\').click()"}' --target android-webview
| Argument | Description |
|---|---|
<method> |
CDP method name (e.g., Runtime.evaluate, DOM.getDocument on browser; Runtime.evaluate, Debugger.enable on mobile) |
[params-json] |
Optional JSON object with method-specific parameters |
--timeout |
Per-command timeout in ms (default: 30 000, max: 30 000) |
--target |
Mobile only — hermes (default, RN JS runtime) or android-webview (in-app WebView DOM). Ignored on browser. |
--url-filter |
--target android-webview only — select the WebView page whose URL contains this substring. |
--metro-port |
Mobile (Hermes) only — override the Metro inspector proxy port (default: 8081). Ignored on browser. |
--app-id |
Mobile (Hermes) only — override the expected app bundle identifier. Ignored on browser. |
Three CDP targets: the same command reaches different runtimes.
| Aspect | Browser (Playwright) | Mobile — Hermes (--target hermes, default) |
Mobile — Android WebView (--target android-webview) |
|---|---|---|---|
| Target | The page's Chrome DevTools session | The app's Hermes JS engine, via Metro's inspector proxy (needs a DEBUG build with Metro running) | The web page inside a debuggable in-app Android WebView, via adb (needs setWebContentsDebuggingEnabled(true)) |
| Available domains | Full Chrome surface (Runtime, DOM, Network, Page, …) |
JS-engine subset only (Runtime, Debugger, Log, HeapProfiler) — no DOM/Page/Network |
Full Chrome surface (Runtime, DOM, Network, Page, Input) |
| Use it for | Web page DOM in the browser | The React Native app's own JavaScript | The web page DOM inside the app's in-app browser |
| Blocked methods | Browser.close, Target.closeTarget, Target.disposeBrowserContext, Browser.crashGpuProcess |
Runtime.terminateExecution, Inspector.detached |
Browser.close, Target.closeTarget, Target.disposeBrowserContext, Browser.crashGpuProcess |
| Result shape | Standard CDP response | Runtime.evaluate nests the value at result.result.value |
Runtime.evaluate nests the value at result.result.value |
Blocked methods return MM_CDP_BLOCKED on either platform; other failures return MM_CDP_FAILED (on mobile the underlying HERMES_* code is preserved in the message). The tool is categorized as mutating — run describe-screen afterward to re-sync if the call changed runtime/page state.
mm hermes-targets [--all] [--metro-port <p>] [--app-id <id>]
Mobile only. Lists and diagnoses the debuggable Hermes targets Metro currently exposes, reporting which target would be chosen or why selection is ambiguous. Use it to confirm Metro is running and the app is registered. Pass --all to bypass the appId filter and discover the real appId.
mm hermes-targets
mm hermes-targets --all
Mobile device actions (iOS/Android only)
These commands require a mobile session (mm launch --platform ios|android). On a browser
session they return MM_TOOL_NOT_SUPPORTED_ON_PLATFORM. Element-targeting commands accept
the same <ref> / --testid / --selector targeting as mm click.
mm scroll-to-element <ref> [--direction up|down] [--maxAttempts <n>]
Scrolls the screen until the target element becomes visible.
mm device-swipe --direction <up|down|left|right> [--startX <n>] [--startY <n>] [--distance <n>]
Swipes the screen. Without coordinates, swipes from the screen center.
mm long-press <ref> [--duration <ms>]
Long-presses the target element (default press duration is the driver's default).
mm tap-coordinates <x> <y>
Taps raw screen coordinates. Prefer element targeting when possible.
mm dismiss-keyboard
Dismisses the on-screen keyboard.
mm dismiss-alert [--accept]
Dismisses a native OS alert. Pass --accept to accept it; omit to dismiss/cancel.
mm get-alert-text
Returns the text of a visible native alert (read-only).
mm open-app <bundleId> / mm close-app <bundleId>
Launches/foregrounds or terminates an app by bundle identifier (e.g. io.metamask).
mm press-button <home|back|enter|lock>
Presses a hardware/system button. Only home, back, enter, and lock are accepted.
mm device-context list / mm device-context switch <name>
Lists available native/webview contexts, or switches the active context. On iOS WebView contexts look like WEBVIEW_1 (Appium backend). On Android a single WEBVIEW context appears whenever a debuggable in-app WebView is open; to drive that page's DOM use mm cdp --target android-webview (see mm cdp).
mm device-clipboard read / mm device-clipboard write <text>
Reads or writes the device clipboard. Distinct from mm clipboard (browser CDP).
mm screen-recording start [--output <path>] / mm screen-recording stop
Starts or stops an on-device screen recording. stop returns the recording path. When
--output is supplied it is sandboxed to the configured artifactsDir; paths that escape
it (absolute paths, .. traversal) are rejected with MM_INVALID_INPUT.
mm device-logs [--duration <seconds>] [--filter <text>]
Fetches recent device logs, optionally scoped by duration and a text filter.
mm generate-locators
Returns ranked locator suggestions (identifier > label > text > type, with confidence) for every interactive element on the current screen. Read-only — useful when authoring mobile automation to pick stable selectors.
mm scroll-to-element e12 --direction down
mm device-swipe --direction up --distance 400
mm open-app io.metamask
mm device-context switch WEBVIEW_1
mm device-logs --filter MetaMask --duration 30
Element Targeting
Every interaction command (click, type, get-text, wait-for) needs a target. You must provide exactly ONE of:
| Method | Format | Stability | When to use |
|---|---|---|---|
| a11y ref | e1, e2, ... |
Ephemeral (per describe-screen) | Default — use refs from the latest describe-screen |
| testId | data-testid value |
Stable across sessions | When you know the testId from prior knowledge |
| CSS selector | Any CSS selector | Fragile | Last resort fallback |
Prefer a11y refs. They come directly from the accessibility tree and map to ARIA selectors, making them the most reliable for the current screen state.
Prior Knowledge
When you call describe-screen, the response may include a priorKnowledge section with:
similarSteps: Past tool invocations on the same screen with confidence scoressuggestedNextActions: Ranked actions based on historical success (e.g., "click confirm button")avoid: Targets that frequently fail on this screen — skip these
Use prior knowledge to guide your actions, but always verify against the current a11y tree.
Error Codes
When a command fails, the response includes error.code. Use this to decide what to do:
| Code | Meaning | Recovery |
|---|---|---|
MM_NO_ACTIVE_SESSION |
No browser session running | Run mm launch first |
MM_SESSION_ALREADY_RUNNING |
Session already exists | Run mm cleanup first, or use --force |
MM_LAUNCH_FAILED |
Browser session launch failed | Check extension path and config; retry |
MM_PAGE_CLOSED |
Page was closed unexpectedly | Normal after some confirmations; run describe-screen |
MM_BUILD_FAILED |
Extension build failed | Check build logs; fix build errors and retry |
MM_DEPENDENCIES_MISSING |
Required build dependencies not installed | Run dependency install (npm/yarn) and retry build |
MM_TARGET_NOT_FOUND |
Element ref/testId/selector not found | Run mm describe-screen to get fresh refs |
MM_WAIT_TIMEOUT |
Element didn't appear in time | Increase timeout or verify you're on the right screen |
MM_CLICK_FAILED |
Click failed after finding element | Element may be obscured; try waiting or scrolling |
MM_CLICK_TIMEOUT |
Click action timed out (element found, click hung) | Run mm describe-screen to verify if click completed; retry with --timeout or different approach |
MM_TYPE_FAILED |
Type failed after finding element | Element may not be an input; verify with describe-screen |
MM_TYPE_TIMEOUT |
Fill action timed out | Run mm describe-screen to verify state; retry with --timeout |
MM_GETTEXT_FAILED |
getText operational failure (non-timeout) | Element may be detached; run mm describe-screen and re-target |
MM_GETTEXT_TIMEOUT |
textContent action timed out | Retry with --timeout |
MM_CLIPBOARD_PERMISSION_DENIED |
Clipboard permission denied by browser | Check browser permissions; try CDP approach |
MM_CLIPBOARD_LAVAMOAT_BLOCKED |
Clipboard blocked by LavaMoat policy | Extension security policy blocks clipboard; use alternative input method |
MM_CLIPBOARD_FAILED |
Clipboard operation failed | Retry; check if page is still active |
MM_NAVIGATION_FAILED |
Navigation error or network failure | Check URL validity; retry once |
MM_NOTIFICATION_TIMEOUT |
Extension notification popup didn't appear | Action may not have triggered a notification; check state |
MM_TAB_NOT_FOUND |
Tab role/URL not found | Run mm get-state to see available tabs |
MM_DISCOVERY_FAILED |
Discovery tool failure | Page may be loading; wait and retry |
MM_SCREENSHOT_FAILED |
Screenshot capture failure | Page may be in unstable state; retry after describe-screen |
MM_STATE_FAILED |
State retrieval failed | Session may be unstable; run mm describe-screen |
MM_KNOWLEDGE_ERROR |
Knowledge store operation failed | Retry; check that session exists |
MM_CONTRACT_NOT_FOUND |
Unknown contract name for seeding | See available contracts below |
MM_SEED_FAILED |
Contract deployment failure | Check Anvil chain is running; verify contract name |
MM_CAPABILITY_NOT_AVAILABLE |
Feature requires a capability not configured | Check environment mode (e2e vs prod) |
MM_CONTEXT_SWITCH_BLOCKED |
Can't switch context with active session | Run mm cleanup first |
MM_SET_CONTEXT_FAILED |
Context switch operation failed | Retry; check session state |
MM_INVALID_INPUT |
Bad parameters | Fix input and retry |
MM_INVALID_CONFIG |
Invalid configuration | Check config file format and required fields |
MM_PORT_IN_USE |
Port already in use | Stop conflicting process or let the daemon auto-allocate |
MM_UNKNOWN_TOOL |
Unknown tool name | Check tool name spelling |
MM_INTERNAL_ERROR |
Internal server error | Retry; if persistent, restart daemon with mm stop && mm serve |
MM_BATCH_TIMEOUT |
batchTimeoutMs deadline exceeded |
Remaining steps were skipped; check partial results |
MM_CDP_BLOCKED |
CDP method is blocked (destructive) | Use a different CDP method; blocked list differs by platform (see cdp) |
MM_CDP_FAILED |
CDP command failed or timed out (mobile: HERMES_* code in message) |
Check method/params; on mobile ensure a DEBUG build with Metro is running, then mm hermes-targets --all |
MM_HERMES_FAILED |
hermes_targets discovery failed (HERMES_* code in message) |
Ensure a DEBUG build with Metro is running; run mm hermes-targets --all to diagnose |
MM_HERMES_NOT_AVAILABLE |
hermes_targets used outside a mobile session |
Launch a mobile (iOS/Android) session first |
MM_DEVICE_ACTION_FAILED |
A mobile device action failed | Check the target/args; ensure the device/emulator is responsive |
MM_DEVICE_NOT_AVAILABLE |
Mobile-only tool invoked without a mobile driver | Launch a mobile (iOS/Android) session first |
MM_TOOL_NOT_SUPPORTED_ON_PLATFORM |
Tool gated off the active platform | Use the tool on its supported platform (browser vs mobile) |
Available Contracts (E2E only)
These contracts can be deployed to the local Anvil chain via seed_contract / seed_contracts:
| Name | Type |
|---|---|
hst |
ERC-20 token |
nfts |
ERC-721 NFT |
erc1155 |
ERC-1155 multi-token |
piggybank |
Simple deposit contract |
failing |
Contract that always reverts (for testing failures) |
multisig |
Multi-signature wallet |
entrypoint |
ERC-4337 EntryPoint |
simpleAccountFactory |
ERC-4337 account factory |
verifyingPaymaster |
ERC-4337 paymaster |
Flow Tags
When launching, tag your session with flow tags for cross-session knowledge:
| Tag | Use for |
|---|---|
send |
Token send flows |
swap |
Token swap flows |
connect |
dApp connection flows |
sign |
Message/transaction signing |
onboarding |
Wallet setup/onboarding |
settings |
Settings configuration |
tx-confirmation |
Transaction confirmation flows |
Daemon Model
- Daemon runs per project, state tracked in
.mm-serverat the project root - Auto-starts on
mm launchif not running - Shuts down after 30 minutes of inactivity
- Logs to
.mm-daemon.log - One tool executes at a time (requests are queued)
- Project resolution:
--projectflag →MM_PROJECTenv var → current git worktree
Workflow Examples
Basic Interaction
mm launch --state default
mm describe-screen
# Response includes a11y nodes: [{ ref: "e1", role: "button", name: "Send" }, ...]
mm click e1
mm describe-screen
# Now on send screen — get new refs
mm type e3 "0.01"
mm click e5
mm cleanup --shutdown
Transaction with Notification
mm launch --state default
mm navigate https://app.uniswap.org
mm describe-screen
# Interact with dApp...
mm click e4 # triggers wallet popup
mm wait-for e2 --timeout 10000 # wait for confirm button in notification
mm click e2 # confirm
mm describe-screen # check result
mm cleanup --shutdown
Running From a Parent Folder
# Set once — all subsequent mm commands target this project
export MM_PROJECT=/path/to/metamask-extension
mm launch --state default
mm describe-screen
mm click e1
mm cleanup --shutdown
# Or use --project per command
mm --project ../metamask-extension launch
mm --project ../metamask-extension describe-screen
Using Prior Knowledge
mm launch --state default --goal "Test send flow" --flow-tags send
mm describe-screen
# Response includes priorKnowledge.suggestedNextActions:
# [{ action: "click", preferredTarget: { type: "testId", value: "send-button" }, confidence: 0.85 }]
# Use the suggestion but verify the target exists in the current a11y tree
mm click e3
mm cleanup --shutdown