Skip to content

Commit 366d00b

Browse files
committed
mcp: auto-start server on install
1 parent 4f7b769 commit 366d00b

File tree

2 files changed

+40
-2
lines changed

2 files changed

+40
-2
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { errorIcon, infoIcon, manageExtensionIcon, trustIcon, warningIcon } from
2626
import { McpCommandIds } from '../common/mcpCommandIds.js';
2727
import { IMcpRegistry } from '../common/mcpRegistryTypes.js';
2828
import { IMcpSamplingService, IMcpServer, IMcpServerContainer, IMcpService, IMcpWorkbenchService, IWorkbenchMcpServer, McpCapability, McpConnectionState, McpServerEditorTab, McpServerInstallState } from '../common/mcpTypes.js';
29+
import { startServerByFilter } from '../common/mcpTypesUtils.js';
2930

3031
export abstract class McpServerAction extends Action implements IMcpServerContainer {
3132

@@ -108,6 +109,7 @@ export class InstallAction extends McpServerAction {
108109
private readonly editor: boolean,
109110
@IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService,
110111
@ITelemetryService private readonly telemetryService: ITelemetryService,
112+
@IMcpService private readonly mcpService: IMcpService,
111113
) {
112114
super('extensions.install', localize('install', "Install"), InstallAction.CLASS, false);
113115
this.update();
@@ -149,7 +151,11 @@ export class InstallAction extends McpServerAction {
149151
};
150152
this.telemetryService.publicLog2<McpServerInstall, McpServerInstallClassification>('mcp:action:install', { name: this.mcpServer.gallery?.name });
151153

152-
await this.mcpWorkbenchService.install(this.mcpServer);
154+
const installed = await this.mcpWorkbenchService.install(this.mcpServer);
155+
156+
await startServerByFilter(this.mcpService, s => {
157+
return s.definition.label === installed.name;
158+
});
153159
}
154160
}
155161

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,42 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { disposableTimeout } from '../../../../base/common/async.js';
67
import { CancellationToken } from '../../../../base/common/cancellation.js';
8+
import { CancellationError } from '../../../../base/common/errors.js';
79
import { DisposableStore } from '../../../../base/common/lifecycle.js';
810
import { autorun } from '../../../../base/common/observable.js';
9-
import { IMcpServer, IMcpServerStartOpts, McpConnectionState, McpServerCacheState } from './mcpTypes.js';
11+
import { IMcpServer, IMcpServerStartOpts, IMcpService, McpConnectionState, McpServerCacheState } from './mcpTypes.js';
12+
13+
/**
14+
* Waits up to `timeout` for a server passing the filter to be discovered,
15+
* and then starts it.
16+
*/
17+
export function startServerByFilter(mcpService: IMcpService, filter: (s: IMcpServer) => boolean, timeout = 5000) {
18+
return new Promise<void>((resolve, reject) => {
19+
const store = new DisposableStore();
20+
store.add(autorun(reader => {
21+
const servers = mcpService.servers.read(reader);
22+
const server = servers.find(filter);
23+
24+
if (server) {
25+
server.start({ promptType: 'all-untrusted' }).then(state => {
26+
if (state.state === McpConnectionState.Kind.Error) {
27+
server.showOutput();
28+
}
29+
});
30+
31+
resolve();
32+
store.dispose();
33+
}
34+
}));
35+
36+
store.add(disposableTimeout(() => {
37+
store.dispose();
38+
reject(new CancellationError());
39+
}, timeout));
40+
});
41+
}
1042

1143
/**
1244
* Starts a server (if needed) and waits for its tools to be live. Returns

0 commit comments

Comments
 (0)