Spaces:
Running
Running
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <link rel="stylesheet" href="shared/http-diagram.css"> | |
| <style> | |
| /* Make MCP-Cookie box much larger and prominent */ | |
| .message-session.mcp-cookie { | |
| padding: 12px 16px; | |
| border-radius: 12px; | |
| min-width: 200px; | |
| background: #f3e5f5; | |
| border: 3px solid #ab47bc; | |
| box-shadow: 0 6px 20px rgba(156, 39, 176, 0.35); | |
| } | |
| .message-session.mcp-cookie .cookie-label { | |
| font-size: 10px; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| color: #7b1fa2; | |
| margin-bottom: 6px; | |
| } | |
| .message-session.mcp-cookie .cookie-value { | |
| font-family: 'SF Mono', Monaco, monospace; | |
| font-size: 14px; | |
| font-weight: 700; | |
| background: white; | |
| padding: 6px 14px; | |
| border-radius: 8px; | |
| border: 2px solid #ce93d8; | |
| color: #7b1fa2; | |
| display: inline-block; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| } | |
| .message-session.mcp-cookie .cookie-value.empty { | |
| color: #9e9e9e; | |
| border-color: #e0e0e0; | |
| font-style: italic; | |
| } | |
| /* Set-Cookie variation - green themed (server issuing) */ | |
| .message-session.mcp-cookie.set-cookie { | |
| background: #e8f5e9; | |
| border-color: #66bb6a; | |
| box-shadow: 0 6px 20px rgba(76, 175, 80, 0.35); | |
| } | |
| .message-session.mcp-cookie.set-cookie .cookie-label { | |
| color: #2e7d32; | |
| } | |
| .message-session.mcp-cookie.set-cookie .cookie-value { | |
| border-color: #a5d6a7; | |
| color: #2e7d32; | |
| } | |
| /* Request with cookie - blue themed */ | |
| .message.request .message-session.mcp-cookie:not(.set-cookie) { | |
| background: #e3f2fd; | |
| border-color: #42a5f5; | |
| box-shadow: 0 6px 20px rgba(33, 150, 243, 0.35); | |
| } | |
| .message.request .message-session.mcp-cookie:not(.set-cookie) .cookie-label { | |
| color: #1565c0; | |
| } | |
| .message.request .message-session.mcp-cookie:not(.set-cookie) .cookie-value { | |
| border-color: #90caf9; | |
| color: #1565c0; | |
| } | |
| </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">Cookie Jar</div> | |
| <div class="empty-state">empty</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 (Cookies)</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">Known Cookie</div> | |
| <div class="empty-state">none</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">Known Cookie</div> | |
| <div class="empty-state">none</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">Known Cookie</div> | |
| <div class="empty-state">none</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">CallToolRequest</div> | |
| <div class="message-session" id="message-session">MCP-Cookie: (none)</div> | |
| </div> | |
| </div> | |
| <div class="narration" id="narration"> | |
| <span class="step-num" id="step-num">β</span> | |
| <span id="narration-text">Client uses MCP cookies for session semantics...</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 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 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 buildCookieMarkup(label, value, isEmpty) { | |
| const valueClass = isEmpty ? 'cookie-value empty' : 'cookie-value'; | |
| return `<div class="cookie-label">${label}</div><div class="${valueClass}">${value}</div>`; | |
| } | |
| 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 = ''; | |
| message.className = 'message'; | |
| } | |
| const steps = [ | |
| // Step 0: Initial state | |
| { | |
| setup: () => { | |
| resetAll(); | |
| stepNum.textContent = 'β'; | |
| narrationText.textContent = 'Client and server exchange cookies at the MCP layer'; | |
| narration.className = 'narration'; | |
| }, | |
| animate: (done) => setTimeout(done, ANIMATION.INITIAL_PAUSE), | |
| after: () => {} | |
| }, | |
| // Step 1: tools/call β LB β Node 1 (no cookie yet) | |
| { | |
| setup: () => { | |
| configMessage('request', 'β', 'tools/call', 'CallToolRequest', buildCookieMarkup('MCP-Cookie', '(none)', true), false, 'right'); | |
| messageSession.classList.add('mcp-cookie'); | |
| setMessagePos(pos.client, yClient); | |
| stepNum.textContent = '1'; | |
| narrationText.textContent = 'Client sends request without cookie (MCP JSON-RPC layer)'; | |
| 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: tools/call result β Node 1 issues cookie | |
| { | |
| setup: () => { | |
| configMessage('response', 'β', 'tools/call', 'CallToolResult', buildCookieMarkup('Set-MCP-Cookie', 'cookie_4f2', false), false, 'left'); | |
| messageSession.classList.add('mcp-cookie', 'set-cookie'); | |
| setMessagePos(pos.server1, yServer1); | |
| stepNum.textContent = '2'; | |
| narrationText.textContent = 'Server issues MCP cookie (can be copied to HTTP headers)'; | |
| narration.className = 'narration response-state'; | |
| server1Session.textContent = 'cookie_4f2'; | |
| server1State.classList.add('active'); | |
| }, | |
| 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 = 'cookie_4f2'; | |
| clientState.classList.add('active'); | |
| } | |
| }, | |
| // Step 3: tools/call β LB β Node 2 (cookie included) | |
| { | |
| setup: () => { | |
| configMessage('request', 'β', 'tools/call', 'CallToolRequest', buildCookieMarkup('MCP-Cookie', 'cookie_4f2', false), false, 'right'); | |
| messageSession.classList.add('mcp-cookie'); | |
| setMessagePos(pos.client, yClient); | |
| stepNum.textContent = '3'; | |
| narrationText.textContent = 'Client returns cookie on the next request'; | |
| narration.className = 'narration request-state'; | |
| clientBox.classList.add('active'); | |
| }, | |
| animate: (done) => animatePath( | |
| [{x: pos.client, y: yClient}, {x: pos.lb, y: yClient}, {x: pos.server2, y: yServer2}], | |
| ANIMATION.MSG_DURATION, | |
| done | |
| ), | |
| after: () => { | |
| clientBox.classList.remove('active'); | |
| server2Box.classList.add('alive', 'active'); | |
| } | |
| }, | |
| // Step 4: tools/call result β Node 2 recognizes cookie | |
| { | |
| setup: () => { | |
| configMessage('response', 'β', 'tools/call', 'CallToolResult', buildCookieMarkup('MCP-Cookie β', 'cookie_4f2', false), false, 'left'); | |
| messageSession.classList.add('mcp-cookie'); | |
| setMessagePos(pos.server2, yServer2); | |
| stepNum.textContent = '4'; | |
| narrationText.textContent = 'Server uses cookie to maintain session semantics'; | |
| narration.className = 'narration response-state'; | |
| server2Session.textContent = 'cookie_4f2'; | |
| server2State.classList.add('active'); | |
| }, | |
| animate: (done) => animatePath( | |
| [{x: pos.server2, y: yServer2}, {x: pos.lb, y: yClient}, {x: pos.client, y: yClient}], | |
| ANIMATION.MSG_DURATION, | |
| done | |
| ), | |
| after: () => { | |
| server2Box.classList.remove('active'); | |
| } | |
| }, | |
| // Step 5: Pause before restart | |
| { | |
| setup: () => { | |
| message.className = 'message'; | |
| stepNum.textContent = 'β'; | |
| narrationText.textContent = 'MCP cookies preserve client/server context across requests'; | |
| narration.className = 'narration success-state'; | |
| }, | |
| animate: (done) => setTimeout(done, ANIMATION.ERROR_PAUSE), | |
| after: () => {} | |
| } | |
| ]; | |
| runStepSequence(steps); | |
| scaleCanvas(); | |
| </script> | |
| </body> | |
| </html> | |