umanggarg Claude Sonnet 4.6 commited on
Commit
3e6d92c
Β·
1 Parent(s): a553525

UI: Add MCP server status panel to sidebar

Browse files

- fetchMcpStatus() in api.js calls GET /mcp-status
- Sidebar shows live MCP server status: connected dot, tool/resource/prompt counts
- Expandable panel lists all discovered tools, resource URIs, and prompt names
- Color coded: green dot = connected, tool names in accent color, URIs in green
- Loaded once on mount via useEffect

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (3) hide show
  1. ui/src/api.js +6 -0
  2. ui/src/components/Sidebar.jsx +64 -2
  3. ui/src/index.css +72 -0
ui/src/api.js CHANGED
@@ -26,6 +26,12 @@ export async function fetchGraph(slug) {
26
  return res.json();
27
  }
28
 
 
 
 
 
 
 
29
  export async function deleteRepo(slug) {
30
  const [owner, name] = slug.split("/");
31
  const res = await fetch(`${BASE}/repos/${owner}/${name}`, { method: "DELETE" });
 
26
  return res.json();
27
  }
28
 
29
+ export async function fetchMcpStatus() {
30
+ const res = await fetch(`${BASE}/mcp-status`);
31
+ if (!res.ok) throw new Error("Failed to fetch MCP status");
32
+ return res.json();
33
+ }
34
+
35
  export async function deleteRepo(slug) {
36
  const [owner, name] = slug.split("/");
37
  const res = await fetch(`${BASE}/repos/${owner}/${name}`, { method: "DELETE" });
ui/src/components/Sidebar.jsx CHANGED
@@ -1,10 +1,17 @@
1
- import { useState } from "react";
2
- import { ingestRepo, deleteRepo } from "../api";
3
 
4
  export default function Sidebar({ repos, activeRepo, onSelectRepo, onReposChange, mode, onModeChange, agentMode, onAgentModeChange }) {
5
  const [url, setUrl] = useState("");
6
  const [status, setStatus] = useState(null); // {type, text}
7
  const [loading, setLoading] = useState(false);
 
 
 
 
 
 
 
8
 
9
  async function handleIngest(e) {
10
  e.preventDefault();
@@ -101,6 +108,61 @@ export default function Sidebar({ repos, activeRepo, onSelectRepo, onReposChange
101
  </div>
102
  )}
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  {/* ── Repos ── */}
105
  <div style={{ flex: 1 }}>
106
  <div className="section-label">Indexed Repos ({repos.length})</div>
 
1
+ import { useState, useEffect } from "react";
2
+ import { ingestRepo, deleteRepo, fetchMcpStatus } from "../api";
3
 
4
  export default function Sidebar({ repos, activeRepo, onSelectRepo, onReposChange, mode, onModeChange, agentMode, onAgentModeChange }) {
5
  const [url, setUrl] = useState("");
6
  const [status, setStatus] = useState(null); // {type, text}
7
  const [loading, setLoading] = useState(false);
8
+ const [mcpInfo, setMcpInfo] = useState(null); // MCP server status
9
+ const [mcpOpen, setMcpOpen] = useState(false); // expand/collapse panel
10
+
11
+ // Load MCP status once on mount
12
+ useEffect(() => {
13
+ fetchMcpStatus().then(setMcpInfo).catch(() => setMcpInfo({ connected: false }));
14
+ }, []);
15
 
16
  async function handleIngest(e) {
17
  e.preventDefault();
 
108
  </div>
109
  )}
110
 
111
+ {/* ── MCP Server Status ── */}
112
+ <div className="mcp-panel">
113
+ <button className="mcp-panel-header" onClick={() => setMcpOpen(o => !o)}>
114
+ <span className={`mcp-dot ${mcpInfo?.connected ? "connected" : "disconnected"}`} />
115
+ <span className="mcp-panel-title">MCP Server</span>
116
+ {mcpInfo?.connected && (
117
+ <span className="mcp-counts">
118
+ {mcpInfo.tools.length}T Β· {mcpInfo.resources.length}R Β· {mcpInfo.prompts.length}P
119
+ </span>
120
+ )}
121
+ <span className="mcp-chevron">{mcpOpen ? "β–΄" : "β–Ύ"}</span>
122
+ </button>
123
+
124
+ {mcpOpen && mcpInfo && (
125
+ <div className="mcp-panel-body">
126
+ {!mcpInfo.connected ? (
127
+ <p className="mcp-error">Not connected β€” is the backend running?</p>
128
+ ) : (
129
+ <>
130
+ {mcpInfo.tools.length > 0 && (
131
+ <div className="mcp-section">
132
+ <div className="mcp-section-label">Tools</div>
133
+ {mcpInfo.tools.map(t => (
134
+ <div key={t.name} className="mcp-item">
135
+ <span className="mcp-item-name">{t.name}</span>
136
+ </div>
137
+ ))}
138
+ </div>
139
+ )}
140
+ {mcpInfo.resources.length > 0 && (
141
+ <div className="mcp-section">
142
+ <div className="mcp-section-label">Resources</div>
143
+ {mcpInfo.resources.map(r => (
144
+ <div key={r.uri} className="mcp-item">
145
+ <span className="mcp-item-name mcp-uri">{r.uri}</span>
146
+ </div>
147
+ ))}
148
+ </div>
149
+ )}
150
+ {mcpInfo.prompts.length > 0 && (
151
+ <div className="mcp-section">
152
+ <div className="mcp-section-label">Prompts</div>
153
+ {mcpInfo.prompts.map(p => (
154
+ <div key={p.name} className="mcp-item">
155
+ <span className="mcp-item-name">/{p.name}</span>
156
+ </div>
157
+ ))}
158
+ </div>
159
+ )}
160
+ </>
161
+ )}
162
+ </div>
163
+ )}
164
+ </div>
165
+
166
  {/* ── Repos ── */}
167
  <div style={{ flex: 1 }}>
168
  <div className="section-label">Indexed Repos ({repos.length})</div>
ui/src/index.css CHANGED
@@ -793,3 +793,75 @@ body {
793
  ::-webkit-scrollbar { width: 6px; }
794
  ::-webkit-scrollbar-track { background: transparent; }
795
  ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
793
  ::-webkit-scrollbar { width: 6px; }
794
  ::-webkit-scrollbar-track { background: transparent; }
795
  ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
796
+
797
+ /* ── MCP Server Status Panel ─────────────────────────────────── */
798
+ .mcp-panel {
799
+ border: 1px solid var(--border);
800
+ border-radius: 8px;
801
+ overflow: hidden;
802
+ font-size: 12px;
803
+ }
804
+
805
+ .mcp-panel-header {
806
+ width: 100%;
807
+ display: flex;
808
+ align-items: center;
809
+ gap: 7px;
810
+ padding: 8px 10px;
811
+ background: none;
812
+ border: none;
813
+ cursor: pointer;
814
+ color: var(--text);
815
+ text-align: left;
816
+ }
817
+ .mcp-panel-header:hover { background: var(--surface); }
818
+
819
+ .mcp-dot {
820
+ width: 7px;
821
+ height: 7px;
822
+ border-radius: 50%;
823
+ flex-shrink: 0;
824
+ }
825
+ .mcp-dot.connected { background: var(--green); }
826
+ .mcp-dot.disconnected { background: var(--muted); }
827
+
828
+ .mcp-panel-title { font-weight: 600; flex: 1; }
829
+
830
+ .mcp-counts {
831
+ font-size: 11px;
832
+ color: var(--muted);
833
+ font-variant-numeric: tabular-nums;
834
+ }
835
+
836
+ .mcp-chevron { font-size: 10px; color: var(--muted); }
837
+
838
+ .mcp-panel-body {
839
+ border-top: 1px solid var(--border);
840
+ padding: 8px 10px;
841
+ display: flex;
842
+ flex-direction: column;
843
+ gap: 8px;
844
+ }
845
+
846
+ .mcp-error { color: var(--muted); font-size: 11px; margin: 0; }
847
+
848
+ .mcp-section { display: flex; flex-direction: column; gap: 3px; }
849
+
850
+ .mcp-section-label {
851
+ font-size: 10px;
852
+ font-weight: 700;
853
+ letter-spacing: 0.06em;
854
+ text-transform: uppercase;
855
+ color: var(--muted);
856
+ margin-bottom: 2px;
857
+ }
858
+
859
+ .mcp-item { display: flex; align-items: center; gap: 6px; padding: 2px 0; }
860
+
861
+ .mcp-item-name {
862
+ font-family: var(--mono);
863
+ font-size: 11px;
864
+ color: var(--accent);
865
+ }
866
+
867
+ .mcp-uri { color: var(--green); }