Skip to main content
Glama
playground.ts5.89 kB
import http from "http"; import { URL } from "url"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; const PORT = Number(process.env.PLAYGROUND_PORT || 4389); async function createClient() { const transport = new StdioClientTransport({ command: "node", args: ["dist/index.js"], env: process.env as Record<string, string>, cwd: process.cwd(), }); const client = new Client({ name: "line-bot-ui", version: "0.1.0" }); await client.connect(transport); return client; } function renderHTML() { return `<!doctype html> <html> <head> <meta charset="utf-8" /> <title>LINE Bot MCP UI</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> body { font-family: system-ui, sans-serif; margin: 20px; } header { margin-bottom: 12px; } label { display:block; margin: 8px 0 4px; font-weight: 600; } select, textarea, input { width: 100%; padding: 8px; box-sizing: border-box; } textarea { min-height: 140px; } .row { display: grid; grid-template-columns: 1fr; gap: 12px; } .actions { margin-top: 12px; } pre { background: #f6f6f6; padding: 8px; overflow: auto; } .small { color:#666; font-size: 12px; } </style> </head> <body> <header> <h2>LINE Bot MCP – Minimal UI</h2> <div class="small">Connected via stdio. Env must be set in your shell (or .env loaded by the server).</div> </header> <section class="row"> <div> <label>Tool</label> <select id="tool"></select> </div> <div> <label>Arguments (JSON)</label> <textarea id="args" placeholder='{"prompt":"Hello"}'></textarea> </div> <div class="actions"> <button id="run">Run</button> <button id="loadSample">Load sample for push_gemini_text</button> </div> <div> <label>Result</label> <pre id="out"></pre> </div> </section> <script> async function loadTools() { const res = await fetch('/api/tools'); const data = await res.json(); const sel = document.getElementById('tool'); sel.innerHTML = ''; for (const t of data.tools || []) { const opt = document.createElement('option'); opt.value = t.name; opt.textContent = t.name; sel.appendChild(opt); } } async function callTool() { const name = (document.getElementById('tool')).value; let argsText = (document.getElementById('args')).value; let args = {}; try { args = argsText ? JSON.parse(argsText) : {}; } catch (e) { document.getElementById('out').textContent = 'Invalid JSON: ' + e.message; return; } const res = await fetch('/api/call', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, args }) }); const data = await res.json(); document.getElementById('out').textContent = JSON.stringify(data, null, 2); } function loadSample() { (document.getElementById('tool')).value = 'push_gemini_text'; (document.getElementById('args')).value = JSON.stringify({ prompt: 'สรุปข่าววันนี้ 3 ข้อ ภาษาไทย' }, null, 2); } document.getElementById('run').addEventListener('click', callTool); document.getElementById('loadSample').addEventListener('click', loadSample); loadTools(); </script> </body> </html>`; } async function main() { const client = await createClient(); const server = http.createServer(async (req, res) => { try { const url = new URL(req.url || '/', 'http://localhost'); // Basic CORS for local Next.js or other frontends const setCORS = () => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS'); }; if (req.method === 'OPTIONS') { setCORS(); res.writeHead(204); res.end(); return; } if (req.method === 'GET' && url.pathname === '/') { const html = renderHTML(); res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(html); return; } if (req.method === 'GET' && url.pathname === '/api/tools') { const tools = await client.listTools(); setCORS(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(tools)); return; } if (req.method === 'POST' && url.pathname === '/api/call') { let body = ''; req.on('data', chunk => (body += chunk)); req.on('end', async () => { try { const { name, args } = JSON.parse(body || '{}'); const result = await client.callTool({ name, arguments: args || {} }); setCORS(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(result)); } catch (e: any) { setCORS(); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: e?.message || String(e) })); } }); return; } res.writeHead(404); res.end('Not Found'); } catch (e: any) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Internal error: ' + (e?.message || String(e))); } }); server.listen(PORT, () => { console.log(`MCP UI ready at http://localhost:${PORT}`); }); } main().catch((e) => { console.error('Failed to start MCP UI:', e); process.exit(1); });

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/tndfame/mcp_management'

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