Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/mcp/common/mcpServerConnection.ts
5263 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 { CancellationTokenSource } from '../../../../base/common/cancellation.js';
7
import { CancellationError } from '../../../../base/common/errors.js';
8
import { Disposable, DisposableStore, IReference, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
9
import { autorun, IObservable, observableValue } from '../../../../base/common/observable.js';
10
import { localize } from '../../../../nls.js';
11
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
12
import { ILogger, log, LogLevel } from '../../../../platform/log/common/log.js';
13
import { IMcpHostDelegate, IMcpMessageTransport } from './mcpRegistryTypes.js';
14
import { McpServerRequestHandler } from './mcpServerRequestHandler.js';
15
import { McpTaskManager } from './mcpTaskManager.js';
16
import { IMcpClientMethods, IMcpServerConnection, McpCollectionDefinition, McpConnectionState, McpServerDefinition, McpServerLaunch } from './mcpTypes.js';
17
18
export class McpServerConnection extends Disposable implements IMcpServerConnection {
19
private readonly _launch = this._register(new MutableDisposable<IReference<IMcpMessageTransport>>());
20
private readonly _state = observableValue<McpConnectionState>('mcpServerState', { state: McpConnectionState.Kind.Stopped });
21
private readonly _requestHandler = observableValue<McpServerRequestHandler | undefined>('mcpServerRequestHandler', undefined);
22
23
public readonly state: IObservable<McpConnectionState> = this._state;
24
public readonly handler: IObservable<McpServerRequestHandler | undefined> = this._requestHandler;
25
26
constructor(
27
private readonly _collection: McpCollectionDefinition,
28
public readonly definition: McpServerDefinition,
29
private readonly _delegate: IMcpHostDelegate,
30
public readonly launchDefinition: McpServerLaunch,
31
private readonly _logger: ILogger,
32
private readonly _errorOnUserInteraction: boolean | undefined,
33
private readonly _taskManager: McpTaskManager,
34
@IInstantiationService private readonly _instantiationService: IInstantiationService,
35
) {
36
super();
37
}
38
39
/** @inheritdoc */
40
public async start(methods: IMcpClientMethods): Promise<McpConnectionState> {
41
const currentState = this._state.get();
42
if (!McpConnectionState.canBeStarted(currentState.state)) {
43
return this._waitForState(McpConnectionState.Kind.Running, McpConnectionState.Kind.Error);
44
}
45
46
this._launch.value = undefined;
47
this._state.set({ state: McpConnectionState.Kind.Starting }, undefined);
48
this._logger.info(localize('mcpServer.starting', 'Starting server {0}', this.definition.label));
49
50
try {
51
const launch = this._delegate.start(this._collection, this.definition, this.launchDefinition, { errorOnUserInteraction: this._errorOnUserInteraction });
52
this._launch.value = this.adoptLaunch(launch, methods);
53
return this._waitForState(McpConnectionState.Kind.Running, McpConnectionState.Kind.Error);
54
} catch (e) {
55
const errorState: McpConnectionState = {
56
state: McpConnectionState.Kind.Error,
57
message: e instanceof Error ? e.message : String(e)
58
};
59
this._state.set(errorState, undefined);
60
return errorState;
61
}
62
}
63
64
private adoptLaunch(launch: IMcpMessageTransport, methods: IMcpClientMethods): IReference<IMcpMessageTransport> {
65
const store = new DisposableStore();
66
const cts = new CancellationTokenSource();
67
68
store.add(toDisposable(() => cts.dispose(true)));
69
store.add(launch);
70
store.add(launch.onDidLog(({ level, message }) => {
71
log(this._logger, level, message);
72
}));
73
74
let didStart = false;
75
store.add(autorun(reader => {
76
const state = launch.state.read(reader);
77
this._state.set(state, undefined);
78
this._logger.info(localize('mcpServer.state', 'Connection state: {0}', McpConnectionState.toString(state)));
79
80
if (state.state === McpConnectionState.Kind.Running && !didStart) {
81
didStart = true;
82
McpServerRequestHandler.create(this._instantiationService, {
83
...methods,
84
launch,
85
logger: this._logger,
86
requestLogLevel: this.definition.devMode ? LogLevel.Info : LogLevel.Debug,
87
taskManager: this._taskManager,
88
}, cts.token).then(
89
handler => {
90
if (!store.isDisposed) {
91
this._requestHandler.set(handler, undefined);
92
} else {
93
handler.dispose();
94
}
95
},
96
err => {
97
if (!store.isDisposed && McpConnectionState.isRunning(this._state.read(undefined))) {
98
let message = err.message;
99
if (err instanceof CancellationError) {
100
message = 'Server exited before responding to `initialize` request.';
101
this._logger.error(message);
102
} else {
103
this._logger.error(err);
104
}
105
this._state.set({ state: McpConnectionState.Kind.Error, message }, undefined);
106
}
107
store.dispose();
108
},
109
);
110
}
111
}));
112
113
return { dispose: () => store.dispose(), object: launch };
114
}
115
116
public async stop(): Promise<void> {
117
this._logger.info(localize('mcpServer.stopping', 'Stopping server {0}', this.definition.label));
118
this._launch.value?.object.stop();
119
await this._waitForState(McpConnectionState.Kind.Stopped, McpConnectionState.Kind.Error);
120
}
121
122
public override dispose(): void {
123
this._requestHandler.get()?.dispose();
124
super.dispose();
125
this._state.set({ state: McpConnectionState.Kind.Stopped }, undefined);
126
}
127
128
private _waitForState(...kinds: McpConnectionState.Kind[]): Promise<McpConnectionState> {
129
const current = this._state.get();
130
if (kinds.includes(current.state)) {
131
return Promise.resolve(current);
132
}
133
134
return new Promise(resolve => {
135
const disposable = autorun(reader => {
136
const state = this._state.read(reader);
137
if (kinds.includes(state.state)) {
138
disposable.dispose();
139
resolve(state);
140
}
141
});
142
});
143
}
144
}
145
146