Skip to main content
Glama
scenario.yaml16 kB
# Adds an MCP server from the Examples tab (sequential thinking via docker) and verifies it renders name: Add MCP server from Examples (Sequential Thinking via docker) image: us-central1-docker.pkg.dev/prj-common-442813/mcpx/mcpx:v0.2.17-66354a7 env: {} dependentContainers: [] configMount: . cleanConfigMount: true verboseOutput: false steps: - name: Load Control-Plane UI kind: browser toolName: browser_navigate payload: url: http://localhost:5173 expected: mode: regex value: "Ran Playwright code" - name: Wait for Add Server button kind: browser toolName: browser_wait_for payload: text: "Add Server" time: 12 expected: mode: contains value: "Waited for Add Server" - name: Click “Add Server” button kind: browser toolName: browser_evaluate payload: function: | () => { const btn = Array.from(document.querySelectorAll('button')) .find(b => /(^|\s)Add Server(\s|$)/i.test(b.textContent || '') && b.offsetParent !== null); btn?.click(); return btn ? 'add-server-clicked' : 'add-server-not-found'; } expected: mode: contains value: "add-server-clicked" - name: Wait for Add MCP Server dialog kind: browser toolName: browser_wait_for payload: text: "Add MCP Server" time: 10 expected: mode: contains value: "Waited for Add MCP Server" - name: Switch to “Examples” tab kind: browser toolName: browser_evaluate payload: function: | () => { const dlg = document.querySelector('[role="dialog"]'); if (!dlg) return 'no-dialog'; const tab = Array.from(dlg.querySelectorAll('[role="tab"],button')) .find(el => /examples/i.test(el.textContent || '')); if (!tab) return 'examples-tab-not-found'; ['pointerdown','mousedown','pointerup','mouseup','click'].forEach(t => tab.dispatchEvent(new MouseEvent(t, { bubbles: true, cancelable: true })) ); return 'examples-tab-open'; } expected: mode: contains value: "examples-tab-open" - name: Select “Sequential Thinking (stdio-docker)” in examples (robust) kind: browser toolName: browser_evaluate payload: function: | () => new Promise((resolve) => { const dlg = document.querySelector('[role="dialog"]'); if (!dlg) return resolve('no-dialog'); // Open the Select Server Type combobox const combo = dlg.querySelector('[role="combobox"],[aria-haspopup="listbox"]') || Array.from(dlg.querySelectorAll('button,[role="button"]')) .find(b => /Select Server Type/i.test(b.textContent || '')); if (!combo) return resolve('combobox-not-found'); ['pointerdown','mousedown','pointerup','mouseup','click'].forEach(t => combo.dispatchEvent(new MouseEvent(t, { bubbles: true, cancelable: true })) ); const deadline = Date.now() + 5000; (function pick() { const list = document.querySelector('[role="listbox"]'); // portal if (list && list.getBoundingClientRect().height > 0) { const target = Array.from(list.querySelectorAll('[role="option"],[data-radix-collection-item],li,div')) .find(el => /^\s*Sequential\s*Thinking\s*\(stdio-docker\)\s*$/i.test(el.textContent || '')); if (!target) { if (Date.now() > deadline) return resolve('sequential-option-not-found'); return setTimeout(pick, 100); } // Make sure it’s visible/clickable target.scrollIntoView({ block: 'center' }); ['pointerdown','mousedown','pointerup','mouseup','click'].forEach(t => target.dispatchEvent(new MouseEvent(t, { bubbles: true, cancelable: true })) ); // Some headless combos require Enter to commit target.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); target.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', bubbles: true })); // Wait for listbox to close and label to update const doneBy = Date.now() + 2000; (function settle() { const stillOpen = document.querySelector('[role="listbox"]'); const label = (combo.textContent || '').trim(); if (!stillOpen && /Sequential\s*Thinking\s*\(stdio-docker\)/i.test(label)) { return resolve('sequential-option-picked'); } if (Date.now() > doneBy) return resolve('sequential-option-clicked'); setTimeout(settle, 80); })(); return; } if (Date.now() > deadline) return resolve('listbox-not-open'); setTimeout(pick, 80); })(); }) expected: mode: regex value: "sequential-option-picked|sequential-option-clicked" - name: Confirm examples preview is Sequential Thinking kind: browser toolName: browser_evaluate payload: function: | () => { const dlg = document.querySelector('[role="dialog"]'); if (!dlg) return 'no-dialog'; // Grab everything in the preview area (text, code) const previewText = Array.from( dlg.querySelectorAll('section,div,pre,code,textarea,[data-preview]') ).map(n => n.textContent || '').join('\n'); // Accept any of these signals that we’re on sequential thinking const ok = /sequential\s*thinking/i.test(previewText) || /stdio-docker/i.test(previewText) || /sequentialthinking/i.test(previewText); return ok ? 'sequential-preview-ok' : 'sequential-preview-missing'; } expected: mode: contains value: "sequential-preview-ok" - name: Click “Use This Example” kind: browser toolName: browser_evaluate payload: function: | () => { const dlg = document.querySelector('[role="dialog"]'); if (!dlg) return 'no-dialog'; const btn = Array.from(dlg.querySelectorAll('button')) .find(b => /Use This Example/i.test(b.textContent || '')); if (!btn) return 'use-example-not-found'; ['pointerdown','mousedown','pointerup','mouseup','click'].forEach((type) => btn.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true })) ); return 'used-example'; } expected: mode: contains value: "used-example" - name: Switch back to JSON tab kind: browser toolName: browser_evaluate payload: function: | () => { const dlg = document.querySelector('[role="dialog"]'); if (!dlg) return 'no-dialog'; const tab = Array.from(dlg.querySelectorAll('[role="tab"],button')) .find(el => /json\s*config/i.test(el.textContent || '')); if (!tab) return 'json-tab-not-found'; ['pointerdown','mousedown','pointerup','mouseup','click'].forEach((type) => tab.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true })) ); return 'json-tab-open'; } expected: mode: contains value: "json-tab-open" - name: Verify editor has sequentialthinking JSON kind: browser toolName: browser_evaluate payload: function: | () => new Promise((resolve) => { const deadline = Date.now() + 15000; const readBuffer = (dlg) => { const M = window.monaco; let buf = ''; let modelsSnapshot = []; if (M?.editor?.getModels) { const models = M.editor.getModels(); modelsSnapshot = models.map((model) => model.getValue()); if (models?.length) { buf = models.reduce( (acc, model) => (model.getValue().length > acc.length ? model.getValue() : acc), '' ); } } if (!buf) { const ta = dlg.querySelector( 'textarea[aria-label="Editor content"], textarea' ); buf = (ta && ta.value) || ''; } return { buf, modelsSnapshot }; }; const poll = () => { const dlg = document.querySelector('[role="dialog"]'); if (!dlg) { return resolve('no-dialog'); } const { buf, modelsSnapshot } = readBuffer(dlg); const text = dlg.innerText || ''; const ok = /"sequentialthinking"\s*:/.test(buf) || /"sequentialthinking"\s*:/.test(text) || /docker/i.test(buf) || /docker/i.test(text); if (ok) { return resolve('editor-sequential-ok'); } if (Date.now() > deadline) { const sample = buf.slice(0, 160); const textSample = text.slice(0, 160); return resolve( `editor-still-memory:${sample}|text:${textSample}|models:${modelsSnapshot .map((m) => m.slice(0, 80)) .join('||')}` ); } setTimeout(poll, 250); }; poll(); }) expected: mode: contains value: "editor-sequential-ok" - name: Wait until Add Server is enabled kind: browser toolName: browser_evaluate payload: function: | () => new Promise((resolve, reject) => { const timeout = Date.now() + 22000; (function tick() { const dlg = document.querySelector('[role="dialog"]'); const btn = dlg && Array.from(dlg.querySelectorAll('button')) .find(b => /Add Server/i.test(b.textContent || '')); if (btn && !btn.disabled) return resolve('add-server-enabled'); if (Date.now() > timeout) return reject('add-server-still-disabled'); setTimeout(tick, 200); })(); }) expected: mode: contains value: "add-server-enabled" - name: Submit Add Server kind: browser toolName: browser_evaluate payload: function: | () => { const dlg = document.querySelector('[role="dialog"]'); if (!dlg) return 'no-dialog'; const form = dlg.querySelector('form'); if (form) { try { form.requestSubmit(); return 'form-submitted'; } catch {} } const btn = Array.from(dlg.querySelectorAll('button')) .reverse() .find(b => /Add Server/i.test(b.textContent || '') && !b.disabled); if (btn) { ['pointerdown','mousedown','pointerup','mouseup','click'].forEach(t => btn.dispatchEvent(new MouseEvent(t, { bubbles: true, cancelable: true })) ); return 'modal-submit-clicked'; } return 'add-server-button-not-found-or-disabled'; } expected: mode: regex value: "form-submitted|modal-submit-clicked" - name: Wait for “Add MCP Server” dialog to close kind: browser toolName: browser_evaluate payload: function: | () => new Promise((resolve, reject) => { const deadline = Date.now() + 20000; (function poll() { if (!document.querySelector('[role="dialog"]')) return resolve('dialog-closed'); if (Date.now() > deadline) return reject('dialog-still-open'); setTimeout(poll, 200); })(); }) expected: mode: regex value: "dialog-closed" - name: Soft refresh Control-Plane UI kind: browser toolName: browser_navigate payload: url: http://localhost:5173 expected: mode: regex value: "Ran Playwright code" - name: Wait for UI chrome kind: browser toolName: browser_wait_for payload: text: "Add Server" time: 12 expected: mode: contains value: "Waited for Add Server" - name: Wait for sequentialthinking node to render kind: browser toolName: browser_evaluate payload: function: | () => new Promise((resolve, reject) => { const deadline = Date.now() + 30000; (function poll() { const root = document.querySelector('main') || document.body; const byHeading = Array .from(root.querySelectorAll('h1,h2,h3,h4,[role="heading"]')) .find(h => /\bsequential\s*thinking\b|\bsequentialthinking\b/i .test((h.textContent || '').replace(/\s+/g,' ').trim())); const byNode = Array .from(root.querySelectorAll('.react-flow__node,[data-id],[data-nodeid]')) .find(n => /\bsequential\s*thinking\b|\bsequentialthinking\b/i.test(n.textContent || '')); const byCard = Array .from(root.querySelectorAll('div,article,section')) .find(el => /\bsequential\s*thinking\b|\bsequentialthinking\b/i.test(el.textContent || '') && /\bTools?\b/i.test(el.textContent || '')); if (byHeading || byNode || byCard) return resolve('sequentialthinking-tile-ready'); if (Date.now() > deadline) return reject('sequentialthinking-tile-timeout'); setTimeout(poll, 200); })(); }) expected: mode: contains value: "sequentialthinking-tile-ready" - name: Open sequentialthinking server drawer kind: browser toolName: browser_evaluate payload: function: | () => { const root = document.querySelector('main') || document.body; const cand = Array.from( root.querySelectorAll('h1,h2,h3,h4,[role="heading"],.react-flow__node,[data-id],[data-nodeid]') ).find(el => /\bsequential\s*thinking\b|\bsequentialthinking\b/i.test(el.textContent || '')); if (!cand) return 'sequentialthinking-tile-not-found'; cand.scrollIntoView({ block: 'center' }); ['pointerdown','mousedown','pointerup','mouseup','click'].forEach(type => cand.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true })) ); return 'clicked'; } expected: mode: contains value: "clicked" - name: Verify sequentialthinking drawer is open kind: browser toolName: browser_evaluate payload: function: | () => new Promise((resolve) => { const deadline = Date.now() + 15000; const poll = () => { const panels = Array.from(document.querySelectorAll('aside,[data-sidebar],[role="dialog"]')); const panel = panels.find(p => /sequential\s*thinking|sequentialthinking/i.test(p.textContent || '')); if (!panel) { if (Date.now() > deadline) return resolve('drawer-not-open'); return setTimeout(poll, 250); } const txt = panel.textContent || ''; const lower = txt.toLowerCase(); const hasSequential = /\bsequential\s*thinking\b|\bsequentialthinking\b/i.test(txt); const hasAction = /(Edit|Remove|Close)/i.test(txt); const hasStatus = /(Connected|Connecting|Last\s*Call|Calls?\s*\d*)/i.test(txt); const hasTools = /\bTools?\b/i.test(txt); if (hasSequential && (hasAction || (hasStatus && hasTools))) { return resolve('drawer-sequential-ok'); } if (Date.now() > deadline) { return resolve(`drawer-open-missing-content:${lower.slice(0, 160)}`); } setTimeout(poll, 250); }; poll(); }) expected: mode: contains value: "drawer-sequential-ok"

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/TheLunarCompany/lunar'

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