-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathextension.ts
More file actions
204 lines (187 loc) · 7.85 KB
/
extension.ts
File metadata and controls
204 lines (187 loc) · 7.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
import * as vscode from 'vscode';
import { Logger } from './common/logger';
import { CliResolver } from './codeql/cli-resolver';
import { ServerManager } from './server/server-manager';
import { PackInstaller } from './server/pack-installer';
import { McpProvider } from './server/mcp-provider';
import { StoragePaths } from './bridge/storage-paths';
import { DatabaseWatcher } from './bridge/database-watcher';
import { QueryResultsWatcher } from './bridge/query-results-watcher';
import { EnvironmentBuilder } from './bridge/environment-builder';
/** API surface returned from activate() for testing and interop. */
export interface ExtensionApi {
readonly mcpProvider: McpProvider;
}
const disposables: vscode.Disposable[] = [];
export async function activate(
context: vscode.ExtensionContext,
): Promise<ExtensionApi> {
const logger = new Logger();
disposables.push(logger);
logger.info('CodeQL Development MCP Server extension activating...');
// --- Core components ---
const storagePaths = new StoragePaths(context);
const cliResolver = new CliResolver(logger, storagePaths.getCodeqlGlobalStoragePath());
const serverManager = new ServerManager(context, logger);
const packInstaller = new PackInstaller(cliResolver, serverManager, logger);
const envBuilder = new EnvironmentBuilder(
context,
cliResolver,
storagePaths,
logger,
);
const mcpProvider = new McpProvider(serverManager, envBuilder, logger);
disposables.push(cliResolver, serverManager, packInstaller, storagePaths, envBuilder, mcpProvider);
// --- Bridge: filesystem watchers ---
const config = vscode.workspace.getConfiguration('codeql-mcp');
const watchEnabled = config.get<boolean>('watchCodeqlExtension', true);
if (watchEnabled) {
try {
const dbWatcher = new DatabaseWatcher(storagePaths, logger);
const queryWatcher = new QueryResultsWatcher(storagePaths, logger);
disposables.push(dbWatcher, queryWatcher);
// File-content changes (new databases, query results) do NOT require
// a new MCP server definition. The running server discovers files on
// its own through filesystem scanning at tool invocation time. The
// definition only needs to change when the server binary, workspace
// folder registration, or configuration changes.
//
// The watchers are still useful: they log file events for debugging
// and DatabaseWatcher tracks known databases internally.
} catch (err) {
logger.warn(
`Failed to initialize file watchers: ${err instanceof Error ? err.message : String(err)}`,
);
}
}
// --- VS Code API event subscriptions ---
// Re-probe CLI when extensions change (e.g. vscode-codeql installed/updated)
context.subscriptions.push(
vscode.extensions.onDidChange(() => {
logger.info('Extensions changed — invalidating CLI resolver cache');
cliResolver.invalidateCache();
envBuilder.invalidate();
mcpProvider.fireDidChange();
}),
);
// Re-compute env when config changes
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration((e) => {
if (e.affectsConfiguration('codeql-mcp')) {
logger.info('Configuration changed — requesting MCP server restart');
mcpProvider.requestRestart();
}
}),
);
// Invalidate cached environment when workspace folders change.
// VS Code itself manages MCP server lifecycle when roots change
// (stopping and restarting the server as needed). We just clear
// the cached env so the next server start picks up updated folders.
context.subscriptions.push(
vscode.workspace.onDidChangeWorkspaceFolders(() => {
logger.info('Workspace folders changed — invalidating environment cache');
envBuilder.invalidate();
}),
);
// --- Register MCP server definition provider ---
logger.info('Registering ql-mcp MCP server definition provider...');
try {
context.subscriptions.push(
vscode.lm.registerMcpServerDefinitionProvider('ql-mcp', mcpProvider),
);
logger.info(
'ql-mcp registered. The server will start when Copilot needs it, ' +
'or start it manually via the MCP servers list.',
);
} catch (err) {
logger.warn(
`Failed to register MCP server definition provider: ${err instanceof Error ? err.message : String(err)}. ` +
'MCP server definitions will not be available. This may happen on older VS Code versions.',
);
}
// --- Register commands ---
context.subscriptions.push(
vscode.commands.registerCommand('codeql-mcp.reinstallServer', async () => {
logger.info('Reinstalling MCP server (user command)...');
logger.show();
await vscode.window.withProgress(
{ location: vscode.ProgressLocation.Notification, title: 'CodeQL MCP: Reinstalling server...' },
async () => {
await serverManager.install({ force: true });
await packInstaller.installAll();
mcpProvider.fireDidChange();
},
);
vscode.window.showInformationMessage('CodeQL MCP Server reinstalled successfully.');
}),
vscode.commands.registerCommand('codeql-mcp.reinstallPacks', async () => {
logger.info('Reinstalling CodeQL tool query packs (user command)...');
logger.show();
await vscode.window.withProgress(
{ location: vscode.ProgressLocation.Notification, title: 'CodeQL MCP: Installing query packs...' },
async () => {
await packInstaller.installAll({ force: true });
mcpProvider.fireDidChange();
},
);
vscode.window.showInformationMessage('CodeQL tool query packs reinstalled successfully.');
}),
vscode.commands.registerCommand('codeql-mcp.showStatus', async () => {
const cliPath = await cliResolver.resolve();
const version = await serverManager.getInstalledVersion();
const lines = [
`Launch: ${serverManager.getDescription()}`,
`Local install: ${version ?? 'not yet installed'}`,
`CodeQL CLI: ${cliPath ?? 'not found'}`,
`vscode-codeql storage: ${storagePaths.getCodeqlGlobalStoragePath()}`,
`Query results: ${storagePaths.getQueryStoragePath()}`,
];
logger.info('--- Status ---');
for (const line of lines) {
logger.info(line);
}
logger.show();
vscode.window.showInformationMessage(lines.join(' | '));
}),
vscode.commands.registerCommand('codeql-mcp.showLogs', () => {
logger.show();
}),
);
// --- Auto-install on activation ---
const autoInstall = config.get<boolean>('autoInstall', true);
if (autoInstall) {
logger.info('Auto-install enabled — starting background setup...');
logger.info(`Install directory: ${serverManager.getInstallDir?.() ?? 'unknown'}`);
logger.info(`Server launch: ${serverManager.getDescription?.() ?? 'unknown'}`);
const autoDownloadPacks = config.get<boolean>('autoDownloadPacks', true);
// Run in background — don't block activation
void (async () => {
try {
await serverManager.ensureInstalled();
await packInstaller.installAll({ downloadForCliVersion: autoDownloadPacks });
mcpProvider.fireDidChange();
logger.info('✅ MCP server setup complete. Server is ready to be started.');
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
logger.error(`❌ Auto-install failed: ${msg}`);
vscode.window.showErrorMessage(
`CodeQL MCP setup failed: ${msg}. See "CodeQL MCP" output channel.`,
);
}
})();
} else {
logger.info('Auto-install disabled via codeql-mcp.autoInstall setting.');
}
logger.info('CodeQL Development MCP Server extension activated.');
return { environmentBuilder: envBuilder, mcpProvider, serverManager };
}
export function deactivate(): void {
for (const d of disposables) {
try {
d.dispose();
} catch {
// Best-effort cleanup
}
}
disposables.length = 0;
}