@wener/mcps 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.mjs +15 -0
- package/dist/mcps-cli.mjs +174727 -0
- package/lib/chat/agent.js +187 -0
- package/lib/chat/agent.js.map +1 -0
- package/lib/chat/audit.js +238 -0
- package/lib/chat/audit.js.map +1 -0
- package/lib/chat/converters.js +467 -0
- package/lib/chat/converters.js.map +1 -0
- package/lib/chat/handler.js +1068 -0
- package/lib/chat/handler.js.map +1 -0
- package/lib/chat/index.js +12 -0
- package/lib/chat/index.js.map +1 -0
- package/lib/chat/types.js +35 -0
- package/lib/chat/types.js.map +1 -0
- package/lib/contracts/AuditContract.js +85 -0
- package/lib/contracts/AuditContract.js.map +1 -0
- package/lib/contracts/McpsContract.js +113 -0
- package/lib/contracts/McpsContract.js.map +1 -0
- package/lib/contracts/index.js +3 -0
- package/lib/contracts/index.js.map +1 -0
- package/lib/dev.server.js +7 -0
- package/lib/dev.server.js.map +1 -0
- package/lib/entities/ChatRequestEntity.js +318 -0
- package/lib/entities/ChatRequestEntity.js.map +1 -0
- package/lib/entities/McpRequestEntity.js +271 -0
- package/lib/entities/McpRequestEntity.js.map +1 -0
- package/lib/entities/RequestLogEntity.js +177 -0
- package/lib/entities/RequestLogEntity.js.map +1 -0
- package/lib/entities/ResponseEntity.js +150 -0
- package/lib/entities/ResponseEntity.js.map +1 -0
- package/lib/entities/index.js +11 -0
- package/lib/entities/index.js.map +1 -0
- package/lib/entities/types.js +11 -0
- package/lib/entities/types.js.map +1 -0
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -0
- package/lib/mcps-cli.js +44 -0
- package/lib/mcps-cli.js.map +1 -0
- package/lib/providers/McpServerHandlerDef.js +40 -0
- package/lib/providers/McpServerHandlerDef.js.map +1 -0
- package/lib/providers/findMcpServerDef.js +26 -0
- package/lib/providers/findMcpServerDef.js.map +1 -0
- package/lib/providers/prometheus/def.js +24 -0
- package/lib/providers/prometheus/def.js.map +1 -0
- package/lib/providers/prometheus/index.js +2 -0
- package/lib/providers/prometheus/index.js.map +1 -0
- package/lib/providers/relay/def.js +32 -0
- package/lib/providers/relay/def.js.map +1 -0
- package/lib/providers/relay/index.js +2 -0
- package/lib/providers/relay/index.js.map +1 -0
- package/lib/providers/sql/def.js +31 -0
- package/lib/providers/sql/def.js.map +1 -0
- package/lib/providers/sql/index.js +2 -0
- package/lib/providers/sql/index.js.map +1 -0
- package/lib/providers/tencent-cls/def.js +44 -0
- package/lib/providers/tencent-cls/def.js.map +1 -0
- package/lib/providers/tencent-cls/index.js +2 -0
- package/lib/providers/tencent-cls/index.js.map +1 -0
- package/lib/scripts/bundle.js +90 -0
- package/lib/scripts/bundle.js.map +1 -0
- package/lib/server/api-routes.js +96 -0
- package/lib/server/api-routes.js.map +1 -0
- package/lib/server/audit.js +274 -0
- package/lib/server/audit.js.map +1 -0
- package/lib/server/chat-routes.js +82 -0
- package/lib/server/chat-routes.js.map +1 -0
- package/lib/server/config.js +223 -0
- package/lib/server/config.js.map +1 -0
- package/lib/server/db.js +97 -0
- package/lib/server/db.js.map +1 -0
- package/lib/server/index.js +2 -0
- package/lib/server/index.js.map +1 -0
- package/lib/server/mcp-handler.js +167 -0
- package/lib/server/mcp-handler.js.map +1 -0
- package/lib/server/mcp-routes.js +112 -0
- package/lib/server/mcp-routes.js.map +1 -0
- package/lib/server/mcps-router.js +119 -0
- package/lib/server/mcps-router.js.map +1 -0
- package/lib/server/schema.js +129 -0
- package/lib/server/schema.js.map +1 -0
- package/lib/server/server.js +166 -0
- package/lib/server/server.js.map +1 -0
- package/lib/web/ChatPage.js +827 -0
- package/lib/web/ChatPage.js.map +1 -0
- package/lib/web/McpInspectorPage.js +214 -0
- package/lib/web/McpInspectorPage.js.map +1 -0
- package/lib/web/ServersPage.js +93 -0
- package/lib/web/ServersPage.js.map +1 -0
- package/lib/web/main.js +541 -0
- package/lib/web/main.js.map +1 -0
- package/package.json +83 -0
- package/src/chat/agent.ts +240 -0
- package/src/chat/audit.ts +377 -0
- package/src/chat/converters.test.ts +325 -0
- package/src/chat/converters.ts +459 -0
- package/src/chat/handler.test.ts +137 -0
- package/src/chat/handler.ts +1233 -0
- package/src/chat/index.ts +16 -0
- package/src/chat/types.ts +72 -0
- package/src/contracts/AuditContract.ts +93 -0
- package/src/contracts/McpsContract.ts +141 -0
- package/src/contracts/index.ts +18 -0
- package/src/dev.server.ts +7 -0
- package/src/entities/ChatRequestEntity.ts +157 -0
- package/src/entities/McpRequestEntity.ts +149 -0
- package/src/entities/RequestLogEntity.ts +78 -0
- package/src/entities/ResponseEntity.ts +75 -0
- package/src/entities/index.ts +12 -0
- package/src/entities/types.ts +188 -0
- package/src/index.ts +1 -0
- package/src/mcps-cli.ts +59 -0
- package/src/providers/McpServerHandlerDef.ts +105 -0
- package/src/providers/findMcpServerDef.ts +31 -0
- package/src/providers/prometheus/def.ts +21 -0
- package/src/providers/prometheus/index.ts +1 -0
- package/src/providers/relay/def.ts +31 -0
- package/src/providers/relay/index.ts +1 -0
- package/src/providers/relay/relay.test.ts +47 -0
- package/src/providers/sql/def.ts +33 -0
- package/src/providers/sql/index.ts +1 -0
- package/src/providers/tencent-cls/def.ts +38 -0
- package/src/providers/tencent-cls/index.ts +1 -0
- package/src/scripts/bundle.ts +82 -0
- package/src/server/api-routes.ts +98 -0
- package/src/server/audit.ts +310 -0
- package/src/server/chat-routes.ts +95 -0
- package/src/server/config.test.ts +162 -0
- package/src/server/config.ts +198 -0
- package/src/server/db.ts +115 -0
- package/src/server/index.ts +1 -0
- package/src/server/mcp-handler.ts +209 -0
- package/src/server/mcp-routes.ts +133 -0
- package/src/server/mcps-router.ts +133 -0
- package/src/server/schema.ts +175 -0
- package/src/server/server.ts +163 -0
- package/src/web/ChatPage.tsx +1005 -0
- package/src/web/McpInspectorPage.tsx +254 -0
- package/src/web/ServersPage.tsx +139 -0
- package/src/web/main.tsx +600 -0
- package/src/web/styles.css +15 -0
package/lib/web/main.js
ADDED
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import { ChevronDown, ChevronUp, RefreshCw, Stethoscope } from "lucide-react";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import { HashRouter, Routes, Route, NavLink, Navigate } from "react-router-dom";
|
|
5
|
+
import { ChatPage } from "./ChatPage.js";
|
|
6
|
+
import { McpInspectorPage } from "./McpInspectorPage.js";
|
|
7
|
+
import "./styles.css";
|
|
8
|
+
// Simple API client using fetch
|
|
9
|
+
export const api = {
|
|
10
|
+
async overview() {
|
|
11
|
+
const res = await fetch("/api/mcps/overview");
|
|
12
|
+
return res.json();
|
|
13
|
+
},
|
|
14
|
+
async stats(params = {}) {
|
|
15
|
+
const url = new URL("/api/mcps/stats", window.location.origin);
|
|
16
|
+
if (params.from)
|
|
17
|
+
url.searchParams.set("from", params.from);
|
|
18
|
+
if (params.to)
|
|
19
|
+
url.searchParams.set("to", params.to);
|
|
20
|
+
const res = await fetch(url);
|
|
21
|
+
return res.json();
|
|
22
|
+
},
|
|
23
|
+
async auditList(params = {}) {
|
|
24
|
+
const url = new URL("/api/audit", window.location.origin);
|
|
25
|
+
if (params.limit)
|
|
26
|
+
url.searchParams.set("limit", String(params.limit));
|
|
27
|
+
const res = await fetch(url);
|
|
28
|
+
return res.json();
|
|
29
|
+
},
|
|
30
|
+
async servers() {
|
|
31
|
+
const res = await fetch("/api/mcps/servers");
|
|
32
|
+
return res.json();
|
|
33
|
+
},
|
|
34
|
+
async models() {
|
|
35
|
+
const res = await fetch("/api/mcps/models");
|
|
36
|
+
return res.json();
|
|
37
|
+
},
|
|
38
|
+
async healthCheck(model) {
|
|
39
|
+
const start = Date.now();
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch("/v1/chat/completions", {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: {
|
|
44
|
+
"Content-Type": "application/json"
|
|
45
|
+
},
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
model,
|
|
48
|
+
messages: [
|
|
49
|
+
{
|
|
50
|
+
role: "user",
|
|
51
|
+
content: "hello"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
max_tokens: 10
|
|
55
|
+
})
|
|
56
|
+
});
|
|
57
|
+
const durationMs = Date.now() - start;
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
const data = await res.json().catch(() => ({}));
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
error: data.error?.message || `HTTP ${res.status}`,
|
|
63
|
+
durationMs
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
ok: true,
|
|
68
|
+
durationMs
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
error: e instanceof Error ? e.message : "Unknown error",
|
|
75
|
+
durationMs: Date.now() - start
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
function getServerTypeBadgeClass(type) {
|
|
81
|
+
const classes = {
|
|
82
|
+
"tencent-cls": "badge-info",
|
|
83
|
+
sql: "badge-success",
|
|
84
|
+
prometheus: "badge-secondary",
|
|
85
|
+
relay: "badge-warning"
|
|
86
|
+
};
|
|
87
|
+
return classes[type] || "badge-primary";
|
|
88
|
+
}
|
|
89
|
+
// Header hints for each server type
|
|
90
|
+
const serverTypeHeaders = {
|
|
91
|
+
"tencent-cls": {
|
|
92
|
+
required: [
|
|
93
|
+
"X-CLS-SECRET-ID",
|
|
94
|
+
"X-CLS-SECRET-KEY",
|
|
95
|
+
"X-CLS-REGION"
|
|
96
|
+
],
|
|
97
|
+
optional: [
|
|
98
|
+
"X-CLS-ENDPOINT"
|
|
99
|
+
]
|
|
100
|
+
},
|
|
101
|
+
sql: {
|
|
102
|
+
required: [
|
|
103
|
+
"X-DB-URL"
|
|
104
|
+
],
|
|
105
|
+
optional: [
|
|
106
|
+
"X-DB-READ-URL",
|
|
107
|
+
"X-DB-WRITE-URL"
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
prometheus: {
|
|
111
|
+
required: [
|
|
112
|
+
"X-SERVICE-URL"
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
relay: {
|
|
116
|
+
required: [
|
|
117
|
+
"X-MCP-URL"
|
|
118
|
+
],
|
|
119
|
+
optional: [
|
|
120
|
+
"X-MCP-TYPE",
|
|
121
|
+
"X-MCP-COMMAND"
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
function ServerTypeCard({ serverType }) {
|
|
126
|
+
const headers = serverTypeHeaders[serverType.type];
|
|
127
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
128
|
+
className: "card bg-base-100 shadow-sm border border-base-300"
|
|
129
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
130
|
+
className: "card-body p-4"
|
|
131
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
132
|
+
className: "flex items-center gap-2 mb-2"
|
|
133
|
+
}, /*#__PURE__*/ React.createElement("span", {
|
|
134
|
+
className: `badge ${getServerTypeBadgeClass(serverType.type)}`
|
|
135
|
+
}, serverType.type)), /*#__PURE__*/ React.createElement("p", {
|
|
136
|
+
className: "text-sm text-base-content/70 mb-2"
|
|
137
|
+
}, serverType.description), /*#__PURE__*/ React.createElement("code", {
|
|
138
|
+
className: "text-xs bg-base-200 px-2 py-1 rounded block mb-2"
|
|
139
|
+
}, serverType.dynamicEndpoint), headers && /*#__PURE__*/ React.createElement("div", {
|
|
140
|
+
className: "text-xs"
|
|
141
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
142
|
+
className: "text-base-content/50 mb-1"
|
|
143
|
+
}, "Headers:"), /*#__PURE__*/ React.createElement("div", {
|
|
144
|
+
className: "flex flex-wrap gap-1"
|
|
145
|
+
}, headers.required.map((h) => /*#__PURE__*/ React.createElement("code", {
|
|
146
|
+
key: h,
|
|
147
|
+
className: "bg-error/10 text-error px-1 rounded"
|
|
148
|
+
}, h)), headers.optional?.map((h) => /*#__PURE__*/ React.createElement("code", {
|
|
149
|
+
key: h,
|
|
150
|
+
className: "bg-base-200 px-1 rounded"
|
|
151
|
+
}, h))))));
|
|
152
|
+
}
|
|
153
|
+
function ServerCard({ server }) {
|
|
154
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
155
|
+
className: "card bg-base-100 shadow-sm border border-base-300"
|
|
156
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
157
|
+
className: "card-body p-4 flex-row justify-between items-center"
|
|
158
|
+
}, /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("div", {
|
|
159
|
+
className: "font-semibold text-base-content"
|
|
160
|
+
}, server.name), /*#__PURE__*/ React.createElement("span", {
|
|
161
|
+
className: `badge badge-sm ${getServerTypeBadgeClass(server.type)}`
|
|
162
|
+
}, server.type)), server.disabled && /*#__PURE__*/ React.createElement("span", {
|
|
163
|
+
className: "badge badge-error badge-sm"
|
|
164
|
+
}, "disabled")));
|
|
165
|
+
}
|
|
166
|
+
function ModelCard({ model }) {
|
|
167
|
+
const [checking, setChecking] = useState(false);
|
|
168
|
+
const [result, setResult] = useState(null);
|
|
169
|
+
const handleCheck = async () => {
|
|
170
|
+
setChecking(true);
|
|
171
|
+
setResult(null);
|
|
172
|
+
const res = await api.healthCheck(model.name);
|
|
173
|
+
setResult(res);
|
|
174
|
+
setChecking(false);
|
|
175
|
+
};
|
|
176
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
177
|
+
className: "card bg-base-100 shadow-sm border border-base-300"
|
|
178
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
179
|
+
className: "card-body p-4"
|
|
180
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
181
|
+
className: "flex justify-between items-start"
|
|
182
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
183
|
+
className: "font-semibold text-base-content mb-2"
|
|
184
|
+
}, model.name), /*#__PURE__*/ React.createElement("button", {
|
|
185
|
+
type: "button",
|
|
186
|
+
className: "btn btn-xs btn-ghost",
|
|
187
|
+
onClick: handleCheck,
|
|
188
|
+
disabled: checking,
|
|
189
|
+
title: "Health check"
|
|
190
|
+
}, checking ? /*#__PURE__*/ React.createElement("span", {
|
|
191
|
+
className: "loading loading-spinner loading-xs"
|
|
192
|
+
}) : /*#__PURE__*/ React.createElement(Stethoscope, {
|
|
193
|
+
className: "w-4 h-4"
|
|
194
|
+
}))), /*#__PURE__*/ React.createElement("div", {
|
|
195
|
+
className: "flex gap-2 flex-wrap"
|
|
196
|
+
}, model.adapter && /*#__PURE__*/ React.createElement("span", {
|
|
197
|
+
className: "badge badge-secondary badge-sm"
|
|
198
|
+
}, model.adapter), model.baseUrl && /*#__PURE__*/ React.createElement("span", {
|
|
199
|
+
className: "text-xs text-base-content/50 truncate max-w-48",
|
|
200
|
+
title: model.baseUrl
|
|
201
|
+
}, model.baseUrl)), result && /*#__PURE__*/ React.createElement("div", {
|
|
202
|
+
className: `mt-2 text-xs ${result.ok ? "text-success" : "text-error"}`
|
|
203
|
+
}, result.ok ? `✓ OK (${result.durationMs}ms)` : `✗ ${result.error}`)));
|
|
204
|
+
}
|
|
205
|
+
// Overview Page
|
|
206
|
+
function OverviewPage() {
|
|
207
|
+
const [overview, setOverview] = useState(null);
|
|
208
|
+
const [stats, setStats] = useState(null);
|
|
209
|
+
const [loading, setLoading] = useState(true);
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
Promise.all([
|
|
212
|
+
api.overview(),
|
|
213
|
+
api.stats()
|
|
214
|
+
]).then(([o, s]) => {
|
|
215
|
+
setOverview(o);
|
|
216
|
+
setStats(s);
|
|
217
|
+
}).finally(() => setLoading(false));
|
|
218
|
+
}, []);
|
|
219
|
+
if (loading)
|
|
220
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
221
|
+
className: "loading loading-spinner loading-lg mx-auto"
|
|
222
|
+
});
|
|
223
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
224
|
+
className: "space-y-6"
|
|
225
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
226
|
+
className: "grid grid-cols-2 md:grid-cols-4 gap-4"
|
|
227
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
228
|
+
className: "stat bg-base-100 rounded-box shadow-sm border border-base-300"
|
|
229
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
230
|
+
className: "stat-value text-2xl"
|
|
231
|
+
}, overview?.servers.length ?? 0), /*#__PURE__*/ React.createElement("div", {
|
|
232
|
+
className: "stat-desc"
|
|
233
|
+
}, "Configured Servers")), /*#__PURE__*/ React.createElement("div", {
|
|
234
|
+
className: "stat bg-base-100 rounded-box shadow-sm border border-base-300"
|
|
235
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
236
|
+
className: "stat-value text-2xl"
|
|
237
|
+
}, overview?.models.length ?? 0), /*#__PURE__*/ React.createElement("div", {
|
|
238
|
+
className: "stat-desc"
|
|
239
|
+
}, "Model Configs")), /*#__PURE__*/ React.createElement("div", {
|
|
240
|
+
className: "stat bg-base-100 rounded-box shadow-sm border border-base-300"
|
|
241
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
242
|
+
className: "stat-value text-2xl"
|
|
243
|
+
}, stats?.totalRequests ?? 0), /*#__PURE__*/ React.createElement("div", {
|
|
244
|
+
className: "stat-desc"
|
|
245
|
+
}, "Total Requests")), /*#__PURE__*/ React.createElement("div", {
|
|
246
|
+
className: "stat bg-base-100 rounded-box shadow-sm border border-base-300"
|
|
247
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
248
|
+
className: `stat-value text-2xl ${(stats?.totalErrors ?? 0) > 0 ? "text-error" : ""}`
|
|
249
|
+
}, stats?.totalErrors ?? 0), /*#__PURE__*/ React.createElement("div", {
|
|
250
|
+
className: "stat-desc"
|
|
251
|
+
}, "Errors"))), /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("h2", {
|
|
252
|
+
className: "text-lg font-semibold mb-3 text-base-content"
|
|
253
|
+
}, "Supported Server Types"), /*#__PURE__*/ React.createElement("div", {
|
|
254
|
+
className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"
|
|
255
|
+
}, overview?.serverTypes.map((st) => /*#__PURE__*/ React.createElement(ServerTypeCard, {
|
|
256
|
+
key: st.type,
|
|
257
|
+
serverType: st
|
|
258
|
+
}))), /*#__PURE__*/ React.createElement("div", {
|
|
259
|
+
className: "mt-3 text-sm text-base-content/50"
|
|
260
|
+
}, /*#__PURE__*/ React.createElement("span", {
|
|
261
|
+
className: "font-medium"
|
|
262
|
+
}, "Common Headers:"), " ", /*#__PURE__*/ React.createElement("code", {
|
|
263
|
+
className: "bg-base-200 px-1 rounded"
|
|
264
|
+
}, "X-MCP-Readonly"), " (TRUE = only readonly tools)", " ", /*#__PURE__*/ React.createElement("code", {
|
|
265
|
+
className: "bg-base-200 px-1 rounded"
|
|
266
|
+
}, "X-MCP-Include"), " ", /*#__PURE__*/ React.createElement("code", {
|
|
267
|
+
className: "bg-base-200 px-1 rounded"
|
|
268
|
+
}, "X-MCP-Exclude"), " (glob patterns for tool filtering)")), overview && overview.servers.length > 0 && /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("h2", {
|
|
269
|
+
className: "text-lg font-semibold mb-3 text-base-content"
|
|
270
|
+
}, "Configured Servers (", overview.servers.length, ")"), /*#__PURE__*/ React.createElement("div", {
|
|
271
|
+
className: "grid grid-cols-1 md:grid-cols-2 gap-4"
|
|
272
|
+
}, overview.servers.slice(0, 4).map((s) => /*#__PURE__*/ React.createElement(ServerCard, {
|
|
273
|
+
key: s.name,
|
|
274
|
+
server: s
|
|
275
|
+
}))), overview.servers.length > 4 && /*#__PURE__*/ React.createElement("div", {
|
|
276
|
+
className: "text-center mt-4"
|
|
277
|
+
}, /*#__PURE__*/ React.createElement(NavLink, {
|
|
278
|
+
to: "/servers",
|
|
279
|
+
className: "btn btn-ghost btn-sm"
|
|
280
|
+
}, "View all ", overview.servers.length, " servers \u2192"))), stats && stats.byMethod.length > 0 && /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("h2", {
|
|
281
|
+
className: "text-lg font-semibold mb-3 text-base-content"
|
|
282
|
+
}, "Requests by Method"), /*#__PURE__*/ React.createElement("div", {
|
|
283
|
+
className: "overflow-x-auto bg-base-100 rounded-box shadow-sm border border-base-300"
|
|
284
|
+
}, /*#__PURE__*/ React.createElement("table", {
|
|
285
|
+
className: "table table-zebra"
|
|
286
|
+
}, /*#__PURE__*/ React.createElement("thead", null, /*#__PURE__*/ React.createElement("tr", null, /*#__PURE__*/ React.createElement("th", null, "Method"), /*#__PURE__*/ React.createElement("th", null, "Count"))), /*#__PURE__*/ React.createElement("tbody", null, stats.byMethod.map((item) => /*#__PURE__*/ React.createElement("tr", {
|
|
287
|
+
key: item.method
|
|
288
|
+
}, /*#__PURE__*/ React.createElement("td", null, /*#__PURE__*/ React.createElement("span", {
|
|
289
|
+
className: "badge badge-info badge-sm"
|
|
290
|
+
}, item.method)), /*#__PURE__*/ React.createElement("td", null, item.count))))))));
|
|
291
|
+
}
|
|
292
|
+
// Servers Page
|
|
293
|
+
function ServersPage() {
|
|
294
|
+
const [servers, setServers] = useState([]);
|
|
295
|
+
const [loading, setLoading] = useState(true);
|
|
296
|
+
useEffect(() => {
|
|
297
|
+
api.servers().then((data) => {
|
|
298
|
+
setServers(data.servers);
|
|
299
|
+
setLoading(false);
|
|
300
|
+
});
|
|
301
|
+
}, []);
|
|
302
|
+
if (loading)
|
|
303
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
304
|
+
className: "loading loading-spinner loading-lg mx-auto"
|
|
305
|
+
});
|
|
306
|
+
return /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("h2", {
|
|
307
|
+
className: "text-lg font-semibold mb-3 text-base-content"
|
|
308
|
+
}, "Configured Servers (", servers.length, ")"), servers.length > 0 ? /*#__PURE__*/ React.createElement("div", {
|
|
309
|
+
className: "grid grid-cols-1 md:grid-cols-2 gap-4"
|
|
310
|
+
}, servers.map((s) => /*#__PURE__*/ React.createElement(ServerCard, {
|
|
311
|
+
key: s.name,
|
|
312
|
+
server: s
|
|
313
|
+
}))) : /*#__PURE__*/ React.createElement("div", {
|
|
314
|
+
className: "card bg-base-100 shadow-sm border border-base-300"
|
|
315
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
316
|
+
className: "card-body text-center text-base-content/50"
|
|
317
|
+
}, "No servers configured")));
|
|
318
|
+
}
|
|
319
|
+
// Models Page
|
|
320
|
+
function ModelsPage() {
|
|
321
|
+
const [models, setModels] = useState([]);
|
|
322
|
+
const [loading, setLoading] = useState(true);
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
api.models().then((data) => {
|
|
325
|
+
setModels(data.models);
|
|
326
|
+
setLoading(false);
|
|
327
|
+
});
|
|
328
|
+
}, []);
|
|
329
|
+
if (loading)
|
|
330
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
331
|
+
className: "loading loading-spinner loading-lg mx-auto"
|
|
332
|
+
});
|
|
333
|
+
return /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("h2", {
|
|
334
|
+
className: "text-lg font-semibold mb-3 text-base-content"
|
|
335
|
+
}, "Model Configurations (", models.length, ")"), models.length > 0 ? /*#__PURE__*/ React.createElement("div", {
|
|
336
|
+
className: "grid grid-cols-1 md:grid-cols-2 gap-4"
|
|
337
|
+
}, models.map((m) => /*#__PURE__*/ React.createElement(ModelCard, {
|
|
338
|
+
key: m.name,
|
|
339
|
+
model: m
|
|
340
|
+
}))) : /*#__PURE__*/ React.createElement("div", {
|
|
341
|
+
className: "card bg-base-100 shadow-sm border border-base-300"
|
|
342
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
343
|
+
className: "card-body text-center text-base-content/50"
|
|
344
|
+
}, "No models configured")));
|
|
345
|
+
}
|
|
346
|
+
// Log Row with expand support
|
|
347
|
+
function LogRow({ event }) {
|
|
348
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
349
|
+
const hasDetails = event.requestBody || event.responseBody || event.requestHeaders;
|
|
350
|
+
return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement("tr", {
|
|
351
|
+
className: "hover"
|
|
352
|
+
}, /*#__PURE__*/ React.createElement("td", {
|
|
353
|
+
className: "text-xs"
|
|
354
|
+
}, new Date(event.timestamp).toLocaleTimeString()), /*#__PURE__*/ React.createElement("td", null, /*#__PURE__*/ React.createElement("span", {
|
|
355
|
+
className: "badge badge-info badge-xs"
|
|
356
|
+
}, event.method)), /*#__PURE__*/ React.createElement("td", null, /*#__PURE__*/ React.createElement("code", {
|
|
357
|
+
className: "text-xs truncate max-w-48 block",
|
|
358
|
+
title: event.path
|
|
359
|
+
}, event.path)), /*#__PURE__*/ React.createElement("td", {
|
|
360
|
+
className: "text-xs"
|
|
361
|
+
}, event.serverName || event.serverType || "-"), /*#__PURE__*/ React.createElement("td", null, /*#__PURE__*/ React.createElement("span", {
|
|
362
|
+
className: event.status && event.status >= 400 ? "text-error" : "text-success"
|
|
363
|
+
}, event.status || "-")), /*#__PURE__*/ React.createElement("td", {
|
|
364
|
+
className: "text-xs"
|
|
365
|
+
}, event.durationMs != null ? `${event.durationMs}ms` : "-"), /*#__PURE__*/ React.createElement("td", null, hasDetails && /*#__PURE__*/ React.createElement("button", {
|
|
366
|
+
type: "button",
|
|
367
|
+
className: "btn btn-ghost btn-xs",
|
|
368
|
+
onClick: () => setIsExpanded(!isExpanded)
|
|
369
|
+
}, isExpanded ? /*#__PURE__*/ React.createElement(ChevronUp, {
|
|
370
|
+
className: "w-3 h-3"
|
|
371
|
+
}) : /*#__PURE__*/ React.createElement(ChevronDown, {
|
|
372
|
+
className: "w-3 h-3"
|
|
373
|
+
})))), isExpanded && hasDetails && /*#__PURE__*/ React.createElement("tr", null, /*#__PURE__*/ React.createElement("td", {
|
|
374
|
+
colSpan: 7,
|
|
375
|
+
className: "bg-base-200 p-3"
|
|
376
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
377
|
+
className: "space-y-2 text-xs"
|
|
378
|
+
}, event.requestHeaders && /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("div", {
|
|
379
|
+
className: "font-semibold mb-1"
|
|
380
|
+
}, "Request Headers:"), /*#__PURE__*/ React.createElement("pre", {
|
|
381
|
+
className: "bg-base-100 p-2 rounded overflow-auto max-h-24"
|
|
382
|
+
}, JSON.stringify(event.requestHeaders, null, 2))), event.requestBody != null && /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("div", {
|
|
383
|
+
className: "font-semibold mb-1"
|
|
384
|
+
}, "Request Body:"), /*#__PURE__*/ React.createElement("pre", {
|
|
385
|
+
className: "bg-base-100 p-2 rounded overflow-auto max-h-48"
|
|
386
|
+
}, typeof event.requestBody === "string" ? event.requestBody : JSON.stringify(event.requestBody, null, 2))), event.responseBody != null && /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("div", {
|
|
387
|
+
className: "font-semibold mb-1"
|
|
388
|
+
}, "Response Body:"), /*#__PURE__*/ React.createElement("pre", {
|
|
389
|
+
className: "bg-base-100 p-2 rounded overflow-auto max-h-48"
|
|
390
|
+
}, typeof event.responseBody === "string" ? event.responseBody : JSON.stringify(event.responseBody, null, 2))), event.error && /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("div", {
|
|
391
|
+
className: "font-semibold text-error mb-1"
|
|
392
|
+
}, "Error:"), /*#__PURE__*/ React.createElement("pre", {
|
|
393
|
+
className: "bg-error/10 text-error p-2 rounded"
|
|
394
|
+
}, event.error))))));
|
|
395
|
+
}
|
|
396
|
+
// Logs Page (renamed from Audit)
|
|
397
|
+
function LogsPage() {
|
|
398
|
+
const [events, setEvents] = useState([]);
|
|
399
|
+
const [loading, setLoading] = useState(true);
|
|
400
|
+
const [tab, setTab] = useState("all");
|
|
401
|
+
const fetchLogs = () => {
|
|
402
|
+
setLoading(true);
|
|
403
|
+
api.auditList({
|
|
404
|
+
limit: 200
|
|
405
|
+
}).then((data) => {
|
|
406
|
+
setEvents(data.events);
|
|
407
|
+
setLoading(false);
|
|
408
|
+
});
|
|
409
|
+
};
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
fetchLogs();
|
|
412
|
+
const interval = setInterval(fetchLogs, 5000);
|
|
413
|
+
return () => clearInterval(interval);
|
|
414
|
+
}, []);
|
|
415
|
+
const mcpLogs = events.filter((e) => e.path.startsWith("/mcp/"));
|
|
416
|
+
const chatLogs = events.filter((e) => e.path.startsWith("/v1/"));
|
|
417
|
+
const allLogs = events;
|
|
418
|
+
const currentLogs = tab === "all" ? allLogs : tab === "mcp" ? mcpLogs : chatLogs;
|
|
419
|
+
return /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("div", {
|
|
420
|
+
className: "flex items-center justify-between mb-3"
|
|
421
|
+
}, /*#__PURE__*/ React.createElement("h2", {
|
|
422
|
+
className: "text-lg font-semibold text-base-content"
|
|
423
|
+
}, "Logs"), /*#__PURE__*/ React.createElement("button", {
|
|
424
|
+
type: "button",
|
|
425
|
+
className: "btn btn-ghost btn-sm gap-1",
|
|
426
|
+
onClick: fetchLogs,
|
|
427
|
+
disabled: loading
|
|
428
|
+
}, loading ? /*#__PURE__*/ React.createElement("span", {
|
|
429
|
+
className: "loading loading-spinner loading-xs"
|
|
430
|
+
}) : /*#__PURE__*/ React.createElement(RefreshCw, {
|
|
431
|
+
className: "w-4 h-4"
|
|
432
|
+
}), "Refresh")), /*#__PURE__*/ React.createElement("div", {
|
|
433
|
+
className: "tabs tabs-boxed mb-4 bg-base-100 w-fit"
|
|
434
|
+
}, /*#__PURE__*/ React.createElement("button", {
|
|
435
|
+
type: "button",
|
|
436
|
+
className: `tab ${tab === "all" ? "tab-active" : ""}`,
|
|
437
|
+
onClick: () => setTab("all")
|
|
438
|
+
}, "All (", allLogs.length, ")"), /*#__PURE__*/ React.createElement("button", {
|
|
439
|
+
type: "button",
|
|
440
|
+
className: `tab ${tab === "mcp" ? "tab-active" : ""}`,
|
|
441
|
+
onClick: () => setTab("mcp")
|
|
442
|
+
}, "MCP (", mcpLogs.length, ")"), /*#__PURE__*/ React.createElement("button", {
|
|
443
|
+
type: "button",
|
|
444
|
+
className: `tab ${tab === "chat" ? "tab-active" : ""}`,
|
|
445
|
+
onClick: () => setTab("chat")
|
|
446
|
+
}, "Chat (", chatLogs.length, ")")), /*#__PURE__*/ React.createElement("div", {
|
|
447
|
+
className: "overflow-x-auto bg-base-100 rounded-box shadow-sm border border-base-300"
|
|
448
|
+
}, /*#__PURE__*/ React.createElement("table", {
|
|
449
|
+
className: "table table-zebra table-sm"
|
|
450
|
+
}, /*#__PURE__*/ React.createElement("thead", null, /*#__PURE__*/ React.createElement("tr", null, /*#__PURE__*/ React.createElement("th", null, "Time"), /*#__PURE__*/ React.createElement("th", null, "Method"), /*#__PURE__*/ React.createElement("th", null, "Path"), /*#__PURE__*/ React.createElement("th", null, "Server/Type"), /*#__PURE__*/ React.createElement("th", null, "Status"), /*#__PURE__*/ React.createElement("th", null, "Duration"), /*#__PURE__*/ React.createElement("th", null))), /*#__PURE__*/ React.createElement("tbody", null, currentLogs.map((event) => /*#__PURE__*/ React.createElement(LogRow, {
|
|
451
|
+
key: event.id,
|
|
452
|
+
event: event
|
|
453
|
+
})), currentLogs.length === 0 && /*#__PURE__*/ React.createElement("tr", null, /*#__PURE__*/ React.createElement("td", {
|
|
454
|
+
colSpan: 7,
|
|
455
|
+
className: "text-center text-base-content/50"
|
|
456
|
+
}, "No logs yet"))))));
|
|
457
|
+
}
|
|
458
|
+
// Chat Page Wrapper
|
|
459
|
+
function ChatPageWrapper() {
|
|
460
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
461
|
+
className: "card bg-base-100 shadow-sm border border-base-300 h-[calc(100vh-200px)]"
|
|
462
|
+
}, /*#__PURE__*/ React.createElement(ChatPage, null));
|
|
463
|
+
}
|
|
464
|
+
// Inspector Page Wrapper
|
|
465
|
+
function InspectorPageWrapper() {
|
|
466
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
467
|
+
className: "card bg-base-100 shadow-sm border border-base-300 h-[calc(100vh-200px)]"
|
|
468
|
+
}, /*#__PURE__*/ React.createElement(McpInspectorPage, null));
|
|
469
|
+
}
|
|
470
|
+
// Layout
|
|
471
|
+
function Layout({ children }) {
|
|
472
|
+
const [overview, setOverview] = useState(null);
|
|
473
|
+
useEffect(() => {
|
|
474
|
+
api.overview().then(setOverview);
|
|
475
|
+
}, []);
|
|
476
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
477
|
+
className: "min-h-screen bg-base-200"
|
|
478
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
479
|
+
className: "container mx-auto max-w-7xl p-4 md:p-6"
|
|
480
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
481
|
+
className: "flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-6"
|
|
482
|
+
}, /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("h1", {
|
|
483
|
+
className: "text-2xl font-bold text-base-content"
|
|
484
|
+
}, "MCPS Dashboard"), /*#__PURE__*/ React.createElement("p", {
|
|
485
|
+
className: "text-sm text-base-content/60"
|
|
486
|
+
}, overview?.name, " v", overview?.version))), /*#__PURE__*/ React.createElement("nav", {
|
|
487
|
+
className: "tabs tabs-boxed mb-6 bg-base-100"
|
|
488
|
+
}, /*#__PURE__*/ React.createElement(NavLink, {
|
|
489
|
+
to: "/",
|
|
490
|
+
end: true,
|
|
491
|
+
className: ({ isActive }) => `tab ${isActive ? "tab-active" : ""}`
|
|
492
|
+
}, "Overview"), /*#__PURE__*/ React.createElement(NavLink, {
|
|
493
|
+
to: "/servers",
|
|
494
|
+
className: ({ isActive }) => `tab ${isActive ? "tab-active" : ""}`
|
|
495
|
+
}, "Servers"), /*#__PURE__*/ React.createElement(NavLink, {
|
|
496
|
+
to: "/models",
|
|
497
|
+
className: ({ isActive }) => `tab ${isActive ? "tab-active" : ""}`
|
|
498
|
+
}, "Models"), /*#__PURE__*/ React.createElement(NavLink, {
|
|
499
|
+
to: "/logs",
|
|
500
|
+
className: ({ isActive }) => `tab ${isActive ? "tab-active" : ""}`
|
|
501
|
+
}, "Logs"), /*#__PURE__*/ React.createElement(NavLink, {
|
|
502
|
+
to: "/chat",
|
|
503
|
+
className: ({ isActive }) => `tab ${isActive ? "tab-active" : ""}`
|
|
504
|
+
}, "Chat"), /*#__PURE__*/ React.createElement(NavLink, {
|
|
505
|
+
to: "/inspector",
|
|
506
|
+
className: ({ isActive }) => `tab ${isActive ? "tab-active" : ""}`
|
|
507
|
+
}, "Inspector")), children));
|
|
508
|
+
}
|
|
509
|
+
function App() {
|
|
510
|
+
return /*#__PURE__*/ React.createElement(HashRouter, null, /*#__PURE__*/ React.createElement(Layout, null, /*#__PURE__*/ React.createElement(Routes, null, /*#__PURE__*/ React.createElement(Route, {
|
|
511
|
+
path: "/",
|
|
512
|
+
element: /*#__PURE__*/ React.createElement(OverviewPage, null)
|
|
513
|
+
}), /*#__PURE__*/ React.createElement(Route, {
|
|
514
|
+
path: "/servers",
|
|
515
|
+
element: /*#__PURE__*/ React.createElement(ServersPage, null)
|
|
516
|
+
}), /*#__PURE__*/ React.createElement(Route, {
|
|
517
|
+
path: "/models",
|
|
518
|
+
element: /*#__PURE__*/ React.createElement(ModelsPage, null)
|
|
519
|
+
}), /*#__PURE__*/ React.createElement(Route, {
|
|
520
|
+
path: "/logs",
|
|
521
|
+
element: /*#__PURE__*/ React.createElement(LogsPage, null)
|
|
522
|
+
}), /*#__PURE__*/ React.createElement(Route, {
|
|
523
|
+
path: "/chat",
|
|
524
|
+
element: /*#__PURE__*/ React.createElement(ChatPageWrapper, null)
|
|
525
|
+
}), /*#__PURE__*/ React.createElement(Route, {
|
|
526
|
+
path: "/inspector",
|
|
527
|
+
element: /*#__PURE__*/ React.createElement(InspectorPageWrapper, null)
|
|
528
|
+
}), /*#__PURE__*/ React.createElement(Route, {
|
|
529
|
+
path: "*",
|
|
530
|
+
element: /*#__PURE__*/ React.createElement(Navigate, {
|
|
531
|
+
to: "/",
|
|
532
|
+
replace: true
|
|
533
|
+
})
|
|
534
|
+
}))));
|
|
535
|
+
}
|
|
536
|
+
const rootEl = document.getElementById("root");
|
|
537
|
+
if (!rootEl)
|
|
538
|
+
throw new Error("Root element not found");
|
|
539
|
+
const root = createRoot(rootEl);
|
|
540
|
+
root.render(/*#__PURE__*/ React.createElement(App, null));
|
|
541
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/web/main.tsx"],"sourcesContent":["import { ChevronDown, ChevronUp, RefreshCw, Stethoscope } from 'lucide-react';\nimport { useEffect, useState } from 'react';\nimport { createRoot } from 'react-dom/client';\nimport { HashRouter, Routes, Route, NavLink, Navigate } from 'react-router-dom';\nimport type { AuditEvent } from '../contracts';\nimport type { ModelInfo, RequestStats, ServerInfo, ServerTypeInfo, ServiceOverview } from '../contracts/McpsContract';\nimport { ChatPage } from './ChatPage';\nimport { McpInspectorPage } from './McpInspectorPage';\nimport './styles.css';\n\n// Simple API client using fetch\nexport const api = {\n\tasync overview(): Promise<ServiceOverview> {\n\t\tconst res = await fetch('/api/mcps/overview');\n\t\treturn res.json();\n\t},\n\tasync stats(params: { from?: string; to?: string } = {}): Promise<RequestStats> {\n\t\tconst url = new URL('/api/mcps/stats', window.location.origin);\n\t\tif (params.from) url.searchParams.set('from', params.from);\n\t\tif (params.to) url.searchParams.set('to', params.to);\n\t\tconst res = await fetch(url);\n\t\treturn res.json();\n\t},\n\tasync auditList(params: { limit?: number } = {}): Promise<{ events: AuditEvent[]; total: number }> {\n\t\tconst url = new URL('/api/audit', window.location.origin);\n\t\tif (params.limit) url.searchParams.set('limit', String(params.limit));\n\t\tconst res = await fetch(url);\n\t\treturn res.json();\n\t},\n\tasync servers(): Promise<{ servers: ServerInfo[] }> {\n\t\tconst res = await fetch('/api/mcps/servers');\n\t\treturn res.json();\n\t},\n\tasync models(): Promise<{ models: ModelInfo[] }> {\n\t\tconst res = await fetch('/api/mcps/models');\n\t\treturn res.json();\n\t},\n\tasync healthCheck(model: string): Promise<{ ok: boolean; error?: string; durationMs?: number }> {\n\t\tconst start = Date.now();\n\t\ttry {\n\t\t\tconst res = await fetch('/v1/chat/completions', {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel,\n\t\t\t\t\tmessages: [{ role: 'user', content: 'hello' }],\n\t\t\t\t\tmax_tokens: 10,\n\t\t\t\t}),\n\t\t\t});\n\t\t\tconst durationMs = Date.now() - start;\n\t\t\tif (!res.ok) {\n\t\t\t\tconst data = await res.json().catch(() => ({}));\n\t\t\t\treturn { ok: false, error: data.error?.message || `HTTP ${res.status}`, durationMs };\n\t\t\t}\n\t\t\treturn { ok: true, durationMs };\n\t\t} catch (e) {\n\t\t\treturn { ok: false, error: e instanceof Error ? e.message : 'Unknown error', durationMs: Date.now() - start };\n\t\t}\n\t},\n};\n\nfunction getServerTypeBadgeClass(type: string) {\n\tconst classes: Record<string, string> = {\n\t\t'tencent-cls': 'badge-info',\n\t\tsql: 'badge-success',\n\t\tprometheus: 'badge-secondary',\n\t\trelay: 'badge-warning',\n\t};\n\treturn classes[type] || 'badge-primary';\n}\n\n// Header hints for each server type\nconst serverTypeHeaders: Record<string, { required: string[]; optional?: string[] }> = {\n\t'tencent-cls': {\n\t\trequired: ['X-CLS-SECRET-ID', 'X-CLS-SECRET-KEY', 'X-CLS-REGION'],\n\t\toptional: ['X-CLS-ENDPOINT'],\n\t},\n\tsql: {\n\t\trequired: ['X-DB-URL'],\n\t\toptional: ['X-DB-READ-URL', 'X-DB-WRITE-URL'],\n\t},\n\tprometheus: {\n\t\trequired: ['X-SERVICE-URL'],\n\t},\n\trelay: {\n\t\trequired: ['X-MCP-URL'],\n\t\toptional: ['X-MCP-TYPE', 'X-MCP-COMMAND'],\n\t},\n};\n\nfunction ServerTypeCard({ serverType }: { serverType: ServerTypeInfo }) {\n\tconst headers = serverTypeHeaders[serverType.type];\n\treturn (\n\t\t<div className='card bg-base-100 shadow-sm border border-base-300'>\n\t\t\t<div className='card-body p-4'>\n\t\t\t\t<div className='flex items-center gap-2 mb-2'>\n\t\t\t\t\t<span className={`badge ${getServerTypeBadgeClass(serverType.type)}`}>{serverType.type}</span>\n\t\t\t\t</div>\n\t\t\t\t<p className='text-sm text-base-content/70 mb-2'>{serverType.description}</p>\n\t\t\t\t<code className='text-xs bg-base-200 px-2 py-1 rounded block mb-2'>{serverType.dynamicEndpoint}</code>\n\t\t\t\t{headers && (\n\t\t\t\t\t<div className='text-xs'>\n\t\t\t\t\t\t<div className='text-base-content/50 mb-1'>Headers:</div>\n\t\t\t\t\t\t<div className='flex flex-wrap gap-1'>\n\t\t\t\t\t\t\t{headers.required.map((h) => (\n\t\t\t\t\t\t\t\t<code key={h} className='bg-error/10 text-error px-1 rounded'>\n\t\t\t\t\t\t\t\t\t{h}\n\t\t\t\t\t\t\t\t</code>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t{headers.optional?.map((h) => (\n\t\t\t\t\t\t\t\t<code key={h} className='bg-base-200 px-1 rounded'>\n\t\t\t\t\t\t\t\t\t{h}\n\t\t\t\t\t\t\t\t</code>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction ServerCard({ server }: { server: ServerInfo }) {\n\treturn (\n\t\t<div className='card bg-base-100 shadow-sm border border-base-300'>\n\t\t\t<div className='card-body p-4 flex-row justify-between items-center'>\n\t\t\t\t<div>\n\t\t\t\t\t<div className='font-semibold text-base-content'>{server.name}</div>\n\t\t\t\t\t<span className={`badge badge-sm ${getServerTypeBadgeClass(server.type)}`}>{server.type}</span>\n\t\t\t\t</div>\n\t\t\t\t{server.disabled && <span className='badge badge-error badge-sm'>disabled</span>}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction ModelCard({ model }: { model: ModelInfo }) {\n\tconst [checking, setChecking] = useState(false);\n\tconst [result, setResult] = useState<{ ok: boolean; error?: string; durationMs?: number } | null>(null);\n\n\tconst handleCheck = async () => {\n\t\tsetChecking(true);\n\t\tsetResult(null);\n\t\tconst res = await api.healthCheck(model.name);\n\t\tsetResult(res);\n\t\tsetChecking(false);\n\t};\n\n\treturn (\n\t\t<div className='card bg-base-100 shadow-sm border border-base-300'>\n\t\t\t<div className='card-body p-4'>\n\t\t\t\t<div className='flex justify-between items-start'>\n\t\t\t\t\t<div className='font-semibold text-base-content mb-2'>{model.name}</div>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype='button'\n\t\t\t\t\t\tclassName='btn btn-xs btn-ghost'\n\t\t\t\t\t\tonClick={handleCheck}\n\t\t\t\t\t\tdisabled={checking}\n\t\t\t\t\t\ttitle='Health check'\n\t\t\t\t\t>\n\t\t\t\t\t\t{checking ? <span className='loading loading-spinner loading-xs' /> : <Stethoscope className='w-4 h-4' />}\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<div className='flex gap-2 flex-wrap'>\n\t\t\t\t\t{model.adapter && <span className='badge badge-secondary badge-sm'>{model.adapter}</span>}\n\t\t\t\t\t{model.baseUrl && (\n\t\t\t\t\t\t<span className='text-xs text-base-content/50 truncate max-w-48' title={model.baseUrl}>\n\t\t\t\t\t\t\t{model.baseUrl}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t\t{result && (\n\t\t\t\t\t<div className={`mt-2 text-xs ${result.ok ? 'text-success' : 'text-error'}`}>\n\t\t\t\t\t\t{result.ok ? `✓ OK (${result.durationMs}ms)` : `✗ ${result.error}`}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\n// Overview Page\nfunction OverviewPage() {\n\tconst [overview, setOverview] = useState<ServiceOverview | null>(null);\n\tconst [stats, setStats] = useState<RequestStats | null>(null);\n\tconst [loading, setLoading] = useState(true);\n\n\tuseEffect(() => {\n\t\tPromise.all([api.overview(), api.stats()])\n\t\t\t.then(([o, s]) => {\n\t\t\t\tsetOverview(o);\n\t\t\t\tsetStats(s);\n\t\t\t})\n\t\t\t.finally(() => setLoading(false));\n\t}, []);\n\n\tif (loading) return <div className='loading loading-spinner loading-lg mx-auto' />;\n\n\treturn (\n\t\t<div className='space-y-6'>\n\t\t\t{/* Stats Cards */}\n\t\t\t<div className='grid grid-cols-2 md:grid-cols-4 gap-4'>\n\t\t\t\t<div className='stat bg-base-100 rounded-box shadow-sm border border-base-300'>\n\t\t\t\t\t<div className='stat-value text-2xl'>{overview?.servers.length ?? 0}</div>\n\t\t\t\t\t<div className='stat-desc'>Configured Servers</div>\n\t\t\t\t</div>\n\t\t\t\t<div className='stat bg-base-100 rounded-box shadow-sm border border-base-300'>\n\t\t\t\t\t<div className='stat-value text-2xl'>{overview?.models.length ?? 0}</div>\n\t\t\t\t\t<div className='stat-desc'>Model Configs</div>\n\t\t\t\t</div>\n\t\t\t\t<div className='stat bg-base-100 rounded-box shadow-sm border border-base-300'>\n\t\t\t\t\t<div className='stat-value text-2xl'>{stats?.totalRequests ?? 0}</div>\n\t\t\t\t\t<div className='stat-desc'>Total Requests</div>\n\t\t\t\t</div>\n\t\t\t\t<div className='stat bg-base-100 rounded-box shadow-sm border border-base-300'>\n\t\t\t\t\t<div className={`stat-value text-2xl ${(stats?.totalErrors ?? 0) > 0 ? 'text-error' : ''}`}>\n\t\t\t\t\t\t{stats?.totalErrors ?? 0}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className='stat-desc'>Errors</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{/* Supported Server Types */}\n\t\t\t<div>\n\t\t\t\t<h2 className='text-lg font-semibold mb-3 text-base-content'>Supported Server Types</h2>\n\t\t\t\t<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4'>\n\t\t\t\t\t{overview?.serverTypes.map((st) => (\n\t\t\t\t\t\t<ServerTypeCard key={st.type} serverType={st} />\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t\t<div className='mt-3 text-sm text-base-content/50'>\n\t\t\t\t\t<span className='font-medium'>Common Headers:</span>{' '}\n\t\t\t\t\t<code className='bg-base-200 px-1 rounded'>X-MCP-Readonly</code> (TRUE = only readonly tools){' '}\n\t\t\t\t\t<code className='bg-base-200 px-1 rounded'>X-MCP-Include</code>{' '}\n\t\t\t\t\t<code className='bg-base-200 px-1 rounded'>X-MCP-Exclude</code> (glob patterns for tool filtering)\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{/* Configured Servers Preview */}\n\t\t\t{overview && overview.servers.length > 0 && (\n\t\t\t\t<div>\n\t\t\t\t\t<h2 className='text-lg font-semibold mb-3 text-base-content'>\n\t\t\t\t\t\tConfigured Servers ({overview.servers.length})\n\t\t\t\t\t</h2>\n\t\t\t\t\t<div className='grid grid-cols-1 md:grid-cols-2 gap-4'>\n\t\t\t\t\t\t{overview.servers.slice(0, 4).map((s) => (\n\t\t\t\t\t\t\t<ServerCard key={s.name} server={s} />\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t\t{overview.servers.length > 4 && (\n\t\t\t\t\t\t<div className='text-center mt-4'>\n\t\t\t\t\t\t\t<NavLink to='/servers' className='btn btn-ghost btn-sm'>\n\t\t\t\t\t\t\t\tView all {overview.servers.length} servers →\n\t\t\t\t\t\t\t</NavLink>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* Request Stats by Method */}\n\t\t\t{stats && stats.byMethod.length > 0 && (\n\t\t\t\t<div>\n\t\t\t\t\t<h2 className='text-lg font-semibold mb-3 text-base-content'>Requests by Method</h2>\n\t\t\t\t\t<div className='overflow-x-auto bg-base-100 rounded-box shadow-sm border border-base-300'>\n\t\t\t\t\t\t<table className='table table-zebra'>\n\t\t\t\t\t\t\t<thead>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<th>Method</th>\n\t\t\t\t\t\t\t\t\t<th>Count</th>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</thead>\n\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t{stats.byMethod.map((item) => (\n\t\t\t\t\t\t\t\t\t<tr key={item.method}>\n\t\t\t\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t\t\t\t<span className='badge badge-info badge-sm'>{item.method}</span>\n\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t\t<td>{item.count}</td>\n\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t</table>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\n// Servers Page\nfunction ServersPage() {\n\tconst [servers, setServers] = useState<ServerInfo[]>([]);\n\tconst [loading, setLoading] = useState(true);\n\n\tuseEffect(() => {\n\t\tapi.servers().then((data) => {\n\t\t\tsetServers(data.servers);\n\t\t\tsetLoading(false);\n\t\t});\n\t}, []);\n\n\tif (loading) return <div className='loading loading-spinner loading-lg mx-auto' />;\n\n\treturn (\n\t\t<div>\n\t\t\t<h2 className='text-lg font-semibold mb-3 text-base-content'>Configured Servers ({servers.length})</h2>\n\t\t\t{servers.length > 0 ? (\n\t\t\t\t<div className='grid grid-cols-1 md:grid-cols-2 gap-4'>\n\t\t\t\t\t{servers.map((s) => (\n\t\t\t\t\t\t<ServerCard key={s.name} server={s} />\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t) : (\n\t\t\t\t<div className='card bg-base-100 shadow-sm border border-base-300'>\n\t\t\t\t\t<div className='card-body text-center text-base-content/50'>No servers configured</div>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\n// Models Page\nfunction ModelsPage() {\n\tconst [models, setModels] = useState<ModelInfo[]>([]);\n\tconst [loading, setLoading] = useState(true);\n\n\tuseEffect(() => {\n\t\tapi.models().then((data) => {\n\t\t\tsetModels(data.models);\n\t\t\tsetLoading(false);\n\t\t});\n\t}, []);\n\n\tif (loading) return <div className='loading loading-spinner loading-lg mx-auto' />;\n\n\treturn (\n\t\t<div>\n\t\t\t<h2 className='text-lg font-semibold mb-3 text-base-content'>Model Configurations ({models.length})</h2>\n\t\t\t{models.length > 0 ? (\n\t\t\t\t<div className='grid grid-cols-1 md:grid-cols-2 gap-4'>\n\t\t\t\t\t{models.map((m) => (\n\t\t\t\t\t\t<ModelCard key={m.name} model={m} />\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t) : (\n\t\t\t\t<div className='card bg-base-100 shadow-sm border border-base-300'>\n\t\t\t\t\t<div className='card-body text-center text-base-content/50'>No models configured</div>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\n// Log Row with expand support\nfunction LogRow({ event }: { event: AuditEvent }) {\n\tconst [isExpanded, setIsExpanded] = useState(false);\n\n\tconst hasDetails = event.requestBody || event.responseBody || event.requestHeaders;\n\n\treturn (\n\t\t<>\n\t\t\t<tr className='hover'>\n\t\t\t\t<td className='text-xs'>{new Date(event.timestamp).toLocaleTimeString()}</td>\n\t\t\t\t<td>\n\t\t\t\t\t<span className='badge badge-info badge-xs'>{event.method}</span>\n\t\t\t\t</td>\n\t\t\t\t<td>\n\t\t\t\t\t<code className='text-xs truncate max-w-48 block' title={event.path}>\n\t\t\t\t\t\t{event.path}\n\t\t\t\t\t</code>\n\t\t\t\t</td>\n\t\t\t\t<td className='text-xs'>{event.serverName || event.serverType || '-'}</td>\n\t\t\t\t<td>\n\t\t\t\t\t<span className={event.status && event.status >= 400 ? 'text-error' : 'text-success'}>\n\t\t\t\t\t\t{event.status || '-'}\n\t\t\t\t\t</span>\n\t\t\t\t</td>\n\t\t\t\t<td className='text-xs'>{event.durationMs != null ? `${event.durationMs}ms` : '-'}</td>\n\t\t\t\t<td>\n\t\t\t\t\t{hasDetails && (\n\t\t\t\t\t\t<button type='button' className='btn btn-ghost btn-xs' onClick={() => setIsExpanded(!isExpanded)}>\n\t\t\t\t\t\t\t{isExpanded ? <ChevronUp className='w-3 h-3' /> : <ChevronDown className='w-3 h-3' />}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t)}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t\t{isExpanded && hasDetails && (\n\t\t\t\t<tr>\n\t\t\t\t\t<td colSpan={7} className='bg-base-200 p-3'>\n\t\t\t\t\t\t<div className='space-y-2 text-xs'>\n\t\t\t\t\t\t\t{event.requestHeaders && (\n\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t<div className='font-semibold mb-1'>Request Headers:</div>\n\t\t\t\t\t\t\t\t\t<pre className='bg-base-100 p-2 rounded overflow-auto max-h-24'>\n\t\t\t\t\t\t\t\t\t\t{JSON.stringify(event.requestHeaders, null, 2)}\n\t\t\t\t\t\t\t\t\t</pre>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t{event.requestBody != null && (\n\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t<div className='font-semibold mb-1'>Request Body:</div>\n\t\t\t\t\t\t\t\t\t<pre className='bg-base-100 p-2 rounded overflow-auto max-h-48'>\n\t\t\t\t\t\t\t\t\t\t{typeof event.requestBody === 'string'\n\t\t\t\t\t\t\t\t\t\t\t? event.requestBody\n\t\t\t\t\t\t\t\t\t\t\t: JSON.stringify(event.requestBody as object, null, 2)}\n\t\t\t\t\t\t\t\t\t</pre>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t{event.responseBody != null && (\n\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t<div className='font-semibold mb-1'>Response Body:</div>\n\t\t\t\t\t\t\t\t\t<pre className='bg-base-100 p-2 rounded overflow-auto max-h-48'>\n\t\t\t\t\t\t\t\t\t\t{typeof event.responseBody === 'string'\n\t\t\t\t\t\t\t\t\t\t\t? event.responseBody\n\t\t\t\t\t\t\t\t\t\t\t: JSON.stringify(event.responseBody as object, null, 2)}\n\t\t\t\t\t\t\t\t\t</pre>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t{event.error && (\n\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t<div className='font-semibold text-error mb-1'>Error:</div>\n\t\t\t\t\t\t\t\t\t<pre className='bg-error/10 text-error p-2 rounded'>{event.error}</pre>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t)}\n\t\t</>\n\t);\n}\n\n// Logs Page (renamed from Audit)\nfunction LogsPage() {\n\tconst [events, setEvents] = useState<AuditEvent[]>([]);\n\tconst [loading, setLoading] = useState(true);\n\tconst [tab, setTab] = useState<'all' | 'mcp' | 'chat'>('all');\n\n\tconst fetchLogs = () => {\n\t\tsetLoading(true);\n\t\tapi.auditList({ limit: 200 }).then((data) => {\n\t\t\tsetEvents(data.events);\n\t\t\tsetLoading(false);\n\t\t});\n\t};\n\n\tuseEffect(() => {\n\t\tfetchLogs();\n\t\tconst interval = setInterval(fetchLogs, 5000);\n\t\treturn () => clearInterval(interval);\n\t}, []);\n\n\tconst mcpLogs = events.filter((e) => e.path.startsWith('/mcp/'));\n\tconst chatLogs = events.filter((e) => e.path.startsWith('/v1/'));\n\tconst allLogs = events;\n\tconst currentLogs = tab === 'all' ? allLogs : tab === 'mcp' ? mcpLogs : chatLogs;\n\n\treturn (\n\t\t<div>\n\t\t\t<div className='flex items-center justify-between mb-3'>\n\t\t\t\t<h2 className='text-lg font-semibold text-base-content'>Logs</h2>\n\t\t\t\t<button type='button' className='btn btn-ghost btn-sm gap-1' onClick={fetchLogs} disabled={loading}>\n\t\t\t\t\t{loading ? <span className='loading loading-spinner loading-xs' /> : <RefreshCw className='w-4 h-4' />}\n\t\t\t\t\tRefresh\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<div className='tabs tabs-boxed mb-4 bg-base-100 w-fit'>\n\t\t\t\t<button type='button' className={`tab ${tab === 'all' ? 'tab-active' : ''}`} onClick={() => setTab('all')}>\n\t\t\t\t\tAll ({allLogs.length})\n\t\t\t\t</button>\n\t\t\t\t<button type='button' className={`tab ${tab === 'mcp' ? 'tab-active' : ''}`} onClick={() => setTab('mcp')}>\n\t\t\t\t\tMCP ({mcpLogs.length})\n\t\t\t\t</button>\n\t\t\t\t<button type='button' className={`tab ${tab === 'chat' ? 'tab-active' : ''}`} onClick={() => setTab('chat')}>\n\t\t\t\t\tChat ({chatLogs.length})\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<div className='overflow-x-auto bg-base-100 rounded-box shadow-sm border border-base-300'>\n\t\t\t\t<table className='table table-zebra table-sm'>\n\t\t\t\t\t<thead>\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t<th>Time</th>\n\t\t\t\t\t\t\t<th>Method</th>\n\t\t\t\t\t\t\t<th>Path</th>\n\t\t\t\t\t\t\t<th>Server/Type</th>\n\t\t\t\t\t\t\t<th>Status</th>\n\t\t\t\t\t\t\t<th>Duration</th>\n\t\t\t\t\t\t\t<th></th>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</thead>\n\t\t\t\t\t<tbody>\n\t\t\t\t\t\t{currentLogs.map((event) => (\n\t\t\t\t\t\t\t<LogRow key={event.id} event={event} />\n\t\t\t\t\t\t))}\n\t\t\t\t\t\t{currentLogs.length === 0 && (\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td colSpan={7} className='text-center text-base-content/50'>\n\t\t\t\t\t\t\t\t\tNo logs yet\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</tbody>\n\t\t\t\t</table>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\n// Chat Page Wrapper\nfunction ChatPageWrapper() {\n\treturn (\n\t\t<div className='card bg-base-100 shadow-sm border border-base-300 h-[calc(100vh-200px)]'>\n\t\t\t<ChatPage />\n\t\t</div>\n\t);\n}\n\n// Inspector Page Wrapper\nfunction InspectorPageWrapper() {\n\treturn (\n\t\t<div className='card bg-base-100 shadow-sm border border-base-300 h-[calc(100vh-200px)]'>\n\t\t\t<McpInspectorPage />\n\t\t</div>\n\t);\n}\n\n// Layout\nfunction Layout({ children }: { children: React.ReactNode }) {\n\tconst [overview, setOverview] = useState<ServiceOverview | null>(null);\n\n\tuseEffect(() => {\n\t\tapi.overview().then(setOverview);\n\t}, []);\n\n\treturn (\n\t\t<div className='min-h-screen bg-base-200'>\n\t\t\t<div className='container mx-auto max-w-7xl p-4 md:p-6'>\n\t\t\t\t{/* Header */}\n\t\t\t\t<div className='flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-6'>\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<h1 className='text-2xl font-bold text-base-content'>MCPS Dashboard</h1>\n\t\t\t\t\t\t<p className='text-sm text-base-content/60'>\n\t\t\t\t\t\t\t{overview?.name} v{overview?.version}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\n\t\t\t\t{/* Navigation */}\n\t\t\t\t<nav className='tabs tabs-boxed mb-6 bg-base-100'>\n\t\t\t\t\t<NavLink to='/' end className={({ isActive }) => `tab ${isActive ? 'tab-active' : ''}`}>\n\t\t\t\t\t\tOverview\n\t\t\t\t\t</NavLink>\n\t\t\t\t\t<NavLink to='/servers' className={({ isActive }) => `tab ${isActive ? 'tab-active' : ''}`}>\n\t\t\t\t\t\tServers\n\t\t\t\t\t</NavLink>\n\t\t\t\t\t<NavLink to='/models' className={({ isActive }) => `tab ${isActive ? 'tab-active' : ''}`}>\n\t\t\t\t\t\tModels\n\t\t\t\t\t</NavLink>\n\t\t\t\t\t<NavLink to='/logs' className={({ isActive }) => `tab ${isActive ? 'tab-active' : ''}`}>\n\t\t\t\t\t\tLogs\n\t\t\t\t\t</NavLink>\n\t\t\t\t\t<NavLink to='/chat' className={({ isActive }) => `tab ${isActive ? 'tab-active' : ''}`}>\n\t\t\t\t\t\tChat\n\t\t\t\t\t</NavLink>\n\t\t\t\t\t<NavLink to='/inspector' className={({ isActive }) => `tab ${isActive ? 'tab-active' : ''}`}>\n\t\t\t\t\t\tInspector\n\t\t\t\t\t</NavLink>\n\t\t\t\t</nav>\n\n\t\t\t\t{/* Content */}\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction App() {\n\treturn (\n\t\t<HashRouter>\n\t\t\t<Layout>\n\t\t\t\t<Routes>\n\t\t\t\t\t<Route path='/' element={<OverviewPage />} />\n\t\t\t\t\t<Route path='/servers' element={<ServersPage />} />\n\t\t\t\t\t<Route path='/models' element={<ModelsPage />} />\n\t\t\t\t\t<Route path='/logs' element={<LogsPage />} />\n\t\t\t\t\t<Route path='/chat' element={<ChatPageWrapper />} />\n\t\t\t\t\t<Route path='/inspector' element={<InspectorPageWrapper />} />\n\t\t\t\t\t<Route path='*' element={<Navigate to='/' replace />} />\n\t\t\t\t</Routes>\n\t\t\t</Layout>\n\t\t</HashRouter>\n\t);\n}\n\nconst rootEl = document.getElementById('root');\nif (!rootEl) throw new Error('Root element not found');\nconst root = createRoot(rootEl);\nroot.render(<App />);\n"],"names":["ChevronDown","ChevronUp","RefreshCw","Stethoscope","useEffect","useState","createRoot","HashRouter","Routes","Route","NavLink","Navigate","ChatPage","McpInspectorPage","api","overview","res","fetch","json","stats","params","url","URL","window","location","origin","from","searchParams","set","to","auditList","limit","String","servers","models","healthCheck","model","start","Date","now","method","headers","body","JSON","stringify","messages","role","content","max_tokens","durationMs","ok","data","catch","error","message","status","e","Error","getServerTypeBadgeClass","type","classes","sql","prometheus","relay","serverTypeHeaders","required","optional","ServerTypeCard","serverType","div","className","span","p","description","code","dynamicEndpoint","map","h","key","ServerCard","server","name","disabled","ModelCard","checking","setChecking","result","setResult","handleCheck","button","onClick","title","adapter","baseUrl","OverviewPage","setOverview","setStats","loading","setLoading","Promise","all","then","o","s","finally","length","totalRequests","totalErrors","h2","serverTypes","st","slice","byMethod","table","thead","tr","th","tbody","item","td","count","ServersPage","setServers","ModelsPage","setModels","m","LogRow","event","isExpanded","setIsExpanded","hasDetails","requestBody","responseBody","requestHeaders","timestamp","toLocaleTimeString","path","serverName","colSpan","pre","LogsPage","events","setEvents","tab","setTab","fetchLogs","interval","setInterval","clearInterval","mcpLogs","filter","startsWith","chatLogs","allLogs","currentLogs","id","ChatPageWrapper","InspectorPageWrapper","Layout","children","h1","version","nav","end","isActive","App","element","replace","rootEl","document","getElementById","root","render"],"mappings":"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"}
|