|
4 | 4 | *--------------------------------------------------------------------------------------------*/
|
5 | 5 |
|
6 | 6 | import './media/mcpServerEditor.css';
|
7 |
| -import { $, Dimension, append, setParentFlowTo } from '../../../../base/browser/dom.js'; |
| 7 | +import { $, Dimension, append, clearNode, setParentFlowTo } from '../../../../base/browser/dom.js'; |
8 | 8 | import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
|
9 | 9 | import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
|
10 | 10 | import { DomScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js';
|
@@ -42,12 +42,13 @@ import { InstallCountWidget, McpServerIconWidget, McpServerWidget, onClick, Publ
|
42 | 42 | import { DropDownAction, InstallAction, ManageMcpServerAction, UninstallAction } from './mcpServerActions.js';
|
43 | 43 | import { McpServerEditorInput } from './mcpServerEditorInput.js';
|
44 | 44 | import { IEditorOptions } from '../../../../platform/editor/common/editor.js';
|
45 |
| -import { ILocalMcpServer } from '../../../../platform/mcp/common/mcpManagement.js'; |
| 45 | +import { ILocalMcpServer, IMcpServerManifest, IMcpServerPackage, PackageType } from '../../../../platform/mcp/common/mcpManagement.js'; |
46 | 46 | import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
|
47 | 47 |
|
48 | 48 | const enum McpServerEditorTab {
|
49 | 49 | Readme = 'readme',
|
50 | 50 | Configuration = 'configuration',
|
| 51 | + Manifest = 'manifest', |
51 | 52 | }
|
52 | 53 |
|
53 | 54 | function toDateString(date: Date) {
|
@@ -138,6 +139,7 @@ export class McpServerEditor extends EditorPane {
|
138 | 139 | private template: IExtensionEditorTemplate | undefined;
|
139 | 140 |
|
140 | 141 | private mcpServerReadme: Cache<string> | null;
|
| 142 | + private mcpServerManifest: Cache<IMcpServerManifest> | null; |
141 | 143 |
|
142 | 144 | // Some action bar items use a webview whose vertical scroll position we track in this map
|
143 | 145 | private initialScrollProgress: Map<WebviewIndex, number> = new Map();
|
@@ -167,6 +169,7 @@ export class McpServerEditor extends EditorPane {
|
167 | 169 | ) {
|
168 | 170 | super(McpServerEditor.ID, group, telemetryService, themeService, storageService);
|
169 | 171 | this.mcpServerReadme = null;
|
| 172 | + this.mcpServerManifest = null; |
170 | 173 | }
|
171 | 174 |
|
172 | 175 | override get scopedContextKeyService(): IContextKeyService | undefined {
|
@@ -296,6 +299,7 @@ export class McpServerEditor extends EditorPane {
|
296 | 299 | const token = this.transientDisposables.add(new CancellationTokenSource()).token;
|
297 | 300 |
|
298 | 301 | this.mcpServerReadme = new Cache(() => mcpServer.getReadme(token));
|
| 302 | + this.mcpServerManifest = new Cache(() => mcpServer.getManifest(token)); |
299 | 303 | template.mcpServer = mcpServer;
|
300 | 304 |
|
301 | 305 | template.name.textContent = mcpServer.label;
|
@@ -325,6 +329,10 @@ export class McpServerEditor extends EditorPane {
|
325 | 329 | template.navbar.push(McpServerEditorTab.Configuration, localize('configuration', "Configuration"), localize('configurationtooltip', "Server configuration details"));
|
326 | 330 | }
|
327 | 331 |
|
| 332 | + if (extension.gallery || extension.local?.manifest) { |
| 333 | + template.navbar.push(McpServerEditorTab.Manifest, localize('manifest', "Manifest"), localize('manifesttooltip', "Server manifest details")); |
| 334 | + } |
| 335 | + |
328 | 336 | if (template.navbar.currentId) {
|
329 | 337 | this.onNavbarChange(extension, { id: template.navbar.currentId, focus: !preserveFocus }, template);
|
330 | 338 | }
|
@@ -382,6 +390,7 @@ export class McpServerEditor extends EditorPane {
|
382 | 390 | switch (id) {
|
383 | 391 | case McpServerEditorTab.Configuration: return this.openConfiguration(extension, template, token);
|
384 | 392 | case McpServerEditorTab.Readme: return this.openDetails(extension, template, token);
|
| 393 | + case McpServerEditorTab.Manifest: return this.openManifest(extension, template, token); |
385 | 394 | }
|
386 | 395 | return Promise.resolve(null);
|
387 | 396 | }
|
@@ -561,8 +570,36 @@ export class McpServerEditor extends EditorPane {
|
561 | 570 | return { focus: () => content.focus() };
|
562 | 571 | }
|
563 | 572 |
|
| 573 | + private async openManifest(mcpServer: IWorkbenchMcpServer, template: IExtensionEditorTemplate, token: CancellationToken): Promise<IActiveElement | null> { |
| 574 | + const manifestContainer = append(template.content, $('.manifest')); |
| 575 | + const content = $('div', { class: 'manifest-content', tabindex: '0' }); |
| 576 | + |
| 577 | + try { |
| 578 | + const manifest = await this.loadContents(() => this.mcpServerManifest!.get(), content); |
| 579 | + if (token.isCancellationRequested) { |
| 580 | + return null; |
| 581 | + } |
| 582 | + this.renderManifestDetails(content, manifest); |
| 583 | + } catch (error) { |
| 584 | + // Handle error - show no manifest message |
| 585 | + while (content.firstChild) { |
| 586 | + content.removeChild(content.firstChild); |
| 587 | + } |
| 588 | + const noManifestMessage = append(content, $('.no-manifest')); |
| 589 | + noManifestMessage.textContent = localize('noManifest', "No manifest available for this MCP server."); |
| 590 | + } |
| 591 | + |
| 592 | + const scrollableContent = new DomScrollableElement(content, {}); |
| 593 | + const layout = () => scrollableContent.scanDomNode(); |
| 594 | + this.contentDisposables.add(toDisposable(arrays.insert(this.layoutParticipants, { layout }))); |
| 595 | + |
| 596 | + append(manifestContainer, scrollableContent.getDomNode()); |
| 597 | + |
| 598 | + return { focus: () => content.focus() }; |
| 599 | + } |
| 600 | + |
564 | 601 | private renderConfigurationDetails(container: HTMLElement, mcpServer: IWorkbenchMcpServer): void {
|
565 |
| - container.remove(); |
| 602 | + clearNode(container); |
566 | 603 |
|
567 | 604 | const config = mcpServer.config;
|
568 | 605 |
|
@@ -613,6 +650,84 @@ export class McpServerEditor extends EditorPane {
|
613 | 650 | }
|
614 | 651 | }
|
615 | 652 |
|
| 653 | + private renderManifestDetails(container: HTMLElement, manifest: IMcpServerManifest): void { |
| 654 | + clearNode(container); |
| 655 | + |
| 656 | + if (manifest.packages && manifest.packages.length > 0) { |
| 657 | + const packagesByType = new Map<PackageType, IMcpServerPackage[]>(); |
| 658 | + for (const pkg of manifest.packages) { |
| 659 | + const type = pkg.registry_name; |
| 660 | + let packages = packagesByType.get(type); |
| 661 | + if (!packages) { |
| 662 | + packagesByType.set(type, packages = []); |
| 663 | + } |
| 664 | + packages.push(pkg); |
| 665 | + } |
| 666 | + |
| 667 | + append(container, $('.manifest-section', undefined, $('.manifest-section-title', undefined, localize('packages', "Packages")))); |
| 668 | + |
| 669 | + for (const [packageType, packages] of packagesByType) { |
| 670 | + const packageSection = append(container, $('.package-section', undefined, $('.package-section-title', undefined, packageType.toUpperCase()))); |
| 671 | + const packagesGrid = append(packageSection, $('.package-details')); |
| 672 | + |
| 673 | + for (let i = 0; i < packages.length; i++) { |
| 674 | + const pkg = packages[i]; |
| 675 | + append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('packageName', "Package:")), $('.detail-value', undefined, pkg.name))); |
| 676 | + if (pkg.package_arguments && pkg.package_arguments.length > 0) { |
| 677 | + const argStrings: string[] = []; |
| 678 | + for (const arg of pkg.package_arguments) { |
| 679 | + if (arg.type === 'named') { |
| 680 | + argStrings.push(`--${arg.name}`); |
| 681 | + if (arg.value) { |
| 682 | + argStrings.push(arg.value); |
| 683 | + } |
| 684 | + } |
| 685 | + if (arg.type === 'positional') { |
| 686 | + argStrings.push(arg.value ?? arg.value_hint); |
| 687 | + } |
| 688 | + } |
| 689 | + append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('packagearguments', "Package Arguments:")), $('code.detail-value', undefined, argStrings.join(' ')))); |
| 690 | + } |
| 691 | + if (pkg.runtime_arguments && pkg.runtime_arguments.length > 0) { |
| 692 | + const argStrings: string[] = []; |
| 693 | + for (const arg of pkg.runtime_arguments) { |
| 694 | + if (arg.type === 'named') { |
| 695 | + argStrings.push(`--${arg.name}`); |
| 696 | + if (arg.value) { |
| 697 | + argStrings.push(arg.value); |
| 698 | + } |
| 699 | + } |
| 700 | + if (arg.type === 'positional') { |
| 701 | + argStrings.push(arg.value ?? arg.value_hint); |
| 702 | + } |
| 703 | + } |
| 704 | + append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('runtimeargs', "Runtime Arguments:")), $('code.detail-value', undefined, argStrings.join(' ')))); |
| 705 | + } |
| 706 | + if (pkg.environment_variables && pkg.environment_variables.length > 0) { |
| 707 | + const envStrings = pkg.environment_variables.map((envVar: any) => `${envVar.name}=${envVar.value}`); |
| 708 | + append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('environmentVariables', "Environment Variables:")), $('code.detail-value', undefined, envStrings.join(' ')))); |
| 709 | + } |
| 710 | + if (i < packages.length - 1) { |
| 711 | + append(packagesGrid, $('.package-separator')); |
| 712 | + } |
| 713 | + } |
| 714 | + } |
| 715 | + } |
| 716 | + |
| 717 | + if (manifest.remotes && manifest.remotes.length > 0) { |
| 718 | + const packageSection = append(container, $('.package-section', undefined, $('.package-section-title', undefined, localize('remotes', "Remote").toLocaleUpperCase()))); |
| 719 | + for (const remote of manifest.remotes) { |
| 720 | + const packagesGrid = append(packageSection, $('.package-details')); |
| 721 | + append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('url', "URL:")), $('.detail-value', undefined, remote.url))); |
| 722 | + append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('transport', "Transport:")), $('.detail-value', undefined, remote.transport_type))); |
| 723 | + if (remote.headers && remote.headers.length > 0) { |
| 724 | + const headerStrings = remote.headers.map((header: any) => `${header.name}: ${header.value}`); |
| 725 | + append(packagesGrid, $('.package-detail', undefined, $('.detail-label', undefined, localize('headers', "Headers:")), $('.detail-value', undefined, headerStrings.join(', ')))); |
| 726 | + } |
| 727 | + } |
| 728 | + } |
| 729 | + } |
| 730 | + |
616 | 731 | private renderAdditionalDetails(container: HTMLElement, extension: IWorkbenchMcpServer): void {
|
617 | 732 | const content = $('div', { class: 'additional-details-content', tabindex: '0' });
|
618 | 733 | const scrollableContent = new DomScrollableElement(content, {});
|
|
0 commit comments