Skip to main content
Glama
code-playground.html21.2 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Code Playground</title> <style> :root { --bg-primary: #0d1117; --bg-secondary: #161b22; --bg-tertiary: #21262d; --bg-editor: #0d1117; --text-primary: #e6edf3; --text-secondary: #8b949e; --text-muted: #6e7681; --border-color: #30363d; --accent-blue: #58a6ff; --accent-green: #3fb950; --accent-purple: #a371f7; --accent-orange: #d29922; --accent-red: #f85149; --accent-cyan: #79c0ff; --font-mono: 'SF Mono', 'Fira Code', 'Monaco', 'Consolas', monospace; } * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg-primary); color: var(--text-primary); padding: 20px; } .container { max-width: 900px; margin: 0 auto; } h1 { font-size: 24px; margin-bottom: 8px; display: flex; align-items: center; gap: 10px; } .subtitle { color: var(--text-secondary); font-size: 14px; margin-bottom: 20px; } .toolbar { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; } .toolbar-group { display: flex; gap: 4px; background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 8px; padding: 4px; } .toolbar-btn { background: transparent; border: none; color: var(--text-secondary); padding: 8px 12px; border-radius: 6px; font-size: 13px; cursor: pointer; transition: all 0.15s; display: flex; align-items: center; gap: 6px; } .toolbar-btn:hover { background: var(--bg-tertiary); color: var(--text-primary); } .toolbar-btn.active { background: rgba(88, 166, 255, 0.15); color: var(--accent-blue); } .toolbar-btn.run { background: var(--accent-green); color: #fff; } .toolbar-btn.run:hover { background: #36a346; } .editor-container { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px; } .panel { background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 12px; overflow: hidden; display: flex; flex-direction: column; } .panel-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: var(--bg-tertiary); border-bottom: 1px solid var(--border-color); } .panel-title { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 8px; } .panel-actions { display: flex; gap: 8px; } .panel-btn { background: transparent; border: none; color: var(--text-muted); cursor: pointer; padding: 4px; border-radius: 4px; transition: color 0.15s; } .panel-btn:hover { color: var(--text-primary); } .editor-wrapper { position: relative; flex: 1; min-height: 300px; } .line-numbers { position: absolute; left: 0; top: 0; bottom: 0; width: 40px; background: var(--bg-tertiary); border-right: 1px solid var(--border-color); padding: 12px 8px; font-family: var(--font-mono); font-size: 13px; line-height: 1.5; color: var(--text-muted); text-align: right; user-select: none; overflow: hidden; } .code-editor { width: 100%; height: 100%; min-height: 300px; padding: 12px 16px 12px 52px; background: var(--bg-editor); border: none; color: var(--text-primary); font-family: var(--font-mono); font-size: 13px; line-height: 1.5; resize: none; outline: none; tab-size: 2; } .code-editor::placeholder { color: var(--text-muted); } .output-wrapper { flex: 1; min-height: 300px; max-height: 400px; overflow: auto; } .output { padding: 12px 16px; font-family: var(--font-mono); font-size: 13px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; } .output-line { padding: 2px 0; display: flex; gap: 8px; } .output-line.log { color: var(--text-primary); } .output-line.warn { color: var(--accent-orange); background: rgba(210, 153, 34, 0.1); padding: 2px 8px; margin: 0 -8px; border-radius: 4px; } .output-line.error { color: var(--accent-red); background: rgba(248, 81, 73, 0.1); padding: 2px 8px; margin: 0 -8px; border-radius: 4px; } .output-line.result { color: var(--accent-cyan); } .output-line.info { color: var(--accent-blue); } .output-prefix { color: var(--text-muted); user-select: none; } .empty-output { color: var(--text-muted); font-style: italic; } .examples-section { margin-top: 20px; } .examples-title { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: var(--text-secondary); } .examples-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; } .example-card { background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 8px; padding: 12px; cursor: pointer; transition: all 0.15s; } .example-card:hover { border-color: var(--accent-blue); background: rgba(88, 166, 255, 0.05); } .example-title { font-size: 13px; font-weight: 600; margin-bottom: 4px; display: flex; align-items: center; gap: 6px; } .example-desc { font-size: 12px; color: var(--text-muted); } .status-bar { display: flex; justify-content: space-between; align-items: center; padding: 8px 16px; background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 8px; margin-top: 16px; font-size: 12px; color: var(--text-muted); } .status-item { display: flex; align-items: center; gap: 6px; } .status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent-green); } .status-dot.error { background: var(--accent-red); } .actions { display: flex; gap: 12px; margin-top: 16px; } .btn { background: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 10px 20px; border-radius: 8px; font-size: 14px; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; gap: 8px; } .btn:hover { border-color: var(--accent-blue); background: rgba(88, 166, 255, 0.1); } .btn-primary { background: var(--accent-blue); border-color: var(--accent-blue); color: #fff; } .btn-primary:hover { background: #4c9aed; } @media (max-width: 700px) { .editor-container { grid-template-columns: 1fr; } } /* Syntax highlighting classes (for future enhancement) */ .token-keyword { color: var(--accent-purple); } .token-string { color: var(--accent-green); } .token-number { color: var(--accent-orange); } .token-comment { color: var(--text-muted); font-style: italic; } .token-function { color: var(--accent-blue); } </style> </head> <body> <div class="container"> <h1>🎮 Code Playground</h1> <p class="subtitle">Write, run, and share JavaScript code in real-time</p> <div class="toolbar"> <div class="toolbar-group"> <button class="toolbar-btn run" id="run-btn">▶️ Run</button> </div> <div class="toolbar-group"> <button class="toolbar-btn" id="clear-btn">🗑️ Clear</button> <button class="toolbar-btn" id="format-btn">✨ Format</button> </div> <div class="toolbar-group"> <button class="toolbar-btn" id="copy-btn">📋 Copy</button> <button class="toolbar-btn" id="share-btn">📤 Share</button> </div> </div> <div class="editor-container"> <div class="panel"> <div class="panel-header"> <span class="panel-title">📝 Code</span> <div class="panel-actions"> <button class="panel-btn" id="expand-editor" title="Expand">⤢</button> </div> </div> <div class="editor-wrapper"> <div class="line-numbers" id="line-numbers">1</div> <textarea class="code-editor" id="code-editor" placeholder="// Write your JavaScript code here... // Press Ctrl+Enter to run console.log('Hello, World!'); " spellcheck="false"></textarea> </div> </div> <div class="panel"> <div class="panel-header"> <span class="panel-title">📺 Output</span> <div class="panel-actions"> <button class="panel-btn" id="clear-output" title="Clear output">🗑️</button> </div> </div> <div class="output-wrapper"> <div class="output" id="output"> <div class="empty-output">Run your code to see output here...</div> </div> </div> </div> </div> <div class="status-bar"> <div class="status-item"> <span class="status-dot" id="status-dot"></span> <span id="status-text">Ready</span> </div> <div class="status-item"> <span id="exec-time"></span> </div> </div> <div class="examples-section"> <div class="examples-title">📚 Examples</div> <div class="examples-grid" id="examples-grid"> <!-- Generated dynamically --> </div> </div> <div class="actions"> <button class="btn btn-primary" id="send-btn">💬 Send to Chat</button> </div> </div> <script type="module"> import { App, PostMessageTransport } from "@modelcontextprotocol/ext-apps"; // State let lastResult = null; let executionHistory = []; // Examples const examples = [ { title: "Hello World", icon: "👋", desc: "Basic console output", code: `// Hello World console.log("Hello, World!"); console.log("Welcome to the playground!");` }, { title: "Array Methods", icon: "📦", desc: "Map, filter, reduce", code: `// Array manipulation const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map(n => n * 2); console.log("Doubled:", doubled); const evens = numbers.filter(n => n % 2 === 0); console.log("Evens:", evens); const sum = numbers.reduce((a, b) => a + b, 0); console.log("Sum:", sum);` }, { title: "Async/Await", icon: "⏳", desc: "Promise handling", code: `// Async operations async function fetchData() { console.log("Fetching data..."); // Simulate API call await new Promise(r => setTimeout(r, 500)); const data = { name: "John", age: 30 }; console.log("Data received:", data); return data; } fetchData().then(result => { console.log("Final result:", result); });` }, { title: "Object Destructuring", icon: "🎯", desc: "ES6+ syntax", code: `// Destructuring examples const user = { name: "Alice", age: 25, location: { city: "NYC", country: "USA" } }; const { name, age } = user; console.log(\`\${name} is \${age} years old\`); const { location: { city } } = user; console.log(\`Lives in \${city}\`); // Array destructuring const [first, second, ...rest] = [1, 2, 3, 4, 5]; console.log({ first, second, rest });` }, { title: "Classes", icon: "🏗️", desc: "OOP in JavaScript", code: `// ES6 Classes class Animal { constructor(name) { this.name = name; } speak() { console.log(\`\${this.name} makes a sound\`); } } class Dog extends Animal { speak() { console.log(\`\${this.name} barks!\`); } } const dog = new Dog("Rex"); dog.speak(); console.log("Is Dog?", dog instanceof Dog); console.log("Is Animal?", dog instanceof Animal);` }, { title: "Error Handling", icon: "⚠️", desc: "Try/catch patterns", code: `// Error handling function divide(a, b) { if (b === 0) { throw new Error("Cannot divide by zero!"); } return a / b; } try { console.log("10 / 2 =", divide(10, 2)); console.log("10 / 0 =", divide(10, 0)); } catch (error) { console.error("Error:", error.message); } finally { console.log("Operation complete"); }` } ]; // Initialize MCP App const app = new App({ appInfo: { name: "Code Playground", version: "1.0.0" }, appCapabilities: {} }); app.ontoolinput = (input) => { console.log("[Playground] Tool input:", input); if (input.arguments?.code) { document.getElementById('code-editor').value = input.arguments.code; updateLineNumbers(); } }; app.ontoolresult = (result) => { console.log("[Playground] Tool result:", result); }; // Connect to host await app.connect(new PostMessageTransport(window.parent)); console.log("[Playground] Connected to host"); // DOM elements const codeEditor = document.getElementById('code-editor'); const output = document.getElementById('output'); const lineNumbers = document.getElementById('line-numbers'); const statusDot = document.getElementById('status-dot'); const statusText = document.getElementById('status-text'); const execTime = document.getElementById('exec-time'); // Render examples const examplesGrid = document.getElementById('examples-grid'); examplesGrid.innerHTML = examples.map((ex, i) => ` <div class="example-card" data-index="${i}"> <div class="example-title">${ex.icon} ${ex.title}</div> <div class="example-desc">${ex.desc}</div> </div> `).join(''); examplesGrid.addEventListener('click', (e) => { const card = e.target.closest('.example-card'); if (card) { const index = parseInt(card.dataset.index); codeEditor.value = examples[index].code; updateLineNumbers(); runCode(); } }); // Update line numbers function updateLineNumbers() { const lines = codeEditor.value.split('\n').length; lineNumbers.innerHTML = Array.from({ length: lines }, (_, i) => i + 1).join('<br>'); } codeEditor.addEventListener('input', updateLineNumbers); codeEditor.addEventListener('scroll', () => { lineNumbers.scrollTop = codeEditor.scrollTop; }); // Tab key handling codeEditor.addEventListener('keydown', (e) => { if (e.key === 'Tab') { e.preventDefault(); const start = codeEditor.selectionStart; const end = codeEditor.selectionEnd; codeEditor.value = codeEditor.value.substring(0, start) + ' ' + codeEditor.value.substring(end); codeEditor.selectionStart = codeEditor.selectionEnd = start + 2; updateLineNumbers(); } // Ctrl/Cmd + Enter to run if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); runCode(); } }); // Run code function runCode() { const code = codeEditor.value; const startTime = performance.now(); output.innerHTML = ''; statusDot.classList.remove('error'); statusText.textContent = 'Running...'; // Create custom console const logs = []; const customConsole = { log: (...args) => logs.push({ type: 'log', args }), warn: (...args) => logs.push({ type: 'warn', args }), error: (...args) => logs.push({ type: 'error', args }), info: (...args) => logs.push({ type: 'info', args }), }; try { // Execute in async context to support await const asyncCode = `(async () => { ${code} })()`; const fn = new Function('console', asyncCode); const result = fn(customConsole); // Handle promise result Promise.resolve(result).then(value => { if (value !== undefined) { logs.push({ type: 'result', args: ['→', value] }); } renderOutput(logs); const elapsed = (performance.now() - startTime).toFixed(1); statusText.textContent = 'Completed'; execTime.textContent = `${elapsed}ms`; lastResult = { code, logs, success: true }; }).catch(error => { logs.push({ type: 'error', args: [error.message] }); renderOutput(logs); statusDot.classList.add('error'); statusText.textContent = 'Error'; lastResult = { code, logs, success: false, error: error.message }; }); } catch (error) { logs.push({ type: 'error', args: [error.message] }); renderOutput(logs); statusDot.classList.add('error'); statusText.textContent = 'Error'; execTime.textContent = ''; lastResult = { code, logs, success: false, error: error.message }; } } function renderOutput(logs) { if (logs.length === 0) { output.innerHTML = '<div class="empty-output">No output</div>'; return; } output.innerHTML = logs.map(log => { const prefix = log.type === 'result' ? '' : log.type === 'error' ? '✖ ' : log.type === 'warn' ? '⚠ ' : log.type === 'info' ? 'ℹ ' : '› '; const content = log.args.map(arg => { if (typeof arg === 'object') { try { return JSON.stringify(arg, null, 2); } catch { return String(arg); } } return String(arg); }).join(' '); return `<div class="output-line ${log.type}"><span class="output-prefix">${prefix}</span>${escapeHtml(content)}</div>`; }).join(''); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Button handlers document.getElementById('run-btn').addEventListener('click', runCode); document.getElementById('clear-btn').addEventListener('click', () => { codeEditor.value = ''; updateLineNumbers(); output.innerHTML = '<div class="empty-output">Run your code to see output here...</div>'; statusText.textContent = 'Ready'; execTime.textContent = ''; }); document.getElementById('clear-output').addEventListener('click', () => { output.innerHTML = '<div class="empty-output">Run your code to see output here...</div>'; }); document.getElementById('copy-btn').addEventListener('click', async () => { await navigator.clipboard.writeText(codeEditor.value); const btn = document.getElementById('copy-btn'); btn.textContent = '✓ Copied!'; setTimeout(() => btn.textContent = '📋 Copy', 1500); }); document.getElementById('format-btn').addEventListener('click', () => { // Basic formatting (just trim and normalize indentation for now) let code = codeEditor.value; code = code.split('\n').map(line => line.trimEnd()).join('\n'); codeEditor.value = code; updateLineNumbers(); }); document.getElementById('share-btn').addEventListener('click', () => { shareCode(); }); document.getElementById('send-btn').addEventListener('click', () => { shareCode(); }); function shareCode() { const code = codeEditor.value; if (!code.trim()) { app.sendMessage('user', [{ type: 'text', text: '⚠️ No code to share!' }]); return; } let message = '```javascript\n' + code + '\n```'; if (lastResult && lastResult.code === code) { message += '\n\n**Output:**\n```\n'; message += lastResult.logs.map(log => { const prefix = log.type === 'error' ? '✖ ' : log.type === 'warn' ? '⚠ ' : '› '; return prefix + log.args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '); }).join('\n'); message += '\n```'; } app.sendMessage('user', [{ type: 'text', text: message }]); } // Initialize updateLineNumbers(); // Notify host of size const observer = new ResizeObserver(() => { const height = document.body.scrollHeight; app.sendSizeChanged(undefined, height); }); observer.observe(document.body); </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/jamesdowzard/mcp-apps-poc'

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