/**
* MCP Configuration Utilities
*/
import type {
MCPConfig,
MCPServer,
InstallMethod,
MCPClient,
} from '../types/index.js';
import type { MCPRegistryEntry } from '../configs/mcp-registry.js';
import { isWindows } from './platform.js';
// Re-exports for backward compatibility
export {
getMCPConfigPath,
clientConfigExists,
MCP_CLIENTS,
} from './mcp-paths.js';
export { readMCPConfig, writeMCPConfig } from './mcp-io.js';
/**
* Environment options for octocode server configuration
*/
export interface OctocodeEnvOptions {
enableLocal?: boolean;
githubToken?: string;
}
/**
* Get octocode MCP server configuration for a given install method
*/
export function getOctocodeServerConfig(
method: InstallMethod,
envOptions?: OctocodeEnvOptions
): MCPServer {
let config: MCPServer;
switch (method) {
case 'direct':
config = {
command: 'bash',
args: [
'-c',
'curl -sL https://octocodeai.com/octocode/latest/index.js -o /tmp/index.js && node /tmp/index.js',
],
};
break;
case 'npx':
config = {
command: 'npx',
args: ['octocode-mcp@latest'],
};
break;
default:
throw new Error(`Unknown install method: ${method}`);
}
// Add env options if provided
if (envOptions) {
const env: Record<string, string> = {};
if (envOptions.enableLocal) {
env.ENABLE_LOCAL = 'true';
}
if (envOptions.githubToken) {
env.GITHUB_TOKEN = envOptions.githubToken;
}
if (Object.keys(env).length > 0) {
config.env = env;
}
}
return config;
}
/**
* Get Windows-compatible octocode config for direct method
*/
export function getOctocodeServerConfigWindows(
method: InstallMethod,
envOptions?: OctocodeEnvOptions
): MCPServer {
if (method === 'direct') {
// Windows doesn't have bash/curl by default, use PowerShell
const config: MCPServer = {
command: 'powershell',
args: [
'-Command',
"Invoke-WebRequest -Uri 'https://octocodeai.com/octocode/latest/index.js' -OutFile $env:TEMP\\index.js; node $env:TEMP\\index.js",
],
};
// Add env options if provided
if (envOptions) {
const env: Record<string, string> = {};
if (envOptions.enableLocal) {
env.ENABLE_LOCAL = 'true';
}
if (envOptions.githubToken) {
env.GITHUB_TOKEN = envOptions.githubToken;
}
if (Object.keys(env).length > 0) {
config.env = env;
}
}
return config;
}
// npx works the same on Windows
return getOctocodeServerConfig(method, envOptions);
}
/**
* Add or update octocode in MCP config
*/
export function mergeOctocodeConfig(
config: MCPConfig,
method: InstallMethod,
envOptions?: OctocodeEnvOptions
): MCPConfig {
const serverConfig = isWindows
? getOctocodeServerConfigWindows(method, envOptions)
: getOctocodeServerConfig(method, envOptions);
return {
...config,
mcpServers: {
...config.mcpServers,
octocode: serverConfig,
},
};
}
/**
* Check if octocode is already configured
*/
export function isOctocodeConfigured(config: MCPConfig): boolean {
return Boolean(config.mcpServers?.octocode);
}
/**
* Get currently configured octocode method
*/
export function getConfiguredMethod(config: MCPConfig): InstallMethod | null {
const octocode = config.mcpServers?.octocode;
if (!octocode) return null;
if (octocode.command === 'npx') return 'npx';
if (octocode.command === 'bash' || octocode.command === 'powershell') {
return 'direct';
}
return null;
}
// ============================================================================
// Client Installation Status
// ============================================================================
import { getMCPConfigPath, configFileExists } from './mcp-paths.js';
import { readMCPConfig } from './mcp-io.js';
export interface ClientInstallStatus {
client: MCPClient;
configExists: boolean;
octocodeInstalled: boolean;
method: InstallMethod | null;
configPath: string;
}
/**
* Check if octocode is installed for a specific client
*/
export function getClientInstallStatus(
client: MCPClient,
customPath?: string
): ClientInstallStatus {
const configPath = getMCPConfigPath(client, customPath);
const configExists = configFileExists(client, customPath);
let octocodeInstalled = false;
let method: InstallMethod | null = null;
if (configExists) {
const config = readMCPConfig(configPath);
if (config) {
octocodeInstalled = isOctocodeConfigured(config);
method = getConfiguredMethod(config);
}
}
return {
client,
configExists,
octocodeInstalled,
method,
configPath,
};
}
/**
* Get installation status for all available clients
*/
export function getAllClientInstallStatus(): ClientInstallStatus[] {
const clients: MCPClient[] = [
'cursor',
'claude-desktop',
'claude-code',
'opencode',
'vscode-cline',
'vscode-roo',
'vscode-continue',
'windsurf',
'trae',
'antigravity',
'zed',
];
return clients.map(client => getClientInstallStatus(client));
}
/**
* Find clients that already have octocode installed
*/
export function findInstalledClients(): ClientInstallStatus[] {
return getAllClientInstallStatus().filter(status => status.octocodeInstalled);
}
// ============================================================================
// External MCP Server Configuration (from Registry)
// ============================================================================
/**
* Convert a registry entry to an MCP server config
* Replaces ${VAR_NAME} placeholders with actual environment variable values
*/
export function registryEntryToServerConfig(
entry: MCPRegistryEntry,
envValues?: Record<string, string>
): MCPServer {
const config: MCPServer = {
command: entry.installConfig.command,
args: [...entry.installConfig.args],
};
// Replace placeholders in args with actual values
if (envValues && config.args) {
config.args = config.args.map(arg => {
// Replace ${VAR_NAME} patterns
return arg.replace(/\$\{(\w+)\}/g, (_, varName) => {
return envValues[varName] || `\${${varName}}`;
});
});
}
// Merge env from install config and provided values
const env: Record<string, string> = {};
// Add env from installConfig
if (entry.installConfig.env) {
Object.assign(env, entry.installConfig.env);
}
// Add required env vars from provided values
if (envValues) {
for (const [key, value] of Object.entries(envValues)) {
if (value) {
env[key] = value;
}
}
}
if (Object.keys(env).length > 0) {
config.env = env;
}
return config;
}
/**
* Add or update an external MCP server in the config
*/
export function mergeExternalMCPConfig(
config: MCPConfig,
entry: MCPRegistryEntry,
envValues?: Record<string, string>
): MCPConfig {
const serverConfig = registryEntryToServerConfig(entry, envValues);
return {
...config,
mcpServers: {
...config.mcpServers,
[entry.id]: serverConfig,
},
};
}
/**
* Check if an external MCP server is already configured
*/
export function isExternalMCPConfigured(
config: MCPConfig,
entryId: string
): boolean {
return Boolean(config.mcpServers?.[entryId]);
}
/**
* Remove an external MCP server from the config
*/
export function removeExternalMCPConfig(
config: MCPConfig,
entryId: string
): MCPConfig {
if (!config.mcpServers?.[entryId]) {
return config;
}
const remainingServers = Object.fromEntries(
Object.entries(config.mcpServers).filter(([key]) => key !== entryId)
);
return {
...config,
mcpServers: remainingServers,
};
}
/**
* Get list of installed external MCPs from config
*/
export function getInstalledExternalMCPs(
config: MCPConfig,
registry: MCPRegistryEntry[]
): MCPRegistryEntry[] {
if (!config.mcpServers) return [];
const installedIds = new Set(Object.keys(config.mcpServers));
return registry.filter(entry => installedIds.has(entry.id));
}
/**
* Validate that all required env vars have values
*/
export function validateRequiredEnvVars(
entry: MCPRegistryEntry,
envValues: Record<string, string>
): { valid: boolean; missing: string[] } {
const missing: string[] = [];
if (entry.requiredEnvVars) {
for (const envVar of entry.requiredEnvVars) {
if (!envValues[envVar.name]) {
missing.push(envVar.name);
}
}
}
return {
valid: missing.length === 0,
missing,
};
}