Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/authentication/browser/authenticationMcpAccessService.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 { Emitter, Event } from '../../../../base/common/event.js';
7
import { Disposable } from '../../../../base/common/lifecycle.js';
8
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
9
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
10
import { IProductService } from '../../../../platform/product/common/productService.js';
11
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
12
13
export interface AllowedMcpServer {
14
id: string;
15
name: string;
16
/**
17
* If true or undefined, the extension is allowed to use the account
18
* If false, the extension is not allowed to use the account
19
* TODO: undefined shouldn't be a valid value, but it is for now
20
*/
21
allowed?: boolean;
22
lastUsed?: number;
23
// If true, this comes from the product.json
24
trusted?: boolean;
25
}
26
27
export const IAuthenticationMcpAccessService = createDecorator<IAuthenticationMcpAccessService>('IAuthenticationMcpAccessService');
28
export interface IAuthenticationMcpAccessService {
29
readonly _serviceBrand: undefined;
30
31
readonly onDidChangeMcpSessionAccess: Event<{ providerId: string; accountName: string }>;
32
33
/**
34
* Check MCP server access to an account
35
* @param providerId The id of the authentication provider
36
* @param accountName The account name that access is checked for
37
* @param mcpServerId The id of the MCP server requesting access
38
* @returns Returns true or false if the user has opted to permanently grant or disallow access, and undefined
39
* if they haven't made a choice yet
40
*/
41
isAccessAllowed(providerId: string, accountName: string, mcpServerId: string): boolean | undefined;
42
readAllowedMcpServers(providerId: string, accountName: string): AllowedMcpServer[];
43
updateAllowedMcpServers(providerId: string, accountName: string, mcpServers: AllowedMcpServer[]): void;
44
removeAllowedMcpServers(providerId: string, accountName: string): void;
45
}
46
47
// TODO@TylerLeonhardt: Should this class only keep track of allowed things and throw away disallowed ones?
48
export class AuthenticationMcpAccessService extends Disposable implements IAuthenticationMcpAccessService {
49
_serviceBrand: undefined;
50
51
private _onDidChangeMcpSessionAccess: Emitter<{ providerId: string; accountName: string }> = this._register(new Emitter<{ providerId: string; accountName: string }>());
52
readonly onDidChangeMcpSessionAccess: Event<{ providerId: string; accountName: string }> = this._onDidChangeMcpSessionAccess.event;
53
54
constructor(
55
@IStorageService private readonly _storageService: IStorageService,
56
@IProductService private readonly _productService: IProductService
57
) {
58
super();
59
}
60
61
isAccessAllowed(providerId: string, accountName: string, mcpServerId: string): boolean | undefined {
62
const trustedMCPServerAuthAccess = this._productService.trustedMcpAuthAccess;
63
if (Array.isArray(trustedMCPServerAuthAccess)) {
64
if (trustedMCPServerAuthAccess.includes(mcpServerId)) {
65
return true;
66
}
67
} else if (trustedMCPServerAuthAccess?.[providerId]?.includes(mcpServerId)) {
68
return true;
69
}
70
71
const allowList = this.readAllowedMcpServers(providerId, accountName);
72
const mcpServerData = allowList.find(mcpServer => mcpServer.id === mcpServerId);
73
if (!mcpServerData) {
74
return undefined;
75
}
76
// This property didn't exist on this data previously, inclusion in the list at all indicates allowance
77
return mcpServerData.allowed !== undefined
78
? mcpServerData.allowed
79
: true;
80
}
81
82
readAllowedMcpServers(providerId: string, accountName: string): AllowedMcpServer[] {
83
let trustedMCPServers: AllowedMcpServer[] = [];
84
try {
85
const trustedMCPServerSrc = this._storageService.get(`mcpserver-${providerId}-${accountName}`, StorageScope.APPLICATION);
86
if (trustedMCPServerSrc) {
87
trustedMCPServers = JSON.parse(trustedMCPServerSrc);
88
}
89
} catch (err) { }
90
91
// Add trusted MCP servers from product.json if they're not already in the list
92
const trustedMcpServerAuthAccess = this._productService.trustedMcpAuthAccess;
93
const trustedMcpServerIds =
94
// Case 1: trustedMcpServerAuthAccess is an array
95
Array.isArray(trustedMcpServerAuthAccess)
96
? trustedMcpServerAuthAccess
97
// Case 2: trustedMcpServerAuthAccess is an object
98
: typeof trustedMcpServerAuthAccess === 'object'
99
? trustedMcpServerAuthAccess[providerId] ?? []
100
: [];
101
102
for (const mcpServerId of trustedMcpServerIds) {
103
const existingServer = trustedMCPServers.find(server => server.id === mcpServerId);
104
if (!existingServer) {
105
// Add new trusted server (name will be set by caller if they have server info)
106
trustedMCPServers.push({
107
id: mcpServerId,
108
name: mcpServerId, // Default to ID, caller can update with proper name
109
allowed: true,
110
trusted: true
111
});
112
} else {
113
// Update existing server to be trusted
114
existingServer.allowed = true;
115
existingServer.trusted = true;
116
}
117
}
118
119
return trustedMCPServers;
120
}
121
122
updateAllowedMcpServers(providerId: string, accountName: string, mcpServers: AllowedMcpServer[]): void {
123
const allowList = this.readAllowedMcpServers(providerId, accountName);
124
for (const mcpServer of mcpServers) {
125
const index = allowList.findIndex(e => e.id === mcpServer.id);
126
if (index === -1) {
127
allowList.push(mcpServer);
128
} else {
129
allowList[index].allowed = mcpServer.allowed;
130
// Update name if provided and not already set to a proper name
131
if (mcpServer.name && mcpServer.name !== mcpServer.id && allowList[index].name !== mcpServer.name) {
132
allowList[index].name = mcpServer.name;
133
}
134
}
135
}
136
137
// Filter out trusted servers before storing - they should only come from product.json, not user storage
138
const userManagedServers = allowList.filter(server => !server.trusted);
139
this._storageService.store(`mcpserver-${providerId}-${accountName}`, JSON.stringify(userManagedServers), StorageScope.APPLICATION, StorageTarget.USER);
140
this._onDidChangeMcpSessionAccess.fire({ providerId, accountName });
141
}
142
143
removeAllowedMcpServers(providerId: string, accountName: string): void {
144
this._storageService.remove(`mcpserver-${providerId}-${accountName}`, StorageScope.APPLICATION);
145
this._onDidChangeMcpSessionAccess.fire({ providerId, accountName });
146
}
147
}
148
149
registerSingleton(IAuthenticationMcpAccessService, AuthenticationMcpAccessService, InstantiationType.Delayed);
150
151