MCP Tunnel
by leomercier
Verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VM Terminal</title>
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
<link
href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.min.css"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0.11.0/lib/addon-web-links.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
height: 100vh;
display: flex;
flex-direction: column;
background-color: #1e1e1e;
}
#terminal-container {
flex: 1;
padding: 10px;
height: calc(100vh - 20px);
}
#terminal-container .terminal {
height: 100%;
}
.header {
background-color: #282828;
color: #f0f0f0;
padding: 5px 10px;
font-family: sans-serif;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header button {
background-color: #3a3a3a;
color: #f0f0f0;
border: none;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
}
.header button:hover {
background-color: #4a4a4a;
}
</style>
</head>
<body>
<div class="header">
<div>VM Terminal</div>
<button id="clear-btn">Clear</button>
</div>
<div id="terminal-container"></div>
<script type="module">
const term = new Terminal({
cursorBlink: true,
theme: {
background: "#1e1e1e",
foreground: "#f0f0f0",
cursor: "#f0f0f0",
selectionBackground: "#565656"
},
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
fontSize: 14,
lineHeight: 1.2,
scrollback: 5000,
cursorStyle: "block"
});
// Add addons
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.loadAddon(new WebLinksAddon());
term.open(document.getElementById("terminal-container"));
fitAddon.fit();
term.focus();
// Handle window resize
window.addEventListener("resize", () => {
fitAddon.fit();
});
// Clear button functionality
document.getElementById("clear-btn").addEventListener("click", () => {
term.clear();
});
let ws;
let commandBuffer = "";
let commandHistory = [];
let historyPosition = -1;
function connectWebSocket() {
ws = new WebSocket("ws://" + window.location.host);
ws.onopen = () => {
term.writeln("\r\nConnected to VM terminal");
term.write("\r\n$ ");
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === "output") {
term.write(data.content);
if (!data.content.endsWith("\n")) {
term.write("\r\n");
}
term.write("$ ");
}
};
ws.onclose = () => {
term.writeln("\r\nConnection lost. Reconnecting...");
setTimeout(connectWebSocket, 2000);
};
ws.onerror = (error) => {
console.error("WebSocket error:", error);
term.writeln("\r\nConnection error. Please try again.");
};
}
function clearCurrentLine() {
const currentLine = commandBuffer;
for (let i = 0; i < currentLine.length; i++) {
term.write("\b \b");
}
return currentLine;
}
term.onKey(({ key, domEvent }) => {
const printable =
!domEvent.altKey && !domEvent.ctrlKey && !domEvent.metaKey;
if (domEvent.keyCode === 13) {
// Enter key
term.write("\r\n");
if (commandBuffer.trim() !== "") {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(
JSON.stringify({
type: "command",
command: commandBuffer
})
);
// Add to history if not duplicate
if (
commandHistory.length === 0 ||
commandHistory[commandHistory.length - 1] !== commandBuffer
) {
commandHistory.push(commandBuffer);
}
historyPosition = -1;
} else {
term.writeln("Not connected to the server.");
term.write("$ ");
}
} else {
term.write("$ ");
}
commandBuffer = "";
} else if (domEvent.keyCode === 8) {
// Backspace
if (commandBuffer.length > 0) {
commandBuffer = commandBuffer.slice(0, -1);
term.write("\b \b");
}
} else if (domEvent.keyCode === 38) {
// Up arrow - History previous
if (commandHistory.length > 0) {
if (historyPosition === -1) {
historyPosition = commandHistory.length - 1;
} else if (historyPosition > 0) {
historyPosition--;
}
clearCurrentLine();
commandBuffer = commandHistory[historyPosition];
term.write(commandBuffer);
}
} else if (domEvent.keyCode === 40) {
// Down arrow - History next
if (historyPosition !== -1) {
if (historyPosition < commandHistory.length - 1) {
historyPosition++;
clearCurrentLine();
commandBuffer = commandHistory[historyPosition];
term.write(commandBuffer);
} else {
historyPosition = -1;
clearCurrentLine();
commandBuffer = "";
}
}
} else if (domEvent.ctrlKey && key.toLowerCase() === "c") {
// Ctrl+C
term.write("^C\r\n$ ");
commandBuffer = "";
} else if (domEvent.ctrlKey && key.toLowerCase() === "l") {
// Ctrl+L (clear)
term.clear();
term.write("$ " + commandBuffer);
} else if (printable) {
commandBuffer += key;
term.write(key);
}
});
window.addEventListener("load", connectWebSocket);
</script>
</body>
</html>