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