| 1 | <html> |
| 2 | |
| 3 | <head> |
| 4 | <title>MCP Server Detail</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-tools-modal"> |
| 15 | <div class="mcp-tools-header"> |
| 16 | <div class="mcp-tools-heading"> |
| 17 | <h3 x-text="$store.mcpServersStore.serverDetail?.name || 'MCP tools'"></h3> |
| 18 | <p x-text="$store.mcpServersStore.serverDetail?.description || 'Tools exposed by this MCP server.'"></p> |
| 19 | </div> |
| 20 | <div class="mcp-tools-actions"> |
| 21 | <span class="mcp-tools-count" x-text="$store.mcpServersStore.serverDetailToolsCountLabel"></span> |
| 22 | <button type="button" class="button confirm" :disabled="$store.mcpServersStore.applying" @click="$store.mcpServersStore.applyNow()"> |
| 23 | <x-icon aria-hidden="true" name="check"></x-icon> |
| 24 | <span x-text="$store.mcpServersStore.applying ? 'Applying' : 'Apply'"></span> |
| 25 | </button> |
| 26 | </div> |
| 27 | </div> |
| 28 | |
| 29 | <label class="mcp-tool-search"> |
| 30 | <x-icon aria-hidden="true" name="search"></x-icon> |
| 31 | <input type="search" x-model.debounce.150ms="$store.mcpServersStore.toolSearch" placeholder="Search tools" /> |
| 32 | <button type="button" class="mcp-tool-search-clear" |
| 33 | x-show="$store.mcpServersStore.toolSearch" |
| 34 | @click="$store.mcpServersStore.clearToolSearch()" |
| 35 | title="Clear search"> |
| 36 | <x-icon aria-hidden="true" name="close"></x-icon> |
| 37 | </button> |
| 38 | </label> |
| 39 | |
| 40 | <div class="tools-container"> |
| 41 | <template x-if="$store.mcpServersStore.serverDetailTools.length === 0"> |
| 42 | <div class="mcp-tools-empty">No tools exposed by this server.</div> |
| 43 | </template> |
| 44 | <template x-if="$store.mcpServersStore.serverDetailTools.length > 0 && $store.mcpServersStore.filteredServerDetailTools.length === 0"> |
| 45 | <div class="mcp-tools-empty">No tools match this search.</div> |
| 46 | </template> |
| 47 | <template x-for="tool in $store.mcpServersStore.filteredServerDetailTools" :key="tool.name"> |
| 48 | <div class="tool-item"> |
| 49 | <div class="tool-header"> |
| 50 | <div> |
| 51 | <h4 x-text="tool.name"></h4> |
| 52 | <p class="tool-description" x-text="tool.description || 'No description provided.'"></p> |
| 53 | </div> |
| 54 | <div class="mcp-tool-toggle-group"> |
| 55 | <label class="toggle plugin-status-toggle mcp-tool-toggle" |
| 56 | :class="{ 'disabled-appearance': !$store.mcpServersStore.canConfigureServerTools($store.mcpServersStore.serverDetail?.name) }" |
| 57 | :title="!$store.mcpServersStore.canConfigureServerTools($store.mcpServersStore.serverDetail?.name) ? 'Add this inherited server to the current config before changing tools' : ($store.mcpServersStore.isServerToolEnabled($store.mcpServersStore.serverDetail?.name, tool.name) ? 'Disable tool' : 'Enable tool')"> |
| 58 | <input type="checkbox" |
| 59 | :checked="$store.mcpServersStore.isServerToolEnabled($store.mcpServersStore.serverDetail?.name, tool.name)" |
| 60 | :disabled="!$store.mcpServersStore.canConfigureServerTools($store.mcpServersStore.serverDetail?.name)" |
| 61 | @change="$store.mcpServersStore.toggleServerTool($store.mcpServersStore.serverDetail?.name, tool.name, $event.target.checked)" |
| 62 | @click.stop> |
| 63 | <span class="toggler"></span> |
| 64 | </label> |
| 65 | <span class="plugin-status-text" |
| 66 | x-text="$store.mcpServersStore.isServerToolEnabled($store.mcpServersStore.serverDetail?.name, tool.name) ? 'ON' : 'OFF'"></span> |
| 67 | </div> |
| 68 | </div> |
| 69 | |
| 70 | <template x-if="tool.input_schema?.properties"> |
| 71 | <div class="tool-properties"> |
| 72 | <p class="properties-title">Properties:</p> |
| 73 | <ul> |
| 74 | <template x-for="(prop, propName) in tool.input_schema.properties" |
| 75 | :key="propName"> |
| 76 | <li> |
| 77 | <span class="prop-name" x-text="propName"></span>: |
| 78 | <span class="prop-type" x-text="prop.type || 'any'"></span> |
| 79 | <template x-if="prop.description"> |
| 80 | <span class="prop-desc" x-text="' - ' + prop.description"></span> |
| 81 | </template> |
| 82 | </li> |
| 83 | </template> |
| 84 | </ul> |
| 85 | </div> |
| 86 | </template> |
| 87 | </div> |
| 88 | </template> |
| 89 | </div> |
| 90 | </div> |
| 91 | </template> |
| 92 | </div> |
| 93 | |
| 94 | <style> |
| 95 | .mcp-tools-modal { |
| 96 | display: flex; |
| 97 | flex-direction: column; |
| 98 | gap: 1rem; |
| 99 | color: var(--color-text); |
| 100 | font-family: var(--font-family-main); |
| 101 | } |
| 102 | |
| 103 | .mcp-tools-header { |
| 104 | display: flex; |
| 105 | justify-content: space-between; |
| 106 | align-items: flex-start; |
| 107 | gap: 1rem; |
| 108 | } |
| 109 | |
| 110 | .mcp-tools-heading { |
| 111 | min-width: 0; |
| 112 | } |
| 113 | |
| 114 | .mcp-tools-heading h3, |
| 115 | .mcp-tools-heading p, |
| 116 | .tool-item h4, |
| 117 | .tool-description, |
| 118 | .properties-title { |
| 119 | margin: 0; |
| 120 | } |
| 121 | |
| 122 | .mcp-tools-heading h3 { |
| 123 | font-size: 1.15rem; |
| 124 | line-height: 1.2; |
| 125 | } |
| 126 | |
| 127 | .mcp-tools-heading p, |
| 128 | .tool-description, |
| 129 | .mcp-tools-empty { |
| 130 | color: var(--color-text-muted); |
| 131 | font-size: 0.88rem; |
| 132 | line-height: 1.4; |
| 133 | } |
| 134 | |
| 135 | .mcp-tools-actions { |
| 136 | display: flex; |
| 137 | align-items: center; |
| 138 | justify-content: flex-end; |
| 139 | gap: 0.5rem; |
| 140 | flex-wrap: wrap; |
| 141 | } |
| 142 | |
| 143 | .mcp-tools-modal .button { |
| 144 | display: inline-flex; |
| 145 | align-items: center; |
| 146 | justify-content: center; |
| 147 | gap: 0.35rem; |
| 148 | min-height: 2rem; |
| 149 | padding: 0.35rem 0.65rem; |
| 150 | border: 1px solid var(--color-border); |
| 151 | border-radius: 6px; |
| 152 | background: var(--color-panel); |
| 153 | color: var(--color-text); |
| 154 | cursor: pointer; |
| 155 | } |
| 156 | |
| 157 | .mcp-tools-modal .button.confirm { |
| 158 | border-color: color-mix(in srgb, var(--color-highlight) 65%, var(--color-border)); |
| 159 | background: var(--color-highlight); |
| 160 | color: #fff; |
| 161 | } |
| 162 | |
| 163 | .mcp-tools-modal .button:disabled { |
| 164 | opacity: 0.55; |
| 165 | cursor: not-allowed; |
| 166 | } |
| 167 | |
| 168 | .mcp-tools-count { |
| 169 | display: inline-flex; |
| 170 | align-items: center; |
| 171 | min-height: 1.45rem; |
| 172 | padding: 0.15rem 0.45rem; |
| 173 | border: 1px solid var(--color-border); |
| 174 | border-radius: 999px; |
| 175 | color: var(--color-text-muted); |
| 176 | font-size: 0.72rem; |
| 177 | font-weight: 600; |
| 178 | } |
| 179 | |
| 180 | .mcp-tool-search { |
| 181 | display: flex; |
| 182 | align-items: center; |
| 183 | gap: 0.35rem; |
| 184 | min-height: 2.25rem; |
| 185 | padding: 0.25rem 0.5rem; |
| 186 | border: 1px solid var(--color-border); |
| 187 | border-radius: 7px; |
| 188 | background: var(--color-input); |
| 189 | color: var(--color-text-muted); |
| 190 | } |
| 191 | |
| 192 | .mcp-tool-search input { |
| 193 | width: 100%; |
| 194 | min-width: 0; |
| 195 | border: 0; |
| 196 | background: transparent; |
| 197 | color: var(--color-text); |
| 198 | outline: none; |
| 199 | padding: 0.2rem; |
| 200 | } |
| 201 | |
| 202 | .mcp-tool-search input::-webkit-search-cancel-button, |
| 203 | .mcp-tool-search input::-webkit-search-decoration { |
| 204 | -webkit-appearance: none; |
| 205 | appearance: none; |
| 206 | display: none; |
| 207 | } |
| 208 | |
| 209 | .mcp-tool-search-clear { |
| 210 | display: inline-flex; |
| 211 | align-items: center; |
| 212 | justify-content: center; |
| 213 | width: 1.45rem; |
| 214 | height: 1.45rem; |
| 215 | border: 0; |
| 216 | border-radius: 5px; |
| 217 | background: transparent; |
| 218 | color: var(--color-text-muted); |
| 219 | cursor: pointer; |
| 220 | } |
| 221 | |
| 222 | .mcp-tools-modal .material-symbols-outlined { |
| 223 | font-size: 1.05rem; |
| 224 | line-height: 1; |
| 225 | } |
| 226 | |
| 227 | .tools-container { |
| 228 | display: flex; |
| 229 | flex-direction: column; |
| 230 | gap: 0.75rem; |
| 231 | } |
| 232 | |
| 233 | .tool-item { |
| 234 | padding: 0.85rem; |
| 235 | border: 1px solid var(--color-border); |
| 236 | border-radius: 8px; |
| 237 | background: color-mix(in srgb, var(--color-panel) 88%, transparent); |
| 238 | } |
| 239 | |
| 240 | .tool-header { |
| 241 | display: flex; |
| 242 | align-items: flex-start; |
| 243 | justify-content: space-between; |
| 244 | gap: 1rem; |
| 245 | } |
| 246 | |
| 247 | .tool-item h4 { |
| 248 | font-size: 1rem; |
| 249 | line-height: 1.25; |
| 250 | overflow-wrap: anywhere; |
| 251 | } |
| 252 | |
| 253 | .tool-description { |
| 254 | margin-top: 0.25rem; |
| 255 | overflow-wrap: anywhere; |
| 256 | } |
| 257 | |
| 258 | .mcp-tool-toggle-group { |
| 259 | display: inline-flex; |
| 260 | align-items: center; |
| 261 | gap: 0.45rem; |
| 262 | min-width: 5.2rem; |
| 263 | flex: 0 0 auto; |
| 264 | } |
| 265 | |
| 266 | .mcp-tool-toggle { |
| 267 | width: 48px; |
| 268 | height: 28px; |
| 269 | flex: 0 0 48px; |
| 270 | } |
| 271 | |
| 272 | .mcp-tool-toggle .toggler { |
| 273 | border-radius: 999px; |
| 274 | } |
| 275 | |
| 276 | .mcp-tool-toggle .toggler:before { |
| 277 | width: 20px; |
| 278 | height: 20px; |
| 279 | left: 4px; |
| 280 | bottom: 4px; |
| 281 | } |
| 282 | |
| 283 | .mcp-tool-toggle input:checked + .toggler:before { |
| 284 | transform: translateX(20px); |
| 285 | } |
| 286 | |
| 287 | .mcp-tool-toggle.disabled-appearance { |
| 288 | opacity: 0.6; |
| 289 | } |
| 290 | |
| 291 | .mcp-tool-toggle-group .plugin-status-text { |
| 292 | color: var(--color-text-muted); |
| 293 | font-size: 0.76rem; |
| 294 | font-weight: 700; |
| 295 | min-width: 1.7rem; |
| 296 | } |
| 297 | |
| 298 | .tool-properties { |
| 299 | margin-top: 0.8rem; |
| 300 | padding: 0.75rem; |
| 301 | border: 1px solid var(--color-border); |
| 302 | background: var(--color-input); |
| 303 | border-radius: 6px; |
| 304 | } |
| 305 | |
| 306 | .properties-title { |
| 307 | font-weight: 600; |
| 308 | margin-bottom: 0.5rem; |
| 309 | } |
| 310 | |
| 311 | .tool-properties ul { |
| 312 | margin: 0; |
| 313 | padding-left: 1.5em; |
| 314 | } |
| 315 | |
| 316 | .tool-properties li { |
| 317 | margin-bottom: 0.3em; |
| 318 | } |
| 319 | |
| 320 | .prop-name { |
| 321 | font-weight: 600; |
| 322 | color: var(--color-highlight); |
| 323 | } |
| 324 | |
| 325 | .prop-type { |
| 326 | color: var(--color-text-muted); |
| 327 | font-style: italic; |
| 328 | } |
| 329 | |
| 330 | .prop-desc { |
| 331 | color: var(--color-text); |
| 332 | } |
| 333 | |
| 334 | .mcp-tools-empty { |
| 335 | padding: 0.9rem; |
| 336 | text-align: center; |
| 337 | border: 1px solid var(--color-border); |
| 338 | border-radius: 8px; |
| 339 | background: color-mix(in srgb, var(--color-panel) 88%, transparent); |
| 340 | } |
| 341 | |
| 342 | @media (max-width: 760px) { |
| 343 | .mcp-tools-header, |
| 344 | .tool-header { |
| 345 | flex-direction: column; |
| 346 | align-items: stretch; |
| 347 | } |
| 348 | |
| 349 | .mcp-tools-actions { |
| 350 | justify-content: flex-start; |
| 351 | } |
| 352 | } |
| 353 | </style> |
| 354 | |
| 355 | </body> |
| 356 | |
| 357 | </html> |