New:Socket for Asana Is Now Available.Learn more
Get Started

@metamask/client-mcp-core

Package Overview
Dependencies
Maintainers
7
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@metamask/client-mcp-core

HTTP daemon and CLI for agent-driven browser extension testing with Playwright

latest
Source
npmnpm
Version
0.8.0
Version published
Maintainers
7
Created
Source

@metamask/client-mcp-core

HTTP daemon and CLI architecture for agent-driven browser extension testing with Playwright.

Overview

This package provides the core infrastructure for enabling LLM agents to interact with browser extensions through Playwright. It ships a persistent HTTP daemon that manages browser lifecycle and a unified mm CLI that agents (and developers) use to drive sessions.

The design is consumer-agnostic: the core handles protocol, tooling, and knowledge — consumers provide extension-specific logic by implementing the ISessionManager interface and injecting capabilities.

                         ┌─────────────────────────────────┐
                         │         LLM Agent / Dev         │
                         └────────────┬────────────────────┘
                                      │  mm CLI commands
                                      ▼
                         ┌─────────────────────────────────┐
                         │     mm CLI  (src/cli/mm.ts)     │
                         │  discover / auto-start daemon   │
                         └────────────┬────────────────────┘
                                      │  HTTP (127.0.0.1)
                                      ▼
  ┌───────────────────────────────────────────────────────────────────┐
  │                    HTTP Daemon (createServer)                     │
  │                                                                   │
  │  ┌──────────┐  ┌───────────────┐  ┌────────────┐  ┌────────────┐  │
  │  │  Routes  │  │ RequestQueue  │  │   Tool     │  │ Knowledge  │  │
  │  │ /health  │  │ (async mutex) │  │  Registry  │  │   Store    │  │
  │  │ /status  │  │               │  │  47 tools  │  │            │  │
  │  │ /launch  │  └───────────────┘  └─────┬──────┘  └────────────┘  │
  │  │ /cleanup │                           │                         │
  │  │ /tool/:n │                           ▼                         │
  │  └──────────┘               ┌──────────────────┐                  │
  │                             │   ToolContext    │                  │
  │                             │  sessionManager  │                  │
  │                             │  page / refMap   │                  │
  │                             │  workflowContext │                  │
  │                             │  knowledgeStore  │                  │
  │                             └────────┬─────────┘                  │
  └──────────────────────────────────────┼────────────────────────────┘
                                         │
                   ┌─────────────────────┼─────────────────────┐
                   │          ISessionManager                  │
                   │       (consumer implementation)           │
                   │                                           │
                   │  Session lifecycle   Page management      │
                   │  Extension state     A11y reference map   │
                   │  Navigation          Screenshots          │
                   │  Capabilities (opt)  Environment config   │
                   └─────────────────────┬─────────────────────┘
                                         │
                   ┌─────────────────────┼─────────────────────┐
                   │          WorkflowContext                  │
                   │                                           │
                   │  build?            fixture?               │
                   │  chain?            contractSeeding?       │
                   │  stateSnapshot?    mockServer?            │
                   │  config: EnvironmentConfig                │
                   └─────────────────────┬─────────────────────┘
                                         │
                                         ▼
                   ┌───────────────────────────────────────────┐
                   │        Playwright  →  Chrome Browser      │
                   │            Browser Extension              │
                   └───────────────────────────────────────────┘

Requirements

  • Node.js ^20 || ^22 || >=24
  • TypeScript >=5.0 (for consumer type definitions)
  • Playwright ^1.49.0 (peer dependency)

Installation

As a project dependency (the CLI is available via npx mm or yarn mm):

yarn add @metamask/client-mcp-core

As a global CLI (puts mm directly on your PATH — recommended for LLM agents):

npm install -g @metamask/client-mcp-core

The global CLI can target any project via --project or MM_PROJECT (see Project Targeting).

Getting Started

Consuming this package requires two things: a daemon entry point and a configuration file.

1. Create a daemon entry point

// daemon.ts
import { createServer, allocatePort } from '@metamask/client-mcp-core';
import { MySessionManager } from './my-session-manager';
import { createMyContext } from './my-context';

const server = createServer({
  sessionManager: new MySessionManager(),
  contextFactory: async () => {
    // Consumer owns port allocation — use the allocatePort() helper
    // or any other strategy that fits your infrastructure.
    const anvil = await allocatePort();
    const fixture = await allocatePort();
    await Promise.all([
      new Promise<void>((r) => anvil.server.close(() => r())),
      new Promise<void>((r) => fixture.server.close(() => r())),
    ]);

    return createMyContext({
      ports: { anvil: anvil.port, fixture: fixture.port },
    });
  },
});

server.start().then((state) => {
  console.error(`Daemon started on port ${state.port}`);
});

2. Create a configuration file

Create mm-client-cli.config.ts in your project root:

export default {
  daemon: 'path/to/daemon.ts',
  runtime: 'tsx',
};

The daemon field tells the CLI where the daemon entry point lives. The runtime field specifies the TypeScript runner (defaults to tsx).

