New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@metamask/client-mcp-core

Package Overview
Dependencies
Maintainers
4
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@metamask/client-mcp-core

MCP server for MetaMask Extension visual testing with LLM agents

Source
npmnpm
Version
0.1.1
Version published
Weekly downloads
32K
-29.61%
Maintainers
4
Weekly downloads
 
Created
Source

@metamask/client-mcp-core

MCP (Model Context Protocol) server for MetaMask Extension visual testing with LLM agents.

Overview

This package provides the core MCP server infrastructure for enabling LLM agents to interact with the MetaMask browser extension through Playwright.

Requirements

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

Installation

yarn add @metamask/client-mcp-core

Architecture

High-Level Overview

┌─────────────────────────────────────────────────────────────────────────┐
│                           LLM Agent                                     │
│                    (Claude, GPT, etc.)                                  │
└─────────────────────────────────────────────────────────────────────────┘
                                  │
                                  │ MCP Protocol (stdio)
                                  ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                    @metamask/client-mcp-core                     │
│                                                                         │
│  Core MCP Server + Generic Tools                                        │
│  - Session management                                                   │
│  - Element interaction (click, type, wait)                              │
│  - Discovery (testIds, accessibility tree)                              │
│  - Screenshots                                                          │
│  - Knowledge store (cross-session learning)                             │
└─────────────────────────────────────────────────────────────────────────┘
                                  │
                                  │ Capability Injection
                                  ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                   MetaMask Extension Provider                           │
│                                                                         │
│  - Build capability (yarn build:test)                                   │
│  - Fixture/state management                                             │
│  - Anvil blockchain integration                                         │
│  - Contract seeding                                                     │
└─────────────────────────────────────────────────────────────────────────┘
                                  │
                                  │ Playwright
                                  ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                    Headed Chrome Browser                                │
│                    + MetaMask Extension                                 │
└─────────────────────────────────────────────────────────────────────────┘

Detailed Architecture

The package follows a capability-based dependency injection pattern that separates concerns between:

  • Core MCP Server - Protocol handling, tool routing, and generic browser interactions
  • Session Manager Interface - Abstract contract for extension-specific session management
  • Capabilities - Optional features injected by consumer implementations
┌─────────────────────────────────────────────────────────────────────────┐
│                         createMcpServer()                               │
│                                                                         │
│  ┌─────────────────────┐    ┌─────────────────────────────────────┐    │
│  │   Tool Definitions  │───▶│         Tool Handlers               │    │
│  │   (mm_click, etc.)  │    │   (registry.ts + individual tools)  │    │
│  └─────────────────────┘    └──────────────┬──────────────────────┘    │
│                                            │                            │
│                                            ▼                            │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                    ISessionManager Interface                     │   │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐  │   │
│  │  │ Page Mgmt   │ │ Navigation  │ │ Screenshots │ │ A11y Refs │  │   │
│  │  └─────────────┘ └─────────────┘ └─────────────┘ └───────────┘  │   │
│  │  ┌──────────────────────────────────────────────────────────┐   │   │
│  │  │              Optional Capabilities                        │   │   │
│  │  │  • BuildCapability      • FixtureCapability              │   │   │
│  │  │  • ChainCapability      • ContractSeedingCapability      │   │   │
│  │  │  • StateSnapshotCapability                               │   │   │
│  │  └──────────────────────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────┘
                                  │
                                  │ setSessionManager()
                                  ▼
┌─────────────────────────────────────────────────────────────────────────┐
│              Consumer Implementation (e.g., MetaMask)                   │
│                                                                         │
│  class MetaMaskSessionManager implements ISessionManager {              │
│    // Browser context, page tracking, extension-specific logic          │
│    // Capability implementations for build, fixtures, chain, etc.       │
│  }                                                                      │
└─────────────────────────────────────────────────────────────────────────┘

Core Components

ComponentDescription
createMcpServer()Factory function that creates the MCP server instance
ISessionManagerInterface that consumers must implement for session management
setSessionManager()Injects the consumer's session manager into the core
WorkflowContextContainer for browser capability and optional capabilities
EnvironmentConfigConfiguration discriminated by 'e2e' or 'prod' mode

Capability System

The package defines several capabilities that consumers can provide.

BuildCapability (Optional)

Enables the mm_build tool. Implement this to allow LLM agents to build the extension from source.

