Skip to content

Commit 5db3062

Browse files
committed
Adds provideMcpServerDefinitions
1 parent 308d1f3 commit 5db3062

File tree

7 files changed

+135
-66
lines changed

7 files changed

+135
-66
lines changed

src/container.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -233,11 +233,6 @@ export class Container {
233233
this._disposables.push((this._statusBarController = new StatusBarController(this)));
234234
this._disposables.push((this._codeLensController = new GitCodeLensController(this)));
235235

236-
const mcpProvider = getMcpProvider(this);
237-
if (mcpProvider != null) {
238-
this._disposables.push(mcpProvider);
239-
}
240-
241236
const webviews = new WebviewsController(this);
242237
this._disposables.push(webviews);
243238
this._disposables.push((this._views = new Views(this, webviews)));
@@ -279,6 +274,11 @@ export class Container {
279274
this._disposables.push(cliIntegration);
280275
}
281276

277+
const mcpProvider = getMcpProvider(this);
278+
if (mcpProvider != null) {
279+
this._disposables.push(mcpProvider);
280+
}
281+
282282
this._disposables.push(
283283
configuration.onDidChange(e => {
284284
if (configuration.changed(e, 'terminalLinks.enabled')) {

src/env/browser/providers.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,7 @@ export function getSupportedWorkspacesStorageProvider(
4242
export function getGkCliIntegrationProvider(_container: Container): undefined {
4343
return undefined;
4444
}
45+
46+
export function getMcpProvider(_container: Container): undefined {
47+
return undefined;
48+
}

src/env/node/gk/cli/integration.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { getLogScope } from '../../../../system/logger.scope';
1515
import { compare } from '../../../../system/version';
1616
import { run } from '../../git/shell';
1717
import { getPlatform, isWeb } from '../../platform';
18+
import { toMcpInstallProvider } from '../mcp/utils';
1819
import { CliCommandHandlers } from './commands';
1920
import type { IpcServer } from './ipcServer';
2021
import { createIpcServer } from './ipcServer';
@@ -731,16 +732,3 @@ class CLIInstallError extends Error {
731732
return message;
732733
}
733734
}
734-
735-
function toMcpInstallProvider(appHostName: string | undefined): string | undefined {
736-
switch (appHostName) {
737-
case 'code':
738-
return 'vscode';
739-
case 'code-insiders':
740-
return 'vscode-insiders';
741-
case 'code-exploration':
742-
return 'vscode-exploration';
743-
default:
744-
return appHostName;
745-
}
746-
}

src/env/node/gk/mcp.ts

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/env/node/gk/mcp/integration.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import type { Event, McpServerDefinition } from 'vscode';
2+
import {
3+
version as codeVersion,
4+
Disposable,
5+
env,
6+
EventEmitter,
7+
lm,
8+
McpStdioServerDefinition,
9+
Uri,
10+
window,
11+
} from 'vscode';
12+
import type { Container } from '../../../../container';
13+
import type { StorageChangeEvent } from '../../../../system/-webview/storage';
14+
import { getHostAppName } from '../../../../system/-webview/vscode';
15+
import { debounce } from '../../../../system/function/debounce';
16+
import { satisfies } from '../../../../system/version';
17+
import { getPlatform } from '../../platform';
18+
import { toMcpInstallProvider } from './utils';
19+
20+
export class McpProvider implements Disposable {
21+
static #instance: McpProvider | undefined;
22+
23+
static create(container: Container): McpProvider | undefined {
24+
if (!satisfies(codeVersion, '>= 1.101.0') || !lm.registerMcpServerDefinitionProvider) return undefined;
25+
26+
if (this.#instance == null) {
27+
this.#instance = new McpProvider(container);
28+
}
29+
30+
return this.#instance;
31+
}
32+
33+
private readonly _disposable: Disposable;
34+
private readonly _onDidChangeMcpServerDefinitions = new EventEmitter<void>();
35+
get onDidChangeMcpServerDefinitions(): Event<void> {
36+
return this._onDidChangeMcpServerDefinitions.event;
37+
}
38+
39+
private constructor(private readonly container: Container) {
40+
this._disposable = Disposable.from(
41+
this.container.storage.onDidChange(e => this.checkStorage(e)),
42+
lm.registerMcpServerDefinitionProvider('gitlens.mcpProvider', {
43+
onDidChangeMcpServerDefinitions: this._onDidChangeMcpServerDefinitions.event,
44+
provideMcpServerDefinitions: () => this.provideMcpServerDefinitions(),
45+
}),
46+
);
47+
48+
this.checkStorage();
49+
}
50+
51+
private checkStorage(e?: StorageChangeEvent): void {
52+
if (e != null && !(e.keys as string[]).includes('gk:cli:install')) return;
53+
this._onDidChangeMcpServerDefinitions.fire();
54+
}
55+
56+
private async provideMcpServerDefinitions(): Promise<McpServerDefinition[]> {
57+
const config = await this.getMcpConfiguration();
58+
if (config == null) {
59+
return [];
60+
}
61+
62+
const serverDefinition = new McpStdioServerDefinition(
63+
config.name,
64+
config.command,
65+
config.args,
66+
{},
67+
config.version,
68+
);
69+
70+
this.notifyServerProvided();
71+
72+
return [serverDefinition];
73+
}
74+
75+
private async getMcpConfiguration(): Promise<
76+
{ name: string; type: string; command: string; args: string[]; version?: string } | undefined
77+
> {
78+
const cliInstall = this.container.storage.get('gk:cli:install');
79+
const cliPath = this.container.storage.get('gk:cli:path');
80+
81+
if (cliInstall?.status !== 'completed' || !cliPath) {
82+
return undefined;
83+
}
84+
85+
const platform = getPlatform();
86+
const executable = platform === 'windows' ? 'gk.exe' : 'gk';
87+
const command = Uri.joinPath(Uri.file(cliPath), executable);
88+
89+
const appName = toMcpInstallProvider(await getHostAppName());
90+
const args = ['mcp', `--host=${appName}`, '--source=gitlens', `--scheme=${env.uriScheme}`];
91+
return {
92+
name: 'GitKraken MCP Server',
93+
type: 'stdio',
94+
command: command.fsPath,
95+
args: args,
96+
version: cliInstall.version,
97+
};
98+
}
99+
100+
dispose(): void {
101+
this._disposable.dispose();
102+
this._onDidChangeMcpServerDefinitions.dispose();
103+
}
104+
105+
private _notifyServerProvided = false;
106+
private notifyServerProvided = debounce(() => {
107+
if (this._notifyServerProvided) return;
108+
109+
void window.showInformationMessage('GitLens can now automatically configure the GitKraken MCP server for you');
110+
this._notifyServerProvided = true;
111+
}, 250);
112+
}

src/env/node/gk/mcp/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export function toMcpInstallProvider(appHostName: string | undefined): string | undefined {
2+
switch (appHostName) {
3+
case 'code':
4+
return 'vscode';
5+
case 'code-insiders':
6+
return 'vscode-insiders';
7+
case 'code-exploration':
8+
return 'vscode-exploration';
9+
default:
10+
return appHostName;
11+
}
12+
}

src/env/node/providers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { GkCliIntegrationProvider } from './gk/cli/integration';
1414
import { LocalRepositoryLocationProvider } from './gk/localRepositoryLocationProvider';
1515
import { LocalSharedGkStorageLocationProvider } from './gk/localSharedGkStorageLocationProvider';
1616
import { LocalGkWorkspacesSharedStorageProvider } from './gk/localWorkspacesSharedStorageProvider';
17-
import { McpProvider } from './gk/mcp';
17+
import { McpProvider } from './gk/mcp/integration';
1818

1919
let gitInstance: Git | undefined;
2020
function ensureGit(container: Container) {

0 commit comments

Comments
 (0)