Path: blob/main/src/vs/platform/mcp/common/mcpManagementIpc.ts
5257 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Emitter, Event } from '../../../base/common/event.js';6import { cloneAndChange } from '../../../base/common/objects.js';7import { URI, UriComponents } from '../../../base/common/uri.js';8import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from '../../../base/common/uriIpc.js';9import { IChannel, IServerChannel } from '../../../base/parts/ipc/common/ipc.js';10import { ILogService } from '../../log/common/log.js';11import { RemoteAgentConnectionContext } from '../../remote/common/remoteAgentEnvironment.js';12import { DidUninstallMcpServerEvent, IGalleryMcpServer, ILocalMcpServer, IMcpManagementService, IInstallableMcpServer, InstallMcpServerEvent, InstallMcpServerResult, InstallOptions, UninstallMcpServerEvent, UninstallOptions, IAllowedMcpServersService } from './mcpManagement.js';13import { AbstractMcpManagementService } from './mcpManagementService.js';1415function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI;16function transformIncomingURI(uri: UriComponents | undefined, transformer: IURITransformer | null): URI | undefined;17function transformIncomingURI(uri: UriComponents | undefined, transformer: IURITransformer | null): URI | undefined {18return uri ? URI.revive(transformer ? transformer.transformIncoming(uri) : uri) : undefined;19}2021function transformIncomingServer(mcpServer: ILocalMcpServer, transformer: IURITransformer | null): ILocalMcpServer {22transformer = transformer ? transformer : DefaultURITransformer;23const manifest = mcpServer.manifest;24const transformed = transformAndReviveIncomingURIs({ ...mcpServer, ...{ manifest: undefined } }, transformer);25return { ...transformed, ...{ manifest } };26}2728function transformIncomingOptions<O extends { mcpResource?: UriComponents }>(options: O | undefined, transformer: IURITransformer | null): O | undefined {29return options?.mcpResource ? transformAndReviveIncomingURIs(options, transformer ?? DefaultURITransformer) : options;30}3132function transformOutgoingExtension(extension: ILocalMcpServer, transformer: IURITransformer | null): ILocalMcpServer {33return transformer ? cloneAndChange(extension, value => value instanceof URI ? transformer.transformOutgoingURI(value) : undefined) : extension;34}3536function transformOutgoingURI(uri: URI, transformer: IURITransformer | null): URI {37return transformer ? transformer.transformOutgoingURI(uri) : uri;38}3940export class McpManagementChannel<TContext = RemoteAgentConnectionContext | string> implements IServerChannel<TContext> {41readonly onInstallMcpServer: Event<InstallMcpServerEvent>;42readonly onDidInstallMcpServers: Event<readonly InstallMcpServerResult[]>;43readonly onDidUpdateMcpServers: Event<readonly InstallMcpServerResult[]>;44readonly onUninstallMcpServer: Event<UninstallMcpServerEvent>;45readonly onDidUninstallMcpServer: Event<DidUninstallMcpServerEvent>;4647constructor(private service: IMcpManagementService, private getUriTransformer: (requestContext: TContext) => IURITransformer | null) {48this.onInstallMcpServer = Event.buffer(service.onInstallMcpServer, true);49this.onDidInstallMcpServers = Event.buffer(service.onDidInstallMcpServers, true);50this.onDidUpdateMcpServers = Event.buffer(service.onDidUpdateMcpServers, true);51this.onUninstallMcpServer = Event.buffer(service.onUninstallMcpServer, true);52this.onDidUninstallMcpServer = Event.buffer(service.onDidUninstallMcpServer, true);53}5455listen<T>(context: TContext, event: string): Event<T> {56const uriTransformer = this.getUriTransformer(context);57switch (event) {58case 'onInstallMcpServer': {59return Event.map<InstallMcpServerEvent, InstallMcpServerEvent>(this.onInstallMcpServer, event => {60return { ...event, mcpResource: transformOutgoingURI(event.mcpResource, uriTransformer) };61}) as Event<T>;62}63case 'onDidInstallMcpServers': {64return Event.map<readonly InstallMcpServerResult[], readonly InstallMcpServerResult[]>(this.onDidInstallMcpServers, results =>65results.map(i => ({66...i,67local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local,68mcpResource: transformOutgoingURI(i.mcpResource, uriTransformer)69}))) as Event<T>;70}71case 'onDidUpdateMcpServers': {72return Event.map<readonly InstallMcpServerResult[], readonly InstallMcpServerResult[]>(this.onDidUpdateMcpServers, results =>73results.map(i => ({74...i,75local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local,76mcpResource: transformOutgoingURI(i.mcpResource, uriTransformer)77}))) as Event<T>;78}79case 'onUninstallMcpServer': {80return Event.map<UninstallMcpServerEvent, UninstallMcpServerEvent>(this.onUninstallMcpServer, event => {81return { ...event, mcpResource: transformOutgoingURI(event.mcpResource, uriTransformer) };82}) as Event<T>;83}84case 'onDidUninstallMcpServer': {85return Event.map<DidUninstallMcpServerEvent, DidUninstallMcpServerEvent>(this.onDidUninstallMcpServer, event => {86return { ...event, mcpResource: transformOutgoingURI(event.mcpResource, uriTransformer) };87}) as Event<T>;88}89}9091throw new Error('Invalid listen');92}9394async call<T>(context: TContext, command: string, args?: unknown): Promise<T> {95const uriTransformer: IURITransformer | null = this.getUriTransformer(context);96const argsArray = Array.isArray(args) ? args : [];97switch (command) {98case 'getInstalled': {99const mcpServers = await this.service.getInstalled(transformIncomingURI(argsArray[0], uriTransformer));100return mcpServers.map(e => transformOutgoingExtension(e, uriTransformer)) as T;101}102case 'install': {103return this.service.install(argsArray[0], transformIncomingOptions(argsArray[1], uriTransformer)) as T;104}105case 'installFromGallery': {106return this.service.installFromGallery(argsArray[0], transformIncomingOptions(argsArray[1], uriTransformer)) as T;107}108case 'uninstall': {109return this.service.uninstall(transformIncomingServer(argsArray[0], uriTransformer), transformIncomingOptions(argsArray[1], uriTransformer)) as T;110}111case 'updateMetadata': {112return this.service.updateMetadata(transformIncomingServer(argsArray[0], uriTransformer), argsArray[1], transformIncomingURI(argsArray[2], uriTransformer)) as T;113}114}115116throw new Error('Invalid call');117}118}119120export class McpManagementChannelClient extends AbstractMcpManagementService implements IMcpManagementService {121122declare readonly _serviceBrand: undefined;123124private readonly _onInstallMcpServer = this._register(new Emitter<InstallMcpServerEvent>());125get onInstallMcpServer() { return this._onInstallMcpServer.event; }126127private readonly _onDidInstallMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());128get onDidInstallMcpServers() { return this._onDidInstallMcpServers.event; }129130private readonly _onUninstallMcpServer = this._register(new Emitter<UninstallMcpServerEvent>());131get onUninstallMcpServer() { return this._onUninstallMcpServer.event; }132133private readonly _onDidUninstallMcpServer = this._register(new Emitter<DidUninstallMcpServerEvent>());134get onDidUninstallMcpServer() { return this._onDidUninstallMcpServer.event; }135136private readonly _onDidUpdateMcpServers = this._register(new Emitter<InstallMcpServerResult[]>());137get onDidUpdateMcpServers() { return this._onDidUpdateMcpServers.event; }138139constructor(140private readonly channel: IChannel,141@IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService,142@ILogService logService: ILogService143) {144super(allowedMcpServersService, logService);145this._register(this.channel.listen<InstallMcpServerEvent>('onInstallMcpServer')(e => this._onInstallMcpServer.fire(({ ...e, mcpResource: transformIncomingURI(e.mcpResource, null) }))));146this._register(this.channel.listen<readonly InstallMcpServerResult[]>('onDidInstallMcpServers')(results => this._onDidInstallMcpServers.fire(results.map(e => ({ ...e, local: e.local ? transformIncomingServer(e.local, null) : e.local, mcpResource: transformIncomingURI(e.mcpResource, null) })))));147this._register(this.channel.listen<readonly InstallMcpServerResult[]>('onDidUpdateMcpServers')(results => this._onDidUpdateMcpServers.fire(results.map(e => ({ ...e, local: e.local ? transformIncomingServer(e.local, null) : e.local, mcpResource: transformIncomingURI(e.mcpResource, null) })))));148this._register(this.channel.listen<UninstallMcpServerEvent>('onUninstallMcpServer')(e => this._onUninstallMcpServer.fire(({ ...e, mcpResource: transformIncomingURI(e.mcpResource, null) }))));149this._register(this.channel.listen<DidUninstallMcpServerEvent>('onDidUninstallMcpServer')(e => this._onDidUninstallMcpServer.fire(({ ...e, mcpResource: transformIncomingURI(e.mcpResource, null) }))));150}151152install(server: IInstallableMcpServer, options?: InstallOptions): Promise<ILocalMcpServer> {153return Promise.resolve(this.channel.call<ILocalMcpServer>('install', [server, options])).then(local => transformIncomingServer(local, null));154}155156installFromGallery(extension: IGalleryMcpServer, installOptions?: InstallOptions): Promise<ILocalMcpServer> {157return Promise.resolve(this.channel.call<ILocalMcpServer>('installFromGallery', [extension, installOptions])).then(local => transformIncomingServer(local, null));158}159160uninstall(extension: ILocalMcpServer, options?: UninstallOptions): Promise<void> {161return Promise.resolve(this.channel.call<void>('uninstall', [extension, options]));162}163164getInstalled(mcpResource?: URI): Promise<ILocalMcpServer[]> {165return Promise.resolve(this.channel.call<ILocalMcpServer[]>('getInstalled', [mcpResource]))166.then(servers => servers.map(server => transformIncomingServer(server, null)));167}168169updateMetadata(local: ILocalMcpServer, gallery: IGalleryMcpServer, mcpResource?: URI): Promise<ILocalMcpServer> {170return Promise.resolve(this.channel.call<ILocalMcpServer>('updateMetadata', [local, gallery, mcpResource])).then(local => transformIncomingServer(local, null));171}172}173174175