type BuildCapability = {
  // Build the extension (e.g., yarn build:test)
  build(options?: BuildOptions): Promise<BuildResult>;

  // Get path to built extension directory
  getExtensionPath(): string;

  // Check if extension is already built
  isBuilt(): Promise<boolean>;
};

type BuildOptions = {
  buildType?: string; // e.g., "build:test"
  force?: boolean; // Force rebuild even if exists
};

type BuildResult = {
  success: boolean;
  extensionPath: string;
  durationMs: number;
  error?: string;
};

FixtureCapability (Optional)

Enables wallet state management through fixtures. Essential for E2E testing where you need reproducible wallet states.

type FixtureCapability = {
  // Start fixture server with given wallet state
  start(state: WalletState): Promise<void>;

  // Stop fixture server
  stop(): Promise<void>;

  // Get default pre-onboarded wallet state (25 ETH, unlocked)
  getDefaultState(): WalletState;

  // Get fresh onboarding state (no wallet configured)
  getOnboardingState(): WalletState;

  // Resolve a named preset to fixture data
  resolvePreset(presetName: string): WalletState;
};

type WalletState = {
  data: Record<string, unknown>; // Extension storage state
  meta?: { version: number };
};

ChainCapability (Optional)

Manages local blockchain (Anvil) for E2E testing. Required for contract interactions.

type ChainCapability = {
  // Start the local Anvil node
  start(): Promise<void>;

  // Stop the Anvil node
  stop(): Promise<void>;

  // Check if Anvil is running
  isRunning(): boolean;

  // Set the port for the Anvil node
  setPort(port: number): void;
};

ContractSeedingCapability (Optional)

Enables smart contract deployment tools (mm_seed_contract, mm_seed_contracts, etc.).

type ContractSeedingCapability = {
  // Deploy a single contract
  deployContract(
    name: string,
    options?: DeployOptions,
  ): Promise<ContractDeployment>;

  // Deploy multiple contracts in sequence
  deployContracts(
    names: string[],
    options?: DeployOptions,
  ): Promise<{
    deployed: ContractDeployment[];
    failed: { name: string; error: string }[];
  }>;

  // Get deployed contract address by name
  getContractAddress(name: string): string | null;

  // List all deployed contracts in this session
  listDeployedContracts(): ContractInfo[];

  // Get available contract names
  getAvailableContracts(): string[];

  // Clear the deployment registry
  clearRegistry(): void;

  // Initialize the capability (called during session launch)
  initialize(): void;
};

type DeployOptions = {
  hardfork?: string; // EVM hardfork (default: "prague")
  deployerOptions?: {
    fromAddress?: string; // Impersonate address
    fromPrivateKey?: string; // Deploy from specific key
  };
};

StateSnapshotCapability (Optional)

type StateSnapshotCapability = {
  // Get detailed state snapshot
  getState(page: Page, options: StateOptions): Promise<StateSnapshot>;

  // Detect current screen from page content
  detectCurrentScreen(page: Page): Promise<string>;
};

type StateOptions = {
  extensionId?: string;
  chainId?: number;
};

MockServerCapability (Optional)

Enables mock server for API testing scenarios.

type MockServerCapability = {
  // Start the mock server
  start(): Promise<void>;

  // Stop the mock server
  stop(): Promise<void>;

  // Check if mock server is running
  isRunning(): boolean;

  // Get the server instance
  getServer(): unknown;

  // Get the port the server is running on
  getPort(): number;
};

Client Integration

How to Consume the Package

Consumers must:

  • Implement ISessionManager - The core interface for session management
  • Inject the session manager - Call setSessionManager() before starting the server
  • Start the MCP server - Call server.start()

McpServerConfig

The createMcpServer() function accepts a configuration object:

export type McpServerConfig = {
  name: string;
  version: string;
  onCleanup?: () => Promise<void>;
  logger?: (message: string) => void;
};

Minimal Integration Example

import {
  createMcpServer,
  setSessionManager,
  ISessionManager,
  type McpServerConfig,
} from '@metamask/client-mcp-core';

// 1. Implement the ISessionManager interface
class MyExtensionSessionManager implements ISessionManager {
  // ... implement all required methods
  // See ISessionManager interface for full contract
}

// 2. Create and inject your session manager
const sessionManager = new MyExtensionSessionManager();
setSessionManager(sessionManager);

