@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
439 lines (372 loc) • 15.6 kB
JavaScript
// VectorChat Setup Wizard - Client-side JavaScript
let currentStep = 1;
const totalSteps = 6;
// Parse URL parameters to pre-fill form
function getUrlParams() {
const params = new URLSearchParams(window.location.search);
return {
username: params.get('username') || 'Allicia',
modelType: params.get('modelType') || 'auto',
modelPath: params.get('modelPath') || '',
contextSize: params.get('contextSize') || '2048',
enableIPFS: params.get('enableIPFS') === 'true'
};
}
// Pre-fill form with URL parameters
function preFillForm() {
const params = getUrlParams();
// Pre-fill identity
if (document.getElementById('userId')) {
document.getElementById('userId').value = params.username;
}
// Pre-fill model settings
if (document.getElementById('modelType')) {
document.getElementById('modelType').value = params.modelType;
updateModelOptions();
}
if (document.getElementById('modelPath') && params.modelPath) {
document.getElementById('modelPath').value = params.modelPath;
}
if (document.getElementById('contextSize')) {
document.getElementById('contextSize').value = params.contextSize;
}
if (document.getElementById('enableIPFS')) {
document.getElementById('enableIPFS').checked = params.enableIPFS;
}
}
function updateProgress() {
const progress = ((currentStep - 1) / (totalSteps - 1)) * 100;
document.getElementById('progressBar').style.width = progress + '%';
}
function nextStep() {
document.getElementById('step' + currentStep).classList.remove('active');
currentStep++;
document.getElementById('step' + currentStep).classList.add('active');
updateProgress();
// Run step-specific actions
if (currentStep === 2) {
runSystemCheck();
}
if (currentStep === 4) {
// Check default model when entering model configuration step
const modelType = document.getElementById('modelType').value;
if (modelType === 'qwen3-1.7b') {
checkDefaultModel();
}
}
}
function prevStep() {
document.getElementById('step' + currentStep).classList.remove('active');
currentStep--;
document.getElementById('step' + currentStep).classList.add('active');
updateProgress();
}
function updateModelOptions() {
const modelType = document.getElementById('modelType').value;
const customPath = document.getElementById('customModelPath');
const defaultModelInfo = document.getElementById('defaultModelInfo');
customPath.style.display = modelType === 'custom' ? 'block' : 'none';
// Show default model info for Qwen 3 1.7B
if (defaultModelInfo) {
defaultModelInfo.style.display = modelType === 'qwen3-1.7b' ? 'block' : 'none';
}
// Check if model exists when selecting default model
if (modelType === 'qwen3-1.7b') {
checkDefaultModel();
}
}
// Check if default model exists
async function checkDefaultModel() {
const modelCheckIcon = document.getElementById('modelCheckIcon');
const modelCheckStatus = document.getElementById('modelCheckStatus');
const downloadSection = document.getElementById('downloadSection');
const step4Next = document.getElementById('step4Next');
try {
const response = await fetch('/api/check-model');
const data = await response.json();
if (data.exists) {
modelCheckIcon.textContent = '✓';
modelCheckIcon.className = 'status-icon success';
modelCheckStatus.textContent = `Model found: ${data.path}`;
downloadSection.style.display = 'none';
step4Next.disabled = false;
} else {
modelCheckIcon.textContent = '⚠️';
modelCheckIcon.className = 'status-icon warning';
modelCheckStatus.textContent = 'Model not found - download required';
downloadSection.style.display = 'block';
step4Next.disabled = true;
}
} catch (error) {
console.error('Model check failed:', error);
modelCheckIcon.textContent = '✗';
modelCheckIcon.className = 'status-icon error';
modelCheckStatus.textContent = 'Error checking model';
}
}
// Download model from IPFS
async function downloadModelFromIPFS() {
const downloadBtn = document.getElementById('downloadBtn');
const downloadProgress = document.getElementById('downloadProgress');
const downloadProgressBar = document.getElementById('downloadProgressBar');
const downloadStatus = document.getElementById('downloadStatus');
const step4Next = document.getElementById('step4Next');
// Disable button and show progress
downloadBtn.disabled = true;
downloadBtn.textContent = '⏳ Downloading...';
downloadProgress.style.display = 'block';
try {
const response = await fetch('/api/download-model', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
cid: 'QmXmPxxtLxtDgh4CYscgTDXyCbNfVWdzhG2UgS6fcX6mXS',
name: 'Qwen3-1.7B-Q6_K.gguf',
size: 1717986816
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const lines = text.split('\n');
for (const line of lines) {
if (!line.trim()) continue;
try {
const data = JSON.parse(line);
if (data.progress !== undefined) {
const percent = Math.round(data.progress * 100);
downloadProgressBar.style.width = percent + '%';
downloadProgressBar.textContent = percent + '%';
const downloaded = formatBytes(data.downloaded);
const total = formatBytes(data.total);
downloadStatus.textContent = `Downloaded: ${downloaded} / ${total}`;
}
if (data.complete) {
downloadProgressBar.style.width = '100%';
downloadProgressBar.textContent = '100%';
downloadStatus.textContent = '✓ Download complete!';
downloadBtn.textContent = '✓ Downloaded';
step4Next.disabled = false;
// Update model check status
const modelCheckIcon = document.getElementById('modelCheckIcon');
const modelCheckStatus = document.getElementById('modelCheckStatus');
modelCheckIcon.textContent = '✓';
modelCheckIcon.className = 'status-icon success';
modelCheckStatus.textContent = `Model downloaded: ${data.path}`;
}
if (data.error) {
throw new Error(data.error);
}
} catch (e) {
console.error('Parse error:', e);
}
}
}
} catch (error) {
console.error('Download failed:', error);
downloadStatus.textContent = '✗ Download failed: ' + error.message;
downloadBtn.disabled = false;
downloadBtn.textContent = '🔄 Retry Download';
}
}
// Format bytes to human-readable
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
}
async function runSystemCheck() {
try {
const response = await fetch('/api/system-check');
const data = await response.json();
// Update Python status
updateStatus('python', data.python);
// Update Node status
updateStatus('node', data.node);
// Update dependencies status
updateStatus('deps', data.dependencies);
// Enable next button if all checks pass
const allPassed = data.python.ok && data.node.ok && data.dependencies.ok;
document.getElementById('step2Next').disabled = !allPassed;
} catch (error) {
console.error('System check failed:', error);
// Show error state
updateStatus('python', { ok: false, message: 'Connection error' });
updateStatus('node', { ok: false, message: 'Connection error' });
updateStatus('deps', { ok: false, message: 'Connection error' });
}
}
function updateStatus(type, data) {
const icon = document.getElementById(type + 'Icon');
const status = document.getElementById(type + 'Status');
if (data.ok) {
icon.textContent = '✓';
icon.className = 'status-icon success';
status.textContent = data.message;
if (type === 'deps') {
const fixBox = document.getElementById('depsFixBox');
if (fixBox) {
fixBox.style.display = 'none';
}
}
} else {
icon.textContent = '✗';
icon.className = 'status-icon error';
status.textContent = data.message;
if (type === 'deps') {
const fixBox = document.getElementById('depsFixBox');
const fixCommands = document.getElementById('depsFixCommands');
const fixTitle = document.getElementById('depsFixTitle');
const fixNote = document.getElementById('depsFixNote');
if (fixBox && fixCommands && fixTitle && fixNote && data.fix) {
fixBox.style.display = 'block';
fixTitle.textContent = data.fix.title || 'Install required build tools:';
fixCommands.textContent = (data.fix.commands || []).join('\n');
fixNote.textContent = data.fix.note || '';
}
}
}
}
async function applyConfiguration() {
const config = {
userId: document.getElementById('userId').value,
modelType: document.getElementById('modelType').value,
modelPath: document.getElementById('modelPath').value,
contextSize: parseInt(document.getElementById('contextSize').value),
enableIPFS: document.getElementById('enableIPFS').checked,
mcpPort: parseInt(document.getElementById('mcpPort').value),
wsPort: parseInt(document.getElementById('wsPort').value)
};
try {
const response = await fetch('/api/configure', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
const result = await response.json();
if (result.success) {
nextStep();
} else {
alert('Configuration failed: ' + result.error);
}
} catch (error) {
alert('Failed to apply configuration: ' + error.message);
}
}
async function startDaemon() {
try {
const response = await fetch('/api/start-daemon', { method: 'POST' });
const result = await response.json();
if (result.success) {
alert('✓ Daemon started successfully!\n\nYou can now use VectorChat with your AI assistants.');
window.close();
} else {
alert('Failed to start daemon: ' + result.error);
}
} catch (error) {
alert('Failed to start daemon: ' + error.message);
}
}
// Quick Setup (minimal interaction)
async function quickSetup() {
const aiName = document.getElementById('aiName').value.trim();
const userName = document.getElementById('userName').value.trim();
if (!aiName) {
alert('Please enter a name for your AI assistant');
return;
}
if (!userName) {
alert('Please enter your name');
return;
}
// Use default configuration
const config = {
userId: aiName, // AI assistant name
displayName: aiName,
identityType: 'ai_assistant',
modelType: 'qwen3-1.7b',
modelPath: `~/.vectorchat/models/Qwen3-1.7B-Q6_K.gguf`,
contextSize: 2048,
enableIPFS: true,
ipfsApi: 'http://localhost:5001',
mcpPort: 8766,
wsPort: 8767,
userName: userName, // Store user name separately
quickSetup: true
};
// Save configuration
try {
const response = await fetch('/api/save-config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
if (response.ok) {
// Check if model exists
const modelCheck = await fetch('/api/check-model');
const modelData = await modelCheck.json();
if (!modelData.exists) {
// Show model download step
currentStep = 4;
document.getElementById('step1').classList.remove('active');
document.getElementById('step4').classList.add('active');
updateProgress();
updateModelOptions();
} else {
// Complete setup
showSuccess();
}
} else {
alert('Failed to save configuration. Please try again.');
}
} catch (error) {
console.error('Quick setup error:', error);
alert('Setup failed. Please try the advanced setup.');
}
}
function showSuccess() {
// Hide all steps
for (let i = 1; i <= 6; i++) {
const step = document.getElementById(`step${i}`);
if (step) step.style.display = 'none';
}
// Show success message
document.body.innerHTML = `
<div style="text-align: center; padding: 50px; font-family: Arial, sans-serif;">
<h1>🎉 VectorChat Setup Complete!</h1>
<p>Your AI assistant is ready to use.</p>
<p>You can now close this window and start using VectorChat.</p>
<button onclick="window.location.href='/ui'" style="padding: 10px 20px; font-size: 16px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer;">
Open VectorChat UI
</button>
</div>
`;
}
// Show advanced setup wizard
function showAdvancedSetup() {
// Copy values from quick setup to advanced fields
const aiName = document.getElementById('aiName').value;
const userName = document.getElementById('userName').value;
document.getElementById('aiNameAdvanced').value = aiName;
document.getElementById('userId').value = userName;
// Go to system check step
nextStep();
}
// Initialize
updateProgress();
// Pre-fill form when page loads
window.addEventListener('DOMContentLoaded', () => {
preFillForm();
// Show message if pre-filled
const params = getUrlParams();
if (params.username !== 'Allicia') {
console.log('Form pre-filled with AI-gathered preferences:', params);
}
// Initialize model selection display
updateModelOptions();
});