@codingame/monaco-vscode-mcp-service-override 37.1.0 → 37.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/common.d.ts +2 -0
- package/common.js +56 -0
- package/index.d.ts +1 -2
- package/index.js +1 -51
- package/package.json +10 -2
- package/session.d.ts +1 -0
- package/session.js +2 -0
- package/vscode/src/vs/platform/mcp/common/allowedMcpServersService.js +3 -3
- package/vscode/src/vs/platform/mcp/common/mcpGalleryService.js +8 -8
- package/vscode/src/vs/platform/mcp/common/mcpResourceScannerService.js +7 -3
- package/vscode/src/vs/workbench/api/browser/mainThreadMcp.d.ts +108 -0
- package/vscode/src/vs/workbench/api/browser/mainThreadMcp.js +698 -0
- package/vscode/src/vs/workbench/contrib/mcp/browser/mcp.contribution.js +3 -3
- package/vscode/src/vs/workbench/contrib/mcp/browser/mcpAddContextContribution.js +2 -2
- package/vscode/src/vs/workbench/contrib/mcp/browser/mcpCommands.js +108 -108
- package/vscode/src/vs/workbench/contrib/mcp/browser/mcpElicitationService.js +47 -47
- package/vscode/src/vs/workbench/contrib/mcp/browser/mcpGatewayService.js +4 -4
- package/vscode/src/vs/workbench/contrib/mcp/browser/mcpLanguageFeatures.js +37 -37
- package/vscode/src/vs/workbench/contrib/mcp/browser/mcpMigration.js +20 -16
- package/vscode/src/vs/workbench/contrib/mcp/browser/mcpResourceQuickAccess.js +52 -50
- package/vscode/src/vs/workbench/contrib/mcp/browser/mcpServerEditor.js +45 -43
- package/vscode/src/vs/workbench/contrib/mcp/browser/mcpServerEditorInput.js +2 -2
- package/vscode/src/vs/workbench/contrib/mcp/browser/mcpServerIcons.js +5 -5
- package/vscode/src/vs/workbench/contrib/mcp/browser/mcpServerWidgets.js +5 -5
- package/vscode/src/vs/workbench/contrib/mcp/browser/mcpServersView.js +18 -18
- package/vscode/src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.js +14 -14
- package/vscode/src/vs/workbench/contrib/mcp/common/discovery/extensionMcpDiscovery.js +8 -6
- package/vscode/src/vs/workbench/contrib/mcp/common/discovery/nativeMcpDiscoveryAbstract.js +1 -1
- package/vscode/src/vs/workbench/contrib/mcp/common/discovery/nativeMcpDiscoveryAdapters.js +2 -2
- package/vscode/src/vs/workbench/contrib/mcp/common/mcpContextKeys.js +13 -13
- package/vscode/src/vs/workbench/contrib/mcp/common/mcpGatewayToolBrokerChannel.js +4 -4
- package/vscode/src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.js +7 -7
- package/vscode/src/vs/workbench/contrib/mcp/common/mcpRegistry.js +25 -25
- package/vscode/src/vs/workbench/contrib/mcp/common/mcpResourceFilesystem.js +1 -1
- package/vscode/src/vs/workbench/contrib/mcp/common/mcpSamplingLog.js +1 -1
- package/vscode/src/vs/workbench/contrib/mcp/common/mcpSamplingService.js +28 -24
- package/vscode/src/vs/workbench/contrib/mcp/common/mcpSandboxService.js +6 -6
- package/vscode/src/vs/workbench/contrib/mcp/common/mcpServerConnection.js +3 -3
- package/vscode/src/vs/workbench/contrib/mcp/common/mcpServerRequestHandler.js +1 -1
- package/vscode/src/vs/workbench/contrib/mcp/common/mcpService.js +13 -11
- package/vscode/src/vs/workbench/services/authentication/browser/authenticationMcpService.js +13 -18
- package/vscode/src/vs/workbench/services/authentication/browser/authenticationMcpUsageService.js +3 -1
- package/vscode/src/vs/workbench/contrib/mcp/common/mcpDevMode.service.d.ts +0 -6
- package/vscode/src/vs/workbench/contrib/mcp/common/mcpDevMode.service.js +0 -6
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
|
|
2
|
+
import { __decorate, __param } from '@codingame/monaco-vscode-api/external/tslib/tslib.es6';
|
|
3
|
+
import { mapFindFirst } from '@codingame/monaco-vscode-api/vscode/vs/base/common/arraysFind';
|
|
4
|
+
import { RunOnceScheduler, disposableTimeout } from '@codingame/monaco-vscode-api/vscode/vs/base/common/async';
|
|
5
|
+
import { CancellationError } from '@codingame/monaco-vscode-api/vscode/vs/base/common/errors';
|
|
6
|
+
import { Emitter } from '@codingame/monaco-vscode-api/vscode/vs/base/common/event';
|
|
7
|
+
import { Disposable, DisposableMap, DisposableStore, MutableDisposable } from '@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle';
|
|
8
|
+
import '@codingame/monaco-vscode-api/vscode/vs/base/common/observableInternal/index';
|
|
9
|
+
import Severity from '@codingame/monaco-vscode-api/vscode/vs/base/common/severity';
|
|
10
|
+
import { URI } from '@codingame/monaco-vscode-api/vscode/vs/base/common/uri';
|
|
11
|
+
import { generateUuid } from '@codingame/monaco-vscode-api/vscode/vs/base/common/uuid';
|
|
12
|
+
import { localize } from '@codingame/monaco-vscode-api/vscode/vs/nls';
|
|
13
|
+
import { ContextKeyExpr } from '@codingame/monaco-vscode-api/vscode/vs/platform/contextkey/common/contextkey';
|
|
14
|
+
import { IContextKeyService } from '@codingame/monaco-vscode-api/vscode/vs/platform/contextkey/common/contextkey.service';
|
|
15
|
+
import { IConfigurationService } from '@codingame/monaco-vscode-api/vscode/vs/platform/configuration/common/configuration.service';
|
|
16
|
+
import { IDialogService } from '@codingame/monaco-vscode-api/vscode/vs/platform/dialogs/common/dialogs.service';
|
|
17
|
+
import { ExtensionIdentifier } from '@codingame/monaco-vscode-api/vscode/vs/platform/extensions/common/extensions';
|
|
18
|
+
import { LogLevel } from '@codingame/monaco-vscode-api/vscode/vs/platform/log/common/log';
|
|
19
|
+
import { ITelemetryService } from '@codingame/monaco-vscode-api/vscode/vs/platform/telemetry/common/telemetry.service';
|
|
20
|
+
import { ISecretStorageService } from '@codingame/monaco-vscode-api/vscode/vs/platform/secrets/common/secrets.service';
|
|
21
|
+
import { IWorkbenchMcpGatewayService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/mcp/common/mcpGatewayService.service';
|
|
22
|
+
import { IMcpRegistry } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/mcp/common/mcpRegistryTypes.service';
|
|
23
|
+
import { McpServerLaunch, McpServerTransportType, McpServerDefinition, McpServerTrust, McpCollectionSortOrder, McpCollectionProvenance, extensionPrefixedIdentifier, McpConnectionState, mcpOAuthClientSecretStorageKey, UserInteractionRequiredError } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/mcp/common/mcpTypes';
|
|
24
|
+
import { mcpEnterpriseManagedAuthIdpSection } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/mcp/common/mcpConfiguration';
|
|
25
|
+
import { IAuthenticationMcpAccessService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/authentication/browser/authenticationMcpAccessService.service';
|
|
26
|
+
import { IAuthenticationMcpService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/authentication/browser/authenticationMcpService.service';
|
|
27
|
+
import { IAuthenticationMcpUsageService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/authentication/browser/authenticationMcpUsageService.service';
|
|
28
|
+
import { IAuthenticationService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/authentication/common/authentication.service';
|
|
29
|
+
import { IDynamicAuthenticationProviderStorageService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/authentication/common/dynamicAuthenticationProviderStorage.service';
|
|
30
|
+
import { ExtensionHostKind, extensionHostKindToString } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/extensions/common/extensionHostKind';
|
|
31
|
+
import { IExtensionService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/extensions/common/extensions.service';
|
|
32
|
+
import { extHostNamedCustomer } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/extensions/common/extHostCustomers';
|
|
33
|
+
import { ExtHostContext, MainContext } from '@codingame/monaco-vscode-api/vscode/vs/workbench/api/common/extHost.protocol';
|
|
34
|
+
import { autorun } from '@codingame/monaco-vscode-api/vscode/vs/base/common/observableInternal/reactions/autorun';
|
|
35
|
+
import { observableValue } from '@codingame/monaco-vscode-api/vscode/vs/base/common/observableInternal/observables/observableValue';
|
|
36
|
+
|
|
37
|
+
let MainThreadMcp = class MainThreadMcp extends Disposable {
|
|
38
|
+
constructor(
|
|
39
|
+
_extHostContext,
|
|
40
|
+
_mcpRegistry,
|
|
41
|
+
dialogService,
|
|
42
|
+
_authenticationService,
|
|
43
|
+
authenticationMcpServersService,
|
|
44
|
+
authenticationMCPServerAccessService,
|
|
45
|
+
authenticationMCPServerUsageService,
|
|
46
|
+
_dynamicAuthenticationProviderStorageService,
|
|
47
|
+
_extensionService,
|
|
48
|
+
_contextKeyService,
|
|
49
|
+
_telemetryService,
|
|
50
|
+
_mcpGatewayService,
|
|
51
|
+
_configurationService,
|
|
52
|
+
_secretStorageService
|
|
53
|
+
) {
|
|
54
|
+
super();
|
|
55
|
+
this._extHostContext = _extHostContext;
|
|
56
|
+
this._mcpRegistry = _mcpRegistry;
|
|
57
|
+
this.dialogService = dialogService;
|
|
58
|
+
this._authenticationService = _authenticationService;
|
|
59
|
+
this.authenticationMcpServersService = authenticationMcpServersService;
|
|
60
|
+
this.authenticationMCPServerAccessService = authenticationMCPServerAccessService;
|
|
61
|
+
this.authenticationMCPServerUsageService = authenticationMCPServerUsageService;
|
|
62
|
+
this._dynamicAuthenticationProviderStorageService = _dynamicAuthenticationProviderStorageService;
|
|
63
|
+
this._extensionService = _extensionService;
|
|
64
|
+
this._contextKeyService = _contextKeyService;
|
|
65
|
+
this._telemetryService = _telemetryService;
|
|
66
|
+
this._mcpGatewayService = _mcpGatewayService;
|
|
67
|
+
this._configurationService = _configurationService;
|
|
68
|
+
this._secretStorageService = _secretStorageService;
|
|
69
|
+
this._serverIdCounter = 0;
|
|
70
|
+
this._servers = ( new Map());
|
|
71
|
+
this._serverDefinitions = ( new Map());
|
|
72
|
+
this._serverAuthTracking = ( new McpServerAuthTracker());
|
|
73
|
+
this._collectionDefinitions = this._register(( new DisposableMap()));
|
|
74
|
+
this._gateways = this._register(( new DisposableMap()));
|
|
75
|
+
this._register(
|
|
76
|
+
_authenticationService.onDidChangeSessions(e => this._onDidChangeAuthSessions(e.providerId, e.label))
|
|
77
|
+
);
|
|
78
|
+
const proxy = this._proxy = ( _extHostContext.getProxy(ExtHostContext.ExtHostMcp));
|
|
79
|
+
this._register(this._mcpRegistry.registerDelegate({
|
|
80
|
+
priority: _extHostContext.extensionHostKind === ExtensionHostKind.LocalWebWorker ? 0 : 1,
|
|
81
|
+
waitForInitialProviderPromises() {
|
|
82
|
+
return proxy.$waitForInitialCollectionProviders();
|
|
83
|
+
},
|
|
84
|
+
canStart(collection, serverDefinition) {
|
|
85
|
+
if (collection.remoteAuthority !== _extHostContext.remoteAuthority) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if (serverDefinition.launch.type === McpServerTransportType.Stdio && _extHostContext.extensionHostKind === ExtensionHostKind.LocalWebWorker) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
},
|
|
93
|
+
async substituteVariables(serverDefinition, launch) {
|
|
94
|
+
const ser = await proxy.$substituteVariables(
|
|
95
|
+
serverDefinition.variableReplacement?.folder?.uri,
|
|
96
|
+
McpServerLaunch.toSerialized(launch)
|
|
97
|
+
);
|
|
98
|
+
return McpServerLaunch.fromSerialized(ser);
|
|
99
|
+
},
|
|
100
|
+
start: (_collection, serverDefiniton, resolveLaunch, options) => {
|
|
101
|
+
const id = ++this._serverIdCounter;
|
|
102
|
+
const launch = ( new ExtHostMcpServerLaunch(
|
|
103
|
+
_extHostContext.extensionHostKind,
|
|
104
|
+
() => proxy.$stopMcp(id),
|
|
105
|
+
msg => proxy.$sendMessage(id, JSON.stringify(msg))
|
|
106
|
+
));
|
|
107
|
+
this._servers.set(id, launch);
|
|
108
|
+
this._serverDefinitions.set(id, serverDefiniton);
|
|
109
|
+
proxy.$startMcp(id, {
|
|
110
|
+
launch: resolveLaunch,
|
|
111
|
+
defaultCwd: serverDefiniton.defaultCwd ?? serverDefiniton.variableReplacement?.folder?.uri,
|
|
112
|
+
errorOnUserInteraction: options?.errorOnUserInteraction
|
|
113
|
+
});
|
|
114
|
+
return launch;
|
|
115
|
+
}
|
|
116
|
+
}));
|
|
117
|
+
const onDidChangeMcpServerDefinitionsTrigger = this._register(( new RunOnceScheduler(() => this._publishServerDefinitions(), 500)));
|
|
118
|
+
this._register(autorun(reader => {
|
|
119
|
+
const collections = this._mcpRegistry.collections.read(reader);
|
|
120
|
+
for (const collection of collections) {
|
|
121
|
+
collection.serverDefinitions.read(reader);
|
|
122
|
+
}
|
|
123
|
+
if (!onDidChangeMcpServerDefinitionsTrigger.isScheduled()) {
|
|
124
|
+
onDidChangeMcpServerDefinitionsTrigger.schedule();
|
|
125
|
+
}
|
|
126
|
+
}));
|
|
127
|
+
onDidChangeMcpServerDefinitionsTrigger.schedule();
|
|
128
|
+
}
|
|
129
|
+
_publishServerDefinitions() {
|
|
130
|
+
const collections = this._mcpRegistry.collections.get();
|
|
131
|
+
const allServers = [];
|
|
132
|
+
for (const collection of collections) {
|
|
133
|
+
const servers = collection.serverDefinitions.get();
|
|
134
|
+
for (const server of servers) {
|
|
135
|
+
allServers.push(McpServerDefinition.toSerialized(server));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
this._proxy.$onDidChangeMcpServerDefinitions(allServers);
|
|
139
|
+
}
|
|
140
|
+
$upsertMcpCollection(collection, serversDto) {
|
|
141
|
+
const servers = serversDto.map(McpServerDefinition.fromSerialized);
|
|
142
|
+
const existing = this._collectionDefinitions.get(collection.id);
|
|
143
|
+
if (existing) {
|
|
144
|
+
existing.servers.set(servers, undefined);
|
|
145
|
+
} else {
|
|
146
|
+
const serverDefinitions = observableValue("mcpServers", servers);
|
|
147
|
+
const extensionId = ( new ExtensionIdentifier(collection.extensionId));
|
|
148
|
+
const store = ( new DisposableStore());
|
|
149
|
+
const handle = store.add(( new MutableDisposable()));
|
|
150
|
+
const register = () => {
|
|
151
|
+
handle.value ??= this._mcpRegistry.registerCollection({
|
|
152
|
+
...collection,
|
|
153
|
+
provenance: McpCollectionProvenance.Extension,
|
|
154
|
+
source: extensionId,
|
|
155
|
+
order: McpCollectionSortOrder.Extension,
|
|
156
|
+
resolveServerLanch: collection.canResolveLaunch ? (async def => {
|
|
157
|
+
const r = await this._proxy.$resolveMcpLaunch(collection.id, def.label);
|
|
158
|
+
return r ? McpServerLaunch.fromSerialized(r) : undefined;
|
|
159
|
+
}) : undefined,
|
|
160
|
+
trustBehavior: collection.isTrustedByDefault ? McpServerTrust.Kind.Trusted : McpServerTrust.Kind.TrustedOnNonce,
|
|
161
|
+
remoteAuthority: this._extHostContext.remoteAuthority,
|
|
162
|
+
serverDefinitions
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
const whenClauseStr = mapFindFirst(
|
|
166
|
+
this._extensionService.extensions,
|
|
167
|
+
e => ExtensionIdentifier.equals(extensionId, e.identifier) ? e.contributes?.mcpServerDefinitionProviders?.find(p => extensionPrefixedIdentifier(extensionId, p.id) === collection.id)?.when : undefined
|
|
168
|
+
);
|
|
169
|
+
const whenClause = whenClauseStr && ContextKeyExpr.deserialize(whenClauseStr);
|
|
170
|
+
if (!whenClause) {
|
|
171
|
+
register();
|
|
172
|
+
} else {
|
|
173
|
+
const evaluate = () => {
|
|
174
|
+
if (this._contextKeyService.contextMatchesRules(whenClause)) {
|
|
175
|
+
register();
|
|
176
|
+
} else {
|
|
177
|
+
handle.clear();
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
store.add(this._contextKeyService.onDidChangeContext(evaluate));
|
|
181
|
+
evaluate();
|
|
182
|
+
}
|
|
183
|
+
this._collectionDefinitions.set(collection.id, {
|
|
184
|
+
servers: serverDefinitions,
|
|
185
|
+
dispose: () => store.dispose()
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
$deleteMcpCollection(collectionId) {
|
|
190
|
+
this._collectionDefinitions.deleteAndDispose(collectionId);
|
|
191
|
+
}
|
|
192
|
+
$onDidChangeState(id, update) {
|
|
193
|
+
const server = this._servers.get(id);
|
|
194
|
+
if (!server) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
server.state.set(update, undefined);
|
|
198
|
+
if (!McpConnectionState.isRunning(update)) {
|
|
199
|
+
server.dispose();
|
|
200
|
+
this._servers.delete(id);
|
|
201
|
+
this._serverDefinitions.delete(id);
|
|
202
|
+
this._serverAuthTracking.untrack(id);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
$onDidPublishLog(id, level, log) {
|
|
206
|
+
if (typeof level === "string") {
|
|
207
|
+
level = LogLevel.Info;
|
|
208
|
+
log = level;
|
|
209
|
+
}
|
|
210
|
+
this._servers.get(id)?.pushLog(level, log);
|
|
211
|
+
}
|
|
212
|
+
$onDidReceiveMessage(id, message) {
|
|
213
|
+
this._servers.get(id)?.pushMessage(message);
|
|
214
|
+
}
|
|
215
|
+
async $getTokenForProviderId(id, providerId, scopes, options = {}) {
|
|
216
|
+
const server = this._serverDefinitions.get(id);
|
|
217
|
+
if (!server) {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
return this._getSessionForProvider(
|
|
221
|
+
id,
|
|
222
|
+
server,
|
|
223
|
+
providerId,
|
|
224
|
+
scopes,
|
|
225
|
+
undefined,
|
|
226
|
+
options.errorOnUserInteraction,
|
|
227
|
+
options.clientId
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
async $getTokenFromServerMetadata(
|
|
231
|
+
id,
|
|
232
|
+
authDetails,
|
|
233
|
+
{
|
|
234
|
+
errorOnUserInteraction,
|
|
235
|
+
forceNewRegistration,
|
|
236
|
+
clientId
|
|
237
|
+
} = {}
|
|
238
|
+
) {
|
|
239
|
+
const server = this._serverDefinitions.get(id);
|
|
240
|
+
if (!server) {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
const authorizationServer = URI.revive(authDetails.authorizationServer);
|
|
244
|
+
const resourceServer = authDetails.resourceMetadata?.resource ? ( URI.parse(authDetails.resourceMetadata.resource)) : undefined;
|
|
245
|
+
const resolvedScopes = authDetails.scopes ?? authDetails.resourceMetadata?.scopes_supported ?? authDetails.authorizationServerMetadata.scopes_supported ?? [];
|
|
246
|
+
if (authDetails.enterpriseManaged) {
|
|
247
|
+
const resource = authDetails.resourceMetadata?.resource;
|
|
248
|
+
if (!resource) {
|
|
249
|
+
throw ( new Error(( localize(
|
|
250
|
+
4985,
|
|
251
|
+
"The enterprise-managed MCP server '{0}' did not advertise a protected-resource metadata document with a 'resource' identifier.",
|
|
252
|
+
server.label
|
|
253
|
+
))));
|
|
254
|
+
}
|
|
255
|
+
const resourceAuthServers = authDetails.resourceMetadata?.authorization_servers ?? [];
|
|
256
|
+
const audience = resourceAuthServers[0];
|
|
257
|
+
if (!audience) {
|
|
258
|
+
throw ( new Error(( localize(
|
|
259
|
+
4986,
|
|
260
|
+
"The enterprise-managed MCP server '{0}' did not advertise an `authorization_servers` entry in its protected-resource metadata.",
|
|
261
|
+
server.label
|
|
262
|
+
))));
|
|
263
|
+
}
|
|
264
|
+
const xaaScopes = authDetails.scopes ?? authDetails.resourceMetadata?.scopes_supported ?? [];
|
|
265
|
+
const issuer = this._ensureXaaIssuer();
|
|
266
|
+
const xaaProviderId = await this._authenticationService.createOrGetXaaProvider(issuer);
|
|
267
|
+
if (!xaaProviderId) {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
const resourceClientId = clientId ?? authDetails.clientId;
|
|
271
|
+
let resourceClientSecret;
|
|
272
|
+
if (resourceClientId) {
|
|
273
|
+
try {
|
|
274
|
+
resourceClientSecret = await this._secretStorageService.get(mcpOAuthClientSecretStorageKey(resource, resourceClientId));
|
|
275
|
+
} catch {}
|
|
276
|
+
}
|
|
277
|
+
return this._getSessionForProvider(
|
|
278
|
+
id,
|
|
279
|
+
server,
|
|
280
|
+
xaaProviderId,
|
|
281
|
+
xaaScopes,
|
|
282
|
+
issuer,
|
|
283
|
+
errorOnUserInteraction,
|
|
284
|
+
resourceClientId,
|
|
285
|
+
resource,
|
|
286
|
+
audience,
|
|
287
|
+
resourceClientSecret
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
let providerId = await this._authenticationService.getOrActivateProviderIdForServer(authorizationServer, resourceServer);
|
|
291
|
+
const resolvedClientId = clientId ?? authDetails.clientId;
|
|
292
|
+
const mcpServerUrl = server.launch.type === McpServerTransportType.HTTP ? ( server.launch.uri.toString(true)) : undefined;
|
|
293
|
+
let clientSecret;
|
|
294
|
+
let didLookupClientSecret = false;
|
|
295
|
+
if (resolvedClientId && mcpServerUrl) {
|
|
296
|
+
try {
|
|
297
|
+
clientSecret = await this._secretStorageService.get(mcpOAuthClientSecretStorageKey(mcpServerUrl, resolvedClientId));
|
|
298
|
+
didLookupClientSecret = true;
|
|
299
|
+
} catch {}
|
|
300
|
+
}
|
|
301
|
+
if (didLookupClientSecret && providerId && !forceNewRegistration && this._authenticationService.isDynamicAuthenticationProvider(providerId)) {
|
|
302
|
+
const registered = await this._dynamicAuthenticationProviderStorageService.getClientRegistration(providerId);
|
|
303
|
+
if (registered && registered.clientSecret !== clientSecret) {
|
|
304
|
+
forceNewRegistration = true;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (forceNewRegistration && providerId) {
|
|
308
|
+
if (!this._authenticationService.isDynamicAuthenticationProvider(providerId)) {
|
|
309
|
+
throw ( new Error("Cannot force new registration for a non-dynamic authentication provider."));
|
|
310
|
+
}
|
|
311
|
+
this._authenticationService.unregisterAuthenticationProvider(providerId);
|
|
312
|
+
await this._dynamicAuthenticationProviderStorageService.removeDynamicProvider(providerId);
|
|
313
|
+
providerId = undefined;
|
|
314
|
+
}
|
|
315
|
+
if (!providerId) {
|
|
316
|
+
const provider = await this._authenticationService.createDynamicAuthenticationProvider(
|
|
317
|
+
authorizationServer,
|
|
318
|
+
authDetails.authorizationServerMetadata,
|
|
319
|
+
authDetails.resourceMetadata,
|
|
320
|
+
resolvedClientId,
|
|
321
|
+
clientSecret
|
|
322
|
+
);
|
|
323
|
+
if (!provider) {
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
326
|
+
providerId = provider.id;
|
|
327
|
+
}
|
|
328
|
+
return this._getSessionForProvider(
|
|
329
|
+
id,
|
|
330
|
+
server,
|
|
331
|
+
providerId,
|
|
332
|
+
resolvedScopes,
|
|
333
|
+
authorizationServer,
|
|
334
|
+
errorOnUserInteraction,
|
|
335
|
+
resolvedClientId,
|
|
336
|
+
authDetails.resourceMetadata?.resource,
|
|
337
|
+
undefined,
|
|
338
|
+
clientSecret
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
_ensureXaaIssuer() {
|
|
342
|
+
const config = this._configurationService.getValue(mcpEnterpriseManagedAuthIdpSection) ?? {};
|
|
343
|
+
const configuredIssuer = config.issuer?.trim();
|
|
344
|
+
if (!configuredIssuer) {
|
|
345
|
+
throw ( new Error(( localize(
|
|
346
|
+
4987,
|
|
347
|
+
"Enterprise-managed MCP authentication requires `mcp.enterpriseManagedAuth.idp.issuer` to be configured. Set it via enterprise policy (Windows Group Policy / macOS managed preferences / Linux `/etc/vscode/policy.json`) or, for local testing, by hand-editing `settings.json`."
|
|
348
|
+
))));
|
|
349
|
+
}
|
|
350
|
+
let parsed;
|
|
351
|
+
try {
|
|
352
|
+
parsed = ( URI.parse(configuredIssuer));
|
|
353
|
+
} catch {
|
|
354
|
+
throw ( new Error(( localize(
|
|
355
|
+
4988,
|
|
356
|
+
"Enterprise-managed MCP authentication requires `mcp.enterpriseManagedAuth.idp.issuer` to be a valid URL; got '{0}'.",
|
|
357
|
+
configuredIssuer
|
|
358
|
+
))));
|
|
359
|
+
}
|
|
360
|
+
if (parsed.scheme !== "https" && parsed.scheme !== "http") {
|
|
361
|
+
throw ( new Error(( localize(
|
|
362
|
+
4989,
|
|
363
|
+
"Enterprise-managed MCP authentication requires `mcp.enterpriseManagedAuth.idp.issuer` to use the `https` or `http` scheme; got '{0}'.",
|
|
364
|
+
configuredIssuer
|
|
365
|
+
))));
|
|
366
|
+
}
|
|
367
|
+
return parsed;
|
|
368
|
+
}
|
|
369
|
+
async _getSessionForProvider(
|
|
370
|
+
serverId,
|
|
371
|
+
server,
|
|
372
|
+
providerId,
|
|
373
|
+
scopes,
|
|
374
|
+
authorizationServer,
|
|
375
|
+
errorOnUserInteraction = false,
|
|
376
|
+
clientId,
|
|
377
|
+
resource,
|
|
378
|
+
audience,
|
|
379
|
+
clientSecret
|
|
380
|
+
) {
|
|
381
|
+
const authContext = {
|
|
382
|
+
authorizationServer,
|
|
383
|
+
clientId,
|
|
384
|
+
resource,
|
|
385
|
+
audience
|
|
386
|
+
};
|
|
387
|
+
const sessions = await this._authenticationService.getSessions(providerId, scopes, {
|
|
388
|
+
authorizationServer,
|
|
389
|
+
clientId,
|
|
390
|
+
clientSecret,
|
|
391
|
+
resource,
|
|
392
|
+
audience
|
|
393
|
+
}, true);
|
|
394
|
+
if (server.launch.type !== McpServerTransportType.HTTP) {
|
|
395
|
+
return undefined;
|
|
396
|
+
}
|
|
397
|
+
const mcpServerUrl = ( server.launch.uri.toString(true));
|
|
398
|
+
const accountNamePreference = this.authenticationMcpServersService.getAccountPreference(server.id, providerId);
|
|
399
|
+
let matchingAccountPreferenceSession;
|
|
400
|
+
if (accountNamePreference) {
|
|
401
|
+
matchingAccountPreferenceSession = sessions.find(session => session.account.label === accountNamePreference);
|
|
402
|
+
}
|
|
403
|
+
const provider = this._authenticationService.getProvider(providerId);
|
|
404
|
+
let session;
|
|
405
|
+
if (sessions.length) {
|
|
406
|
+
if (matchingAccountPreferenceSession && this.authenticationMCPServerAccessService.isAccessAllowedForUrl(
|
|
407
|
+
providerId,
|
|
408
|
+
matchingAccountPreferenceSession.account.label,
|
|
409
|
+
server.id,
|
|
410
|
+
mcpServerUrl
|
|
411
|
+
)) {
|
|
412
|
+
this.authenticationMCPServerUsageService.addAccountUsage(
|
|
413
|
+
providerId,
|
|
414
|
+
matchingAccountPreferenceSession.account.label,
|
|
415
|
+
scopes,
|
|
416
|
+
server.id,
|
|
417
|
+
server.label
|
|
418
|
+
);
|
|
419
|
+
this._serverAuthTracking.track(providerId, serverId, scopes, authContext);
|
|
420
|
+
return matchingAccountPreferenceSession.accessToken;
|
|
421
|
+
}
|
|
422
|
+
if (!provider.supportsMultipleAccounts && this.authenticationMCPServerAccessService.isAccessAllowedForUrl(providerId, sessions[0].account.label, server.id, mcpServerUrl)) {
|
|
423
|
+
this.authenticationMCPServerUsageService.addAccountUsage(providerId, sessions[0].account.label, scopes, server.id, server.label);
|
|
424
|
+
this._serverAuthTracking.track(providerId, serverId, scopes, authContext);
|
|
425
|
+
return sessions[0].accessToken;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (errorOnUserInteraction) {
|
|
429
|
+
throw ( new UserInteractionRequiredError("authentication"));
|
|
430
|
+
}
|
|
431
|
+
const isAllowed = await this.loginPrompt(server.label, provider.label, false);
|
|
432
|
+
if (!isAllowed) {
|
|
433
|
+
throw ( new Error("User did not consent to login."));
|
|
434
|
+
}
|
|
435
|
+
if (sessions.length) {
|
|
436
|
+
if (provider.supportsMultipleAccounts && errorOnUserInteraction) {
|
|
437
|
+
throw ( new UserInteractionRequiredError("authentication"));
|
|
438
|
+
}
|
|
439
|
+
session = provider.supportsMultipleAccounts ? await this.authenticationMcpServersService.selectSession(providerId, server.id, server.label, scopes, sessions) : sessions[0];
|
|
440
|
+
} else {
|
|
441
|
+
if (errorOnUserInteraction) {
|
|
442
|
+
throw ( new UserInteractionRequiredError("authentication"));
|
|
443
|
+
}
|
|
444
|
+
const accountToCreate = matchingAccountPreferenceSession?.account;
|
|
445
|
+
do {
|
|
446
|
+
session = await this._authenticationService.createSession(providerId, scopes, {
|
|
447
|
+
activateImmediate: true,
|
|
448
|
+
account: accountToCreate,
|
|
449
|
+
authorizationServer,
|
|
450
|
+
clientId,
|
|
451
|
+
clientSecret,
|
|
452
|
+
resource,
|
|
453
|
+
audience
|
|
454
|
+
});
|
|
455
|
+
} while (accountToCreate && accountToCreate.label !== session.account.label && !(await this.continueWithIncorrectAccountPrompt(session.account.label, accountToCreate.label)));
|
|
456
|
+
}
|
|
457
|
+
this.authenticationMCPServerAccessService.updateAllowedMcpServers(providerId, session.account.label, [{
|
|
458
|
+
id: server.id,
|
|
459
|
+
name: server.label,
|
|
460
|
+
allowed: true,
|
|
461
|
+
url: mcpServerUrl
|
|
462
|
+
}]);
|
|
463
|
+
this.authenticationMcpServersService.updateAccountPreference(server.id, providerId, session.account);
|
|
464
|
+
this.authenticationMCPServerUsageService.addAccountUsage(providerId, session.account.label, scopes, server.id, server.label);
|
|
465
|
+
this._serverAuthTracking.track(providerId, serverId, scopes, authContext);
|
|
466
|
+
return session.accessToken;
|
|
467
|
+
}
|
|
468
|
+
async continueWithIncorrectAccountPrompt(chosenAccountLabel, requestedAccountLabel) {
|
|
469
|
+
const result = await this.dialogService.prompt({
|
|
470
|
+
message: ( localize(4990, "Incorrect account detected")),
|
|
471
|
+
detail: ( localize(
|
|
472
|
+
4991,
|
|
473
|
+
"The chosen account, {0}, does not match the requested account, {1}.",
|
|
474
|
+
chosenAccountLabel,
|
|
475
|
+
requestedAccountLabel
|
|
476
|
+
)),
|
|
477
|
+
type: Severity.Warning,
|
|
478
|
+
cancelButton: true,
|
|
479
|
+
buttons: [{
|
|
480
|
+
label: ( localize(4992, "Keep {0}", chosenAccountLabel)),
|
|
481
|
+
run: () => chosenAccountLabel
|
|
482
|
+
}, {
|
|
483
|
+
label: ( localize(4993, "Login with {0}", requestedAccountLabel)),
|
|
484
|
+
run: () => requestedAccountLabel
|
|
485
|
+
}]
|
|
486
|
+
});
|
|
487
|
+
if (!result.result) {
|
|
488
|
+
throw ( new CancellationError());
|
|
489
|
+
}
|
|
490
|
+
return result.result === chosenAccountLabel;
|
|
491
|
+
}
|
|
492
|
+
async _onDidChangeAuthSessions(providerId, providerLabel) {
|
|
493
|
+
const serversUsingProvider = this._serverAuthTracking.get(providerId);
|
|
494
|
+
if (!serversUsingProvider) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
for (const {
|
|
498
|
+
serverId,
|
|
499
|
+
scopes,
|
|
500
|
+
context
|
|
501
|
+
} of serversUsingProvider) {
|
|
502
|
+
const server = this._servers.get(serverId);
|
|
503
|
+
const serverDefinition = this._serverDefinitions.get(serverId);
|
|
504
|
+
if (!server || !serverDefinition) {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const state = server.state.get();
|
|
508
|
+
if (state.state !== McpConnectionState.Kind.Running) {
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
try {
|
|
512
|
+
await this._getSessionForProvider(
|
|
513
|
+
serverId,
|
|
514
|
+
serverDefinition,
|
|
515
|
+
providerId,
|
|
516
|
+
scopes,
|
|
517
|
+
context.authorizationServer,
|
|
518
|
+
true,
|
|
519
|
+
context.clientId,
|
|
520
|
+
context.resource,
|
|
521
|
+
context.audience
|
|
522
|
+
);
|
|
523
|
+
} catch (e) {
|
|
524
|
+
if (UserInteractionRequiredError.is(e)) {
|
|
525
|
+
server.pushLog(LogLevel.Warning, ( localize(
|
|
526
|
+
4994,
|
|
527
|
+
"Authentication session for {0} removed, stopping server",
|
|
528
|
+
providerLabel
|
|
529
|
+
)));
|
|
530
|
+
server.stop();
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
$logMcpAuthSetup(data) {
|
|
536
|
+
this._telemetryService.publicLog2("mcp/authSetup", data);
|
|
537
|
+
}
|
|
538
|
+
async $startMcpGateway(chatSessionResource) {
|
|
539
|
+
const result = await this._mcpGatewayService.createGateway(
|
|
540
|
+
this._extHostContext.extensionHostKind === ExtensionHostKind.Remote,
|
|
541
|
+
chatSessionResource ? URI.revive(chatSessionResource) : undefined
|
|
542
|
+
);
|
|
543
|
+
if (!result) {
|
|
544
|
+
return undefined;
|
|
545
|
+
}
|
|
546
|
+
if (this._store.isDisposed) {
|
|
547
|
+
result.dispose();
|
|
548
|
+
return undefined;
|
|
549
|
+
}
|
|
550
|
+
const gatewayId = generateUuid();
|
|
551
|
+
const store = ( new DisposableStore());
|
|
552
|
+
store.add(result);
|
|
553
|
+
store.add(result.onDidChangeServers(servers => {
|
|
554
|
+
this._proxy.$onDidChangeGatewayServers(gatewayId, servers.map(s => ({
|
|
555
|
+
label: s.label,
|
|
556
|
+
address: s.address
|
|
557
|
+
})));
|
|
558
|
+
}));
|
|
559
|
+
this._gateways.set(gatewayId, store);
|
|
560
|
+
return {
|
|
561
|
+
servers: result.servers.map(s => ({
|
|
562
|
+
label: s.label,
|
|
563
|
+
address: s.address
|
|
564
|
+
})),
|
|
565
|
+
gatewayId
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
$disposeMcpGateway(gatewayId) {
|
|
569
|
+
this._gateways.deleteAndDispose(gatewayId);
|
|
570
|
+
}
|
|
571
|
+
async loginPrompt(mcpLabel, providerLabel, recreatingSession) {
|
|
572
|
+
const message = recreatingSession ? ( localize(
|
|
573
|
+
4995,
|
|
574
|
+
"The MCP Server Definition '{0}' wants you to authenticate to {1}.",
|
|
575
|
+
mcpLabel,
|
|
576
|
+
providerLabel
|
|
577
|
+
)) : ( localize(
|
|
578
|
+
4996,
|
|
579
|
+
"The MCP Server Definition '{0}' wants to authenticate to {1}.",
|
|
580
|
+
mcpLabel,
|
|
581
|
+
providerLabel
|
|
582
|
+
));
|
|
583
|
+
const buttons = [{
|
|
584
|
+
label: ( localize(4997, "&&Allow")),
|
|
585
|
+
run() {
|
|
586
|
+
return true;
|
|
587
|
+
}
|
|
588
|
+
}];
|
|
589
|
+
const {
|
|
590
|
+
result
|
|
591
|
+
} = await this.dialogService.prompt({
|
|
592
|
+
type: Severity.Info,
|
|
593
|
+
message,
|
|
594
|
+
buttons,
|
|
595
|
+
cancelButton: true
|
|
596
|
+
});
|
|
597
|
+
return result ?? false;
|
|
598
|
+
}
|
|
599
|
+
dispose() {
|
|
600
|
+
for (const server of ( this._servers.values())) {
|
|
601
|
+
server.extHostDispose();
|
|
602
|
+
}
|
|
603
|
+
this._servers.clear();
|
|
604
|
+
this._serverDefinitions.clear();
|
|
605
|
+
this._serverAuthTracking.clear();
|
|
606
|
+
super.dispose();
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
MainThreadMcp = __decorate([extHostNamedCustomer(MainContext.MainThreadMcp), ( __param(1, IMcpRegistry)), ( __param(2, IDialogService)), ( __param(3, IAuthenticationService)), ( __param(4, IAuthenticationMcpService)), ( __param(5, IAuthenticationMcpAccessService)), ( __param(6, IAuthenticationMcpUsageService)), ( __param(7, IDynamicAuthenticationProviderStorageService)), ( __param(8, IExtensionService)), ( __param(9, IContextKeyService)), ( __param(10, ITelemetryService)), ( __param(11, IWorkbenchMcpGatewayService)), ( __param(12, IConfigurationService)), ( __param(13, ISecretStorageService))], MainThreadMcp);
|
|
610
|
+
class ExtHostMcpServerLaunch extends Disposable {
|
|
611
|
+
pushLog(level, message) {
|
|
612
|
+
this._onDidLog.fire({
|
|
613
|
+
message,
|
|
614
|
+
level
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
pushMessage(message) {
|
|
618
|
+
let parsed;
|
|
619
|
+
try {
|
|
620
|
+
parsed = JSON.parse(message);
|
|
621
|
+
} catch (e) {
|
|
622
|
+
this.pushLog(LogLevel.Warning, `Failed to parse message: ${JSON.stringify(message)}`);
|
|
623
|
+
}
|
|
624
|
+
if (parsed) {
|
|
625
|
+
if (Array.isArray(parsed)) {
|
|
626
|
+
parsed.forEach(p => this._onDidReceiveMessage.fire(p));
|
|
627
|
+
} else {
|
|
628
|
+
this._onDidReceiveMessage.fire(parsed);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
constructor(extHostKind, stop, send) {
|
|
633
|
+
super();
|
|
634
|
+
this.stop = stop;
|
|
635
|
+
this.send = send;
|
|
636
|
+
this.state = observableValue("mcpServerState", {
|
|
637
|
+
state: McpConnectionState.Kind.Starting
|
|
638
|
+
});
|
|
639
|
+
this._onDidLog = this._register(( new Emitter()));
|
|
640
|
+
this.onDidLog = this._onDidLog.event;
|
|
641
|
+
this._onDidReceiveMessage = this._register(( new Emitter()));
|
|
642
|
+
this.onDidReceiveMessage = this._onDidReceiveMessage.event;
|
|
643
|
+
this._register(disposableTimeout(() => {
|
|
644
|
+
this.pushLog(
|
|
645
|
+
LogLevel.Info,
|
|
646
|
+
`Starting server from ${extensionHostKindToString(extHostKind)} extension host`
|
|
647
|
+
);
|
|
648
|
+
}));
|
|
649
|
+
}
|
|
650
|
+
extHostDispose() {
|
|
651
|
+
if (McpConnectionState.isRunning(this.state.get())) {
|
|
652
|
+
this.pushLog(LogLevel.Warning, "Extension host shut down, server will stop.");
|
|
653
|
+
this.state.set({
|
|
654
|
+
state: McpConnectionState.Kind.Stopped
|
|
655
|
+
}, undefined);
|
|
656
|
+
}
|
|
657
|
+
this.dispose();
|
|
658
|
+
}
|
|
659
|
+
dispose() {
|
|
660
|
+
if (McpConnectionState.isRunning(this.state.get())) {
|
|
661
|
+
this.stop();
|
|
662
|
+
}
|
|
663
|
+
super.dispose();
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
class McpServerAuthTracker {
|
|
667
|
+
constructor() {
|
|
668
|
+
this._tracking = ( new Map());
|
|
669
|
+
}
|
|
670
|
+
track(providerId, serverId, scopes, context) {
|
|
671
|
+
const servers = this._tracking.get(providerId) || [];
|
|
672
|
+
const filtered = servers.filter(s => s.serverId !== serverId);
|
|
673
|
+
filtered.push({
|
|
674
|
+
serverId,
|
|
675
|
+
scopes,
|
|
676
|
+
context
|
|
677
|
+
});
|
|
678
|
+
this._tracking.set(providerId, filtered);
|
|
679
|
+
}
|
|
680
|
+
untrack(serverId) {
|
|
681
|
+
for (const [providerId, servers] of this._tracking.entries()) {
|
|
682
|
+
const filtered = servers.filter(s => s.serverId !== serverId);
|
|
683
|
+
if (filtered.length === 0) {
|
|
684
|
+
this._tracking.delete(providerId);
|
|
685
|
+
} else {
|
|
686
|
+
this._tracking.set(providerId, filtered);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
get(providerId) {
|
|
691
|
+
return this._tracking.get(providerId);
|
|
692
|
+
}
|
|
693
|
+
clear() {
|
|
694
|
+
this._tracking.clear();
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export { MainThreadMcp, McpServerAuthTracker };
|