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>`;
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. While it mentions 'real-time updates and file watching' as features, it doesn't describe what the server does (e.g., serves files, provides a web interface), how it handles errors, whether it runs in the background, or what happens when it starts (e.g., opens a browser). For a server-starting tool with zero annotation coverage, this leaves significant behavioral gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core action ('Start a live gallery server') and adds key features. There's no wasted text, and it's appropriately sized for the tool's complexity. However, it could be slightly more structured by separating purpose from features for better readability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool starts a server (a complex operation) with no annotations and no output schema, the description is incomplete. It doesn't explain what the server outputs (e.g., a URL, status messages), how to interact with it, or what 'real-time updates' entail. For a tool of this nature, more context on behavior and results is needed to be fully helpful to an AI agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, with clear documentation for both parameters ('workspace_path' and 'port'), including defaults. The description doesn't add any parameter-specific information beyond what's in the schema, such as format details or constraints. With high schema coverage, the baseline score of 3 is appropriate as the schema handles the parameter semantics adequately.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with a specific verb ('Start') and resource ('live gallery server'), and mentions key features ('real-time updates and file watching'). It distinguishes itself from sibling tools like 'superdesign_gallery' by specifying it's a 'live' server. However, it doesn't explicitly differentiate from all siblings in terms of scope or functionality.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention when this tool is appropriate compared to siblings like 'superdesign_gallery' (which might be a static gallery) or 'superdesign_iterate' (which might involve iterative processes). There's no information about prerequisites, dependencies, or typical use cases.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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