main
html 301 lines 10.4 KB
Raw
1 <html>
2
3 <head>
4 <title>MCP Scanner</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-scan-modal" x-create="$store.mcpServersStore.onScanModalOpen()" x-destroy="$store.mcpServersStore.scanCleanup()">
15 <div class="scan-field">
16 <label>Draft MCP server</label>
17 <textarea class="scan-target" x-model="$store.mcpServersStore.scanTargetJson" readonly></textarea>
18 </div>
19
20 <div class="scan-field">
21 <label>Security Checks</label>
22 <div class="scan-checks">
23 <template x-for="[key, meta] of Object.entries($store.mcpServersStore.scanChecksMeta)" :key="key">
24 <label>
25 <input type="checkbox" x-model="$store.mcpServersStore.scanChecks[key]" @change="$store.mcpServersStore.buildScanPrompt()" />
26 <span x-text="meta.label"></span>
27 </label>
28 </template>
29 </div>
30 </div>
31
32 <div class="scan-field">
33 <label>Inspection Options</label>
34 <div class="scan-checks">
35 <label>
36 <input type="checkbox" x-model="$store.mcpServersStore.scanOptions.inspectRuntime" @change="$store.mcpServersStore.buildScanPrompt()" />
37 <span>Inspect runtime tools</span>
38 </label>
39 <label x-show="$store.mcpServersStore.scanServer?.url || $store.mcpServersStore.scanServer?.serverUrl">
40 <input type="checkbox" x-model="$store.mcpServersStore.scanOptions.allowRemoteNetwork" @change="$store.mcpServersStore.buildScanPrompt()" />
41 <span>Allow remote network inspection</span>
42 </label>
43 <label x-show="$store.mcpServersStore.scanServer?.command">
44 <input type="checkbox" x-model="$store.mcpServersStore.scanOptions.allowLocalExecution" @change="$store.mcpServersStore.buildScanPrompt()" />
45 <span>Allow local command execution</span>
46 </label>
47 </div>
48 </div>
49
50 <div class="scan-field">
51 <label>Agent Prompt <span>(editable)</span></label>
52 <textarea class="scan-prompt" x-model="$store.mcpServersStore.scanPrompt"></textarea>
53 </div>
54
55 <div class="scan-actions">
56 <button type="button" class="button" @click="$store.mcpServersStore.copyScanPrompt()">Copy Prompt</button>
57 <button type="button" class="button" :disabled="$store.mcpServersStore.scanLoading" @click="$store.mcpServersStore.runConfigInspection()">
58 <span x-show="$store.mcpServersStore.scanLoading"><span class="scan-spinner"></span>Inspecting</span>
59 <span x-show="!$store.mcpServersStore.scanLoading">Run Config Inspection</span>
60 </button>
61 <button type="button" class="button confirm" :disabled="$store.mcpServersStore.agentScanning" @click="$store.mcpServersStore.runAgentScan()">
62 <span x-show="$store.mcpServersStore.agentScanning"><span class="scan-spinner"></span>Scanning</span>
63 <span x-show="!$store.mcpServersStore.agentScanning">Run Scan</span>
64 </button>
65 <button type="button" class="button" x-show="$store.mcpServersStore.scanCtxId" @click="$store.mcpServersStore.openScanChatInNewWindow()">
66 Open in Chat
67 </button>
68 </div>
69
70 <div class="mcp-scan-result" x-show="$store.mcpServersStore.scanResult">
71 <div class="mcp-scan-heading">
72 <x-icon aria-hidden="true"
73 :name="$store.mcpServersStore.scanResult?.risk_level === 'ok' ? 'verified' : ($store.mcpServersStore.scanResult?.risk_level === 'error' ? 'error' : 'warning')"></x-icon>
74 <span x-text="$store.mcpServersStore.scanRiskLabel"></span>
75 </div>
76 <div class="mcp-scan-warnings">
77 <template x-for="warning in $store.mcpServersStore.scanWarnings" :key="warning.title + warning.message">
78 <div class="mcp-scan-warning" :class="warning.level">
79 <strong x-text="warning.title"></strong>
80 <span x-text="warning.message"></span>
81 </div>
82 </template>
83 </div>
84 </div>
85
86 <div x-show="$store.mcpServersStore.scanOutput" class="scan-output">
87 <label>Scan Results</label>
88 <div class="scan-output-html" x-html="$store.mcpServersStore.renderedScanOutput"></div>
89 </div>
90 </div>
91 </template>
92 </div>
93
94 <style>
95 .mcp-scan-modal {
96 display: flex;
97 flex-direction: column;
98 gap: 1rem;
99 padding: 0.5rem;
100 color: var(--color-text);
101 font-family: var(--font-family-main);
102 }
103
104 .scan-field {
105 display: flex;
106 flex-direction: column;
107 gap: 0.35rem;
108 }
109
110 .scan-field label,
111 .scan-output label {
112 color: var(--color-text-muted);
113 font-size: 0.85rem;
114 font-weight: 600;
115 }
116
117 .scan-field > label span {
118 font-weight: 400;
119 opacity: 0.7;
120 }
121
122 .scan-field textarea {
123 width: 100%;
124 box-sizing: border-box;
125 border: 1px solid var(--color-border);
126 border-radius: 6px;
127 background: var(--color-panel);
128 color: var(--color-text);
129 padding: 0.5rem 0.75rem;
130 font-family: var(--font-family-code);
131 font-size: 0.82rem;
132 line-height: 1.45;
133 resize: vertical;
134 }
135
136 .scan-field textarea:focus {
137 border-color: var(--color-highlight);
138 outline: none;
139 }
140
141 .scan-target {
142 min-height: 7rem;
143 }
144
145 .scan-prompt {
146 min-height: 18rem;
147 }
148
149 .scan-checks {
150 display: flex;
151 flex-wrap: wrap;
152 gap: 0.5rem 1.25rem;
153 }
154
155 .scan-checks label {
156 display: inline-flex;
157 align-items: center;
158 gap: 0.35rem;
159 color: var(--color-text);
160 cursor: pointer;
161 font-size: 0.85rem;
162 user-select: none;
163 }
164
165 .scan-checks input[type="checkbox"] {
166 accent-color: var(--color-highlight);
167 }
168
169 .scan-actions {
170 display: flex;
171 flex-wrap: wrap;
172 gap: 0.5rem;
173 }
174
175 .mcp-scan-modal .button {
176 display: inline-flex;
177 align-items: center;
178 justify-content: center;
179 gap: 0.35rem;
180 min-height: 2rem;
181 padding: 0.35rem 0.75rem;
182 border: 1px solid var(--color-border);
183 border-radius: 6px;
184 background: var(--color-panel);
185 color: var(--color-text);
186 cursor: pointer;
187 }
188
189 .mcp-scan-modal .button.confirm {
190 border-color: color-mix(in srgb, var(--color-highlight) 65%, var(--color-border));
191 background: var(--color-highlight);
192 color: #fff;
193 }
194
195 .mcp-scan-modal .button:disabled {
196 opacity: 0.55;
197 cursor: not-allowed;
198 }
199
200 .mcp-scan-result {
201 border: 1px solid var(--color-border);
202 border-radius: 8px;
203 background: color-mix(in srgb, var(--color-panel) 88%, transparent);
204 padding: 0.75rem;
205 }
206
207 .mcp-scan-heading {
208 display: flex;
209 align-items: center;
210 gap: 0.4rem;
211 font-weight: 700;
212 }
213
214 .mcp-scan-warnings {
215 display: flex;
216 flex-direction: column;
217 gap: 0.4rem;
218 margin-top: 0.65rem;
219 }
220
221 .mcp-scan-warning {
222 display: flex;
223 flex-direction: column;
224 gap: 0.12rem;
225 padding: 0.55rem 0.65rem;
226 border-left: 3px solid var(--color-border);
227 border-radius: 5px;
228 background: var(--color-input);
229 font-size: 0.82rem;
230 }
231
232 .mcp-scan-warning.warning {
233 border-left-color: var(--color-warning-text);
234 }
235
236 .mcp-scan-warning.error {
237 border-left-color: var(--color-error-text);
238 }
239
240 .scan-output {
241 border-top: 1px solid var(--color-border);
242 padding-top: 1rem;
243 }
244
245 .scan-output-html {
246 line-height: 1.5;
247 }
248
249 .scan-output-html table {
250 width: 100%;
251 margin: 0.75rem 0;
252 border-collapse: collapse;
253 }
254
255 .scan-output-html th,
256 .scan-output-html td {
257 border: 1px solid var(--color-border);
258 padding: 0.4rem 0.6rem;
259 text-align: left;
260 font-size: 0.85rem;
261 }
262
263 .scan-output-html th {
264 background: var(--color-panel);
265 font-weight: 600;
266 }
267
268 .scan-output-html pre {
269 overflow-x: auto;
270 border: 1px solid var(--color-border);
271 border-radius: 6px;
272 background: var(--color-panel);
273 padding: 0.75rem;
274 }
275
276 .scan-output-html code {
277 font-size: 0.8rem;
278 }
279
280 .scan-spinner {
281 display: inline-block;
282 width: 1em;
283 height: 1em;
284 margin-right: 0.4em;
285 border: 2px solid var(--color-border);
286 border-top-color: currentColor;
287 border-radius: 50%;
288 vertical-align: middle;
289 animation: scan-spin 0.6s linear infinite;
290 }
291
292 @keyframes scan-spin {
293 to {
294 transform: rotate(360deg);
295 }
296 }
297 </style>
298
299 </body>
300
301 </html>