Skip to content

Commit 304c07e

Browse files
authored
render manifest (microsoft#252395)
1 parent 691764e commit 304c07e

File tree

4 files changed

+194
-5
lines changed

4 files changed

+194
-5
lines changed

src/vs/workbench/contrib/mcp/browser/mcpServerEditor.ts

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
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';
88
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
99
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
1010
import { DomScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js';
@@ -42,12 +42,13 @@ import { InstallCountWidget, McpServerIconWidget, McpServerWidget, onClick, Publ
4242
import { DropDownAction, InstallAction, ManageMcpServerAction, UninstallAction } from './mcpServerActions.js';
4343
import { McpServerEditorInput } from './mcpServerEditorInput.js';
4444
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';
4646
import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
4747

4848
const enum McpServerEditorTab {
4949
Readme = 'readme',
5050
Configuration = 'configuration',
51+
Manifest = 'manifest',
5152
}
5253

5354
function toDateString(date: Date) {
@@ -138,6 +139,7 @@ export class McpServerEditor extends EditorPane {
138139
private template: IExtensionEditorTemplate | undefined;
139140

140141
private mcpServerReadme: Cache<string> | null;
142+
private mcpServerManifest: Cache<IMcpServerManifest> | null;
141143

142144
// Some action bar items use a webview whose vertical scroll position we track in this map
143145
private initialScrollProgress: Map<WebviewIndex, number> = new Map();
@@ -167,6 +169,7 @@ export class McpServerEditor extends EditorPane {
167169
) {
168170
super(McpServerEditor.ID, group, telemetryService, themeService, storageService);
169171
this.mcpServerReadme = null;
172+
this.mcpServerManifest = null;
170173
}
171174

172175
override get scopedContextKeyService(): IContextKeyService | undefined {
@@ -296,6 +299,7 @@ export class McpServerEditor extends EditorPane {
296299
const token = this.transientDisposables.add(new CancellationTokenSource()).token;
297300

298301
this.mcpServerReadme = new Cache(() => mcpServer.getReadme(token));
302+
this.mcpServerManifest = new Cache(() => mcpServer.getManifest(token));
299303
template.mcpServer = mcpServer;
300304

301305
template.name.textContent = mcpServer.label;
@@ -325,6 +329,10 @@ export class McpServerEditor extends EditorPane {
325329
template.navbar.push(McpServerEditorTab.Configuration, localize('configuration', "Configuration"), localize('configurationtooltip', "Server configuration details"));
326330
}
327331

332+
if (extension.gallery || extension.local?.manifest) {
333+
template.navbar.push(McpServerEditorTab.Manifest, localize('manifest', "Manifest"), localize('manifesttooltip', "Server manifest details"));
334+
}
335+
328336
if (template.navbar.currentId) {
329337
this.onNavbarChange(extension, { id: template.navbar.currentId, focus: !preserveFocus }, template);
330338
}
@@ -382,6 +390,7 @@ export class McpServerEditor extends EditorPane {
382390
switch (id) {
383391
case McpServerEditorTab.Configuration: return this.openConfiguration(extension, template, token);
384392
case McpServerEditorTab.Readme: return this.openDetails(extension, template, token);
393+
case McpServerEditorTab.Manifest: return this.openManifest(extension, template, token);
385394
}
386395
return Promise.resolve(null);
387396
}
@@ -561,8 +570,36 @@ export class McpServerEditor extends EditorPane {
561570
return { focus: () => content.focus() };
562571
}
563572

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+
564601
private renderConfigurationDetails(container: HTMLElement, mcpServer: IWorkbenchMcpServer): void {
565-
container.remove();
602+
clearNode(container);
566603

567604
const config = mcpServer.config;
568605

@@ -613,6 +650,84 @@ export class McpServerEditor extends EditorPane {
613650
}
614651
}
615652

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+
616731
private renderAdditionalDetails(container: HTMLElement, extension: IWorkbenchMcpServer): void {
617732
const content = $('div', { class: 'additional-details-content', tabindex: '0' });
618733
const scrollableContent = new DomScrollableElement(content, {});

src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { IEditorOptions } from '../../../../platform/editor/common/editor.js';
1717
import { IFileService } from '../../../../platform/files/common/files.js';
1818
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
1919
import { ILabelService } from '../../../../platform/label/common/label.js';
20-
import { DidUninstallMcpServerEvent, IGalleryMcpServer, IMcpGalleryService, InstallMcpServerResult, IQueryOptions, IInstallableMcpServer } from '../../../../platform/mcp/common/mcpManagement.js';
20+
import { DidUninstallMcpServerEvent, IGalleryMcpServer, IMcpGalleryService, InstallMcpServerResult, IQueryOptions, IInstallableMcpServer, IMcpServerManifest } from '../../../../platform/mcp/common/mcpManagement.js';
2121
import { IMcpServerConfiguration, IMcpServerVariable, IMcpStdioServerConfiguration } from '../../../../platform/mcp/common/mcpPlatformTypes.js';
2222
import { IProductService } from '../../../../platform/product/common/productService.js';
2323
import { StorageScope } from '../../../../platform/storage/common/storage.js';
@@ -108,6 +108,18 @@ class McpWorkbenchServer implements IWorkbenchMcpServer {
108108
return Promise.reject(new Error('not available'));
109109
}
110110

111+
async getManifest(token: CancellationToken): Promise<IMcpServerManifest> {
112+
if (this.local?.manifest) {
113+
return this.local.manifest;
114+
}
115+
116+
if (this.gallery) {
117+
return this.mcpGalleryService.getManifest(this.gallery, token);
118+
}
119+
120+
throw new Error('No manifest available');
121+
}
122+
111123
}
112124

113125
export class McpWorkbenchService extends Disposable implements IMcpWorkbenchService {

src/vs/workbench/contrib/mcp/browser/media/mcpServerEditor.css

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,65 @@
3030
font-style: italic;
3131
padding: 20px;
3232
}
33+
34+
.manifest-content {
35+
padding: 20px;
36+
box-sizing: border-box;
37+
38+
.manifest-section {
39+
margin-bottom: 20px;
40+
41+
.manifest-section-title {
42+
font-size: 26px;
43+
display: inline-block;
44+
margin: 0px;
45+
font-weight: 600;
46+
height: 100%;
47+
box-sizing: border-box;
48+
padding: 10px;
49+
padding-left: 0px;
50+
flex: 1;
51+
position: relative;
52+
overflow: hidden;
53+
text-overflow: ellipsis;
54+
}
55+
}
56+
57+
.package-section {
58+
59+
margin-top: 10px;
60+
61+
.package-section-title {
62+
margin-bottom: 5px;
63+
font-size: 1.4em;
64+
font-weight: 600;
65+
border-bottom: 1px solid var(--vscode-panelSection-border);
66+
padding: 5px;
67+
}
68+
69+
.package-details {
70+
padding-left: 10px;
71+
.package-detail {
72+
display: grid;
73+
grid-template-columns: 200px 1fr;
74+
gap: 12px;
75+
align-items: start;
76+
padding: 8px 0px;
77+
}
78+
79+
.package-separator {
80+
margin-top: 10px;
81+
border-bottom: 1px solid var(--vscode-panelSection-border);
82+
}
83+
}
84+
}
85+
86+
87+
.no-manifest {
88+
font-style: italic;
89+
padding: 20px;
90+
}
91+
}
92+
93+
3394
}

src/vs/workbench/contrib/mcp/common/mcpTypes.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { RawContextKey } from '../../../../platform/contextkey/common/contextkey
2020
import { IEditorOptions } from '../../../../platform/editor/common/editor.js';
2121
import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
2222
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
23-
import { IInstallableMcpServer as IInstallableMcpServer, IGalleryMcpServer, IQueryOptions } from '../../../../platform/mcp/common/mcpManagement.js';
23+
import { IInstallableMcpServer as IInstallableMcpServer, IGalleryMcpServer, IQueryOptions, IMcpServerManifest } from '../../../../platform/mcp/common/mcpManagement.js';
2424
import { IMcpDevModeConfig, IMcpServerConfiguration } from '../../../../platform/mcp/common/mcpPlatformTypes.js';
2525
import { StorageScope } from '../../../../platform/storage/common/storage.js';
2626
import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
@@ -588,6 +588,7 @@ export interface IWorkbenchMcpServer {
588588
readonly config?: IMcpServerConfiguration | undefined;
589589
hasReadme(): boolean;
590590
getReadme(token: CancellationToken): Promise<string>;
591+
getManifest(token: CancellationToken): Promise<IMcpServerManifest>;
591592
}
592593

593594
export const IMcpWorkbenchService = createDecorator<IMcpWorkbenchService>('IMcpWorkbenchService');

0 commit comments

Comments
 (0)