@vectorchat/mcp-server
Version:
VectorChat MCP Server - Encrypted AI-to-AI communication with hardware security (YubiKey/TPM). 45+ MCP tools for Windsurf, Claude, and AI assistants. Model-based identity with EMDM encryption. Dynamic AI playbook system, communication zones, message relay
800 lines (685 loc) âĸ 26.7 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Daemon Control - VectorChat Web</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--primary-color: #667eea;
--secondary-color: #764ba2;
--success-color: #4caf50;
--warning-color: #ff9800;
--error-color: #f44336;
--surface-color: #ffffff;
--background-color: #f5f5f5;
--text-primary: #333333;
--text-secondary: #666666;
--border-color: #e0e0e0;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--background-color);
color: var(--text-primary);
line-height: 1.6;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: var(--surface-color);
border-radius: 12px;
box-shadow: var(--shadow);
overflow: hidden;
}
.header {
background: var(--primary-gradient);
color: white;
padding: 24px;
text-align: center;
}
.header h1 {
font-size: 1.8rem;
font-weight: 600;
margin-bottom: 8px;
}
.header p {
opacity: 0.9;
font-size: 1rem;
}
.content {
padding: 24px;
}
.status-card {
background: #f8f9fa;
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 20px;
margin-bottom: 24px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
.status-item {
text-align: center;
padding: 12px;
background: white;
border-radius: 6px;
border: 1px solid #e0e0e0;
}
.status-label {
font-size: 0.85rem;
color: var(--text-secondary);
margin-bottom: 4px;
}
.status-value {
font-size: 1.1rem;
font-weight: 600;
}
.status-online {
color: var(--success-color);
}
.status-offline {
color: var(--error-color);
}
.status-warning {
color: var(--warning-color);
}
.control-section {
background: #f8f9fa;
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 20px;
margin-bottom: 24px;
}
.control-section-title {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 16px;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.control-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
}
.control-button {
padding: 12px 16px;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
justify-content: center;
}
.control-button:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.control-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.control-start {
background: var(--success-color);
color: white;
}
.control-stop {
background: var(--error-color);
color: white;
}
.control-restart {
background: var(--warning-color);
color: white;
}
.control-info {
background: var(--primary-color);
color: white;
}
.logs-section {
background: #f8f9fa;
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 20px;
margin-bottom: 24px;
}
.logs-title {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 16px;
color: var(--text-primary);
}
.logs-container {
background: #2d2d2d;
color: #f8f8f2;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
padding: 16px;
border-radius: 6px;
max-height: 300px;
overflow-y: auto;
}
.log-entry {
margin-bottom: 4px;
padding: 2px 0;
}
.log-timestamp {
color: #888;
margin-right: 8px;
}
.log-level {
font-weight: 600;
margin-right: 8px;
}
.log-info {
color: #61dafb;
}
.log-success {
color: #4caf50;
}
.log-warning {
color: #ff9800;
}
.log-error {
color: #f44336;
}
.actions {
padding: 24px;
border-top: 1px solid var(--border-color);
display: flex;
gap: 12px;
justify-content: space-between;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s ease;
font-size: 1rem;
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: var(--primary-gradient);
color: white;
}
.btn-secondary {
background: #f5f5f5;
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.loading {
text-align: center;
padding: 40px;
color: var(--text-secondary);
}
.loading-spinner {
width: 32px;
height: 32px;
border: 3px solid var(--border-color);
border-top: 3px solid var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@media (max-width: 768px) {
.status-card {
grid-template-columns: 1fr;
}
.control-grid {
grid-template-columns: 1fr;
}
.actions {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>đĨī¸ Daemon Control</h1>
<p>Manage your VectorChat daemon and system status</p>
</div>
<div class="content">
<!-- System Status -->
<div class="status-card" id="statusCard">
<div class="status-item">
<div class="status-label">Daemon Status</div>
<div class="status-value status-offline" id="daemonStatus">Offline</div>
</div>
<div class="status-item">
<div class="status-label">AI Model</div>
<div class="status-value status-offline" id="modelStatus">Not loaded</div>
</div>
<div class="status-item">
<div class="status-label">Encryption</div>
<div class="status-value status-offline" id="encryptionStatus">Inactive</div>
</div>
<div class="status-item">
<div class="status-label">WebSocket</div>
<div class="status-value status-offline" id="websocketStatus">Disconnected</div>
</div>
</div>
<!-- Daemon Control -->
<div class="control-section">
<div class="control-section-title">⥠Daemon Management</div>
<div class="control-grid">
<button class="control-button control-start" onclick="startDaemon()">
âļī¸ Start Daemon
</button>
<button class="control-button control-stop" onclick="stopDaemon()">
âšī¸ Stop Daemon
</button>
<button class="control-button control-restart" onclick="restartDaemon()">
đ Restart Daemon
</button>
<button class="control-button control-info" onclick="getDaemonStatus()">
đ Status
</button>
</div>
</div>
<!-- System Control -->
<div class="control-section">
<div class="control-section-title">đ§ System Operations</div>
<div class="control-grid">
<button class="control-button control-info" onclick="checkHealth()">
đ Health Check
</button>
<button class="control-button control-info" onclick="reloadConfig()">
đ Reload Config
</button>
<button class="control-button control-info" onclick="clearCache()">
đī¸ Clear Cache
</button>
<button class="control-button control-info" onclick="regenerateKeys()">
đ Regenerate Keys
</button>
</div>
</div>
<!-- Logs -->
<div class="logs-section">
<div class="logs-title">đ System Logs</div>
<div class="logs-container" id="logsContainer">
<div class="log-entry">
<span class="log-timestamp">[00:00:00]</span>
<span class="log-level log-info">INFO</span>
<span>VectorChat Web Control Panel loaded</span>
</div>
</div>
</div>
<div class="loading" id="loadingIndicator" style="display: none;">
<div class="loading-spinner"></div>
<p>Executing command...</p>
</div>
</div>
<div class="actions">
<button class="btn btn-secondary" onclick="goBack()">â Back to Chat</button>
<div>
<button class="btn btn-secondary" onclick="refreshStatus()">đ Refresh</button>
<button class="btn btn-primary" onclick="openAdvanced()">âī¸ Advanced</button>
</div>
</div>
</div>
<script>
let logs = [];
// Initialize
document.addEventListener('DOMContentLoaded', function() {
refreshStatus();
startLogUpdates();
});
async function refreshStatus() {
try {
// Check daemon health
const healthResponse = await fetch('http://localhost:3737/health');
const isDaemonOnline = healthResponse.ok;
// Update status
updateStatus('daemonStatus', isDaemonOnline ? 'Online' : 'Offline',
isDaemonOnline ? 'status-online' : 'status-offline');
if (isDaemonOnline) {
// Get detailed status
const statusResponse = await fetch('http://localhost:3737/api/status');
const statusData = await statusResponse.json();
updateStatus('modelStatus',
statusData.model_status?.status === 'loaded' ? 'Loaded' : 'Not loaded',
statusData.model_status?.status === 'loaded' ? 'status-online' : 'status-warning');
updateStatus('encryptionStatus',
statusData.emdm_active ? 'Active' : 'Inactive',
statusData.emdm_active ? 'status-online' : 'status-offline');
updateStatus('websocketStatus',
statusData.transport === 'WebSocket' ? 'Connected' : 'Disconnected',
statusData.transport === 'WebSocket' ? 'status-online' : 'status-offline');
} else {
updateStatus('modelStatus', 'Not loaded', 'status-offline');
updateStatus('encryptionStatus', 'Inactive', 'status-offline');
updateStatus('websocketStatus', 'Disconnected', 'status-offline');
}
addLogEntry('Status refreshed', 'info');
} catch (error) {
console.error('Error refreshing status:', error);
updateStatus('daemonStatus', 'Offline', 'status-offline');
updateStatus('modelStatus', 'Error', 'status-offline');
updateStatus('encryptionStatus', 'Error', 'status-offline');
updateStatus('websocketStatus', 'Error', 'status-offline');
addLogEntry(`Error refreshing status: ${error.message}`, 'error');
}
}
function updateStatus(elementId, value, className) {
const element = document.getElementById(elementId);
element.textContent = value;
element.className = `status-value ${className}`;
}
async function startDaemon() {
showLoading();
try {
// Try to start daemon via API
const response = await fetch('http://localhost:3737/api/start-daemon', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
confirm: true
})
});
if (response.ok) {
addLogEntry('Daemon start requested', 'info');
showNotification('Daemon start requested', 'success');
// Wait a moment then refresh status
setTimeout(refreshStatus, 2000);
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
console.error('Error starting daemon:', error);
addLogEntry(`Failed to start daemon: ${error.message}`, 'error');
showNotification(`Failed to start daemon: ${error.message}`, 'error');
}
hideLoading();
}
async function stopDaemon() {
if (!confirm('Stop the VectorChat daemon? This will disconnect all users.')) {
return;
}
showLoading();
try {
const response = await fetch('http://localhost:3737/api/stop-daemon', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
confirm: true
})
});
if (response.ok) {
addLogEntry('Daemon stop requested', 'info');
showNotification('Daemon stop requested', 'success');
// Wait a moment then refresh status
setTimeout(refreshStatus, 2000);
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
console.error('Error stopping daemon:', error);
addLogEntry(`Failed to stop daemon: ${error.message}`, 'error');
showNotification(`Failed to stop daemon: ${error.message}`, 'error');
}
hideLoading();
}
async function restartDaemon() {
if (!confirm('Restart the VectorChat daemon? This will temporarily disconnect all users.')) {
return;
}
showLoading();
try {
const response = await fetch('http://localhost:3737/api/restart-daemon', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
confirm: true,
reason: 'User requested restart'
})
});
if (response.ok) {
addLogEntry('Daemon restart requested', 'warning');
showNotification('Daemon restart requested', 'success');
// Wait a moment then refresh status
setTimeout(refreshStatus, 3000);
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
console.error('Error restarting daemon:', error);
addLogEntry(`Failed to restart daemon: ${error.message}`, 'error');
showNotification(`Failed to restart daemon: ${error.message}`, 'error');
}
hideLoading();
}
async function getDaemonStatus() {
try {
const response = await fetch('http://localhost:3737/api/status');
const status = await response.json();
const statusText = `Daemon Status:
âĸ State: ${status.user_id || 'Unknown'}
âĸ Model: ${status.model || 'Not set'}
âĸ EMDM: ${status.emdm_active ? 'Active' : 'Inactive'}
âĸ Transport: ${status.transport || 'None'}
âĸ Peers: ${status.known_peers || 0}
âĸ Sessions: ${status.active_sessions || 0}`;
alert(statusText);
addLogEntry('Daemon status retrieved', 'info');
} catch (error) {
showNotification(`Failed to get daemon status: ${error.message}`, 'error');
addLogEntry(`Failed to get daemon status: ${error.message}`, 'error');
}
}
async function checkHealth() {
try {
const response = await fetch('http://localhost:3737/health');
if (response.ok) {
showNotification('Daemon is healthy!', 'success');
addLogEntry('Health check passed', 'success');
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
showNotification(`Health check failed: ${error.message}`, 'error');
addLogEntry(`Health check failed: ${error.message}`, 'error');
}
}
async function reloadConfig() {
try {
const response = await fetch('http://localhost:3737/api/reload-config', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
confirm: true
})
});
if (response.ok) {
showNotification('Configuration reloaded!', 'success');
addLogEntry('Configuration reloaded', 'info');
// Refresh status
setTimeout(refreshStatus, 1000);
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
showNotification(`Failed to reload config: ${error.message}`, 'error');
addLogEntry(`Failed to reload config: ${error.message}`, 'error');
}
}
async function clearCache() {
if (!confirm('Clear daemon cache? This may temporarily affect performance.')) {
return;
}
try {
const response = await fetch('http://localhost:3737/api/clear-cache', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
confirm: true
})
});
if (response.ok) {
showNotification('Cache cleared!', 'success');
addLogEntry('Cache cleared', 'info');
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
showNotification(`Failed to clear cache: ${error.message}`, 'error');
addLogEntry(`Failed to clear cache: ${error.message}`, 'error');
}
}
async function regenerateKeys() {
if (!confirm('Regenerate encryption keys? This will invalidate existing encrypted data.')) {
return;
}
try {
const response = await fetch('http://localhost:3737/api/regenerate-keys', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
confirm: true
})
});
if (response.ok) {
showNotification('Encryption keys regenerated!', 'success');
addLogEntry('Encryption keys regenerated', 'warning');
// Refresh status
setTimeout(refreshStatus, 1000);
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
showNotification(`Failed to regenerate keys: ${error.message}`, 'error');
addLogEntry(`Failed to regenerate keys: ${error.message}`, 'error');
}
}
function openAdvanced() {
window.open('settings.html', '_blank', 'width=1000,height=700');
}
function goBack() {
if (window.opener) {
window.close();
} else {
window.location.href = 'web-app.html';
}
}
function startLogUpdates() {
// Update logs every 5 seconds
setInterval(() => {
// In a real implementation, this would fetch logs from the daemon
// For now, just add a periodic status message
if (Math.random() < 0.3) { // 30% chance
addLogEntry('System heartbeat', 'info');
}
}, 5000);
}
function addLogEntry(message, level = 'info') {
const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit', second:'2-digit'});
const logEntry = document.createElement('div');
logEntry.className = 'log-entry';
logEntry.innerHTML = `
<span class="log-timestamp">[${timestamp}]</span>
<span class="log-level log-${level}">${level.toUpperCase()}</span>
<span>${message}</span>
`;
const logsContainer = document.getElementById('logsContainer');
logsContainer.appendChild(logEntry);
logsContainer.scrollTop = logsContainer.scrollHeight;
// Keep only last 100 entries
while (logsContainer.children.length > 100) {
logsContainer.removeChild(logsContainer.firstChild);
}
logs.push({ message, level, timestamp });
}
function showLoading() {
document.getElementById('loadingIndicator').style.display = 'block';
}
function hideLoading() {
document.getElementById('loadingIndicator').style.display = 'none';
}
function showNotification(message, type = 'info') {
// Create notification element
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 8px;
color: white;
font-weight: 500;
z-index: 3000;
opacity: 0;
transform: translateX(100%);
transition: all 0.3s ease;
max-width: 400px;
`;
let backgroundColor;
switch (type) {
case 'error':
backgroundColor = '#f44336';
break;
case 'success':
backgroundColor = '#4caf50';
break;
case 'warning':
backgroundColor = '#ff9800';
break;
default:
backgroundColor = '#667eea';
}
notification.style.backgroundColor = backgroundColor;
notification.textContent = message;
document.body.appendChild(notification);
// Animate in
setTimeout(() => {
notification.style.opacity = '1';
notification.style.transform = 'translateX(0)';
}, 100);
// Animate out and remove
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transform = 'translateX(100%)';
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
// Auto-refresh status every 10 seconds
setInterval(refreshStatus, 10000);
</script>
</body>
</html>