Spaces:
Running
Running
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <link rel="stylesheet" href="shared/http-diagram.css"> | |
| <style> | |
| /* Make RequestId box much larger and prominent */ | |
| .message-session.request-id { | |
| padding: 12px 16px; | |
| border-radius: 12px; | |
| min-width: 180px; | |
| background: #fff3e0; | |
| border: 3px solid #ffa726; | |
| box-shadow: 0 6px 20px rgba(255, 152, 0, 0.35); | |
| } | |
| .message-session.request-id .request-label { | |
| font-size: 10px; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| color: #e65100; | |
| margin-bottom: 6px; | |
| } | |
| .message-session.request-id .request-value { | |
| font-family: 'SF Mono', Monaco, monospace; | |
| font-size: 14px; | |
| font-weight: 700; | |
| background: white; | |
| padding: 6px 14px; | |
| border-radius: 8px; | |
| border: 2px solid #ffb74d; | |
| color: #e65100; | |
| display: inline-block; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| } | |
| /* Client response variation - blue themed */ | |
| .message.client-response .message-session.request-id { | |
| background: #e3f2fd; | |
| border-color: #42a5f5; | |
| box-shadow: 0 6px 20px rgba(33, 150, 243, 0.35); | |
| } | |
| .message.client-response .message-session.request-id .request-label { | |
| color: #1565c0; | |
| } | |
| .message.client-response .message-session.request-id .request-value { | |
| border-color: #90caf9; | |
| color: #1565c0; | |
| } | |
| /* Error variation - red themed */ | |
| .message.error-response .message-session.request-id { | |
| background: #ffebee; | |
| border-color: #ef5350; | |
| box-shadow: 0 6px 20px rgba(244, 67, 54, 0.35); | |
| } | |
| .message.error-response .message-session.request-id .request-label { | |
| color: #c62828; | |
| } | |
| .message.error-response .message-session.request-id .request-value { | |
| border-color: #ef9a9a; | |
| color: #c62828; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="canvas-root"> | |
| <div class="diagram"> | |
| <!-- Client --> | |
| <div class="entity client"> | |
| <div class="entity-box" id="client-box"> | |
| <span>MCP Client</span> | |
| <span class="entity-status" id="client-status"></span> | |
| </div> | |
| <div class="state-badge" id="client-state"> | |
| <div class="badge-title">Connection State</div> | |
| <div class="empty-state">Mcp-Session-Id: β</div> | |
| <div class="session-id" id="client-session"></div> | |
| <div class="caps-row" id="client-caps"></div> | |
| </div> | |
| </div> | |
| <!-- Load Balancer --> | |
| <div class="entity lb"> | |
| <div class="entity-box">LB</div> | |
| </div> | |
| <!-- Connection lines --> | |
| <div class="connection conn-client-lb"></div> | |
| <div class="connection conn-lb-servers"></div> | |
| <!-- Server Cluster --> | |
| <div class="server-cluster"> | |
| <div class="cluster-label">Streamable HTTP (Shared Init State)</div> | |
| </div> | |
| <!-- Server Nodes with session badges to the right --> | |
| <div class="entity server server-1"> | |
| <div class="entity-box" id="server-1-box"> | |
| <span>Node 1</span> | |
| <span class="entity-status" id="server-1-status"></span> | |
| </div> | |
| <div class="state-badge" id="server-1-state"> | |
| <div class="badge-title">Connection State</div> | |
| <div class="empty-state">Mcp-Session-Id: β</div> | |
| <div class="session-id" id="server-1-session"></div> | |
| <div class="caps-row" id="server-1-caps"></div> | |
| </div> | |
| </div> | |
| <div class="entity server server-2"> | |
| <div class="entity-box" id="server-2-box"> | |
| <span>Node 2</span> | |
| <span class="entity-status" id="server-2-status"></span> | |
| </div> | |
| <div class="state-badge" id="server-2-state"> | |
| <div class="badge-title">Connection State</div> | |
| <div class="empty-state">Mcp-Session-Id: β</div> | |
| <div class="session-id" id="server-2-session"></div> | |
| <div class="caps-row" id="server-2-caps"></div> | |
| </div> | |
| </div> | |
| <div class="entity server server-3"> | |
| <div class="entity-box" id="server-3-box"> | |
| <span>Node 3</span> | |
| <span class="entity-status" id="server-3-status"></span> | |
| </div> | |
| <div class="state-badge" id="server-3-state"> | |
| <div class="badge-title">Connection State</div> | |
| <div class="empty-state">Mcp-Session-Id: β</div> | |
| <div class="session-id" id="server-3-session"></div> | |
| <div class="caps-row" id="server-3-caps"></div> | |
| </div> | |
| </div> | |
| <!-- Message --> | |
| <div class="message" id="message"> | |
| <div class="message-header"> | |
| <span class="message-type" id="message-type">initialize</span> | |
| <span class="message-arrow" id="message-arrow">β</span> | |
| </div> | |
| <div class="message-detail" id="message-detail">InitializeRequest</div> | |
| <div class="message-session" id="message-session">Mcp-Session-Id: sess_abc123</div> | |
| </div> | |
| </div> | |
| <div class="narration" id="narration"> | |
| <span class="step-num" id="step-num">β</span> | |
| <span id="narration-text">Shared storage syncs initialize state across servers...</span> | |
| </div> | |
| </div> | |
| <script src="shared/animation-config.js"></script> | |
| <script src="shared/diagram-helpers.js"></script> | |
| <script src="shared/canvas-scale.js"></script> | |
| <script> | |
| const message = document.getElementById('message'); | |
| const messageType = document.getElementById('message-type'); | |
| const messageArrow = document.getElementById('message-arrow'); | |
| const messageDetail = document.getElementById('message-detail'); | |
| const messageSession = document.getElementById('message-session'); | |
| const narration = document.getElementById('narration'); | |
| const narrationText = document.getElementById('narration-text'); | |
| const stepNum = document.getElementById('step-num'); | |
| const clientBox = document.getElementById('client-box'); | |
| const clientState = document.getElementById('client-state'); | |
| const clientSession = document.getElementById('client-session'); | |
| const clientCaps = document.getElementById('client-caps'); | |
| const clientStatus = document.getElementById('client-status'); | |
| const server1Box = document.getElementById('server-1-box'); | |
| const server2Box = document.getElementById('server-2-box'); | |
| const server3Box = document.getElementById('server-3-box'); | |
| const server1State = document.getElementById('server-1-state'); | |
| const server2State = document.getElementById('server-2-state'); | |
| const server3State = document.getElementById('server-3-state'); | |
| const server1Status = document.getElementById('server-1-status'); | |
| const server2Status = document.getElementById('server-2-status'); | |
| const server3Status = document.getElementById('server-3-status'); | |
| const server1Session = document.getElementById('server-1-session'); | |
| const server1Caps = document.getElementById('server-1-caps'); | |
| const server2Session = document.getElementById('server-2-session'); | |
| const server2Caps = document.getElementById('server-2-caps'); | |
| const server3Session = document.getElementById('server-3-session'); | |
| const server3Caps = document.getElementById('server-3-caps'); | |
| const messageElements = { | |
| container: message, | |
| header: message.querySelector('.message-header'), | |
| type: messageType, | |
| arrow: messageArrow, | |
| detail: messageDetail, | |
| session: messageSession, | |
| }; | |
| // Positions (x centers) | |
| const pos = { | |
| client: 65, | |
| lb: 240, | |
| server1: 470, | |
| server2: 470, | |
| server3: 470 | |
| }; | |
| const yClient = 115; | |
| const yServer1 = 38; | |
| const yServer2 = 143; | |
| const yServer3 = 248; | |
| function setMessagePos(x, y) { | |
| setMessagePosition(message, x, y); | |
| } | |
| function animateMessage(fromX, fromY, toX, toY, duration, callback) { | |
| animateMessageBetween(message, fromX, fromY, toX, toY, duration, callback); | |
| } | |
| function animatePath(waypoints, totalDuration, callback) { | |
| animatePathBetween(message, waypoints, totalDuration, callback); | |
| } | |
| function configMessage(type, arrow, method, detail, sessionText, sessionError, direction) { | |
| configureMessage(messageElements, { | |
| type, | |
| arrow, | |
| method, | |
| detail, | |
| sessionText, | |
| sessionError, | |
| direction, | |
| }); | |
| } | |
| function buildRequestIdMarkup(requestId) { | |
| return `<div class="request-label">Must route to original server</div><div class="request-value">${requestId}</div>`; | |
| } | |
| function setStatus(element, text, className) { | |
| element.textContent = text; | |
| element.className = 'entity-status' + (text ? ' visible' : '') + (className ? ' ' + className : ''); | |
| } | |
| function resetAll() { | |
| clientBox.className = 'entity-box'; | |
| server1Box.className = 'entity-box'; | |
| server2Box.className = 'entity-box'; | |
| server3Box.className = 'entity-box'; | |
| clientState.className = 'state-badge'; | |
| server1State.className = 'state-badge'; | |
| server2State.className = 'state-badge'; | |
| server3State.className = 'state-badge'; | |
| // Clear dynamic content | |
| clientSession.textContent = ''; | |
| clientCaps.innerHTML = ''; | |
| server1Session.textContent = ''; | |
| server1Caps.innerHTML = ''; | |
| server2Session.textContent = ''; | |
| server2Caps.innerHTML = ''; | |
| server3Session.textContent = ''; | |
| server3Caps.innerHTML = ''; | |
| setStatus(clientStatus, '', ''); | |
| setStatus(server1Status, '', ''); | |
| setStatus(server2Status, '', ''); | |
| setStatus(server3Status, '', ''); | |
| message.className = 'message'; | |
| } | |
| const steps = [ | |
| // Step 0: Initial state | |
| { | |
| setup: () => { | |
| resetAll(); | |
| stepNum.textContent = 'β'; | |
| narrationText.textContent = 'Shared storage syncs initialize state across servers'; | |
| narration.className = 'narration'; | |
| }, | |
| animate: (done) => setTimeout(done, ANIMATION.INITIAL_PAUSE), | |
| after: () => {} | |
| }, | |
| // Step 1: Initialize request β LB β Node 1 | |
| { | |
| setup: () => { | |
| configMessage('request', 'β', 'initialize', 'InitializeRequest', null, false, 'right'); | |
| setMessagePos(pos.client, yClient); | |
| stepNum.textContent = '1'; | |
| narrationText.textContent = 'Client initializes with capabilities'; | |
| narration.className = 'narration request-state'; | |
| clientBox.classList.add('alive', 'active'); | |
| }, | |
| animate: (done) => animatePath( | |
| [{x: pos.client, y: yClient}, {x: pos.lb, y: yClient}, {x: pos.server1, y: yServer1}], | |
| ANIMATION.MSG_DURATION, | |
| done | |
| ), | |
| after: () => { | |
| clientBox.classList.remove('active'); | |
| server1Box.classList.add('alive', 'active'); | |
| } | |
| }, | |
| // Step 2: Initialize result β shared to all nodes | |
| { | |
| setup: () => { | |
| configMessage('response', 'β', 'initialize', 'InitializeResult', 'Mcp-Session-Id: sess_shared', false, 'left'); | |
| setMessagePos(pos.server1, yServer1); | |
| stepNum.textContent = '2'; | |
| narrationText.textContent = 'Shared store syncs session to all servers'; | |
| narration.className = 'narration response-state'; | |
| server1Session.textContent = 'sess_shared'; | |
| server1Caps.innerHTML = '<span class="cap">sampling β</span>'; | |
| server2Session.textContent = 'sess_shared'; | |
| server2Caps.innerHTML = '<span class="cap">sampling β</span>'; | |
| server3Session.textContent = 'sess_shared'; | |
| server3Caps.innerHTML = '<span class="cap">sampling β</span>'; | |
| server1State.classList.add('session'); | |
| server2State.classList.add('session'); | |
| server3State.classList.add('session'); | |
| }, | |
| animate: (done) => animatePath( | |
| [{x: pos.server1, y: yServer1}, {x: pos.lb, y: yClient}, {x: pos.client, y: yClient}], | |
| ANIMATION.MSG_DURATION, | |
| done | |
| ), | |
| after: () => { | |
| server1Box.classList.remove('active'); | |
| clientSession.textContent = 'sess_shared'; | |
| clientCaps.innerHTML = '<span class="cap">tools β</span><span class="cap">prompts β</span>'; | |
| clientState.classList.add('session'); | |
| } | |
| }, | |
| // Step 3: notifications/initialized β LB β Node 1 | |
| { | |
| setup: () => { | |
| configMessage('notification', 'β', 'initialized', 'notification', 'Mcp-Session-Id: sess_shared', false, 'right'); | |
| setMessagePos(pos.client, yClient); | |
| stepNum.textContent = '3'; | |
| narrationText.textContent = 'Client sends initialized notification'; | |
| narration.className = 'narration notification-state'; | |
| clientBox.classList.add('active'); | |
| server2Box.classList.add('alive'); | |
| server3Box.classList.add('alive'); | |
| setStatus(server2Status, 'SYNCED', ''); | |
| setStatus(server3Status, 'SYNCED', ''); | |
| }, | |
| animate: (done) => animatePath( | |
| [{x: pos.client, y: yClient}, {x: pos.lb, y: yClient}, {x: pos.server1, y: yServer1}], | |
| ANIMATION.MSG_DURATION, | |
| done | |
| ), | |
| after: () => { | |
| clientBox.classList.remove('active'); | |
| clientState.classList.add('active'); | |
| server1State.classList.add('active'); | |
| server2State.classList.add('active'); | |
| server3State.classList.add('active'); | |
| setStatus(server2Status, '', ''); | |
| setStatus(server3Status, '', ''); | |
| } | |
| }, | |
| // Step 4: Server sends elicitation request (waiting) | |
| { | |
| setup: () => { | |
| configMessage('server-request', 'β', 'elicitation/request', 'ElicitRequest', buildRequestIdMarkup('req_41'), false, 'left'); | |
| messageSession.classList.add('request-id'); | |
| setMessagePos(pos.server1, yServer1); | |
| stepNum.textContent = '4'; | |
| narrationText.textContent = 'Node 1 asks client to confirm (WAITING)'; | |
| narration.className = 'narration waiting-state'; | |
| server1Box.classList.add('waiting'); | |
| setStatus(server1Status, 'WAITING', 'waiting'); | |
| }, | |
| animate: (done) => animatePath( | |
| [{x: pos.server1, y: yServer1}, {x: pos.lb, y: yClient}, {x: pos.client, y: yClient}], | |
| ANIMATION.MSG_DURATION, | |
| done | |
| ), | |
| after: () => { | |
| clientBox.classList.add('active'); | |
| } | |
| }, | |
| // Step 5: Client responds but routed to Node 3 | |
| { | |
| setup: () => { | |
| configMessage('client-response', 'β', 'elicitation/response', 'ElicitResult', buildRequestIdMarkup('req_41'), false, 'right'); | |
| messageSession.classList.add('request-id'); | |
| setMessagePos(pos.client, yClient); | |
| stepNum.textContent = '5'; | |
| narrationText.textContent = 'Client response routed to the wrong server'; | |
| narration.className = 'narration request-state'; | |
| }, | |
| animate: (done) => animatePath( | |
| [{x: pos.client, y: yClient}, {x: pos.lb, y: yClient}, {x: pos.server3, y: yServer3}], | |
| ANIMATION.MSG_DURATION, | |
| done | |
| ), | |
| after: () => { | |
| clientBox.classList.remove('active'); | |
| server3Box.classList.add('active'); | |
| } | |
| }, | |
| // Step 6: Error β Node 3 doesn't know pending request | |
| { | |
| setup: () => { | |
| configMessage('error-response', 'β', 'ERROR', 'Unknown request', buildRequestIdMarkup('req_41'), true, 'left'); | |
| messageSession.classList.add('request-id'); | |
| setMessagePos(pos.server3, yServer3); | |
| stepNum.textContent = 'β'; | |
| narrationText.textContent = 'Shared init state is not enough for server requests'; | |
| narration.className = 'narration error-state'; | |
| server3Box.classList.add('error'); | |
| }, | |
| animate: (done) => animatePath( | |
| [{x: pos.server3, y: yServer3}, {x: pos.lb, y: yClient}, {x: pos.client, y: yClient}], | |
| ANIMATION.MSG_DURATION, | |
| done | |
| ), | |
| after: () => { | |
| clientBox.classList.add('error'); | |
| } | |
| }, | |
| // Step 7: Pause on error | |
| { | |
| setup: () => { | |
| message.className = 'message'; | |
| }, | |
| animate: (done) => setTimeout(done, ANIMATION.ERROR_PAUSE), | |
| after: () => {} | |
| } | |
| ]; | |
| runStepSequence(steps); | |
| scaleCanvas(); | |
| </script> | |
| </body> | |
| </html> | |