Skip to main content

Thunderbird Marionette MCP

CI PyPI version Python versions License: MIT

An MCP (Model Context Protocol) server exposing UI automation of a running Thunderbird via the native Marionette protocol. Lets an AI assistant click buttons, type text, install extensions, capture screenshots, run chrome-scope JavaScript, and drive Thunderbird end-to-end.

What & why

Existing Thunderbird MCP servers (TKasperczyk/thunderbird-mcp, U-C4N/Thunderbird-MCP, and others) all work through the WebExtension API from inside Thunderbird. They handle mail/folder/contact data but cannot click UI, invoke hotkeys, or interact with extension popups.

Marionette is Gecko's built-in automation protocol. It gives full chrome and content scope, including popups, dialogs, Services, Cc/Ci, MailServices, and the WebExtension popup DOM. This server wraps Marionette as MCP tools, so an LLM client can drive Thunderbird for extension development and end-to-end testing.

Like "playwright for Thunderbird".

Install

Package on PyPI: https://pypi.org/project/tb-marionette-mcp/

uv tool install tb-marionette-mcp

Or with pipx:

pipx install tb-marionette-mcp

Or plain pip (into an isolated venv, not system Python):

pip install tb-marionette-mcp

Prerequisites

  • Thunderbird 153 on PATH (or via TB_MCP_BINARY). Live-tested against TB 153; TB 140–152 may work but is untested — chrome-only XUL window fallbacks target TB 153 API surface.
  • Python 3.11+
  • Linux (Fedora / Ubuntu tested), macOS. Windows is not supported yet.

Install Thunderbird:

# Fedora
sudo dnf install thunderbird

# Ubuntu / Debian
sudo apt install thunderbird

# macOS
brew install --cask thunderbird

Configure your MCP client

Claude Desktop

Add to ~/.config/Claude/claude_desktop_config.json (or the platform equivalent):

{
  "mcpServers": {
    "tb-marionette": {
      "command": "uv",
      "args": ["tool", "run", "tb-marionette-mcp"]
    }
  }
}

Claude Code CLI

claude mcp add tb-marionette -- uv tool run tb-marionette-mcp

opencode

Add to opencode.json:

{
  "mcp": {
    "tb-marionette": {
      "type": "local",
      "command": ["uv", "tool", "run", "tb-marionette-mcp"]
    }
  }
}

Quickstart

Three worked examples: extension dev cycle, mail-reader navigation, and raw chrome-JS access. All tool calls below are JSON-RPC payloads exactly as an MCP client sends them.

A. Extension dev cycle — install, click, screenshot

1. Launch Thunderbird with a dedicated profile:

{"tool": "thunderbird_launch", "arguments": {"profile": "test-profile"}}

2. Install a temporary WebExtension:

{"tool": "extension_install", "arguments": {"xpi_path": "/abs/path/to/ext.xpi", "temporary": true}}

3. Find a chrome-scope button (e.g. addon toolbar button):

{"tool": "find_element", "arguments": {"strategy": "id", "selector": "button-appmenu", "context": "chrome"}}

4. Click it:

{"tool": "click", "arguments": {"element_id": "<element_id from step 3>", "context": "chrome"}}

5. Capture a screenshot:

{"tool": "screenshot", "arguments": {"full": true}}

Result contains base64-encoded PNG under data_base64.

6. Reload after editing the source:

{"tool": "extension_reload", "arguments": {"addon_id": "yourext@example.com", "xpi_path": "/abs/path/to/ext.xpi"}}

7. Trigger a WebExtension command shortcut directly (bypasses key dispatch — useful when TB chrome-XUL windows swallow WebDriver key events):

{"tool": "extension_trigger_command", "arguments": {"addon_id": "yourext@example.com", "command_name": "my-shortcut"}}

B. Mail-reader — open Inbox, read first message

Assumes an account is already configured in the profile.

1. Attach to a running Thunderbird (or launch, as in A.1):

{"tool": "thunderbird_status", "arguments": {}}

Any subsequent tool call auto-connects to TB_MCP_MARIONETTE_PORT if TB is already running with --marionette.

