Skip to content

fix: connect-existing with remote/containerized Firefox - #50

Merged
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2
Mar 29, 2026
Merged

fix: connect-existing with remote/containerized Firefox#50
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2

Conversation

@MayCXC

@MayCXCMayCXC commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes several issues that prevented --connect-existing from working with Firefox running on a remote host or in a separate container (e.g., via SSH tunnel).

Bug fixes

  1. --marionette-host CLI arg was silently ignored. The option was parsed and the type had the field, but it was never passed to the options object. Geckodriver always connected to 127.0.0.1.

  2. selenium-manager downloaded a 307MB Firefox binary. In connect-existing mode Firefox is already running. Changed --browser firefox to --driver geckodriver so selenium-manager only downloads the driver (~5MB).

  3. No cleanup on disconnect. No lifecycle handling existed. The Marionette session was never released when the MCP client disconnected, so Firefox refused new connections until restarted. Added SIGTERM/SIGINT/stdin handlers that clean up Firefox and close the server before exiting, following the pattern from the MCP SDK examples. StdioServerTransport does not currently fire onclose on stdin EOF (PR #1814), so raw stdin listeners are included as a workaround.

  4. kill() skipped session cleanup. Was synchronous and did not send DELETE /session. Made async with session cleanup so Marionette accepts new connections.

  5. Seamless reconnect after Firefox restart.getFirefox() now detects a lost connection and reconnects transparently instead of throwing an error on the first call.

New features

  1. --marionette-host parameter. Controls which host geckodriver connects to for Marionette and where the BiDi WebSocket URL is rewritten to point. Supports MARIONETTE_HOST env var.

  2. BiDi support for connect-existing. Opens a WebSocket to Firefox's Remote Agent using the webSocketUrl session capability, enabling console and network event monitoring.

Test plan

  • list_pages works with --connect-existing --marionette-host host.internal
  • list_console_messages returns BiDi data
  • Quit and restart Firefox without MCP reconnect: seamless reconnect on next tool call, pages and BiDi both work
  • MCP reconnect without quitting Firefox: old container exits cleanly (no zombie), new container connects, pages and BiDi both work
  • No stale Marionette sessions after disconnect

Supersedes #51.

@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 1b0f461 to fcd5855CompareMarch 28, 2026 08:16
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 2 times, most recently from ae0f07a to 5f6b39fCompareMarch 29, 2026 11:54
@MayCXCMayCXC changed the title Enable BiDi support for connect-existing modefix: connect-existing with remote/containerized FirefoxMar 29, 2026
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 6 times, most recently from 478f62d to 4740414CompareMarch 29, 2026 13:41
When using --connect-existing to connect to Firefox running on a
different host (e.g., via SSH tunnel or in a separate container),
several issues prevented it from working:
1. The --marionette-host CLI arg was parsed but never passed to
the options object, so geckodriver always connected to 127.0.0.1.
2. selenium-manager was invoked with --browser firefox, causing it
to download a 307MB Firefox binary that connect-existing never
uses. Changed to --driver geckodriver to fetch only the driver.
3. No cleanup on disconnect: the Marionette session was never
released when the MCP client disconnected, so Firefox refused
new connections until restarted. Added SIGTERM/SIGINT/stdin
handlers that call firefox.close() before exit. Raw stdin
listeners are needed because StdioServerTransport does not fire
onclose on stdin EOF.
4. kill() was synchronous and skipped DELETE /session, leaving
stale sessions. Made it async and added session cleanup.
5. BiDi was unavailable in connect-existing mode. Added WebSocket
support via the webSocketUrl session capability, with URL
rewriting for remote hosts. BiDi subscription failure is
non-fatal so Classic WebDriver continues working.
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 4740414 to c02fc02CompareMarch 29, 2026 13:48
@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@juliandescottes@freema iterated on this a bit, it is ready now.

@freema

Copy link
Copy Markdown
Collaborator

Hey @MayCXC, this looks great, thanks! I'll merge this into main. I'll add tests and do some manual testing, then cut a new release.

Small note: kill() is now async but reset() calls it without await — I'll fix that when adding tests.

@freema
freema merged commit 6e17b50 into mozilla:mainMar 29, 2026
freema added a commit that referenced this pull request Mar 29, 2026
- Fix prettier formatting (long lines, missing line breaks)
- Fix curly brace requirement for if statements
- Fix no-base-to-string: use String() instead of .toString()
- Fix no-floating-promises: void async kill() in reset()
- Fix no-misused-promises: wrap async cleanup in void handler
- Remove unused FirefoxDisconnectedError import
- Fix IBiDi type to include subscribe method
- Fix type narrowing for bidiConnection return
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
freema added a commit that referenced this pull request Mar 29, 2026
Tests cover:
- GeckodriverHttpDriver BiDi: getBidi(), subscribe, caching, error handling
- Session cleanup: kill() and quit() send DELETE /session
- kill() resilience when DELETE fails
- marionetteHost option passthrough
- Reconnect: reset() clears driver state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@juliandescottes

Copy link
Copy Markdown
Collaborator

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

OK with me 👍

@juliandescottes

Copy link
Copy Markdown
Collaborator

Thanks a lot!

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@MayCXC@freema@juliandescottes
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix: connect-existing with remote/containerized Firefox by MayCXC · Pull Request #50 · mozilla/firefox-devtools-mcp · GitHub
Skip to content

fix: connect-existing with remote/containerized Firefox - #50

Merged
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2
Mar 29, 2026
Merged

fix: connect-existing with remote/containerized Firefox#50
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2

Conversation

@MayCXC

@MayCXCMayCXC commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes several issues that prevented --connect-existing from working with Firefox running on a remote host or in a separate container (e.g., via SSH tunnel).

Bug fixes

  1. --marionette-host CLI arg was silently ignored. The option was parsed and the type had the field, but it was never passed to the options object. Geckodriver always connected to 127.0.0.1.

  2. selenium-manager downloaded a 307MB Firefox binary. In connect-existing mode Firefox is already running. Changed --browser firefox to --driver geckodriver so selenium-manager only downloads the driver (~5MB).

  3. No cleanup on disconnect. No lifecycle handling existed. The Marionette session was never released when the MCP client disconnected, so Firefox refused new connections until restarted. Added SIGTERM/SIGINT/stdin handlers that clean up Firefox and close the server before exiting, following the pattern from the MCP SDK examples. StdioServerTransport does not currently fire onclose on stdin EOF (PR #1814), so raw stdin listeners are included as a workaround.

  4. kill() skipped session cleanup. Was synchronous and did not send DELETE /session. Made async with session cleanup so Marionette accepts new connections.

  5. Seamless reconnect after Firefox restart.getFirefox() now detects a lost connection and reconnects transparently instead of throwing an error on the first call.

New features

  1. --marionette-host parameter. Controls which host geckodriver connects to for Marionette and where the BiDi WebSocket URL is rewritten to point. Supports MARIONETTE_HOST env var.

  2. BiDi support for connect-existing. Opens a WebSocket to Firefox's Remote Agent using the webSocketUrl session capability, enabling console and network event monitoring.

Test plan

  • list_pages works with --connect-existing --marionette-host host.internal
  • list_console_messages returns BiDi data
  • Quit and restart Firefox without MCP reconnect: seamless reconnect on next tool call, pages and BiDi both work
  • MCP reconnect without quitting Firefox: old container exits cleanly (no zombie), new container connects, pages and BiDi both work
  • No stale Marionette sessions after disconnect

Supersedes #51.

@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 1b0f461 to fcd5855CompareMarch 28, 2026 08:16
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 2 times, most recently from ae0f07a to 5f6b39fCompareMarch 29, 2026 11:54
@MayCXCMayCXC changed the title Enable BiDi support for connect-existing modefix: connect-existing with remote/containerized FirefoxMar 29, 2026
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 6 times, most recently from 478f62d to 4740414CompareMarch 29, 2026 13:41
When using --connect-existing to connect to Firefox running on a
different host (e.g., via SSH tunnel or in a separate container),
several issues prevented it from working:
1. The --marionette-host CLI arg was parsed but never passed to
the options object, so geckodriver always connected to 127.0.0.1.
2. selenium-manager was invoked with --browser firefox, causing it
to download a 307MB Firefox binary that connect-existing never
uses. Changed to --driver geckodriver to fetch only the driver.
3. No cleanup on disconnect: the Marionette session was never
released when the MCP client disconnected, so Firefox refused
new connections until restarted. Added SIGTERM/SIGINT/stdin
handlers that call firefox.close() before exit. Raw stdin
listeners are needed because StdioServerTransport does not fire
onclose on stdin EOF.
4. kill() was synchronous and skipped DELETE /session, leaving
stale sessions. Made it async and added session cleanup.
5. BiDi was unavailable in connect-existing mode. Added WebSocket
support via the webSocketUrl session capability, with URL
rewriting for remote hosts. BiDi subscription failure is
non-fatal so Classic WebDriver continues working.
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 4740414 to c02fc02CompareMarch 29, 2026 13:48
@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@juliandescottes@freema iterated on this a bit, it is ready now.

@freema

Copy link
Copy Markdown
Collaborator

Hey @MayCXC, this looks great, thanks! I'll merge this into main. I'll add tests and do some manual testing, then cut a new release.

Small note: kill() is now async but reset() calls it without await — I'll fix that when adding tests.

@freema
freema merged commit 6e17b50 into mozilla:mainMar 29, 2026
freema added a commit that referenced this pull request Mar 29, 2026
- Fix prettier formatting (long lines, missing line breaks)
- Fix curly brace requirement for if statements
- Fix no-base-to-string: use String() instead of .toString()
- Fix no-floating-promises: void async kill() in reset()
- Fix no-misused-promises: wrap async cleanup in void handler
- Remove unused FirefoxDisconnectedError import
- Fix IBiDi type to include subscribe method
- Fix type narrowing for bidiConnection return
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
freema added a commit that referenced this pull request Mar 29, 2026
Tests cover:
- GeckodriverHttpDriver BiDi: getBidi(), subscribe, caching, error handling
- Session cleanup: kill() and quit() send DELETE /session
- kill() resilience when DELETE fails
- marionetteHost option passthrough
- Reconnect: reset() clears driver state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@juliandescottes

Copy link
Copy Markdown
Collaborator

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

OK with me 👍

@juliandescottes

Copy link
Copy Markdown
Collaborator

Thanks a lot!

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@MayCXC@freema@juliandescottes
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: connect-existing with remote/containerized Firefox by MayCXC · Pull Request #50 · mozilla/firefox-devtools-mcp · GitHub
Skip to content

fix: connect-existing with remote/containerized Firefox - #50

Merged
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2
Mar 29, 2026
Merged

fix: connect-existing with remote/containerized Firefox#50
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2

Conversation

@MayCXC

@MayCXCMayCXC commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes several issues that prevented --connect-existing from working with Firefox running on a remote host or in a separate container (e.g., via SSH tunnel).

Bug fixes

  1. --marionette-host CLI arg was silently ignored. The option was parsed and the type had the field, but it was never passed to the options object. Geckodriver always connected to 127.0.0.1.

  2. selenium-manager downloaded a 307MB Firefox binary. In connect-existing mode Firefox is already running. Changed --browser firefox to --driver geckodriver so selenium-manager only downloads the driver (~5MB).

  3. No cleanup on disconnect. No lifecycle handling existed. The Marionette session was never released when the MCP client disconnected, so Firefox refused new connections until restarted. Added SIGTERM/SIGINT/stdin handlers that clean up Firefox and close the server before exiting, following the pattern from the MCP SDK examples. StdioServerTransport does not currently fire onclose on stdin EOF (PR #1814), so raw stdin listeners are included as a workaround.

  4. kill() skipped session cleanup. Was synchronous and did not send DELETE /session. Made async with session cleanup so Marionette accepts new connections.

  5. Seamless reconnect after Firefox restart.getFirefox() now detects a lost connection and reconnects transparently instead of throwing an error on the first call.

New features

  1. --marionette-host parameter. Controls which host geckodriver connects to for Marionette and where the BiDi WebSocket URL is rewritten to point. Supports MARIONETTE_HOST env var.

  2. BiDi support for connect-existing. Opens a WebSocket to Firefox's Remote Agent using the webSocketUrl session capability, enabling console and network event monitoring.

Test plan

  • list_pages works with --connect-existing --marionette-host host.internal
  • list_console_messages returns BiDi data
  • Quit and restart Firefox without MCP reconnect: seamless reconnect on next tool call, pages and BiDi both work
  • MCP reconnect without quitting Firefox: old container exits cleanly (no zombie), new container connects, pages and BiDi both work
  • No stale Marionette sessions after disconnect

Supersedes #51.

@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 1b0f461 to fcd5855CompareMarch 28, 2026 08:16
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 2 times, most recently from ae0f07a to 5f6b39fCompareMarch 29, 2026 11:54
@MayCXCMayCXC changed the title Enable BiDi support for connect-existing modefix: connect-existing with remote/containerized FirefoxMar 29, 2026
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 6 times, most recently from 478f62d to 4740414CompareMarch 29, 2026 13:41
When using --connect-existing to connect to Firefox running on a
different host (e.g., via SSH tunnel or in a separate container),
several issues prevented it from working:
1. The --marionette-host CLI arg was parsed but never passed to
the options object, so geckodriver always connected to 127.0.0.1.
2. selenium-manager was invoked with --browser firefox, causing it
to download a 307MB Firefox binary that connect-existing never
uses. Changed to --driver geckodriver to fetch only the driver.
3. No cleanup on disconnect: the Marionette session was never
released when the MCP client disconnected, so Firefox refused
new connections until restarted. Added SIGTERM/SIGINT/stdin
handlers that call firefox.close() before exit. Raw stdin
listeners are needed because StdioServerTransport does not fire
onclose on stdin EOF.
4. kill() was synchronous and skipped DELETE /session, leaving
stale sessions. Made it async and added session cleanup.
5. BiDi was unavailable in connect-existing mode. Added WebSocket
support via the webSocketUrl session capability, with URL
rewriting for remote hosts. BiDi subscription failure is
non-fatal so Classic WebDriver continues working.
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 4740414 to c02fc02CompareMarch 29, 2026 13:48
@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@juliandescottes@freema iterated on this a bit, it is ready now.

@freema

Copy link
Copy Markdown
Collaborator

Hey @MayCXC, this looks great, thanks! I'll merge this into main. I'll add tests and do some manual testing, then cut a new release.

Small note: kill() is now async but reset() calls it without await — I'll fix that when adding tests.

@freema
freema merged commit 6e17b50 into mozilla:mainMar 29, 2026
freema added a commit that referenced this pull request Mar 29, 2026
- Fix prettier formatting (long lines, missing line breaks)
- Fix curly brace requirement for if statements
- Fix no-base-to-string: use String() instead of .toString()
- Fix no-floating-promises: void async kill() in reset()
- Fix no-misused-promises: wrap async cleanup in void handler
- Remove unused FirefoxDisconnectedError import
- Fix IBiDi type to include subscribe method
- Fix type narrowing for bidiConnection return
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
freema added a commit that referenced this pull request Mar 29, 2026
Tests cover:
- GeckodriverHttpDriver BiDi: getBidi(), subscribe, caching, error handling
- Session cleanup: kill() and quit() send DELETE /session
- kill() resilience when DELETE fails
- marionetteHost option passthrough
- Reconnect: reset() clears driver state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@juliandescottes

Copy link
Copy Markdown
Collaborator

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

OK with me 👍

@juliandescottes

Copy link
Copy Markdown
Collaborator

Thanks a lot!

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@MayCXC@freema@juliandescottes
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: connect-existing with remote/containerized Firefox by MayCXC · Pull Request #50 · mozilla/firefox-devtools-mcp · GitHub
Skip to content

fix: connect-existing with remote/containerized Firefox - #50

Merged
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2
Mar 29, 2026
Merged

fix: connect-existing with remote/containerized Firefox#50
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2

Conversation

@MayCXC

@MayCXCMayCXC commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes several issues that prevented --connect-existing from working with Firefox running on a remote host or in a separate container (e.g., via SSH tunnel).

Bug fixes

  1. --marionette-host CLI arg was silently ignored. The option was parsed and the type had the field, but it was never passed to the options object. Geckodriver always connected to 127.0.0.1.

  2. selenium-manager downloaded a 307MB Firefox binary. In connect-existing mode Firefox is already running. Changed --browser firefox to --driver geckodriver so selenium-manager only downloads the driver (~5MB).

  3. No cleanup on disconnect. No lifecycle handling existed. The Marionette session was never released when the MCP client disconnected, so Firefox refused new connections until restarted. Added SIGTERM/SIGINT/stdin handlers that clean up Firefox and close the server before exiting, following the pattern from the MCP SDK examples. StdioServerTransport does not currently fire onclose on stdin EOF (PR #1814), so raw stdin listeners are included as a workaround.

  4. kill() skipped session cleanup. Was synchronous and did not send DELETE /session. Made async with session cleanup so Marionette accepts new connections.

  5. Seamless reconnect after Firefox restart.getFirefox() now detects a lost connection and reconnects transparently instead of throwing an error on the first call.

New features

  1. --marionette-host parameter. Controls which host geckodriver connects to for Marionette and where the BiDi WebSocket URL is rewritten to point. Supports MARIONETTE_HOST env var.

  2. BiDi support for connect-existing. Opens a WebSocket to Firefox's Remote Agent using the webSocketUrl session capability, enabling console and network event monitoring.

Test plan

  • list_pages works with --connect-existing --marionette-host host.internal
  • list_console_messages returns BiDi data
  • Quit and restart Firefox without MCP reconnect: seamless reconnect on next tool call, pages and BiDi both work
  • MCP reconnect without quitting Firefox: old container exits cleanly (no zombie), new container connects, pages and BiDi both work
  • No stale Marionette sessions after disconnect

Supersedes #51.

@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 1b0f461 to fcd5855CompareMarch 28, 2026 08:16
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 2 times, most recently from ae0f07a to 5f6b39fCompareMarch 29, 2026 11:54
@MayCXCMayCXC changed the title Enable BiDi support for connect-existing modefix: connect-existing with remote/containerized FirefoxMar 29, 2026
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 6 times, most recently from 478f62d to 4740414CompareMarch 29, 2026 13:41
When using --connect-existing to connect to Firefox running on a
different host (e.g., via SSH tunnel or in a separate container),
several issues prevented it from working:
1. The --marionette-host CLI arg was parsed but never passed to
the options object, so geckodriver always connected to 127.0.0.1.
2. selenium-manager was invoked with --browser firefox, causing it
to download a 307MB Firefox binary that connect-existing never
uses. Changed to --driver geckodriver to fetch only the driver.
3. No cleanup on disconnect: the Marionette session was never
released when the MCP client disconnected, so Firefox refused
new connections until restarted. Added SIGTERM/SIGINT/stdin
handlers that call firefox.close() before exit. Raw stdin
listeners are needed because StdioServerTransport does not fire
onclose on stdin EOF.
4. kill() was synchronous and skipped DELETE /session, leaving
stale sessions. Made it async and added session cleanup.
5. BiDi was unavailable in connect-existing mode. Added WebSocket
support via the webSocketUrl session capability, with URL
rewriting for remote hosts. BiDi subscription failure is
non-fatal so Classic WebDriver continues working.
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 4740414 to c02fc02CompareMarch 29, 2026 13:48
@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@juliandescottes@freema iterated on this a bit, it is ready now.

@freema

Copy link
Copy Markdown
Collaborator

Hey @MayCXC, this looks great, thanks! I'll merge this into main. I'll add tests and do some manual testing, then cut a new release.

Small note: kill() is now async but reset() calls it without await — I'll fix that when adding tests.

@freema
freema merged commit 6e17b50 into mozilla:mainMar 29, 2026
freema added a commit that referenced this pull request Mar 29, 2026
- Fix prettier formatting (long lines, missing line breaks)
- Fix curly brace requirement for if statements
- Fix no-base-to-string: use String() instead of .toString()
- Fix no-floating-promises: void async kill() in reset()
- Fix no-misused-promises: wrap async cleanup in void handler
- Remove unused FirefoxDisconnectedError import
- Fix IBiDi type to include subscribe method
- Fix type narrowing for bidiConnection return
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
freema added a commit that referenced this pull request Mar 29, 2026
Tests cover:
- GeckodriverHttpDriver BiDi: getBidi(), subscribe, caching, error handling
- Session cleanup: kill() and quit() send DELETE /session
- kill() resilience when DELETE fails
- marionetteHost option passthrough
- Reconnect: reset() clears driver state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@juliandescottes

Copy link
Copy Markdown
Collaborator

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

OK with me 👍

@juliandescottes

Copy link
Copy Markdown
Collaborator

Thanks a lot!

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@MayCXC@freema@juliandescottes
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix: connect-existing with remote/containerized Firefox by MayCXC · Pull Request #50 · mozilla/firefox-devtools-mcp · GitHub
Skip to content

fix: connect-existing with remote/containerized Firefox - #50

Merged
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2
Mar 29, 2026
Merged

fix: connect-existing with remote/containerized Firefox#50
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2

Conversation

@MayCXC

@MayCXCMayCXC commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes several issues that prevented --connect-existing from working with Firefox running on a remote host or in a separate container (e.g., via SSH tunnel).

Bug fixes

  1. --marionette-host CLI arg was silently ignored. The option was parsed and the type had the field, but it was never passed to the options object. Geckodriver always connected to 127.0.0.1.

  2. selenium-manager downloaded a 307MB Firefox binary. In connect-existing mode Firefox is already running. Changed --browser firefox to --driver geckodriver so selenium-manager only downloads the driver (~5MB).

  3. No cleanup on disconnect. No lifecycle handling existed. The Marionette session was never released when the MCP client disconnected, so Firefox refused new connections until restarted. Added SIGTERM/SIGINT/stdin handlers that clean up Firefox and close the server before exiting, following the pattern from the MCP SDK examples. StdioServerTransport does not currently fire onclose on stdin EOF (PR #1814), so raw stdin listeners are included as a workaround.

  4. kill() skipped session cleanup. Was synchronous and did not send DELETE /session. Made async with session cleanup so Marionette accepts new connections.

  5. Seamless reconnect after Firefox restart.getFirefox() now detects a lost connection and reconnects transparently instead of throwing an error on the first call.

New features

  1. --marionette-host parameter. Controls which host geckodriver connects to for Marionette and where the BiDi WebSocket URL is rewritten to point. Supports MARIONETTE_HOST env var.

  2. BiDi support for connect-existing. Opens a WebSocket to Firefox's Remote Agent using the webSocketUrl session capability, enabling console and network event monitoring.

Test plan

  • list_pages works with --connect-existing --marionette-host host.internal
  • list_console_messages returns BiDi data
  • Quit and restart Firefox without MCP reconnect: seamless reconnect on next tool call, pages and BiDi both work
  • MCP reconnect without quitting Firefox: old container exits cleanly (no zombie), new container connects, pages and BiDi both work
  • No stale Marionette sessions after disconnect

Supersedes #51.

@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 1b0f461 to fcd5855CompareMarch 28, 2026 08:16
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 2 times, most recently from ae0f07a to 5f6b39fCompareMarch 29, 2026 11:54
@MayCXCMayCXC changed the title Enable BiDi support for connect-existing modefix: connect-existing with remote/containerized FirefoxMar 29, 2026
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 6 times, most recently from 478f62d to 4740414CompareMarch 29, 2026 13:41
When using --connect-existing to connect to Firefox running on a
different host (e.g., via SSH tunnel or in a separate container),
several issues prevented it from working:
1. The --marionette-host CLI arg was parsed but never passed to
the options object, so geckodriver always connected to 127.0.0.1.
2. selenium-manager was invoked with --browser firefox, causing it
to download a 307MB Firefox binary that connect-existing never
uses. Changed to --driver geckodriver to fetch only the driver.
3. No cleanup on disconnect: the Marionette session was never
released when the MCP client disconnected, so Firefox refused
new connections until restarted. Added SIGTERM/SIGINT/stdin
handlers that call firefox.close() before exit. Raw stdin
listeners are needed because StdioServerTransport does not fire
onclose on stdin EOF.
4. kill() was synchronous and skipped DELETE /session, leaving
stale sessions. Made it async and added session cleanup.
5. BiDi was unavailable in connect-existing mode. Added WebSocket
support via the webSocketUrl session capability, with URL
rewriting for remote hosts. BiDi subscription failure is
non-fatal so Classic WebDriver continues working.
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 4740414 to c02fc02CompareMarch 29, 2026 13:48
@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@juliandescottes@freema iterated on this a bit, it is ready now.

@freema

Copy link
Copy Markdown
Collaborator

Hey @MayCXC, this looks great, thanks! I'll merge this into main. I'll add tests and do some manual testing, then cut a new release.

Small note: kill() is now async but reset() calls it without await — I'll fix that when adding tests.

@freema
freema merged commit 6e17b50 into mozilla:mainMar 29, 2026
freema added a commit that referenced this pull request Mar 29, 2026
- Fix prettier formatting (long lines, missing line breaks)
- Fix curly brace requirement for if statements
- Fix no-base-to-string: use String() instead of .toString()
- Fix no-floating-promises: void async kill() in reset()
- Fix no-misused-promises: wrap async cleanup in void handler
- Remove unused FirefoxDisconnectedError import
- Fix IBiDi type to include subscribe method
- Fix type narrowing for bidiConnection return
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
freema added a commit that referenced this pull request Mar 29, 2026
Tests cover:
- GeckodriverHttpDriver BiDi: getBidi(), subscribe, caching, error handling
- Session cleanup: kill() and quit() send DELETE /session
- kill() resilience when DELETE fails
- marionetteHost option passthrough
- Reconnect: reset() clears driver state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@juliandescottes

Copy link
Copy Markdown
Collaborator

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

OK with me 👍

@juliandescottes

Copy link
Copy Markdown
Collaborator

Thanks a lot!

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@MayCXC@freema@juliandescottes
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: connect-existing with remote/containerized Firefox by MayCXC · Pull Request #50 · mozilla/firefox-devtools-mcp · GitHub
Skip to content

fix: connect-existing with remote/containerized Firefox - #50

Merged
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2
Mar 29, 2026
Merged

fix: connect-existing with remote/containerized Firefox#50
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2

Conversation

@MayCXC

@MayCXCMayCXC commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes several issues that prevented --connect-existing from working with Firefox running on a remote host or in a separate container (e.g., via SSH tunnel).

Bug fixes

  1. --marionette-host CLI arg was silently ignored. The option was parsed and the type had the field, but it was never passed to the options object. Geckodriver always connected to 127.0.0.1.

  2. selenium-manager downloaded a 307MB Firefox binary. In connect-existing mode Firefox is already running. Changed --browser firefox to --driver geckodriver so selenium-manager only downloads the driver (~5MB).

  3. No cleanup on disconnect. No lifecycle handling existed. The Marionette session was never released when the MCP client disconnected, so Firefox refused new connections until restarted. Added SIGTERM/SIGINT/stdin handlers that clean up Firefox and close the server before exiting, following the pattern from the MCP SDK examples. StdioServerTransport does not currently fire onclose on stdin EOF (PR #1814), so raw stdin listeners are included as a workaround.

  4. kill() skipped session cleanup. Was synchronous and did not send DELETE /session. Made async with session cleanup so Marionette accepts new connections.

  5. Seamless reconnect after Firefox restart.getFirefox() now detects a lost connection and reconnects transparently instead of throwing an error on the first call.

New features

  1. --marionette-host parameter. Controls which host geckodriver connects to for Marionette and where the BiDi WebSocket URL is rewritten to point. Supports MARIONETTE_HOST env var.

  2. BiDi support for connect-existing. Opens a WebSocket to Firefox's Remote Agent using the webSocketUrl session capability, enabling console and network event monitoring.

Test plan

  • list_pages works with --connect-existing --marionette-host host.internal
  • list_console_messages returns BiDi data
  • Quit and restart Firefox without MCP reconnect: seamless reconnect on next tool call, pages and BiDi both work
  • MCP reconnect without quitting Firefox: old container exits cleanly (no zombie), new container connects, pages and BiDi both work
  • No stale Marionette sessions after disconnect

Supersedes #51.

@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 1b0f461 to fcd5855CompareMarch 28, 2026 08:16
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 2 times, most recently from ae0f07a to 5f6b39fCompareMarch 29, 2026 11:54
@MayCXCMayCXC changed the title Enable BiDi support for connect-existing modefix: connect-existing with remote/containerized FirefoxMar 29, 2026
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 6 times, most recently from 478f62d to 4740414CompareMarch 29, 2026 13:41
When using --connect-existing to connect to Firefox running on a
different host (e.g., via SSH tunnel or in a separate container),
several issues prevented it from working:
1. The --marionette-host CLI arg was parsed but never passed to
the options object, so geckodriver always connected to 127.0.0.1.
2. selenium-manager was invoked with --browser firefox, causing it
to download a 307MB Firefox binary that connect-existing never
uses. Changed to --driver geckodriver to fetch only the driver.
3. No cleanup on disconnect: the Marionette session was never
released when the MCP client disconnected, so Firefox refused
new connections until restarted. Added SIGTERM/SIGINT/stdin
handlers that call firefox.close() before exit. Raw stdin
listeners are needed because StdioServerTransport does not fire
onclose on stdin EOF.
4. kill() was synchronous and skipped DELETE /session, leaving
stale sessions. Made it async and added session cleanup.
5. BiDi was unavailable in connect-existing mode. Added WebSocket
support via the webSocketUrl session capability, with URL
rewriting for remote hosts. BiDi subscription failure is
non-fatal so Classic WebDriver continues working.
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 4740414 to c02fc02CompareMarch 29, 2026 13:48
@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@juliandescottes@freema iterated on this a bit, it is ready now.

@freema

Copy link
Copy Markdown
Collaborator

Hey @MayCXC, this looks great, thanks! I'll merge this into main. I'll add tests and do some manual testing, then cut a new release.

Small note: kill() is now async but reset() calls it without await — I'll fix that when adding tests.

@freema
freema merged commit 6e17b50 into mozilla:mainMar 29, 2026
freema added a commit that referenced this pull request Mar 29, 2026
- Fix prettier formatting (long lines, missing line breaks)
- Fix curly brace requirement for if statements
- Fix no-base-to-string: use String() instead of .toString()
- Fix no-floating-promises: void async kill() in reset()
- Fix no-misused-promises: wrap async cleanup in void handler
- Remove unused FirefoxDisconnectedError import
- Fix IBiDi type to include subscribe method
- Fix type narrowing for bidiConnection return
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
freema added a commit that referenced this pull request Mar 29, 2026
Tests cover:
- GeckodriverHttpDriver BiDi: getBidi(), subscribe, caching, error handling
- Session cleanup: kill() and quit() send DELETE /session
- kill() resilience when DELETE fails
- marionetteHost option passthrough
- Reconnect: reset() clears driver state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@juliandescottes

Copy link
Copy Markdown
Collaborator

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

OK with me 👍

@juliandescottes

Copy link
Copy Markdown
Collaborator

Thanks a lot!

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@MayCXC@freema@juliandescottes
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: connect-existing with remote/containerized Firefox by MayCXC · Pull Request #50 · mozilla/firefox-devtools-mcp · GitHub
Skip to content

fix: connect-existing with remote/containerized Firefox - #50

Merged
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2
Mar 29, 2026
Merged

fix: connect-existing with remote/containerized Firefox#50
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2

Conversation

@MayCXC

@MayCXCMayCXC commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes several issues that prevented --connect-existing from working with Firefox running on a remote host or in a separate container (e.g., via SSH tunnel).

Bug fixes

  1. --marionette-host CLI arg was silently ignored. The option was parsed and the type had the field, but it was never passed to the options object. Geckodriver always connected to 127.0.0.1.

  2. selenium-manager downloaded a 307MB Firefox binary. In connect-existing mode Firefox is already running. Changed --browser firefox to --driver geckodriver so selenium-manager only downloads the driver (~5MB).

  3. No cleanup on disconnect. No lifecycle handling existed. The Marionette session was never released when the MCP client disconnected, so Firefox refused new connections until restarted. Added SIGTERM/SIGINT/stdin handlers that clean up Firefox and close the server before exiting, following the pattern from the MCP SDK examples. StdioServerTransport does not currently fire onclose on stdin EOF (PR #1814), so raw stdin listeners are included as a workaround.

  4. kill() skipped session cleanup. Was synchronous and did not send DELETE /session. Made async with session cleanup so Marionette accepts new connections.

  5. Seamless reconnect after Firefox restart.getFirefox() now detects a lost connection and reconnects transparently instead of throwing an error on the first call.

New features

  1. --marionette-host parameter. Controls which host geckodriver connects to for Marionette and where the BiDi WebSocket URL is rewritten to point. Supports MARIONETTE_HOST env var.

  2. BiDi support for connect-existing. Opens a WebSocket to Firefox's Remote Agent using the webSocketUrl session capability, enabling console and network event monitoring.

Test plan

  • list_pages works with --connect-existing --marionette-host host.internal
  • list_console_messages returns BiDi data
  • Quit and restart Firefox without MCP reconnect: seamless reconnect on next tool call, pages and BiDi both work
  • MCP reconnect without quitting Firefox: old container exits cleanly (no zombie), new container connects, pages and BiDi both work
  • No stale Marionette sessions after disconnect

Supersedes #51.

@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 1b0f461 to fcd5855CompareMarch 28, 2026 08:16
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 2 times, most recently from ae0f07a to 5f6b39fCompareMarch 29, 2026 11:54
@MayCXCMayCXC changed the title Enable BiDi support for connect-existing modefix: connect-existing with remote/containerized FirefoxMar 29, 2026
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 6 times, most recently from 478f62d to 4740414CompareMarch 29, 2026 13:41
When using --connect-existing to connect to Firefox running on a
different host (e.g., via SSH tunnel or in a separate container),
several issues prevented it from working:
1. The --marionette-host CLI arg was parsed but never passed to
the options object, so geckodriver always connected to 127.0.0.1.
2. selenium-manager was invoked with --browser firefox, causing it
to download a 307MB Firefox binary that connect-existing never
uses. Changed to --driver geckodriver to fetch only the driver.
3. No cleanup on disconnect: the Marionette session was never
released when the MCP client disconnected, so Firefox refused
new connections until restarted. Added SIGTERM/SIGINT/stdin
handlers that call firefox.close() before exit. Raw stdin
listeners are needed because StdioServerTransport does not fire
onclose on stdin EOF.
4. kill() was synchronous and skipped DELETE /session, leaving
stale sessions. Made it async and added session cleanup.
5. BiDi was unavailable in connect-existing mode. Added WebSocket
support via the webSocketUrl session capability, with URL
rewriting for remote hosts. BiDi subscription failure is
non-fatal so Classic WebDriver continues working.
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 4740414 to c02fc02CompareMarch 29, 2026 13:48
@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@juliandescottes@freema iterated on this a bit, it is ready now.

@freema

Copy link
Copy Markdown
Collaborator

Hey @MayCXC, this looks great, thanks! I'll merge this into main. I'll add tests and do some manual testing, then cut a new release.

Small note: kill() is now async but reset() calls it without await — I'll fix that when adding tests.

@freema
freema merged commit 6e17b50 into mozilla:mainMar 29, 2026
freema added a commit that referenced this pull request Mar 29, 2026
- Fix prettier formatting (long lines, missing line breaks)
- Fix curly brace requirement for if statements
- Fix no-base-to-string: use String() instead of .toString()
- Fix no-floating-promises: void async kill() in reset()
- Fix no-misused-promises: wrap async cleanup in void handler
- Remove unused FirefoxDisconnectedError import
- Fix IBiDi type to include subscribe method
- Fix type narrowing for bidiConnection return
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
freema added a commit that referenced this pull request Mar 29, 2026
Tests cover:
- GeckodriverHttpDriver BiDi: getBidi(), subscribe, caching, error handling
- Session cleanup: kill() and quit() send DELETE /session
- kill() resilience when DELETE fails
- marionetteHost option passthrough
- Reconnect: reset() clears driver state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@juliandescottes

Copy link
Copy Markdown
Collaborator

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

OK with me 👍

@juliandescottes

Copy link
Copy Markdown
Collaborator

Thanks a lot!

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@MayCXC@freema@juliandescottes
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix: connect-existing with remote/containerized Firefox by MayCXC · Pull Request #50 · mozilla/firefox-devtools-mcp · GitHub
Skip to content

fix: connect-existing with remote/containerized Firefox - #50

Merged
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2
Mar 29, 2026
Merged

fix: connect-existing with remote/containerized Firefox#50
freema merged 1 commit into
mozilla:mainfrom
MayCXC:connect-existing-bidi-v2

Conversation

@MayCXC

@MayCXCMayCXC commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes several issues that prevented --connect-existing from working with Firefox running on a remote host or in a separate container (e.g., via SSH tunnel).

Bug fixes

  1. --marionette-host CLI arg was silently ignored. The option was parsed and the type had the field, but it was never passed to the options object. Geckodriver always connected to 127.0.0.1.

  2. selenium-manager downloaded a 307MB Firefox binary. In connect-existing mode Firefox is already running. Changed --browser firefox to --driver geckodriver so selenium-manager only downloads the driver (~5MB).

  3. No cleanup on disconnect. No lifecycle handling existed. The Marionette session was never released when the MCP client disconnected, so Firefox refused new connections until restarted. Added SIGTERM/SIGINT/stdin handlers that clean up Firefox and close the server before exiting, following the pattern from the MCP SDK examples. StdioServerTransport does not currently fire onclose on stdin EOF (PR #1814), so raw stdin listeners are included as a workaround.

  4. kill() skipped session cleanup. Was synchronous and did not send DELETE /session. Made async with session cleanup so Marionette accepts new connections.

  5. Seamless reconnect after Firefox restart.getFirefox() now detects a lost connection and reconnects transparently instead of throwing an error on the first call.

New features

  1. --marionette-host parameter. Controls which host geckodriver connects to for Marionette and where the BiDi WebSocket URL is rewritten to point. Supports MARIONETTE_HOST env var.

  2. BiDi support for connect-existing. Opens a WebSocket to Firefox's Remote Agent using the webSocketUrl session capability, enabling console and network event monitoring.

Test plan

  • list_pages works with --connect-existing --marionette-host host.internal
  • list_console_messages returns BiDi data
  • Quit and restart Firefox without MCP reconnect: seamless reconnect on next tool call, pages and BiDi both work
  • MCP reconnect without quitting Firefox: old container exits cleanly (no zombie), new container connects, pages and BiDi both work
  • No stale Marionette sessions after disconnect

Supersedes #51.

@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 1b0f461 to fcd5855CompareMarch 28, 2026 08:16
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 2 times, most recently from ae0f07a to 5f6b39fCompareMarch 29, 2026 11:54
@MayCXCMayCXC changed the title Enable BiDi support for connect-existing modefix: connect-existing with remote/containerized FirefoxMar 29, 2026
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch 6 times, most recently from 478f62d to 4740414CompareMarch 29, 2026 13:41
When using --connect-existing to connect to Firefox running on a
different host (e.g., via SSH tunnel or in a separate container),
several issues prevented it from working:
1. The --marionette-host CLI arg was parsed but never passed to
the options object, so geckodriver always connected to 127.0.0.1.
2. selenium-manager was invoked with --browser firefox, causing it
to download a 307MB Firefox binary that connect-existing never
uses. Changed to --driver geckodriver to fetch only the driver.
3. No cleanup on disconnect: the Marionette session was never
released when the MCP client disconnected, so Firefox refused
new connections until restarted. Added SIGTERM/SIGINT/stdin
handlers that call firefox.close() before exit. Raw stdin
listeners are needed because StdioServerTransport does not fire
onclose on stdin EOF.
4. kill() was synchronous and skipped DELETE /session, leaving
stale sessions. Made it async and added session cleanup.
5. BiDi was unavailable in connect-existing mode. Added WebSocket
support via the webSocketUrl session capability, with URL
rewriting for remote hosts. BiDi subscription failure is
non-fatal so Classic WebDriver continues working.
@MayCXC
MayCXCforce-pushed the connect-existing-bidi-v2 branch from 4740414 to c02fc02CompareMarch 29, 2026 13:48
@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@juliandescottes@freema iterated on this a bit, it is ready now.

@freema

Copy link
Copy Markdown
Collaborator

Hey @MayCXC, this looks great, thanks! I'll merge this into main. I'll add tests and do some manual testing, then cut a new release.

Small note: kill() is now async but reset() calls it without await — I'll fix that when adding tests.

@freema
freema merged commit 6e17b50 into mozilla:mainMar 29, 2026
freema added a commit that referenced this pull request Mar 29, 2026
- Fix prettier formatting (long lines, missing line breaks)
- Fix curly brace requirement for if statements
- Fix no-base-to-string: use String() instead of .toString()
- Fix no-floating-promises: void async kill() in reset()
- Fix no-misused-promises: wrap async cleanup in void handler
- Remove unused FirefoxDisconnectedError import
- Fix IBiDi type to include subscribe method
- Fix type narrowing for bidiConnection return
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
freema added a commit that referenced this pull request Mar 29, 2026
Tests cover:
- GeckodriverHttpDriver BiDi: getBidi(), subscribe, caching, error handling
- Session cleanup: kill() and quit() send DELETE /session
- kill() resilience when DELETE fails
- marionetteHost option passthrough
- Reconnect: reset() clears driver state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@juliandescottes

Copy link
Copy Markdown
Collaborator

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

@MayCXC

Copy link
Copy Markdown
ContributorAuthor

@MayCXC

Hi Aaron,

Already sent an email, but trying here as well. We want to update the project's license to Apache/MIT https://bugzilla.mozilla.org/show_bug.cgi?id=2032966

We need the agreement from existing contributors to proceed. If you are OK with the change, could either reply here or by email to say that you agree. Let me know if you have any question.

OK with me 👍

@juliandescottes

Copy link
Copy Markdown
Collaborator

Thanks a lot!

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@MayCXC@freema@juliandescottes