main
html 829 lines 31.7 KB
Raw
1 <html>
2
3 <head>
4 <title>MCP Servers</title>
5
6 <script type="module">
7 import { store } from "/components/settings/mcp/client/mcp-servers-store.js";
8 </script>
9 </head>
10
11 <body>
12 <div x-data>
13 <template x-if="$store.mcpServersStore">
14 <div class="mcp-manager" x-create="$store.mcpServersStore.initialize()" x-destroy="$store.mcpServersStore.onClose()">
15 <header class="mcp-manager-header">
16 <div class="mcp-manager-heading">
17 <div class="mcp-manager-title-row">
18 <h2 x-text="$store.mcpServersStore.scopeTitle"></h2>
19 <span class="mcp-scope-pill" x-text="$store.mcpServersStore.scope === 'project' ? 'Project' : 'Global'"></span>
20 </div>
21 <p x-text="$store.mcpServersStore.scopeSubtitle"></p>
22
23 <div class="mcp-view-controls">
24 <div class="mcp-view-tabs">
25 <button type="button" :class="{ active: $store.mcpServersStore.activeView === 'visual' }"
26 @click="$store.mcpServersStore.setActiveView('visual')">
27 <x-icon aria-hidden="true" name="dashboard_customize"></x-icon>
28 <span>Manager</span>
29 </button>
30 <button type="button" :class="{ active: $store.mcpServersStore.activeView === 'raw' }"
31 @click="$store.mcpServersStore.setActiveView('raw')">
32 <x-icon aria-hidden="true" name="data_object"></x-icon>
33 <span>Raw JSON</span>
34 </button>
35 </div>
36
37 <label class="mcp-search" x-show="$store.mcpServersStore.activeView === 'visual'" style="display: none;">
38 <x-icon aria-hidden="true" name="search"></x-icon>
39 <input type="search" x-model.debounce.150ms="$store.mcpServersStore.serverSearch" placeholder="Search MCP servers" />
40 <button type="button" class="mcp-search-clear"
41 x-show="$store.mcpServersStore.serverSearchActive"
42 @click="$store.mcpServersStore.clearServerSearch()"
43 title="Clear search">
44 <x-icon aria-hidden="true" name="close"></x-icon>
45 </button>
46 </label>
47 </div>
48 </div>
49
50 <div class="mcp-manager-actions">
51 <button type="button" class="button" title="Refresh status" @click="$store.mcpServersStore.loadStatus()">
52 <x-icon aria-hidden="true" name="refresh"></x-icon>
53 </button>
54 <button type="button" class="button confirm" :disabled="$store.mcpServersStore.applying" @click="$store.mcpServersStore.applyNow()">
55 <x-icon aria-hidden="true" name="check"></x-icon>
56 <span x-text="$store.mcpServersStore.applying ? 'Applying' : 'Apply'"></span>
57 </button>
58 </div>
59 </header>
60
61 <section x-show="$store.mcpServersStore.activeView === 'visual'" class="mcp-visual-panel">
62 <section class="mcp-config-list" id="mcp-configured-servers">
63 <div class="mcp-section-title">
64 <h3>Configured servers</h3>
65 <span x-text="$store.mcpServersStore.configuredServersCountLabel"></span>
66 </div>
67 <template x-if="$store.mcpServersStore.configuredServers.length === 0">
68 <div class="mcp-empty">No MCP servers configured.</div>
69 </template>
70 <template x-if="$store.mcpServersStore.configuredServers.length > 0 && $store.mcpServersStore.filteredConfiguredServers.length === 0">
71 <div class="mcp-empty">No configured MCP servers match this search.</div>
72 </template>
73 <template x-for="entry in $store.mcpServersStore.filteredConfiguredServers" :key="entry.name">
74 <article class="mcp-config-card" :class="{ disabled: entry.config.disabled }">
75 <div class="mcp-config-card-main">
76 <div>
77 <div class="mcp-config-name" x-text="entry.name"></div>
78 <div class="mcp-config-summary" x-text="$store.mcpServersStore.configSummary(entry.config)"></div>
79 </div>
80 <span class="mcp-config-mode" x-text="$store.mcpServersStore.configModeLabel(entry.config)"></span>
81 </div>
82 <div class="mcp-config-actions">
83 <button type="button" class="mcp-icon-button" title="Edit" @click="$store.mcpServersStore.editConfigServer(entry.name)">
84 <x-icon aria-hidden="true" name="edit"></x-icon>
85 </button>
86 <div class="mcp-toggle-group">
87 <label class="toggle plugin-status-toggle mcp-status-toggle"
88 :title="entry.config.disabled ? 'Enable MCP server' : 'Disable MCP server'">
89 <input type="checkbox"
90 :checked="!entry.config.disabled"
91 @change="$store.mcpServersStore.toggleConfigServer(entry.name)"
92 @click.stop>
93 <span class="toggler"></span>
94 </label>
95 <span class="plugin-status-text" x-text="entry.config.disabled ? 'OFF' : 'ON'"></span>
96 </div>
97 <button type="button" class="mcp-icon-button" title="Remove"
98 :disabled="$store.mcpServersStore.applying"
99 @click="$confirmClick($event, () => $store.mcpServersStore.removeConfigServer(entry.name))">
100 <x-icon aria-hidden="true" name="delete"></x-icon>
101 </button>
102 </div>
103 </article>
104 </template>
105 </section>
106
107 <section class="mcp-status-section" id="mcp-servers-status">
108 <div class="mcp-section-title">
109 <h3>Servers status</h3>
110 <span x-text="$store.mcpServersStore.visibleServersCountLabel"></span>
111 </div>
112
113 <div x-show="$store.mcpServersStore.loading" class="mcp-empty">
114 Loading MCP server status...
115 </div>
116
117 <div class="mcp-status-list" x-show="!$store.mcpServersStore.loading">
118 <template x-for="server in $store.mcpServersStore.filteredServers" :key="server.scope + ':' + server.name">
119 <article class="mcp-status-row" :class="$store.mcpServersStore.statusClass(server)">
120 <div class="mcp-status-main">
121 <span class="mcp-status-dot"></span>
122 <div>
123 <div class="mcp-status-name">
124 <span x-text="server.name"></span>
125 <span class="mcp-status-scope" x-text="server.scope || 'global'"></span>
126 </div>
127 <div class="mcp-status-meta">
128 <span x-text="$store.mcpServersStore.statusLabel(server)"></span>
129 <span x-show="server.type" x-text="server.type"></span>
130 </div>
131 </div>
132 </div>
133 <div class="mcp-status-actions">
134 <button type="button" class="button" x-show="server.tool_count > 0"
135 @click="$store.mcpServersStore.onToolCountClick(server.name)"
136 x-text="server.tool_count + ' tools'"></button>
137 <button type="button" class="button" x-show="server.has_log"
138 @click="$store.mcpServersStore.getServerLog(server.name)">Log</button>
139 </div>
140 <div class="mcp-status-error" x-show="server.error" x-text="server.error"></div>
141 </article>
142 </template>
143
144 <div x-show="$store.mcpServersStore.servers.length === 0" class="mcp-empty">
145 No servers.
146 </div>
147 <div x-show="$store.mcpServersStore.servers.length > 0 && $store.mcpServersStore.filteredServers.length === 0" class="mcp-empty">
148 No visible MCP servers match this search.
149 </div>
150 </div>
151 </section>
152
153 <section class="mcp-add-panel" id="mcp-add-server">
154 <div class="mcp-add-header">
155 <h3>Add MCP server</h3>
156 <button type="button" class="mcp-icon-button" title="Clear form" @click="$store.mcpServersStore.resetForm()">
157 <x-icon aria-hidden="true" name="backspace"></x-icon>
158 </button>
159 </div>
160
161 <div class="mcp-segmented" role="tablist" aria-label="MCP server type">
162 <button type="button" :class="{ active: $store.mcpServersStore.serverForm.mode === 'local' }"
163 @click="$store.mcpServersStore.setFormMode('local')">Command (uvx/npx)</button>
164 <button type="button" :class="{ active: $store.mcpServersStore.serverForm.mode === 'remote' }"
165 @click="$store.mcpServersStore.setFormMode('remote')">Remote URL</button>
166 </div>
167
168 <div class="mcp-form-grid">
169 <label class="mcp-field">
170 <span>Name</span>
171 <input type="text" x-model="$store.mcpServersStore.serverForm.name" placeholder="github" />
172 </label>
173
174 <label class="mcp-field" x-show="$store.mcpServersStore.serverForm.mode === 'remote'">
175 <span>Remote MCP server URL</span>
176 <input type="url" x-model="$store.mcpServersStore.serverForm.url" placeholder="https://example.com/mcp" />
177 </label>
178
179 <label class="mcp-field" x-show="$store.mcpServersStore.serverForm.mode === 'local'" style="display: none;">
180 <span>Command line</span>
181 <input type="text" x-model="$store.mcpServersStore.serverForm.command" placeholder="npx -y @modelcontextprotocol/server-filesystem" />
182 </label>
183
184 <label class="mcp-field" x-show="$store.mcpServersStore.serverForm.mode === 'remote'">
185 <span>Transport</span>
186 <select x-model="$store.mcpServersStore.serverForm.type">
187 <option value="streamable-http">Streamable HTTP</option>
188 <option value="sse">SSE</option>
189 </select>
190 </label>
191
192 <label class="mcp-field" x-show="$store.mcpServersStore.serverForm.mode === 'local'" style="display: none;">
193 <span>Arguments</span>
194 <textarea x-model="$store.mcpServersStore.serverForm.argsText" rows="4" placeholder="--yes&#10;@modelcontextprotocol/server-filesystem"></textarea>
195 </label>
196
197 <label class="mcp-field" x-show="$store.mcpServersStore.serverForm.mode === 'remote'">
198 <span>Headers</span>
199 <textarea x-model="$store.mcpServersStore.serverForm.headersText" rows="4" placeholder="Authorization=Bearer ..."></textarea>
200 </label>
201
202 <label class="mcp-field" x-show="$store.mcpServersStore.serverForm.mode === 'local'" style="display: none;">
203 <span>Environment</span>
204 <textarea x-model="$store.mcpServersStore.serverForm.envText" rows="4" placeholder="TOKEN=..."></textarea>
205 </label>
206 </div>
207
208 <button type="button" class="mcp-advanced-toggle" @click="$store.mcpServersStore.advancedOpen = !$store.mcpServersStore.advancedOpen">
209 <x-icon :style="$store.mcpServersStore.advancedOpen ? 'transform:rotate(90deg)' : ''" name="chevron_right"></x-icon>
210 <span>Advanced settings</span>
211 </button>
212
213 <div class="mcp-advanced-body" x-show="$store.mcpServersStore.advancedOpen" x-transition.opacity style="display: none;">
214 <div class="mcp-form-grid">
215 <label class="mcp-field mcp-field-wide">
216 <span>Description</span>
217 <input type="text" x-model="$store.mcpServersStore.serverForm.description" placeholder="Optional" />
218 </label>
219
220 <label class="mcp-field">
221 <span>Startup timeout</span>
222 <input type="number" min="0" x-model="$store.mcpServersStore.serverForm.init_timeout" />
223 </label>
224
225 <label class="mcp-field">
226 <span>Tool timeout</span>
227 <input type="number" min="0" x-model="$store.mcpServersStore.serverForm.tool_timeout" />
228 </label>
229 </div>
230
231 <div class="mcp-toggle-row">
232 <label>
233 <input type="checkbox" x-model="$store.mcpServersStore.serverForm.disabled" />
234 <span>Disabled</span>
235 </label>
236 <label x-show="$store.mcpServersStore.serverForm.mode === 'remote'">
237 <input type="checkbox" x-model="$store.mcpServersStore.serverForm.verify" />
238 <span>Verify SSL</span>
239 </label>
240 </div>
241 </div>
242
243 <div class="mcp-add-actions">
244 <button type="button" class="button" @click="$store.mcpServersStore.openScanModal()">
245 <x-icon aria-hidden="true" name="radar"></x-icon>
246 <span>Scan with Agent Zero</span>
247 </button>
248 <button type="button" class="button confirm" @click="$store.mcpServersStore.addServerFromForm()">
249 <x-icon aria-hidden="true" name="add_circle"></x-icon>
250 <span>Add to config</span>
251 </button>
252 </div>
253 </section>
254 </section>
255
256 <section x-show="$store.mcpServersStore.activeView === 'raw'" class="mcp-raw-panel" style="display: none;">
257 <div class="mcp-raw-toolbar">
258 <h3>MCP Servers Configuration JSON</h3>
259 <div class="mcp-raw-actions">
260 <button type="button" class="button" onclick="openModal('settings/mcp/client/example.html')">Examples</button>
261 <button type="button" class="button" @click="$store.mcpServersStore.formatJson()">
262 <x-icon aria-hidden="true" name="format_indent_increase"></x-icon>
263 <span>Reformat</span>
264 </button>
265 <button type="button" class="button" title="Refresh status" @click="$store.mcpServersStore.loadStatus()">
266 <x-icon aria-hidden="true" name="refresh"></x-icon>
267 </button>
268 <button type="button" class="button confirm" :disabled="$store.mcpServersStore.applying" @click="$store.mcpServersStore.applyNow()">
269 <x-icon aria-hidden="true" name="check"></x-icon>
270 <span x-text="$store.mcpServersStore.applying ? 'Applying' : 'Apply'"></span>
271 </button>
272 </div>
273 </div>
274 <div id="mcp-servers-config-json"></div>
275 </section>
276 </div>
277 </template>
278 </div>
279
280 <style>
281 .mcp-manager {
282 display: flex;
283 flex-direction: column;
284 gap: 1rem;
285 color: var(--color-text);
286 font-family: var(--font-family-main);
287 }
288
289 .mcp-manager button,
290 .mcp-manager input,
291 .mcp-manager textarea,
292 .mcp-manager select {
293 font-family: inherit;
294 }
295
296 .mcp-manager-header,
297 .mcp-add-header,
298 .mcp-section-title,
299 .mcp-raw-toolbar {
300 display: flex;
301 align-items: flex-start;
302 justify-content: space-between;
303 gap: 1rem;
304 }
305
306 .mcp-manager-heading {
307 display: flex;
308 flex-direction: column;
309 gap: 0.55rem;
310 min-width: 0;
311 }
312
313 .mcp-manager-title-row {
314 display: flex;
315 flex-wrap: wrap;
316 align-items: center;
317 gap: 0.5rem;
318 }
319
320 .mcp-manager h2,
321 .mcp-manager h3,
322 .mcp-manager p {
323 margin: 0;
324 }
325
326 .mcp-manager h2 {
327 font-size: 1.35rem;
328 line-height: 1.2;
329 }
330
331 .mcp-manager h3 {
332 font-size: 1rem;
333 line-height: 1.2;
334 }
335
336 .mcp-manager p,
337 .mcp-config-summary,
338 .mcp-status-meta,
339 .mcp-empty {
340 color: var(--color-text-muted);
341 font-size: 0.86rem;
342 line-height: 1.4;
343 }
344
345 .mcp-scope-pill,
346 .mcp-status-scope,
347 .mcp-config-mode,
348 .mcp-section-title > span {
349 display: inline-flex;
350 align-items: center;
351 min-height: 1.45rem;
352 padding: 0.15rem 0.45rem;
353 border: 1px solid var(--color-border);
354 border-radius: 999px;
355 color: var(--color-text-muted);
356 font-size: 0.72rem;
357 font-weight: 600;
358 }
359
360 .mcp-manager-actions,
361 .mcp-add-actions,
362 .mcp-config-actions,
363 .mcp-status-actions,
364 .mcp-toggle-row,
365 .mcp-raw-actions,
366 .mcp-raw-toolbar {
367 display: flex;
368 flex-wrap: wrap;
369 align-items: center;
370 gap: 0.5rem;
371 }
372
373 .mcp-manager .button {
374 display: inline-flex;
375 align-items: center;
376 justify-content: center;
377 gap: 0.35rem;
378 min-height: 2rem;
379 padding: 0.35rem 0.65rem;
380 border: 1px solid var(--color-border);
381 border-radius: 6px;
382 background: var(--color-panel);
383 color: var(--color-text);
384 cursor: pointer;
385 }
386
387 .mcp-manager .button.confirm {
388 border-color: color-mix(in srgb, var(--color-highlight) 65%, var(--color-border));
389 background: var(--color-highlight);
390 color: #fff;
391 }
392
393 .mcp-manager .button:disabled {
394 opacity: 0.55;
395 cursor: not-allowed;
396 }
397
398 .mcp-manager .material-symbols-outlined {
399 font-size: 1.05rem;
400 line-height: 1;
401 }
402
403 .mcp-add-panel,
404 .mcp-config-card,
405 .mcp-status-row,
406 .mcp-scan-result,
407 .mcp-empty {
408 border: 1px solid var(--color-border);
409 border-radius: 8px;
410 background: color-mix(in srgb, var(--color-panel) 88%, transparent);
411 }
412
413 .mcp-add-panel {
414 display: flex;
415 flex-direction: column;
416 gap: 0.85rem;
417 padding: 1rem;
418 }
419
420 .mcp-segmented,
421 .mcp-view-tabs {
422 display: inline-flex;
423 width: fit-content;
424 padding: 0.18rem;
425 border: 1px solid var(--color-border);
426 border-radius: 7px;
427 background: var(--color-input);
428 }
429
430 .mcp-view-controls {
431 display: flex;
432 align-items: center;
433 gap: 0.6rem;
434 flex-wrap: wrap;
435 }
436
437 .mcp-segmented button,
438 .mcp-view-tabs button {
439 display: inline-flex;
440 align-items: center;
441 gap: 0.35rem;
442 min-height: 1.9rem;
443 padding: 0.3rem 0.65rem;
444 border: 0;
445 border-radius: 5px;
446 background: transparent;
447 color: var(--color-text-muted);
448 cursor: pointer;
449 }
450
451 .mcp-segmented button.active,
452 .mcp-view-tabs button.active {
453 background: var(--color-panel);
454 color: var(--color-text);
455 }
456
457 .mcp-search {
458 display: inline-flex;
459 align-items: center;
460 gap: 0.35rem;
461 min-height: 2.1rem;
462 min-width: min(22rem, 100%);
463 padding: 0.25rem 0.45rem;
464 border: 1px solid var(--color-border);
465 border-radius: 7px;
466 background: var(--color-input);
467 color: var(--color-text-muted);
468 }
469
470 .mcp-search input {
471 width: 100%;
472 min-width: 8rem;
473 border: 0;
474 background: transparent;
475 color: var(--color-text);
476 outline: none;
477 padding: 0.2rem;
478 }
479
480 .mcp-search input::-webkit-search-cancel-button,
481 .mcp-search input::-webkit-search-decoration {
482 -webkit-appearance: none;
483 appearance: none;
484 display: none;
485 }
486
487 .mcp-search-clear {
488 display: inline-flex;
489 align-items: center;
490 justify-content: center;
491 width: 1.45rem;
492 height: 1.45rem;
493 border: 0;
494 border-radius: 5px;
495 background: transparent;
496 color: var(--color-text-muted);
497 cursor: pointer;
498 }
499
500 .mcp-form-grid {
501 display: grid;
502 grid-template-columns: repeat(2, minmax(0, 1fr));
503 gap: 0.75rem;
504 }
505
506 .mcp-field {
507 display: flex;
508 flex-direction: column;
509 gap: 0.35rem;
510 min-width: 0;
511 }
512
513 .mcp-field-wide {
514 grid-column: 1 / -1;
515 }
516
517 .mcp-field span {
518 color: var(--color-text-muted);
519 font-size: 0.78rem;
520 font-weight: 600;
521 }
522
523 .mcp-field input,
524 .mcp-field textarea,
525 .mcp-field select {
526 width: 100%;
527 border: 1px solid var(--color-border);
528 border-radius: 7px;
529 background: var(--color-input);
530 color: var(--color-text);
531 padding: 0.55rem 0.65rem;
532 font-size: 0.88rem;
533 outline: none;
534 }
535
536 .mcp-field textarea {
537 resize: vertical;
538 min-height: 5.5rem;
539 font-family: var(--font-family-code);
540 font-size: 0.8rem;
541 }
542
543 .mcp-field input:focus,
544 .mcp-field textarea:focus,
545 .mcp-field select:focus {
546 border-color: var(--color-highlight);
547 }
548
549 .mcp-advanced-toggle {
550 display: inline-flex;
551 align-items: center;
552 gap: 0.35rem;
553 width: fit-content;
554 padding: 0.2rem 0;
555 border: 0;
556 background: transparent;
557 color: var(--color-text);
558 cursor: pointer;
559 font-weight: 600;
560 }
561
562 .mcp-advanced-toggle .material-symbols-outlined {
563 transition: transform 0.12s ease;
564 }
565
566 .mcp-advanced-body {
567 display: flex;
568 flex-direction: column;
569 gap: 0.75rem;
570 }
571
572 .mcp-toggle-row label {
573 display: inline-flex;
574 align-items: center;
575 gap: 0.35rem;
576 color: var(--color-text-muted);
577 font-size: 0.85rem;
578 }
579
580 .mcp-scan-result {
581 padding: 0.75rem;
582 }
583
584 .mcp-scan-heading {
585 display: flex;
586 align-items: center;
587 gap: 0.4rem;
588 font-weight: 700;
589 }
590
591 .mcp-scan-warnings {
592 display: flex;
593 flex-direction: column;
594 gap: 0.4rem;
595 margin-top: 0.65rem;
596 }
597
598 .mcp-scan-warning {
599 display: flex;
600 flex-direction: column;
601 gap: 0.12rem;
602 padding: 0.55rem 0.65rem;
603 border-left: 3px solid var(--color-border);
604 background: var(--color-input);
605 border-radius: 5px;
606 font-size: 0.82rem;
607 }
608
609 .mcp-scan-warning.warning {
610 border-left-color: var(--color-warning-text);
611 }
612
613 .mcp-scan-warning.error {
614 border-left-color: var(--color-error-text);
615 }
616
617 .mcp-visual-panel,
618 .mcp-config-list,
619 .mcp-status-section,
620 .mcp-raw-panel {
621 display: flex;
622 flex-direction: column;
623 gap: 0.65rem;
624 }
625
626 .mcp-config-card {
627 display: flex;
628 align-items: center;
629 justify-content: space-between;
630 gap: 1rem;
631 padding: 0.75rem;
632 }
633
634 .mcp-config-card.disabled {
635 opacity: 0.62;
636 }
637
638 .mcp-config-card-main {
639 display: flex;
640 align-items: flex-start;
641 justify-content: space-between;
642 gap: 0.75rem;
643 min-width: 0;
644 flex: 1;
645 }
646
647 .mcp-config-name,
648 .mcp-status-name {
649 display: flex;
650 align-items: center;
651 gap: 0.4rem;
652 min-width: 0;
653 font-weight: 700;
654 }
655
656 .mcp-config-summary {
657 margin-top: 0.2rem;
658 overflow-wrap: anywhere;
659 }
660
661 .mcp-toggle-group {
662 display: inline-flex;
663 align-items: center;
664 gap: 0.45rem;
665 min-width: 5.2rem;
666 }
667
668 .mcp-status-toggle {
669 width: 48px;
670 height: 28px;
671 flex: 0 0 48px;
672 }
673
674 .mcp-status-toggle .toggler {
675 border-radius: 999px;
676 }
677
678 .mcp-status-toggle .toggler:before {
679 width: 20px;
680 height: 20px;
681 left: 4px;
682 bottom: 4px;
683 }
684
685 .mcp-status-toggle input:checked + .toggler:before {
686 transform: translateX(20px);
687 }
688
689 .mcp-toggle-group .plugin-status-text {
690 color: var(--color-text-muted);
691 font-size: 0.76rem;
692 font-weight: 700;
693 min-width: 1.7rem;
694 }
695
696 .mcp-icon-button {
697 display: inline-flex;
698 align-items: center;
699 justify-content: center;
700 width: 2rem;
701 height: 2rem;
702 border: 1px solid var(--color-border);
703 border-radius: 6px;
704 background: var(--color-panel);
705 color: var(--color-text);
706 cursor: pointer;
707 }
708
709 .mcp-icon-button:disabled {
710 opacity: 0.55;
711 cursor: not-allowed;
712 }
713
714 .mcp-raw-toolbar {
715 align-items: center;
716 }
717
718 .mcp-raw-actions {
719 justify-content: flex-end;
720 }
721
722 #mcp-servers-config-json {
723 width: 100%;
724 height: 28rem;
725 border: 1px solid var(--color-border);
726 border-radius: 8px;
727 overflow: hidden;
728 }
729
730 .mcp-status-list {
731 display: flex;
732 flex-direction: column;
733 gap: 0.5rem;
734 }
735
736 .mcp-status-row {
737 display: grid;
738 grid-template-columns: minmax(0, 1fr) auto;
739 gap: 0.6rem 1rem;
740 padding: 0.75rem;
741 }
742
743 .mcp-status-main {
744 display: flex;
745 align-items: center;
746 gap: 0.65rem;
747 min-width: 0;
748 }
749
750 .mcp-status-dot {
751 width: 0.72rem;
752 height: 0.72rem;
753 border-radius: 50%;
754 border: 2px solid var(--color-border);
755 flex-shrink: 0;
756 }
757
758 .mcp-status-row.ok .mcp-status-dot {
759 border-color: #22c55e;
760 background: #22c55e;
761 }
762
763 .mcp-status-row.idle .mcp-status-dot {
764 border-color: var(--color-warning-text);
765 }
766
767 .mcp-status-row.danger .mcp-status-dot {
768 border-color: var(--color-error-text);
769 background: var(--color-error-text);
770 }
771
772 .mcp-status-meta {
773 display: flex;
774 flex-wrap: wrap;
775 gap: 0.35rem 0.7rem;
776 margin-top: 0.18rem;
777 }
778
779 .mcp-status-error {
780 grid-column: 1 / -1;
781 color: var(--color-error-text);
782 font-size: 0.8rem;
783 line-height: 1.35;
784 overflow-wrap: anywhere;
785 }
786
787 .mcp-empty {
788 padding: 0.9rem;
789 text-align: center;
790 }
791
792 @media (max-width: 760px) {
793 .mcp-manager-header,
794 .mcp-config-card,
795 .mcp-status-row {
796 align-items: stretch;
797 grid-template-columns: 1fr;
798 flex-direction: column;
799 }
800
801 .mcp-form-grid {
802 grid-template-columns: 1fr;
803 }
804
805 .mcp-manager-actions,
806 .mcp-raw-actions,
807 .mcp-status-actions {
808 justify-content: flex-start;
809 }
810
811 .mcp-view-tabs {
812 width: 100%;
813 }
814
815 .mcp-view-tabs button {
816 flex: 1;
817 justify-content: center;
818 }
819
820 .mcp-view-controls,
821 .mcp-search {
822 width: 100%;
823 }
824 }
825 </style>
826
827 </body>
828
829 </html>