extension.ts•12.1 kB
import * as vscode from 'vscode';
import { MCPServer, ToolConfiguration } from './server';
import { listWorkspaceFiles } from './tools/file-tools';
import { logger } from './utils/logger';
// Re-export for testing purposes
export { MCPServer };
let mcpServer: MCPServer | undefined;
let statusBarItem: vscode.StatusBarItem | undefined;
let sharedTerminal: vscode.Terminal | undefined;
// Server state - disabled by default
let serverEnabled: boolean = false;
// Terminal name constant
const TERMINAL_NAME = 'MCP Shell Commands';
/**
* Gets the tool configuration from VS Code settings
* @returns ToolConfiguration object with all tool enablement settings
*/
function getToolConfiguration(): ToolConfiguration {
const config = vscode.workspace.getConfiguration('vscode-mcp-server');
const enabledTools = config.get<any>('enabledTools') || {};
return {
file: enabledTools.file ?? true,
edit: enabledTools.edit ?? true,
shell: enabledTools.shell ?? true,
diagnostics: enabledTools.diagnostics ?? true,
symbol: enabledTools.symbol ?? true
};
}
/**
* Gets or creates the shared terminal for the extension
* @param context The extension context
* @returns The shared terminal instance
*/
export function getExtensionTerminal(context: vscode.ExtensionContext): vscode.Terminal {
// Check if a terminal with our name already exists
const existingTerminal = vscode.window.terminals.find(t => t.name === TERMINAL_NAME);
if (existingTerminal && existingTerminal.exitStatus === undefined) {
// Reuse the existing terminal if it's still open
logger.info('[getExtensionTerminal] Reusing existing terminal for shell commands');
return existingTerminal;
}
// Create a new terminal if it doesn't exist or if it has exited
sharedTerminal = vscode.window.createTerminal(TERMINAL_NAME);
logger.info('[getExtensionTerminal] Created new terminal for shell commands');
context.subscriptions.push(sharedTerminal);
return sharedTerminal;
}
// Function to update status bar
function updateStatusBar(port: number) {
if (!statusBarItem) {
return;
}
if (serverEnabled) {
statusBarItem.text = `$(server) MCP Server: ${port}`;
statusBarItem.tooltip = `MCP Server running at localhost:${port} (Click to toggle)`;
statusBarItem.backgroundColor = undefined;
} else {
statusBarItem.text = `$(server) MCP Server: Off`;
statusBarItem.tooltip = `MCP Server is disabled (Click to toggle)`;
// Use a subtle color to indicate disabled state
statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground');
}
statusBarItem.show();
}
// Function to toggle server state
async function toggleServerState(context: vscode.ExtensionContext): Promise<void> {
logger.info(`[toggleServerState] Starting toggle operation - changing from ${serverEnabled} to ${!serverEnabled}`);
serverEnabled = !serverEnabled;
// Store state for persistence
context.globalState.update('mcpServerEnabled', serverEnabled);
const config = vscode.workspace.getConfiguration('vscode-mcp-server');
const port = config.get<number>('port') || 3000;
const host = config.get<string>('host') || '127.0.0.1';
// Update status bar immediately to provide feedback
updateStatusBar(port);
if (serverEnabled) {
// Start the server if it was disabled
if (!mcpServer) {
logger.info(`[toggleServerState] Creating MCP server instance`);
const terminal = getExtensionTerminal(context);
const toolConfig = getToolConfiguration();
mcpServer = new MCPServer(port, host, terminal, toolConfig);
mcpServer.setFileListingCallback(async (path: string, recursive: boolean) => {
try {
return await listWorkspaceFiles(path, recursive);
} catch (error) {
logger.error(`[toggleServerState] Error listing files: ${error instanceof Error ? error.message : String(error)}`);
throw error;
}
});
mcpServer.setupTools();
logger.info(`[toggleServerState] Starting server at ${new Date().toISOString()}`);
const startTime = Date.now();
await mcpServer.start();
const duration = Date.now() - startTime;
logger.info(`[toggleServerState] Server started successfully at ${new Date().toISOString()} (took ${duration}ms)`);
vscode.window.showInformationMessage(`MCP Server enabled and running at http://localhost:${port}/mcp`);
}
} else {
// Stop the server if it was enabled
if (mcpServer) {
// Show progress indicator
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: 'Stopping MCP Server',
cancellable: false
}, async (progress) => {
logger.info(`[toggleServerState] Stopping server at ${new Date().toISOString()}`);
progress.report({ message: 'Closing connections...' });
const stopTime = Date.now();
if (mcpServer) {
await mcpServer.stop();
}
const duration = Date.now() - stopTime;
logger.info(`[toggleServerState] Server stopped successfully at ${new Date().toISOString()} (took ${duration}ms)`);
mcpServer = undefined;
});
vscode.window.showInformationMessage('MCP Server has been disabled');
}
}
logger.info(`[toggleServerState] Toggle operation completed`);
}
export async function activate(context: vscode.ExtensionContext) {
logger.info('Activating vscode-mcp-server extension');
try {
// Get configuration
const config = vscode.workspace.getConfiguration('vscode-mcp-server');
const defaultEnabled = config.get<boolean>('defaultEnabled') ?? false;
const port = config.get<number>('port') || 3000;
const host = config.get<string>('host') || '127.0.0.1';
// Load saved state or use configured default
serverEnabled = context.globalState.get('mcpServerEnabled', defaultEnabled);
logger.info(`[activate] Using port ${port} from configuration`);
logger.info(`[activate] Server enabled: ${serverEnabled}`);
// Create status bar item
statusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right,
100
);
statusBarItem.command = 'vscode-mcp-server.toggleServer';
// Only start the server if enabled
if (serverEnabled) {
// Create the shared terminal
const terminal = getExtensionTerminal(context);
// Initialize MCP server with the configured port, terminal, and tool configuration
const toolConfig = getToolConfiguration();
mcpServer = new MCPServer(port, host, terminal, toolConfig);
// Set up file listing callback
mcpServer.setFileListingCallback(async (path: string, recursive: boolean) => {
try {
return await listWorkspaceFiles(path, recursive);
} catch (error) {
logger.error(`Error listing files: ${error instanceof Error ? error.message : String(error)}`);
throw error;
}
});
// Call setupTools after setting the callback
mcpServer.setupTools();
await mcpServer.start();
logger.info('MCP Server started successfully');
} else {
logger.info('MCP Server is disabled by default');
}
// Update status bar after server state is determined
updateStatusBar(port);
// Register commands
const toggleServerCommand = vscode.commands.registerCommand(
'vscode-mcp-server.toggleServer',
() => toggleServerState(context)
);
const showServerInfoCommand = vscode.commands.registerCommand(
'vscode-mcp-server.showServerInfo',
() => {
if (serverEnabled) {
vscode.window.showInformationMessage(`MCP Server is running at http://localhost:${port}/mcp`);
} else {
vscode.window.showInformationMessage('MCP Server is currently disabled. Click on the status bar item to enable it.');
}
}
);
// Listen for configuration changes to restart server if needed
const configChangeListener = vscode.workspace.onDidChangeConfiguration(async (event) => {
if (event.affectsConfiguration('vscode-mcp-server.enabledTools')) {
logger.info('[configChangeListener] Tool configuration changed - restarting server if enabled');
if (serverEnabled && mcpServer) {
// Stop current server
await mcpServer.stop();
mcpServer = undefined;
// Start new server with updated configuration
const config = vscode.workspace.getConfiguration('vscode-mcp-server');
const port = config.get<number>('port') || 3000;
const host = config.get<string>('host') || '127.0.0.1';
const terminal = getExtensionTerminal(context);
const toolConfig = getToolConfiguration();
mcpServer = new MCPServer(port, host, terminal, toolConfig);
mcpServer.setFileListingCallback(async (path: string, recursive: boolean) => {
try {
return await listWorkspaceFiles(path, recursive);
} catch (error) {
logger.error(`[configChangeListener] Error listing files: ${error instanceof Error ? error.message : String(error)}`);
throw error;
}
});
mcpServer.setupTools();
await mcpServer.start();
vscode.window.showInformationMessage('MCP Server restarted with updated tool configuration');
}
}
});
// Add all disposables to the context subscriptions
context.subscriptions.push(
statusBarItem,
toggleServerCommand,
showServerInfoCommand,
configChangeListener,
{ dispose: async () => mcpServer && await mcpServer.stop() }
);
} catch (error) {
logger.error(`Failed to start MCP Server: ${error instanceof Error ? error.message : 'Unknown error'}`);
vscode.window.showErrorMessage(`Failed to start MCP Server: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
export async function deactivate() {
if (statusBarItem) {
statusBarItem.dispose();
statusBarItem = undefined;
}
// Dispose the shared terminal
if (sharedTerminal) {
sharedTerminal.dispose();
sharedTerminal = undefined;
}
if (!mcpServer) {
return;
}
try {
logger.info('Stopping MCP Server during extension deactivation');
await mcpServer.stop();
logger.info('MCP Server stopped successfully');
} catch (error) {
logger.error(`Error stopping MCP Server: ${error instanceof Error ? error.message : String(error)}`);
throw error; // Re-throw to ensure VS Code knows about the failure
} finally {
mcpServer = undefined;
// Dispose the logger
logger.dispose();
}
}