// 3. Create and start the MCP server
const config: McpServerConfig = {
  name: 'my-extension-mcp',
  version: '1.0.0',
  onCleanup: async () => {
    // Optional cleanup logic
  },
};

const server = createMcpServer(config);
await server.start();

Full Integration Example

import {
  createMcpServer,
  setSessionManager,
  ISessionManager,
  SessionLaunchInput,
  SessionLaunchResult,
  TrackedPage,
  type ExtensionState,
  type BuildCapability,
  type FixtureCapability,
  type ChainCapability,
  type ContractSeedingCapability,
  type EnvironmentMode,
} from '@metamask/client-mcp-core';
import type { Page, BrowserContext } from '@playwright/test';

class MetaMaskSessionManager implements ISessionManager {
  private context?: BrowserContext;
  private activePage?: Page;
  private extensionId?: string;
  private sessionId?: string;
  private refMap = new Map<string, string>();

  // Capabilities (inject via constructor or lazy-load)
  private buildCapability?: BuildCapability;
  private fixtureCapability?: FixtureCapability;
  private chainCapability?: ChainCapability;
  private contractSeedingCapability?: ContractSeedingCapability;

  // Session Lifecycle
  hasActiveSession(): boolean {
    return this.context !== undefined;
  }

  getSessionId(): string | undefined {
    return this.sessionId;
  }

  async launch(input: SessionLaunchInput): Promise<SessionLaunchResult> {
    // 1. Start local chain if needed
    if (this.chainCapability) {
      await this.chainCapability.start();
    }

    // 2. Start fixture server if needed
    if (this.fixtureCapability && input.stateMode !== 'onboarding') {
      const fixture = input.fixture ?? this.fixtureCapability.getDefaultState();
      await this.fixtureCapability.start(fixture);
    }

    // 3. Launch browser with extension
    // ... Playwright browser launch logic

    // 4. Return session info
    return {
      sessionId: this.sessionId!,
      extensionId: this.extensionId!,
      state: await this.getExtensionState(),
    };
  }

  async cleanup(): Promise<boolean> {
    if (!this.hasActiveSession()) return false;

    // Close browser, stop services
    await this.context?.close();
    await this.chainCapability?.stop();
    await this.fixtureCapability?.stop();

    this.context = undefined;
    this.activePage = undefined;
    return true;
  }

  // Page Management
  getPage(): Page {
    if (!this.activePage) throw new Error('No active session');
    return this.activePage;
  }

  setActivePage(page: Page): void {
    this.activePage = page;
  }

  getTrackedPages(): TrackedPage[] {
    // Return all tracked pages with roles
    return [];
  }

  getContext(): BrowserContext {
    if (!this.context) throw new Error('No active session');
    return this.context;
  }

  // Extension State
  async getExtensionState(): Promise<ExtensionState> {
    // Query extension for current state
    return {
      isLoaded: true,
      currentUrl: this.activePage?.url() ?? '',
      extensionId: this.extensionId ?? '',
      isUnlocked: false,
      currentScreen: 'unknown',
      accountAddress: null,
      networkName: null,
      chainId: null,
      balance: null,
    };
  }

  // A11y Reference Map
  setRefMap(map: Map<string, string>): void {
    this.refMap = map;
  }

  getRefMap(): Map<string, string> {
    return this.refMap;
  }

  clearRefMap(): void {
    this.refMap.clear();
  }

  resolveA11yRef(ref: string): string | undefined {
    return this.refMap.get(ref);
  }

  // Navigation
  async navigateToHome(): Promise<void> {
    // Navigate to extension home page
  }

  async navigateToSettings(): Promise<void> {
    // Navigate to extension settings page
  }

  async navigateToUrl(url: string): Promise<Page> {
    // Open URL in new tab and return the page
    return this.activePage!;
  }

  async navigateToNotification(): Promise<Page> {
    // Navigate to notification page
    return this.activePage!;
  }

  async waitForNotificationPage(timeoutMs: number): Promise<Page> {
    // Wait for notification popup to appear
    return this.activePage!;
  }

  // Screenshots
  async screenshot(options: { name: string; fullPage?: boolean }) {
    // ... screenshot logic
    return { path: '', base64: '', width: 0, height: 0 };
  }

