Skip to main content
Glama

MCP Transport HTTP

by rayenamer
index.html34.3 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>MCP Protocol Demo</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; background: #0a0a0a; color: #e0e0e0; padding: 2rem; padding-bottom: 100px; } .header { text-align: center; margin-bottom: 3rem; } .header h1 { font-size: 3rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-text-fill-color: transparent; margin-bottom: 0.5rem; } .header p { color: #888; font-size: 1.2rem; } .container { max-width: 1400px; margin: 0 auto; } .card { background: #1a1a1a; border: 1px solid #333; border-radius: 12px; padding: 2rem; margin-bottom: 2rem; position: relative; overflow: hidden; } .card::before { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 4px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); } .card h2 { color: #fff; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; } .step-number { background: #667eea; color: white; width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 0.9rem; } .info-box { background: rgba(102, 126, 234, 0.1); border: 1px solid #667eea; border-radius: 8px; padding: 1rem; margin-bottom: 1.5rem; } .info-box h4 { color: #667eea; margin-bottom: 0.5rem; } .info-box p { color: #ccc; font-size: 0.9rem; line-height: 1.5; } .columns { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-top: 1.5rem; } .column { background: #0f0f0f; border: 1px solid #333; border-radius: 8px; padding: 1.5rem; } .column-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem; color: #fff; font-weight: 600; } .method-badge { background: #667eea; color: white; padding: 0.25rem 0.75rem; border-radius: 20px; font-size: 0.8rem; font-weight: bold; } .code-block { background: #0a0a0a; border: 1px solid #333; border-radius: 8px; padding: 1rem; overflow-x: auto; font-family: "Consolas", "Monaco", monospace; font-size: 0.875rem; margin-bottom: 1rem; } .code-block pre { margin: 0; white-space: pre-wrap; } .headers-list { background: #0a0a0a; border: 1px solid #333; border-radius: 8px; padding: 1rem; } .header-item { display: flex; gap: 1rem; padding: 0.5rem 0; border-bottom: 1px solid #222; } .header-item:last-child { border-bottom: none; } .header-key { color: #667eea; font-weight: 600; min-width: 150px; font-family: monospace; } .header-value { color: #e0e0e0; font-family: monospace; word-break: break-all; } .session-id-highlight { background: rgba(102, 126, 234, 0.2); padding: 0.25rem 0.5rem; border-radius: 4px; border: 1px solid #667eea; } .action-button { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 0.75rem 2rem; border-radius: 8px; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.3s ease; margin-top: 1rem; display: block; margin-left: auto; margin-right: auto; } .action-button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3); } .action-button:disabled { opacity: 0.5; cursor: not-allowed; } .status-indicator { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1rem; border-radius: 20px; font-size: 0.875rem; font-weight: 600; margin-left: 1rem; } .status-pending { background: rgba(255, 193, 7, 0.2); color: #ffc107; } .status-success { background: rgba(76, 175, 80, 0.2); color: #4caf50; } .status-error { background: rgba(244, 67, 54, 0.2); color: #f44336; } .sse-stream { background: #0a0a0a; border: 1px solid #333; border-radius: 8px; padding: 1rem; max-height: 300px; overflow-y: auto; font-family: monospace; font-size: 0.8rem; } .sse-event { padding: 0.5rem; border-bottom: 1px solid #222; animation: slideIn 0.3s ease; } .sse-event:last-child { border-bottom: none; } .sse-event-type { color: #667eea; font-weight: bold; } @keyframes slideIn { from { opacity: 0; transform: translateX(-20px); } to { opacity: 1; transform: translateX(0); } } .sse-panel { position: fixed; bottom: 20px; right: 20px; width: 400px; background: #1a1a1a; border: 1px solid #333; border-radius: 12px; padding: 1.5rem; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); max-height: 500px; display: flex; flex-direction: column; } .sse-panel h3 { color: #fff; margin-bottom: 1rem; display: flex; align-items: center; justify-content: space-between; } .sse-indicator { width: 10px; height: 10px; border-radius: 50%; background: #f44336; transition: all 0.3s ease; } .sse-indicator.active { background: #4caf50; box-shadow: 0 0 10px rgba(76, 175, 80, 0.5); } .sse-messages { background: #0a0a0a; border: 1px solid #333; border-radius: 8px; padding: 1rem; overflow-y: auto; flex: 1; font-family: monospace; font-size: 0.8rem; } .sse-message { padding: 0.5rem; border-bottom: 1px solid #222; animation: slideIn 0.3s ease; } .sse-message:last-child { border-bottom: none; } .sse-controls { margin-top: 1rem; display: flex; gap: 0.5rem; } .sse-button { flex: 1; padding: 0.5rem; border: 1px solid #667eea; background: transparent; color: #667eea; border-radius: 6px; cursor: pointer; transition: all 0.3s ease; font-weight: 600; } .sse-button:hover:not(:disabled) { background: #667eea; color: white; } .sse-button:disabled { opacity: 0.5; cursor: not-allowed; } .loading { display: inline-block; width: 20px; height: 20px; border: 3px solid rgba(255, 255, 255, 0.3); border-radius: 50%; border-top-color: #fff; animation: spin 1s ease-in-out infinite; } @keyframes spin { to { transform: rotate(360deg); } } .empty-response { color: #666; font-style: italic; padding: 1rem; } </style> </head> <body> <div class="header"> <h1>MCP Protocol Demo</h1> <p>Model Context Protocol - Streamable HTTP Transport</p> </div> <div class="container"> <!-- Card 1: Initialize Request --> <div class="card" id="card-1"> <h2> <span class="step-number">1</span> Initialize Request </h2> <div class="columns"> <div class="column"> <div class="column-header"> <span class="method-badge">POST</span> <span>Request</span> </div> <div class="code-block"> <pre id="init-request-body"> { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": { "roots": { "listChanged": true } }, "clientInfo": { "name": "MCP Demo Client", "version": "1.0.0" } } }</pre > </div> <div class="headers-list"> <div class="header-item"> <span class="header-key">Content-Type:</span> <span class="header-value">application/json</span> </div> <div class="header-item"> <span class="header-key">Accept:</span> <span class="header-value" >application/json, text/event-stream</span > </div> </div> </div> <div class="column"> <div class="column-header"> <span>Response</span> <span class="status-indicator status-pending" id="init-status" style="display: none" > <span class="loading"></span> Sending... </span> </div> <div id="init-response-content"> <div class="empty-response"> No response yet. Click the button to send request. </div> </div> </div> </div> <button class="action-button" onclick="sendInitialize()"> Send Initialize Request </button> </div> <!-- Card 2: Initialized Notification --> <div class="card" id="card-2"> <h2> <span class="step-number">2</span> Initialized Notification </h2> <div class="columns"> <div class="column"> <div class="column-header"> <span class="method-badge">POST</span> <span>Request</span> </div> <div class="code-block"> <pre id="initialized-request-body"> { "jsonrpc": "2.0", "method": "notifications/initialized", "params": {} }</pre > </div> <div class="headers-list"> <div class="header-item"> <span class="header-key">Content-Type:</span> <span class="header-value">application/json</span> </div> <div class="header-item"> <span class="header-key">Accept:</span> <span class="header-value" >application/json, text/event-stream</span > </div> <div class="header-item"> <span class="header-key">mcp-session-id:</span> <span class="header-value session-id-highlight" id="initialized-session-id" >Not yet available</span > </div> </div> </div> <div class="column"> <div class="column-header"> <span>Response</span> <span class="status-indicator status-pending" id="initialized-status" style="display: none" > <span class="loading"></span> Sending... </span> </div> <div id="initialized-response-content"> <div class="empty-response"> No response yet. Initialize first, then send notification. </div> </div> </div> </div> <button class="action-button" onclick="sendInitialized()" id="initialized-button" disabled > Send Initialized Notification </button> </div> <!-- Card 3: Tool Call --> <div class="card" id="card-3"> <h2> <span class="step-number">3</span> Tool Call - Add Function </h2> <div class="columns"> <div class="column"> <div class="column-header"> <span class="method-badge">POST</span> <span>Request</span> </div> <div class="code-block"> <pre id="tool-request-body"> { "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "add", "arguments": { "a": 5, "b": 3 } } }</pre > </div> <div class="headers-list"> <div class="header-item"> <span class="header-key">Content-Type:</span> <span class="header-value">application/json</span> </div> <div class="header-item"> <span class="header-key">Accept:</span> <span class="header-value" >application/json, text/event-stream</span > </div> <div class="header-item"> <span class="header-key">mcp-session-id:</span> <span class="header-value session-id-highlight" id="tool-session-id" >Not yet available</span > </div> </div> </div> <div class="column"> <div class="column-header"> <span>Response</span> <span class="status-indicator status-pending" id="tool-status" style="display: none" > <span class="loading"></span> Sending... </span> </div> <div id="tool-response-content"> <div class="empty-response"> No response yet. Initialize first, then call tool. </div> </div> </div> </div> <button class="action-button" onclick="sendToolCall()" id="tool-button"> Call Add Tool </button> </div> <!-- Card 4: Custom Request --> <div class="card" id="card-4" style="display: none"> <h2> <span class="step-number">4</span> Custom Request Example </h2> <div class="info-box"> <h4>Try Your Own Request</h4> <p> Experiment with other MCP methods like tools/list, resources/list, or prompts/list. </p> </div> <div class="columns"> <div class="column"> <div class="column-header"> <span class="method-badge">POST</span> <span>Request</span> </div> <div class="code-block" contenteditable="true" id="custom-request-body" style="outline: none" > { "jsonrpc": "2.0", "id": 4, "method": "tools/list", "params": {} } </div> <div class="headers-list"> <div class="header-item"> <span class="header-key">Content-Type:</span> <span class="header-value">application/json</span> </div> <div class="header-item"> <span class="header-key">Accept:</span> <span class="header-value" >application/json, text/event-stream</span > </div> <div class="header-item"> <span class="header-key">mcp-session-id:</span> <span class="header-value session-id-highlight" id="custom-session-id" >Not yet available</span > </div> </div> </div> <div class="column"> <div class="column-header"> <span>Response</span> <span class="status-indicator status-pending" id="custom-status" style="display: none" > <span class="loading"></span> Sending... </span> </div> <div id="custom-response-content"> <div class="empty-response"> No response yet. Initialize first, then send request. </div> </div> </div> </div> <button class="action-button" onclick="sendCustomRequest()" id="custom-button" > Send Custom Request </button> </div> </div> <!-- SSE Panel for GET requests --> <div class="sse-panel"> <h3> Server-Initiated Events (GET SSE) <span class="sse-indicator" id="sse-indicator"></span> </h3> <div class="sse-messages" id="sse-messages"> <div style="color: #666; text-align: center; padding: 2rem"> No GET SSE connection active. Click "Start GET SSE" to begin monitoring server-initiated events. </div> </div> <div class="sse-controls"> <button class="sse-button" onclick="startSSE()" id="sse-start-button" disabled > Start GET SSE </button> <button class="sse-button" onclick="stopSSE()" id="sse-stop-button" disabled > Stop SSE </button> <button class="sse-button" onclick="clearSSE()">Clear</button> </div> </div> <script> let sessionId = null; let sseSource = null; const serverUrl = "http://localhost:8000/mcp/"; // Update session ID in all cards function updateSessionId(id) { sessionId = id; document.getElementById("initialized-session-id").textContent = id || "Not yet available"; document.getElementById("tool-session-id").textContent = id || "Not yet available"; document.getElementById("custom-session-id").textContent = id || "Not yet available"; // Enable buttons after initialization // if (id) { document.getElementById("initialized-button").disabled = false; document.getElementById("tool-button").disabled = false; document.getElementById("custom-button").disabled = false; // document.getElementById("sse-start-button").disabled = false; // } } // Add SSE message to panel (for GET SSE) function addSSEMessage(message) { const container = document.getElementById("sse-messages"); const messageDiv = document.createElement("div"); messageDiv.className = "sse-message"; messageDiv.textContent = message; container.appendChild(messageDiv); container.scrollTop = container.scrollHeight; } // Display response with headers and body/stream function displayResponse(cardId, headers, body, isSSE = false) { const contentDiv = document.getElementById( `${cardId}-response-content` ); // Filter out date-related headers const filteredHeaders = {}; for (const [key, value] of headers.entries()) { if (!key.toLowerCase().includes("date")) { filteredHeaders[key] = value; } } // Build response HTML let html = '<div class="headers-list">'; for (const [key, value] of Object.entries(filteredHeaders)) { const isSessionId = key.toLowerCase() === "mcp-session-id"; html += ` <div class="header-item"> <span class="header-key">${key}:</span> <span class="header-value ${ isSessionId ? "session-id-highlight" : "" }">${value}</span> </div> `; } html += "</div>"; if (isSSE) { html += '<div class="sse-stream" id="' + cardId + '-sse-stream"></div>'; } else { html += '<div class="code-block"><pre>' + body + "</pre></div>"; } contentDiv.innerHTML = html; } // Add SSE event to response stream display function addSSEEvent(cardId, eventType, data) { const streamDiv = document.getElementById(`${cardId}-sse-stream`); if (streamDiv) { const eventDiv = document.createElement("div"); eventDiv.className = "sse-event"; eventDiv.innerHTML = `<span class="sse-event-type">${eventType}:</span> ${data}`; streamDiv.appendChild(eventDiv); streamDiv.scrollTop = streamDiv.scrollHeight; } } // Handle SSE Response from POST requests async function handleSSEResponse(cardId, response) { const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; addSSEEvent(cardId, "stream", "SSE connection established"); try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); // Keep the last incomplete line in the buffer buffer = lines.pop() || ""; for (const line of lines) { if (line.startsWith("data: ")) { const data = line.substring(6); if (data) { addSSEEvent(cardId, "data", data); } } else if (line.startsWith("event: ")) { const eventType = line.substring(7); addSSEEvent(cardId, "event", eventType); } else if (line === "") { // Empty line signals end of an event } } } addSSEEvent(cardId, "stream", "SSE connection closed"); } catch (error) { addSSEEvent(cardId, "error", error.message); } } // Send Initialize Request async function sendInitialize() { const statusEl = document.getElementById("init-status"); statusEl.style.display = "inline-flex"; statusEl.className = "status-indicator status-pending"; statusEl.innerHTML = '<span class="loading"></span> Sending...'; try { const response = await fetch(serverUrl, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json, text/event-stream", }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2024-11-05", capabilities: { roots: { listChanged: true, }, }, clientInfo: { name: "MCP Demo Client", version: "1.0.0", }, }, }), }); // Get session ID from response headers const responseSessionId = response.headers.get("mcp-session-id"); updateSessionId(responseSessionId); // if (responseSessionId) { // } // Handle response based on content type const contentType = response.headers.get("content-type"); if (contentType && contentType.includes("text/event-stream")) { displayResponse("init", response.headers, "", true); handleSSEResponse("init", response); } else { const data = await response.json(); displayResponse( "init", response.headers, JSON.stringify(data, null, 2) ); } statusEl.className = "status-indicator status-success"; statusEl.innerHTML = "✓ Success"; } catch (error) { statusEl.className = "status-indicator status-error"; statusEl.innerHTML = "✗ Error"; displayResponse("init", new Headers(), "Error: " + error.message); } } // Send Initialized Notification async function sendInitialized() { const statusEl = document.getElementById("initialized-status"); statusEl.style.display = "inline-flex"; statusEl.className = "status-indicator status-pending"; statusEl.innerHTML = '<span class="loading"></span> Sending...'; try { const headers = { "Content-Type": "application/json", Accept: "application/json, text/event-stream", }; if (sessionId) { headers["mcp-session-id"] = sessionId; } const response = await fetch(serverUrl, { method: "POST", headers: headers, body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized", params: {}, }), }); if (response.status === 202) { displayResponse( "initialized", response.headers, "HTTP 202 Accepted - Notification acknowledged" ); } else { const contentType = response.headers.get("content-type"); if (contentType && contentType.includes("text/event-stream")) { displayResponse("initialized", response.headers, "", true); handleSSEResponse("initialized", response); } else { const data = await response.text(); displayResponse( "initialized", response.headers, data || "Empty response" ); } } statusEl.className = "status-indicator status-success"; statusEl.innerHTML = "✓ Success"; } catch (error) { statusEl.className = "status-indicator status-error"; statusEl.innerHTML = "✗ Error"; displayResponse( "initialized", new Headers(), "Error: " + error.message ); } } // Send Tool Call async function sendToolCall() { const statusEl = document.getElementById("tool-status"); statusEl.style.display = "inline-flex"; statusEl.className = "status-indicator status-pending"; statusEl.innerHTML = '<span class="loading"></span> Sending...'; try { const headers = { "Content-Type": "application/json", Accept: "application/json, text/event-stream", }; if (sessionId) { headers["mcp-session-id"] = sessionId; } const response = await fetch(serverUrl, { method: "POST", headers: headers, body: JSON.stringify({ jsonrpc: "2.0", id: 3, method: "tools/call", params: { _meta: { progressToken: "abc", }, name: "add", arguments: { a: 5, b: 3, }, }, }), }); const contentType = response.headers.get("content-type"); if (contentType && contentType.includes("text/event-stream")) { displayResponse("tool", response.headers, "", true); handleSSEResponse("tool", response); } else { const data = await response.json(); displayResponse( "tool", response.headers, JSON.stringify(data, null, 2) ); } statusEl.className = "status-indicator status-success"; statusEl.innerHTML = "✓ Success"; } catch (error) { statusEl.className = "status-indicator status-error"; statusEl.innerHTML = "✗ Error"; displayResponse("tool", new Headers(), "Error: " + error.message); } } // Send Custom Request async function sendCustomRequest() { const statusEl = document.getElementById("custom-status"); statusEl.style.display = "inline-flex"; statusEl.className = "status-indicator status-pending"; statusEl.innerHTML = '<span class="loading"></span> Sending...'; try { const requestBody = document.getElementById( "custom-request-body" ).textContent; const headers = { "Content-Type": "application/json", Accept: "application/json, text/event-stream", }; if (sessionId) { headers["mcp-session-id"] = sessionId; } const response = await fetch(serverUrl, { method: "POST", headers: headers, body: requestBody, }); const contentType = response.headers.get("content-type"); if (contentType && contentType.includes("text/event-stream")) { displayResponse("custom", response.headers, "", true); handleSSEResponse("custom", response); } else { const data = await response.json(); displayResponse( "custom", response.headers, JSON.stringify(data, null, 2) ); } statusEl.className = "status-indicator status-success"; statusEl.innerHTML = "✓ Success"; } catch (error) { statusEl.className = "status-indicator status-error"; statusEl.innerHTML = "✗ Error"; displayResponse("custom", new Headers(), "Error: " + error.message); } } let sseReader = null; let sseAbortController = null; // Start GET SSE Connection async function startSSE() { if (sseAbortController) { sseAbortController.abort(); } document.getElementById("sse-messages").innerHTML = ""; sseAbortController = new AbortController(); const headers = { Accept: "text/event-stream", }; if (sessionId) { headers["mcp-session-id"] = sessionId; } document.getElementById("sse-indicator").classList.add("active"); document.getElementById("sse-start-button").disabled = true; document.getElementById("sse-stop-button").disabled = false; addSSEMessage("GET SSE connection established"); try { const response = await fetch(serverUrl, { method: "GET", headers: headers, signal: sseAbortController.signal, }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; sseReader = reader; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); // Keep the last incomplete line in the buffer buffer = lines.pop() || ""; for (const line of lines) { if (line.startsWith("data: ")) { const data = line.substring(6); if (data) { try { const parsed = JSON.parse(data); if (parsed.method) { if (parsed.method.includes("notification")) { addSSEMessage("Server Notification: " + data); } else { addSSEMessage("Server Request: " + data); } } else { addSSEMessage("Message: " + data); } } catch { addSSEMessage("Message: " + data); } } } else if (line.startsWith("event: ")) { const eventType = line.substring(7); addSSEMessage("Event Type: " + eventType); } else if (line === "") { // Empty line signals end of an event } } } addSSEMessage("GET SSE connection closed normally"); } catch (error) { if (error.name !== "AbortError") { addSSEMessage("Connection error: " + error.message); } } finally { stopSSE(); } } // Stop SSE Connection function stopSSE() { if (sseAbortController) { sseAbortController.abort(); sseAbortController = null; } if (sseReader) { sseReader.cancel(); sseReader = null; } document.getElementById("sse-indicator").classList.remove("active"); document.getElementById("sse-start-button").disabled = sessionId ? false : true; document.getElementById("sse-stop-button").disabled = true; addSSEMessage("GET SSE connection closed"); } </script> </body> </html>

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/rayenamer/MCP_transport-http'

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