Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/authentication/browser/authenticationMcpService.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, DisposableStore, dispose, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
7
import { scopesMatch } from '../../../../base/common/oauth.js';
8
import * as nls from '../../../../nls.js';
9
import { MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js';
10
import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
11
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
12
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
13
import { Severity } from '../../../../platform/notification/common/notification.js';
14
import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';
15
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
16
import { IActivityService, NumberBadge } from '../../activity/common/activity.js';
17
import { IAuthenticationMcpAccessService } from './authenticationMcpAccessService.js';
18
import { IAuthenticationMcpUsageService } from './authenticationMcpUsageService.js';
19
import { AuthenticationSession, IAuthenticationProvider, IAuthenticationService, AuthenticationSessionAccount } from '../common/authentication.js';
20
import { Emitter, Event } from '../../../../base/common/event.js';
21
import { IProductService } from '../../../../platform/product/common/productService.js';
22
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
23
24
// OAuth2 spec prohibits space in a scope, so use that to join them.
25
const SCOPESLIST_SEPARATOR = ' ';
26
27
interface SessionRequest {
28
disposables: IDisposable[];
29
requestingMcpServerIds: string[];
30
}
31
32
interface SessionRequestInfo {
33
[scopesList: string]: SessionRequest;
34
}
35
36
// TODO: Move this into MainThreadAuthentication
37
export const IAuthenticationMcpService = createDecorator<IAuthenticationMcpService>('IAuthenticationMcpService');
38
export interface IAuthenticationMcpService {
39
readonly _serviceBrand: undefined;
40
41
/**
42
* Fires when an account preference for a specific provider has changed for the specified MCP servers. Does not fire when:
43
* * An account preference is removed
44
* * A session preference is changed (because it's deprecated)
45
* * A session preference is removed (because it's deprecated)
46
*/
47
onDidChangeAccountPreference: Event<{ mcpServerIds: string[]; providerId: string }>;
48
/**
49
* Returns the accountName (also known as account.label) to pair with `IAuthenticationMCPServerAccessService` to get the account preference
50
* @param providerId The authentication provider id
51
* @param mcpServerId The MCP server id to get the preference for
52
* @returns The accountName of the preference, or undefined if there is no preference set
53
*/
54
getAccountPreference(mcpServerId: string, providerId: string): string | undefined;
55
/**
56
* Sets the account preference for the given provider and MCP server
57
* @param providerId The authentication provider id
58
* @param mcpServerId The MCP server id to set the preference for
59
* @param account The account to set the preference to
60
*/
61
updateAccountPreference(mcpServerId: string, providerId: string, account: AuthenticationSessionAccount): void;
62
/**
63
* Removes the account preference for the given provider and MCP server
64
* @param providerId The authentication provider id
65
* @param mcpServerId The MCP server id to remove the preference for
66
*/
67
removeAccountPreference(mcpServerId: string, providerId: string): void;
68
/**
69
* @deprecated Sets the session preference for the given provider and MCP server
70
* @param providerId
71
* @param mcpServerId
72
* @param session
73
*/
74
updateSessionPreference(providerId: string, mcpServerId: string, session: AuthenticationSession): void;
75
/**
76
* @deprecated Gets the session preference for the given provider and MCP server
77
* @param providerId
78
* @param mcpServerId
79
* @param scopes
80
*/
81
getSessionPreference(providerId: string, mcpServerId: string, scopes: string[]): string | undefined;
82
/**
83
* @deprecated Removes the session preference for the given provider and MCP server
84
* @param providerId
85
* @param mcpServerId
86
* @param scopes
87
*/
88
removeSessionPreference(providerId: string, mcpServerId: string, scopes: string[]): void;
89
selectSession(providerId: string, mcpServerId: string, mcpServerName: string, scopes: string[], possibleSessions: readonly AuthenticationSession[]): Promise<AuthenticationSession>;
90
requestSessionAccess(providerId: string, mcpServerId: string, mcpServerName: string, scopes: string[], possibleSessions: readonly AuthenticationSession[]): void;
91
requestNewSession(providerId: string, scopes: string[], mcpServerId: string, mcpServerName: string): Promise<void>;
92
}
93
94
// TODO@TylerLeonhardt: This should all go in MainThreadAuthentication
95
export class AuthenticationMcpService extends Disposable implements IAuthenticationMcpService {
96
declare readonly _serviceBrand: undefined;
97
private _signInRequestItems = new Map<string, SessionRequestInfo>();
98
private _sessionAccessRequestItems = new Map<string, { [mcpServerId: string]: { disposables: IDisposable[]; possibleSessions: AuthenticationSession[] } }>();
99
private readonly _accountBadgeDisposable = this._register(new MutableDisposable());
100
101
private _onDidAccountPreferenceChange: Emitter<{ providerId: string; mcpServerIds: string[] }> = this._register(new Emitter<{ providerId: string; mcpServerIds: string[] }>());
102
readonly onDidChangeAccountPreference = this._onDidAccountPreferenceChange.event;
103
104
private _inheritAuthAccountPreferenceParentToChildren: Record<string, string[]>;
105
private _inheritAuthAccountPreferenceChildToParent: { [mcpServerId: string]: string };
106
107
constructor(
108
@IActivityService private readonly activityService: IActivityService,
109
@IStorageService private readonly storageService: IStorageService,
110
@IDialogService private readonly dialogService: IDialogService,
111
@IQuickInputService private readonly quickInputService: IQuickInputService,
112
@IProductService private readonly _productService: IProductService,
113
@IAuthenticationService private readonly _authenticationService: IAuthenticationService,
114
@IAuthenticationMcpUsageService private readonly _authenticationUsageService: IAuthenticationMcpUsageService,
115
@IAuthenticationMcpAccessService private readonly _authenticationAccessService: IAuthenticationMcpAccessService
116
) {
117
super();
118
this._inheritAuthAccountPreferenceParentToChildren = this._productService.inheritAuthAccountPreference || {};
119
this._inheritAuthAccountPreferenceChildToParent = Object.entries(this._inheritAuthAccountPreferenceParentToChildren).reduce<{ [mcpServerId: string]: string }>((acc, [parent, children]) => {
120
children.forEach((child: string) => {
121
acc[child] = parent;
122
});
123
return acc;
124
}, {});
125
this.registerListeners();
126
}
127
128
private registerListeners() {
129
this._register(this._authenticationService.onDidChangeSessions(async e => {
130
if (e.event.added?.length) {
131
await this.updateNewSessionRequests(e.providerId, e.event.added);
132
}
133
if (e.event.removed?.length) {
134
await this.updateAccessRequests(e.providerId, e.event.removed);
135
}
136
this.updateBadgeCount();
137
}));
138
139
this._register(this._authenticationService.onDidUnregisterAuthenticationProvider(e => {
140
const accessRequests = this._sessionAccessRequestItems.get(e.id) || {};
141
Object.keys(accessRequests).forEach(mcpServerId => {
142
this.removeAccessRequest(e.id, mcpServerId);
143
});
144
}));
145
}
146
147
private async updateNewSessionRequests(providerId: string, addedSessions: readonly AuthenticationSession[]): Promise<void> {
148
const existingRequestsForProvider = this._signInRequestItems.get(providerId);
149
if (!existingRequestsForProvider) {
150
return;
151
}
152
153
Object.keys(existingRequestsForProvider).forEach(requestedScopes => {
154
// Parse the requested scopes from the stored key
155
const requestedScopesArray = requestedScopes.split(SCOPESLIST_SEPARATOR);
156
157
// Check if any added session has matching scopes (order-independent)
158
if (addedSessions.some(session => scopesMatch(session.scopes, requestedScopesArray))) {
159
const sessionRequest = existingRequestsForProvider[requestedScopes];
160
sessionRequest?.disposables.forEach(item => item.dispose());
161
162
delete existingRequestsForProvider[requestedScopes];
163
if (Object.keys(existingRequestsForProvider).length === 0) {
164
this._signInRequestItems.delete(providerId);
165
} else {
166
this._signInRequestItems.set(providerId, existingRequestsForProvider);
167
}
168
}
169
});
170
}
171
172
private async updateAccessRequests(providerId: string, removedSessions: readonly AuthenticationSession[]) {
173
const providerRequests = this._sessionAccessRequestItems.get(providerId);
174
if (providerRequests) {
175
Object.keys(providerRequests).forEach(mcpServerId => {
176
removedSessions.forEach(removed => {
177
const indexOfSession = providerRequests[mcpServerId].possibleSessions.findIndex(session => session.id === removed.id);
178
if (indexOfSession) {
179
providerRequests[mcpServerId].possibleSessions.splice(indexOfSession, 1);
180
}
181
});
182
183
if (!providerRequests[mcpServerId].possibleSessions.length) {
184
this.removeAccessRequest(providerId, mcpServerId);
185
}
186
});
187
}
188
}
189
190
private updateBadgeCount(): void {
191
this._accountBadgeDisposable.clear();
192
193
let numberOfRequests = 0;
194
this._signInRequestItems.forEach(providerRequests => {
195
Object.keys(providerRequests).forEach(request => {
196
numberOfRequests += providerRequests[request].requestingMcpServerIds.length;
197
});
198
});
199
200
this._sessionAccessRequestItems.forEach(accessRequest => {
201
numberOfRequests += Object.keys(accessRequest).length;
202
});
203
204
if (numberOfRequests > 0) {
205
const badge = new NumberBadge(numberOfRequests, () => nls.localize('sign in', "Sign in requested"));
206
this._accountBadgeDisposable.value = this.activityService.showAccountsActivity({ badge });
207
}
208
}
209
210
private removeAccessRequest(providerId: string, mcpServerId: string): void {
211
const providerRequests = this._sessionAccessRequestItems.get(providerId) || {};
212
if (providerRequests[mcpServerId]) {
213
dispose(providerRequests[mcpServerId].disposables);
214
delete providerRequests[mcpServerId];
215
this.updateBadgeCount();
216
}
217
}
218
219
//#region Account/Session Preference
220
221
updateAccountPreference(mcpServerId: string, providerId: string, account: AuthenticationSessionAccount): void {
222
const parentMcpServerId = this._inheritAuthAccountPreferenceChildToParent[mcpServerId] ?? mcpServerId;
223
const key = this._getKey(parentMcpServerId, providerId);
224
225
// Store the preference in the workspace and application storage. This allows new workspaces to
226
// have a preference set already to limit the number of prompts that are shown... but also allows
227
// a specific workspace to override the global preference.
228
this.storageService.store(key, account.label, StorageScope.WORKSPACE, StorageTarget.MACHINE);
229
this.storageService.store(key, account.label, StorageScope.APPLICATION, StorageTarget.MACHINE);
230
231
const childrenMcpServers = this._inheritAuthAccountPreferenceParentToChildren[parentMcpServerId];
232
const mcpServerIds = childrenMcpServers ? [parentMcpServerId, ...childrenMcpServers] : [parentMcpServerId];
233
this._onDidAccountPreferenceChange.fire({ mcpServerIds, providerId });
234
}
235
236
getAccountPreference(mcpServerId: string, providerId: string): string | undefined {
237
const key = this._getKey(this._inheritAuthAccountPreferenceChildToParent[mcpServerId] ?? mcpServerId, providerId);
238
239
// If a preference is set in the workspace, use that. Otherwise, use the global preference.
240
return this.storageService.get(key, StorageScope.WORKSPACE) ?? this.storageService.get(key, StorageScope.APPLICATION);
241
}
242
243
removeAccountPreference(mcpServerId: string, providerId: string): void {
244
const key = this._getKey(this._inheritAuthAccountPreferenceChildToParent[mcpServerId] ?? mcpServerId, providerId);
245
246
// This won't affect any other workspaces that have a preference set, but it will remove the preference
247
// for this workspace and the global preference. This is only paired with a call to updateSessionPreference...
248
// so we really don't _need_ to remove them as they are about to be overridden anyway... but it's more correct
249
// to remove them first... and in case this gets called from somewhere else in the future.
250
this.storageService.remove(key, StorageScope.WORKSPACE);
251
this.storageService.remove(key, StorageScope.APPLICATION);
252
}
253
254
private _getKey(mcpServerId: string, providerId: string): string {
255
return `${mcpServerId}-${providerId}`;
256
}
257
258
// TODO@TylerLeonhardt: Remove all of this after a couple iterations
259
260
updateSessionPreference(providerId: string, mcpServerId: string, session: AuthenticationSession): void {
261
// The 3 parts of this key are important:
262
// * MCP server id: The MCP server that has a preference
263
// * Provider id: The provider that the preference is for
264
// * The scopes: The subset of sessions that the preference applies to
265
const key = `${mcpServerId}-${providerId}-${session.scopes.join(SCOPESLIST_SEPARATOR)}`;
266
267
// Store the preference in the workspace and application storage. This allows new workspaces to
268
// have a preference set already to limit the number of prompts that are shown... but also allows
269
// a specific workspace to override the global preference.
270
this.storageService.store(key, session.id, StorageScope.WORKSPACE, StorageTarget.MACHINE);
271
this.storageService.store(key, session.id, StorageScope.APPLICATION, StorageTarget.MACHINE);
272
}
273
274
getSessionPreference(providerId: string, mcpServerId: string, scopes: string[]): string | undefined {
275
// The 3 parts of this key are important:
276
// * MCP server id: The MCP server that has a preference
277
// * Provider id: The provider that the preference is for
278
// * The scopes: The subset of sessions that the preference applies to
279
const key = `${mcpServerId}-${providerId}-${scopes.join(SCOPESLIST_SEPARATOR)}`;
280
281
// If a preference is set in the workspace, use that. Otherwise, use the global preference.
282
return this.storageService.get(key, StorageScope.WORKSPACE) ?? this.storageService.get(key, StorageScope.APPLICATION);
283
}
284
285
removeSessionPreference(providerId: string, mcpServerId: string, scopes: string[]): void {
286
// The 3 parts of this key are important:
287
// * MCP server id: The MCP server that has a preference
288
// * Provider id: The provider that the preference is for
289
// * The scopes: The subset of sessions that the preference applies to
290
const key = `${mcpServerId}-${providerId}-${scopes.join(SCOPESLIST_SEPARATOR)}`;
291
292
// This won't affect any other workspaces that have a preference set, but it will remove the preference
293
// for this workspace and the global preference. This is only paired with a call to updateSessionPreference...
294
// so we really don't _need_ to remove them as they are about to be overridden anyway... but it's more correct
295
// to remove them first... and in case this gets called from somewhere else in the future.
296
this.storageService.remove(key, StorageScope.WORKSPACE);
297
this.storageService.remove(key, StorageScope.APPLICATION);
298
}
299
300
private _updateAccountAndSessionPreferences(providerId: string, mcpServerId: string, session: AuthenticationSession): void {
301
this.updateAccountPreference(mcpServerId, providerId, session.account);
302
this.updateSessionPreference(providerId, mcpServerId, session);
303
}
304
305
//#endregion
306
307
private async showGetSessionPrompt(provider: IAuthenticationProvider, accountName: string, mcpServerId: string, mcpServerName: string): Promise<boolean> {
308
enum SessionPromptChoice {
309
Allow = 0,
310
Deny = 1,
311
Cancel = 2
312
}
313
const { result } = await this.dialogService.prompt<SessionPromptChoice>({
314
type: Severity.Info,
315
message: nls.localize('confirmAuthenticationAccess', "The MCP server '{0}' wants to access the {1} account '{2}'.", mcpServerName, provider.label, accountName),
316
buttons: [
317
{
318
label: nls.localize({ key: 'allow', comment: ['&& denotes a mnemonic'] }, "&&Allow"),
319
run: () => SessionPromptChoice.Allow
320
},
321
{
322
label: nls.localize({ key: 'deny', comment: ['&& denotes a mnemonic'] }, "&&Deny"),
323
run: () => SessionPromptChoice.Deny
324
}
325
],
326
cancelButton: {
327
run: () => SessionPromptChoice.Cancel
328
}
329
});
330
331
if (result !== SessionPromptChoice.Cancel) {
332
this._authenticationAccessService.updateAllowedMcpServers(provider.id, accountName, [{ id: mcpServerId, name: mcpServerName, allowed: result === SessionPromptChoice.Allow }]);
333
this.removeAccessRequest(provider.id, mcpServerId);
334
}
335
336
return result === SessionPromptChoice.Allow;
337
}
338
339
/**
340
* This function should be used only when there are sessions to disambiguate.
341
*/
342
async selectSession(providerId: string, mcpServerId: string, mcpServerName: string, scopes: string[], availableSessions: AuthenticationSession[]): Promise<AuthenticationSession> {
343
const allAccounts = await this._authenticationService.getAccounts(providerId);
344
if (!allAccounts.length) {
345
throw new Error('No accounts available');
346
}
347
const disposables = new DisposableStore();
348
const quickPick = disposables.add(this.quickInputService.createQuickPick<{ label: string; session?: AuthenticationSession; account?: AuthenticationSessionAccount }>());
349
quickPick.ignoreFocusOut = true;
350
const accountsWithSessions = new Set<string>();
351
const items: { label: string; session?: AuthenticationSession; account?: AuthenticationSessionAccount }[] = availableSessions
352
// Only grab the first account
353
.filter(session => !accountsWithSessions.has(session.account.label) && accountsWithSessions.add(session.account.label))
354
.map(session => {
355
return {
356
label: session.account.label,
357
session: session
358
};
359
});
360
361
// Add the additional accounts that have been logged into the provider but are
362
// don't have a session yet.
363
allAccounts.forEach(account => {
364
if (!accountsWithSessions.has(account.label)) {
365
items.push({ label: account.label, account });
366
}
367
});
368
items.push({ label: nls.localize('useOtherAccount', "Sign in to another account") });
369
quickPick.items = items;
370
quickPick.title = nls.localize(
371
{
372
key: 'selectAccount',
373
comment: ['The placeholder {0} is the name of a MCP server. {1} is the name of the type of account, such as Microsoft or GitHub.']
374
},
375
"The MCP server '{0}' wants to access a {1} account",
376
mcpServerName,
377
this._authenticationService.getProvider(providerId).label
378
);
379
quickPick.placeholder = nls.localize('getSessionPlateholder', "Select an account for '{0}' to use or Esc to cancel", mcpServerName);
380
381
return await new Promise((resolve, reject) => {
382
disposables.add(quickPick.onDidAccept(async _ => {
383
quickPick.dispose();
384
let session = quickPick.selectedItems[0].session;
385
if (!session) {
386
const account = quickPick.selectedItems[0].account;
387
try {
388
session = await this._authenticationService.createSession(providerId, scopes, { account });
389
} catch (e) {
390
reject(e);
391
return;
392
}
393
}
394
const accountName = session.account.label;
395
396
this._authenticationAccessService.updateAllowedMcpServers(providerId, accountName, [{ id: mcpServerId, name: mcpServerName, allowed: true }]);
397
this._updateAccountAndSessionPreferences(providerId, mcpServerId, session);
398
this.removeAccessRequest(providerId, mcpServerId);
399
400
resolve(session);
401
}));
402
403
disposables.add(quickPick.onDidHide(_ => {
404
if (!quickPick.selectedItems[0]) {
405
reject('User did not consent to account access');
406
}
407
disposables.dispose();
408
}));
409
410
quickPick.show();
411
});
412
}
413
414
private async completeSessionAccessRequest(provider: IAuthenticationProvider, mcpServerId: string, mcpServerName: string, scopes: string[]): Promise<void> {
415
const providerRequests = this._sessionAccessRequestItems.get(provider.id) || {};
416
const existingRequest = providerRequests[mcpServerId];
417
if (!existingRequest) {
418
return;
419
}
420
421
if (!provider) {
422
return;
423
}
424
const possibleSessions = existingRequest.possibleSessions;
425
426
let session: AuthenticationSession | undefined;
427
if (provider.supportsMultipleAccounts) {
428
try {
429
session = await this.selectSession(provider.id, mcpServerId, mcpServerName, scopes, possibleSessions);
430
} catch (_) {
431
// ignore cancel
432
}
433
} else {
434
const approved = await this.showGetSessionPrompt(provider, possibleSessions[0].account.label, mcpServerId, mcpServerName);
435
if (approved) {
436
session = possibleSessions[0];
437
}
438
}
439
440
if (session) {
441
this._authenticationUsageService.addAccountUsage(provider.id, session.account.label, session.scopes, mcpServerId, mcpServerName);
442
}
443
}
444
445
requestSessionAccess(providerId: string, mcpServerId: string, mcpServerName: string, scopes: string[], possibleSessions: AuthenticationSession[]): void {
446
const providerRequests = this._sessionAccessRequestItems.get(providerId) || {};
447
const hasExistingRequest = providerRequests[mcpServerId];
448
if (hasExistingRequest) {
449
return;
450
}
451
452
const provider = this._authenticationService.getProvider(providerId);
453
const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
454
group: '3_accessRequests',
455
command: {
456
id: `${providerId}${mcpServerId}Access`,
457
title: nls.localize({
458
key: 'accessRequest',
459
comment: [`The placeholder {0} will be replaced with an authentication provider''s label. {1} will be replaced with a MCP server name. (1) is to indicate that this menu item contributes to a badge count`]
460
},
461
"Grant access to {0} for {1}... (1)",
462
provider.label,
463
mcpServerName)
464
}
465
});
466
467
const accessCommand = CommandsRegistry.registerCommand({
468
id: `${providerId}${mcpServerId}Access`,
469
handler: async (accessor) => {
470
this.completeSessionAccessRequest(provider, mcpServerId, mcpServerName, scopes);
471
}
472
});
473
474
providerRequests[mcpServerId] = { possibleSessions, disposables: [menuItem, accessCommand] };
475
this._sessionAccessRequestItems.set(providerId, providerRequests);
476
this.updateBadgeCount();
477
}
478
479
async requestNewSession(providerId: string, scopes: string[], mcpServerId: string, mcpServerName: string): Promise<void> {
480
if (!this._authenticationService.isAuthenticationProviderRegistered(providerId)) {
481
// Activate has already been called for the authentication provider, but it cannot block on registering itself
482
// since this is sync and returns a disposable. So, wait for registration event to fire that indicates the
483
// provider is now in the map.
484
await new Promise<void>((resolve, _) => {
485
const dispose = this._authenticationService.onDidRegisterAuthenticationProvider(e => {
486
if (e.id === providerId) {
487
dispose.dispose();
488
resolve();
489
}
490
});
491
});
492
}
493
494
let provider: IAuthenticationProvider;
495
try {
496
provider = this._authenticationService.getProvider(providerId);
497
} catch (_e) {
498
return;
499
}
500
501
const providerRequests = this._signInRequestItems.get(providerId);
502
const scopesList = scopes.join(SCOPESLIST_SEPARATOR);
503
const mcpServerHasExistingRequest = providerRequests
504
&& providerRequests[scopesList]
505
&& providerRequests[scopesList].requestingMcpServerIds.includes(mcpServerId);
506
507
if (mcpServerHasExistingRequest) {
508
return;
509
}
510
511
// Construct a commandId that won't clash with others generated here, nor likely with an MCP server's command
512
const commandId = `${providerId}:${mcpServerId}:signIn${Object.keys(providerRequests || []).length}`;
513
const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
514
group: '2_signInRequests',
515
command: {
516
id: commandId,
517
title: nls.localize({
518
key: 'signInRequest',
519
comment: [`The placeholder {0} will be replaced with an authentication provider's label. {1} will be replaced with a MCP server name. (1) is to indicate that this menu item contributes to a badge count.`]
520
},
521
"Sign in with {0} to use {1} (1)",
522
provider.label,
523
mcpServerName)
524
}
525
});
526
527
const signInCommand = CommandsRegistry.registerCommand({
528
id: commandId,
529
handler: async (accessor) => {
530
const authenticationService = accessor.get(IAuthenticationService);
531
const session = await authenticationService.createSession(providerId, scopes);
532
533
this._authenticationAccessService.updateAllowedMcpServers(providerId, session.account.label, [{ id: mcpServerId, name: mcpServerName, allowed: true }]);
534
this._updateAccountAndSessionPreferences(providerId, mcpServerId, session);
535
}
536
});
537
538
539
if (providerRequests) {
540
const existingRequest = providerRequests[scopesList] || { disposables: [], requestingMcpServerIds: [] };
541
542
providerRequests[scopesList] = {
543
disposables: [...existingRequest.disposables, menuItem, signInCommand],
544
requestingMcpServerIds: [...existingRequest.requestingMcpServerIds, mcpServerId]
545
};
546
this._signInRequestItems.set(providerId, providerRequests);
547
} else {
548
this._signInRequestItems.set(providerId, {
549
[scopesList]: {
550
disposables: [menuItem, signInCommand],
551
requestingMcpServerIds: [mcpServerId]
552
}
553
});
554
}
555
556
this.updateBadgeCount();
557
}
558
}
559
560
registerSingleton(IAuthenticationMcpService, AuthenticationMcpService, InstantiationType.Delayed);
561
562