Skip to content

Commit

Permalink
feat: implement client preference storage and detection
Browse files Browse the repository at this point in the history
- Add Preferences class for managing client selection
- Use config files to detect installed clients
- Store and persist client preferences
- Auto-select single client installations
- Fix ClientType definition and usage

Co-Authored-By: Michael Latman <[email protected]>
  • Loading branch information
devin-ai-integration[bot] and michaellatman committed Dec 11, 2024
1 parent 3c89def commit c977796
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 21 deletions.
4 changes: 3 additions & 1 deletion src/commands/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ function createUnknownPackage(packageName: string, runtime: 'node' | 'python'):
vendor: '',
sourceUrl: '',
homepage: '',
license: ''
license: '',
supportedClients: ['claude', 'zed', 'continue', 'firebase'],
supportedTransports: ['stdio']
};
}

Expand Down
7 changes: 1 addition & 6 deletions src/types/client-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@
/**
* Supported MCP client types
*/
export enum ClientType {
CLAUDE = 'claude',
ZED = 'zed',
CONTINUE = 'continue',
FIREBASE = 'firebase'
}
export type ClientType = 'claude' | 'zed' | 'continue' | 'firebase';

/**
* Server configuration interface
Expand Down
38 changes: 24 additions & 14 deletions src/utils/config-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,58 @@ import { ClaudeAdapter } from '../clients/claude-adapter';
import { ZedAdapter } from '../clients/zed-adapter';
import { ContinueAdapter } from '../clients/continue-adapter';
import { FirebaseAdapter } from '../clients/firebase-adapter';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { Preferences } from './preferences';

export class ConfigManager {
private clients: Map<ClientType, ClientAdapter>;
private preferences: Preferences;

constructor() {
this.clients = new Map();
this.preferences = new Preferences();
this.initializeClients();
}

private initializeClients(): void {
this.clients.set(ClientType.CLAUDE, new ClaudeAdapter({ type: ClientType.CLAUDE }));
this.clients.set(ClientType.ZED, new ZedAdapter({ type: ClientType.ZED }));
this.clients.set(ClientType.CONTINUE, new ContinueAdapter({ type: ClientType.CONTINUE }));
this.clients.set(ClientType.FIREBASE, new FirebaseAdapter({ type: ClientType.FIREBASE }));
this.clients.set('claude', new ClaudeAdapter({ type: 'claude' }));
this.clients.set('zed', new ZedAdapter({ type: 'zed' }));
this.clients.set('continue', new ContinueAdapter({ type: 'continue' }));
this.clients.set('firebase', new FirebaseAdapter({ type: 'firebase' }));
}

async getInstalledClients(): Promise<ClientType[]> {
const installed: ClientType[] = [];
for (const [clientType, adapter] of this.clients.entries()) {
if (await adapter.isInstalled()) {
installed.push(clientType);
}
}
return installed;
return this.preferences.detectInstalledClients();
}

async selectClients(): Promise<ClientType[]> {
const defaultClients = await this.preferences.getDefaultClients();
if (defaultClients.length > 0) {
return defaultClients;
}

const installed = await this.getInstalledClients();
if (installed.length === 0) {
throw new Error('No supported MCP clients found. Please install a supported client first.');
}

// For single client, automatically select it
if (installed.length === 1) {
await this.preferences.setDefaultClients(installed);
return installed;
}

// Multiple clients - selection will be handled by the install command
return installed;
}

async configureClients(serverConfig: ServerConfig, selectedClients?: ClientType[]): Promise<void> {
const clients = selectedClients || await this.selectClients();

// Store selected clients as defaults for future installations
if (selectedClients) {
await this.preferences.setDefaultClients(selectedClients);
}

for (const clientType of clients) {
const adapter = this.clients.get(clientType);
if (adapter && await adapter.validateConfig(serverConfig)) {
Expand Down
133 changes: 133 additions & 0 deletions src/utils/preferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { homedir } from 'os';
import { join } from 'path';
import { existsSync } from 'fs';
import { readFile, writeFile, mkdir } from 'fs/promises';

export type ClientType = 'claude' | 'zed' | 'continue' | 'firebase';

export class Preferences {
private configDir: string;
private preferencesFile: string;

constructor() {
this.configDir = join(homedir(), '.config', 'mcp-get');
this.preferencesFile = join(this.configDir, 'preferences.json');
}

private async ensureConfigDir(): Promise<void> {
if (!existsSync(this.configDir)) {
await mkdir(this.configDir, { recursive: true });
}
}

private getClaudeConfigPath(): string {
if (process.platform === 'win32') {
return join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json');
}
return join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
}

private getZedConfigPath(): string {
if (process.platform === 'win32') {
return join(process.env.APPDATA || '', 'Zed', 'settings.json');
}
return join(homedir(), '.config', 'zed', 'settings.json');
}

private getContinueConfigPath(): string {
if (process.platform === 'win32') {
return join(process.env.APPDATA || '', 'Continue', 'config.json');
}
return join(homedir(), '.config', 'continue', 'config.json');
}

private getFirebaseConfigPath(): string {
if (process.platform === 'win32') {
return join(process.env.APPDATA || '', 'Firebase', 'genkit', 'config.json');
}
return join(homedir(), '.config', 'firebase', 'genkit', 'config.json');
}

async detectInstalledClients(): Promise<ClientType[]> {
const installedClients: ClientType[] = [];

if (existsSync(this.getClaudeConfigPath())) {
installedClients.push('claude');
}
if (existsSync(this.getZedConfigPath())) {
installedClients.push('zed');
}
if (existsSync(this.getContinueConfigPath())) {
installedClients.push('continue');
}
if (existsSync(this.getFirebaseConfigPath())) {
installedClients.push('firebase');
}

return installedClients;
}

async getDefaultClients(): Promise<ClientType[]> {
try {
await this.ensureConfigDir();

if (!existsSync(this.preferencesFile)) {
const installedClients = await this.detectInstalledClients();
if (installedClients.length > 0) {
await this.setDefaultClients(installedClients);
return installedClients;
}
return [];
}

const data = await readFile(this.preferencesFile, 'utf-8');
const prefs = JSON.parse(data);
return prefs.defaultClients || [];
} catch (error) {
console.error('Error reading preferences:', error);
return [];
}
}

async setDefaultClients(clients: ClientType[]): Promise<void> {
try {
await this.ensureConfigDir();

const data = JSON.stringify({
defaultClients: clients
}, null, 2);

await writeFile(this.preferencesFile, data, 'utf-8');
} catch (error) {
console.error('Error saving preferences:', error);
throw error;
}
}

async shouldPromptForClientSelection(): Promise<boolean> {
const installedClients = await this.detectInstalledClients();
return installedClients.length > 1;
}

async getOrSelectDefaultClients(): Promise<ClientType[]> {
const installedClients = await this.detectInstalledClients();

if (installedClients.length === 0) {
throw new Error('No supported MCP clients detected. Please install at least one supported client.');
}

if (installedClients.length === 1) {
await this.setDefaultClients(installedClients);
return installedClients;
}

const defaultClients = await this.getDefaultClients();
if (defaultClients.length > 0) {
return defaultClients;
}

// If no defaults are set but multiple clients are installed,
// the caller should handle prompting the user for selection
return [];
}
}

0 comments on commit c977796

Please sign in to comment.