Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 18 additions & 5 deletions src/steps/add-mcp-server-to-clients/MCPClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { merge } from 'lodash';
export abstract class MCPClient {
name: string;
abstract getConfigPath(): Promise<string>;
abstract getServerPropertyName(): string;
abstract isServerInstalled(): Promise<boolean>;
abstract addServer(apiKey: string): Promise<{ success: boolean }>;
abstract removeServer(): Promise<{ success: boolean }>;
Expand All @@ -18,6 +19,11 @@ export abstract class DefaultMCPClient extends MCPClient {
constructor() {
super();
}

getServerPropertyName(): string {
return 'mcpServers';
}

async isServerInstalled(): Promise<boolean> {
try {
const configPath = await this.getConfigPath();
Expand All @@ -28,8 +34,10 @@ export abstract class DefaultMCPClient extends MCPClient {

const configContent = await fs.promises.readFile(configPath, 'utf8');
const config = JSON.parse(configContent);

return 'mcpServers' in config && 'posthog' in config.mcpServers;
const serverPropertyName = this.getServerPropertyName();
return (
serverPropertyName in config && 'posthog' in config[serverPropertyName]
);
} catch {
return false;
}
Expand All @@ -49,8 +57,9 @@ export abstract class DefaultMCPClient extends MCPClient {

await fs.promises.mkdir(configDir, { recursive: true });

const serverPropertyName = this.getServerPropertyName();
const newServerConfig = {
mcpServers: {
[serverPropertyName]: {
posthog: getDefaultServerConfig(apiKey, type),
},
};
Expand Down Expand Up @@ -87,9 +96,13 @@ export abstract class DefaultMCPClient extends MCPClient {

const configContent = await fs.promises.readFile(configPath, 'utf8');
const config = JSON.parse(configContent);
const serverPropertyName = this.getServerPropertyName();

if ('mcpServers' in config && 'posthog' in config.mcpServers) {
delete config.mcpServers.posthog;
if (
serverPropertyName in config &&
'posthog' in config[serverPropertyName]
) {
delete config[serverPropertyName].posthog;

await fs.promises.writeFile(
configPath,
Expand Down
71 changes: 71 additions & 0 deletions src/steps/add-mcp-server-to-clients/clients/visual-studio-code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import z from 'zod';
import * as path from 'path';
import * as os from 'os';
import { DefaultMCPClient } from '../MCPClient';

export const VisualStudioCodeMCPConfig = z
.object({
servers: z.record(
z.string(),
z.object({
command: z.string().optional(),
args: z.array(z.string()).optional(),
env: z.record(z.string(), z.string()).optional(),
}),
),
})
.passthrough();

export type VisualStudioCodeMCPConfig = z.infer<
typeof VisualStudioCodeMCPConfig
>;

export class VisualStudioCodeClient extends DefaultMCPClient {
name = 'Visual Studio Code';

getServerPropertyName(): string {
return 'servers';
}

async isClientSupported(): Promise<boolean> {
return Promise.resolve(
process.platform === 'darwin' ||
process.platform === 'win32' ||
process.platform === 'linux',
);
}

async getConfigPath(): Promise<string> {
const homeDir = os.homedir();
const isWindows = process.platform === 'win32';
const isMac = process.platform === 'darwin';
const isLinux = process.platform === 'linux';

if (isMac) {
return Promise.resolve(
path.join(
homeDir,
'Library',
'Application Support',
'Code',
'User',
'mcp.json',
),
);
}

if (isWindows) {
return Promise.resolve(
path.join(process.env.APPDATA || '', 'Code', 'User', 'mcp.json'),
);
}

if (isLinux) {
return Promise.resolve(
path.join(homeDir, '.config', 'Code', 'User', 'mcp.json'),
);
}

throw new Error(`Unsupported platform: ${process.platform}`);
}
}
4 changes: 2 additions & 2 deletions src/steps/add-mcp-server-to-clients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import { ClaudeMCPClient } from './clients/claude';
import { getPersonalApiKey } from '../../mcp';
import type { CloudRegion } from '../../utils/types';
import { ClaudeCodeMCPClient } from './clients/claude-code';
import { VisualStudioCodeClient } from './clients/visual-studio-code';

export const getSupportedClients = async (): Promise<MCPClient[]> => {
const allClients = [
new CursorMCPClient(),
new ClaudeMCPClient(),
new ClaudeCodeMCPClient(),
new VisualStudioCodeClient(),
];
const supportedClients: MCPClient[] = [];

Expand Down Expand Up @@ -146,7 +148,6 @@ export const removeMCPServerFromClientsStep = async ({
integration?: Integration;
}): Promise<string[]> => {
const installedClients = await getInstalledClients();

if (installedClients.length === 0) {
analytics.capture('wizard interaction', {
action: 'no mcp servers to remove',
Expand Down Expand Up @@ -198,7 +199,6 @@ export const removeMCPServerFromClientsStep = async ({

export const getInstalledClients = async (): Promise<MCPClient[]> => {
const clients = await getSupportedClients();

const installedClients: MCPClient[] = [];

for (const client of clients) {
Expand Down
Loading