Skip to content

Commit b5ad5e3

Browse files
Include package.json logic in underlying MCP access service (microsoft#252110)
And simplify command. There's more we can simplify now that this is done, but that can come later.
1 parent 4c0b471 commit b5ad5e3

File tree

7 files changed

+844
-126
lines changed

7 files changed

+844
-126
lines changed

src/vs/workbench/api/browser/mainThreadMcp.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,13 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape {
166166
// If we have an existing session preference, use that. If not, we'll return any valid session at the end of this function.
167167
if (matchingAccountPreferenceSession && this.authenticationMCPServerAccessService.isAccessAllowed(providerId, matchingAccountPreferenceSession.account.label, server.id)) {
168168
this._mcpRegistry.setAuthenticationUsage(server.id, providerId);
169+
this.authenticationMCPServerUsageService.addAccountUsage(providerId, matchingAccountPreferenceSession.account.label, scopesSupported, server.id, server.label);
169170
return matchingAccountPreferenceSession.accessToken;
170171
}
171172
// If we only have one account for a single auth provider, lets just check if it's allowed and return it if it is.
172173
if (!provider.supportsMultipleAccounts && this.authenticationMCPServerAccessService.isAccessAllowed(providerId, sessions[0].account.label, server.id)) {
173174
this._mcpRegistry.setAuthenticationUsage(server.id, providerId);
175+
this.authenticationMCPServerUsageService.addAccountUsage(providerId, sessions[0].account.label, scopesSupported, server.id, server.label);
174176
return sessions[0].accessToken;
175177
}
176178
}

src/vs/workbench/contrib/authentication/browser/actions/manageTrustedMcpServersForAccountAction.ts

Lines changed: 81 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ import { Action2 } from '../../../../../platform/actions/common/actions.js';
1212
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
1313
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
1414
import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
15-
import { IProductService } from '../../../../../platform/product/common/productService.js';
1615
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../../platform/quickinput/common/quickInput.js';
17-
import { AllowedMcpServer, IAuthenticationMcpAccessService } from '../../../../services/authentication/browser/authenticationMcpAccessService.js';
18-
import { IAuthenticationMcpUsageService } from '../../../../services/authentication/browser/authenticationMcpUsageService.js';
16+
import { AllowedMcpServer } from '../../../../services/authentication/browser/authenticationMcpAccessService.js';
1917
import { IAuthenticationService } from '../../../../services/authentication/common/authentication.js';
18+
import { IAuthenticationQueryService, IAccountQuery } from '../../../../services/authentication/common/authenticationQuery.js';
2019
import { IMcpService } from '../../../mcp/common/mcpTypes.js';
2120

2221
export class ManageTrustedMcpServersForAccountAction extends Action2 {
@@ -42,135 +41,101 @@ interface TrustedMcpServersQuickPickItem extends IQuickPickItem {
4241

4342
class ManageTrustedMcpServersForAccountActionImpl {
4443
constructor(
45-
@IProductService private readonly _productService: IProductService,
4644
@IMcpService private readonly _mcpServerService: IMcpService,
4745
@IDialogService private readonly _dialogService: IDialogService,
4846
@IQuickInputService private readonly _quickInputService: IQuickInputService,
4947
@IAuthenticationService private readonly _mcpServerAuthenticationService: IAuthenticationService,
50-
@IAuthenticationMcpUsageService private readonly _mcpServerAuthenticationUsageService: IAuthenticationMcpUsageService,
51-
@IAuthenticationMcpAccessService private readonly _mcpServerAuthenticationAccessService: IAuthenticationMcpAccessService,
48+
@IAuthenticationQueryService private readonly _authenticationQueryService: IAuthenticationQueryService,
5249
@ICommandService private readonly _commandService: ICommandService
5350
) { }
5451

5552
async run(options?: { providerId: string; accountLabel: string }) {
56-
const { providerId, accountLabel } = await this._resolveProviderAndAccountLabel(options?.providerId, options?.accountLabel);
57-
if (!providerId || !accountLabel) {
53+
const accountQuery = await this._resolveAccountQuery(options?.providerId, options?.accountLabel);
54+
if (!accountQuery) {
5855
return;
5956
}
6057

61-
const items = await this._getItems(providerId, accountLabel);
58+
const items = await this._getItems(accountQuery);
6259
if (!items.length) {
6360
return;
6461
}
65-
const disposables = new DisposableStore();
66-
const picker = this._createQuickPick(disposables, providerId, accountLabel);
62+
const picker = this._createQuickPick(accountQuery);
6763
picker.items = items;
6864
picker.selectedItems = items.filter((i): i is TrustedMcpServersQuickPickItem => i.type !== 'separator' && !!i.picked);
6965
picker.show();
7066
}
7167

72-
private async _resolveProviderAndAccountLabel(providerId: string | undefined, accountLabel: string | undefined) {
73-
if (!providerId || !accountLabel) {
74-
const accounts = new Array<{ providerId: string; providerLabel: string; accountLabel: string }>();
75-
for (const id of this._mcpServerAuthenticationService.getProviderIds()) {
76-
const providerLabel = this._mcpServerAuthenticationService.getProvider(id).label;
77-
const sessions = await this._mcpServerAuthenticationService.getSessions(id);
78-
const uniqueAccountLabels = new Set<string>();
79-
for (const session of sessions) {
80-
if (!uniqueAccountLabels.has(session.account.label)) {
81-
uniqueAccountLabels.add(session.account.label);
82-
accounts.push({ providerId: id, providerLabel, accountLabel: session.account.label });
83-
}
84-
}
85-
}
86-
87-
const pick = await this._quickInputService.pick(
88-
accounts.map(account => ({
89-
providerId: account.providerId,
90-
label: account.accountLabel,
91-
description: account.providerLabel
92-
})),
93-
{
94-
placeHolder: localize('pickAccount', "Pick an account to manage trusted MCP servers for"),
95-
matchOnDescription: true,
96-
}
97-
);
68+
//#region Account Query Resolution
9869

99-
if (pick) {
100-
providerId = pick.providerId;
101-
accountLabel = pick.label;
102-
} else {
103-
return { providerId: undefined, accountLabel: undefined };
104-
}
70+
private async _resolveAccountQuery(providerId: string | undefined, accountLabel: string | undefined): Promise<IAccountQuery | undefined> {
71+
if (providerId && accountLabel) {
72+
return this._authenticationQueryService.provider(providerId).account(accountLabel);
10573
}
106-
return { providerId, accountLabel };
74+
75+
const accounts = await this._getAllAvailableAccounts();
76+
const pick = await this._quickInputService.pick(accounts, {
77+
placeHolder: localize('pickAccount', "Pick an account to manage trusted MCP servers for"),
78+
matchOnDescription: true,
79+
});
80+
81+
return pick ? this._authenticationQueryService.provider(pick.providerId).account(pick.label) : undefined;
10782
}
10883

109-
private async _getItems(providerId: string, accountLabel: string) {
110-
let allowedMcpServers = this._mcpServerAuthenticationAccessService.readAllowedMcpServers(providerId, accountLabel);
111-
// only include MCP servers that are installed
112-
// TODO: improve?
113-
const resolvedMcpServers = await Promise.all(allowedMcpServers.map(server => this._mcpServerService.servers.get().find(s => s.definition.id === server.id)));
114-
allowedMcpServers = resolvedMcpServers
115-
.map((server, i) => server ? allowedMcpServers[i] : undefined)
116-
.filter(server => !!server);
117-
const trustedMcpServerAuthAccess = this._productService.trustedMcpAuthAccess;
118-
const trustedMcpServerIds =
119-
// Case 1: trustedMcpServerAuthAccess is an array
120-
Array.isArray(trustedMcpServerAuthAccess)
121-
? trustedMcpServerAuthAccess
122-
// Case 2: trustedMcpServerAuthAccess is an object
123-
: typeof trustedMcpServerAuthAccess === 'object'
124-
? trustedMcpServerAuthAccess[providerId] ?? []
125-
: [];
126-
for (const mcpServerId of trustedMcpServerIds) {
127-
const allowedMcpServer = allowedMcpServers.find(server => server.id === mcpServerId);
128-
if (!allowedMcpServer) {
129-
// Add the MCP server to the allowedMcpServers list
130-
// TODO: improve?
131-
const mcpServer = this._mcpServerService.servers.get().find(s => s.definition.id === mcpServerId);
132-
if (mcpServer) {
133-
allowedMcpServers.push({
134-
id: mcpServerId,
135-
name: mcpServer.definition.label,
136-
allowed: true,
137-
trusted: true
84+
private async _getAllAvailableAccounts() {
85+
const accounts = [];
86+
for (const providerId of this._mcpServerAuthenticationService.getProviderIds()) {
87+
const provider = this._mcpServerAuthenticationService.getProvider(providerId);
88+
const sessions = await this._mcpServerAuthenticationService.getSessions(providerId);
89+
const uniqueLabels = new Set<string>();
90+
91+
for (const session of sessions) {
92+
if (!uniqueLabels.has(session.account.label)) {
93+
uniqueLabels.add(session.account.label);
94+
accounts.push({
95+
providerId,
96+
label: session.account.label,
97+
description: provider.label
13898
});
13999
}
140-
} else {
141-
// Update the MCP server to be allowed
142-
allowedMcpServer.allowed = true;
143-
allowedMcpServer.trusted = true;
144100
}
145101
}
102+
return accounts;
103+
}
104+
105+
//#endregion
106+
107+
//#region Item Retrieval and Quick Pick Creation
108+
109+
private async _getItems(accountQuery: IAccountQuery) {
110+
const allowedMcpServers = accountQuery.mcpServers().getAllowedMcpServers();
111+
const serverIdToLabel = new Map<string, string>(this._mcpServerService.servers.get().map(s => [s.definition.id, s.definition.label]));
112+
const filteredMcpServers = allowedMcpServers
113+
// Filter out MCP servers that are not in the current list of servers
114+
.filter(server => serverIdToLabel.has(server.id))
115+
.map(server => {
116+
const usage = accountQuery.mcpServer(server.id).getUsage();
117+
return {
118+
...server,
119+
// Use the server name from the MCP service
120+
name: serverIdToLabel.get(server.id)!,
121+
lastUsed: usage.length > 0 ? Math.max(...usage.map(u => u.lastUsed)) : server.lastUsed
122+
};
123+
});
146124

147-
if (!allowedMcpServers.length) {
125+
if (!filteredMcpServers.length) {
148126
this._dialogService.info(localize('noTrustedMcpServers', "This account has not been used by any MCP servers."));
149127
return [];
150128
}
151129

152-
const usages = this._mcpServerAuthenticationUsageService.readAccountUsages(providerId, accountLabel);
153-
const trustedMcpServers = [];
154-
const otherMcpServers = [];
155-
for (const mcpServer of allowedMcpServers) {
156-
const usage = usages.find(usage => mcpServer.id === usage.mcpServerId);
157-
mcpServer.lastUsed = usage?.lastUsed;
158-
if (mcpServer.trusted) {
159-
trustedMcpServers.push(mcpServer);
160-
} else {
161-
otherMcpServers.push(mcpServer);
162-
}
163-
}
164-
130+
const trustedServers = filteredMcpServers.filter(s => s.trusted);
131+
const otherServers = filteredMcpServers.filter(s => !s.trusted);
165132
const sortByLastUsed = (a: AllowedMcpServer, b: AllowedMcpServer) => (b.lastUsed || 0) - (a.lastUsed || 0);
166133

167-
const items = [
168-
...otherMcpServers.sort(sortByLastUsed).map(this._toQuickPickItem),
134+
return [
135+
...otherServers.sort(sortByLastUsed).map(this._toQuickPickItem),
169136
{ type: 'separator', label: localize('trustedMcpServers', "Trusted by Microsoft") } satisfies IQuickPickSeparator,
170-
...trustedMcpServers.sort(sortByLastUsed).map(this._toQuickPickItem)
137+
...trustedServers.sort(sortByLastUsed).map(this._toQuickPickItem)
171138
];
172-
173-
return items;
174139
}
175140

176141
private _toQuickPickItem(mcpServer: AllowedMcpServer): TrustedMcpServersQuickPickItem {
@@ -198,38 +163,39 @@ class ManageTrustedMcpServersForAccountActionImpl {
198163
};
199164
}
200165

201-
private _createQuickPick(disposableStore: DisposableStore, providerId: string, accountLabel: string) {
166+
private _createQuickPick(accountQuery: IAccountQuery) {
167+
const disposableStore = new DisposableStore();
202168
const quickPick = disposableStore.add(this._quickInputService.createQuickPick<TrustedMcpServersQuickPickItem>({ useSeparators: true }));
169+
170+
// Configure quick pick
203171
quickPick.canSelectMany = true;
204172
quickPick.customButton = true;
205173
quickPick.customLabel = localize('manageTrustedMcpServers.cancel', 'Cancel');
206-
207174
quickPick.title = localize('manageTrustedMcpServers', "Manage Trusted MCP Servers");
208175
quickPick.placeholder = localize('manageMcpServers', "Choose which MCP servers can access this account");
209176

177+
// Set up event handlers
210178
disposableStore.add(quickPick.onDidAccept(() => {
211-
const updatedAllowedList = quickPick.items
212-
.filter((item): item is TrustedMcpServersQuickPickItem => item.type !== 'separator')
213-
.map(i => i.mcpServer);
214-
215-
const allowedMcpServersSet = new Set(quickPick.selectedItems.map(i => i.mcpServer));
216-
updatedAllowedList.forEach(mcpServer => {
217-
mcpServer.allowed = allowedMcpServersSet.has(mcpServer);
218-
});
219-
this._mcpServerAuthenticationAccessService.updateAllowedMcpServers(providerId, accountLabel, updatedAllowedList);
220179
quickPick.hide();
221-
}));
180+
const allServers = quickPick.items
181+
.filter((item: any): item is TrustedMcpServersQuickPickItem => item.type !== 'separator')
182+
.map((i: any) => i.mcpServer);
222183

223-
disposableStore.add(quickPick.onDidHide(() => {
224-
disposableStore.dispose();
225-
}));
184+
const selectedServers = new Set(quickPick.selectedItems.map((i: any) => i.mcpServer));
226185

227-
disposableStore.add(quickPick.onDidCustom(() => {
228-
quickPick.hide();
186+
for (const mcpServer of allServers) {
187+
const isAllowed = selectedServers.has(mcpServer);
188+
accountQuery.mcpServer(mcpServer.id).setAccessAllowed(isAllowed, mcpServer.name);
189+
}
229190
}));
230-
disposableStore.add(quickPick.onDidTriggerItemButton(e =>
231-
this._commandService.executeCommand('_manageAccountPreferencesForMcpServer', e.item.mcpServer.id, providerId)
191+
disposableStore.add(quickPick.onDidHide(() => disposableStore.dispose()));
192+
disposableStore.add(quickPick.onDidCustom(() => quickPick.hide()));
193+
disposableStore.add(quickPick.onDidTriggerItemButton((e: any) =>
194+
this._commandService.executeCommand('_manageAccountPreferencesForMcpServer', e.item.mcpServer.id, accountQuery.providerId)
232195
));
196+
233197
return quickPick;
234198
}
199+
200+
//#endregion
235201
}

src/vs/workbench/services/authentication/browser/authenticationMcpAccessService.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,34 @@ export class AuthenticationMcpAccessService extends Disposable implements IAuthe
8787
}
8888
} catch (err) { }
8989

90+
// Add trusted MCP servers from product.json if they're not already in the list
91+
const trustedMcpServerAuthAccess = this._productService.trustedMcpAuthAccess;
92+
const trustedMcpServerIds =
93+
// Case 1: trustedMcpServerAuthAccess is an array
94+
Array.isArray(trustedMcpServerAuthAccess)
95+
? trustedMcpServerAuthAccess
96+
// Case 2: trustedMcpServerAuthAccess is an object
97+
: typeof trustedMcpServerAuthAccess === 'object'
98+
? trustedMcpServerAuthAccess[providerId] ?? []
99+
: [];
100+
101+
for (const mcpServerId of trustedMcpServerIds) {
102+
const existingServer = trustedMCPServers.find(server => server.id === mcpServerId);
103+
if (!existingServer) {
104+
// Add new trusted server (name will be set by caller if they have server info)
105+
trustedMCPServers.push({
106+
id: mcpServerId,
107+
name: mcpServerId, // Default to ID, caller can update with proper name
108+
allowed: true,
109+
trusted: true
110+
});
111+
} else {
112+
// Update existing server to be trusted
113+
existingServer.allowed = true;
114+
existingServer.trusted = true;
115+
}
116+
}
117+
90118
return trustedMCPServers;
91119
}
92120

@@ -98,9 +126,16 @@ export class AuthenticationMcpAccessService extends Disposable implements IAuthe
98126
allowList.push(mcpServer);
99127
} else {
100128
allowList[index].allowed = mcpServer.allowed;
129+
// Update name if provided and not already set to a proper name
130+
if (mcpServer.name && mcpServer.name !== mcpServer.id && allowList[index].name !== mcpServer.name) {
131+
allowList[index].name = mcpServer.name;
132+
}
101133
}
102134
}
103-
this._storageService.store(`mcpserver-${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.APPLICATION, StorageTarget.USER);
135+
136+
// Filter out trusted servers before storing - they should only come from product.json, not user storage
137+
const userManagedServers = allowList.filter(server => !server.trusted);
138+
this._storageService.store(`mcpserver-${providerId}-${accountName}`, JSON.stringify(userManagedServers), StorageScope.APPLICATION, StorageTarget.USER);
104139
this._onDidChangeMcpSessionAccess.fire({ providerId, accountName });
105140
}
106141

src/vs/workbench/services/authentication/browser/authenticationQueryService.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,12 @@ class AccountMcpServerQuery extends BaseQuery implements IAccountMcpServerQuery
206206
const preferredAccount = this.queryService.authenticationMcpService.getAccountPreference(this.mcpServerId, this.providerId);
207207
return preferredAccount === this.accountName;
208208
}
209+
210+
isTrusted(): boolean {
211+
const allowedMcpServers = this.queryService.authenticationMcpAccessService.readAllowedMcpServers(this.providerId, this.accountName);
212+
const mcpServer = allowedMcpServers.find(server => server.id === this.mcpServerId);
213+
return mcpServer?.trusted === true;
214+
}
209215
}
210216

211217
/**
@@ -263,9 +269,9 @@ class AccountMcpServersQuery extends BaseQuery implements IAccountMcpServersQuer
263269
super(providerId, queryService);
264270
}
265271

266-
getAllowedMcpServerIds(): string[] {
267-
const allowedMcpServers = this.queryService.authenticationMcpAccessService.readAllowedMcpServers(this.providerId, this.accountName);
268-
return allowedMcpServers.filter(server => server.allowed !== false).map(server => server.id);
272+
getAllowedMcpServers(): { id: string; name: string; allowed?: boolean; lastUsed?: number; trusted?: boolean }[] {
273+
return this.queryService.authenticationMcpAccessService.readAllowedMcpServers(this.providerId, this.accountName)
274+
.filter(server => server.allowed !== false);
269275
}
270276

271277
allowAccess(mcpServerIds: string[]): void {

0 commit comments

Comments
 (0)