Path: blob/main/src/vs/platform/mcp/common/mcpManagementIpc.ts
3294 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 { DidUninstallMcpServerEvent, IGalleryMcpServer, ILocalMcpServer, IMcpManagementService, IInstallableMcpServer, InstallMcpServerEvent, InstallMcpServerResult, InstallOptions, UninstallMcpServerEvent, UninstallOptions, IAllowedMcpServersService } from './mcpManagement.js';11import { AbstractMcpManagementService } from './mcpManagementService.js';1213function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI;14function transformIncomingURI(uri: UriComponents | undefined, transformer: IURITransformer | null): URI | undefined;15function transformIncomingURI(uri: UriComponents | undefined, transformer: IURITransformer | null): URI | undefined {16return uri ? URI.revive(transformer ? transformer.transformIncoming(uri) : uri) : undefined;17}1819function transformIncomingServer(mcpServer: ILocalMcpServer, transformer: IURITransformer | null): ILocalMcpServer {20transformer = transformer ? transformer : DefaultURITransformer;21const manifest = mcpServer.manifest;22const transformed = transformAndReviveIncomingURIs({ ...mcpServer, ...{ manifest: undefined } }, transformer);23return { ...transformed, ...{ manifest } };24}2526function transformIncomingOptions<O extends { mcpResource?: UriComponents }>(options: O | undefined, transformer: IURITransformer | null): O | undefined {27return options?.mcpResource ? transformAndReviveIncomingURIs(options, transformer ?? DefaultURITransformer) : options;28}2930function transformOutgoingExtension(extension: ILocalMcpServer, transformer: IURITransformer | null): ILocalMcpServer {31return transformer ? cloneAndChange(extension, value => value instanceof URI ? transformer.transformOutgoingURI(value) : undefined) : extension;32}3334function transformOutgoingURI(uri: URI, transformer: IURITransformer | null): URI {35return transformer ? transformer.transformOutgoingURI(uri) : uri;36}3738export class McpManagementChannel implements IServerChannel {39readonly onInstallMcpServer: Event<InstallMcpServerEvent>;40readonly onDidInstallMcpServers: Event<readonly InstallMcpServerResult[]>;41readonly onDidUpdateMcpServers: Event<readonly InstallMcpServerResult[]>;42readonly onUninstallMcpServer: Event<UninstallMcpServerEvent>;43readonly onDidUninstallMcpServer: Event<DidUninstallMcpServerEvent>;4445constructor(private service: IMcpManagementService, private getUriTransformer: (requestContext: any) => IURITransformer | null) {46this.onInstallMcpServer = Event.buffer(service.onInstallMcpServer, true);47this.onDidInstallMcpServers = Event.buffer(service.onDidInstallMcpServers, true);48this.onDidUpdateMcpServers = Event.buffer(service.onDidUpdateMcpServers, true);49this.onUninstallMcpServer = Event.buffer(service.onUninstallMcpServer, true);50this.onDidUninstallMcpServer = Event.buffer(service.onDidUninstallMcpServer, true);51}5253listen(context: any, event: string): Event<any> {54const uriTransformer = this.getUriTransformer(context);55switch (event) {56case 'onInstallMcpServer': {57return Event.map<InstallMcpServerEvent, InstallMcpServerEvent>(this.onInstallMcpServer, event => {58return { ...event, mcpResource: transformOutgoingURI(event.mcpResource, uriTransformer) };59});60}61case 'onDidInstallMcpServers': {62return Event.map<readonly InstallMcpServerResult[], readonly InstallMcpServerResult[]>(this.onDidInstallMcpServers, results =>63results.map(i => ({64...i,65local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local,66mcpResource: transformOutgoingURI(i.mcpResource, uriTransformer)67})));68}69case 'onDidUpdateMcpServers': {70return Event.map<readonly InstallMcpServerResult[], readonly InstallMcpServerResult[]>(this.onDidUpdateMcpServers, results =>71results.map(i => ({72...i,73local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local,74mcpResource: transformOutgoingURI(i.mcpResource, uriTransformer)75})));76}77case 'onUninstallMcpServer': {78return Event.map<UninstallMcpServerEvent, UninstallMcpServerEvent>(this.onUninstallMcpServer, event => {79return { ...event, mcpResource: transformOutgoingURI(event.mcpResource, uriTransformer) };80});81}82case 'onDidUninstallMcpServer': {83return Event.map<DidUninstallMcpServerEvent, DidUninstallMcpServerEvent>(this.onDidUninstallMcpServer, event => {84return { ...event, mcpResource: transformOutgoingURI(event.mcpResource, uriTransformer) };85});86}87}8889throw new Error('Invalid listen');90}9192async call(context: any, command: string, args?: any): Promise<any> {93const uriTransformer: IURITransformer | null = this.getUriTransformer(context);94switch (command) {95case 'getInstalled': {96const mcpServers = await this.service.getInstalled(transformIncomingURI(args[0], uriTransformer));97return mcpServers.map(e => transformOutgoingExtension(e, uriTransformer));98}99case 'install': {100return this.service.install(args[0], transformIncomingOptions(args[1], uriTransformer));101}102case 'installFromGallery': {103return this.service.installFromGallery(args[0], transformIncomingOptions(args[1], uriTransformer));104}105case 'uninstall': {106return this.service.uninstall(transformIncomingServer(args[0], uriTransformer), transformIncomingOptions(args[1], uriTransformer));107}108case 'updateMetadata': {109return this.service.updateMetadata(transformIncomingServer(args[0], uriTransformer), args[1], transformIncomingURI(args[2], uriTransformer));110}111}112113throw new Error('Invalid call');114}115}116117export class McpManagementChannelClient extends AbstractMcpManagementService implements IMcpManagementService {118119declare readonly _serviceBrand: undefined;120121private readonly _onInstallMcpServer = this._register(new Emitter<InstallMcpServerEvent>());122get onInstallMcpServer() { return this._onInstallMcpServer.event; }123124private readonly _onDidInstallMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());125get onDidInstallMcpServers() { return this._onDidInstallMcpServers.event; }126127private readonly _onUninstallMcpServer = this._register(new Emitter<UninstallMcpServerEvent>());128get onUninstallMcpServer() { return this._onUninstallMcpServer.event; }129130private readonly _onDidUninstallMcpServer = this._register(new Emitter<DidUninstallMcpServerEvent>());131get onDidUninstallMcpServer() { return this._onDidUninstallMcpServer.event; }132133private readonly _onDidUpdateMcpServers = this._register(new Emitter<InstallMcpServerResult[]>());134get onDidUpdateMcpServers() { return this._onDidUpdateMcpServers.event; }135136constructor(137private readonly channel: IChannel,138@IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService139) {140super(allowedMcpServersService);141this._register(this.channel.listen<InstallMcpServerEvent>('onInstallMcpServer')(e => this._onInstallMcpServer.fire(({ ...e, mcpResource: transformIncomingURI(e.mcpResource, null) }))));142this._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) })))));143this._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) })))));144this._register(this.channel.listen<UninstallMcpServerEvent>('onUninstallMcpServer')(e => this._onUninstallMcpServer.fire(({ ...e, mcpResource: transformIncomingURI(e.mcpResource, null) }))));145this._register(this.channel.listen<DidUninstallMcpServerEvent>('onDidUninstallMcpServer')(e => this._onDidUninstallMcpServer.fire(({ ...e, mcpResource: transformIncomingURI(e.mcpResource, null) }))));146}147148install(server: IInstallableMcpServer, options?: InstallOptions): Promise<ILocalMcpServer> {149return Promise.resolve(this.channel.call<ILocalMcpServer>('install', [server, options])).then(local => transformIncomingServer(local, null));150}151152installFromGallery(extension: IGalleryMcpServer, installOptions?: InstallOptions): Promise<ILocalMcpServer> {153return Promise.resolve(this.channel.call<ILocalMcpServer>('installFromGallery', [extension, installOptions])).then(local => transformIncomingServer(local, null));154}155156uninstall(extension: ILocalMcpServer, options?: UninstallOptions): Promise<void> {157return Promise.resolve(this.channel.call<void>('uninstall', [extension, options]));158}159160getInstalled(mcpResource?: URI): Promise<ILocalMcpServer[]> {161return Promise.resolve(this.channel.call<ILocalMcpServer[]>('getInstalled', [mcpResource]))162.then(servers => servers.map(server => transformIncomingServer(server, null)));163}164165updateMetadata(local: ILocalMcpServer, gallery: IGalleryMcpServer, mcpResource?: URI): Promise<ILocalMcpServer> {166return Promise.resolve(this.channel.call<ILocalMcpServer>('updateMetadata', [local, gallery, mcpResource])).then(local => transformIncomingServer(local, null));167}168}169170171