  // Capabilities
  getBuildCapability() {
    return this.buildCapability;
  }
  getFixtureCapability() {
    return this.fixtureCapability;
  }
  getChainCapability() {
    return this.chainCapability;
  }
  getContractSeedingCapability() {
    return this.contractSeedingCapability;
  }
  getStateSnapshotCapability() {
    return undefined;
  }

  // Environment
  getEnvironmentMode(): EnvironmentMode {
    return 'e2e';
  }

  // Required by interface but implementation-specific
  classifyPageRole(
    page: Page,
  ): 'extension' | 'notification' | 'dapp' | 'other' {
    return 'extension';
  }
  getSessionState() {
    return undefined;
  }
  getSessionMetadata() {
    return undefined;
  }

  // Context Management
  setContext(context: 'e2e' | 'prod', options?: Record<string, unknown>): void {
    if (this.hasActiveSession()) {
      throw new Error('Cannot switch context while session is active');
    }
    // Switch environment context and apply optional context-specific config
    void options;
  }

  getContextInfo() {
    return {
      currentContext: this.getEnvironmentMode(),
      hasActiveSession: this.hasActiveSession(),
      sessionId: this.sessionId ?? null,
      capabilities: {
        available: [
          this.buildCapability && 'build',
          this.fixtureCapability && 'fixture',
          this.chainCapability && 'chain',
          this.contractSeedingCapability && 'contractSeeding',
        ].filter(Boolean) as string[],
      },
      canSwitchContext: !this.hasActiveSession(),
    };
  }
}

// Bootstrap the server
async function main() {
  const sessionManager = new MetaMaskSessionManager();
  setSessionManager(sessionManager);

  const server = createMcpServer({
    name: 'metamask-mcp',
    version: '1.0.0',
  });

  await server.start();
}

main().catch(console.error);

Environment Configuration

The package supports two environment modes:

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

// Production-like Environment
const prodConfig: ProdEnvironmentConfig = {
  environment: 'prod',
  extensionName: 'MetaMask',
  toolPrefix: 'mm',
};

Context Switching Options

mm_set_context supports an optional options payload that is forwarded to the session manager's setContext(context, options) implementation.

type SetContextInput = {
  context: 'e2e' | 'prod';
  options?: Record<string, unknown>;
};

// Example: switch to e2e and pass context-specific overrides
await handleSetContext({
  context: 'e2e',
  options: {
    mockServer: {
      enabled: true,
      port: 18000,
    },
  },
});

Use options only for context-specific configuration your ISessionManager implementation understands.

Custom Tool Definitions

The package provides a fixed set of tools prefixed with mm_. Custom tool injection is currently not supported. You can inspect the available tool definitions using getToolDefinitions():

import { getToolDefinitions } from '@metamask/client-mcp-core';

const tools = getToolDefinitions();
console.log(`Available tools: ${tools.map((t) => t.name).join(', ')}`);

Registering Custom Tool Handlers

Custom tool handlers are not supported. The server uses a fixed set of handlers for the provided tools.

Available Tools

All tools are prefixed with mm_ and return a standardized response format:

type ToolResponse<Result> =
  | {
      ok: true;
      meta: {
        timestamp: string; // ISO timestamp
        sessionId?: string; // Current session ID
        durationMs: number; // Operation duration
      };
      result: Result; // Success payload
    }
  | {
      ok: false;
      meta: {
        timestamp: string;
        sessionId?: string;
        durationMs: number;
      };
      error: {
        code: string;
        message: string;
        details?: Record<string, unknown>;
      };
    };

Session Management Tools

mm_build

Build the extension from source. Requires BuildCapability.

Input:

ParameterTypeDefaultDescription
buildType"build:test""build:test"Build script to run
forcebooleanfalseForce rebuild even if build exists

Output:

{
  buildType: 'build:test';
  extensionPathResolved: string; // Absolute path to built extension
}

Example:

{ "buildType": "build:test", "force": true }

mm_launch

Launch a headed Chrome browser with the extension loaded. This is typically the first tool called.

Input:

ParameterTypeDefaultDescription
autoBuildbooleantrueAuto-build if extension not found
stateMode"default" | "onboarding" | "custom""default"Wallet initialization mode
fixturePresetstring-Named preset when stateMode="custom"
fixtureobject-Direct fixture object when stateMode="custom"
ports.anvilnumber8545Anvil RPC port
ports.fixtureServernumber12345Fixture server port
slowMonumber0Slow down actions (ms) for debugging
extensionPathstring-Custom extension directory path
goalstring-Session goal for knowledge store
flowTagsstring[]-Flow categorization tags
tagsstring[]-Free-form tags
seedContractsstring[]-Contracts to deploy on launch