The CLI uses cosmiconfig for config discovery, so you can also use mm-client-cli.config.js, .mm-client-clirc.json, or other supported formats.

3. Use the CLI

mm launch              # auto-starts daemon, opens browser session
mm describe-screen     # get element references
mm click e3            # interact using a11y refs
mm cleanup --shutdown  # stop browser and daemon

If running from outside the project directory (e.g., a parent folder containing multiple repos):

mm --project ./my-extension launch
mm --project ./my-extension describe-screen

# Or set once via environment variable
export MM_PROJECT=/path/to/my-extension
mm launch

Core Concepts

Daemon Model

The architecture relies on a persistent background HTTP daemon that manages the browser lifecycle:

  • Worktree Isolation: Each git worktree runs its own daemon instance, tracked via a .mm-server state file in the project root. This allows parallel work across branches.
  • Port Allocation: The daemon allocates its own HTTP port automatically. Sub-service ports (Anvil, fixture server, etc.) are allocated by the consumer's contextFactory and reported back via allocatedPorts. The allocatePort() helper is exported for convenience.
  • Auto-Start: The daemon starts automatically on mm launch if not already running, and shuts down after a period of inactivity (default: 30 minutes).
  • Request Serialization: A RequestQueue (async mutex) ensures only one tool executes at a time, preventing race conditions on shared browser state.
  • Health Checks: Each daemon generates a unique nonce on startup. The CLI verifies daemon identity via GET /health to detect stale .mm-server files from crashed processes.
  • Logs: Daemon activity is logged to .mm-daemon.log.

Session Manager Interface

ISessionManager is the core abstraction boundary between this package and consumer implementations. Consumers must implement this interface to provide extension-specific browser control.

type ISessionManager = {
  // Session Lifecycle
  hasActiveSession(): boolean;
  getSessionId(): string | undefined;
  getSessionState(): SessionState | undefined;
  getSessionMetadata(): SessionMetadata | undefined;
  launch(input: SessionLaunchInput): Promise<SessionLaunchResult>;
  cleanup(): Promise<boolean>;

  // Page Management
  getPage(): Page;
  setActivePage(page: Page): void;
  getTrackedPages(): TrackedPage[];
  classifyPageRole(page: Page, extensionId?: string): TabRole;
  getContext(): BrowserContext;

  // Extension State
  getExtensionState(): Promise<ExtensionState>;

  // A11y Reference Map
  setRefMap(map: Map<string, string>): void;
  getRefMap(): Map<string, string>;
  clearRefMap(): void;
  resolveA11yRef(ref: string): string | undefined;

  // Navigation
  navigateToHome(): Promise<void>;
  navigateToSettings(): Promise<void>;
  navigateToUrl(url: string): Promise<Page>;
  navigateToNotification(): Promise<Page>;
  waitForNotificationPage(timeoutMs: number): Promise<Page>;

  // Screenshots
  screenshot(options: SessionScreenshotOptions): Promise<ScreenshotResult>;

  // Capabilities (optional, extension-specific)
  getBuildCapability(): BuildCapability | undefined;
  getFixtureCapability(): FixtureCapability | undefined;
  getChainCapability(): ChainCapability | undefined;
  getContractSeedingCapability(): ContractSeedingCapability | undefined;
  getStateSnapshotCapability(): StateSnapshotCapability | undefined;

  // Environment
  setWorkflowContext(context: WorkflowContext): void;
  getEnvironmentMode(): EnvironmentMode;
  setContext(context: 'e2e' | 'prod', options?: Record<string, unknown>): void;
  getContextInfo(): { currentContext: 'e2e' | 'prod'; ... };
};

Workflow Context & Capabilities

The WorkflowContext aggregates optional capabilities that consumers inject through the contextFactory. The tool system checks for capabilities at runtime — tools that depend on missing capabilities return clear errors.

type WorkflowContext = {
  build?: BuildCapability;
  fixture?: FixtureCapability;
  chain?: ChainCapability;
  contractSeeding?: ContractSeedingCapability;
  stateSnapshot?: StateSnapshotCapability;
  mockServer?: MockServerCapability;
  config: EnvironmentConfig;
  allocatedPorts?: PortMap; // reported to /status and persisted in .mm-server
};

Capabilities are created by the consumer's contextFactory function. The factory is responsible for allocating any sub-service ports it needs (the allocatePort() helper is exported for convenience):

async function createMyContext(options: {
  ports: { anvil: number; fixture: number };
}): Promise<WorkflowContext> {
  return {
    build: new MyBuildCapability(),
    fixture: new MyFixtureCapability(options.ports.fixture),
    chain: new MyChainCapability(options.ports.anvil),
    allocatedPorts: {
      anvil: options.ports.anvil,
      fixture: options.ports.fixture,
    },
    config: {
      environment: 'e2e',
      extensionName: 'MyExtension',
      defaultPassword: 'test-password',
      artifactsDir: './test-artifacts',
      defaultChainId: 1337,
      ports: {
        anvil: options.ports.anvil,
        fixtureServer: options.ports.fixture,
      },
    },
  };
}

