/**
* MCP Client for Semem UI
* Handles communication with the MCP server and manages the UI state
*/
export class MCPClient {
constructor() {
this.serverUrl = 'http://localhost:3000';
this.connected = false;
this.serverInfo = null;
this.sessions = [];
this.tools = [];
this.resources = [];
this.prompts = [];
this.currentSessionId = null;
// Initialize UI elements
this.statusIndicator = document.getElementById('mcp-status-indicator');
this.statusText = document.getElementById('mcp-status-text');
this.serverUrlInput = document.getElementById('mcp-server-url');
this.connectButton = document.getElementById('mcp-connect-btn');
this.serverName = document.getElementById('mcp-server-name');
this.serverVersion = document.getElementById('mcp-server-version');
this.serverStatus = document.getElementById('mcp-server-status');
this.serverUptime = document.getElementById('mcp-server-uptime');
this.toolsList = document.getElementById('mcp-tools-list');
this.resourcesList = document.getElementById('mcp-resources-list');
this.promptsList = document.getElementById('mcp-prompts-list');
this.serverInfoElement = document.getElementById('mcp-server-info');
// Ensure UI elements exist
if (!this.connectButton || !this.serverUrlInput) {
console.error('Required MCP UI elements not found');
return;
}
// Initialize tabs
this.setupTabs();
// Bind event listeners
this.connectButton.addEventListener('click', () => this.toggleConnection());
this.serverUrlInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.toggleConnection();
});
// Initialize the UI
this.updateUI();
}
/**
* Setup MCP internal tabs
*/
setupTabs() {
const mcpTabButtons = document.querySelectorAll('#mcp-client-tab .tab-inner-btn');
const mcpTabContents = document.querySelectorAll('#mcp-client-tab .mcp-tab-content');
mcpTabButtons.forEach(button => {
button.addEventListener('click', () => {
const targetTab = button.dataset.tab;
// Remove active class from all buttons and contents
mcpTabButtons.forEach(btn => btn.classList.remove('active'));
mcpTabContents.forEach(content => content.classList.remove('active'));
// Add active class to clicked button and corresponding content
button.classList.add('active');
const targetContent = document.getElementById(targetTab);
if (targetContent) {
targetContent.classList.add('active');
}
});
});
}
/**
* Toggle connection to MCP server
*/
async toggleConnection() {
if (this.connected) {
await this.disconnect();
} else {
await this.connect();
}
}
/**
* Connect to MCP server
*/
async connect() {
try {
this.updateStatus('connecting', 'Connecting...');
this.serverUrl = this.serverUrlInput.value.trim();
// Test connection with health check
const response = await fetch(`${this.serverUrl}/health`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const health = await response.json();
console.log('MCP Server health:', health);
// Get server info
const infoResponse = await fetch(`${this.serverUrl}/info`);
if (infoResponse.ok) {
this.serverInfo = await infoResponse.json();
}
// Mark as connected
this.connected = true;
this.updateStatus('connected', 'Connected');
this.updateServerInfo();
// Load server capabilities
await this.loadCapabilities();
} catch (error) {
console.error('Failed to connect to MCP server:', error);
this.updateStatus('error', `Connection failed: ${error.message}`);
this.connected = false;
}
}
/**
* Disconnect from MCP server
*/
async disconnect() {
this.connected = false;
this.serverInfo = null;
this.tools = [];
this.resources = [];
this.prompts = [];
this.updateStatus('disconnected', 'Disconnected');
this.updateServerInfo();
this.updateToolsList();
this.updateResourcesList();
this.updatePromptsList();
}
/**
* Load server capabilities (tools, resources, prompts)
*/
async loadCapabilities() {
try {
// Load tools
const toolsResponse = await fetch(`${this.serverUrl}/mcp`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/list'
})
});
if (toolsResponse.ok) {
const toolsData = await toolsResponse.json();
if (toolsData.result && toolsData.result.tools) {
this.tools = toolsData.result.tools;
}
}
// Load resources
const resourcesResponse = await fetch(`${this.serverUrl}/mcp`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 2,
method: 'resources/list'
})
});
if (resourcesResponse.ok) {
const resourcesData = await resourcesResponse.json();
if (resourcesData.result && resourcesData.result.resources) {
this.resources = resourcesData.result.resources;
}
}
// Load prompts
const promptsResponse = await fetch(`${this.serverUrl}/mcp`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 3,
method: 'prompts/list'
})
});
if (promptsResponse.ok) {
const promptsData = await promptsResponse.json();
if (promptsData.result && promptsData.result.prompts) {
this.prompts = promptsData.result.prompts;
}
}
// Update UI with loaded data
this.updateToolsList();
this.updateResourcesList();
this.updatePromptsList();
} catch (error) {
console.error('Failed to load MCP capabilities:', error);
}
}
/**
* Update connection status display
*/
updateStatus(status, message) {
if (this.statusText) {
this.statusText.textContent = message;
}
if (this.statusIndicator) {
this.statusIndicator.className = `status-indicator ${status}`;
}
if (this.connectButton) {
this.connectButton.textContent = this.connected ? 'Disconnect' : 'Connect';
this.connectButton.className = this.connected ? 'btn secondary-btn' : 'btn primary-btn';
}
}
/**
* Update server information display
*/
updateServerInfo() {
if (!this.serverInfoElement) return;
if (this.connected && this.serverInfo) {
this.serverInfoElement.classList.remove('hidden');
if (this.serverName) {
this.serverName.textContent = this.serverInfo.name || 'Unknown';
}
if (this.serverVersion) {
this.serverVersion.textContent = this.serverInfo.version || 'Unknown';
}
if (this.serverStatus) {
this.serverStatus.textContent = 'Running';
}
if (this.serverUptime) {
this.serverUptime.textContent = this.formatUptime(this.serverInfo.uptime);
}
} else {
this.serverInfoElement.classList.add('hidden');
}
}
/**
* Update tools list display
*/
updateToolsList() {
if (!this.toolsList) return;
if (this.tools.length > 0) {
this.toolsList.innerHTML = this.tools.map(tool => `
<div class="tool-item">
<h4>${tool.name}</h4>
<p>${tool.description || 'No description available'}</p>
<div class="tool-schema">
<strong>Input Schema:</strong>
<pre>${JSON.stringify(tool.inputSchema, null, 2)}</pre>
</div>
</div>
`).join('');
} else {
this.toolsList.innerHTML = `
<div class="empty-state">
<p>${this.connected ? 'No tools available.' : 'Connect to an MCP server to see available tools.'}</p>
</div>
`;
}
}
/**
* Update resources list display
*/
updateResourcesList() {
if (!this.resourcesList) return;
if (this.resources.length > 0) {
this.resourcesList.innerHTML = this.resources.map(resource => `
<div class="resource-item">
<h4>${resource.name}</h4>
<p><strong>URI:</strong> ${resource.uri}</p>
<p>${resource.description || 'No description available'}</p>
<p><strong>MIME Type:</strong> ${resource.mimeType || 'Unknown'}</p>
</div>
`).join('');
} else {
this.resourcesList.innerHTML = `
<div class="empty-state">
<p>${this.connected ? 'No resources available.' : 'Connect to an MCP server to see available resources.'}</p>
</div>
`;
}
}
/**
* Update prompts list display
*/
updatePromptsList() {
if (!this.promptsList) return;
if (this.prompts.length > 0) {
this.promptsList.innerHTML = this.prompts.map(prompt => `
<div class="prompt-item">
<h4>${prompt.name}</h4>
<p>${prompt.description || 'No description available'}</p>
<div class="prompt-arguments">
<strong>Arguments:</strong>
<pre>${JSON.stringify(prompt.arguments, null, 2)}</pre>
</div>
</div>
`).join('');
} else {
this.promptsList.innerHTML = `
<div class="empty-state">
<p>${this.connected ? 'No prompts available.' : 'Connect to an MCP server to see available prompts.'}</p>
</div>
`;
}
}
/**
* Format uptime in a human-readable format
*/
formatUptime(seconds) {
if (!seconds) return 'Unknown';
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
return `${hours}h ${minutes}m ${secs}s`;
}
/**
* Update the UI state
*/
updateUI() {
this.updateStatus(this.connected ? 'connected' : 'disconnected',
this.connected ? 'Connected' : 'Disconnected');
this.updateServerInfo();
this.updateToolsList();
this.updateResourcesList();
this.updatePromptsList();
}
}
/**
* Initialize MCP Client
*/
export function initMCPClient() {
console.log('Initializing MCP Client...');
// Wait for DOM to be ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.mcpClient = new MCPClient();
});
} else {
window.mcpClient = new MCPClient();
}
}