|
25 | 25 | import { twMerge } from 'tailwind-merge'; |
26 | 26 | import McpServerInfoAndTools from './McpServerInfoAndTools.svelte'; |
27 | 27 | import PageLoading from '../PageLoading.svelte'; |
| 28 | + import { EventStreamService } from '$lib/services/admin/eventstream.svelte'; |
28 | 29 |
|
29 | 30 | type Entry = MCPCatalogEntry & { |
30 | 31 | categories: string[]; // categories for the entry |
|
101 | 102 | let launching = $state(false); |
102 | 103 | let launchError = $state<string>(); |
103 | 104 | let launchProgress = $state<number>(0); |
| 105 | + let launchLogsEventStream = $state<EventStreamService<string>>(); |
| 106 | + let launchLogs = $state<string[]>([]); |
| 107 | + let relaunching = $state(false); |
104 | 108 |
|
105 | 109 | let deletingInstance = $state<MCPServerInstance>(); |
106 | 110 | let deletingServer = $state<MCPCatalogServer>(); |
|
250 | 254 | deletingServer = undefined; |
251 | 255 | } |
252 | 256 |
|
253 | | - async function handleLaunchCatalogEntry(entry: Entry) { |
| 257 | + function listLaunchLogs(mcpServerId: string) { |
| 258 | + launchLogsEventStream = new EventStreamService<string>(); |
| 259 | + launchLogsEventStream.connect(`/api/mcp-servers/${mcpServerId}/logs`, { |
| 260 | + onMessage: (data) => { |
| 261 | + launchLogs = [...launchLogs, data]; |
| 262 | + } |
| 263 | + }); |
| 264 | + } |
| 265 | +
|
| 266 | + async function handleLaunchCatalogEntry(entry: Entry, retryingServer?: MCPCatalogServer) { |
254 | 267 | if (!entry.manifest) { |
255 | 268 | console.error('No server manifest found'); |
256 | 269 | return; |
257 | 270 | } |
258 | 271 |
|
| 272 | + if (launchLogsEventStream) { |
| 273 | + // reset launch logs |
| 274 | + launchLogsEventStream.disconnect(); |
| 275 | + launchLogsEventStream = undefined; |
| 276 | + launchLogs = []; |
| 277 | + } |
| 278 | +
|
259 | 279 | launchError = undefined; |
260 | 280 | launchProgress = 0; |
261 | 281 | launching = true; |
|
279 | 299 | const aliasToUse = configureForm?.name || getUniqueAlias(serverName); |
280 | 300 |
|
281 | 301 | let response: MCPCatalogServer | undefined = undefined; |
282 | | - try { |
283 | | - response = await ChatService.createSingleOrRemoteMcpServer({ |
284 | | - catalogEntryID: entry.id, |
285 | | - manifest: url ? { remoteConfig: { url } } : {}, |
286 | | - alias: aliasToUse |
287 | | - }); |
288 | | - } catch (err) { |
289 | | - launchError = err instanceof Error ? err.message : 'An unknown error occurred'; |
| 302 | + if (!retryingServer) { |
| 303 | + try { |
| 304 | + response = await ChatService.createSingleOrRemoteMcpServer({ |
| 305 | + catalogEntryID: entry.id, |
| 306 | + manifest: url ? { remoteConfig: { url } } : {}, |
| 307 | + alias: aliasToUse |
| 308 | + }); |
| 309 | + } catch (err) { |
| 310 | + launchError = err instanceof Error ? err.message : 'An unknown error occurred'; |
| 311 | + } |
| 312 | + } else { |
| 313 | + response = retryingServer; |
290 | 314 | } |
291 | 315 |
|
292 | 316 | if (response) { |
|
301 | 325 | configuredResponse.id |
302 | 326 | ); |
303 | 327 | if (!launchResponse.success) { |
304 | | - // because something failed, go ahead and delete the server we created |
305 | 328 | launchError = launchResponse.message; |
306 | | - await ChatService.deleteSingleOrRemoteMcpServer(configuredResponse.id); |
| 329 | + listLaunchLogs(configuredResponse.id); |
307 | 330 | } else { |
308 | 331 | launchProgress = 100; |
| 332 | + } |
309 | 333 |
|
310 | | - selectedEntryOrServer = { |
311 | | - server: configuredResponse, |
312 | | - connectURL: configuredResponse.connectURL, |
313 | | - instance: undefined, |
314 | | - parent: entry |
315 | | - } as ConnectedServer; |
| 334 | + selectedEntryOrServer = { |
| 335 | + server: configuredResponse, |
| 336 | + connectURL: configuredResponse.connectURL, |
| 337 | + instance: undefined, |
| 338 | + parent: entry |
| 339 | + } as ConnectedServer; |
316 | 340 |
|
| 341 | + if (!launchError) { |
317 | 342 | const ref = selectedEntryOrServer; |
318 | 343 | setTimeout(() => { |
319 | 344 | launching = false; |
|
322 | 347 | }, 1000); |
323 | 348 | } |
324 | 349 | } catch (err) { |
325 | | - await ChatService.deleteSingleOrRemoteMcpServer(response.id); |
326 | 350 | launchError = err instanceof Error ? err.message : 'An unknown error occurred'; |
327 | 351 | } finally { |
328 | 352 | clearTimeout(timeout1); |
329 | 353 | clearTimeout(timeout2); |
330 | 354 | clearTimeout(timeout3); |
| 355 | +
|
| 356 | + relaunching = false; |
331 | 357 | } |
332 | 358 | } |
333 | 359 | } |
|
401 | 427 | if (!selectedEntryOrServer) return; |
402 | 428 | if (!configureForm) return; |
403 | 429 |
|
| 430 | + if ( |
| 431 | + relaunching && |
| 432 | + 'parent' in selectedEntryOrServer && |
| 433 | + 'server' in selectedEntryOrServer && |
| 434 | + selectedEntryOrServer.parent && |
| 435 | + selectedEntryOrServer.server |
| 436 | + ) { |
| 437 | + configDialog?.close(); |
| 438 | + await handleLaunchCatalogEntry(selectedEntryOrServer.parent, selectedEntryOrServer.server); |
| 439 | + return; |
| 440 | + } |
| 441 | +
|
404 | 442 | try { |
405 | 443 | if ('server' in selectedEntryOrServer && selectedEntryOrServer.server?.id) { |
406 | 444 | if ( |
|
484 | 522 | configDialog?.open(); |
485 | 523 | } |
486 | 524 |
|
| 525 | + async function handleCancelLaunch() { |
| 526 | + if (launchLogsEventStream) { |
| 527 | + launchLogsEventStream.disconnect(); |
| 528 | + } |
| 529 | + if ( |
| 530 | + selectedEntryOrServer && |
| 531 | + 'server' in selectedEntryOrServer && |
| 532 | + selectedEntryOrServer.server |
| 533 | + ) { |
| 534 | + await ChatService.deleteSingleOrRemoteMcpServer(selectedEntryOrServer.server.id); |
| 535 | + selectedEntryOrServer = selectedEntryOrServer.parent; |
| 536 | + } |
| 537 | +
|
| 538 | + launching = false; |
| 539 | + launchError = undefined; |
| 540 | + } |
| 541 | +
|
487 | 542 | const duration = PAGE_TRANSITION_DURATION; |
488 | 543 | </script> |
489 | 544 |
|
|
654 | 709 | text="Configuring and initializing server..." |
655 | 710 | progress={launchProgress} |
656 | 711 | error={launchError} |
657 | | - onClose={() => { |
658 | | - launching = false; |
| 712 | + errorClasses={{ |
| 713 | + root: 'md:w-[95vw]' |
659 | 714 | }} |
| 715 | + onClose={handleCancelLaunch} |
660 | 716 | > |
661 | 717 | {#snippet errorPreContent()} |
662 | 718 | <h4 class="text-xl font-semibold">MCP Server Launch Failed</h4> |
663 | 719 | {/snippet} |
664 | 720 | {#snippet errorPostContent()} |
665 | | - <p class="text-md self-start">An issue occurred while launching the MCP server.</p> |
| 721 | + {@const hasConfigurableParent = |
| 722 | + selectedEntryOrServer && |
| 723 | + 'parent' in selectedEntryOrServer && |
| 724 | + selectedEntryOrServer.parent && |
| 725 | + hasEditableConfiguration(selectedEntryOrServer.parent)} |
| 726 | + {#if launchLogs.length > 0} |
| 727 | + <div |
| 728 | + class="default-scrollbar-thin bg-surface1 max-h-[50vh] w-full overflow-y-auto rounded-lg p-4 shadow-inner" |
| 729 | + > |
| 730 | + {#each launchLogs as log, i (i)} |
| 731 | + <div class="font-mono text-sm"> |
| 732 | + <span class="text-gray-600 dark:text-gray-400">{log}</span> |
| 733 | + </div> |
| 734 | + {/each} |
| 735 | + </div> |
| 736 | + {:else} |
| 737 | + <p class="text-md self-start">An issue occurred while launching the MCP server.</p> |
| 738 | + {/if} |
666 | 739 |
|
667 | | - <p class="text-md self-start">If the problem persists, please contact an administrator.</p> |
| 740 | + <div class="flex w-full flex-col items-center gap-2 md:flex-row"> |
| 741 | + {#if hasConfigurableParent} |
| 742 | + <button |
| 743 | + class="button-primary w-full md:w-1/2 md:flex-1" |
| 744 | + onclick={() => { |
| 745 | + launching = false; |
| 746 | + launchError = undefined; |
| 747 | + relaunching = true; |
| 748 | + if ( |
| 749 | + selectedEntryOrServer && |
| 750 | + 'parent' in selectedEntryOrServer && |
| 751 | + selectedEntryOrServer.parent |
| 752 | + ) { |
| 753 | + if (hasEditableConfiguration(selectedEntryOrServer.parent)) { |
| 754 | + configDialog?.open(); |
| 755 | + } else { |
| 756 | + handleLaunch(); |
| 757 | + } |
| 758 | + } |
| 759 | + }} |
| 760 | + > |
| 761 | + Update Configuration and Try Again |
| 762 | + </button> |
| 763 | + {/if} |
| 764 | + <button class="button w-full md:w-1/2 md:flex-1" onclick={handleCancelLaunch}> |
| 765 | + Cancel and Delete Server |
| 766 | + </button> |
| 767 | + </div> |
668 | 768 | {/snippet} |
669 | 769 | </PageLoading> |
670 | 770 |
|
|
0 commit comments