<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MCP Server - Monitoring Dashboard</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
.header {
background: white;
padding: 30px;
border-radius: 12px;
margin-bottom: 30px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.header h1 {
color: #667eea;
font-size: 32px;
margin-bottom: 10px;
}
.header p {
color: #666;
font-size: 16px;
}
.status-banner {
background: white;
padding: 20px;
border-radius: 12px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.status-indicator {
display: flex;
align-items: center;
gap: 15px;
}
.status-dot {
width: 16px;
height: 16px;
border-radius: 50%;
animation: pulse 2s infinite;
}
.status-dot.online {
background: #10b981;
box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
}
.status-dot.offline {
background: #ef4444;
box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.status-text {
font-size: 18px;
font-weight: 600;
}
.status-text.online {
color: #10b981;
}
.status-text.offline {
color: #ef4444;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.card {
background: white;
padding: 25px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.card h2 {
color: #333;
margin-bottom: 15px;
font-size: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.tool-list {
list-style: none;
}
.tool-item {
padding: 12px;
margin-bottom: 10px;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #667eea;
cursor: pointer;
transition: all 0.3s;
}
.tool-item:hover {
background: #e9ecef;
transform: translateX(5px);
}
.tool-name {
font-weight: 600;
color: #667eea;
margin-bottom: 5px;
}
.tool-desc {
font-size: 14px;
color: #666;
}
.test-panel {
background: white;
padding: 25px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
}
select, textarea {
width: 100%;
padding: 12px;
border: 2px solid #e5e7eb;
border-radius: 8px;
font-size: 14px;
transition: border 0.3s;
}
select:focus, textarea:focus {
outline: none;
border-color: #667eea;
}
textarea {
resize: vertical;
font-family: 'Courier New', monospace;
min-height: 120px;
}
.btn {
padding: 12px 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: all 0.3s;
width: 100%;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.btn:active {
transform: translateY(0);
}
.result-box {
margin-top: 20px;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #667eea;
display: none;
}
.result-box.show {
display: block;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.result-box h3 {
color: #333;
margin-bottom: 15px;
font-size: 18px;
}
.result-content {
background: white;
padding: 15px;
border-radius: 6px;
font-family: 'Courier New', monospace;
font-size: 13px;
white-space: pre-wrap;
max-height: 400px;
overflow-y: auto;
color: #333;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin-top: 15px;
}
.stat-item {
text-align: center;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
}
.stat-value {
font-size: 28px;
font-weight: bold;
color: #667eea;
}
.stat-label {
font-size: 12px;
color: #666;
margin-top: 5px;
}
.info-box {
background: #e0e7ff;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #667eea;
margin-top: 15px;
}
.info-box strong {
color: #667eea;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>π§ MCP Server Dashboard</h1>
<p>Model Context Protocol Server - External Data Connector</p>
</div>
<div class="status-banner">
<div class="status-indicator">
<div id="statusDot" class="status-dot offline"></div>
<span id="statusText" class="status-text offline">μλ² μν νμΈ μ€...</span>
</div>
<div>
<button class="btn" onclick="refreshStatus()" style="width: auto; padding: 8px 16px; font-size: 14px;">
π μλ‘κ³ μΉ¨
</button>
</div>
</div>
<div class="grid">
<div class="card">
<h2>π μ¬μ© κ°λ₯ν λꡬ</h2>
<ul id="toolList" class="tool-list">
<li style="color: #999; padding: 12px;">λ‘λ© μ€...</li>
</ul>
</div>
<div class="card">
<h2>π μλ² μ 보</h2>
<div class="stats-grid">
<div class="stat-item">
<div id="toolCount" class="stat-value">-</div>
<div class="stat-label">λꡬ μ</div>
</div>
<div class="stat-item">
<div id="requestCount" class="stat-value">0</div>
<div class="stat-label">μμ² μ</div>
</div>
<div class="stat-item">
<div id="uptime" class="stat-value">-</div>
<div class="stat-label">κ°λ μκ°</div>
</div>
</div>
<div class="info-box">
<div><strong>νλ‘ν μ½:</strong> HTTP REST API</div>
<div style="margin-top: 5px;"><strong>MCP μλ²:</strong> <code id="mcpServerUrl">http://localhost:3000</code></div>
<div style="margin-top: 5px;"><strong>Direct Access:</strong> <code id="apiProxyUrl">http://localhost:3000</code></div>
</div>
</div>
</div>
<div class="test-panel">
<h2 style="margin-bottom: 20px;">π§ͺ λꡬ ν
μ€νΈ</h2>
<div class="form-group">
<label for="toolSelect">λꡬ μ ν</label>
<select id="toolSelect" onchange="updateArguments()">
<option value="">λꡬλ₯Ό μ ννμΈμ</option>
</select>
</div>
<div class="form-group">
<label for="toolArgs">μΈμ (JSON νμ)</label>
<textarea id="toolArgs" placeholder='{"latitude": 37.5665, "longitude": 126.9780}'>{"latitude": 37.5665, "longitude": 126.9780}</textarea>
</div>
<button class="btn" onclick="executeTool()">βΆ λꡬ μ€ν</button>
<div id="resultBox" class="result-box">
<h3>β
μ€ν κ²°κ³Ό</h3>
<div id="resultContent" class="result-content"></div>
</div>
</div>
</div>
<script>
// Auto-detect MCP Server URL (direct HTTP server on port 3000)
// If accessing via network IP, use that IP for the server as well
const getMCPServerUrl = () => {
const host = window.location.hostname;
if (host !== 'localhost' && host !== '127.0.0.1') {
return `http://${host}:3000`;
}
return 'http://localhost:3000';
};
const MCP_SERVER_URL = getMCPServerUrl();
let tools = [];
let requestCount = 0;
let startTime = Date.now();
// Display current MCP server URL in console
console.log('MCP Server URL:', MCP_SERVER_URL);
// Update UI with current server URLs
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('apiProxyUrl').textContent = MCP_SERVER_URL;
const host = window.location.hostname;
if (host !== 'localhost' && host !== '127.0.0.1') {
document.getElementById('mcpServerUrl').textContent = `http://${host}:3000`;
}
});
// Update uptime every second
setInterval(() => {
const uptime = Math.floor((Date.now() - startTime) / 1000);
const minutes = Math.floor(uptime / 60);
const seconds = uptime % 60;
document.getElementById('uptime').textContent = `${minutes}m ${seconds}s`;
}, 1000);
async function checkServerStatus() {
try {
// Try to fetch from MCP wrapper (standalone HTTP server)
const response = await fetch(`${MCP_SERVER_URL}/capabilities`);
const data = await response.json();
const statusDot = document.getElementById('statusDot');
const statusText = document.getElementById('statusText');
if (data.available) {
statusDot.className = 'status-dot online';
statusText.className = 'status-text online';
statusText.textContent = 'β μλ² μ¨λΌμΈ';
return true;
} else {
throw new Error('MCP server not available');
}
} catch (error) {
const statusDot = document.getElementById('statusDot');
const statusText = document.getElementById('statusText');
statusDot.className = 'status-dot offline';
statusText.className = 'status-text offline';
statusText.textContent = 'β μλ² μ€νλΌμΈ';
return false;
}
}
async function loadTools() {
try {
// Load tools via MCP wrapper
const response = await fetch(`${MCP_SERVER_URL}/capabilities`);
const data = await response.json();
if (data.available && data.tools) {
tools = data.tools;
document.getElementById('toolCount').textContent = tools.length;
// Update tool list
const toolList = document.getElementById('toolList');
toolList.innerHTML = tools.map(tool => `
<li class="tool-item" onclick="selectTool('${tool.name}')">
<div class="tool-name">π§ ${tool.name}</div>
<div class="tool-desc">${tool.description || 'μ€λͺ
μμ'}</div>
</li>
`).join('');
// Update tool select
const toolSelect = document.getElementById('toolSelect');
toolSelect.innerHTML = '<option value="">λꡬλ₯Ό μ ννμΈμ</option>' +
tools.map(tool => `<option value="${tool.name}">${tool.name}</option>`).join('');
} else {
throw new Error('No tools available');
}
} catch (error) {
console.error('Error loading tools:', error);
document.getElementById('toolList').innerHTML =
'<li style="color: #ef4444; padding: 12px;">β λꡬλ₯Ό λΆλ¬μ¬ μ μμ΅λλ€</li>';
document.getElementById('toolCount').textContent = '0';
}
}
function selectTool(toolName) {
document.getElementById('toolSelect').value = toolName;
updateArguments();
}
function updateArguments() {
const toolName = document.getElementById('toolSelect').value;
if (!toolName) return;
const tool = tools.find(t => t.name === toolName);
if (tool && tool.input_schema && tool.input_schema.properties) {
// Create sample arguments based on schema
const sampleArgs = {};
Object.entries(tool.input_schema.properties).forEach(([key, schema]) => {
if (key === 'latitude') sampleArgs[key] = 37.5665;
else if (key === 'longitude') sampleArgs[key] = 126.9780;
else if (key === 'keywords') sampleArgs[key] = ['λΉλ¬Όλ°μ΄', 'λ§ν'];
else if (key === 'radius_km') sampleArgs[key] = 1.0;
else if (schema.type === 'string') sampleArgs[key] = '';
else if (schema.type === 'number') sampleArgs[key] = 0;
else if (schema.type === 'array') sampleArgs[key] = [];
});
document.getElementById('toolArgs').value = JSON.stringify(sampleArgs, null, 2);
}
}
async function executeTool() {
const toolName = document.getElementById('toolSelect').value;
if (!toolName) {
alert('λꡬλ₯Ό μ ννμΈμ');
return;
}
const argsText = document.getElementById('toolArgs').value;
let args;
try {
args = JSON.parse(argsText);
} catch (error) {
alert('JSON νμμ΄ μ¬λ°λ₯΄μ§ μμ΅λλ€: ' + error.message);
return;
}
const resultBox = document.getElementById('resultBox');
const resultContent = document.getElementById('resultContent');
resultBox.className = 'result-box show';
resultContent.textContent = 'β³ μ€ν μ€...';
try {
requestCount++;
document.getElementById('requestCount').textContent = requestCount;
const startTime = performance.now();
// Execute tool via MCP wrapper
const response = await fetch(`${MCP_SERVER_URL}/execute/${toolName}`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(args)
});
const result = await response.json();
const duration = (performance.now() - startTime).toFixed(2);
resultContent.textContent = `β±οΈ μ²λ¦¬ μκ°: ${duration}ms\n\n` +
JSON.stringify(result, null, 2);
} catch (error) {
console.error('Error executing tool:', error);
resultContent.textContent = 'β μ€λ₯:\n' + error.message;
}
}
async function refreshStatus() {
await checkServerStatus();
await loadTools();
}
// Initial load
(async () => {
await checkServerStatus();
await loadTools();
})();
// Auto-refresh every 30 seconds
setInterval(refreshStatus, 30000);
</script>
</body>
</html>