Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
f0f3975
feat(mcp-server-integration): TF-28514: Added: mcp server definition …
anubhav-goel Aug 21, 2025
cd85748
feat(mcp-server-integration): TF-28514: Added: implementation for MCP…
anubhav-goel Aug 21, 2025
c659506
feat(mcp-server-integration): TF-28514: Added: add MCP server provide…
anubhav-goel Aug 21, 2025
40a64ea
feat(mcp-server-integration): TF-28514: Refactored: mcp server label …
anubhav-goel Aug 21, 2025
80ee594
feat(mcp-server-integration): TF-28514: Modified: checking if mcp api…
anubhav-goel Aug 21, 2025
5bddf8a
feat(mcp-server-integration): TF-28514: Modified: checking if docker …
anubhav-goel Aug 21, 2025
d03a6e7
feat(mcp-server-integration): TF-28514: Modified: checking if docker …
anubhav-goel Aug 21, 2025
9e7c9a0
feat(mcp-server-integration): TF-28514: Added: mcp server docker inst…
anubhav-goel Aug 21, 2025
b574df2
feat(mcp-server-integration): TF-28514: Added: mcp server docker not …
anubhav-goel Aug 21, 2025
20c32c0
feat(mcp-server-integration): TF-28514: Added: unregister the mcp ser…
anubhav-goel Aug 21, 2025
a72e09b
feat(mcp-server-integration): TF-28514: Refactored: removed comment
anubhav-goel Aug 21, 2025
c75856a
feat(mcp-server-integration): TF-28514: Added: mcp server info messag…
anubhav-goel Aug 21, 2025
60a5301
feat(mcp-server-integration): TF-28514: Added: changelog for mcp serv…
anubhav-goel Aug 21, 2025
762be2c
feat(mcp-server-integration): TF-28514: Modified: show mcp server inf…
anubhav-goel Aug 21, 2025
d2c9ad9
feat(mcp-server-integration): TF-28514: Removed: disposables from mcp…
anubhav-goel Aug 25, 2025
a0feb2e
feat(mcp-server-integration): TF-28514: Modified: added mcp error log…
anubhav-goel Aug 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/unreleased/ENHANCEMENTS-20250821-185355.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: ENHANCEMENTS
body: Terraform MCP Server deployment using Hashicorp VS Code extension
time: 2025-08-21T18:53:55.326683+05:30
custom:
Issue: "2086"
Repository: vscode-terraform
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@
"main": "./dist/extension",
"browser": "./dist/web/extension",
"contributes": {
"mcpServerDefinitionProviders": [
{
"id": "terraform.mcp.server",
"label": "HashiCorp Terraform MCP Server"
}
],
"languages": [
{
"id": "terraform",
Expand Down
4 changes: 4 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { TerraformCommands } from './commands/terraform';
import * as lsStatus from './status/language';
import { TerraformCloudFeature } from './features/terraformCloud';
import { setupMockServer, stopMockServer } from './test/e2e/specs/mocks/server';
import { McpServerFeature } from './features/mcpServer';

const id = 'terraform';
const brand = `HashiCorp Terraform`;
Expand Down Expand Up @@ -70,6 +71,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>

context.subscriptions.push(new TerraformCloudFeature(context, reporter, tfcOutputChannel));

// Register MCP server feature
context.subscriptions.push(new McpServerFeature(context, reporter, outputChannel));

if (config('terraform').get<boolean>('languageServer.enable') === false) {
reporter.sendTelemetryEvent('disabledTerraformLS');
return;
Expand Down
173 changes: 173 additions & 0 deletions src/features/mcpServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import TelemetryReporter from '@vscode/extension-telemetry';
import { exec } from 'child_process';
import { promisify } from 'util';
import * as vscode from 'vscode';
import which from 'which';

const execAsync = promisify(exec);

interface McpServerDefinition {
label: string;
command: string;
args: string[];
env: Record<string, string>;
}

export class McpServerFeature {
constructor(
private context: vscode.ExtensionContext,
private reporter: TelemetryReporter,
private outputChannel: vscode.OutputChannel,
) {
this.activate();
}

/**
* Helper method to format and log error information
*/
private logError(message: string, error: unknown): void {
this.outputChannel.appendLine(`${message}: ${String(error)}`);
if (error instanceof Error && error.stack) {
this.outputChannel.appendLine(`Stack trace: ${error.stack}`);
}
}

public activate(): void {
try {
if (!this.isMcpApiAvailable()) {
return;
}

const provider = this.registerMcpServerProvider();
if (provider) {
this.context.subscriptions.push(provider);
}
} catch (error) {
this.logError('Failed to register MCP server definition provider', error);
// Don't throw - let the extension continue to work without MCP server
}
}

private isMcpApiAvailable(): boolean {
// Check if VS Code has the MCP API available
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return typeof (vscode as any).lm?.registerMcpServerDefinitionProvider === 'function';
}

private registerMcpServerProvider(): vscode.Disposable | undefined {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const vscodeAny = vscode as any;
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return vscodeAny.lm.registerMcpServerDefinitionProvider('terraform.mcp.server', {
provideMcpServerDefinitions: () => {
return this.provideMcpServerDefinitions();
},
});
} catch (error) {
this.logError('Error registering MCP server provider', error);
return undefined;
}
}

private async provideMcpServerDefinitions(): Promise<McpServerDefinition[]> {
try {
const dockerAvailable = await this.dockerValidations();
if (!dockerAvailable) {
return [];
}

const server: McpServerDefinition = {
label: 'HashiCorp Terraform MCP Server',
command: 'docker',
args: ['run', '-i', '--rm', 'hashicorp/terraform-mcp-server'],
env: {},
};

this.showMcpServerInfoMessage();

return [server];
} catch (error) {
this.logError('Error providing MCP server definitions', error);
return [];
}
}
private async dockerValidations(): Promise<boolean> {
try {
if (!(await this.checkDockerAvailability())) {
return false;
}

if (!(await this.checkDockerRunning())) {
return false;
}

return true;
} catch (error) {
this.logError('Docker validation error', error);
return false;
}
}

private async checkDockerAvailability(): Promise<boolean> {
try {
await which('docker');
return true;
} catch {
void vscode.window
.showWarningMessage(
'Docker is required to run the Terraform MCP Server. Please install Docker to use this feature.',
'Learn More',
)
.then((selection) => {
if (selection === 'Learn More') {
void vscode.env.openExternal(vscode.Uri.parse('https://docs.docker.com/get-docker/'));
}
});
return false;
}
}

private async checkDockerRunning(): Promise<boolean> {
try {
await execAsync('docker info', { timeout: 5000 });
return true;
} catch (error) {
this.logError('Docker daemon check failed', error);
void vscode.window
.showWarningMessage(
'Docker is installed but not running. Please start Docker to use the Terraform MCP Server.',
'Learn More',
)
.then((selection) => {
if (selection === 'Learn More') {
void vscode.env.openExternal(vscode.Uri.parse('https://docs.docker.com/get-started/'));
}
});
return false;
}
}

private showMcpServerInfoMessage(): void {
const message = 'Terraform MCP Server is now available for GitHub Copilot integration.';
const startAction = 'Start MCP Server';
const learnMoreAction = 'Learn More';

void vscode.window.showInformationMessage(message, startAction, learnMoreAction).then((selection) => {
if (selection === startAction) {
void vscode.commands.executeCommand('workbench.action.quickOpen', '>MCP: List Servers');
} else if (selection === learnMoreAction) {
void vscode.env.openExternal(vscode.Uri.parse('https://github.com/hashicorp/terraform-mcp-server'));
}
});
}

dispose(): void {
// context.subscriptions will be disposed by the extension, so any explicit code should not be required.
}
}
Loading