Capability Reference

CapabilityPurposeEnables Tools
BuildCapabilityBuild extension from sourcebuild
FixtureCapabilityManage wallet state via fixtureslaunch (state modes)
ChainCapabilityLocal blockchain (Anvil) lifecycleChain interactions
ContractSeedingCapabilityDeploy smart contracts to Anvilseed_contract, seed_contracts, get_contract_address, list_contracts
StateSnapshotCapabilityRead extension state and detect screensget_state
MockServerCapabilityHTTP mock server for API stubbingMock-dependent tests

Each capability interface is defined in src/capabilities/types.ts:

type BuildCapability = {
  build(options?: BuildOptions): Promise<BuildResult>;
  getExtensionPath(): string;
  isBuilt(): Promise<boolean>;
};

type FixtureCapability = {
  start(state: WalletState): Promise<void>;
  stop(): Promise<void>;
  getDefaultState(): WalletState;
  getOnboardingState(): WalletState;
  resolvePreset(presetName: string): WalletState;
};

type ChainCapability = {
  start(): Promise<void>;
  stop(): Promise<void>;
  isRunning(): boolean;
  setPort(port: number): void;
};

type ContractSeedingCapability = {
  deployContract(
    name: string,
    options?: DeployOptions,
  ): Promise<ContractDeployment>;
  deployContracts(
    names: string[],
    options?: DeployOptions,
  ): Promise<{
    deployed: ContractDeployment[];
    failed: { name: string; error: string }[];
  }>;
  getContractAddress(name: string): string | null;
  listDeployedContracts(): ContractInfo[];
  getAvailableContracts(): string[];
  clearRegistry(): void;
  initialize(): void;
};

type StateSnapshotCapability = {
  getState(page: Page, options: StateOptions): Promise<StateSnapshot>;
  detectCurrentScreen(page: Page): Promise<string>;
};

type MockServerCapability = {
  start(): Promise<void>;
  stop(): Promise<void>;
  isRunning(): boolean;
  getServer(): unknown;
  getPort(): number;
};

Tool System

Tools are standalone functions registered in a central toolRegistry. Each tool receives a ToolContext and returns a ToolResponse.

type ToolFunction<TParams, TResult> = (
  params: TParams,
  context: ToolContext,
) => Promise<ToolResponse<TResult>>;

type ToolContext = {
  sessionManager: ISessionManager;
  get page(): Page; // lazy — throws if no session
  get refMap(): Map<string, string>; // lazy — returns empty map if no session
  workflowContext: WorkflowContext;
  knowledgeStore: KnowledgeStore;
  toolRegistry: Map<string, ToolFunction<unknown, unknown>>;
};

The daemon routes POST /tool/:name requests through the registry, applies Zod validation on inputs, executes the tool through the request queue, and captures observations (extension state, test IDs, a11y snapshot) after each execution.

Registered tools:

