|
| 1 | +/*--------------------------------------------------------------------------------------------- |
| 2 | + * Copyright (c) Microsoft Corporation. All rights reserved. |
| 3 | + * Licensed under the MIT License. See License.txt in the project root for license information. |
| 4 | + *--------------------------------------------------------------------------------------------*/ |
| 5 | + |
| 6 | +import { Emitter, Event } from '../../../base/common/event.js'; |
| 7 | +import { Disposable } from '../../../base/common/lifecycle.js'; |
| 8 | +import { cloneAndChange } from '../../../base/common/objects.js'; |
| 9 | +import { URI, UriComponents } from '../../../base/common/uri.js'; |
| 10 | +import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from '../../../base/common/uriIpc.js'; |
| 11 | +import { IChannel, IServerChannel } from '../../../base/parts/ipc/common/ipc.js'; |
| 12 | +import { DidUninstallMcpServerEvent, IGalleryMcpServer, ILocalMcpServer, IMcpManagementService, IMcpServer, InstallMcpServerEvent, InstallMcpServerResult, InstallOptions, UninstallMcpServerEvent, UninstallOptions } from './mcpManagement.js'; |
| 13 | + |
| 14 | +function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI; |
| 15 | +function transformIncomingURI(uri: UriComponents | undefined, transformer: IURITransformer | null): URI | undefined; |
| 16 | +function transformIncomingURI(uri: UriComponents | undefined, transformer: IURITransformer | null): URI | undefined { |
| 17 | + return uri ? URI.revive(transformer ? transformer.transformIncoming(uri) : uri) : undefined; |
| 18 | +} |
| 19 | + |
| 20 | +function transformIncomingServer(mcpServer: ILocalMcpServer, transformer: IURITransformer | null): ILocalMcpServer { |
| 21 | + transformer = transformer ? transformer : DefaultURITransformer; |
| 22 | + const manifest = mcpServer.manifest; |
| 23 | + const transformed = transformAndReviveIncomingURIs({ ...mcpServer, ...{ manifest: undefined } }, transformer); |
| 24 | + return { ...transformed, ...{ manifest } }; |
| 25 | +} |
| 26 | + |
| 27 | +function transformIncomingOptions<O extends { mcpResource?: UriComponents }>(options: O | undefined, transformer: IURITransformer | null): O | undefined { |
| 28 | + return options?.mcpResource ? transformAndReviveIncomingURIs(options, transformer ?? DefaultURITransformer) : options; |
| 29 | +} |
| 30 | + |
| 31 | +function transformOutgoingExtension(extension: ILocalMcpServer, transformer: IURITransformer | null): ILocalMcpServer { |
| 32 | + return transformer ? cloneAndChange(extension, value => value instanceof URI ? transformer.transformOutgoingURI(value) : undefined) : extension; |
| 33 | +} |
| 34 | + |
| 35 | +function transformOutgoingURI(uri: URI, transformer: IURITransformer | null): URI { |
| 36 | + return transformer ? transformer.transformOutgoingURI(uri) : uri; |
| 37 | +} |
| 38 | + |
| 39 | +export class McpManagementChannel implements IServerChannel { |
| 40 | + readonly onInstallMcpServer: Event<InstallMcpServerEvent>; |
| 41 | + readonly onDidInstallMcpServers: Event<readonly InstallMcpServerResult[]>; |
| 42 | + readonly onUninstallMcpServer: Event<UninstallMcpServerEvent>; |
| 43 | + readonly onDidUninstallMcpServer: Event<DidUninstallMcpServerEvent>; |
| 44 | + |
| 45 | + constructor(private service: IMcpManagementService, private getUriTransformer: (requestContext: any) => IURITransformer | null) { |
| 46 | + this.onInstallMcpServer = Event.buffer(service.onInstallMcpServer, true); |
| 47 | + this.onDidInstallMcpServers = Event.buffer(service.onDidInstallMcpServers, true); |
| 48 | + this.onUninstallMcpServer = Event.buffer(service.onUninstallMcpServer, true); |
| 49 | + this.onDidUninstallMcpServer = Event.buffer(service.onDidUninstallMcpServer, true); |
| 50 | + } |
| 51 | + |
| 52 | + listen(context: any, event: string): Event<any> { |
| 53 | + const uriTransformer = this.getUriTransformer(context); |
| 54 | + switch (event) { |
| 55 | + case 'onInstallMcpServer': { |
| 56 | + return Event.map<InstallMcpServerEvent, InstallMcpServerEvent>(this.onInstallMcpServer, event => { |
| 57 | + return { ...event, mcpResource: transformOutgoingURI(event.mcpResource, uriTransformer) }; |
| 58 | + }); |
| 59 | + } |
| 60 | + case 'onDidInstallMcpServers': { |
| 61 | + return Event.map<readonly InstallMcpServerResult[], readonly InstallMcpServerResult[]>(this.onDidInstallMcpServers, results => |
| 62 | + results.map(i => ({ |
| 63 | + ...i, |
| 64 | + local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local, |
| 65 | + mcpResource: transformOutgoingURI(i.mcpResource, uriTransformer) |
| 66 | + }))); |
| 67 | + } |
| 68 | + case 'onUninstallMcpServer': { |
| 69 | + return Event.map<UninstallMcpServerEvent, UninstallMcpServerEvent>(this.onUninstallMcpServer, event => { |
| 70 | + return { ...event, mcpResource: transformOutgoingURI(event.mcpResource, uriTransformer) }; |
| 71 | + }); |
| 72 | + } |
| 73 | + case 'onDidUninstallMcpServer': { |
| 74 | + return Event.map<DidUninstallMcpServerEvent, DidUninstallMcpServerEvent>(this.onDidUninstallMcpServer, event => { |
| 75 | + return { ...event, mcpResource: transformOutgoingURI(event.mcpResource, uriTransformer) }; |
| 76 | + }); |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + throw new Error('Invalid listen'); |
| 81 | + } |
| 82 | + |
| 83 | + async call(context: any, command: string, args?: any): Promise<any> { |
| 84 | + const uriTransformer: IURITransformer | null = this.getUriTransformer(context); |
| 85 | + switch (command) { |
| 86 | + case 'getInstalled': { |
| 87 | + const mcpServers = await this.service.getInstalled(transformIncomingURI(args[0], uriTransformer)); |
| 88 | + return mcpServers.map(e => transformOutgoingExtension(e, uriTransformer)); |
| 89 | + } |
| 90 | + case 'install': { |
| 91 | + return this.service.install(args[0], transformIncomingOptions(args[1], uriTransformer)); |
| 92 | + } |
| 93 | + case 'installFromGallery': { |
| 94 | + return this.service.installFromGallery(args[0], transformIncomingOptions(args[1], uriTransformer)); |
| 95 | + } |
| 96 | + case 'uninstall': { |
| 97 | + return this.service.uninstall(transformIncomingServer(args[0], uriTransformer), transformIncomingOptions(args[1], uriTransformer)); |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + throw new Error('Invalid call'); |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +export class McpManagementChannelClient extends Disposable implements IMcpManagementService { |
| 106 | + |
| 107 | + declare readonly _serviceBrand: undefined; |
| 108 | + |
| 109 | + private readonly _onInstallMcpServer = this._register(new Emitter<InstallMcpServerEvent>()); |
| 110 | + get onInstallMcpServer() { return this._onInstallMcpServer.event; } |
| 111 | + |
| 112 | + private readonly _onDidInstallMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>()); |
| 113 | + get onDidInstallMcpServers() { return this._onDidInstallMcpServers.event; } |
| 114 | + |
| 115 | + private readonly _onUninstallMcpServer = this._register(new Emitter<UninstallMcpServerEvent>()); |
| 116 | + get onUninstallMcpServer() { return this._onUninstallMcpServer.event; } |
| 117 | + |
| 118 | + private readonly _onDidUninstallMcpServer = this._register(new Emitter<DidUninstallMcpServerEvent>()); |
| 119 | + get onDidUninstallMcpServer() { return this._onDidUninstallMcpServer.event; } |
| 120 | + |
| 121 | + constructor(private readonly channel: IChannel) { |
| 122 | + super(); |
| 123 | + this._register(this.channel.listen<InstallMcpServerEvent>('onInstallMcpServer')(e => this._onInstallMcpServer.fire(({ ...e, mcpResource: transformIncomingURI(e.mcpResource, null) })))); |
| 124 | + this._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) }))))); |
| 125 | + this._register(this.channel.listen<UninstallMcpServerEvent>('onUninstallMcpServer')(e => this._onUninstallMcpServer.fire(({ ...e, mcpResource: transformIncomingURI(e.mcpResource, null) })))); |
| 126 | + this._register(this.channel.listen<DidUninstallMcpServerEvent>('onDidUninstallMcpServer')(e => this._onDidUninstallMcpServer.fire(({ ...e, mcpResource: transformIncomingURI(e.mcpResource, null) })))); |
| 127 | + } |
| 128 | + |
| 129 | + install(server: IMcpServer, options?: InstallOptions): Promise<ILocalMcpServer> { |
| 130 | + return Promise.resolve(this.channel.call<ILocalMcpServer>('install', [server, options])).then(local => transformIncomingServer(local, null)); |
| 131 | + } |
| 132 | + |
| 133 | + installFromGallery(extension: IGalleryMcpServer, installOptions?: InstallOptions): Promise<ILocalMcpServer> { |
| 134 | + return Promise.resolve(this.channel.call<ILocalMcpServer>('installFromGallery', [extension, installOptions])).then(local => transformIncomingServer(local, null)); |
| 135 | + } |
| 136 | + |
| 137 | + uninstall(extension: ILocalMcpServer, options?: UninstallOptions): Promise<void> { |
| 138 | + return Promise.resolve(this.channel.call<void>('uninstall', [extension, options])); |
| 139 | + } |
| 140 | + |
| 141 | + getInstalled(mcpResource?: URI): Promise<ILocalMcpServer[]> { |
| 142 | + return Promise.resolve(this.channel.call<ILocalMcpServer[]>('getInstalled', [mcpResource])) |
| 143 | + .then(servers => servers.map(server => transformIncomingServer(server, null))); |
| 144 | + } |
| 145 | +} |
0 commit comments