Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/mcp/browser/mcpMigration.ts
3296 views
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 { Disposable } from '../../../../base/common/lifecycle.js';
7
import { ConfigurationTarget } from '../../../../platform/configuration/common/configuration.js';
8
import { ILogService } from '../../../../platform/log/common/log.js';
9
import { IMcpServerConfiguration, IMcpServerVariable, IMcpStdioServerConfiguration, McpServerType } from '../../../../platform/mcp/common/mcpPlatformTypes.js';
10
import { IStringDictionary } from '../../../../base/common/collections.js';
11
import { mcpConfigurationSection } from '../../../contrib/mcp/common/mcpConfiguration.js';
12
import { IWorkbenchMcpManagementService } from '../../../services/mcp/common/mcpWorkbenchManagementService.js';
13
import { IWorkbenchContribution } from '../../../common/contributions.js';
14
import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js';
15
import { FileOperationResult, IFileService, toFileOperationResult } from '../../../../platform/files/common/files.js';
16
import { URI } from '../../../../base/common/uri.js';
17
import { parse } from '../../../../base/common/jsonc.js';
18
import { isObject, Mutable } from '../../../../base/common/types.js';
19
import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js';
20
import { IJSONEditingService } from '../../../services/configuration/common/jsonEditing.js';
21
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
22
import { ICommandService } from '../../../../platform/commands/common/commands.js';
23
import { McpCommandIds } from '../common/mcpCommandIds.js';
24
import { localize } from '../../../../nls.js';
25
26
interface IMcpConfiguration {
27
inputs?: IMcpServerVariable[];
28
servers?: IStringDictionary<IMcpServerConfiguration>;
29
}
30
31
export class McpConfigMigrationContribution extends Disposable implements IWorkbenchContribution {
32
33
static ID = 'workbench.mcp.config.migration';
34
35
constructor(
36
@IWorkbenchMcpManagementService private readonly mcpManagementService: IWorkbenchMcpManagementService,
37
@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,
38
@IFileService private readonly fileService: IFileService,
39
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
40
@IJSONEditingService private readonly jsonEditingService: IJSONEditingService,
41
@ILogService private readonly logService: ILogService,
42
@INotificationService private readonly notificationService: INotificationService,
43
@ICommandService private readonly commandService: ICommandService,
44
) {
45
super();
46
this.migrateMcpConfig();
47
}
48
49
private async migrateMcpConfig(): Promise<void> {
50
try {
51
const userMcpConfig = await this.parseMcpConfig(this.userDataProfileService.currentProfile.settingsResource);
52
if (userMcpConfig && userMcpConfig.servers && Object.keys(userMcpConfig.servers).length > 0) {
53
await Promise.all(Object.entries(userMcpConfig.servers).map(([name, config], index) => this.mcpManagementService.install({ name, config, inputs: index === 0 ? userMcpConfig.inputs : undefined })));
54
await this.removeMcpConfig(this.userDataProfileService.currentProfile.settingsResource);
55
}
56
} catch (error) {
57
this.logService.error(`MCP migration: Failed to migrate user MCP config`, error);
58
}
59
this.watchForMcpConfiguration(this.userDataProfileService.currentProfile.settingsResource, false);
60
61
const remoteEnvironment = await this.remoteAgentService.getEnvironment();
62
if (remoteEnvironment) {
63
try {
64
const userRemoteMcpConfig = await this.parseMcpConfig(remoteEnvironment.settingsPath);
65
if (userRemoteMcpConfig && userRemoteMcpConfig.servers && Object.keys(userRemoteMcpConfig.servers).length > 0) {
66
await Promise.all(Object.entries(userRemoteMcpConfig.servers).map(([name, config], index) => this.mcpManagementService.install({ name, config, inputs: index === 0 ? userRemoteMcpConfig.inputs : undefined }, { target: ConfigurationTarget.USER_REMOTE })));
67
await this.removeMcpConfig(remoteEnvironment.settingsPath);
68
}
69
} catch (error) {
70
this.logService.error(`MCP migration: Failed to migrate remote MCP config`, error);
71
}
72
this.watchForMcpConfiguration(remoteEnvironment.settingsPath, true);
73
}
74
75
}
76
77
private watchForMcpConfiguration(file: URI, isRemote: boolean): void {
78
this._register(this.fileService.watch(file));
79
this._register(this.fileService.onDidFilesChange(e => {
80
if (e.contains(file)) {
81
this.checkForMcpConfigInFile(file, isRemote);
82
}
83
}));
84
}
85
86
private async checkForMcpConfigInFile(settingsFile: URI, isRemote: boolean): Promise<void> {
87
try {
88
const mcpConfig = await this.parseMcpConfig(settingsFile);
89
if (mcpConfig && mcpConfig.servers && Object.keys(mcpConfig.servers).length > 0) {
90
this.showMcpConfigErrorNotification(isRemote);
91
}
92
} catch (error) {
93
// Ignore parsing errors - file might not exist or be malformed
94
}
95
}
96
97
private showMcpConfigErrorNotification(isRemote: boolean): void {
98
const message = isRemote
99
? localize('mcp.migration.remoteConfigFound', 'MCP servers should no longer be configured in remote user settings. Use the dedicated MCP configuration instead.')
100
: localize('mcp.migration.userConfigFound', 'MCP servers should no longer be configured in user settings. Use the dedicated MCP configuration instead.');
101
102
const openConfigLabel = isRemote
103
? localize('mcp.migration.openRemoteConfig', 'Open Remote User MCP Configuration')
104
: localize('mcp.migration.openUserConfig', 'Open User MCP Configuration');
105
106
const commandId = isRemote ? McpCommandIds.OpenRemoteUserMcp : McpCommandIds.OpenUserMcp;
107
108
this.notificationService.prompt(
109
Severity.Error,
110
message,
111
[{
112
label: localize('mcp.migration.update', 'Update Now'),
113
run: async () => {
114
await this.migrateMcpConfig();
115
await this.commandService.executeCommand(commandId);
116
},
117
}, {
118
label: openConfigLabel,
119
keepOpen: true,
120
run: () => this.commandService.executeCommand(commandId)
121
}]
122
);
123
}
124
125
private async parseMcpConfig(settingsFile: URI): Promise<IMcpConfiguration | undefined> {
126
try {
127
const content = await this.fileService.readFile(settingsFile);
128
const settingsObject: IStringDictionary<any> = parse(content.value.toString());
129
if (!isObject(settingsObject)) {
130
return undefined;
131
}
132
const mcpConfiguration = settingsObject[mcpConfigurationSection] as IMcpConfiguration;
133
if (mcpConfiguration && mcpConfiguration.servers) {
134
for (const [, config] of Object.entries(mcpConfiguration.servers)) {
135
if (config.type === undefined) {
136
(<Mutable<IMcpServerConfiguration>>config).type = (<IMcpStdioServerConfiguration>config).command ? McpServerType.LOCAL : McpServerType.REMOTE;
137
}
138
}
139
}
140
return mcpConfiguration;
141
} catch (error) {
142
if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) {
143
this.logService.warn(`MCP migration: Failed to parse MCP config from ${settingsFile}:`, error);
144
}
145
return;
146
}
147
}
148
149
private async removeMcpConfig(settingsFile: URI): Promise<void> {
150
try {
151
await this.jsonEditingService.write(settingsFile, [
152
{
153
path: [mcpConfigurationSection],
154
value: undefined
155
}
156
], true);
157
} catch (error) {
158
this.logService.warn(`MCP migration: Failed to remove MCP config from ${settingsFile}:`, error);
159
}
160
}
161
}
162
163