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.2.tar.gz (54.6 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.2-py3-none-any.whl (23.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: tb_marionette_mcp-0.2.2.tar.gz
  • Upload date:
  • Size: 54.6 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.2.tar.gz
Algorithm Hash digest
SHA256 5ce0b5f6b6c5c7cab9fc6335517ec037f4185bdca07a4429339d46e0d498bf47
MD5 e13e40cc00cbf671e450593c8a759edb
BLAKE2b-256 367e877d8f786e76b8660d95e7afd4c5288ed756eb1e2524b3cc7e186e9aab2e

See more details on using hashes here.

Provenance

The following attestation bundles were made for tb_marionette_mcp-0.2.2.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.2-py3-none-any.whl.

File metadata

File hashes

Hashes for tb_marionette_mcp-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 33d656a737ae384d1ec1701e2b7c2424b6168c2fbf31f4fda574e4a9698d9e86
MD5 e33031135afd9e51053e0a06d5680d4b
BLAKE2b-256 c4312f31a73dd1ca3d7ec3df17c83f423cacbe2eef7045399f172adf55869c76

See more details on using hashes here.

Provenance

The following attestation bundles were made for tb_marionette_mcp-0.2.2-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

This release

0.2.2 This release

2 files

0.2.1

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