Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/githubMcp/common/githubMcpDefinitionProvider.ts
13399 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 * as l10n from '@vscode/l10n';
7
import type { CancellationToken, McpHttpServerDefinition, McpServerDefinitionProvider } from 'vscode';
8
import { authProviderId, IAuthenticationService } from '../../../platform/authentication/common/authentication';
9
import { AuthProviderId, ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
10
import { ILogService } from '../../../platform/log/common/logService';
11
import { Event } from '../../../util/vs/base/common/event';
12
import { URI } from '../../../util/vs/base/common/uri';
13
14
const EnterpriseURLConfig = 'github-enterprise.uri';
15
16
export class GitHubMcpDefinitionProvider implements McpServerDefinitionProvider<McpHttpServerDefinition> {
17
18
readonly onDidChangeMcpServerDefinitions: Event<void>;
19
20
private _askedForAuth = false;
21
22
constructor(
23
@IConfigurationService private readonly configurationService: IConfigurationService,
24
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
25
@ILogService private readonly logService: ILogService
26
) {
27
const configurationEvent = Event.chain(configurationService.onDidChangeConfiguration, $ => $
28
.filter(e => {
29
// If they change the toolsets
30
if (e.affectsConfiguration(ConfigKey.GitHubMcpToolsets.fullyQualifiedId)) {
31
logService.debug('GitHubMcpDefinitionProvider: Configuration change affects GitHub MCP toolsets.');
32
return true;
33
}
34
// If they change readonly mode
35
if (e.affectsConfiguration(ConfigKey.GitHubMcpReadonly.fullyQualifiedId)) {
36
logService.debug('GitHubMcpDefinitionProvider: Configuration change affects GitHub MCP readonly mode.');
37
return true;
38
}
39
// If they change lockdown mode
40
if (e.affectsConfiguration(ConfigKey.GitHubMcpLockdown.fullyQualifiedId)) {
41
logService.debug('GitHubMcpDefinitionProvider: Configuration change affects GitHub MCP lockdown mode.');
42
return true;
43
}
44
// If they change the channel
45
if (e.affectsConfiguration(ConfigKey.GitHubMcpChannel.fullyQualifiedId)) {
46
logService.debug('GitHubMcpDefinitionProvider: Configuration change affects GitHub MCP channel.');
47
return true;
48
}
49
// If they change to GHE or GitHub.com
50
if (e.affectsConfiguration(ConfigKey.Shared.AuthProvider.fullyQualifiedId)) {
51
logService.debug('GitHubMcpDefinitionProvider: Configuration change affects GitHub auth provider.');
52
return true;
53
}
54
// If they change the GHE URL
55
if (e.affectsConfiguration(EnterpriseURLConfig)) {
56
logService.debug('GitHubMcpDefinitionProvider: Configuration change affects GitHub Enterprise URL.');
57
return true;
58
}
59
return false;
60
})
61
// void event
62
.map(() => { })
63
);
64
let havePermissiveToken = !!this.authenticationService.permissiveGitHubSession;
65
const authEvent = Event.chain(this.authenticationService.onDidAuthenticationChange, $ => $
66
.filter(() => {
67
const hadToken = havePermissiveToken;
68
havePermissiveToken = !!this.authenticationService.permissiveGitHubSession;
69
return hadToken !== havePermissiveToken;
70
})
71
.map(() => {
72
this.logService.debug(`GitHubMcpDefinitionProvider: Permissive GitHub session availability changed: ${havePermissiveToken}`);
73
})
74
);
75
this.onDidChangeMcpServerDefinitions = Event.any(configurationEvent, authEvent);
76
}
77
78
private get toolsets(): string[] {
79
return this.configurationService.getConfig<string[]>(ConfigKey.GitHubMcpToolsets);
80
}
81
82
private get readonly(): boolean {
83
return this.configurationService.getConfig<boolean>(ConfigKey.GitHubMcpReadonly);
84
}
85
86
private get lockdown(): boolean {
87
return this.configurationService.getConfig<boolean>(ConfigKey.GitHubMcpLockdown);
88
}
89
90
private get channel(): ConfigKey.GitHubMcpChannelValue {
91
return this.configurationService.getConfig<ConfigKey.GitHubMcpChannelValue>(ConfigKey.GitHubMcpChannel);
92
}
93
94
private get gheConfig(): string | undefined {
95
return this.configurationService.getNonExtensionConfig<string>(EnterpriseURLConfig);
96
}
97
98
private getGheUri(): URI {
99
const uri = this.gheConfig;
100
if (!uri) {
101
throw new Error('GitHub Enterprise URI is not configured.');
102
}
103
// Prefix with 'copilot-api.'
104
const url = URI.parse(uri).with({ path: '/mcp/' });
105
return url.with({ authority: `copilot-api.${url.authority}` });
106
}
107
108
provideMcpServerDefinitions(): McpHttpServerDefinition[] {
109
const providerId = authProviderId(this.configurationService);
110
const toolsets = this.toolsets.sort().join(',');
111
const readonly = this.readonly;
112
const lockdown = this.lockdown;
113
const channel = this.channel;
114
const isSignedIn = !!this.authenticationService.permissiveGitHubSession;
115
116
const basics = providerId === AuthProviderId.GitHubEnterprise
117
? { label: 'GitHub Enterprise', uri: this.getGheUri() }
118
: { label: 'GitHub', uri: URI.parse('https://api.githubcopilot.com/mcp/') };
119
120
// Build headers object conditionally
121
const headers: Record<string, string> = {};
122
// Build version string with toolsets and flags
123
let version: string;
124
if (isSignedIn) {
125
version = toolsets.length ? toolsets : '0';
126
if (toolsets.length > 0) {
127
headers['X-MCP-Toolsets'] = toolsets;
128
}
129
if (readonly) {
130
headers['X-MCP-Readonly'] = 'true';
131
version += '|readonly';
132
}
133
if (lockdown) {
134
headers['X-MCP-Lockdown'] = 'true';
135
version += '|lockdown';
136
}
137
if (channel === 'insiders') {
138
headers['X-MCP-Insiders'] = 'true';
139
version += '|insiders';
140
}
141
} else {
142
version = 'signedout';
143
}
144
return [
145
{
146
...basics,
147
headers,
148
version
149
}
150
];
151
}
152
153
async resolveMcpServerDefinition(server: McpHttpServerDefinition, token: CancellationToken): Promise<McpHttpServerDefinition> {
154
const accessToken = this.authenticationService.permissiveGitHubSession?.accessToken;
155
if (accessToken) {
156
server.headers['Authorization'] = `Bearer ${accessToken}`;
157
return server;
158
}
159
160
if (this._askedForAuth) {
161
throw new Error('User denied authentication. Cannot connect to GitHub MCP Server.');
162
}
163
164
try {
165
const session = await this.authenticationService.getGitHubSession('permissive', {
166
createIfNone: {
167
detail: l10n.t('Additional permissions are required to use GitHub MCP Server'),
168
},
169
});
170
server.headers['Authorization'] = `Bearer ${session.accessToken}`;
171
return server;
172
} finally {
173
this._askedForAuth = true;
174
}
175
}
176
}
177
178