Skip to content

Commit c01d551

Browse files
author
Marek Safarik
committed
Streaming communication using Server-sent events
Signed-off-by: Marek Safarik <[email protected]>
1 parent 90f33db commit c01d551

File tree

4 files changed

+242
-118
lines changed

4 files changed

+242
-118
lines changed

mcp-client/mcp-client

-25 Bytes
Binary file not shown.

mcp-client/templates/index.html

Lines changed: 209 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,60 +3,232 @@
33
<head>
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6-
<title>MCP Client for Keylime</title>
7-
<script src="https://unpkg.com/[email protected]"></script>
6+
<title>Keylime MCP Client</title>
87
<style>
98
* { margin: 0; padding: 0; box-sizing: border-box; }
10-
body { font-family: system-ui, -apple-system, sans-serif; height: 100vh; display: flex; flex-direction: column; background: #f5f5f5; }
11-
.header { background: #2563eb; color: white; padding: 1rem; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
12-
.chat-container { flex: 1; overflow-y: auto; padding: 1rem; }
13-
.message { margin-bottom: 1rem; padding: 0.75rem; border-radius: 8px; max-width: 80%; white-space: pre-wrap; word-wrap: break-word; }
14-
.message.user { background: #2563eb; color: white; margin-left: auto; }
15-
.message.assistant { background: white; border: 1px solid #e5e7eb; }
16-
.message .time { font-size: 0.75rem; opacity: 0.7; margin-top: 0.25rem; }
17-
.input-area { background: white; padding: 1rem; border-top: 1px solid #e5e7eb; display: flex; gap: 0.5rem; }
18-
textarea { flex: 1; padding: 0.75rem; border: 1px solid #d1d5db; border-radius: 8px; resize: none; font-family: inherit; font-size: 1rem; }
19-
button { background: #2563eb; color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 8px; cursor: pointer; font-size: 1rem; }
20-
button:hover { background: #1d4ed8; }
21-
button:disabled { background: #9ca3af; cursor: not-allowed; }
9+
10+
body {
11+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
12+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
13+
height: 100vh;
14+
display: flex;
15+
align-items: center;
16+
justify-content: center;
17+
padding: 20px;
18+
}
19+
20+
.container {
21+
background: white;
22+
border-radius: 16px;
23+
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
24+
width: 100%;
25+
max-width: 900px;
26+
height: 90vh;
27+
display: flex;
28+
flex-direction: column;
29+
overflow: hidden;
30+
}
31+
32+
.header {
33+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
34+
color: white;
35+
padding: 24px;
36+
text-align: center;
37+
}
38+
39+
.header h1 {
40+
font-size: 24px;
41+
font-weight: 600;
42+
}
43+
44+
.messages {
45+
flex: 1;
46+
overflow-y: auto;
47+
padding: 24px;
48+
background: #f8f9fa;
49+
}
50+
51+
.message {
52+
margin-bottom: 16px;
53+
animation: slideIn 0.3s ease;
54+
}
55+
56+
@keyframes slideIn {
57+
from {
58+
opacity: 0;
59+
transform: translateY(10px);
60+
}
61+
to {
62+
opacity: 1;
63+
transform: translateY(0);
64+
}
65+
}
66+
67+
.message-content {
68+
padding: 16px;
69+
border-radius: 12px;
70+
max-width: 80%;
71+
word-wrap: break-word;
72+
white-space: pre-wrap;
73+
}
74+
75+
.message.user .message-content {
76+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
77+
color: white;
78+
margin-left: auto;
79+
}
80+
81+
.message.assistant .message-content {
82+
background: white;
83+
color: #2d3748;
84+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
85+
}
86+
87+
.input-container {
88+
padding: 24px;
89+
background: white;
90+
border-top: 1px solid #e2e8f0;
91+
}
92+
93+
.input-wrapper {
94+
display: flex;
95+
gap: 12px;
96+
}
97+
98+
textarea {
99+
flex: 1;
100+
padding: 14px;
101+
border: 2px solid #e2e8f0;
102+
border-radius: 12px;
103+
font-size: 15px;
104+
font-family: inherit;
105+
resize: none;
106+
transition: border-color 0.2s;
107+
}
108+
109+
textarea:focus {
110+
outline: none;
111+
border-color: #667eea;
112+
}
113+
114+
button {
115+
padding: 14px 28px;
116+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
117+
color: white;
118+
border: none;
119+
border-radius: 12px;
120+
font-size: 15px;
121+
font-weight: 600;
122+
cursor: pointer;
123+
transition: transform 0.2s, box-shadow 0.2s;
124+
}
125+
126+
button:hover:not(:disabled) {
127+
transform: translateY(-2px);
128+
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
129+
}
130+
131+
button:disabled {
132+
opacity: 0.6;
133+
cursor: not-allowed;
134+
}
135+
136+
.messages::-webkit-scrollbar {
137+
width: 8px;
138+
}
139+
140+
.messages::-webkit-scrollbar-track {
141+
background: #f1f1f1;
142+
}
143+
144+
.messages::-webkit-scrollbar-thumb {
145+
background: #cbd5e0;
146+
border-radius: 4px;
147+
}
148+
149+
.messages::-webkit-scrollbar-thumb:hover {
150+
background: #a0aec0;
151+
}
22152
</style>
23153
</head>
24154
<body>
25-
<div class="header">
26-
<h1>MCP Client for Keylime</h1>
27-
</div>
28-
29-
<div class="chat-container" id="messages">
30-
{{template "messages.html" .}}
155+
<div class="container">
156+
<div class="header">
157+
<h1>🔐 Keylime MCP Client</h1>
158+
</div>
159+
160+
<div class="messages" id="messages">
161+
<div class="message assistant">
162+
<div class="message-content">👋 Hi! Ask me anything about Keylime state or run commands.</div>
163+
</div>
164+
</div>
165+
166+
<div class="input-container">
167+
<form class="input-wrapper" id="chatForm">
168+
<textarea
169+
id="messageInput"
170+
name="message"
171+
placeholder="Ask about Keylime state or run commands..."
172+
rows="2"
173+
required
174+
></textarea>
175+
<button type="submit" id="sendBtn">Send</button>
176+
</form>
177+
</div>
31178
</div>
32179

33-
<form hx-post="/send" hx-target="#messages" hx-swap="innerHTML" class="input-area" id="chatForm">
34-
<textarea id="messageInput" name="message" placeholder="Type your message..." rows="3" required></textarea>
35-
<button type="submit" id="sendBtn">Send</button>
36-
</form>
37-
38180
<script>
181+
const messagesContainer = document.getElementById('messages');
39182
const form = document.getElementById('chatForm');
40183
const messageInput = document.getElementById('messageInput');
41184
const sendBtn = document.getElementById('sendBtn');
42185

43-
// Auto-scroll to bottom on new messages
44-
document.body.addEventListener('htmx:afterSwap', function(evt) {
45-
const container = document.getElementById('messages');
46-
container.scrollTop = container.scrollHeight;
186+
function addMessage(role, content) {
187+
const div = document.createElement('div');
188+
div.className = `message ${role}`;
189+
div.innerHTML = `<div class="message-content">${escapeHtml(content)}</div>`;
190+
messagesContainer.appendChild(div);
191+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
192+
}
193+
194+
function escapeHtml(text) {
195+
const div = document.createElement('div');
196+
div.textContent = text;
197+
return div.innerHTML;
198+
}
47199

48-
// Reset form after response
49-
if (evt.detail.target.id === 'messages') {
50-
messageInput.value = '';
200+
form.addEventListener('submit', function(e) {
201+
e.preventDefault();
202+
203+
const message = messageInput.value.trim();
204+
if (!message) return;
205+
206+
sendBtn.disabled = true;
207+
sendBtn.textContent = 'Processing...';
208+
messageInput.value = '';
209+
210+
const params = new URLSearchParams();
211+
params.append('message', message);
212+
213+
const eventSource = new EventSource('/send?' + params.toString());
214+
215+
eventSource.onmessage = function(event) {
216+
const data = JSON.parse(event.data);
217+
addMessage(data.role, data.content);
218+
};
219+
220+
eventSource.onerror = function() {
221+
eventSource.close();
51222
sendBtn.disabled = false;
52223
sendBtn.textContent = 'Send';
53-
}
224+
};
54225
});
55226

56-
// Disable button during request
57-
document.body.addEventListener('htmx:beforeRequest', function() {
58-
sendBtn.disabled = true;
59-
sendBtn.textContent = 'Sending...';
227+
messageInput.addEventListener('keydown', function(e) {
228+
if (e.key === 'Enter' && !e.shiftKey) {
229+
e.preventDefault();
230+
form.dispatchEvent(new Event('submit'));
231+
}
60232
});
61233
</script>
62234
</body>

mcp-client/templates/messages.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{{range .Messages}}
22
<div class="message {{.Role}}">
3-
<div class="content">{{.Content}}</div>
4-
<div class="time">{{.Time}}</div>
3+
<div class="message-content">{{.Content}}</div>
54
</div>
65
{{end}}

0 commit comments

Comments
 (0)