2. Drive the 3-pane via chrome-scope JS — select INBOX and read the first message's subject. This is the stable pattern (documented in tests/integration/test_mail_ui_navigation.py); avoids XUL widget IDs that shift between TB releases:

{
  "tool": "execute_script",
  "arguments": {
    "context": "chrome",
    "async_": true,
    "timeout": 30.0,
    "script": "let [resolve] = arguments; (async () => { let mainWin = Services.wm.getMostRecentWindow('mail:3pane'); let about3pane = mainWin.document.getElementById('tabmail').currentAbout3Pane; let acctMgr = Cc['@mozilla.org/messenger/account-manager;1'].getService(Ci.nsIMsgAccountManager); let inbox = acctMgr.allServers[0].rootFolder.getChildNamed('INBOX'); await about3pane.displayFolder(inbox.URI); await new Promise(r => about3pane.setTimeout(r, 200)); let threadTree = about3pane.document.getElementById('threadTree'); threadTree.selectedIndex = 0; let hdr = about3pane.gDBView.getMsgHdrAt(0); resolve({subject: hdr.mime2DecodedSubject, from: hdr.mime2DecodedAuthor}); })();"
  }
}

Response: {"result": {"subject": "...", "from": "..."}}.

3. Capture the message reader:

{"tool": "screenshot", "arguments": {"full": true}}

C. Chrome-JS access — inspect installed accounts

{
  "tool": "execute_script",
  "arguments": {
    "context": "chrome",
    "script": "let acctMgr = Cc['@mozilla.org/messenger/account-manager;1'].getService(Ci.nsIMsgAccountManager); return acctMgr.allServers.map(s => ({name: s.prettyName, type: s.type, host: s.hostName}));"
  }
}

Result: [{"name": "user@example.com", "type": "imap", "host": "imap.example.com"}, ...].

Chrome context exposes Cc, Ci, Services, MailServices, ExtensionParent, ChromeUtils — the same JSM/ESM surface TB's own UI uses. This is what makes Marionette-based automation strictly more capable than WebExtension-based approaches for UI-driving and internal introspection.

Tool reference

All 31 tools are pydantic-validated. Response objects are always dicts (or lists of dicts) so future fields can be added without breaking clients.

Process

Tool Description Key params
thunderbird_launch Start TB with --marionette and profile profile, marionette_port=2828, wait_ready=True
thunderbird_terminate SIGTERM tracked or given pid pid=None
thunderbird_status Report running / connected

Extensions

Tool Description Key params
extension_install Install XPI via Addons.install xpi_path, temporary=True
extension_uninstall Remove addon by id addon_id
extension_reload Uninstall + install (dev cycle) addon_id, xpi_path
extension_list List all addons via AddonManager
extension_trigger_command Fire commands.onCommand directly (bypass kbd) addon_id, command_name

UI

Tool Description Key params
find_element Locate one element strategy, selector, context="chrome"
find_elements Locate many elements strategy, selector, context="chrome"
click Click an element element_id
type_text Type text (optional clear first) element_id, text, clear=False
get_text Element inner text element_id
get_attribute HTML attribute value element_id, name
get_property DOM property value element_id, name
is_displayed Visibility check element_id
list_windows All open window handles + title/url
switch_to_window Switch active window handle
switch_to_frame Switch into an iframe element_id
switch_to_default Switch back to top-level content
wait_for_element Poll until element is present (and visible) strategy, selector, timeout=10.0, visible

Strategy enum: id | css | xpath | link_text | partial_link_text | tag_name | class_name | name.

Keys

Tool Description Key params
send_keys Send raw keys globally or to element keys, element_id=None
send_hotkey Parse & dispatch a chord chord (e.g. "Ctrl+Shift+N")

Chord grammar: Mod (+ Mod)* + Key. Modifiers: Ctrl | Alt | Shift | Meta | Cmd (Cmd = Meta). Named keys: Enter | Escape | Tab | Space | Delete | Backspace | Up | Down | Left | Right | Home | End | PageUp | PageDown | Insert | F1..F12.

Scripts

