Skip to main content
Glama

superdesign_live_gallery

Start a live gallery server with real-time updates and file watching for design workspaces. Configure workspace path and port to monitor design changes automatically.

Instructions

Start a live gallery server with real-time updates and file watching

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
workspace_pathNoWorkspace path (defaults to current directory)
portNoPort for the live gallery server (default: 3000)

Implementation Reference

  • Zod input schema definition for the superdesign_live_gallery tool parameters.
    const LiveGallerySchema = z.object({
      workspace_path: z.string().optional().describe("Workspace path (defaults to current directory)"),
      port: z.number().optional().describe("Port for the live gallery server (default: 3000)")
    });
  • src/index.ts:2037-2046 (registration)
    Registration of the superdesign_live_gallery tool in the list of available tools.
      name: "superdesign_live_gallery",
      description: "Start a live gallery server with real-time updates and file watching",
      inputSchema: {
        type: "object",
        properties: {
          workspace_path: { type: "string", description: "Workspace path (defaults to current directory)" },
          port: { type: "number", description: "Port for the live gallery server (default: 3000)" }
        },
      },
    },
  • Primary handler for the superdesign_live_gallery tool: validates input, starts HTTP server via createLiveGalleryServer, and returns server URL and instructions.
          case "superdesign_live_gallery": {
            const { workspace_path, port } = LiveGallerySchema.parse(args);
            
            try {
              const superdesignDir = getSuperdeignDirectory(workspace_path);
              const designIterationsDir = path.join(superdesignDir, 'design_iterations');
              
              if (!existsSync(designIterationsDir)) {
                return {
                  content: [{ type: "text", text: "No design iterations found. Generate some designs first using superdesign_generate." }],
                };
              }
              
              const serverPort = port || 3000;
              const serverUrl = await createLiveGalleryServer(superdesignDir, serverPort);
              
              let response = `LIVE GALLERY SERVER STARTED:
    
    🌐 Server URL: ${serverUrl}
    πŸ“ Workspace: ${superdesignDir}
    πŸ”΄ Live Updates: Enabled
    πŸ“‘ Port: ${serverPort}
    
    === FEATURES ===
    ✨ Real-time file watching
    πŸ”„ Auto-refresh on file changes
    πŸŒ™ Dark/Light mode toggle
    πŸ“± Responsive design
    πŸ—‚οΈ File metadata display
    
    === USAGE ===
    1. Open ${serverUrl} in your browser
    2. The gallery will automatically update when design files change
    3. Use the theme toggle to switch between light and dark modes
    4. Click "View Full" to open designs in new tabs
    5. Use "Copy Path" to copy file paths to clipboard
    
    === LIVE UPDATES ===
    The gallery will automatically detect:
    β€’ New design files added to design_iterations/
    β€’ Modified design files (with visual highlights)
    β€’ Deleted design files (with smooth removal)
    
    Keep this server running to maintain live updates. The gallery will reconnect automatically if the connection is lost.
    
    Server is now running at: ${serverUrl}`;
              
              return {
                content: [{ type: "text", text: response }],
              };
            } catch (error: any) {
              return {
                content: [{ type: "text", text: `Error starting live gallery server: ${error.message}` }],
              };
            }
          }
  • Core helper function that creates an HTTP server for serving the live gallery HTML, design files, and Server-Sent Events (SSE) for real-time file change notifications.
    function createLiveGalleryServer(superdesignDir: string, port: number = 3000): Promise<string> {
      return new Promise((resolve, reject) => {
        const server = http.createServer((req, res) => {
          const parsedUrl = url.parse(req.url || '', true);
          const pathname = parsedUrl.pathname;
          
          // CORS headers
          res.setHeader('Access-Control-Allow-Origin', '*');
          res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
          res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
          
          if (req.method === 'OPTIONS') {
            res.writeHead(200);
            res.end();
            return;
          }
          
          if (pathname === '/') {
            // Serve the gallery HTML
            try {
              const designIterationsDir = path.join(superdesignDir, 'design_iterations');
              const designFiles = glob.sync('*.{html,svg}', { cwd: designIterationsDir });
              const galleryHtml = generateLiveGalleryHTML(designFiles, superdesignDir);
              
              res.writeHead(200, { 'Content-Type': 'text/html' });
              res.end(galleryHtml);
            } catch (error: any) {
              res.writeHead(500, { 'Content-Type': 'text/plain' });
              res.end(`Error generating gallery: ${error.message}`);
            }
          } else if (pathname === '/events') {
            // Server-Sent Events endpoint
            res.writeHead(200, {
              'Content-Type': 'text/event-stream',
              'Cache-Control': 'no-cache',
              'Connection': 'keep-alive'
            });
            
            const watcher = startFileWatcher(superdesignDir);
            watcher.clients.add(res);
            
            // Send initial connection message
            res.write('data: {"event": "connected", "data": {}}\n\n');
            
            // Clean up when client disconnects
            req.on('close', () => {
              watcher.clients.delete(res);
            });
          } else if (pathname?.startsWith('/design_iterations/')) {
            // Serve design files
            const fileName = pathname.substring('/design_iterations/'.length);
            const filePath = path.join(superdesignDir, 'design_iterations', fileName);
            
            if (existsSync(filePath)) {
              const ext = path.extname(fileName).toLowerCase();
              const contentType = ext === '.html' ? 'text/html' : 
                                ext === '.svg' ? 'image/svg+xml' : 'text/plain';
              
              res.writeHead(200, { 'Content-Type': contentType });
              res.end(readFileSync(filePath));
            } else {
              res.writeHead(404, { 'Content-Type': 'text/plain' });
              res.end('File not found');
            }
          } else {
            res.writeHead(404, { 'Content-Type': 'text/plain' });
            res.end('Not found');
          }
        });
        
        server.listen(port, () => {
          resolve(`http://localhost:${port}`);
        });
        
        server.on('error', (error) => {
          reject(error);
        });
      });
    }
  • Generates the full interactive HTML page for the live gallery, including client-side JavaScript for SSE-based real-time updates, design previews, actions (view/copy/delete), and theming.
    function generateLiveGalleryHTML(designFiles: string[], superdesignDir: string): string {
      const metadata = getDesignMetadata(superdesignDir);
      const metadataMap = new Map(metadata.map(m => [m.fileName, m]));
      
      const designCards = designFiles.map((file, index) => {
        const fileExtension = path.extname(file).toLowerCase();
        const fileName = path.basename(file, fileExtension);
        const relativePath = `/design_iterations/${file}`;
        const fileMetadata = metadataMap.get(file);
        
        let previewContent = '';
        if (fileExtension === '.html') {
          previewContent = `<iframe src="${relativePath}" loading="lazy"></iframe>`;
        } else if (fileExtension === '.svg') {
          previewContent = `<object data="${relativePath}" type="image/svg+xml" class="svg-preview"></object>`;
        }
        
        // Format file size
        const formatFileSize = (bytes: number) => {
          if (bytes < 1024) return `${bytes} B`;
          if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
          return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
        };
        
        // Format date
        const formatDate = (dateString: string) => {
          const date = new Date(dateString);
          return date.toLocaleDateString('en-US', {
            month: 'short',
            day: 'numeric',
            hour: '2-digit',
            minute: '2-digit',
            hour12: false
          });
        };
        
        return `
          <div class="design-card" data-file="${file}" data-created="${fileMetadata?.createdAt || ''}">
            <div class="design-header">
              <div class="design-title">
                <h3>${fileName}</h3>
                <span class="design-type">${fileExtension.toUpperCase()}</span>
              </div>
              <div class="design-meta">
                ${fileMetadata ? `
                  <span class="design-size">${formatFileSize(fileMetadata.fileSize)}</span>
                  <span class="design-date">${formatDate(fileMetadata.createdAt)}</span>
                ` : ''}
              </div>
            </div>
            <div class="design-preview">
              ${previewContent}
            </div>
            <div class="design-actions">
              <button onclick="openFullscreen('${relativePath}')" class="btn-primary">View Full</button>
              <button onclick="copyPath('${relativePath}')" class="btn-secondary">Copy Path</button>
              <button onclick="deleteDesign('${file}')" class="btn-danger">Delete</button>
            </div>
          </div>
        `;
      }).join('');
      
      return `<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Superdesign Live Gallery</title>
        <style>
            * {
                margin: 0;
                padding: 0;
                box-sizing: border-box;
            }
            
            :root {
                --bg-primary: #ffffff;
                --bg-secondary: #f8fafc;
                --bg-card: #ffffff;
                --text-primary: #1a202c;
                --text-secondary: #718096;
                --border-color: #e2e8f0;
                --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
                --shadow-hover: 0 10px 25px -5px rgba(0, 0, 0, 0.2);
                --accent-color: #4299e1;
            }
            
            [data-theme="dark"] {
                --bg-primary: #1a202c;
                --bg-secondary: #2d3748;
                --bg-card: #2d3748;
                --text-primary: #f7fafc;
                --text-secondary: #a0aec0;
                --border-color: #4a5568;
                --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
                --shadow-hover: 0 10px 25px -5px rgba(0, 0, 0, 0.4);
            }
            
            body {
                font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
                background: var(--bg-primary);
                color: var(--text-primary);
                min-height: 100vh;
                padding: 20px;
                transition: background 0.3s ease, color 0.3s ease;
            }
            
            .header {
                margin-bottom: 40px;
                display: flex;
                justify-content: space-between;
                align-items: flex-start;
            }
            
            .header-content h1 {
                font-size: 2.5rem;
                color: var(--text-primary);
                margin-bottom: 8px;
                font-weight: 600;
                letter-spacing: -0.02em;
            }
            
            .header-content p {
                color: var(--text-secondary);
                font-size: 1rem;
                font-weight: 400;
            }
            
            .header-controls {
                display: flex;
                gap: 12px;
                align-items: center;
            }
            
            .live-indicator {
                display: flex;
                align-items: center;
                gap: 8px;
                padding: 6px 12px;
                background: var(--bg-card);
                border: 1px solid var(--border-color);
                border-radius: 20px;
                font-size: 0.875rem;
                color: var(--text-secondary);
            }
            
            .live-dot {
                width: 8px;
                height: 8px;
                border-radius: 50%;
                background: #48bb78;
                animation: pulse 2s infinite;
            }
            
            @keyframes pulse {
                0%, 100% { opacity: 1; }
                50% { opacity: 0.5; }
            }
            
            .theme-toggle {
                background: var(--bg-card);
                border: 1px solid var(--border-color);
                color: var(--text-primary);
                padding: 8px 16px;
                border-radius: 8px;
                cursor: pointer;
                font-family: inherit;
                font-size: 0.875rem;
                transition: all 0.2s ease;
                display: flex;
                align-items: center;
                gap: 8px;
            }
            
            .theme-toggle:hover {
                background: var(--bg-secondary);
                transform: translateY(-1px);
            }
            
            .gallery-grid {
                display: grid;
                grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
                gap: 24px;
                max-width: 1400px;
            }
            
            .design-card {
                background: var(--bg-card);
                border: 1px solid var(--border-color);
                border-radius: 12px;
                box-shadow: var(--shadow);
                overflow: hidden;
                transition: transform 0.2s ease, box-shadow 0.2s ease;
            }
            
            .design-card:hover {
                transform: translateY(-4px);
                box-shadow: var(--shadow-hover);
            }
            
            .design-card.new-file {
                border-color: var(--accent-color);
                box-shadow: 0 0 0 2px rgba(66, 153, 225, 0.1);
            }
            
            .design-header {
                padding: 16px;
                border-bottom: 1px solid var(--border-color);
                display: flex;
                justify-content: space-between;
                align-items: flex-start;
            }
            
            .design-title {
                display: flex;
                align-items: center;
                gap: 8px;
            }
            
            .design-title h3 {
                color: var(--text-primary);
                font-size: 1rem;
                font-weight: 500;
                font-family: inherit;
            }
            
            .design-type {
                background: var(--bg-secondary);
                color: var(--text-secondary);
                padding: 4px 8px;
                border-radius: 4px;
                font-size: 0.75rem;
                font-weight: 400;
                font-family: inherit;
            }
            
            .design-meta {
                display: flex;
                flex-direction: column;
                align-items: flex-end;
                gap: 2px;
            }
            
            .design-size, .design-date {
                color: var(--text-secondary);
                font-size: 0.75rem;
                font-family: inherit;
            }
            
            .design-date {
                opacity: 0.8;
            }
            
            .design-preview {
                height: 300px;
                position: relative;
                overflow: hidden;
            }
            
            .design-preview iframe {
                width: 100%;
                height: 100%;
                border: none;
                transform: scale(0.5);
                transform-origin: top left;
                width: 200%;
                height: 200%;
            }
            
            .design-preview .svg-preview {
                width: 100%;
                height: 100%;
                object-fit: contain;
                background: var(--bg-primary);
            }
            
            .design-actions {
                padding: 16px;
                display: flex;
                gap: 8px;
            }
            
            .btn-primary {
                background: var(--accent-color);
                color: white;
                border: none;
                padding: 8px 16px;
                border-radius: 6px;
                font-size: 0.875rem;
                font-family: inherit;
                cursor: pointer;
                transition: background 0.2s ease;
            }
            
            .btn-primary:hover {
                background: #3182ce;
            }
            
            .btn-secondary {
                background: var(--bg-secondary);
                color: var(--text-secondary);
                border: 1px solid var(--border-color);
                padding: 8px 16px;
                border-radius: 6px;
                font-size: 0.875rem;
                font-family: inherit;
                cursor: pointer;
                transition: background 0.2s ease;
            }
            
            .btn-secondary:hover {
                background: var(--border-color);
            }
            
            .btn-danger {
                background: #e53e3e;
                color: white;
                border: none;
                padding: 8px 16px;
                border-radius: 6px;
                font-size: 0.875rem;
                font-family: inherit;
                cursor: pointer;
                transition: background 0.2s ease;
            }
            
            .btn-danger:hover {
                background: #c53030;
            }
            
            .empty-state {
                text-align: center;
                padding: 60px 20px;
                color: var(--text-secondary);
            }
            
            .empty-state h3 {
                font-size: 1.25rem;
                margin-bottom: 8px;
            }
            
            @media (max-width: 768px) {
                .gallery-grid {
                    grid-template-columns: 1fr;
                }
                
                .design-preview {
                    height: 200px;
                }
                
                .header {
                    flex-direction: column;
                    gap: 16px;
                    align-items: flex-start;
                }
            }
        </style>
    </head>
    <body>
        <div class="header">
            <div class="header-content">
                <h1>superdesign.live</h1>
                <p id="file-count">${designFiles.length} designs found</p>
            </div>
            <div class="header-controls">
                <div class="live-indicator">
                    <div class="live-dot"></div>
                    <span id="connection-status">Connecting...</span>
                </div>
                <button class="theme-toggle" onclick="toggleTheme()">
                    <span id="theme-icon">πŸŒ™</span>
                    <span id="theme-text">Dark</span>
                </button>
            </div>
        </div>
        
        <div class="gallery-grid" id="gallery-grid">
            ${designCards}
        </div>
        
        <script>
            // Theme management
            const theme = localStorage.getItem('theme') || 'light';
            document.documentElement.setAttribute('data-theme', theme);
            updateThemeButton();
            
            function toggleTheme() {
                const currentTheme = document.documentElement.getAttribute('data-theme');
                const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
                
                document.documentElement.setAttribute('data-theme', newTheme);
                localStorage.setItem('theme', newTheme);
                updateThemeButton();
            }
            
            function updateThemeButton() {
                const theme = document.documentElement.getAttribute('data-theme');
                const icon = document.getElementById('theme-icon');
                const text = document.getElementById('theme-text');
                
                if (theme === 'dark') {
                    icon.textContent = 'β˜€οΈ';
                    text.textContent = 'Light';
                } else {
                    icon.textContent = 'πŸŒ™';
                    text.textContent = 'Dark';
                }
            }
            
            function openFullscreen(path) {
                window.open(path, '_blank');
            }
            
            function copyPath(path) {
                navigator.clipboard.writeText(path).then(() => {
                    showToast('Path copied to clipboard!', 'success');
                });
            }
            
            function deleteDesign(fileName) {
                if (confirm(\`Are you sure you want to delete \${fileName}?\`)) {
                    showToast('Delete functionality via MCP server!', 'info');
                }
            }
            
            function showToast(message, type = 'info') {
                const colors = {
                    success: '#48bb78',
                    error: '#e53e3e',
                    info: '#4299e1',
                    warning: '#ed8936'
                };
                
                const toast = document.createElement('div');
                toast.textContent = message;
                toast.style.cssText = \`
                    position: fixed;
                    top: 20px;
                    right: 20px;
                    background: \${colors[type] || colors.info};
                    color: white;
                    padding: 12px 20px;
                    border-radius: 6px;
                    z-index: 1000;
                    font-size: 14px;
                    font-family: inherit;
                    animation: slideIn 0.3s ease;
                \`;
                
                document.body.appendChild(toast);
                setTimeout(() => {
                    toast.style.animation = 'slideOut 0.3s ease';
                    setTimeout(() => toast.remove(), 300);
                }, 3000);
            }
            
            // Server-Sent Events for live updates
            let eventSource;
            
            function connectToEvents() {
                eventSource = new EventSource('/events');
                
                eventSource.onopen = function() {
                    document.getElementById('connection-status').textContent = 'Live';
                    showToast('Connected to live updates!', 'success');
                };
                
                eventSource.onmessage = function(event) {
                    const data = JSON.parse(event.data);
                    handleFileChange(data.event, data.data);
                };
                
                eventSource.onerror = function() {
                    document.getElementById('connection-status').textContent = 'Disconnected';
                    showToast('Connection lost, attempting to reconnect...', 'warning');
                    
                    // Attempt to reconnect after 3 seconds
                    setTimeout(() => {
                        eventSource.close();
                        connectToEvents();
                    }, 3000);
                };
            }
            
            function handleFileChange(event, data) {
                const { file, type } = data;
                
                if (type === 'added') {
                    showToast(\`New design added: \${file}\`, 'success');
                    // Refresh the gallery to show new file
                    setTimeout(() => location.reload(), 1000);
                } else if (type === 'deleted') {
                    showToast(\`Design deleted: \${file}\`, 'info');
                    // Remove the card from the DOM
                    const card = document.querySelector(\`[data-file="\${file}"]\`);
                    if (card) {
                        card.style.animation = 'fadeOut 0.3s ease';
                        setTimeout(() => card.remove(), 300);
                        updateFileCount();
                    }
                } else if (type === 'modified') {
                    showToast(\`Design updated: \${file}\`, 'info');
                    // Highlight the modified file
                    const card = document.querySelector(\`[data-file="\${file}"]\`);
                    if (card) {
                        card.classList.add('new-file');
                        setTimeout(() => card.classList.remove('new-file'), 3000);
                        
                        // Refresh the iframe if it's an HTML file
                        const iframe = card.querySelector('iframe');
                        if (iframe) {
                            iframe.src = iframe.src;
                        }
                    }
                }
            }
            
            function updateFileCount() {
                const cards = document.querySelectorAll('.design-card');
                document.getElementById('file-count').textContent = \`\${cards.length} designs found\`;
            }
            
            // Add CSS animations
            const style = document.createElement('style');
            style.textContent = \`
                @keyframes slideIn {
                    from { transform: translateX(100%); opacity: 0; }
                    to { transform: translateX(0); opacity: 1; }
                }
                @keyframes slideOut {
                    from { transform: translateX(0); opacity: 1; }
                    to { transform: translateX(100%); opacity: 0; }
                }
                @keyframes fadeOut {
                    from { opacity: 1; transform: scale(1); }
                    to { opacity: 0; transform: scale(0.8); }
                }
            \`;
            document.head.appendChild(style);
            
            // Connect to live updates
            connectToEvents();
        </script>
    </body>
    </html>`;
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jonthebeef/superdesign-mcp-claude-code'

If you have feedback or need assistance with the MCP directory API, please join our Discord server