<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Figma MCP Plugin</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
padding: 10px;
background-color: #404040;
font-size: 12px;
}
#status {
padding: 8px;
border-radius: 4px;
margin-bottom: 10px;
background-color: #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.connected {
background-color: #e6ffe6 !important;
color: #006600;
}
.disconnected {
background-color: #ffe6e6 !important;
color: #660000;
}
.reconnecting {
background-color: #fff6e6 !important;
color: #665500;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}
.log {
font-family: monospace;
height: 200px;
overflow-y: auto;
background-color: #1e1e1e;
color: #e0e0e0;
padding: 8px;
font-size: 10px;
border-radius: 4px;
line-height: 1.3;
}
.log div {
margin-bottom: 2px;
}
.log .timestamp {
color: #8a8a8a;
margin-right: 5px;
}
.log .message-plugin {
color: #6a9eff;
}
.log .message-server {
color: #6aff9e;
}
.log .message-error {
color: #ff6a6a;
}
h3 {
margin: 8px 0 5px 0;
font-size: 13px;
}
.server-input {
margin-top: 8px;
gap: 6px;
display: flex;
background-color: #e0e0e0;
}
input {
flex: 1;
padding: 6px;
font-size: 11px;
border: 1px solid #ccc;
border-radius: 4px;
}
.disabled {
opacity: 0.5;
pointer-events: none;
}
</style>
</head>
<body>
<div id="status">
<span id="status-text">Waiting to connect to MCP server...</span>
</div>
<div class="server-input">
<input type="text" id="server-url" value="ws://localhost:8080" placeholder="WebSocket URL" disabled>
</div>
<h3>Console log:</h3>
<div id="log" class="log"></div>
<script>
let ws;
const status = document.getElementById('status');
const statusText = document.getElementById('status-text');
const log = document.getElementById('log');
const serverUrlInput = document.getElementById('server-url');
let serverUrl = 'ws://localhost:8080';
let connectionCheckInterval;
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
let reconnectTimeout;
// Listen for messages from the plugin code
onmessage = (event) => {
const msg = event.data.pluginMessage;
if (msg.type === 'connect-to-server') {
serverUrl = msg.serverUrl;
serverUrlInput.value = serverUrl;
reconnectAttempts = 0; // Reset reconnect attempts when manually connecting
connectWebSocket(serverUrl);
} else {
logMessage(`Plugin: ${JSON.stringify(msg)}`, 'plugin');
// If ws is connected, forward the message to server
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(msg));
}
}
};
// Check connection status and update UI
function checkConnectionStatus() {
if (!ws) {
updateConnectionStatus('disconnected', 'No connection to server');
attemptReconnect();
return;
}
switch (ws.readyState) {
case WebSocket.CONNECTING:
updateConnectionStatus('connecting', `Connecting to ${serverUrl}...`);
break;
case WebSocket.OPEN:
updateConnectionStatus('connected', `Connected to MCP server`);
reconnectAttempts = 0; // Reset reconnect attempts when connected
break;
case WebSocket.CLOSING:
updateConnectionStatus('disconnecting', `Disconnecting from MCP server...`);
break;
case WebSocket.CLOSED:
updateConnectionStatus('disconnected', `Disconnected from MCP server`);
attemptReconnect();
break;
default:
updateConnectionStatus('disconnected', `Unknown connection state`);
attemptReconnect();
}
}
// Attempt to reconnect to the server
function attemptReconnect() {
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
reconnectTimeout = null;
}
if (reconnectAttempts >= maxReconnectAttempts) {
logMessage(`Maximum reconnection attempts (${maxReconnectAttempts}) reached. Please check server status.`, 'error');
return;
}
reconnectAttempts++;
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts - 1), 30000); // Exponential backoff with 30s max
updateConnectionStatus('reconnecting', `Reconnecting (${reconnectAttempts}/${maxReconnectAttempts})...`);
logMessage(`Attempting to reconnect (${reconnectAttempts}/${maxReconnectAttempts}) in ${delay/1000}s...`, 'server');
reconnectTimeout = setTimeout(() => {
if (ws && ws.readyState !== WebSocket.CLOSED) {
ws.close();
}
logMessage(`Reconnecting to ${serverUrl}...`, 'server');
connectWebSocket(serverUrl);
}, delay);
}
// Update connection status UI
function updateConnectionStatus(state, message) {
statusText.textContent = message;
// Update status class
status.className = '';
if (state === 'connected') {
status.classList.add('connected');
serverUrlInput.classList.add('disabled');
} else if (state === 'disconnected' || state === 'error') {
status.classList.add('disconnected');
serverUrlInput.classList.remove('disabled');
} else if (state === 'connecting' || state === 'reconnecting') {
status.classList.add('reconnecting');
}
}
// Connect to WebSocket server
function connectWebSocket(url) {
try {
// Clear existing interval
if (connectionCheckInterval) {
clearInterval(connectionCheckInterval);
}
// Close existing connection if any
if (ws) {
ws.close();
}
updateConnectionStatus('connecting', `Connecting to ${url}...`);
ws = new WebSocket(url);
ws.onopen = () => {
updateConnectionStatus('connected', 'Connected to MCP server');
// Send ready message to server
ws.send(JSON.stringify({ type: 'plugin-ready' }));
logMessage('Connected to MCP server', 'server');
// Notify plugin code about connection status
parent.postMessage({
pluginMessage: {
type: 'connection-status',
status: 'connected',
url: url
}
}, '*');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
logMessage(`Server: ${event.data}`, 'server');
// Forward message to plugin code
parent.postMessage({ pluginMessage: data }, '*');
};
ws.onclose = () => {
updateConnectionStatus('disconnected', 'Disconnected from MCP server');
logMessage('Disconnected from MCP server', 'error');
// Notify plugin code about connection status
parent.postMessage({
pluginMessage: {
type: 'connection-status',
status: 'disconnected',
url: url
}
}, '*');
};
ws.onerror = (error) => {
updateConnectionStatus('error', 'Error connecting to MCP server');
logMessage(`WebSocket error: ${error}`, 'error');
// Notify plugin code about connection status
parent.postMessage({
pluginMessage: {
type: 'connection-status',
status: 'error',
url: url,
error: String(error)
}
}, '*');
};
// Start connection check interval (check every 5 seconds)
connectionCheckInterval = setInterval(checkConnectionStatus, 5000);
} catch (error) {
updateConnectionStatus('error', `Error: ${error.message}`);
logMessage(`Error: ${error.message}`, 'error');
// Notify plugin code about connection status
parent.postMessage({
pluginMessage: {
type: 'connection-status',
status: 'error',
url: url,
error: error.message
}
}, '*');
}
}
// Add message to log
function logMessage(message, type = 'default') {
const entry = document.createElement('div');
const timestamp = document.createElement('span');
timestamp.className = 'timestamp';
timestamp.textContent = `${new Date().toLocaleTimeString()}`;
const messageSpan = document.createElement('span');
messageSpan.className = `message-${type}`;
messageSpan.textContent = message;
entry.appendChild(timestamp);
entry.appendChild(messageSpan);
log.appendChild(entry);
log.scrollTop = log.scrollHeight;
// Limit log entries to prevent memory issues
if (log.children.length > 100) {
log.removeChild(log.children[0]);
}
}
</script>
</body>
</html>