<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MCP Control Center - Hammerspace MCP</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
background: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
.header {
background: white;
border-radius: 12px;
padding: 24px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.header h1 {
color: #1e3a8a;
font-size: 28px;
margin-bottom: 8px;
}
.header p {
color: #64748b;
font-size: 14px;
}
.nav-links {
margin-top: 16px;
}
.nav-links a {
display: inline-block;
padding: 8px 16px;
background: #3b82f6;
color: white;
text-decoration: none;
border-radius: 6px;
margin-right: 8px;
font-size: 14px;
transition: background 0.3s;
}
.nav-links a:hover {
background: #2563eb;
}
.mcp-servers {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.server-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
border-left: 4px solid #3b82f6;
}
.server-card.running {
border-left-color: #059669;
}
.server-card.stopped {
border-left-color: #dc2626;
}
.server-card.error {
border-left-color: #d97706;
}
.server-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.server-name {
font-size: 18px;
font-weight: 600;
color: #1e3a8a;
}
.server-status {
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
}
.server-status.running {
background: #dcfce7;
color: #166534;
}
.server-status.stopped {
background: #fef2f2;
color: #dc2626;
}
.server-status.error {
background: #fed7aa;
color: #9a3412;
}
.server-details {
margin-bottom: 16px;
}
.detail-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 14px;
}
.detail-label {
color: #64748b;
}
.detail-value {
color: #1e3a8a;
font-weight: 500;
}
.server-actions {
display: flex;
gap: 8px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn:hover {
transform: translateY(-1px);
}
.btn-start {
background: #059669;
color: white;
}
.btn-start:hover {
background: #047857;
}
.btn-stop {
background: #dc2626;
color: white;
}
.btn-stop:hover {
background: #b91c1c;
}
.btn-refresh {
background: #3b82f6;
color: white;
}
.btn-refresh:hover {
background: #2563eb;
}
.status-overview {
background: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.status-overview h2 {
color: #1e3a8a;
font-size: 20px;
margin-bottom: 16px;
}
.status-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
.stat-item {
text-align: center;
padding: 16px;
background: #f8fafc;
border-radius: 8px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #1e3a8a;
margin-bottom: 4px;
}
.stat-label {
font-size: 14px;
color: #64748b;
}
.loading {
text-align: center;
padding: 40px;
color: #64748b;
}
.error-message {
background: #fef2f2;
color: #dc2626;
padding: 12px;
border-radius: 6px;
margin-bottom: 16px;
font-size: 14px;
}
.success-message {
background: #dcfce7;
color: #166534;
padding: 12px;
border-radius: 6px;
margin-bottom: 16px;
font-size: 14px;
}
.auto-refresh {
position: fixed;
bottom: 20px;
right: 20px;
background: #3b82f6;
color: white;
padding: 12px 20px;
border-radius: 20px;
font-size: 12px;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.auto-refresh.disabled {
background: #94a3b8;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>π§ MCP Control Center</h1>
<p>Monitor and control all MCP servers (Hammerspace, Milvus, Kubernetes)</p>
<div class="nav-links">
<a href="/">π Main Interface</a>
<a href="/monitor">π File Monitor</a>
<a href="/debug">π Debug Logs</a>
<a href="/events">π Events</a>
</div>
</div>
<div class="status-overview">
<h2>π System Overview</h2>
<div class="status-stats" id="statusStats">
<div class="stat-item">
<div class="stat-value" id="totalServers">-</div>
<div class="stat-label">Total Servers</div>
</div>
<div class="stat-item">
<div class="stat-value" id="runningServers">-</div>
<div class="stat-label">Running</div>
</div>
<div class="stat-item">
<div class="stat-value" id="totalTools">-</div>
<div class="stat-label">Available Tools</div>
</div>
<div class="stat-item">
<div class="stat-value" id="totalResources">-</div>
<div class="stat-label">Resources</div>
</div>
</div>
</div>
<div id="messageContainer"></div>
<div class="mcp-servers" id="mcpServers">
<div class="loading">Loading MCP server status...</div>
</div>
<div class="auto-refresh" id="autoRefresh" onclick="toggleAutoRefresh()">
π Auto-refresh: ON
</div>
</div>
<script>
let autoRefreshInterval = null;
let autoRefreshEnabled = true;
// Start auto-refresh
startAutoRefresh();
// Initial load
loadMCPStatus();
function startAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
}
autoRefreshInterval = setInterval(loadMCPStatus, 5000); // Refresh every 5 seconds
}
function stopAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
}
function toggleAutoRefresh() {
autoRefreshEnabled = !autoRefreshEnabled;
const button = document.getElementById('autoRefresh');
if (autoRefreshEnabled) {
startAutoRefresh();
button.textContent = 'π Auto-refresh: ON';
button.classList.remove('disabled');
} else {
stopAutoRefresh();
button.textContent = 'βΈοΈ Auto-refresh: OFF';
button.classList.add('disabled');
}
}
async function loadMCPStatus() {
try {
const response = await fetch('/api/mcp/status');
const data = await response.json();
if (data.success) {
updateStatusOverview(data.status);
updateServerCards(data.status);
} else {
showError('Failed to load MCP status: ' + data.error);
}
} catch (error) {
showError('Error loading MCP status: ' + error.message);
}
}
function updateStatusOverview(status) {
const servers = status.servers || {};
const totalServers = Object.keys(servers).length;
const runningServers = Object.values(servers).filter(s => s.status === 'running').length;
const totalTools = Object.values(servers).reduce((sum, s) => sum + (s.tools_count || 0), 0);
const totalResources = Object.values(servers).reduce((sum, s) => sum + (s.resources_count || 0), 0);
document.getElementById('totalServers').textContent = totalServers;
document.getElementById('runningServers').textContent = runningServers;
document.getElementById('totalTools').textContent = totalTools;
document.getElementById('totalResources').textContent = totalResources;
}
function updateServerCards(status) {
const servers = status.servers || {};
const container = document.getElementById('mcpServers');
if (Object.keys(servers).length === 0) {
container.innerHTML = '<div class="loading">No MCP servers configured</div>';
return;
}
container.innerHTML = Object.entries(servers).map(([serverId, server]) => {
const statusClass = server.status || 'unknown';
const lastCheck = server.last_check ? new Date(server.last_check).toLocaleTimeString() : 'Never';
const uptime = server.uptime ? formatUptime(server.uptime) : 'N/A';
return `
<div class="server-card ${statusClass}">
<div class="server-header">
<div class="server-name">${server.name}</div>
<div class="server-status ${statusClass}">${server.status || 'unknown'}</div>
</div>
<div class="server-details">
<div class="detail-row">
<span class="detail-label">Port:</span>
<span class="detail-value">${server.port || 'stdio'}</span>
</div>
<div class="detail-row">
<span class="detail-label">Tools:</span>
<span class="detail-value">${server.tools_count || 0}</span>
</div>
<div class="detail-row">
<span class="detail-label">Resources:</span>
<span class="detail-value">${server.resources_count || 0}</span>
</div>
<div class="detail-row">
<span class="detail-label">Uptime:</span>
<span class="detail-value">${uptime}</span>
</div>
<div class="detail-row">
<span class="detail-label">Last Check:</span>
<span class="detail-value">${lastCheck}</span>
</div>
${server.error_message ? `
<div class="error-message">
Error: ${server.error_message}
</div>
` : ''}
</div>
<div class="server-actions">
<button class="btn btn-refresh" onclick="refreshServer('${serverId}')">π Refresh</button>
${server.status === 'running' ?
`<button class="btn btn-stop" onclick="stopServer('${serverId}')">βΉοΈ Stop</button>` :
`<button class="btn btn-start" onclick="startServer('${serverId}')">βΆοΈ Start</button>`
}
</div>
</div>
`;
}).join('');
}
function formatUptime(seconds) {
if (seconds < 60) return `${Math.round(seconds)}s`;
if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
return `${Math.round(seconds / 3600)}h`;
}
async function startServer(serverId) {
try {
const response = await fetch(`/api/mcp/start/${serverId}`);
const data = await response.json();
if (data.success) {
showSuccess(data.message);
loadMCPStatus(); // Refresh immediately
} else {
showError(data.message || 'Failed to start server');
}
} catch (error) {
showError('Error starting server: ' + error.message);
}
}
async function stopServer(serverId) {
try {
const response = await fetch(`/api/mcp/stop/${serverId}`);
const data = await response.json();
if (data.success) {
showSuccess(data.message);
loadMCPStatus(); // Refresh immediately
} else {
showError(data.message || 'Failed to stop server');
}
} catch (error) {
showError('Error stopping server: ' + error.message);
}
}
async function refreshServer(serverId) {
showSuccess(`Refreshing ${serverId} server...`);
loadMCPStatus();
}
function showError(message) {
showMessage(message, 'error');
}
function showSuccess(message) {
showMessage(message, 'success');
}
function showMessage(message, type) {
const container = document.getElementById('messageContainer');
const messageDiv = document.createElement('div');
messageDiv.className = type === 'error' ? 'error-message' : 'success-message';
messageDiv.textContent = message;
container.innerHTML = '';
container.appendChild(messageDiv);
// Auto-hide after 5 seconds
setTimeout(() => {
if (messageDiv.parentNode) {
messageDiv.parentNode.removeChild(messageDiv);
}
}, 5000);
}
</script>
</body>
</html>