ToolDescription
Lifecycle
buildTriggers an extension build using the configured BuildCapability. Accepts build type and force options.
launchLaunches a new browser session with the configured extension. Supports state modes (default, onboarding, custom), fixture presets, goal/tag metadata, and optional contract seeding on start.
cleanupTears down the active browser session and cleans up all resources (browser, services, fixtures).
Interaction
clickClicks an element identified by a11y ref, test ID, or CSS selector. Waits for the element to be visible before clicking. Supports within to scope the target inside a parent element.
typeTypes text into an input element identified by a11y ref, test ID, or CSS selector. Clears the field first, then sets the new value (uses Playwright's fill()). Supports within scoping.
wait_forWaits for an element to become visible on the page within a configurable timeout. Supports within to scope the target inside a parent element.
get_textReads the text content of an element identified by a11y ref, test ID, or CSS selector. Returns the text, target descriptor, and character length. Supports within scoping. Categorized as read-only (no observations in response).
clipboardReads from or writes to the system clipboard via Chrome DevTools Protocol. Useful for pasting seed phrases or copying addresses.
Navigation
navigateNavigates the browser to a named screen (home, settings, notification) or an arbitrary URL.
switch_to_tabSwitches the active page to a tab matching a given role (e.g., extension, dapp) or URL prefix.
close_tabCloses a browser tab matching a given role or URL. Falls back to the extension tab if the active tab is closed.
wait_for_notificationWaits for the extension notification popup to appear within a timeout. Returns the notification page URL.
Discovery
describe_screenCaptures a comprehensive screen snapshot: extension state, visible test IDs, trimmed a11y tree with refs, optional screenshot, and prior knowledge from historical sessions.
accessibility_snapshotCaptures a trimmed accessibility tree of the current page with deterministic refs (e1, e2, ...). Supports scoping to a root CSS selector.
list_testidsCollects all visible data-testid attributes from the current page with text previews and visibility status.
State
get_stateRetrieves the current extension state (URL, screen, network, balance, account) and tracked tab information.
get_contextReturns the current environment context (e2e or prod), session status, available capabilities, and whether context switching is allowed.
set_contextSwitches the session environment between e2e and prod modes. Blocked while a session is active.
Screenshots
screenshotCaptures a screenshot of the current page. Supports naming, full-page capture, scoping to a CSS selector, and optional base64 output.
Knowledge
knowledge_lastRetrieves the N most recent step records from the knowledge store, with optional scope and filter parameters.
knowledge_searchSearches step records by query string with token-based matching and synonym expansion. Scores results by relevance to screen, URL, test IDs, and a11y nodes.
knowledge_summarizeGenerates a recipe-style summary of a session's tool invocations, showing the step sequence with targets and outcomes.
knowledge_sessionsLists available knowledge sessions with metadata (goal, flow tags, timestamps), with optional filtering.
Contracts
seed_contractDeploys a single smart contract to the local Anvil chain by name. Requires ContractSeedingCapability.
seed_contractsDeploys multiple smart contracts in sequence. Returns both successful deployments and individual failures.
get_contract_addressLooks up the deployed address of a contract by name from the session's deployment registry.
list_contractsLists all contracts deployed in the current session with addresses and deployment timestamps.
Batching
run_stepsExecutes a batch of tool invocations sequentially. Supports stopOnError to halt on first failure, includeObservations ('all', 'none', 'failures') to control observations, and batchTimeoutMs to set an overall deadline (remaining steps are skipped on timeout). Accepts tool aliases like navigate_home / navigate-home. Returns per-step results with timing.
Advanced
mock_networkAdds, clears, lists, and inspects targeted Playwright network mocks on the active browser context. Unmatched same-origin requests are continued unchanged.
cdpSends a raw Chrome DevTools Protocol command, dispatched through the active platform driver. Browser: targets the page's Chrome CDP session (full Runtime/DOM/Network/Page surface); destructive methods (Browser.close, Target.closeTarget, etc.) are blocked. Mobile (iOS/Android): targets the app's React Native Hermes JS runtime via Metro's inspector proxy (delegates to @metamask/device-mcp, needs a DEBUG build with Metro running); only the JS-engine subset exists (Runtime, Debugger, Log, HeapProfiler — no DOM/Page/Network) and Runtime.terminateExecution / Inspector.detached are blocked. Optional metroPort / appId are mobile-only (ignored on browser). Categorized as mutating — run describe_screen afterward to re-sync. See Browser vs Mobile CDP.
Hermes (mobile only)
hermes_targetsLists and diagnoses the debuggable Hermes targets Metro exposes (iOS and Android), reporting which target would be chosen or why selection is ambiguous. Use to confirm Metro is running and the app is registered. Pass all to bypass the appId filter and discover the real appId. Mobile only — there is no browser equivalent.
Mobile (iOS/Android only)
scroll_to_elementScrolls the screen until an element (a11y ref, test ID, or selector) becomes visible. Supports direction (up/down) and maxAttempts. Mobile only.
device_swipeSwipes the screen in a direction (up/down/left/right), with optional startX/startY/distance. Mobile only.
long_pressLong-presses an element (a11y ref, test ID, or selector) for an optional durationMs. Mobile only.
tap_coordinatesTaps raw x/y screen coordinates. Mobile only.
dismiss_keyboardDismisses the on-screen keyboard. Mobile only.
dismiss_alertDismisses a native alert, accepting it when accept is true. Mobile only.
get_alert_textReturns the text of a visible native alert. Read-only. Mobile only.
open_appLaunches or foregrounds an app by bundleId. Mobile only.
close_appTerminates an app by bundleId. Mobile only.
press_buttonPresses a hardware/system button. One of home, back, enter, lock. Mobile only.
device_contextLists native/webview contexts (list) or switches to one (switch + name). Mobile only.
device_clipboardReads (read) or writes (write + text) the device clipboard. Distinct from the browser clipboard tool (which uses CDP). Mobile only.
screen_recordingStarts (start, optional outputPath) or stops (stop) a screen recording. When provided, outputPath is sandboxed to the configured artifactsDir (paths escaping it are rejected). Read-only. Mobile only.
device_logsFetches device logs with optional durationSeconds and filter. Read-only. Mobile only.
generate_locatorsGenerates ranked locator suggestions (identifier > label > text > type, with confidence) for every interactive element on the current screen, by walking the device snapshot. Read-only. Mobile only.

Accessibility References

The core uses Chrome DevTools Protocol accessibility APIs to build a deterministic reference map of interactive and meaningful accessibility nodes. Each element gets a short ref like e1, e2, etc., mapped to a Playwright-compatible selector.

Agents call describe_screen to get the current reference map, then use refs for interaction:

mm describe-screen    → { ..., a11y: [{ ref: "e1", role: "button", name: "Submit" }, ...] }
mm click e1           → clicks the "Submit" button
mm type e3 "hello"    → types into the element mapped to e3

This accessibility-first approach provides reliable element targeting that survives minor UI changes.

Knowledge Store

The KnowledgeStore provides cross-session learning by recording every tool execution as a structured step record:

  • Step Recording: Each tool invocation captures the tool name, input, outcome, observation (extension state, visible test IDs, a11y nodes), and timing.
  • Session Metadata: Sessions are tagged with goals, flow tags, and free-form tags for filtering.
  • Prior Knowledge: Before tool execution, the store can generate context from historical sessions — similar steps, suggested actions, and patterns to avoid — based on the current screen state.
  • Search: Token-based search with synonym expansion across sessions, scored by relevance to screen, URL, test IDs, and a11y nodes.
  • Sensitive Data Handling: Input text for password fields and other sensitive inputs is automatically redacted.

Knowledge artifacts are stored on disk at test-artifacts/llm-knowledge/ organized by session ID.

Environment Modes

The package supports two environment modes via discriminated union configuration:

E2E Testing — Full test infrastructure with local chain, fixtures, and contract seeding:

const e2eConfig: E2EEnvironmentConfig = {
  environment: 'e2e',
  extensionName: 'MetaMask',
  defaultPassword: 'password123',
  artifactsDir: './test-artifacts',
  defaultChainId: 1337,
  ports: { anvil: 8545, fixtureServer: 12345 },
};

Production-like — Minimal configuration without test infrastructure:

const prodConfig: ProdEnvironmentConfig = {
  environment: 'prod',
  extensionName: 'MetaMask',
};

Use set_context / get_context tools to switch between modes at runtime (requires no active session).

Server Configuration

The createServer() function accepts a ServerConfig object:

type ServerConfig = {
  /** Session manager instance (required) */
  sessionManager: ISessionManager;
  /** Factory function to create workflow context (may be sync or async) */
  contextFactory: () => WorkflowContext | Promise<WorkflowContext>;
  /** Shared knowledge store instance (optional — a new instance is created if omitted) */
  knowledgeStore?: KnowledgeStore;
  /** Idle timeout in milliseconds (optional, defaults to 1_800_000 = 30 min) */
  idleShutdownMs?: number;
  /** Per-request execution timeout in milliseconds (default: 30_000) */
  requestTimeoutMs?: number;
  /** Path to log file (optional) */
  logFilePath?: string;
};

The contextFactory is called once during start(). It is responsible for allocating any sub-service ports and returning a WorkflowContext. The core validates the returned shape at runtime — config.environment must be a string and every value in allocatedPorts (if provided) must be a finite number.

The allocatePort() utility is exported as a convenience for consumers who need ephemeral port allocation inside their factory.

The returned ServerInstance exposes:

  • start(): Promise<DaemonState> — Calls contextFactory, starts HTTP server, writes .mm-server state, sets up idle timeout and signal handlers.
  • stop(): Promise<void> — Stops accepting connections, cleans up session, removes .mm-server state.

HTTP API

The daemon exposes the following endpoints on 127.0.0.1:

MethodPathDescription
GET/healthHealth check with nonce verification
GET/statusDaemon status (PID, port, uptime, sub-ports)
POST/launchStart a browser session
POST/cleanupStop the current browser session
POST/tool/:nameExecute a registered tool with JSON body

All responses follow a consistent shape:

// Success
{ ok: true, result: T, observations?: { state, testIds, a11y } }

// Error
{ ok: false, error: { code: string, message: string } }

The observations field is included for mutating tools (click, type, navigate, launch, cleanup, build, etc.) and for run_steps when its includeObservations parameter is 'all' (default) or 'failures'. Read-only and discovery tools omit observations from the response.

CLI Reference

The mm CLI provides a unified interface for agents and developers. All commands communicate with the daemon over HTTP — the daemon is auto-started on mm launch if not already running.

Global Options

OptionDescription
--project <path>Target a specific project directory (absolute or relative). Overrides MM_PROJECT and git-based discovery.
Environment VariableDescription
MM_PROJECTDefault project directory when --project is not provided. Falls back to the current git worktree root.

Project Targeting

By default, the CLI resolves the target project from the current git worktree. This works when running from inside the project directory. For other scenarios, the resolution order is:

  • --project <path> — Explicit flag, highest priority. Accepts absolute or relative paths.
  • MM_PROJECT — Environment variable. Useful for setting once in agent config or shell profile.
  • Git worktreegit rev-parse --show-toplevel from the current working directory (existing behavior).
# From inside the project (unchanged)
mm launch

# From a parent folder containing multiple repos
mm --project ./metamask-extension launch

# Via environment variable
export MM_PROJECT=/path/to/metamask-extension
mm describe-screen

Lifecycle

CommandDescription
mm launch [--context e2e|prod] [--state default|onboarding|custom] [--extension-path <path>] [--goal <text>] [--force] [--flow-tags <tags>]Auto-starts the daemon if needed, then launches a headed Chrome session with the configured extension. Use --context to set the environment context before launching. Use --state to control wallet initialization. Use --extension-path to override the extension directory. Use --goal and --flow-tags for knowledge tagging. Use --force to replace an existing session.
mm launch --platform ios|android [--device-id <id>] [--app-bundle <path>] [--metro-port <port>] [--reinstall] [--reset-app-data] [--allow-fox-code-mismatch]Launches a mobile session. Use --device-id to target a specific device (auto-detected when exactly one is connected). Use --app-bundle to install a specific app artifact before launching. Use --metro-port to attach to a running Metro bundler (development builds). --reinstall and --reset-app-data are destructive to the app container and wallet state; --allow-fox-code-mismatch bypasses the consumer's app-identity guard. Consumers decide how to honor these — see Mobile launch options.
mm cleanup [--shutdown]Stops the browser, tears down test services (fixture server, Anvil, mock server), and releases session resources. Add --shutdown to also terminate the daemon process.
mm stop [--force]Stops the daemon process (symmetric to mm serve). Sends a best-effort cleanup before shutdown. Use --force to remove stale .mm-server state from crashed daemons.
mm statusDisplays the daemon's current status: PID, port, uptime, allocated sub-ports, and whether a browser session is active.
mm serve [--background]Manually starts the HTTP daemon without launching a browser session. Use --background to detach the process. Fails if a daemon is already running for this worktree.

Interaction

CommandDescription
mm click <ref> [--timeout <ms>] [--selector <css>] [--testid <id>] [--within <scope>]Clicks an element by its accessibility reference (e.g., e3). The ref comes from a prior describe-screen call. Waits for the element to be visible before clicking. The --timeout covers the entire operation (visibility wait + click action). Default: 15s. Use --within to scope the target inside a parent element (testid:<id>, selector:<css>, or a bare a11y ref).
mm type <ref> <text> [--timeout <ms>] [--selector <css>] [--testid <id>] [--within <scope>]Types text into an input element identified by its accessibility reference. Clears the field first, then sets the new value (uses Playwright's fill()). Accepts --timeout for the total time budget (default: 15s). Use --within to scope the target inside a parent element.
mm get-text <ref> [--timeout <ms>] [--selector <css>] [--testid <id>] [--within <scope>]Reads the text content of an element. Returns the inner text, target descriptor, and character length. Accepts --timeout for the total time budget (default: 15s). Useful for asserting visible values without screenshots.
mm describe-screenCaptures the full screen state: extension info, visible test IDs, a trimmed accessibility tree with deterministic refs (e1, e2, ...), and prior knowledge from historical sessions. This is the primary command for understanding what's on screen before interacting.
mm screenshot [--name <name>]Takes a full-page screenshot of the current page. Saves to the artifacts directory. Use --name to set a descriptive filename.
mm wait-for <ref> [--timeout <ms>] [--selector <css>] [--testid <id>] [--within <scope>]Blocks until an element identified by its accessibility reference becomes visible, or the timeout expires. Default timeout is 15 seconds. Use --within to scope the target inside a parent element.
mm wait-for-notification [--timeout <ms>]Waits for the extension notification popup to appear within a timeout. Returns the notification page URL.
mm clipboard <read|write> [text]Reads from or writes to the system clipboard via Chrome DevTools Protocol. Useful for pasting seed phrases or copying addresses.

Navigation

CommandDescription
mm navigate <url>Opens a new tab and navigates to the given URL. Useful for navigating to dApps or external pages.
mm navigate-homeNavigates the extension tab to the wallet home screen.
mm navigate-settingsNavigates the extension tab to the settings page.
mm switch-to-tab <role> | --role <role> | --url <url>Switches the active page to a tab matching a given role (e.g., extension, dapp) or URL prefix. Supports a positional role as first argument.
mm close-tab --role <role> | --url <url>Closes a browser tab matching a given role or URL. Falls back to the extension tab if the active tab is closed.

State & Context

CommandDescription
mm get-stateReturns the current extension state: loaded status, current URL, screen name, network, chain ID, account address, and balance. Also lists all tracked browser tabs.
mm get-contextReturns the current environment context (e2e or prod), session status, available capabilities, and whether context switching is allowed.
mm set-context <e2e|prod>Switches the session environment between e2e and prod modes. Blocked while a session is active — run mm cleanup first.

Knowledge

CommandDescription
mm knowledge-search <query>Searches the knowledge store for past tool invocations matching the query. Results are scored by relevance to screen, URL, test IDs, and a11y nodes.
mm knowledge-lastRetrieves the most recent step records from the current session's knowledge store.
mm knowledge-sessionsLists recent knowledge sessions with metadata (goal, flow tags, timestamps).
mm knowledge-summarize [--session <id>]Generates a recipe-style summary of a session's tool invocations, showing the step sequence with targets and outcomes.

Batching

CommandDescription
mm run-steps <json>Executes a batch of tool invocations sequentially from a JSON definition. Each step specifies a tool name and arguments.

Advanced

CommandDescription
mm mock-network add '<json-rule-or-config>'Adds targeted route mocks during an active session. Pass either a single rule, an array of rules, or an object with a routes array.
mm mock-network clearClears route mocks and recorded requests.
mm mock-network listLists active route mocks.
mm mock-network requests [--limit <n>]Shows recorded matched and missed requests.
mm cdp <method> [params-json] [--timeout <ms>] [--metro-port <p>] [--app-id <id>]Sends a raw Chrome DevTools Protocol command against the active session. Escape hatch for when structured tools are insufficient. Works on both browser and mobile (Hermes) — the target runtime and blocked methods differ by platform (see below).
mm cdp Runtime.evaluate '{"expression":"document.title"}'
mm cdp Network.enable
mm cdp DOM.getDocument '{"depth":2}' --timeout 60000

The params-json argument must be a valid JSON object. The --timeout flag sets a per-command timeout (default: 30 000 ms, max: 30 000 ms). The tool is categorized as mutating — run mm describe-screen afterward to re-sync session state.

Browser vs Mobile (Hermes) CDP

cdp dispatches through the active platform driver, so the same command works on a browser or a mobile session — but the two targets are not interchangeable:

AspectBrowser (Playwright)Mobile (React Native Hermes)
Target runtimeThe page's Chrome DevTools targetThe app's Hermes JS engine, via Metro's inspector proxy
Available domainsFull Chrome surface — Runtime, DOM, Network, Page, Accessibility, …JS-engine subset only — Runtime, Debugger, Log, HeapProfiler (no DOM / Page / Network)
Blocked methodsBrowser.close, Target.closeTarget, Target.disposeBrowserContext, Browser.crashGpuProcessRuntime.terminateExecution, Inspector.detached
metroPort/appIdIgnoredSelect the Metro inspector port (default 8081) and target app (io.metamask.MetaMask iOS / io.metamask Android)
PrerequisiteAn active browser sessionAn active mobile session with a DEBUG build and Metro running
ErrorsMM_CDP_BLOCKED / MM_CDP_FAILEDMM_CDP_BLOCKED / MM_CDP_FAILED (the underlying HERMES_* code is preserved in the message)

On mobile, Runtime.evaluate nests its value at result.result.value. Metro defaults can also be set globally via the HERMES_METRO_PORT / HERMES_APP_ID env vars on the @metamask/device-mcp backend.

# Mobile (Hermes) — evaluate JS in the running app
mm cdp Runtime.evaluate '{"expression":"1+1","returnByValue":true}' --app-id io.metamask --metro-port 8081

Hermes targets (mobile only)

CommandDescription
mm hermes-targets [--all] [--metro-port <p>] [--app-id <id>]Lists and diagnoses the debuggable Hermes targets Metro exposes, reporting which target would be chosen or why selection is ambiguous. Pass --all to bypass the appId filter and discover the real appId. Mobile only — there is no browser equivalent.
mm hermes-targets
mm hermes-targets --all

Mobile launch options

mm launch --platform ios|android accepts additional options that the core validates and forwards verbatim to the consumer's ISessionManager.launch(). The core does not act on them itself — it owns no install or device lifecycle. Each consumer decides how (or whether) to honor them:

OptionFieldMeaning
--device-id <id>deviceIdTarget a specific device. Auto-detected when exactly one is connected.
--app-bundle <path>appBundlePathApp artifact to install before launching (iOS .app, Android .apk).
--metro-port <port>metroPortMetro bundler / inspector proxy port for watch-mode attach (development builds).
--reinstallreinstallDestructive. Uninstall and reinstall the app, discarding its container.
--reset-app-dataresetAppDataDestructive. Clear app data, discarding existing wallet state.
--allow-fox-code-mismatchallowFoxCodeMismatchDangerous. Bypass the consumer's app-identity compatibility guard.
# Reuse whatever is already installed on the only booted device
mm launch --platform ios

# Install a specific build on a specific simulator
mm launch --platform ios --device-id <UDID> --app-bundle ios/build/MetaMask.app

# Attach to a running Metro bundler (development build)
mm launch --platform ios --metro-port 8081

# Replace the installed app, discarding wallet state
mm launch --platform ios --app-bundle ios/build/MetaMask.app --reinstall

The destructive flags default to false. Because they can irreversibly destroy real wallet state in a prod-context session, consumers are expected to guard them — for example by refusing --reinstall when no --app-bundle is supplied, or by requiring --allow-fox-code-mismatch before replacing an app whose identity differs from the installed one.

When a consumer's launch() throws an error carrying a code that is a known ErrorCode, the launch tool preserves it instead of collapsing it into MM_LAUNCH_FAILED, so precise device/prerequisite failures reach the agent intact.

Mobile device actions (iOS/Android only)

These commands require a mobile session (mm launch --platform ios|android). Run on a browser session, they return MM_TOOL_NOT_SUPPORTED_ON_PLATFORM.

CommandDescription
mm scroll-to-element <ref> [--direction up|down] [--maxAttempts <n>] [--selector <css>] [--testid <id>]Scrolls until the target element is visible.
mm device-swipe --direction <up|down|left|right> [--startX <n>] [--startY <n>] [--distance <n>]Swipes the screen in a direction.
mm long-press <ref> [--duration <ms>] [--selector <css>] [--testid <id>]Long-presses the target element.
mm tap-coordinates <x> <y>Taps raw screen coordinates.
mm dismiss-keyboardDismisses the on-screen keyboard.
mm dismiss-alert [--accept]Dismisses a native alert; --accept accepts it.
mm get-alert-textPrints the text of a visible native alert.
mm open-app <bundleId>Launches or foregrounds an app.
mm close-app <bundleId>Terminates an app.
mm press-button <home|back|enter|lock>Presses a hardware/system button.
mm device-context list / mm device-context switch <name>Lists or switches native/webview contexts.
mm device-clipboard read / mm device-clipboard write <text>Reads or writes the device clipboard.
mm screen-recording start [--output <path>] / mm screen-recording stopStarts or stops a screen recording; --output is sandboxed to artifactsDir.
mm device-logs [--duration <seconds>] [--filter <text>]Fetches device logs.
mm generate-locatorsLists ranked selector suggestions for on-screen elements.
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
mm generate-locators

For the full agent-facing reference and workflow guidelines, see SKILL.md.

Error Classification

Tool errors are classified into specific error codes for structured handling:

CodeMeaning
Session & Lifecycle
MM_NO_ACTIVE_SESSIONNo browser session running
MM_SESSION_ALREADY_RUNNINGSession already exists
MM_LAUNCH_FAILEDBrowser session launch failed
MM_PAGE_CLOSEDBrowser page was closed unexpectedly
Build
MM_BUILD_FAILEDExtension build failed
MM_DEPENDENCIES_MISSINGRequired build dependencies not installed
Interaction
MM_TARGET_NOT_FOUNDElement not found by ref, testId, or selector
MM_WAIT_TIMEOUTTimeout waiting for element visibility
MM_CLICK_FAILEDClick operation failed
MM_CLICK_TIMEOUTClick action timed out (element found, click hung)
MM_TYPE_FAILEDType operation failed
MM_TYPE_TIMEOUTFill action timed out
MM_GETTEXT_FAILEDgetText operation failed
MM_GETTEXT_TIMEOUTtextContent action timed out
Clipboard
MM_CLIPBOARD_PERMISSION_DENIEDClipboard permission denied by browser
MM_CLIPBOARD_LAVAMOAT_BLOCKEDClipboard blocked by LavaMoat policy
MM_CLIPBOARD_FAILEDClipboard operation failed
Navigation & Tabs
MM_NAVIGATION_FAILEDNavigation error or network failure
MM_NOTIFICATION_TIMEOUTNotification popup did not appear
MM_TAB_NOT_FOUNDTab not found by role or URL
Discovery & State
MM_DISCOVERY_FAILEDDiscovery tool failure
MM_SCREENSHOT_FAILEDScreenshot capture failure
MM_STATE_FAILEDState retrieval failed
Knowledge
MM_KNOWLEDGE_ERRORKnowledge store operation failed
Contracts
MM_CONTRACT_NOT_FOUNDUnknown contract name
MM_SEED_FAILEDContract deployment failure
Context & Config
MM_CONTEXT_SWITCH_BLOCKEDContext switch while session is active
MM_SET_CONTEXT_FAILEDContext switch operation failed
MM_CAPABILITY_NOT_AVAILABLEFeature requires a capability not configured
MM_INVALID_INPUTBad parameters
MM_INVALID_CONFIGInvalid configuration
MM_PORT_IN_USEPort already in use
System
MM_UNKNOWN_TOOLUnknown tool name
MM_INTERNAL_ERRORInternal server error
MM_BATCH_TIMEOUTbatchTimeoutMs deadline exceeded in run_steps
MM_CDP_BLOCKEDCDP method is blocked (destructive to session); on mobile the Hermes blocklist maps here too
MM_CDP_FAILEDCDP command execution failed or timed out (on mobile, the underlying HERMES_* code is in the message)
MM_HERMES_FAILEDhermes_targets discovery failed (underlying HERMES_* code is in the message)
MM_HERMES_NOT_AVAILABLEhermes_targets used outside a mobile (iOS/Android) session
MM_DEVICE_ACTION_FAILEDA mobile device action (swipe, tap, app control, clipboard, etc.) failed
MM_DEVICE_NOT_AVAILABLEA mobile-only tool was invoked without a mobile driver capability
MM_TOOL_NOT_SUPPORTED_ON_PLATFORMTool gated off the active platform (browser-only tool on mobile, or mobile-only tool on browser)

Development

yarn build        # Build the package
yarn test         # Run tests and type checks
yarn lint         # Lint everything
yarn lint:fix     # Auto-fix lint issues

License

(MIT OR Apache-2.0)

Keywords

playwright

FAQs

Package last updated on 29 Jul 2026

Related posts