MCP (Model Context Protocol) server for MetaMask Extension visual testing with LLM agents.
This package provides the core MCP server infrastructure for enabling LLM agents to interact with the MetaMask browser extension through Playwright.
- Node.js ^20 || ^22 || >=24 (required)
- TypeScript >=5.0 (for consumer type definitions)
- Playwright
^1.49.0(peer dependency)
yarn add @metamask/client-mcp-core┌─────────────────────────────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────────────────────────────────┘
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. │
│ } │
└─────────────────────────────────────────────────────────────────────────┘
| Component | Description |
|---|---|
createMcpServer() |
Factory function that creates the MCP server instance |
ISessionManager |
Interface that consumers must implement for session management |
setSessionManager() |
Injects the consumer's session manager into the core |
WorkflowContext |
Container for browser capability and optional capabilities |
EnvironmentConfig |
Configuration discriminated by 'e2e' or 'prod' mode |
The package defines several capabilities that consumers can provide.
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;
};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 };
};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;
};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
};
};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;
};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;
};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()
The createMcpServer() function accepts a configuration object:
export type McpServerConfig = {
name: string;
version: string;
onCleanup?: () => Promise<void>;
logger?: (message: string) => void;
};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();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'): void {
if (this.hasActiveSession()) {
throw new Error('Cannot switch context while session is active');
}
// Switch environment context
}
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);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',
};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(', ')}`);Custom tool handlers are not supported. The server uses a fixed set of handlers for the provided 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>;
};
};Build the extension from source. Requires BuildCapability.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
buildType |
"build:test" |
"build:test" |
Build script to run |
force |
boolean |
false |
Force rebuild even if build exists |
Output:
{
buildType: 'build:test';
extensionPathResolved: string; // Absolute path to built extension
}Example:
{ "buildType": "build:test", "force": true }Launch a headed Chrome browser with the extension loaded. This is typically the first tool called.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
autoBuild |
boolean |
true |
Auto-build if extension not found |
stateMode |
"default" | "onboarding" | "custom" |
"default" |
Wallet initialization mode |
fixturePreset |
string |
- | Named preset when stateMode="custom" |
fixture |
object |
- | Direct fixture object when stateMode="custom" |
ports.anvil |
number |
8545 |
Anvil RPC port |
ports.fixtureServer |
number |
12345 |
Fixture server port |
slowMo |
number |
0 |
Slow down actions (ms) for debugging |
extensionPath |
string |
- | Custom extension directory path |
goal |
string |
- | Session goal for knowledge store |
flowTags |
string[] |
- | Flow categorization tags |
tags |
string[] |
- | Free-form tags |
seedContracts |
string[] |
- | Contracts to deploy on launch |
State Modes:
default- Pre-onboarded wallet with 25 ETH, ready to useonboarding- Fresh state, requires wallet setup flowcustom- 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"]
}Stop the browser and all services (Anvil, fixture server). Always call when done.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
sessionId |
string |
- | Optional session ID to clean up |
Output:
{
cleanedUp: boolean; // Whether cleanup was performed
}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 }[];
};
}List all visible data-testid attributes on the current page. Use to discover interaction targets.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
number |
150 |
Maximum 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
}
]
}Get a trimmed accessibility tree with deterministic refs (e1, e2, ...). Refs can be used with mm_click and mm_type.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
rootSelector |
string |
- | 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"] }
]
}Comprehensive screen state combining extension state, testIds, and accessibility snapshot. Optionally includes screenshot.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
includeScreenshot |
boolean |
false |
Capture screenshot |
screenshotName |
string |
- | Screenshot filename |
includeScreenshotBase64 |
boolean |
false |
Include 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
}Click an element. Specify exactly ONE of: a11yRef, testId, or selector.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
a11yRef |
string |
- | Accessibility ref from mm_accessibility_snapshot (e.g., "e5") |
testId |
string |
- | data-testid attribute value |
selector |
string |
- | CSS selector |
timeoutMs |
number |
15000 |
Max 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" }Type text into an input element. Specify exactly ONE of: a11yRef, testId, or selector.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
a11yRef |
string |
- | Accessibility ref |
testId |
string |
- | data-testid value |
selector |
string |
- | CSS selector |
text |
string |
required | Text to type |
timeoutMs |
number |
15000 |
Max wait time |
Output:
{
typed: boolean;
target: string;
textLength: number;
}Example:
{ "testId": "amount-input", "text": "0.5" }Wait for an element to become visible. Specify exactly ONE of: a11yRef, testId, or selector.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
a11yRef |
string |
- | Accessibility ref |
testId |
string |
- | data-testid value |
selector |
string |
- | CSS selector |
timeoutMs |
number |
15000 |
Max wait time (100-120000) |
Output:
{
found: boolean;
target: string;
}Read from or write to the browser clipboard. Useful for pasting content (e.g., Secret Recovery Phrase) into components that support paste functionality.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
action |
"write" | "read" |
required | Clipboard action |
text |
string |
- | 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
Navigate to a specific screen in the extension.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
screen |
"home" | "settings" | "notification" | "url" |
required | Target screen |
url |
string |
- | Required when screen="url" |
Output:
{
navigated: boolean;
currentUrl: string;
}Examples:
{ "screen": "home" }
{ "screen": "settings" }
{ "screen": "url", "url": "https://app.uniswap.org" }Wait for a notification popup to appear (e.g., after dApp interaction). Sets the notification page as active.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
timeoutMs |
number |
15000 |
Max wait time (1000-60000) |
Output:
{
found: boolean;
pageUrl: string;
}Switch the active page for subsequent interactions.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
role |
"extension" | "notification" | "dapp" | "other" |
- | Tab role to switch to |
url |
string |
- | URL prefix to match |
Output:
{
switched: boolean;
activeTab: {
role: TabRole;
url: string;
}
}Example:
{ "role": "dapp" }
{ "url": "https://app.uniswap.org" }Close a specific tab. Cannot close the extension home page.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
role |
"notification" | "dapp" | "other" |
- | Tab role to close |
url |
string |
- | URL prefix to match |
Output:
{
closed: boolean;
closedUrl: string;
}Capture a screenshot and save to test-artifacts/screenshots/.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
string |
required | Filename (without extension) |
fullPage |
boolean |
true |
Capture full page |
selector |
string |
- | Capture specific element only |
includeBase64 |
boolean |
false |
Include base64 in response |
Output:
{
path: string; // File path
width: number;
height: number;
base64?: string; // If includeBase64=true
}Deploy a smart contract to the local Anvil node. Requires ContractSeedingCapability.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
contractName |
string |
required | Contract to deploy (see list below) |
hardfork |
string |
"prague" |
EVM hardfork |
deployerOptions.fromAddress |
string |
- | Impersonate address |
deployerOptions.fromPrivateKey |
string |
- | Deploy from specific key |
Available Contracts:
| Name | Description |
|---|---|
hst |
ERC-20 TST token |
nfts |
ERC-721 NFT collection |
erc1155 |
ERC-1155 multi-token |
piggybank |
Simple ETH storage |
failing |
Always reverts (error testing) |
multisig |
Multi-signature wallet |
entrypoint |
ERC-4337 EntryPoint |
simpleAccountFactory |
ERC-4337 account factory |
verifyingPaymaster |
ERC-4337 paymaster |
Output:
{
contractName: string;
contractAddress: string;
deployedAt: string; // ISO timestamp
}Deploy multiple contracts in sequence.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
contracts |
string[] |
required | Contracts to deploy (1-9) |
hardfork |
string |
"prague" |
EVM hardfork |
Output:
{
deployed: [{ contractName, contractAddress, deployedAt }];
failed: [{ contractName, error }];
}Get the deployed address of a contract.
Input:
| Parameter | Type | Description |
|---|---|---|
contractName |
string |
Contract name to look up |
Output:
{
contractName: string;
contractAddress: string | null;
}List all contracts deployed in this session.
Input: None
Output:
{
contracts: [{
contractName: string;
contractAddress: string;
deployedAt: string;
}];
}The knowledge store enables cross-session learning by recording tool invocations and their context.
Get the last N step records from the knowledge store.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
n |
number |
20 |
Number of steps (1-200) |
scope |
"current" | "all" | { sessionId } |
"current" |
Which sessions to query |
filters.flowTag |
string |
- | Filter by flow tag |
filters.tag |
string |
- | Filter by tag |
filters.screen |
string |
- | Filter by screen |
filters.sinceHours |
number |
- | 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:
| Parameter | Type | Default | Description |
|---|---|---|---|
query |
string |
required | Search query (1-200 chars) |
limit |
number |
20 |
Max results (1-100) |
scope |
"current" | "all" | { sessionId } |
"all" |
Which sessions to search |
filters |
KnowledgeFilters |
- | Additional filters |
Output:
{
matches: KnowledgeStepSummary[];
query: string;
}Generate a recipe-like summary of steps taken in a session.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
scope |
"current" | { sessionId } |
"current" |
Session to summarize |
Output:
{
sessionId: string;
stepCount: number;
recipe: [{
stepNumber: number;
tool: string;
notes: string;
}];
}List recent sessions with metadata.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
number |
10 |
Max sessions (1-50) |
filters |
KnowledgeFilters |
- | Filter options |
Output:
{
sessions: [{
sessionId: string;
createdAt: string;
goal?: string;
flowTags: string[];
tags: string[];
}];
}Execute multiple tools in sequence. Reduces round trips for multi-step flows.
Input:
| Parameter | Type | Default | Description |
|---|---|---|---|
steps |
array |
required | Tool calls to execute (1-50) |
steps[].tool |
string |
required | Tool name (e.g., mm_click) |
steps[].args |
object |
{} |
Tool arguments |
stopOnError |
boolean |
false |
Stop 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
}yarn buildyarn test# In this repo
yarn build && yalc publish
# In consumer repo
yalc add @metamask/client-mcp-coreMIT