Tool Description Key params
execute_script Run JS in chrome or content scope script, args=[], context="chrome", async_=False
wait_for_condition Poll JS predicate until truthy script, timeout=30, poll_interval=0.5

chrome context has full Cc / Ci / Services / MailServices access.

Diagnostics

Tool Description Key params
screenshot PNG/JPEG of screen or element element_id=None, format, full
get_page_source Current DOM serialized context="content"
get_current_url URL of active tab / window
get_window_title Title of active window
get_console_logs Chrome console messages (optional filter) clear=False, level=None
get_marionette_log Tail stderr of a TB we launched

Environment variables

Variable Default Meaning
TB_MCP_BINARY which thunderbird Thunderbird executable
TB_MCP_MARIONETTE_HOST 127.0.0.1 Marionette host
TB_MCP_MARIONETTE_PORT 2828 Marionette port
TB_MCP_LOG_LEVEL INFO structlog level
TB_MCP_STARTUP_TIMEOUT 30 seconds to wait for TB port open
TB_MCP_INTEGRATION 1 0 skips integration tests

Troubleshooting

  • Port 2828 already in use — another TB instance is running with Marionette. Either terminate it (thunderbird_terminate or pkill thunderbird) or launch on a different port via marionette_port=2829.
  • TB does not respond — check thunderbird_status; if running=true but connected=false, TB started without --marionette. Kill and relaunch.
  • Extension install fails — for temporary=false the XPI must be signed by Mozilla; for dev use temporary=true (unsigned, cleared on restart).
  • CI without display — wrap the pytest / launch command with xvfb-run -a ... or set up Xvfb and export DISPLAY=:0.
  • Attach to externally-started TB — do not call thunderbird_launch. If TB is already running on TB_MCP_MARIONETTE_PORT, any tool call auto-connects. get_marionette_log returns available=false in that mode (we have no stderr handle).

Development

uv sync                        # install deps + editable
uv run pytest                  # full suite (unit + integration)
uv run pytest --no-integration # unit only
uv run ruff check              # lint
uv run mypy                    # type-check

Integration tests spawn a real Thunderbird under xvfb-run and default ON; opt-out via --no-integration or TB_MCP_INTEGRATION=0.

Roadmap

  • Prebuilt Fedora + Thunderbird CI Docker image (ghcr.io/hubbitus/tb-mcp-ci)
  • Windows support
  • MCP protocol 2.0 migration
  • WebDriver BiDi transport (Marionette wire is on Mozilla's sunset roadmap)

References

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tb_marionette_mcp-0.2.1.tar.gz (54.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tb_marionette_mcp-0.2.1-py3-none-any.whl (23.8 kB view details)

Uploaded Python 3

File details

Details for the file tb_marionette_mcp-0.2.1.tar.gz.

File metadata

  • Download URL: tb_marionette_mcp-0.2.1.tar.gz
  • Upload date:
  • Size: 54.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for tb_marionette_mcp-0.2.1.tar.gz
Algorithm Hash digest
SHA256 9fbae0080ba0beae7694b99669f8971b844e16bc9bbe1a02e10f81c35777cdfa
MD5 73739f045a3a5d292a52776a958fdcf6
BLAKE2b-256 0007fa3f6e65a266551f06748aa20434c17b305cf33168652517c1e705cc6fe9

See more details on using hashes here.

Provenance

The following attestation bundles were made for tb_marionette_mcp-0.2.1.tar.gz:

Publisher: release.yml on Hubbitus/thunderbird-marionette-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tb_marionette_mcp-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for tb_marionette_mcp-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 4da1ae40b2c6461e8e490778e9db50114ba16ed9baa46b0c565c26f2659cf740
MD5 925bdc63e1f5614a661aab3d19cd660c
BLAKE2b-256 6fe1634950271969f63d13b2ab6cd52d85c2c49a067be031bdf912bcf6ca17b0

See more details on using hashes here.

Provenance

The following attestation bundles were made for tb_marionette_mcp-0.2.1-py3-none-any.whl:

Publisher: release.yml on Hubbitus/thunderbird-marionette-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.2.2

2 files

This release

0.2.1 This release

2 files

0.2.0

2 files

0.1.4

2 files

0.1.3

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page