State Modes:

  • default - Pre-onboarded wallet with 25 ETH, ready to use
  • onboarding - Fresh state, requires wallet setup flow
  • custom - Use provided fixture or preset

Output:

{
  sessionId: string;        // Unique session identifier
  extensionId: string;      // Extension's Chrome ID
  state: ExtensionState;    // Initial extension state
  prerequisites?: [{        // Steps taken before launch
    step: string;
    description: string;
  }];
}

Example:

{
  "stateMode": "default",
  "goal": "Test send flow",
  "flowTags": ["send"],
  "seedContracts": ["hst"]
}

mm_cleanup

Stop the browser and all services (Anvil, fixture server). Always call when done.

Input:

ParameterTypeDefaultDescription
sessionIdstring-Optional session ID to clean up

Output:

{
  cleanedUp: boolean; // Whether cleanup was performed
}

Discovery Tools

mm_get_state

Get current extension state including screen, balance, network, and account.

Input: None

Output:

{
  state: {
    isLoaded: boolean;
    currentUrl: string;
    extensionId: string;
    isUnlocked: boolean;
    currentScreen: ScreenName;
    accountAddress: string | null;
    networkName: string | null;
    chainId: number | null;
    balance: string | null;
  };
  tabs?: {
    active: { role: TabRole; url: string };
    tracked: { role: TabRole; url: string }[];
  };
}

mm_list_testids

List all visible data-testid attributes on the current page. Use to discover interaction targets.

Input:

ParameterTypeDefaultDescription
limitnumber150Maximum items to return (1-500)

Output:

{
  items: [{
    testId: string;    // The data-testid value
    tag: string;       // HTML tag (button, input, div, etc.)
    text?: string;     // Visible text content
    visible: boolean;  // Whether element is visible
  }];
}

Example Output:

{
  "items": [
    {
      "testId": "account-menu-icon",
      "tag": "button",
      "text": "",
      "visible": true
    },
    {
      "testId": "eth-overview-send",
      "tag": "button",
      "text": "Send",
      "visible": true
    },
    {
      "testId": "token-balance",
      "tag": "span",
      "text": "25 ETH",
      "visible": true
    }
  ]
}

mm_accessibility_snapshot

Get a trimmed accessibility tree with deterministic refs (e1, e2, ...). Refs can be used with mm_click and mm_type.

Input:

ParameterTypeDefaultDescription
rootSelectorstring-CSS selector to scope the snapshot

Included Roles:

  • Actionable: button, link, checkbox, radio, switch, textbox, combobox, menuitem
  • Important: dialog, alert, status, heading

Output:

{
  nodes: [{
    ref: string;       // Deterministic ref (e1, e2, e3, ...)
    role: string;      // ARIA role
    name: string;      // Accessible name
    disabled?: boolean;
    checked?: boolean;
    expanded?: boolean;
    path: string[];    // Ancestor path for context
  }];
}

Example Output:

{
  "nodes": [
    { "ref": "e1", "role": "button", "name": "Send", "path": ["main", "div"] },
    { "ref": "e2", "role": "button", "name": "Swap", "path": ["main", "div"] },
    { "ref": "e3", "role": "textbox", "name": "Amount", "path": ["form"] }
  ]
}

mm_describe_screen

Comprehensive screen state combining extension state, testIds, and accessibility snapshot. Optionally includes screenshot.

Input:

ParameterTypeDefaultDescription
includeScreenshotbooleanfalseCapture screenshot
screenshotNamestring-Screenshot filename
includeScreenshotBase64booleanfalseInclude base64 in response

Output:

{
  state: ExtensionState;
  testIds: { items: TestIdItem[] };
  a11y: { nodes: A11yNodeTrimmed[] };
  screenshot: {
    path: string;
    width: number;
    height: number;
    base64?: string;
  } | null;
  priorKnowledge?: PriorKnowledgeV1;  // Past session hints
}

Interaction Tools

mm_click

Click an element. Specify exactly ONE of: a11yRef, testId, or selector.

Input:

ParameterTypeDefaultDescription
a11yRefstring-Accessibility ref from mm_accessibility_snapshot (e.g., "e5")
testIdstring-data-testid attribute value
selectorstring-CSS selector
timeoutMsnumber15000Max wait time (0-60000)

Output:

{
  clicked: boolean;
  target: string;                 // Resolved selector
  pageClosedAfterClick?: boolean; // True if click caused page close
}

Examples:

{ "a11yRef": "e5" }
{ "testId": "confirm-btn" }
{ "selector": "button.primary" }

mm_type

Type text into an input element. Specify exactly ONE of: a11yRef, testId, or selector.

Input:

ParameterTypeDefaultDescription
a11yRefstring-Accessibility ref
testIdstring-data-testid value
selectorstring-CSS selector
textstringrequiredText to type
timeoutMsnumber15000Max wait time

Output:

{
  typed: boolean;
  target: string;
  textLength: number;
}

Example:

{ "testId": "amount-input", "text": "0.5" }

mm_wait_for

Wait for an element to become visible. Specify exactly ONE of: a11yRef, testId, or selector.

Input:

ParameterTypeDefaultDescription
a11yRefstring-Accessibility ref
testIdstring-data-testid value
selectorstring-CSS selector
timeoutMsnumber15000Max wait time (100-120000)

Output:

{
  found: boolean;
  target: string;
}

mm_clipboard

Read from or write to the browser clipboard. Useful for pasting content (e.g., Secret Recovery Phrase) into components that support paste functionality.

Input:

ParameterTypeDefaultDescription
action"write" | "read"requiredClipboard action
textstring-Text to write (required when action="write")

Output:

{
  action: "write" | "read";
  success: boolean;
  text?: string;  // Present when action="read" and successful
}

Examples:

{ "action": "write", "text": "word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12" }
{ "action": "read" }

Use Case - Fast SRP Entry:

1. mm_clipboard { "action": "write", "text": "abandon abandon ... about" }
2. mm_click { "testId": "srp-input-import__paste-button" }
→ All 12 words populated instantly via paste

mm_navigate

Navigate to a specific screen in the extension.

Input:

ParameterTypeDefaultDescription
screen"home" | "settings" | "notification" | "url"requiredTarget screen
urlstring-Required when screen="url"

Output:

{
  navigated: boolean;
  currentUrl: string;
}

Examples:

{ "screen": "home" }
{ "screen": "settings" }
{ "screen": "url", "url": "https://app.uniswap.org" }

Multi-Tab Tools

mm_wait_for_notification

Wait for a notification popup to appear (e.g., after dApp interaction). Sets the notification page as active.

Input:

ParameterTypeDefaultDescription
timeoutMsnumber15000Max wait time (1000-60000)

Output:

{
  found: boolean;
  pageUrl: string;
}

mm_switch_to_tab

Switch the active page for subsequent interactions.

Input:

ParameterTypeDefaultDescription
role"extension" | "notification" | "dapp" | "other"-Tab role to switch to
urlstring-URL prefix to match

Output:

{
  switched: boolean;
  activeTab: {
    role: TabRole;
    url: string;
  }
}

Example:

{ "role": "dapp" }
{ "url": "https://app.uniswap.org" }

mm_close_tab

Close a specific tab. Cannot close the extension home page.

Input:

ParameterTypeDefaultDescription
role"notification" | "dapp" | "other"-Tab role to close
urlstring-URL prefix to match

Output:

{
  closed: boolean;
  closedUrl: string;
}

Screenshot Tools

mm_screenshot

Capture a screenshot and save to test-artifacts/screenshots/.

Input:

ParameterTypeDefaultDescription
namestringrequiredFilename (without extension)
fullPagebooleantrueCapture full page
selectorstring-Capture specific element only
includeBase64booleanfalseInclude base64 in response

Output:

{
  path: string;      // File path
  width: number;
  height: number;
  base64?: string;   // If includeBase64=true
}

Smart Contract Tools

mm_seed_contract

Deploy a smart contract to the local Anvil node. Requires ContractSeedingCapability.

Input:

ParameterTypeDefaultDescription
contractNamestringrequiredContract to deploy (see list below)
hardforkstring"prague"EVM hardfork
deployerOptions.fromAddressstring-Impersonate address
deployerOptions.fromPrivateKeystring-Deploy from specific key

Available Contracts:

NameDescription
hstERC-20 TST token
nftsERC-721 NFT collection
erc1155ERC-1155 multi-token
piggybankSimple ETH storage
failingAlways reverts (error testing)
multisigMulti-signature wallet
entrypointERC-4337 EntryPoint
simpleAccountFactoryERC-4337 account factory
verifyingPaymasterERC-4337 paymaster

Output:

{
  contractName: string;
  contractAddress: string;
  deployedAt: string; // ISO timestamp
}

mm_seed_contracts

Deploy multiple contracts in sequence.

Input:

ParameterTypeDefaultDescription
contractsstring[]requiredContracts to deploy (1-9)
hardforkstring"prague"EVM hardfork

Output:

{
  deployed: [{ contractName, contractAddress, deployedAt }];
  failed: [{ contractName, error }];
}

mm_get_contract_address

Get the deployed address of a contract.

Input:

ParameterTypeDescription
contractNamestringContract name to look up

Output:

{
  contractName: string;
  contractAddress: string | null;
}

mm_list_contracts

List all contracts deployed in this session.

Input: None

Output:

{
  contracts: [{
    contractName: string;
    contractAddress: string;
    deployedAt: string;
  }];
}

Knowledge Store Tools

The knowledge store enables cross-session learning by recording tool invocations and their context.

mm_knowledge_last

Get the last N step records from the knowledge store.

Input:

ParameterTypeDefaultDescription
nnumber20Number of steps (1-200)
scope"current" | "all" | { sessionId }"current"Which sessions to query
filters.flowTagstring-Filter by flow tag
filters.tagstring-Filter by tag
filters.screenstring-Filter by screen
filters.sinceHoursnumber-Only steps from last N hours

Output:

{
  steps: [{
    timestamp: string;
    tool: string;
    screen: ScreenName;
    snippet: string;      // Human-readable summary
    sessionId?: string;
    matchedFields?: string[];
    sessionGoal?: string;
  }];
}

Search step records by tool name, screen, testId, or accessibility names.

Input:

ParameterTypeDefaultDescription
querystringrequiredSearch query (1-200 chars)
limitnumber20Max results (1-100)
scope"current" | "all" | { sessionId }"all"Which sessions to search
filtersKnowledgeFilters-Additional filters

Output:

{
  matches: KnowledgeStepSummary[];
  query: string;
}

mm_knowledge_summarize

Generate a recipe-like summary of steps taken in a session.

Input:

ParameterTypeDefaultDescription
scope"current" | { sessionId }"current"Session to summarize

Output:

{
  sessionId: string;
  stepCount: number;
  recipe: [{
    stepNumber: number;
    tool: string;
    notes: string;
  }];
}

mm_knowledge_sessions

List recent sessions with metadata.

Input:

ParameterTypeDefaultDescription
limitnumber10Max sessions (1-50)
filtersKnowledgeFilters-Filter options

Output:

{
  sessions: [{
    sessionId: string;
    createdAt: string;
    goal?: string;
    flowTags: string[];
    tags: string[];
  }];
}

Batching Tools

mm_run_steps

Execute multiple tools in sequence. Reduces round trips for multi-step flows.

Input:

ParameterTypeDefaultDescription
stepsarrayrequiredTool calls to execute (1-50)
steps[].toolstringrequiredTool name (e.g., mm_click)
steps[].argsobject{}Tool arguments
stopOnErrorbooleanfalseStop on first error
includeObservations"none" | "failures" | "all""all"When to include state observations

Output:

{
  steps: [{
    tool: string;
    ok: boolean;
    result?: unknown;
    error?: { code: string; message: string; details?: unknown };
    meta: { durationMs: number; timestamp: string };
  }];
  summary: {
    ok: boolean;      // All steps succeeded
    total: number;
    succeeded: number;
    failed: number;
    durationMs: number;
  };
}

Example:

{
  "steps": [
    { "tool": "mm_click", "args": { "testId": "send-button" } },
    { "tool": "mm_type", "args": { "testId": "amount-input", "text": "0.1" } },
    { "tool": "mm_click", "args": { "testId": "confirm-button" } }
  ],
  "stopOnError": true
}

Development

Building

yarn build

Testing

yarn test

Local Development with yalc

# In this repo
yarn build && yalc publish

# In consumer repo
yalc add @metamask/client-mcp-core

License

MIT

Keywords

mcp

FAQs

Package last updated on 24 Feb 2026

Related posts