@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
769 lines (662 loc) • 27.4 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Identity Setup - 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: 600px;
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;
}
.progress-bar {
background: rgba(255, 255, 255, 0.2);
border-radius: 20px;
height: 6px;
margin: 20px 0;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: white;
border-radius: 20px;
transition: width 0.3s ease;
width: 0%;
}
.content {
padding: 24px;
}
.form-group {
margin-bottom: 24px;
}
.form-group:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-weight: 600;
margin-bottom: 8px;
color: var(--text-primary);
}
.form-input {
width: 100%;
padding: 12px 16px;
border: 2px solid var(--border-color);
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.3s ease;
font-family: inherit;
}
.form-input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.form-input.error {
border-color: var(--error-color);
}
.form-help {
font-size: 0.85rem;
color: var(--text-secondary);
margin-top: 4px;
}
.form-error {
font-size: 0.85rem;
color: var(--error-color);
margin-top: 4px;
}
.model-info {
background: #e8f5e8;
border: 1px solid #4caf50;
border-radius: 8px;
padding: 16px;
margin-bottom: 20px;
}
.model-info h3 {
color: #2e7d32;
margin-bottom: 8px;
font-size: 1rem;
}
.model-info p {
color: #4caf50;
font-size: 0.9rem;
margin: 0;
}
.security-info {
background: #fff3e0;
border: 1px solid #ff9800;
border-radius: 8px;
padding: 16px;
margin-bottom: 20px;
}
.security-info h3 {
color: #e65100;
margin-bottom: 8px;
font-size: 1rem;
}
.security-info p {
color: #e65100;
font-size: 0.9rem;
margin: 0;
}
.preview-section {
background: #f5f5f5;
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 16px;
margin-bottom: 20px;
}
.preview-title {
font-weight: 600;
margin-bottom: 12px;
color: var(--text-primary);
}
.identity-preview {
font-family: 'Courier New', monospace;
font-size: 0.9rem;
background: white;
padding: 12px;
border-radius: 6px;
border: 1px solid #e0e0e0;
word-break: break-all;
}
.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-primary:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.btn-secondary {
background: #f5f5f5;
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background: #e8e8e8;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none ;
}
.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); }
}
.success-message {
background: #e8f5e8;
border: 1px solid #4caf50;
border-radius: 8px;
padding: 20px;
text-align: center;
margin-bottom: 20px;
}
.success-message h3 {
color: #2e7d32;
margin-bottom: 8px;
}
.success-message p {
color: #4caf50;
}
@media (max-width: 768px) {
.container {
margin: 0;
border-radius: 0;
height: 100vh;
}
.actions {
flex-direction: column;
}
body {
padding: 0;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔐 Setup Your Identity</h1>
<p>Create your secure VectorChat identity</p>
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
</div>
<div class="content">
<!-- Model Information -->
<div class="model-info" id="modelInfo" style="display: none;">
<h3>🤖 AI Model Selected</h3>
<p id="modelDetails">Loading model information...</p>
</div>
<!-- Security Information -->
<div class="security-info">
<h3>🛡️ Security & Privacy</h3>
<p>Your identity will be cryptographically linked to your AI model using EMDM encryption with a 496T key space. This ensures maximum security and privacy for all communications.</p>
</div>
<!-- Identity Form -->
<form id="identityForm">
<div class="form-group">
<label class="form-label" for="displayName">Display Name</label>
<input type="text" class="form-input" id="displayName" placeholder="e.g., Alice, Bob, or your preferred name" required>
<div class="form-help">This is how you'll appear to other users</div>
</div>
<div class="form-group">
<label class="form-label" for="username">Username</label>
<input type="text" class="form-input" id="username" placeholder="e.g., alice, bob123" required>
<div class="form-help">Unique identifier (lowercase, no spaces)</div>
<div class="form-error" id="usernameError" style="display: none;"></div>
</div>
<div class="form-group">
<label class="form-label" for="modelPath">AI Model</label>
<select class="form-input" id="modelPath" required>
<option value="">Select your AI model...</option>
</select>
<div class="form-help">The model will be cryptographically verified for security</div>
</div>
<div class="form-group">
<label class="form-label" for="contextSize">Context Size</label>
<select class="form-input" id="contextSize" required>
<option value="2048">2K tokens (Fast, recommended)</option>
<option value="4096">4K tokens (Balanced)</option>
<option value="8192">8K tokens (Large context)</option>
<option value="16384">16K tokens (Maximum)</option>
</select>
<div class="form-help">How much conversation history the AI remembers</div>
</div>
<div class="form-group">
<label class="form-label">Advanced Options</label>
<div style="display: flex; gap: 16px; flex-wrap: wrap;">
<label style="display: flex; align-items: center; gap: 8px; font-weight: normal;">
<input type="checkbox" id="enableIPFS" checked>
<span>Enable IPFS P2P networking</span>
</label>
<label style="display: flex; align-items: center; gap: 8px; font-weight: normal;">
<input type="checkbox" id="autoDownload" checked>
<span>Auto-download missing models</span>
</label>
</div>
<div class="form-help">IPFS enables decentralized peer-to-peer communication</div>
</div>
</form>
<!-- Identity Preview -->
<div class="preview-section">
<div class="preview-title">🔍 Identity Preview</div>
<div class="identity-preview" id="identityPreview">
Generating preview...
</div>
</div>
<!-- Success Message -->
<div class="success-message" id="successMessage" style="display: none;">
<h3>✅ Identity Created Successfully!</h3>
<p>Your VectorChat identity has been configured and is ready to use.</p>
</div>
<div class="loading" id="loadingIndicator" style="display: none;">
<div class="loading-spinner"></div>
<p>Creating your identity...</p>
</div>
</div>
<div class="actions">
<button class="btn btn-secondary" onclick="goBack()">← Back</button>
<div>
<button class="btn btn-secondary" onclick="refreshPreview()">🔄 Preview</button>
<button class="btn btn-primary" onclick="createIdentity()" id="createButton">
Create Identity →
</button>
</div>
</div>
</div>
<script>
let selectedModel = null;
let currentStep = 1;
const totalSteps = 3;
// Initialize
document.addEventListener('DOMContentLoaded', function() {
loadModelOptions();
setupEventListeners();
updateProgress();
updatePreview();
// Check if model was passed from URL or parent window
const urlParams = new URLSearchParams(window.location.search);
const modelParam = urlParams.get('model');
if (modelParam) {
selectedModel = JSON.parse(decodeURIComponent(modelParam));
updateModelInfo();
}
// Listen for model selection from parent
window.addEventListener('message', function(event) {
if (event.data.type === 'model_selected') {
selectedModel = event.data.model;
updateModelInfo();
}
});
});
function setupEventListeners() {
// Update preview on input changes
['displayName', 'username', 'modelPath', 'contextSize'].forEach(id => {
document.getElementById(id).addEventListener('input', updatePreview);
document.getElementById(id).addEventListener('change', updatePreview);
});
// Username validation
document.getElementById('username').addEventListener('input', validateUsername);
}
async function loadModelOptions() {
try {
const response = await fetch('http://localhost:3737/api/scan-models', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
paths: ['~/.vectorchat/models', '~/.lmstudio/models', '~/.ollama/models']
})
});
const data = await response.json();
const modelSelect = document.getElementById('modelPath');
// Clear existing options
modelSelect.innerHTML = '<option value="">Select your AI model...</option>';
// Add available models
if (data.models && data.models.length > 0) {
data.models.forEach(model => {
const option = document.createElement('option');
option.value = model.path;
option.textContent = `${model.name} (${model.size} GB)`;
modelSelect.appendChild(option);
});
} else {
// Add fallback options
const fallbackOptions = [
{ name: 'Qwen 3 1.7B (Recommended)', path: 'qwen3-1.7b' },
{ name: 'Qwen 2.5 7B', path: 'qwen2.5-7b' },
{ name: 'GPT-2 (Fallback)', path: 'gpt2' }
];
fallbackOptions.forEach(model => {
const option = document.createElement('option');
option.value = model.path;
option.textContent = model.name;
modelSelect.appendChild(option);
});
}
} catch (error) {
console.error('Error loading models:', error);
// Show error but continue with fallback options
const modelSelect = document.getElementById('modelPath');
modelSelect.innerHTML = `
<option value="">Select your AI model...</option>
<option value="qwen3-1.7b">Qwen 3 1.7B (Recommended)</option>
<option value="qwen2.5-7b">Qwen 2.5 7B</option>
<option value="gpt2">GPT-2 (Fallback)</option>
`;
}
}
function validateUsername() {
const username = document.getElementById('username').value;
const errorElement = document.getElementById('usernameError');
if (username.length > 0) {
if (username.length < 3) {
errorElement.textContent = 'Username must be at least 3 characters';
errorElement.style.display = 'block';
document.getElementById('username').classList.add('error');
return false;
}
if (!/^[a-z0-9_-]+$/.test(username)) {
errorElement.textContent = 'Username can only contain lowercase letters, numbers, hyphens, and underscores';
errorElement.style.display = 'block';
document.getElementById('username').classList.add('error');
return false;
}
errorElement.style.display = 'none';
document.getElementById('username').classList.remove('error');
return true;
}
errorElement.style.display = 'none';
document.getElementById('username').classList.remove('error');
return true;
}
function updatePreview() {
const displayName = document.getElementById('displayName').value;
const username = document.getElementById('username').value;
const modelPath = document.getElementById('modelPath').value;
const contextSize = document.getElementById('contextSize').value;
let preview = `Display Name: ${displayName || 'Not set'}\n`;
preview += `Username: @${username || 'not-set'}\n`;
preview += `Model: ${getModelName(modelPath)}\n`;
preview += `Context: ${contextSize} tokens\n`;
preview += `IPFS: ${document.getElementById('enableIPFS').checked ? 'Enabled' : 'Disabled'}\n`;
preview += `Auto-download: ${document.getElementById('autoDownload').checked ? 'Enabled' : 'Disabled'}\n\n`;
preview += `Identity Hash: ${generateIdentityHash(displayName, username, modelPath)}`;
document.getElementById('identityPreview').textContent = preview;
updateProgress();
}
function getModelName(path) {
if (!path) return 'Not selected';
const modelNames = {
'qwen3-1.7b': 'Qwen 3 1.7B',
'qwen2.5-7b': 'Qwen 2.5 7B',
'gpt2': 'GPT-2'
};
return modelNames[path] || path.split('/').pop() || 'Unknown';
}
function generateIdentityHash(displayName, username, modelPath) {
// Simple hash generation for preview
const input = `${displayName}-${username}-${modelPath}-${Date.now()}`;
let hash = 0;
for (let i = 0; i < input.length; i++) {
const char = input.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash).toString(36).substring(0, 12).toUpperCase();
}
function updateModelInfo() {
if (selectedModel) {
document.getElementById('modelInfo').style.display = 'block';
document.getElementById('modelDetails').textContent =
`${selectedModel.name} - ${selectedModel.description}`;
document.getElementById('modelPath').value = selectedModel.path || selectedModel.id;
updatePreview();
}
}
function updateProgress() {
const displayName = document.getElementById('displayName').value;
const username = document.getElementById('username').value;
const modelPath = document.getElementById('modelPath').value;
let completedSteps = 0;
if (displayName) completedSteps++;
if (username && validateUsername()) completedSteps++;
if (modelPath) completedSteps++;
currentStep = completedSteps + 1;
const progress = (completedSteps / totalSteps) * 100;
document.getElementById('progressFill').style.width = `${progress}%`;
}
async function createIdentity() {
// Validate form
if (!validateForm()) {
showNotification('Please fill in all required fields correctly', 'error');
return;
}
showLoading();
try {
const identityData = {
displayName: document.getElementById('displayName').value,
username: document.getElementById('username').value,
modelPath: document.getElementById('modelPath').value,
contextSize: parseInt(document.getElementById('contextSize').value),
enableIPFS: document.getElementById('enableIPFS').checked,
autoDownload: document.getElementById('autoDownload').checked,
userIdentity: `${document.getElementById('username').value}@[identity]`
};
// Send to daemon
const response = await fetch('http://localhost:3737/api/apply-config', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
aiName: identityData.displayName,
userName: identityData.username,
modelType: identityData.modelPath,
contextSize: identityData.contextSize,
enableIPFS: identityData.enableIPFS,
mcpPort: 8766,
wsPort: 8765
})
});
if (response.ok) {
showSuccess();
showNotification('Identity created successfully!', 'success');
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (error) {
console.error('Error creating identity:', error);
hideLoading();
showNotification(`Failed to create identity: ${error.message}`, 'error');
}
}
function validateForm() {
const displayName = document.getElementById('displayName').value;
const username = document.getElementById('username').value;
const modelPath = document.getElementById('modelPath').value;
return displayName && username && validateUsername() && modelPath;
}
function showSuccess() {
hideLoading();
document.getElementById('successMessage').style.display = 'block';
document.getElementById('identityForm').style.display = 'none';
document.querySelector('.actions').style.display = 'none';
// Update header
document.querySelector('.header h1').textContent = '✅ Identity Created!';
document.querySelector('.header p').textContent = 'Your VectorChat identity is ready to use';
// Add continue button
setTimeout(() => {
const actions = document.querySelector('.actions');
actions.style.display = 'flex';
actions.innerHTML = `
<button class="btn btn-secondary" onclick="goBack()">← Setup Another</button>
<button class="btn btn-primary" onclick="continueToApp()">Continue to App →</button>
`;
}, 2000);
}
function continueToApp() {
// Save identity to localStorage
const identityData = {
displayName: document.getElementById('displayName').value,
username: document.getElementById('username').value,
modelPath: document.getElementById('modelPath').value,
contextSize: document.getElementById('contextSize').value,
enableIPFS: document.getElementById('enableIPFS').checked,
autoDownload: document.getElementById('autoDownload').checked
};
localStorage.setItem('vectorchat-identity', JSON.stringify(identityData));
// Redirect to main app
window.location.href = 'web-app.html';
}
function goBack() {
if (window.opener) {
window.close();
} else {
window.location.href = 'web-app.html';
}
}
function refreshPreview() {
updatePreview();
}
function showLoading() {
document.getElementById('loadingIndicator').style.display = 'block';
document.getElementById('createButton').disabled = true;
}
function hideLoading() {
document.getElementById('loadingIndicator').style.display = 'none';
document.getElementById('createButton').disabled = false;
}
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);
}
</script>
</body>
</html>