Add MCP introspection support for Electron frontend with Playwright integration

Co-authored-by: thomasnordquist <7721625+thomasnordquist@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-19 19:35:14 +00:00
co-authored by thomasnordquist
parent 2d8b976e33
commit 0046656134
9 changed files with 307 additions and 1 deletions
+3
View File
@@ -9,3 +9,6 @@ test.png
.awcache
.scannerwork
screen*.png
# MCP introspection artifacts
mqtt-explorer-mcp-screenshot.png
+162
View File
@@ -0,0 +1,162 @@
# MCP Introspection Support
This document explains how to use MCP (Model Context Protocol) introspection with MQTT Explorer for deterministic testing and automation with tools like GitHub Copilot with Playwright.
## What is MCP Introspection?
MCP introspection allows external tools to interact with and control the MQTT Explorer Electron application through the Chrome DevTools Protocol (CDP). This enables:
- Deterministic automated testing with Playwright
- GitHub Copilot agent mode integration
- Remote debugging and inspection of the application
- Automated UI testing and interaction
## Prerequisites
- Node.js >= 18
- Yarn package manager
- MQTT Explorer built from source
## Quick Start
### Option 1: Using the npm script (Recommended)
```bash
# Build the application first
yarn build
# Start with MCP introspection enabled
yarn start:mcp
```
### Option 2: Using the shell script directly
```bash
# Build the application first
yarn build
# Start with default debugging port (9222)
./scripts/start-with-mcp.sh
# Or specify a custom port
REMOTE_DEBUG_PORT=9223 ./scripts/start-with-mcp.sh
```
### Option 3: Manual electron launch
```bash
# Build the application first
yarn build
# Start with MCP flags
electron . --enable-mcp-introspection --remote-debugging-port=9222
```
## Using with Playwright
Once the application is running with MCP introspection enabled, you can connect to it using Playwright:
```javascript
const { chromium } = require('playwright');
(async () => {
// Connect to the running Electron app
const browser = await chromium.connectOverCDP('http://localhost:9222');
const context = browser.contexts()[0];
const page = context.pages()[0];
// Now you can interact with the page
await page.screenshot({ path: 'screenshot.png' });
// ... perform your tests ...
await browser.close();
})();
```
## Using with GitHub Copilot Agent Mode
When using GitHub Copilot with Playwright MCP server:
1. Ensure the MCP configuration is set up (see `mcp.json`)
2. Start MQTT Explorer with MCP introspection:
```bash
yarn start:mcp
```
3. Use Copilot agent commands to interact with the running application
## Configuration
### MCP Configuration File
The `mcp.json` file in the root directory contains the MCP server configuration for Playwright:
```json
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-playwright"
],
"env": {
"PLAYWRIGHT_BROWSERS_PATH": "0"
}
}
}
}
```
### Remote Debugging Port
The default remote debugging port is `9222`. You can customize it by:
1. Setting the `REMOTE_DEBUG_PORT` environment variable:
```bash
REMOTE_DEBUG_PORT=9223 yarn start:mcp
```
2. Passing the `--remote-debugging-port` flag directly:
```bash
electron . --enable-mcp-introspection --remote-debugging-port=9223
```
## Troubleshooting
### Port Already in Use
If you get an error that the port is already in use, either:
- Stop the other process using that port
- Use a different port with `REMOTE_DEBUG_PORT` environment variable
### Cannot Connect
Ensure that:
1. The application is running with the `--enable-mcp-introspection` flag
2. The remote debugging port matches between the application and your client
3. No firewall is blocking the connection
## Security Considerations
When running with MCP introspection enabled:
- The application exposes debugging capabilities on a local port
- Only run this in development/testing environments
- Do not expose the debugging port to untrusted networks
- Do not run with introspection in production
## Related Files
- `src/development.ts` - Helper functions for MCP introspection mode
- `src/electron.ts` - Main Electron process with MCP support
- `scripts/start-with-mcp.sh` - Convenience script to start with MCP
- `mcp.json` - MCP server configuration
- `src/spec/demoVideo.ts` - Example Playwright test implementation
## Further Reading
- [Model Context Protocol](https://modelcontextprotocol.io/)
- [Playwright Documentation](https://playwright.dev/)
- [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)
- [Electron Remote Debugging](https://www.electronjs.org/docs/latest/tutorial/debugging-main-process)
+12
View File
@@ -47,6 +47,18 @@ To achieve a reliable product automated tests run regularly on travis.
- MQTT integration
- UI-Tests (The demo is a recorded ui test)
## MCP Introspection for Testing and Automation
MQTT Explorer supports MCP (Model Context Protocol) introspection for deterministic testing and integration with tools like GitHub Copilot agent mode with Playwright.
See [MCP_INTROSPECTION.md](MCP_INTROSPECTION.md) for detailed documentation.
Quick start:
```bash
yarn build
yarn start:mcp
```
## Run UI-tests
A [mosquitto](https://mosquitto.org/) MQTT broker is required to run the ui-tests.
+72
View File
@@ -0,0 +1,72 @@
/**
* Example script demonstrating how to use Playwright to connect to
* MQTT Explorer running with MCP introspection enabled.
*
* Prerequisites:
* 1. Start MQTT Explorer with MCP introspection:
* yarn start:mcp
*
* 2. Run this script:
* node examples/playwright-mcp-example.js
*/
const { chromium } = require('playwright')
async function main() {
console.log('Connecting to MQTT Explorer via Chrome DevTools Protocol...')
try {
// Connect to the running Electron app via CDP
const browser = await chromium.connectOverCDP('http://localhost:9222')
console.log('Connected successfully!')
// Get the existing browser context
const contexts = browser.contexts()
if (contexts.length === 0) {
console.error('No browser contexts found')
await browser.close()
return
}
const context = contexts[0]
const pages = context.pages()
if (pages.length === 0) {
console.error('No pages found')
await browser.close()
return
}
const page = pages[0]
console.log(`Page title: ${await page.title()}`)
// Take a screenshot
await page.screenshot({ path: 'mqtt-explorer-mcp-screenshot.png' })
console.log('Screenshot saved to mqtt-explorer-mcp-screenshot.png')
// Example: Check if the connection form is visible
const usernameInput = await page.locator('//label[contains(text(), "Username")]/..//input')
const isVisible = await usernameInput.isVisible().catch(() => false)
console.log(`Username input visible: ${isVisible}`)
// You can add more interactions here
// For example:
// - Fill in connection details
// - Click connect button
// - Verify UI elements
// - Test specific features
console.log('Done! Browser connection will remain open.')
console.log('Press Ctrl+C to close this script (the app will keep running)')
// Keep the script running to maintain the connection
await new Promise(() => {})
} catch (error) {
console.error('Error connecting to MQTT Explorer:', error.message)
console.error('\nMake sure MQTT Explorer is running with MCP introspection:')
console.error(' yarn start:mcp')
process.exit(1)
}
}
main()
+14
View File
@@ -0,0 +1,14 @@
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-playwright"
],
"env": {
"PLAYWRIGHT_BROWSERS_PATH": "0"
}
}
}
}
+1
View File
@@ -9,6 +9,7 @@
"private": "true",
"scripts": {
"start": "electron .",
"start:mcp": "./scripts/start-with-mcp.sh",
"test": "yarn test:app && yarn test:backend",
"test:app": "cd app && yarn test",
"test:backend": "cd backend && yarn test",
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# Script to launch MQTT Explorer with MCP introspection enabled
# This allows tools like GitHub Copilot with Playwright MCP to interact with the app
REMOTE_DEBUG_PORT="${REMOTE_DEBUG_PORT:-9222}"
echo "Starting MQTT Explorer with MCP introspection enabled"
echo "Remote debugging port: $REMOTE_DEBUG_PORT"
echo ""
echo "You can now use Playwright MCP or other debugging tools to connect to the app"
echo "Connect to: http://localhost:$REMOTE_DEBUG_PORT"
echo ""
# Build the app if needed
if [ ! -d "dist" ]; then
echo "Building the app..."
yarn build
fi
# Start the app with MCP introspection flags
electron . --enable-mcp-introspection --remote-debugging-port=$REMOTE_DEBUG_PORT "$@"
+12
View File
@@ -32,3 +32,15 @@ export function isDev() {
export function runningUiTestOnCi() {
return Boolean(process.argv.find(arg => arg === '--runningUiTestOnCi'))
}
export function enableMcpIntrospection() {
return Boolean(process.argv.find(arg => arg === '--enable-mcp-introspection'))
}
export function getRemoteDebuggingPort() {
const portArg = process.argv.find(arg => arg.startsWith('--remote-debugging-port='))
if (portArg) {
return parseInt(portArg.split('=')[1], 10)
}
return enableMcpIntrospection() ? 9222 : undefined
}
+9 -1
View File
@@ -8,7 +8,7 @@ import { promises as fsPromise } from 'fs'
// import { electronTelemetryFactory } from 'electron-telemetry'
import { menuTemplate } from './MenuTemplate'
import buildOptions from './buildOptions'
import { waitForDevServer, isDev, runningUiTestOnCi, loadDevTools } from './development'
import { waitForDevServer, isDev, runningUiTestOnCi, loadDevTools, enableMcpIntrospection, getRemoteDebuggingPort } from './development'
import { shouldAutoUpdate, handleAutoUpdate } from './autoUpdater'
import { registerCrashReporter } from './registerCrashReporter'
import { makeOpenDialogRpc, makeSaveDialogRpc } from '../events/OpenDialogRequest'
@@ -22,6 +22,14 @@ registerCrashReporter()
// disable-dev-shm-usage is required to run the debug console
app.commandLine.appendSwitch('--no-sandbox --disable-dev-shm-usage')
// Enable remote debugging for MCP introspection
const remoteDebuggingPort = getRemoteDebuggingPort()
if (remoteDebuggingPort) {
app.commandLine.appendSwitch('--remote-debugging-port', remoteDebuggingPort.toString())
log.info(`Remote debugging enabled on port ${remoteDebuggingPort}`)
}
app.whenReady().then(() => {
backendRpc.on(makeOpenDialogRpc(), async request => {
return dialog.showOpenDialog(BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0], request)